zencode-cli 0.2.2 → 0.4.1

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.
@@ -56,6 +56,7 @@ var init_client = __esm({
56
56
  params.tools = tools;
57
57
  params.tool_choice = "auto";
58
58
  }
59
+ params.stream_options = { include_usage: true };
59
60
  const abortController = new AbortController();
60
61
  this.activeAbortController = abortController;
61
62
  try {
@@ -67,7 +68,16 @@ var init_client = __esm({
67
68
  let reasoningStarted = false;
68
69
  let reasoningEnded = false;
69
70
  const toolCallMap = /* @__PURE__ */ new Map();
71
+ let usageInfo = null;
70
72
  for await (const chunk of stream) {
73
+ const chunkUsage = chunk.usage;
74
+ if (chunkUsage && chunkUsage.total_tokens) {
75
+ usageInfo = {
76
+ prompt_tokens: chunkUsage.prompt_tokens ?? 0,
77
+ completion_tokens: chunkUsage.completion_tokens ?? 0,
78
+ total_tokens: chunkUsage.total_tokens
79
+ };
80
+ }
71
81
  const choice = chunk.choices[0];
72
82
  if (!choice) continue;
73
83
  const delta = choice.delta;
@@ -140,6 +150,9 @@ var init_client = __esm({
140
150
  if (toolCalls.length > 0) {
141
151
  assistantMessage.tool_calls = toolCalls;
142
152
  }
153
+ if (usageInfo) {
154
+ assistantMessage.usage = usageInfo;
155
+ }
143
156
  callbacks.onFinish?.(assistantMessage);
144
157
  return assistantMessage;
145
158
  } catch (error) {
@@ -193,6 +206,13 @@ var init_client = __esm({
193
206
  }
194
207
  }));
195
208
  }
209
+ if (response.usage) {
210
+ result.usage = {
211
+ prompt_tokens: response.usage.prompt_tokens,
212
+ completion_tokens: response.usage.completion_tokens,
213
+ total_tokens: response.usage.total_tokens
214
+ };
215
+ }
196
216
  return result;
197
217
  }
198
218
  get modelName() {
@@ -797,7 +817,16 @@ function formatToolDetail(toolName, params) {
797
817
  }
798
818
  async function confirmExecution(toolName, params) {
799
819
  if (structuredConfirmHandler) {
800
- return structuredConfirmHandler(toolName, params);
820
+ const result = await new Promise((resolve10) => {
821
+ pendingConfirmResolver = resolve10;
822
+ structuredConfirmHandler(toolName, params).then((res) => {
823
+ if (pendingConfirmResolver === resolve10) {
824
+ pendingConfirmResolver = null;
825
+ resolve10(res);
826
+ }
827
+ });
828
+ });
829
+ return result;
801
830
  }
802
831
  const detail = formatToolDetail(toolName, params);
803
832
  const prompt = `
@@ -808,12 +837,13 @@ ${detail}
808
837
  const approved = await globalConfirmHandler(`${chalk.yellow("?")} \u662F\u5426\u6267\u884C\uFF1F(${chalk.green("y")}/${chalk.red("N")}) `);
809
838
  return { approved };
810
839
  }
811
- var globalConfirmHandler, structuredConfirmHandler;
840
+ var globalConfirmHandler, structuredConfirmHandler, pendingConfirmResolver;
812
841
  var init_permission = __esm({
813
842
  "src/tools/permission.ts"() {
814
843
  "use strict";
815
844
  globalConfirmHandler = async () => false;
816
845
  structuredConfirmHandler = null;
846
+ pendingConfirmResolver = null;
817
847
  }
818
848
  });
819
849
 
@@ -970,21 +1000,32 @@ var init_agent = __esm({
970
1000
  tools.length > 0 ? tools : void 0,
971
1001
  callbacks
972
1002
  );
1003
+ const validToolCalls = [];
1004
+ const invalidToolCalls = [];
1005
+ if (assistantMsg.tool_calls) {
1006
+ for (const toolCall of assistantMsg.tool_calls) {
1007
+ try {
1008
+ JSON.parse(toolCall.function.arguments);
1009
+ validToolCalls.push(toolCall);
1010
+ } catch {
1011
+ invalidToolCalls.push(toolCall);
1012
+ }
1013
+ }
1014
+ }
1015
+ assistantMsg.tool_calls = validToolCalls.length > 0 ? validToolCalls : void 0;
973
1016
  this.conversation.addAssistantMessage(assistantMsg);
974
- if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0) {
1017
+ for (const toolCall of invalidToolCalls) {
1018
+ callbacks.onToolResult?.(toolCall.function.name, `\u53C2\u6570\u89E3\u6790\u5931\u8D25\uFF1A\u65E0\u6548\u7684 JSON \u5B57\u7B26\u4E32
1019
+ ${toolCall.function.arguments}`, false);
1020
+ }
1021
+ if (validToolCalls.length === 0) {
975
1022
  lastContent = assistantMsg.content || "";
976
1023
  break;
977
1024
  }
978
- for (const toolCall of assistantMsg.tool_calls) {
1025
+ for (const toolCall of validToolCalls) {
979
1026
  if (this.interrupted) break;
980
1027
  const toolName = toolCall.function.name;
981
- let params;
982
- try {
983
- params = JSON.parse(toolCall.function.arguments);
984
- } catch {
985
- this.conversation.addToolResult(toolCall.id, "\u53C2\u6570\u89E3\u6790\u5931\u8D25\uFF1A\u65E0\u6548\u7684 JSON");
986
- continue;
987
- }
1028
+ const params = JSON.parse(toolCall.function.arguments);
988
1029
  try {
989
1030
  if (toolName === "edit-file") {
990
1031
  const editPath = params["path"];
@@ -1090,6 +1131,8 @@ function buildCorePrompt() {
1090
1131
  - \u4E0D\u8981\u6DFB\u52A0\u7528\u6237\u672A\u8981\u6C42\u7684\u6CE8\u91CA\u3001\u6587\u6863\u3001\u7C7B\u578B\u6CE8\u89E3
1091
1132
  - \u4E0D\u8981\u5F15\u5165\u5B89\u5168\u6F0F\u6D1E\uFF08\u6CE8\u5165\u3001XSS\u3001SQL \u6CE8\u5165\u7B49 OWASP Top 10\uFF09
1092
1133
  - \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
1134
+ - \u7EDD\u5BF9\u7981\u6B62\u4F7F\u7528 \`// ... existing code\`\u3001\`// TODO\` \u6216\u4EFB\u4F55\u5F62\u5F0F\u7684\u7701\u7565\u5360\u4F4D\u7B26\u3002\u5FC5\u987B\u63D0\u4F9B\u5B8C\u6574\u4E14\u53EF\u8FD0\u884C\u7684\u4EE3\u7801\u3002
1135
+ - \u6C38\u8FDC\u4E0D\u8981\u5047\u8BBE\u67D0\u4E2A\u7B2C\u4E09\u65B9\u5E93\u5DF2\u5B89\u88C5\u3002\u5728\u4F7F\u7528\u4EFB\u4F55\u975E Node.js \u5185\u7F6E\u6A21\u5757\u524D\uFF0C\u5FC5\u987B\u786E\u8BA4\u5176\u5728\u9879\u76EE\u4E2D\u5DF2\u914D\u7F6E\u3002
1093
1136
 
1094
1137
  # \u4EA4\u4E92\u98CE\u683C
1095
1138
 
@@ -1122,7 +1165,8 @@ function buildPlanningPrompt() {
1122
1165
 
1123
1166
  \u4EE3\u7801\u8D28\u91CF\uFF1A
1124
1167
  - \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`;
1168
+ - \u4E0D\u8981\u7559\u4E0BTODO\u7136\u540E\u653E\u7740\u4E0D\u7BA1
1169
+ - \u6267\u884C\u4FEE\u6539\u540E\u5FC5\u987B\u8FDB\u884C\u95ED\u73AF\u9A8C\u8BC1\u3002\u4E3B\u52A8\u68C0\u67E5\u4EE3\u7801\u903B\u8F91\uFF0C\u6216\u4F7F\u7528 bash \u8FD0\u884C\u6784\u5EFA\u3001Lint \u7B49\u547D\u4EE4\u786E\u4FDD\u4FEE\u6539\u6CA1\u6709\u5F15\u5165\u9519\u8BEF`;
1126
1170
  }
1127
1171
  var init_planning = __esm({
1128
1172
  "src/core/prompt/layers/planning.ts"() {
@@ -1141,6 +1185,7 @@ function buildParallelPrompt() {
1141
1185
  - \u9700\u8981\u641C\u7D22 2+ \u4E2A\u6A21\u5F0F \u2192 spawn-agents \u5E76\u884C\u641C\u7D22
1142
1186
  - \u9700\u8981\u4E86\u89E3 2+ \u4E2A\u6A21\u5757 \u2192 spawn-agents \u5E76\u884C\u5206\u6790
1143
1187
  - \u53EA\u6709 1 \u4E2A\u76EE\u6807 \u2192 \u76F4\u63A5\u7528\u5DE5\u5177\uFF0C\u65E0\u9700 spawn-agents
1188
+ - \u5FC5\u987B\u4E3A\u6BCF\u4E2A\u5B50 Agent \u5206\u914D\u660E\u786E\u4E14\u65E0\u91CD\u53E0\u7684\u76EE\u6807\uFF0C\u907F\u514D\u591A\u4E2A Agent \u91CD\u590D\u5904\u7406\u540C\u4E00\u5BF9\u8C61\u9020\u6210\u6D6A\u8D39
1144
1189
 
1145
1190
  \u793A\u4F8B - \u7528\u6237\u8BF4"\u5E2E\u6211\u7406\u89E3\u8BA4\u8BC1\u6A21\u5757"\uFF1A
1146
1191
  \u6B63\u786E\uFF1Aspawn-agents \u540C\u65F6\u8BFB auth controller\u3001auth service\u3001auth middleware\u3001auth types
@@ -1162,7 +1207,8 @@ function buildGitPrompt() {
1162
1207
 
1163
1208
  \u63D0\u4EA4\u89C4\u8303\uFF1A
1164
1209
  - \u53EA\u5728\u7528\u6237\u660E\u786E\u8981\u6C42\u65F6\u624D\u521B\u5EFA commit
1165
- - \u7528 git diff \u67E5\u770B\u53D8\u66F4\uFF0C\u518D\u5199 commit message
1210
+ - \u63D0\u4EA4\u524D\u5FC5\u987B\u7528 git status \u548C git diff --staged \u68C0\u67E5\u5373\u5C06\u63D0\u4EA4\u7684\u5185\u5BB9\uFF0C\u786E\u4FDD\u6CA1\u6709\u6DF7\u5165\u4E0D\u76F8\u5173\u6587\u4EF6\u6216\u8C03\u8BD5\u4EE3\u7801
1211
+ - \u5EFA\u8BAE\u9075\u5FAA Conventional Commits \u89C4\u8303\uFF08\u5982 feat: ..., fix: ...\uFF09
1166
1212
  - commit message \u63CF\u8FF0"\u4E3A\u4EC0\u4E48"\u800C\u975E"\u6539\u4E86\u4EC0\u4E48"
1167
1213
 
1168
1214
  \u5B89\u5168\u89C4\u5219\uFF1A
@@ -1600,7 +1646,8 @@ var init_sub_agent = __esm({
1600
1646
  allowedTools;
1601
1647
  maxTurns;
1602
1648
  timeoutMs;
1603
- constructor(client, registry, config, task, allowedTools = ["read-file", "glob", "grep"], maxTurns = 10, timeoutMs = DEFAULT_TIMEOUT_MS) {
1649
+ tracker;
1650
+ constructor(client, registry, config, task, allowedTools = ["read-file", "glob", "grep"], maxTurns = 10, timeoutMs = DEFAULT_TIMEOUT_MS, tracker) {
1604
1651
  this.client = client;
1605
1652
  this.registry = registry;
1606
1653
  this.config = config;
@@ -1608,6 +1655,7 @@ var init_sub_agent = __esm({
1608
1655
  this.allowedTools = allowedTools.filter((t) => t !== "spawn-agents" && t !== "todo");
1609
1656
  this.maxTurns = Math.min(maxTurns, 15);
1610
1657
  this.timeoutMs = timeoutMs;
1658
+ this.tracker = tracker;
1611
1659
  }
1612
1660
  async run() {
1613
1661
  return Promise.race([
@@ -1637,6 +1685,9 @@ var init_sub_agent = __esm({
1637
1685
  conversation.getMessages(),
1638
1686
  tools.length > 0 ? tools : void 0
1639
1687
  );
1688
+ if (assistantMsg.usage && this.tracker) {
1689
+ this.tracker.addTokens(assistantMsg.usage.total_tokens);
1690
+ }
1640
1691
  conversation.addAssistantMessage(assistantMsg);
1641
1692
  if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0) {
1642
1693
  lastContent = assistantMsg.content || "";
@@ -1760,7 +1811,7 @@ function createSpawnAgentsTool(client, registry, config, tracker) {
1760
1811
  if (tools.length === 0) {
1761
1812
  tools = DEFAULT_TOOLS.filter((t) => autoTools.includes(t));
1762
1813
  }
1763
- return new SubAgent(client, registry, config, task.description, tools, maxTurns);
1814
+ return new SubAgent(client, registry, config, task.description, tools, maxTurns, void 0, tracker);
1764
1815
  });
1765
1816
  const wrappedRuns = agents.map(
1766
1817
  (agent) => agent.run().then(
@@ -1929,11 +1980,13 @@ var init_runner = __esm({
1929
1980
  registry;
1930
1981
  config;
1931
1982
  agentConfig;
1932
- constructor(client, registry, config, agentConfig) {
1983
+ tracker;
1984
+ constructor(client, registry, config, agentConfig, tracker) {
1933
1985
  this.client = client;
1934
1986
  this.registry = registry;
1935
1987
  this.config = config;
1936
1988
  this.agentConfig = agentConfig;
1989
+ this.tracker = tracker;
1937
1990
  }
1938
1991
  /**
1939
1992
  * 执行子 Agent 任务
@@ -1974,6 +2027,9 @@ ${context}`;
1974
2027
  tools.length > 0 ? tools : void 0,
1975
2028
  callbacks
1976
2029
  );
2030
+ if (assistantMsg.usage && this.tracker) {
2031
+ this.tracker.addTokens(assistantMsg.usage.total_tokens);
2032
+ }
1977
2033
  conversation.addAssistantMessage(assistantMsg);
1978
2034
  if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0) {
1979
2035
  lastContent = assistantMsg.content || "";
@@ -2058,7 +2114,7 @@ ${context}`;
2058
2114
  });
2059
2115
 
2060
2116
  // src/tools/dispatch.ts
2061
- function createDispatchTool(defaultClient, toolRegistry, config, agentRegistry, callbacks) {
2117
+ function createDispatchTool(defaultClient, toolRegistry, config, agentRegistry, tracker, callbacks) {
2062
2118
  return {
2063
2119
  name: "dispatch",
2064
2120
  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",
@@ -2100,11 +2156,14 @@ function createDispatchTool(defaultClient, toolRegistry, config, agentRegistry,
2100
2156
  maxTokens: config.max_tokens
2101
2157
  });
2102
2158
  }
2103
- const runner = new SubAgentRunner(client, toolRegistry, config, agentConfig);
2159
+ const runner = new SubAgentRunner(client, toolRegistry, config, agentConfig, tracker);
2160
+ tracker?.start([`${agentName}: ${task.slice(0, 60)}`]);
2104
2161
  try {
2105
2162
  const result = await runner.execute(task, context, callbacks?.() ?? {});
2163
+ tracker?.finish();
2106
2164
  return { content: result || "\uFF08\u5B50 Agent \u6267\u884C\u5B8C\u6210\uFF0C\u65E0\u8F93\u51FA\uFF09" };
2107
2165
  } catch (err) {
2166
+ tracker?.finish();
2108
2167
  const msg = err instanceof Error ? err.message : String(err);
2109
2168
  return { content: `\u5B50 Agent \u6267\u884C\u9519\u8BEF\uFF1A${msg}` };
2110
2169
  }
@@ -2119,6 +2178,26 @@ var init_dispatch = __esm({
2119
2178
  }
2120
2179
  });
2121
2180
 
2181
+ // src/cli/tui/stores/progressStore.ts
2182
+ import { EventEmitter } from "events";
2183
+ var ProgressStore, progressStore;
2184
+ var init_progressStore = __esm({
2185
+ "src/cli/tui/stores/progressStore.ts"() {
2186
+ "use strict";
2187
+ ProgressStore = class extends EventEmitter {
2188
+ current;
2189
+ update(progress) {
2190
+ this.current = progress;
2191
+ this.emit("change", progress);
2192
+ }
2193
+ get() {
2194
+ return this.current;
2195
+ }
2196
+ };
2197
+ progressStore = new ProgressStore();
2198
+ }
2199
+ });
2200
+
2122
2201
  // src/cli/tui/bridge.ts
2123
2202
  function gray(text) {
2124
2203
  return `${ANSI_GRAY}${text}${ANSI_RESET}`;
@@ -2212,19 +2291,19 @@ function createThinkFilter() {
2212
2291
  return result;
2213
2292
  };
2214
2293
  }
2215
- function createTokenBatcher(dispatch, type) {
2216
- let buffer = "";
2294
+ function createActionBatcher(dispatch) {
2295
+ let pendingActions = /* @__PURE__ */ new Map();
2217
2296
  let timer = null;
2218
2297
  function flush() {
2219
- if (buffer.length > 0) {
2220
- const text = buffer;
2221
- buffer = "";
2222
- dispatch({ type, text });
2298
+ if (pendingActions.size > 0) {
2299
+ const actions = Array.from(pendingActions.values());
2300
+ pendingActions.clear();
2301
+ dispatch({ type: "BATCH", actions });
2223
2302
  }
2224
2303
  }
2225
2304
  function start() {
2226
2305
  if (!timer) {
2227
- timer = setInterval(flush, BATCH_INTERVAL_MS);
2306
+ timer = setInterval(flush, 200);
2228
2307
  }
2229
2308
  }
2230
2309
  function stop() {
@@ -2234,17 +2313,17 @@ function createTokenBatcher(dispatch, type) {
2234
2313
  timer = null;
2235
2314
  }
2236
2315
  }
2237
- function append(text) {
2238
- buffer += text;
2239
- start();
2240
- }
2241
- function pause() {
2242
- if (timer) {
2243
- clearInterval(timer);
2244
- timer = null;
2316
+ function queue(key, action) {
2317
+ if (action.type === "APPEND_CONTENT" || action.type === "APPEND_THOUGHT") {
2318
+ const existing = pendingActions.get(key);
2319
+ if (existing) {
2320
+ action = { ...action, text: existing.text + action.text };
2321
+ }
2245
2322
  }
2323
+ pendingActions.set(key, action);
2324
+ start();
2246
2325
  }
2247
- return { append, stop, flush, pause };
2326
+ return { queue, stop, flush };
2248
2327
  }
2249
2328
  function extractCodeFromArgs(name, args) {
2250
2329
  const field = name === "write-file" ? "content" : "new_string";
@@ -2264,98 +2343,84 @@ function registerConfirmToolId(toolName, id) {
2264
2343
  activeToolIds.set(toolName, id);
2265
2344
  }
2266
2345
  function createBridgeCallbacks(dispatch) {
2267
- const contentBatcher = createTokenBatcher(dispatch, "APPEND_CONTENT");
2268
- const thoughtBatcher = createTokenBatcher(dispatch, "APPEND_THOUGHT");
2346
+ const batcher = createActionBatcher(dispatch);
2269
2347
  let inThink = false;
2270
2348
  let tagBuffer = "";
2271
2349
  activeToolIds = /* @__PURE__ */ new Map();
2272
2350
  streamingToolIds = /* @__PURE__ */ new Map();
2273
- lastStreamingArgs = /* @__PURE__ */ new Map();
2351
+ lastStreamingValues = /* @__PURE__ */ new Map();
2274
2352
  toolCallCounter = 0;
2275
- let streamingThrottleTimer = null;
2276
- let pendingStreamingUpdate = null;
2277
- function flushStreamingUpdate() {
2278
- if (pendingStreamingUpdate) {
2279
- pendingStreamingUpdate();
2280
- pendingStreamingUpdate = null;
2281
- }
2282
- if (streamingThrottleTimer) {
2283
- clearTimeout(streamingThrottleTimer);
2284
- streamingThrottleTimer = null;
2285
- }
2286
- }
2287
2353
  return {
2288
2354
  onContent: (text) => {
2355
+ let contentAcc = "";
2356
+ let thoughtAcc = "";
2289
2357
  for (let i = 0; i < text.length; i++) {
2290
2358
  const ch = text[i];
2291
2359
  if (tagBuffer.length > 0 || ch === "<") {
2292
2360
  tagBuffer += ch;
2293
2361
  if (tagBuffer === "<think>") {
2294
- contentBatcher.flush();
2362
+ if (contentAcc) batcher.queue("content", { type: "APPEND_CONTENT", text: contentAcc });
2363
+ contentAcc = "";
2364
+ batcher.flush();
2295
2365
  inThink = true;
2296
2366
  tagBuffer = "";
2297
2367
  continue;
2298
2368
  } else if (tagBuffer === "</think>") {
2299
- thoughtBatcher.flush();
2369
+ if (thoughtAcc) batcher.queue("thought", { type: "APPEND_THOUGHT", text: thoughtAcc });
2370
+ thoughtAcc = "";
2371
+ batcher.flush();
2300
2372
  inThink = false;
2301
2373
  tagBuffer = "";
2302
2374
  continue;
2303
2375
  } else if (!"<think>".startsWith(tagBuffer) && !"</think>".startsWith(tagBuffer)) {
2304
- if (inThink) {
2305
- thoughtBatcher.append(tagBuffer);
2306
- } else {
2307
- contentBatcher.append(tagBuffer);
2308
- }
2376
+ if (inThink) thoughtAcc += tagBuffer;
2377
+ else contentAcc += tagBuffer;
2309
2378
  tagBuffer = "";
2310
2379
  }
2311
2380
  continue;
2312
2381
  }
2313
- if (inThink) {
2314
- thoughtBatcher.append(ch);
2315
- } else {
2316
- contentBatcher.append(ch);
2317
- }
2382
+ if (inThink) thoughtAcc += ch;
2383
+ else contentAcc += ch;
2318
2384
  }
2385
+ if (thoughtAcc) batcher.queue("thought", { type: "APPEND_THOUGHT", text: thoughtAcc });
2386
+ if (contentAcc) batcher.queue("content", { type: "APPEND_CONTENT", text: contentAcc });
2319
2387
  },
2320
2388
  onToolCallStreaming: (index, name, accumulatedArgs) => {
2321
2389
  if (!streamingToolIds.has(index)) {
2322
- contentBatcher.flush();
2323
- thoughtBatcher.flush();
2390
+ batcher.flush();
2324
2391
  const id2 = `tool-${++toolCallCounter}`;
2325
2392
  streamingToolIds.set(index, id2);
2326
2393
  activeToolIds.set(name, id2);
2327
- dispatch({ type: "TOOL_STREAMING", id: id2, name, streamingContent: "0" });
2394
+ lastStreamingValues.set(id2, "0");
2395
+ batcher.queue(`tool-${id2}`, { type: "TOOL_STREAMING", id: id2, name, streamingContent: "0" });
2328
2396
  }
2329
2397
  const id = streamingToolIds.get(index);
2330
- lastStreamingArgs.set(id, accumulatedArgs);
2331
2398
  const lineCount = (accumulatedArgs.match(/\\n/g) || []).length;
2332
- pendingStreamingUpdate = () => {
2333
- dispatch({ type: "TOOL_STREAMING", id, name, streamingContent: String(lineCount) });
2334
- };
2335
- if (!streamingThrottleTimer) {
2336
- streamingThrottleTimer = setTimeout(() => {
2337
- flushStreamingUpdate();
2338
- }, 80);
2399
+ const streamingContent = String(lineCount);
2400
+ if (lastStreamingValues.get(id) !== streamingContent) {
2401
+ lastStreamingValues.set(id, streamingContent);
2402
+ progressStore.update({ name, progress: `${streamingContent} lines` });
2339
2403
  }
2340
2404
  },
2341
2405
  onToolExecuting: (name, params) => {
2342
- flushStreamingUpdate();
2343
- contentBatcher.flush();
2344
- thoughtBatcher.flush();
2345
- contentBatcher.pause();
2346
- thoughtBatcher.pause();
2406
+ progressStore.update(void 0);
2407
+ batcher.flush();
2347
2408
  const existingId = activeToolIds.get(name);
2348
2409
  const id = existingId || `tool-${++toolCallCounter}`;
2349
2410
  activeToolIds.set(name, id);
2411
+ lastStreamingValues.set(id, JSON.stringify(params));
2350
2412
  dispatch({ type: "TOOL_EXECUTING", id, name, params });
2351
2413
  },
2352
2414
  onToolResult: (name, result, truncated) => {
2415
+ progressStore.update(void 0);
2416
+ batcher.flush();
2353
2417
  const id = activeToolIds.get(name) || `tool-${++toolCallCounter}`;
2354
2418
  const lines = result.split("\n");
2355
2419
  const isWriteTool = name === "write-file" || name === "edit-file";
2356
2420
  let summary;
2357
2421
  if (isWriteTool) {
2358
- const code = extractCodeFromArgs(name, lastStreamingArgs.get(id) || "");
2422
+ const stored = lastStreamingValues.get(id) || "";
2423
+ const code = extractCodeFromArgs(name, stored);
2359
2424
  const codeLines = code ? code.split("\n").length : 0;
2360
2425
  summary = codeLines > 0 ? `${codeLines} lines` : truncated ? "truncated" : `${lines.length} lines`;
2361
2426
  } else {
@@ -2366,31 +2431,119 @@ function createBridgeCallbacks(dispatch) {
2366
2431
  dispatch({ type: "TOOL_RESULT", id, resultSummary: summary, resultContent });
2367
2432
  },
2368
2433
  onDenied: (toolName, feedback) => {
2434
+ batcher.flush();
2369
2435
  const id = activeToolIds.get(toolName) || `tool-${++toolCallCounter}`;
2370
2436
  dispatch({ type: "TOOL_DENIED", id, feedback });
2371
2437
  },
2372
2438
  onError: (err) => {
2373
- contentBatcher.stop();
2374
- thoughtBatcher.stop();
2439
+ batcher.stop();
2375
2440
  dispatch({ type: "SET_ERROR", error: err.message });
2376
2441
  },
2377
2442
  _stopBatcher: () => {
2378
- contentBatcher.stop();
2379
- thoughtBatcher.stop();
2443
+ batcher.stop();
2380
2444
  }
2381
2445
  };
2382
2446
  }
2383
- var BATCH_INTERVAL_MS, ANSI_GRAY, ANSI_RESET, toolCallCounter, activeToolIds, streamingToolIds, lastStreamingArgs;
2447
+ var ANSI_GRAY, ANSI_RESET, toolCallCounter, activeToolIds, streamingToolIds, lastStreamingValues;
2384
2448
  var init_bridge = __esm({
2385
2449
  "src/cli/tui/bridge.ts"() {
2386
2450
  "use strict";
2387
- BATCH_INTERVAL_MS = 64;
2451
+ init_progressStore();
2388
2452
  ANSI_GRAY = "\x1B[90m";
2389
2453
  ANSI_RESET = "\x1B[0m";
2390
2454
  toolCallCounter = 0;
2391
2455
  activeToolIds = /* @__PURE__ */ new Map();
2392
2456
  streamingToolIds = /* @__PURE__ */ new Map();
2393
- lastStreamingArgs = /* @__PURE__ */ new Map();
2457
+ lastStreamingValues = /* @__PURE__ */ new Map();
2458
+ }
2459
+ });
2460
+
2461
+ // src/cli/ui.ts
2462
+ import chalk2 from "chalk";
2463
+ import ora from "ora";
2464
+ import { Marked } from "marked";
2465
+ import * as _markedTerminal from "marked-terminal";
2466
+ import { createTwoFilesPatch } from "diff";
2467
+ function renderMarkdown(text) {
2468
+ const rendered = marked.parse(text);
2469
+ return rendered.replace(/\n+$/, "");
2470
+ }
2471
+ function printStream(text) {
2472
+ process.stdout.write(text);
2473
+ }
2474
+ function printToolCall(toolName, params) {
2475
+ let detail = "";
2476
+ if (toolName === "bash" && params["command"]) {
2477
+ detail = ` ${chalk2.dim(String(params["command"]).slice(0, 80))}`;
2478
+ } else if ((toolName === "read-file" || toolName === "write-file" || toolName === "edit-file") && params["path"]) {
2479
+ detail = ` ${chalk2.dim(String(params["path"]))}`;
2480
+ } else if (toolName === "glob" && params["pattern"]) {
2481
+ detail = ` ${chalk2.dim(String(params["pattern"]))}`;
2482
+ } else if (toolName === "grep" && params["pattern"]) {
2483
+ detail = ` ${chalk2.dim(String(params["pattern"]))}`;
2484
+ } else if (toolName === "spawn-agents" && params["tasks"]) {
2485
+ const tasks = params["tasks"];
2486
+ detail = ` ${chalk2.dim(`${tasks.length} \u4E2A\u5E76\u884C\u4EFB\u52A1`)}`;
2487
+ } else if (toolName === "todo" && params["action"]) {
2488
+ const action = String(params["action"]);
2489
+ const id = params["id"] ? ` [${params["id"]}]` : "";
2490
+ detail = ` ${chalk2.dim(`${action}${id}`)}`;
2491
+ }
2492
+ const icon = toolName === "spawn-agents" ? ">>" : toolName === "todo" ? "#" : ">";
2493
+ console.log(chalk2.yellow(` ${icon} ${toolName}`) + detail);
2494
+ }
2495
+ function printToolResult(toolName, result, truncated) {
2496
+ if (truncated) {
2497
+ console.log(chalk2.dim(` \u2713 ${toolName} (\u8F93\u51FA\u5DF2\u622A\u65AD)`));
2498
+ } else {
2499
+ const lines = result.split("\n").length;
2500
+ console.log(chalk2.dim(` \u2713 ${toolName} (${lines} \u884C)`));
2501
+ }
2502
+ }
2503
+ function printError(message) {
2504
+ console.error(chalk2.red(`\u2717 ${message}`));
2505
+ }
2506
+ function printInfo(message) {
2507
+ console.log(chalk2.cyan(`\u2139 ${message}`));
2508
+ }
2509
+ function printWarning(message) {
2510
+ console.log(chalk2.yellow(`\u26A0 ${message}`));
2511
+ }
2512
+ function printSuccess(message) {
2513
+ console.log(chalk2.green(`\u2713 ${message}`));
2514
+ }
2515
+ function printWelcome(modelName) {
2516
+ console.log(chalk2.bold.cyan("\n ZenCode") + chalk2.dim(" - \u6781\u7B80 AI \u7F16\u7A0B\u52A9\u624B\n"));
2517
+ console.log(chalk2.dim(` \u6A21\u578B: ${modelName}`));
2518
+ console.log(chalk2.dim(` \u8F93\u5165 /help \u67E5\u770B\u547D\u4EE4\uFF0CCtrl+C \u9000\u51FA
2519
+ `));
2520
+ }
2521
+ var markedTerminal2, marked;
2522
+ var init_ui = __esm({
2523
+ "src/cli/ui.ts"() {
2524
+ "use strict";
2525
+ markedTerminal2 = _markedTerminal.markedTerminal;
2526
+ marked = new Marked(markedTerminal2({
2527
+ reflowText: false,
2528
+ code: chalk2.cyan,
2529
+ codespan: chalk2.yellow,
2530
+ heading: chalk2.bold.magenta,
2531
+ hr: () => chalk2.dim("\u2500".repeat(Math.max(0, (process.stdout.columns || 80) - 12))),
2532
+ strong: chalk2.bold,
2533
+ em: chalk2.italic,
2534
+ link: chalk2.blue,
2535
+ href: chalk2.blue.underline
2536
+ }));
2537
+ marked.use({
2538
+ renderer: {
2539
+ text(token) {
2540
+ if (typeof token === "object" && token.tokens) {
2541
+ return this.parser.parseInline(token.tokens);
2542
+ }
2543
+ return typeof token === "string" ? token : token.text;
2544
+ }
2545
+ }
2546
+ });
2394
2547
  }
2395
2548
  });
2396
2549
 
@@ -2407,7 +2560,8 @@ var init_sub_agent_tracker = __esm({
2407
2560
  total: descriptions.length,
2408
2561
  completed: 0,
2409
2562
  failed: 0,
2410
- descriptions
2563
+ descriptions,
2564
+ tokens: 0
2411
2565
  };
2412
2566
  this.notify();
2413
2567
  }
@@ -2421,6 +2575,11 @@ var init_sub_agent_tracker = __esm({
2421
2575
  this.progress = { ...this.progress, failed: this.progress.failed + 1 };
2422
2576
  this.notify();
2423
2577
  }
2578
+ addTokens(count) {
2579
+ if (!this.progress) return;
2580
+ this.progress = { ...this.progress, tokens: this.progress.tokens + count };
2581
+ this.notify();
2582
+ }
2424
2583
  finish() {
2425
2584
  this.progress = null;
2426
2585
  this.notify();
@@ -2462,19 +2621,26 @@ function updateLastAssistant(messages, updater) {
2462
2621
  const result = [...messages];
2463
2622
  for (let i = result.length - 1; i >= 0; i--) {
2464
2623
  if (result[i].role === "assistant") {
2465
- result[i] = updater(result[i]);
2624
+ const updated = updater(result[i]);
2625
+ if (updated === result[i]) return messages;
2626
+ result[i] = updated;
2466
2627
  return result;
2467
2628
  }
2468
2629
  }
2469
2630
  return result;
2470
2631
  }
2471
2632
  function updateToolInBlocks(blocks, toolId, updater) {
2472
- return blocks.map((b) => {
2633
+ let changed = false;
2634
+ const newBlocks = blocks.map((b) => {
2473
2635
  if (b.type === "tool" && b.toolCall.id === toolId) {
2474
- return { type: "tool", toolCall: updater(b.toolCall) };
2636
+ const updatedTc = updater(b.toolCall);
2637
+ if (updatedTc === b.toolCall) return b;
2638
+ changed = true;
2639
+ return { type: "tool", toolCall: updatedTc };
2475
2640
  }
2476
2641
  return b;
2477
2642
  });
2643
+ return changed ? newBlocks : blocks;
2478
2644
  }
2479
2645
  function tuiReducer(state, action) {
2480
2646
  switch (action.type) {
@@ -2506,186 +2672,190 @@ function tuiReducer(state, action) {
2506
2672
  ]
2507
2673
  };
2508
2674
  case "APPEND_CONTENT": {
2509
- return {
2510
- ...state,
2511
- messages: updateLastAssistant(state.messages, (msg) => {
2512
- const blocks = [...msg.blocks];
2513
- const last = blocks[blocks.length - 1];
2514
- if (last && last.type === "text") {
2515
- blocks[blocks.length - 1] = { type: "text", text: last.text + action.text };
2516
- } else {
2517
- blocks.push({ type: "text", text: action.text });
2518
- }
2519
- return { ...msg, blocks };
2520
- })
2521
- };
2675
+ if (!action.text) return state;
2676
+ const newMessages2 = updateLastAssistant(state.messages, (msg) => {
2677
+ const blocks = [...msg.blocks];
2678
+ const last = blocks[blocks.length - 1];
2679
+ if (last && last.type === "text") {
2680
+ blocks[blocks.length - 1] = { type: "text", text: last.text + action.text };
2681
+ } else {
2682
+ blocks.push({ type: "text", text: action.text });
2683
+ }
2684
+ return { ...msg, blocks };
2685
+ });
2686
+ if (newMessages2 === state.messages) return state;
2687
+ return { ...state, messages: newMessages2 };
2522
2688
  }
2523
2689
  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
- };
2690
+ if (!action.text) return state;
2691
+ const newMessages2 = updateLastAssistant(state.messages, (msg) => {
2692
+ const blocks = [...msg.blocks];
2693
+ const last = blocks[blocks.length - 1];
2694
+ if (last && last.type === "thought") {
2695
+ blocks[blocks.length - 1] = { type: "thought", text: last.text + action.text };
2696
+ } else {
2697
+ blocks.push({ type: "thought", text: action.text });
2698
+ }
2699
+ return { ...msg, blocks };
2700
+ });
2701
+ if (newMessages2 === state.messages) return state;
2702
+ return { ...state, messages: newMessages2 };
2537
2703
  }
2538
2704
  case "TOOL_EXECUTING": {
2539
- return {
2540
- ...state,
2541
- messages: updateLastAssistant(state.messages, (msg) => {
2542
- const existingIdx = msg.blocks.findIndex(
2543
- (b) => b.type === "tool" && b.toolCall.id === action.id
2544
- );
2545
- if (existingIdx >= 0) {
2546
- return {
2547
- ...msg,
2548
- blocks: updateToolInBlocks(msg.blocks, action.id, (tc) => ({
2549
- ...tc,
2550
- status: "running",
2551
- params: action.params
2552
- }))
2553
- };
2554
- }
2555
- return {
2556
- ...msg,
2557
- blocks: [
2558
- ...msg.blocks,
2559
- {
2560
- type: "tool",
2561
- toolCall: {
2562
- id: action.id,
2563
- name: action.name,
2564
- params: action.params,
2565
- status: "running"
2566
- }
2705
+ const newMessages2 = updateLastAssistant(state.messages, (msg) => {
2706
+ const existingIdx = msg.blocks.findIndex(
2707
+ (b) => b.type === "tool" && b.toolCall.id === action.id
2708
+ );
2709
+ if (existingIdx >= 0) {
2710
+ const newBlocks = updateToolInBlocks(msg.blocks, action.id, (tc) => {
2711
+ if (tc.status === "running" && JSON.stringify(tc.params) === JSON.stringify(action.params)) return tc;
2712
+ return { ...tc, status: "running", params: action.params };
2713
+ });
2714
+ if (newBlocks === msg.blocks) return msg;
2715
+ return { ...msg, blocks: newBlocks };
2716
+ }
2717
+ return {
2718
+ ...msg,
2719
+ blocks: [
2720
+ ...msg.blocks,
2721
+ {
2722
+ type: "tool",
2723
+ toolCall: {
2724
+ id: action.id,
2725
+ name: action.name,
2726
+ params: action.params,
2727
+ status: "running"
2567
2728
  }
2568
- ]
2569
- };
2570
- })
2571
- };
2729
+ }
2730
+ ]
2731
+ };
2732
+ });
2733
+ if (newMessages2 === state.messages) return state;
2734
+ return { ...state, messages: newMessages2 };
2572
2735
  }
2573
2736
  case "TOOL_STREAMING": {
2574
- return {
2575
- ...state,
2576
- messages: updateLastAssistant(state.messages, (msg) => {
2577
- const existingIdx = msg.blocks.findIndex(
2578
- (b) => b.type === "tool" && b.toolCall.id === action.id
2579
- );
2580
- if (existingIdx >= 0) {
2581
- return {
2582
- ...msg,
2583
- blocks: updateToolInBlocks(msg.blocks, action.id, (tc) => ({
2584
- ...tc,
2585
- streamingContent: action.streamingContent
2586
- }))
2587
- };
2588
- }
2737
+ const { id, streamingContent, name } = action;
2738
+ const newMessages2 = updateLastAssistant(state.messages, (msg) => {
2739
+ const existingBlock = msg.blocks.find(
2740
+ (b) => b.type === "tool" && b.toolCall.id === id
2741
+ );
2742
+ if (existingBlock && existingBlock.type === "tool" && existingBlock.toolCall.streamingContent === streamingContent) {
2743
+ return msg;
2744
+ }
2745
+ if (existingBlock) {
2589
2746
  return {
2590
2747
  ...msg,
2591
- blocks: [
2592
- ...msg.blocks,
2593
- {
2594
- type: "tool",
2595
- toolCall: {
2596
- id: action.id,
2597
- name: action.name,
2598
- params: {},
2599
- status: "running",
2600
- streamingContent: action.streamingContent
2601
- }
2602
- }
2603
- ]
2748
+ blocks: updateToolInBlocks(msg.blocks, id, (tc) => ({
2749
+ ...tc,
2750
+ streamingContent
2751
+ }))
2604
2752
  };
2605
- })
2606
- };
2607
- }
2608
- case "TOOL_RESULT": {
2609
- return {
2610
- ...state,
2611
- messages: updateLastAssistant(state.messages, (msg) => ({
2612
- ...msg,
2613
- blocks: updateToolInBlocks(msg.blocks, action.id, (tc) => ({
2614
- ...tc,
2615
- status: "done",
2616
- resultSummary: action.resultSummary,
2617
- resultContent: action.resultContent
2618
- }))
2619
- }))
2620
- };
2621
- }
2622
- case "TOOL_DENIED": {
2623
- return {
2624
- ...state,
2625
- messages: updateLastAssistant(state.messages, (msg) => ({
2626
- ...msg,
2627
- blocks: updateToolInBlocks(msg.blocks, action.id, (tc) => ({
2628
- ...tc,
2629
- status: "denied",
2630
- denyFeedback: action.feedback
2631
- }))
2632
- }))
2633
- };
2634
- }
2635
- case "TOOL_CONFIRMING": {
2636
- return {
2637
- ...state,
2638
- messages: updateLastAssistant(state.messages, (msg) => ({
2753
+ }
2754
+ return {
2639
2755
  ...msg,
2640
2756
  blocks: [
2641
2757
  ...msg.blocks,
2642
2758
  {
2643
2759
  type: "tool",
2644
2760
  toolCall: {
2645
- id: action.id,
2646
- name: action.name,
2647
- params: action.params,
2648
- status: "confirming"
2761
+ id,
2762
+ name,
2763
+ params: {},
2764
+ status: "running",
2765
+ streamingContent
2649
2766
  }
2650
2767
  }
2651
- ],
2652
- confirmPending: {
2653
- toolName: action.name,
2654
- params: action.params,
2655
- resolve: action.resolve
2768
+ ]
2769
+ };
2770
+ });
2771
+ if (newMessages2 === state.messages) {
2772
+ return state;
2773
+ }
2774
+ return { ...state, messages: newMessages2 };
2775
+ }
2776
+ case "TOOL_RESULT": {
2777
+ const newMessages2 = updateLastAssistant(state.messages, (msg) => {
2778
+ const newBlocks = updateToolInBlocks(msg.blocks, action.id, (tc) => {
2779
+ if (tc.status === "done" && tc.resultSummary === action.resultSummary) return tc;
2780
+ return { ...tc, status: "done", resultSummary: action.resultSummary, resultContent: action.resultContent };
2781
+ });
2782
+ if (newBlocks === msg.blocks) return msg;
2783
+ return { ...msg, blocks: newBlocks };
2784
+ });
2785
+ if (newMessages2 === state.messages) return state;
2786
+ return { ...state, messages: newMessages2 };
2787
+ }
2788
+ case "TOOL_DENIED": {
2789
+ const newMessages2 = updateLastAssistant(state.messages, (msg) => {
2790
+ const newBlocks = updateToolInBlocks(msg.blocks, action.id, (tc) => ({
2791
+ ...tc,
2792
+ status: "denied",
2793
+ denyFeedback: action.feedback
2794
+ }));
2795
+ if (newBlocks === msg.blocks) return msg;
2796
+ return { ...msg, blocks: newBlocks };
2797
+ });
2798
+ if (newMessages2 === state.messages) return state;
2799
+ return { ...state, messages: newMessages2 };
2800
+ }
2801
+ case "TOOL_CONFIRMING": {
2802
+ const newMessages2 = updateLastAssistant(state.messages, (msg) => ({
2803
+ ...msg,
2804
+ blocks: [
2805
+ ...msg.blocks,
2806
+ {
2807
+ type: "tool",
2808
+ toolCall: {
2809
+ id: action.id,
2810
+ name: action.name,
2811
+ params: action.params,
2812
+ status: "confirming"
2813
+ }
2656
2814
  }
2657
- }))
2658
- };
2815
+ ],
2816
+ confirmPending: {
2817
+ toolName: action.name,
2818
+ params: action.params,
2819
+ resolve: action.resolve
2820
+ }
2821
+ }));
2822
+ return { ...state, messages: newMessages2 };
2659
2823
  }
2660
2824
  case "CONFIRM_RESPONDED": {
2661
- return {
2662
- ...state,
2663
- messages: updateLastAssistant(state.messages, (msg) => ({
2664
- ...msg,
2665
- confirmPending: void 0
2666
- }))
2667
- };
2825
+ const newMessages2 = updateLastAssistant(state.messages, (msg) => {
2826
+ if (!msg.confirmPending) return msg;
2827
+ return { ...msg, confirmPending: void 0 };
2828
+ });
2829
+ if (newMessages2 === state.messages) return state;
2830
+ return { ...state, messages: newMessages2 };
2668
2831
  }
2669
2832
  case "FINISH_STREAMING":
2670
- return {
2671
- ...state,
2672
- messages: updateLastAssistant(state.messages, (msg) => ({
2673
- ...msg,
2674
- isStreaming: false
2675
- }))
2676
- };
2833
+ const newMessages = updateLastAssistant(state.messages, (msg) => {
2834
+ if (!msg.isStreaming) return msg;
2835
+ return { ...msg, isStreaming: false };
2836
+ });
2837
+ if (newMessages === state.messages) return state;
2838
+ return { ...state, messages: newMessages };
2677
2839
  case "SET_RUNNING":
2840
+ if (state.isRunning === action.running) return state;
2678
2841
  return { ...state, isRunning: action.running };
2679
2842
  case "SET_ERROR":
2680
2843
  return { ...state, error: action.error, isRunning: false };
2681
2844
  case "CLEAR_ERROR":
2845
+ if (state.error === void 0) return state;
2682
2846
  return { ...state, error: void 0 };
2683
2847
  case "CLEAR_MESSAGES":
2848
+ if (state.messages.length === 0) return state;
2684
2849
  return { ...state, messages: [] };
2685
2850
  case "SET_TODO_PLAN":
2851
+ if (state.todoPlan === action.plan) return state;
2686
2852
  return { ...state, todoPlan: action.plan };
2687
2853
  case "SET_SUB_AGENT_PROGRESS":
2854
+ if (JSON.stringify(state.subAgentProgress) === JSON.stringify(action.progress)) return state;
2688
2855
  return { ...state, subAgentProgress: action.progress };
2856
+ case "BATCH":
2857
+ const newState = action.actions.reduce((s, a) => tuiReducer(s, a), state);
2858
+ return newState === state ? state : newState;
2689
2859
  default:
2690
2860
  return state;
2691
2861
  }
@@ -2699,6 +2869,7 @@ var init_state = __esm({
2699
2869
  });
2700
2870
 
2701
2871
  // src/cli/tui/components/ToolCallLine.tsx
2872
+ import React from "react";
2702
2873
  import { Box, Text } from "ink";
2703
2874
  import { jsx, jsxs } from "react/jsx-runtime";
2704
2875
  function getToolParamSummary(name, params) {
@@ -2712,9 +2883,21 @@ function getToolParamSummary(name, params) {
2712
2883
  case "glob":
2713
2884
  case "grep":
2714
2885
  return String(params["pattern"] || "");
2886
+ case "spawn-agents": {
2887
+ const tasks = params["tasks"];
2888
+ if (!tasks || tasks.length === 0) return "";
2889
+ return `${tasks.length} tasks: ${tasks.map((t) => t.description || "?").join(" | ").slice(0, 100)}`;
2890
+ }
2891
+ case "dispatch":
2892
+ return `${params["agent"] || "?"}: ${String(params["task"] || "").slice(0, 80)}`;
2715
2893
  default:
2716
2894
  const keys = Object.keys(params);
2717
- return keys.length > 0 ? String(params[keys[0]] || "").slice(0, 60) : "";
2895
+ if (keys.length === 0) return "";
2896
+ const val = params[keys[0]];
2897
+ if (val === null || val === void 0) return "";
2898
+ if (typeof val === "string") return val.slice(0, 60);
2899
+ if (typeof val === "number" || typeof val === "boolean") return String(val);
2900
+ return JSON.stringify(val).slice(0, 60);
2718
2901
  }
2719
2902
  }
2720
2903
  function truncateContent(text, maxLines) {
@@ -2729,70 +2912,81 @@ function truncateContent(text, maxLines) {
2729
2912
  }
2730
2913
  return result;
2731
2914
  }
2732
- function ToolCallLine({ toolCall }) {
2733
- const { name, params, status, resultContent, denyFeedback } = toolCall;
2734
- const summary = getToolParamSummary(name, params);
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";
2754
- }
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
- ) });
2779
- }
2915
+ var ToolCallLine;
2780
2916
  var init_ToolCallLine = __esm({
2781
2917
  "src/cli/tui/components/ToolCallLine.tsx"() {
2782
2918
  "use strict";
2919
+ ToolCallLine = React.memo(function ToolCallLine2({ toolCall }) {
2920
+ const { name, params, status, resultContent, denyFeedback } = toolCall;
2921
+ const summary = getToolParamSummary(name, params);
2922
+ let borderColor = "#504945";
2923
+ let titleColor = "#fabd2f";
2924
+ let statusColor = "#fabd2f";
2925
+ let statusText = "RUNNING";
2926
+ if (status === "done") {
2927
+ borderColor = "#b8bb26";
2928
+ titleColor = "#b8bb26";
2929
+ statusColor = "#b8bb26";
2930
+ statusText = "DONE";
2931
+ } else if (status === "denied") {
2932
+ borderColor = "#fb4934";
2933
+ titleColor = "#fb4934";
2934
+ statusColor = "#fb4934";
2935
+ statusText = "DENIED";
2936
+ } else if (status === "confirming") {
2937
+ borderColor = "#fe8019";
2938
+ titleColor = "#fe8019";
2939
+ statusColor = "#fe8019";
2940
+ statusText = "CONFIRM";
2941
+ }
2942
+ const contentLines = resultContent ? truncateContent(resultContent, 15) : [];
2943
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 0, marginBottom: 1, width: "100%", children: /* @__PURE__ */ jsxs(
2944
+ Box,
2945
+ {
2946
+ flexDirection: "column",
2947
+ borderStyle: "round",
2948
+ borderColor,
2949
+ paddingX: 1,
2950
+ width: "100%",
2951
+ children: [
2952
+ /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
2953
+ /* @__PURE__ */ jsx(Box, { backgroundColor: statusColor, width: 9, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { color: "#282828", bold: true, children: statusText }) }),
2954
+ /* @__PURE__ */ jsx(Text, { color: titleColor, bold: true, children: name.toUpperCase() }),
2955
+ /* @__PURE__ */ jsx(Text, { color: "#ebdbb2", dimColor: true, italic: true, wrap: "truncate-end", children: summary })
2956
+ ] }),
2957
+ 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)) }),
2958
+ status === "denied" && denyFeedback && /* @__PURE__ */ jsxs(Box, { gap: 1, marginTop: 0, children: [
2959
+ /* @__PURE__ */ jsx(Text, { color: "#fb4934", bold: true, children: "REASON:" }),
2960
+ /* @__PURE__ */ jsx(Text, { color: "#ebdbb2", children: denyFeedback })
2961
+ ] }),
2962
+ status === "confirming" && /* @__PURE__ */ jsx(Box, { marginTop: 0, children: /* @__PURE__ */ jsx(Text, { color: "#fe8019", italic: true, children: "Waiting for your permission..." }) })
2963
+ ]
2964
+ }
2965
+ ) });
2966
+ });
2783
2967
  }
2784
2968
  });
2785
2969
 
2786
2970
  // src/cli/tui/components/MessageBubble.tsx
2787
- import React from "react";
2788
- import { Box as Box2, Text as Text2 } from "ink";
2971
+ import React2, { useMemo } from "react";
2972
+ import { Box as Box2, Text as Text2, useStdout } from "ink";
2789
2973
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2790
- var MessageBubble;
2974
+ var MemoizedMarkdown, MemoizedThought, MessageBubble;
2791
2975
  var init_MessageBubble = __esm({
2792
2976
  "src/cli/tui/components/MessageBubble.tsx"() {
2793
2977
  "use strict";
2978
+ init_ui();
2794
2979
  init_ToolCallLine();
2795
- MessageBubble = React.memo(function MessageBubble2({ message }) {
2980
+ MemoizedMarkdown = React2.memo(({ text }) => {
2981
+ const { stdout } = useStdout();
2982
+ const rendered = useMemo(() => renderMarkdown(text), [text, stdout.columns]);
2983
+ return /* @__PURE__ */ jsx2(Text2, { children: rendered });
2984
+ });
2985
+ MemoizedThought = React2.memo(({ text, isStreaming }) => {
2986
+ const displayText = isStreaming ? text : text.trim();
2987
+ return /* @__PURE__ */ jsx2(Text2, { color: "#928374", italic: true, children: displayText });
2988
+ });
2989
+ MessageBubble = React2.memo(function MessageBubble2({ message }) {
2796
2990
  const { role, blocks, isStreaming } = message;
2797
2991
  const isUser = role === "user";
2798
2992
  const label = isUser ? "USER" : "AI";
@@ -2809,11 +3003,11 @@ var init_MessageBubble = __esm({
2809
3003
  if (block.type === "text") {
2810
3004
  const text = block.text.trim();
2811
3005
  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}`);
3006
+ return /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, marginBottom: i < blocks.length - 1 ? 1 : 0, width: "100%", children: isUser ? /* @__PURE__ */ jsx2(Text2, { color: contentColor, children: text }) : /* @__PURE__ */ jsx2(MemoizedMarkdown, { text }) }, `text-${i}`);
2813
3007
  } else if (block.type === "thought") {
2814
3008
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 0, width: "100%", children: [
2815
3009
  /* @__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() }) })
3010
+ /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, marginBottom: 0, children: /* @__PURE__ */ jsx2(MemoizedThought, { text: block.text, isStreaming }) })
2817
3011
  ] }, `thought-${i}`);
2818
3012
  } else {
2819
3013
  return /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, width: "100%", children: /* @__PURE__ */ jsx2(ToolCallLine, { toolCall: block.toolCall }) }, block.toolCall.id);
@@ -2826,25 +3020,88 @@ var init_MessageBubble = __esm({
2826
3020
  }
2827
3021
  });
2828
3022
 
3023
+ // src/cli/tui/components/Header.tsx
3024
+ import { Box as Box3, Text as Text3, useStdout as useStdout2 } from "ink";
3025
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
3026
+ function Header({ modelName }) {
3027
+ const { stdout } = useStdout2();
3028
+ const logo = [
3029
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
3030
+ " \u255A\u2550\u2550\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D",
3031
+ " \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 ",
3032
+ " \u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D ",
3033
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
3034
+ " \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"
3035
+ ];
3036
+ const width = (stdout?.columns ?? 80) - 2;
3037
+ return /* @__PURE__ */ jsxs3(
3038
+ Box3,
3039
+ {
3040
+ flexDirection: "column",
3041
+ width,
3042
+ borderStyle: "round",
3043
+ borderColor: "#504945",
3044
+ paddingX: 1,
3045
+ marginTop: 0,
3046
+ marginBottom: 1,
3047
+ children: [
3048
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", marginBottom: 1, alignItems: "center", children: logo.map((line, i) => /* @__PURE__ */ jsx3(Text3, { color: "#fe8019", bold: true, children: line }, i)) }),
3049
+ /* @__PURE__ */ jsxs3(Box3, { justifyContent: "space-between", borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, borderColor: "#3c3836", paddingTop: 0, children: [
3050
+ /* @__PURE__ */ jsxs3(Box3, { gap: 1, children: [
3051
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "#fabd2f", children: "ZEN CODE" }),
3052
+ /* @__PURE__ */ jsx3(Text3, { color: "#a89984", dimColor: true, children: "v0.4.1" })
3053
+ ] }),
3054
+ /* @__PURE__ */ jsx3(Text3, { color: "#83a598", bold: true, children: modelName })
3055
+ ] })
3056
+ ]
3057
+ }
3058
+ );
3059
+ }
3060
+ var init_Header = __esm({
3061
+ "src/cli/tui/components/Header.tsx"() {
3062
+ "use strict";
3063
+ }
3064
+ });
3065
+
2829
3066
  // 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";
2833
- var ChatArea;
3067
+ import React3, { useMemo as useMemo2 } from "react";
3068
+ import { Box as Box4, Static } from "ink";
3069
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3070
+ var HEADER_ITEM, ChatArea;
2834
3071
  var init_ChatArea = __esm({
2835
3072
  "src/cli/tui/components/ChatArea.tsx"() {
2836
3073
  "use strict";
2837
3074
  init_MessageBubble();
2838
- ChatArea = React2.memo(function ChatArea2({ messages }) {
2839
- return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", width: "100%", children: messages.map((msg) => /* @__PURE__ */ jsx3(MessageBubble, { message: msg }, msg.id)) });
3075
+ init_Header();
3076
+ HEADER_ITEM = { id: "static-header", isHeader: true };
3077
+ ChatArea = React3.memo(function ChatArea2({ messages, modelName }) {
3078
+ const completedMessages = messages.filter((m) => !m.isStreaming);
3079
+ const activeMessages = messages.filter((m) => m.isStreaming);
3080
+ const completedCount = completedMessages.length;
3081
+ const lastCompletedId = completedMessages[completedMessages.length - 1]?.id;
3082
+ const staticItems = useMemo2(() => [
3083
+ HEADER_ITEM,
3084
+ ...completedMessages
3085
+ ], [completedCount, lastCompletedId]);
3086
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", width: "100%", children: [
3087
+ /* @__PURE__ */ jsx4(Static, { items: staticItems, children: (item) => {
3088
+ if (item.isHeader) {
3089
+ return /* @__PURE__ */ jsx4(Header, { modelName }, "header");
3090
+ }
3091
+ return /* @__PURE__ */ jsx4(MessageBubble, { message: item }, item.id);
3092
+ } }),
3093
+ activeMessages.map((msg) => /* @__PURE__ */ jsx4(MessageBubble, { message: msg }, msg.id))
3094
+ ] });
3095
+ }, (prev, next) => {
3096
+ return prev.messages === next.messages && prev.modelName === next.modelName;
2840
3097
  });
2841
3098
  }
2842
3099
  });
2843
3100
 
2844
3101
  // src/cli/tui/components/InputArea.tsx
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";
3102
+ import React4, { useRef, useState } from "react";
3103
+ import { Box as Box5, Text as Text4, useInput, useStdout as useStdout4 } from "ink";
3104
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2848
3105
  function CleanTextInput({
2849
3106
  onSubmit,
2850
3107
  placeholder,
@@ -2854,6 +3111,7 @@ function CleanTextInput({
2854
3111
  const [value, setValue] = useState("");
2855
3112
  const [cursor, setCursor] = useState(0);
2856
3113
  const lastCtrlCAtRef = useRef(0);
3114
+ const pastedTextsRef = useRef([]);
2857
3115
  useInput((input, key) => {
2858
3116
  if (onScroll) {
2859
3117
  if (key.pageUp || key.upArrow && key.shift) {
@@ -2870,6 +3128,7 @@ function CleanTextInput({
2870
3128
  setValue("");
2871
3129
  setCursor(0);
2872
3130
  lastCtrlCAtRef.current = 0;
3131
+ pastedTextsRef.current = [];
2873
3132
  return;
2874
3133
  }
2875
3134
  const now = Date.now();
@@ -2883,12 +3142,18 @@ function CleanTextInput({
2883
3142
  return;
2884
3143
  }
2885
3144
  if (key.return) {
2886
- const trimmed = value.trim();
3145
+ let trimmed = value.trim();
2887
3146
  if (trimmed) {
3147
+ const stored = pastedTextsRef.current;
3148
+ trimmed = trimmed.replace(/\[pasted text#(\d+)\]/g, (_match, numStr) => {
3149
+ const idx = parseInt(numStr, 10) - 1;
3150
+ return idx >= 0 && idx < stored.length ? stored[idx] : _match;
3151
+ });
2888
3152
  onSubmit(trimmed);
2889
3153
  }
2890
3154
  setValue("");
2891
3155
  setCursor(0);
3156
+ pastedTextsRef.current = [];
2892
3157
  return;
2893
3158
  }
2894
3159
  if (key.backspace || key.delete) {
@@ -2919,6 +3184,16 @@ function CleanTextInput({
2919
3184
  setCursor(0);
2920
3185
  return;
2921
3186
  }
3187
+ if (input.includes("\n") || input.includes("\r")) {
3188
+ const cleaned = input.replace(/\x1b\[[0-9;]*[A-Za-z]/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
3189
+ if (cleaned.length > 0) {
3190
+ pastedTextsRef.current.push(cleaned);
3191
+ const placeholder2 = `[pasted text#${pastedTextsRef.current.length}]`;
3192
+ setValue((prev) => prev.slice(0, cursor) + placeholder2 + prev.slice(cursor));
3193
+ setCursor((prev) => prev + placeholder2.length);
3194
+ }
3195
+ return;
3196
+ }
2922
3197
  if (key.ctrl || key.meta || key.escape) return;
2923
3198
  if (!input || input.length === 0) return;
2924
3199
  if (input.includes("\x1B") || input.includes("\0")) return;
@@ -2934,84 +3209,119 @@ function CleanTextInput({
2934
3209
  setCursor((prev) => prev + input.length);
2935
3210
  });
2936
3211
  if (value.length === 0) {
2937
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
2938
- /* @__PURE__ */ jsx4(Text3, { inverse: true, children: " " }),
2939
- /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: placeholder || "" })
3212
+ return /* @__PURE__ */ jsxs5(Text4, { wrap: "wrap", children: [
3213
+ /* @__PURE__ */ jsx5(Text4, { inverse: true, children: " " }),
3214
+ /* @__PURE__ */ jsx5(Text4, { dimColor: true, children: placeholder || "" })
2940
3215
  ] });
2941
3216
  }
2942
3217
  const before = value.slice(0, cursor);
2943
3218
  const at = cursor < value.length ? value[cursor] : " ";
2944
3219
  const after = cursor < value.length ? value.slice(cursor + 1) : "";
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 })
2949
- ] });
2950
- }
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(
2956
- CleanTextInput,
2957
- {
2958
- onSubmit,
2959
- placeholder: "Type a message or /command...",
2960
- onExitRequest,
2961
- onScroll
2962
- }
2963
- ) })
3220
+ return /* @__PURE__ */ jsxs5(Text4, { wrap: "wrap", children: [
3221
+ before,
3222
+ /* @__PURE__ */ jsx5(Text4, { inverse: true, children: at }),
3223
+ after
2964
3224
  ] });
2965
3225
  }
3226
+ var InputArea;
2966
3227
  var init_InputArea = __esm({
2967
3228
  "src/cli/tui/components/InputArea.tsx"() {
2968
3229
  "use strict";
3230
+ InputArea = React4.memo(function InputArea2({ onSubmit, isRunning, onExitRequest, onScroll }) {
3231
+ const { stdout } = useStdout4();
3232
+ const labelCols = 10;
3233
+ const textWidth = (stdout?.columns ?? 80) - 2 - labelCols;
3234
+ return /* @__PURE__ */ jsxs5(Box5, { paddingX: 0, children: [
3235
+ /* @__PURE__ */ jsx5(Box5, { backgroundColor: isRunning ? "#504945" : "#b8bb26", paddingX: 1, marginRight: 1, children: /* @__PURE__ */ jsx5(Text4, { color: "#282828", bold: true, children: isRunning ? " WAIT " : " INPUT " }) }),
3236
+ /* @__PURE__ */ jsx5(Box5, { width: textWidth, children: isRunning ? /* @__PURE__ */ jsx5(Text4, { color: "#a89984", italic: true, children: "Thinking..." }) : /* @__PURE__ */ jsx5(
3237
+ CleanTextInput,
3238
+ {
3239
+ onSubmit,
3240
+ placeholder: "Type a message or /command...",
3241
+ onExitRequest,
3242
+ onScroll
3243
+ }
3244
+ ) })
3245
+ ] });
3246
+ });
2969
3247
  }
2970
3248
  });
2971
3249
 
2972
3250
  // src/cli/tui/components/StatusBar.tsx
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 }) {
2976
- const todoProgress = todoPlan ? `${todoPlan.items.filter((i) => i.status === "completed").length}/${todoPlan.items.length}` : null;
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
- ] })
3000
- ] })
3001
- ] }),
3002
- /* @__PURE__ */ jsx5(Box5, { backgroundColor: "#3c3836", paddingX: 1, children: /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: "/help" }) })
3003
- ] });
3251
+ import React5, { useEffect, useState as useState2 } from "react";
3252
+ import { Box as Box6, Text as Text5 } from "ink";
3253
+ import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3254
+ function formatTokens(n) {
3255
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
3256
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
3257
+ return String(n);
3004
3258
  }
3259
+ var StatusBar;
3005
3260
  var init_StatusBar = __esm({
3006
3261
  "src/cli/tui/components/StatusBar.tsx"() {
3007
3262
  "use strict";
3263
+ init_progressStore();
3264
+ StatusBar = React5.memo(function StatusBar2({ isRunning, modelName, todoPlan, subAgentProgress }) {
3265
+ const [activeStreamingTool, setActiveStreamingTool] = useState2(progressStore.get());
3266
+ useEffect(() => {
3267
+ const handleUpdate = (progress) => {
3268
+ setActiveStreamingTool(progress);
3269
+ };
3270
+ progressStore.on("change", handleUpdate);
3271
+ return () => {
3272
+ progressStore.off("change", handleUpdate);
3273
+ };
3274
+ }, []);
3275
+ const todoProgress = todoPlan ? `${todoPlan.items.filter((i) => i.status === "completed").length}/${todoPlan.items.length}` : null;
3276
+ return /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, height: 1, width: "100%", children: [
3277
+ /* @__PURE__ */ jsx6(Box6, { backgroundColor: "#3c3836", paddingX: 1, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text5, { color: "#ebdbb2", bold: true, children: "ZENCODE" }) }),
3278
+ /* @__PURE__ */ jsxs6(Box6, { backgroundColor: "#504945", paddingX: 1, flexGrow: 1, flexBasis: 0, children: [
3279
+ /* @__PURE__ */ jsx6(Text5, { color: "#ebdbb2", wrap: "truncate-end", children: modelName }),
3280
+ isRunning && !subAgentProgress && !activeStreamingTool && /* @__PURE__ */ jsxs6(Fragment, { children: [
3281
+ /* @__PURE__ */ jsx6(Text5, { color: "#ebdbb2", children: " \u2502 " }),
3282
+ /* @__PURE__ */ jsx6(Text5, { color: "#8ec07c", children: "\u25CF thinking..." })
3283
+ ] }),
3284
+ activeStreamingTool && /* @__PURE__ */ jsxs6(Fragment, { children: [
3285
+ /* @__PURE__ */ jsx6(Text5, { color: "#ebdbb2", children: " \u2502 " }),
3286
+ /* @__PURE__ */ jsxs6(Text5, { color: "#fabd2f", wrap: "truncate-end", children: [
3287
+ "\u25CF ",
3288
+ activeStreamingTool.name,
3289
+ ": ",
3290
+ activeStreamingTool.progress
3291
+ ] })
3292
+ ] }),
3293
+ subAgentProgress && /* @__PURE__ */ jsxs6(Fragment, { children: [
3294
+ /* @__PURE__ */ jsx6(Text5, { color: "#ebdbb2", children: " \u2502 " }),
3295
+ /* @__PURE__ */ jsxs6(Text5, { color: "#b8bb26", children: [
3296
+ "Agents: ",
3297
+ subAgentProgress.completed + subAgentProgress.failed,
3298
+ "/",
3299
+ subAgentProgress.total
3300
+ ] }),
3301
+ /* @__PURE__ */ jsx6(Text5, { color: "#ebdbb2", children: " \u2502 " }),
3302
+ /* @__PURE__ */ jsxs6(Text5, { color: "#83a598", children: [
3303
+ "tokens: ",
3304
+ formatTokens(subAgentProgress.tokens)
3305
+ ] })
3306
+ ] }),
3307
+ todoProgress && /* @__PURE__ */ jsxs6(Fragment, { children: [
3308
+ /* @__PURE__ */ jsx6(Text5, { color: "#ebdbb2", children: " \u2502 " }),
3309
+ /* @__PURE__ */ jsxs6(Text5, { color: "#fabd2f", children: [
3310
+ "Plan: ",
3311
+ todoProgress
3312
+ ] })
3313
+ ] })
3314
+ ] }),
3315
+ /* @__PURE__ */ jsx6(Box6, { backgroundColor: "#3c3836", paddingX: 1, flexShrink: 0, children: /* @__PURE__ */ jsx6(Text5, { color: "#ebdbb2", children: "/help" }) })
3316
+ ] });
3317
+ });
3008
3318
  }
3009
3319
  });
3010
3320
 
3011
3321
  // src/cli/tui/components/ConfirmPrompt.tsx
3012
- import { useState as useState2 } from "react";
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";
3322
+ import { useState as useState3 } from "react";
3323
+ import { Box as Box7, Text as Text6, useInput as useInput2 } from "ink";
3324
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3015
3325
  function getToolDetails(toolName, params) {
3016
3326
  const lines = [];
3017
3327
  let label = toolName;
@@ -3057,8 +3367,8 @@ function getToolDetails(toolName, params) {
3057
3367
  return { lines, label };
3058
3368
  }
3059
3369
  function FeedbackInput({ onSubmit }) {
3060
- const [value, setValue] = useState2("");
3061
- const [cursor, setCursor] = useState2(0);
3370
+ const [value, setValue] = useState3("");
3371
+ const [cursor, setCursor] = useState3(0);
3062
3372
  useInput2((input, key) => {
3063
3373
  if (key.return) {
3064
3374
  onSubmit(value);
@@ -3097,23 +3407,23 @@ function FeedbackInput({ onSubmit }) {
3097
3407
  setCursor((prev) => prev + input.length);
3098
3408
  });
3099
3409
  if (value.length === 0) {
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..." })
3410
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
3411
+ /* @__PURE__ */ jsx7(Text6, { inverse: true, children: " " }),
3412
+ /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "\u8F93\u5165\u53CD\u9988\u6307\u4EE4\u7ED9 AI..." })
3103
3413
  ] });
3104
3414
  }
3105
3415
  const before = value.slice(0, cursor);
3106
3416
  const at = cursor < value.length ? value[cursor] : " ";
3107
3417
  const after = cursor < value.length ? value.slice(cursor + 1) : "";
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 })
3418
+ return /* @__PURE__ */ jsxs7(Fragment2, { children: [
3419
+ /* @__PURE__ */ jsx7(Text6, { children: before }),
3420
+ /* @__PURE__ */ jsx7(Text6, { inverse: true, children: at }),
3421
+ /* @__PURE__ */ jsx7(Text6, { children: after })
3112
3422
  ] });
3113
3423
  }
3114
3424
  function ConfirmPrompt({ confirm, onRespond }) {
3115
- const [selected, setSelected] = useState2(0);
3116
- const [feedbackMode, setFeedbackMode] = useState2(false);
3425
+ const [selected, setSelected] = useState3(0);
3426
+ const [feedbackMode, setFeedbackMode] = useState3(false);
3117
3427
  useInput2((input, key) => {
3118
3428
  if (feedbackMode) return;
3119
3429
  if (key.leftArrow) {
@@ -3136,21 +3446,21 @@ function ConfirmPrompt({ confirm, onRespond }) {
3136
3446
  }, { isActive: !feedbackMode });
3137
3447
  const { lines, label } = getToolDetails(confirm.toolName, confirm.params);
3138
3448
  if (feedbackMode) {
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(
3142
- Box6,
3449
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 0, marginY: 0, children: [
3450
+ /* @__PURE__ */ jsx7(Box7, { backgroundColor: "#fe8019", paddingX: 1, children: /* @__PURE__ */ jsx7(Text6, { color: "#282828", bold: true, children: " FEEDBACK " }) }),
3451
+ /* @__PURE__ */ jsxs7(
3452
+ Box7,
3143
3453
  {
3144
3454
  flexDirection: "column",
3145
3455
  borderStyle: "round",
3146
3456
  borderColor: "#fe8019",
3147
3457
  paddingX: 1,
3148
3458
  children: [
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(
3459
+ /* @__PURE__ */ jsx7(Text6, { bold: true, color: "#fabd2f", children: label }),
3460
+ lines.map((line, i) => /* @__PURE__ */ jsx7(Text6, { color: "#a89984", children: line }, i)),
3461
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 0, gap: 1, children: [
3462
+ /* @__PURE__ */ jsx7(Text6, { color: "#83a598", bold: true, children: "Reason: " }),
3463
+ /* @__PURE__ */ jsx7(
3154
3464
  FeedbackInput,
3155
3465
  {
3156
3466
  onSubmit: (text) => {
@@ -3167,29 +3477,29 @@ function ConfirmPrompt({ confirm, onRespond }) {
3167
3477
  ]
3168
3478
  }
3169
3479
  ),
3170
- /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "Enter to send \xB7 Esc to cancel" }) })
3480
+ /* @__PURE__ */ jsx7(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "Enter to send \xB7 Esc to cancel" }) })
3171
3481
  ] });
3172
3482
  }
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(
3176
- Box6,
3483
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 0, marginY: 0, children: [
3484
+ /* @__PURE__ */ jsx7(Box7, { backgroundColor: "#fe8019", paddingX: 1, children: /* @__PURE__ */ jsx7(Text6, { color: "#282828", bold: true, children: " CONFIRMATION REQUIRED " }) }),
3485
+ /* @__PURE__ */ jsxs7(
3486
+ Box7,
3177
3487
  {
3178
3488
  flexDirection: "column",
3179
3489
  borderStyle: "round",
3180
3490
  borderColor: "#fe8019",
3181
3491
  paddingX: 1,
3182
3492
  children: [
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) => {
3493
+ /* @__PURE__ */ jsx7(Text6, { bold: true, color: "#fabd2f", children: label }),
3494
+ lines.map((line, i) => /* @__PURE__ */ jsx7(Text6, { color: "#a89984", children: line }, i)),
3495
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 0, gap: 2, justifyContent: "center", children: OPTIONS.map((opt, i) => {
3186
3496
  const isSelected = i === selected;
3187
3497
  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: [
3498
+ return /* @__PURE__ */ jsx7(Box7, { children: isSelected ? /* @__PURE__ */ jsxs7(Text6, { bold: true, backgroundColor: optColor, color: "#282828", children: [
3189
3499
  " ",
3190
3500
  opt.label,
3191
3501
  " "
3192
- ] }) : /* @__PURE__ */ jsxs5(Text5, { color: optColor, children: [
3502
+ ] }) : /* @__PURE__ */ jsxs7(Text6, { color: optColor, children: [
3193
3503
  " ",
3194
3504
  opt.label,
3195
3505
  " "
@@ -3198,7 +3508,7 @@ function ConfirmPrompt({ confirm, onRespond }) {
3198
3508
  ]
3199
3509
  }
3200
3510
  ),
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" }) })
3511
+ /* @__PURE__ */ jsx7(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text6, { dimColor: true, children: "\u2190 \u2192 select \xB7 Enter confirm \xB7 y/n shortcuts \xB7 Tab feedback" }) })
3202
3512
  ] });
3203
3513
  }
3204
3514
  var OPTIONS;
@@ -3214,16 +3524,17 @@ var init_ConfirmPrompt = __esm({
3214
3524
  });
3215
3525
 
3216
3526
  // src/cli/tui/components/TodoPanel.tsx
3217
- import { Box as Box7, Text as Text6 } from "ink";
3218
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3527
+ import React7 from "react";
3528
+ import { Box as Box8, Text as Text7 } from "ink";
3529
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3219
3530
  function getStatusIcon(status) {
3220
3531
  switch (status) {
3221
3532
  case "completed":
3222
- return "\u2705";
3533
+ return "*";
3223
3534
  case "in-progress":
3224
- return "\u23F3";
3535
+ return ">";
3225
3536
  default:
3226
- return "\u26AA";
3537
+ return " ";
3227
3538
  }
3228
3539
  }
3229
3540
  function getStatusColor(status) {
@@ -3236,100 +3547,89 @@ function getStatusColor(status) {
3236
3547
  return "#928374";
3237
3548
  }
3238
3549
  }
3239
- function TodoPanel({ plan }) {
3240
- const completed = plan.items.filter((i) => i.status === "completed").length;
3241
- const total = plan.items.length;
3242
- return /* @__PURE__ */ jsxs6(
3243
- Box7,
3244
- {
3245
- flexDirection: "column",
3246
- borderStyle: "round",
3247
- borderColor: "#83a598",
3248
- paddingX: 1,
3249
- marginY: 1,
3250
- 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: [
3254
- completed,
3255
- "/",
3256
- total,
3257
- " tasks"
3258
- ] })
3259
- ] }),
3260
- plan.items.map((item) => /* @__PURE__ */ jsxs6(Box7, { gap: 1, children: [
3261
- /* @__PURE__ */ jsx7(Text6, { color: getStatusColor(item.status), children: getStatusIcon(item.status) }),
3262
- /* @__PURE__ */ jsx7(
3263
- Text6,
3264
- {
3265
- color: item.status === "completed" ? "#b8bb26" : item.status === "in-progress" ? "#fabd2f" : "#ebdbb2",
3266
- dimColor: item.status === "pending",
3267
- strikethrough: item.status === "completed",
3268
- children: item.title
3269
- }
3270
- )
3271
- ] }, item.id))
3272
- ]
3273
- }
3274
- );
3275
- }
3550
+ var TodoPanel;
3276
3551
  var init_TodoPanel = __esm({
3277
3552
  "src/cli/tui/components/TodoPanel.tsx"() {
3278
3553
  "use strict";
3279
- }
3280
- });
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";
3554
+ TodoPanel = React7.memo(function TodoPanel2({ plan }) {
3555
+ const completed = plan.items.filter((i) => i.status === "completed").length;
3556
+ const total = plan.items.length;
3557
+ return /* @__PURE__ */ jsxs8(
3558
+ Box8,
3559
+ {
3560
+ flexDirection: "column",
3561
+ borderStyle: "round",
3562
+ borderColor: "#83a598",
3563
+ paddingX: 1,
3564
+ marginY: 0,
3565
+ width: "100%",
3566
+ children: [
3567
+ /* @__PURE__ */ jsxs8(Box8, { justifyContent: "space-between", marginBottom: 0, children: [
3568
+ /* @__PURE__ */ jsx8(Text7, { bold: true, color: "#83a598", children: "PROJECT PLAN" }),
3569
+ /* @__PURE__ */ jsxs8(Text7, { color: "#ebdbb2", children: [
3570
+ completed,
3571
+ "/",
3572
+ total,
3573
+ " tasks"
3574
+ ] })
3575
+ ] }),
3576
+ plan.items.map((item) => /* @__PURE__ */ jsxs8(Box8, { gap: 0, children: [
3577
+ /* @__PURE__ */ jsxs8(Text7, { color: getStatusColor(item.status), children: [
3578
+ "[",
3579
+ getStatusIcon(item.status),
3580
+ "]"
3581
+ ] }),
3582
+ /* @__PURE__ */ jsx8(Text7, { children: " " }),
3583
+ /* @__PURE__ */ jsx8(
3584
+ Text7,
3585
+ {
3586
+ color: item.status === "completed" ? "#b8bb26" : item.status === "in-progress" ? "#fabd2f" : "#ebdbb2",
3587
+ dimColor: item.status === "pending",
3588
+ strikethrough: item.status === "completed",
3589
+ children: item.title
3590
+ }
3591
+ )
3592
+ ] }, item.id))
3593
+ ]
3594
+ }
3595
+ );
3596
+ }, (prev, next) => {
3597
+ return prev.plan === next.plan;
3598
+ });
3309
3599
  }
3310
3600
  });
3311
3601
 
3312
3602
  // src/cli/tui/App.tsx
3313
- import { useReducer, useCallback, useEffect, useRef as useRef2, useState as useState3 } from "react";
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";
3603
+ import { useReducer, useCallback, useEffect as useEffect2, useRef as useRef2, useState as useState4 } from "react";
3604
+ import { Box as Box9, Text as Text8, useInput as useInput3, useStdout as useStdout5 } from "ink";
3605
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
3316
3606
  function App({ config, client, agent, registry, todoStore, subAgentTracker, agentRegistry, skillRegistry }) {
3317
- const { stdout } = useStdout();
3318
- const [, setWidth] = useState3(stdout.columns);
3319
- useEffect(() => {
3607
+ const { stdout } = useStdout5();
3608
+ const [resetKey, setResetKey] = useState4(0);
3609
+ useEffect2(() => {
3610
+ let timer;
3611
+ let lastCols = stdout.columns;
3612
+ let lastRows = stdout.rows;
3320
3613
  const onResize = () => {
3321
- setWidth(stdout.columns);
3614
+ if (stdout.columns === lastCols && stdout.rows === lastRows) return;
3615
+ lastCols = stdout.columns;
3616
+ lastRows = stdout.rows;
3617
+ clearTimeout(timer);
3618
+ timer = setTimeout(() => {
3619
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
3620
+ setResetKey((prev) => prev + 1);
3621
+ }, 100);
3322
3622
  };
3323
3623
  stdout.on("resize", onResize);
3324
3624
  return () => {
3325
3625
  stdout.off("resize", onResize);
3626
+ clearTimeout(timer);
3326
3627
  };
3327
3628
  }, [stdout]);
3328
3629
  const [state, dispatch] = useReducer(
3329
3630
  tuiReducer,
3330
3631
  createInitialState(config.model)
3331
3632
  );
3332
- const [resetKey, setResetKey] = useState3(0);
3333
3633
  const currentCallbacksRef = useRef2(null);
3334
3634
  const agentRef = useRef2(agent);
3335
3635
  const todoStoreRef = useRef2(todoStore);
@@ -3337,12 +3637,12 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3337
3637
  agentRef.current = agent;
3338
3638
  todoStoreRef.current = todoStore;
3339
3639
  subAgentTrackerRef.current = subAgentTracker;
3340
- useEffect(() => {
3640
+ useEffect2(() => {
3341
3641
  return todoStoreRef.current.subscribe((plan) => {
3342
3642
  dispatch({ type: "SET_TODO_PLAN", plan });
3343
3643
  });
3344
3644
  }, []);
3345
- useEffect(() => {
3645
+ useEffect2(() => {
3346
3646
  let timer = null;
3347
3647
  let latest = null;
3348
3648
  const unsub = subAgentTrackerRef.current.subscribe((progress) => {
@@ -3372,7 +3672,7 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3372
3672
  (found, msg) => found || msg.confirmPending,
3373
3673
  void 0
3374
3674
  );
3375
- useEffect(() => {
3675
+ useEffect2(() => {
3376
3676
  setStructuredConfirmHandler((toolName, params) => {
3377
3677
  return new Promise((resolve10) => {
3378
3678
  const id = `confirm-${Date.now()}`;
@@ -3432,7 +3732,7 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3432
3732
  await runAgent(expandedPrompt);
3433
3733
  return;
3434
3734
  }
3435
- handleSlashCommand2(text, {
3735
+ handleSlashCommand(text, {
3436
3736
  config,
3437
3737
  agent: agentRef.current,
3438
3738
  registry,
@@ -3475,9 +3775,10 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3475
3775
  return;
3476
3776
  }
3477
3777
  if (input === "c" && key.ctrl) {
3478
- if (state.isRunning) {
3778
+ if (state.isRunning || confirmPending) {
3479
3779
  agentRef.current.interrupt();
3480
3780
  currentCallbacksRef.current?._stopBatcher?.();
3781
+ dispatch({ type: "CONFIRM_RESPONDED", id: "" });
3481
3782
  dispatch({ type: "SET_RUNNING", running: false });
3482
3783
  dispatch({ type: "FINISH_STREAMING" });
3483
3784
  return;
@@ -3487,18 +3788,21 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3487
3788
  process.exit(0);
3488
3789
  }
3489
3790
  });
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: [
3791
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingLeft: 1, paddingRight: 1, width: "100%", children: [
3792
+ /* @__PURE__ */ jsx9(
3793
+ ChatArea,
3794
+ {
3795
+ messages: state.messages,
3796
+ modelName: state.modelName
3797
+ }
3798
+ ),
3799
+ state.error && /* @__PURE__ */ jsxs9(Box9, { borderStyle: "round", borderColor: "#fb4934", paddingX: 1, marginBottom: 0, children: [
3496
3800
  /* @__PURE__ */ jsx9(Text8, { color: "#fb4934", bold: true, children: "ERROR: " }),
3497
3801
  /* @__PURE__ */ jsx9(Text8, { color: "#fb4934", children: state.error })
3498
3802
  ] }),
3499
3803
  confirmPending && /* @__PURE__ */ jsx9(ConfirmPrompt, { confirm: confirmPending, onRespond: handleConfirmResponse }),
3500
3804
  state.todoPlan && /* @__PURE__ */ jsx9(TodoPanel, { plan: state.todoPlan }),
3501
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(
3805
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 0, children: /* @__PURE__ */ jsx9(
3502
3806
  InputArea,
3503
3807
  {
3504
3808
  onSubmit: handleSubmit,
@@ -3506,7 +3810,7 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3506
3810
  onExitRequest: () => process.exit(0)
3507
3811
  }
3508
3812
  ) }),
3509
- /* @__PURE__ */ jsx9(Box9, { marginTop: 0, children: /* @__PURE__ */ jsx9(
3813
+ /* @__PURE__ */ jsx9(
3510
3814
  StatusBar,
3511
3815
  {
3512
3816
  isRunning: state.isRunning,
@@ -3514,125 +3818,9 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3514
3818
  todoPlan: state.todoPlan,
3515
3819
  subAgentProgress: state.subAgentProgress
3516
3820
  }
3517
- ) })
3821
+ )
3518
3822
  ] }, resetKey);
3519
3823
  }
3520
- function handleSlashCommand2(input, ctx) {
3521
- const { config, agent, registry, dispatch, setResetKey, client, todoStore, subAgentTracker, agentRegistry, skillRegistry } = ctx;
3522
- const parts = input.trim().split(/\s+/);
3523
- const command = parts[0];
3524
- switch (command) {
3525
- case "/help":
3526
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3527
- dispatch({ type: "START_ASSISTANT" });
3528
- {
3529
- let helpText = `\u53EF\u7528\u547D\u4EE4:
3530
- /help \u663E\u793A\u6B64\u5E2E\u52A9\u4FE1\u606F
3531
- /skills \u5217\u51FA\u6240\u6709\u53EF\u7528\u6280\u80FD
3532
- /agents \u5217\u51FA\u6240\u6709\u53EF\u7528\u5B50 Agent
3533
- /parallel \u5207\u6362\u5E76\u884C\u5B50 Agent \u529F\u80FD on/off
3534
- /todo \u5207\u6362 todo \u8BA1\u5212\u529F\u80FD on/off
3535
- /clear \u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2
3536
- /info \u663E\u793A\u5F53\u524D\u914D\u7F6E
3537
- Ctrl+C \u53D6\u6D88\u5F53\u524D\u8BF7\u6C42 / \u9000\u51FA
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
- }
3548
- dispatch({ type: "FINISH_STREAMING" });
3549
- break;
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")}` });
3560
- }
3561
- dispatch({ type: "FINISH_STREAMING" });
3562
- break;
3563
- }
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" });
3576
- break;
3577
- }
3578
- case "/clear":
3579
- agent.getConversation().clear();
3580
- dispatch({ type: "CLEAR_MESSAGES" });
3581
- setResetKey((prev) => prev + 1);
3582
- break;
3583
- case "/parallel": {
3584
- const current = config.features.parallel_agents;
3585
- const next = current === "on" ? "off" : "on";
3586
- config.features.parallel_agents = next;
3587
- if (next === "off") {
3588
- registry.unregister("spawn-agents");
3589
- } else if (!registry.has("spawn-agents")) {
3590
- registry.register(createSpawnAgentsTool(client, registry, config, subAgentTracker));
3591
- }
3592
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3593
- dispatch({ type: "START_ASSISTANT" });
3594
- dispatch({ type: "APPEND_CONTENT", text: `\u5E76\u884C\u5B50 Agent \u529F\u80FD\u5DF2${next === "on" ? "\u5F00\u542F" : "\u5173\u95ED"}` });
3595
- dispatch({ type: "FINISH_STREAMING" });
3596
- break;
3597
- }
3598
- case "/todo": {
3599
- const current = config.features.todo;
3600
- const next = current === "on" ? "off" : "on";
3601
- config.features.todo = next;
3602
- if (next === "off") {
3603
- registry.unregister("todo");
3604
- } else if (!registry.has("todo")) {
3605
- registry.register(createTodoTool(todoStore));
3606
- }
3607
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3608
- dispatch({ type: "START_ASSISTANT" });
3609
- dispatch({ type: "APPEND_CONTENT", text: `Todo \u8BA1\u5212\u529F\u80FD\u5DF2${next === "on" ? "\u5F00\u542F" : "\u5173\u95ED"}` });
3610
- dispatch({ type: "FINISH_STREAMING" });
3611
- break;
3612
- }
3613
- case "/info":
3614
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3615
- dispatch({ type: "START_ASSISTANT" });
3616
- dispatch({
3617
- type: "APPEND_CONTENT",
3618
- text: `\u6A21\u578B: ${config.model}
3619
- \u57FA\u7840 URL: ${config.base_url}
3620
- \u5B50 Agent: ${agentRegistry.listNames().join(", ") || "\u65E0"}
3621
- \u6280\u80FD: ${skillRegistry.listNames().map((n) => "/" + n).join(", ") || "\u65E0"}`
3622
- });
3623
- dispatch({ type: "FINISH_STREAMING" });
3624
- break;
3625
- default:
3626
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3627
- dispatch({ type: "START_ASSISTANT" });
3628
- dispatch({
3629
- type: "APPEND_CONTENT",
3630
- text: `\u672A\u77E5\u547D\u4EE4: ${command}\u3002\u8F93\u5165 /help \u67E5\u770B\u5E2E\u52A9\u3002`
3631
- });
3632
- dispatch({ type: "FINISH_STREAMING" });
3633
- break;
3634
- }
3635
- }
3636
3824
  var init_App = __esm({
3637
3825
  "src/cli/tui/App.tsx"() {
3638
3826
  "use strict";
@@ -3640,14 +3828,11 @@ var init_App = __esm({
3640
3828
  init_permission();
3641
3829
  init_state();
3642
3830
  init_bridge();
3643
- init_spawn_agents();
3644
- init_todo();
3645
3831
  init_ChatArea();
3646
3832
  init_InputArea();
3647
3833
  init_StatusBar();
3648
3834
  init_ConfirmPrompt();
3649
3835
  init_TodoPanel();
3650
- init_Header();
3651
3836
  }
3652
3837
  });
3653
3838
 
@@ -3687,7 +3872,7 @@ async function startTui(options) {
3687
3872
  if (config.features.todo === "on") {
3688
3873
  registry.register(createTodoTool(todoStore));
3689
3874
  }
3690
- registry.register(createDispatchTool(client, registry, config, agentRegistry));
3875
+ registry.register(createDispatchTool(client, registry, config, agentRegistry, subAgentTracker));
3691
3876
  const agent = new Agent(client, registry, config, systemPrompt);
3692
3877
  const { waitUntilExit } = render(
3693
3878
  /* @__PURE__ */ jsx10(
@@ -3841,69 +4026,9 @@ init_spawn_agents();
3841
4026
  init_todo();
3842
4027
  init_dispatch();
3843
4028
  init_bridge();
4029
+ init_ui();
3844
4030
  import * as readline from "readline";
3845
-
3846
- // src/cli/ui.ts
3847
- import chalk2 from "chalk";
3848
- import ora from "ora";
3849
- import { marked } from "marked";
3850
- import * as _markedTerminal from "marked-terminal";
3851
- import { createTwoFilesPatch } from "diff";
3852
- var markedTerminal2 = _markedTerminal.markedTerminal;
3853
- marked.use(markedTerminal2());
3854
- function printStream(text) {
3855
- process.stdout.write(text);
3856
- }
3857
- function printToolCall(toolName, params) {
3858
- let detail = "";
3859
- if (toolName === "bash" && params["command"]) {
3860
- detail = ` ${chalk2.dim(String(params["command"]).slice(0, 80))}`;
3861
- } else if ((toolName === "read-file" || toolName === "write-file" || toolName === "edit-file") && params["path"]) {
3862
- detail = ` ${chalk2.dim(String(params["path"]))}`;
3863
- } else if (toolName === "glob" && params["pattern"]) {
3864
- detail = ` ${chalk2.dim(String(params["pattern"]))}`;
3865
- } else if (toolName === "grep" && params["pattern"]) {
3866
- detail = ` ${chalk2.dim(String(params["pattern"]))}`;
3867
- } else if (toolName === "spawn-agents" && params["tasks"]) {
3868
- const tasks = params["tasks"];
3869
- detail = ` ${chalk2.dim(`${tasks.length} \u4E2A\u5E76\u884C\u4EFB\u52A1`)}`;
3870
- } else if (toolName === "todo" && params["action"]) {
3871
- const action = String(params["action"]);
3872
- const id = params["id"] ? ` [${params["id"]}]` : "";
3873
- detail = ` ${chalk2.dim(`${action}${id}`)}`;
3874
- }
3875
- const icon = toolName === "spawn-agents" ? "\u26A1" : toolName === "todo" ? "\u{1F4CB}" : "\u2699";
3876
- console.log(chalk2.yellow(` ${icon} ${toolName}`) + detail);
3877
- }
3878
- function printToolResult(toolName, result, truncated) {
3879
- if (truncated) {
3880
- console.log(chalk2.dim(` \u2713 ${toolName} (\u8F93\u51FA\u5DF2\u622A\u65AD)`));
3881
- } else {
3882
- const lines = result.split("\n").length;
3883
- console.log(chalk2.dim(` \u2713 ${toolName} (${lines} \u884C)`));
3884
- }
3885
- }
3886
- function printError(message) {
3887
- console.error(chalk2.red(`\u2717 ${message}`));
3888
- }
3889
- function printInfo(message) {
3890
- console.log(chalk2.cyan(`\u2139 ${message}`));
3891
- }
3892
- function printWarning(message) {
3893
- console.log(chalk2.yellow(`\u26A0 ${message}`));
3894
- }
3895
- function printSuccess(message) {
3896
- console.log(chalk2.green(`\u2713 ${message}`));
3897
- }
3898
- function printWelcome(modelName) {
3899
- console.log(chalk2.bold.cyan("\n ZenCode") + chalk2.dim(" - \u6781\u7B80 AI \u7F16\u7A0B\u52A9\u624B\n"));
3900
- console.log(chalk2.dim(` \u6A21\u578B: ${modelName}`));
3901
- console.log(chalk2.dim(` \u8F93\u5165 /help \u67E5\u770B\u547D\u4EE4\uFF0CCtrl+C \u9000\u51FA
3902
- `));
3903
- }
3904
-
3905
- // src/cli/repl.ts
3906
- function handleSlashCommand(input, context) {
4031
+ function handleSlashCommand2(input, context) {
3907
4032
  const parts = input.trim().split(/\s+/);
3908
4033
  const command = parts[0];
3909
4034
  switch (command) {
@@ -4095,7 +4220,7 @@ async function startRepl(options) {
4095
4220
  rl.prompt();
4096
4221
  return;
4097
4222
  }
4098
- const handled = handleSlashCommand(input, {
4223
+ const handled = handleSlashCommand2(input, {
4099
4224
  config,
4100
4225
  registry,
4101
4226
  client,
@@ -4171,6 +4296,7 @@ init_spawn_agents();
4171
4296
  init_todo();
4172
4297
  init_dispatch();
4173
4298
  init_bridge();
4299
+ init_ui();
4174
4300
  async function runOnce(prompt, config) {
4175
4301
  const agentRegistry = new SubAgentConfigRegistry();
4176
4302
  for (const agentConfig of loadAllAgentConfigs()) {
@@ -4234,7 +4360,7 @@ async function runOnce(prompt, config) {
4234
4360
  }
4235
4361
  function createCli() {
4236
4362
  const program2 = new Command();
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) => {
4363
+ program2.name("zencode").description("\u6781\u7B80 CLI AI \u7F16\u7A0B\u5DE5\u5177").version("0.4.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) => {
4238
4364
  const config = loadConfig(opts);
4239
4365
  if (!config.api_key) {
4240
4366
  printError("\u672A\u8BBE\u7F6E API \u5BC6\u94A5\u3002\u8BF7\u901A\u8FC7\u4EE5\u4E0B\u65B9\u5F0F\u4E4B\u4E00\u8BBE\u7F6E\uFF1A");
@@ -4250,6 +4376,7 @@ function createCli() {
4250
4376
  await startRepl({ config });
4251
4377
  } else {
4252
4378
  const { startTui: startTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
4379
+ process.stdout.write("\x1Bc");
4253
4380
  await startTui2({ config });
4254
4381
  }
4255
4382
  });