zencode-cli 0.2.3 → 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.
@@ -817,7 +817,16 @@ function formatToolDetail(toolName, params) {
817
817
  }
818
818
  async function confirmExecution(toolName, params) {
819
819
  if (structuredConfirmHandler) {
820
- 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;
821
830
  }
822
831
  const detail = formatToolDetail(toolName, params);
823
832
  const prompt = `
@@ -828,12 +837,13 @@ ${detail}
828
837
  const approved = await globalConfirmHandler(`${chalk.yellow("?")} \u662F\u5426\u6267\u884C\uFF1F(${chalk.green("y")}/${chalk.red("N")}) `);
829
838
  return { approved };
830
839
  }
831
- var globalConfirmHandler, structuredConfirmHandler;
840
+ var globalConfirmHandler, structuredConfirmHandler, pendingConfirmResolver;
832
841
  var init_permission = __esm({
833
842
  "src/tools/permission.ts"() {
834
843
  "use strict";
835
844
  globalConfirmHandler = async () => false;
836
845
  structuredConfirmHandler = null;
846
+ pendingConfirmResolver = null;
837
847
  }
838
848
  });
839
849
 
@@ -990,21 +1000,32 @@ var init_agent = __esm({
990
1000
  tools.length > 0 ? tools : void 0,
991
1001
  callbacks
992
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;
993
1016
  this.conversation.addAssistantMessage(assistantMsg);
994
- 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) {
995
1022
  lastContent = assistantMsg.content || "";
996
1023
  break;
997
1024
  }
998
- for (const toolCall of assistantMsg.tool_calls) {
1025
+ for (const toolCall of validToolCalls) {
999
1026
  if (this.interrupted) break;
1000
1027
  const toolName = toolCall.function.name;
1001
- let params;
1002
- try {
1003
- params = JSON.parse(toolCall.function.arguments);
1004
- } catch {
1005
- this.conversation.addToolResult(toolCall.id, "\u53C2\u6570\u89E3\u6790\u5931\u8D25\uFF1A\u65E0\u6548\u7684 JSON");
1006
- continue;
1007
- }
1028
+ const params = JSON.parse(toolCall.function.arguments);
1008
1029
  try {
1009
1030
  if (toolName === "edit-file") {
1010
1031
  const editPath = params["path"];
@@ -1110,6 +1131,8 @@ function buildCorePrompt() {
1110
1131
  - \u4E0D\u8981\u6DFB\u52A0\u7528\u6237\u672A\u8981\u6C42\u7684\u6CE8\u91CA\u3001\u6587\u6863\u3001\u7C7B\u578B\u6CE8\u89E3
1111
1132
  - \u4E0D\u8981\u5F15\u5165\u5B89\u5168\u6F0F\u6D1E\uFF08\u6CE8\u5165\u3001XSS\u3001SQL \u6CE8\u5165\u7B49 OWASP Top 10\uFF09
1112
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
1113
1136
 
1114
1137
  # \u4EA4\u4E92\u98CE\u683C
1115
1138
 
@@ -1142,7 +1165,8 @@ function buildPlanningPrompt() {
1142
1165
 
1143
1166
  \u4EE3\u7801\u8D28\u91CF\uFF1A
1144
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
1145
- - \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`;
1146
1170
  }
1147
1171
  var init_planning = __esm({
1148
1172
  "src/core/prompt/layers/planning.ts"() {
@@ -1161,6 +1185,7 @@ function buildParallelPrompt() {
1161
1185
  - \u9700\u8981\u641C\u7D22 2+ \u4E2A\u6A21\u5F0F \u2192 spawn-agents \u5E76\u884C\u641C\u7D22
1162
1186
  - \u9700\u8981\u4E86\u89E3 2+ \u4E2A\u6A21\u5757 \u2192 spawn-agents \u5E76\u884C\u5206\u6790
1163
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
1164
1189
 
1165
1190
  \u793A\u4F8B - \u7528\u6237\u8BF4"\u5E2E\u6211\u7406\u89E3\u8BA4\u8BC1\u6A21\u5757"\uFF1A
1166
1191
  \u6B63\u786E\uFF1Aspawn-agents \u540C\u65F6\u8BFB auth controller\u3001auth service\u3001auth middleware\u3001auth types
@@ -1182,7 +1207,8 @@ function buildGitPrompt() {
1182
1207
 
1183
1208
  \u63D0\u4EA4\u89C4\u8303\uFF1A
1184
1209
  - \u53EA\u5728\u7528\u6237\u660E\u786E\u8981\u6C42\u65F6\u624D\u521B\u5EFA commit
1185
- - \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
1186
1212
  - commit message \u63CF\u8FF0"\u4E3A\u4EC0\u4E48"\u800C\u975E"\u6539\u4E86\u4EC0\u4E48"
1187
1213
 
1188
1214
  \u5B89\u5168\u89C4\u5219\uFF1A
@@ -2152,6 +2178,26 @@ var init_dispatch = __esm({
2152
2178
  }
2153
2179
  });
2154
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
+
2155
2201
  // src/cli/tui/bridge.ts
2156
2202
  function gray(text) {
2157
2203
  return `${ANSI_GRAY}${text}${ANSI_RESET}`;
@@ -2245,19 +2291,19 @@ function createThinkFilter() {
2245
2291
  return result;
2246
2292
  };
2247
2293
  }
2248
- function createTokenBatcher(dispatch, type) {
2249
- let buffer = "";
2294
+ function createActionBatcher(dispatch) {
2295
+ let pendingActions = /* @__PURE__ */ new Map();
2250
2296
  let timer = null;
2251
2297
  function flush() {
2252
- if (buffer.length > 0) {
2253
- const text = buffer;
2254
- buffer = "";
2255
- dispatch({ type, text });
2298
+ if (pendingActions.size > 0) {
2299
+ const actions = Array.from(pendingActions.values());
2300
+ pendingActions.clear();
2301
+ dispatch({ type: "BATCH", actions });
2256
2302
  }
2257
2303
  }
2258
2304
  function start() {
2259
2305
  if (!timer) {
2260
- timer = setInterval(flush, BATCH_INTERVAL_MS);
2306
+ timer = setInterval(flush, 200);
2261
2307
  }
2262
2308
  }
2263
2309
  function stop() {
@@ -2267,17 +2313,17 @@ function createTokenBatcher(dispatch, type) {
2267
2313
  timer = null;
2268
2314
  }
2269
2315
  }
2270
- function append(text) {
2271
- buffer += text;
2272
- start();
2273
- }
2274
- function pause() {
2275
- if (timer) {
2276
- clearInterval(timer);
2277
- 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
+ }
2278
2322
  }
2323
+ pendingActions.set(key, action);
2324
+ start();
2279
2325
  }
2280
- return { append, stop, flush, pause };
2326
+ return { queue, stop, flush };
2281
2327
  }
2282
2328
  function extractCodeFromArgs(name, args) {
2283
2329
  const field = name === "write-file" ? "content" : "new_string";
@@ -2297,98 +2343,84 @@ function registerConfirmToolId(toolName, id) {
2297
2343
  activeToolIds.set(toolName, id);
2298
2344
  }
2299
2345
  function createBridgeCallbacks(dispatch) {
2300
- const contentBatcher = createTokenBatcher(dispatch, "APPEND_CONTENT");
2301
- const thoughtBatcher = createTokenBatcher(dispatch, "APPEND_THOUGHT");
2346
+ const batcher = createActionBatcher(dispatch);
2302
2347
  let inThink = false;
2303
2348
  let tagBuffer = "";
2304
2349
  activeToolIds = /* @__PURE__ */ new Map();
2305
2350
  streamingToolIds = /* @__PURE__ */ new Map();
2306
- lastStreamingArgs = /* @__PURE__ */ new Map();
2351
+ lastStreamingValues = /* @__PURE__ */ new Map();
2307
2352
  toolCallCounter = 0;
2308
- let streamingThrottleTimer = null;
2309
- let pendingStreamingUpdate = null;
2310
- function flushStreamingUpdate() {
2311
- if (pendingStreamingUpdate) {
2312
- pendingStreamingUpdate();
2313
- pendingStreamingUpdate = null;
2314
- }
2315
- if (streamingThrottleTimer) {
2316
- clearTimeout(streamingThrottleTimer);
2317
- streamingThrottleTimer = null;
2318
- }
2319
- }
2320
2353
  return {
2321
2354
  onContent: (text) => {
2355
+ let contentAcc = "";
2356
+ let thoughtAcc = "";
2322
2357
  for (let i = 0; i < text.length; i++) {
2323
2358
  const ch = text[i];
2324
2359
  if (tagBuffer.length > 0 || ch === "<") {
2325
2360
  tagBuffer += ch;
2326
2361
  if (tagBuffer === "<think>") {
2327
- contentBatcher.flush();
2362
+ if (contentAcc) batcher.queue("content", { type: "APPEND_CONTENT", text: contentAcc });
2363
+ contentAcc = "";
2364
+ batcher.flush();
2328
2365
  inThink = true;
2329
2366
  tagBuffer = "";
2330
2367
  continue;
2331
2368
  } else if (tagBuffer === "</think>") {
2332
- thoughtBatcher.flush();
2369
+ if (thoughtAcc) batcher.queue("thought", { type: "APPEND_THOUGHT", text: thoughtAcc });
2370
+ thoughtAcc = "";
2371
+ batcher.flush();
2333
2372
  inThink = false;
2334
2373
  tagBuffer = "";
2335
2374
  continue;
2336
2375
  } else if (!"<think>".startsWith(tagBuffer) && !"</think>".startsWith(tagBuffer)) {
2337
- if (inThink) {
2338
- thoughtBatcher.append(tagBuffer);
2339
- } else {
2340
- contentBatcher.append(tagBuffer);
2341
- }
2376
+ if (inThink) thoughtAcc += tagBuffer;
2377
+ else contentAcc += tagBuffer;
2342
2378
  tagBuffer = "";
2343
2379
  }
2344
2380
  continue;
2345
2381
  }
2346
- if (inThink) {
2347
- thoughtBatcher.append(ch);
2348
- } else {
2349
- contentBatcher.append(ch);
2350
- }
2382
+ if (inThink) thoughtAcc += ch;
2383
+ else contentAcc += ch;
2351
2384
  }
2385
+ if (thoughtAcc) batcher.queue("thought", { type: "APPEND_THOUGHT", text: thoughtAcc });
2386
+ if (contentAcc) batcher.queue("content", { type: "APPEND_CONTENT", text: contentAcc });
2352
2387
  },
2353
2388
  onToolCallStreaming: (index, name, accumulatedArgs) => {
2354
2389
  if (!streamingToolIds.has(index)) {
2355
- contentBatcher.flush();
2356
- thoughtBatcher.flush();
2390
+ batcher.flush();
2357
2391
  const id2 = `tool-${++toolCallCounter}`;
2358
2392
  streamingToolIds.set(index, id2);
2359
2393
  activeToolIds.set(name, id2);
2360
- 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" });
2361
2396
  }
2362
2397
  const id = streamingToolIds.get(index);
2363
- lastStreamingArgs.set(id, accumulatedArgs);
2364
2398
  const lineCount = (accumulatedArgs.match(/\\n/g) || []).length;
2365
- pendingStreamingUpdate = () => {
2366
- dispatch({ type: "TOOL_STREAMING", id, name, streamingContent: String(lineCount) });
2367
- };
2368
- if (!streamingThrottleTimer) {
2369
- streamingThrottleTimer = setTimeout(() => {
2370
- flushStreamingUpdate();
2371
- }, 80);
2399
+ const streamingContent = String(lineCount);
2400
+ if (lastStreamingValues.get(id) !== streamingContent) {
2401
+ lastStreamingValues.set(id, streamingContent);
2402
+ progressStore.update({ name, progress: `${streamingContent} lines` });
2372
2403
  }
2373
2404
  },
2374
2405
  onToolExecuting: (name, params) => {
2375
- flushStreamingUpdate();
2376
- contentBatcher.flush();
2377
- thoughtBatcher.flush();
2378
- contentBatcher.pause();
2379
- thoughtBatcher.pause();
2406
+ progressStore.update(void 0);
2407
+ batcher.flush();
2380
2408
  const existingId = activeToolIds.get(name);
2381
2409
  const id = existingId || `tool-${++toolCallCounter}`;
2382
2410
  activeToolIds.set(name, id);
2411
+ lastStreamingValues.set(id, JSON.stringify(params));
2383
2412
  dispatch({ type: "TOOL_EXECUTING", id, name, params });
2384
2413
  },
2385
2414
  onToolResult: (name, result, truncated) => {
2415
+ progressStore.update(void 0);
2416
+ batcher.flush();
2386
2417
  const id = activeToolIds.get(name) || `tool-${++toolCallCounter}`;
2387
2418
  const lines = result.split("\n");
2388
2419
  const isWriteTool = name === "write-file" || name === "edit-file";
2389
2420
  let summary;
2390
2421
  if (isWriteTool) {
2391
- const code = extractCodeFromArgs(name, lastStreamingArgs.get(id) || "");
2422
+ const stored = lastStreamingValues.get(id) || "";
2423
+ const code = extractCodeFromArgs(name, stored);
2392
2424
  const codeLines = code ? code.split("\n").length : 0;
2393
2425
  summary = codeLines > 0 ? `${codeLines} lines` : truncated ? "truncated" : `${lines.length} lines`;
2394
2426
  } else {
@@ -2399,31 +2431,119 @@ function createBridgeCallbacks(dispatch) {
2399
2431
  dispatch({ type: "TOOL_RESULT", id, resultSummary: summary, resultContent });
2400
2432
  },
2401
2433
  onDenied: (toolName, feedback) => {
2434
+ batcher.flush();
2402
2435
  const id = activeToolIds.get(toolName) || `tool-${++toolCallCounter}`;
2403
2436
  dispatch({ type: "TOOL_DENIED", id, feedback });
2404
2437
  },
2405
2438
  onError: (err) => {
2406
- contentBatcher.stop();
2407
- thoughtBatcher.stop();
2439
+ batcher.stop();
2408
2440
  dispatch({ type: "SET_ERROR", error: err.message });
2409
2441
  },
2410
2442
  _stopBatcher: () => {
2411
- contentBatcher.stop();
2412
- thoughtBatcher.stop();
2443
+ batcher.stop();
2413
2444
  }
2414
2445
  };
2415
2446
  }
2416
- var BATCH_INTERVAL_MS, ANSI_GRAY, ANSI_RESET, toolCallCounter, activeToolIds, streamingToolIds, lastStreamingArgs;
2447
+ var ANSI_GRAY, ANSI_RESET, toolCallCounter, activeToolIds, streamingToolIds, lastStreamingValues;
2417
2448
  var init_bridge = __esm({
2418
2449
  "src/cli/tui/bridge.ts"() {
2419
2450
  "use strict";
2420
- BATCH_INTERVAL_MS = 64;
2451
+ init_progressStore();
2421
2452
  ANSI_GRAY = "\x1B[90m";
2422
2453
  ANSI_RESET = "\x1B[0m";
2423
2454
  toolCallCounter = 0;
2424
2455
  activeToolIds = /* @__PURE__ */ new Map();
2425
2456
  streamingToolIds = /* @__PURE__ */ new Map();
2426
- 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
+ });
2427
2547
  }
2428
2548
  });
2429
2549
 
@@ -2501,19 +2621,26 @@ function updateLastAssistant(messages, updater) {
2501
2621
  const result = [...messages];
2502
2622
  for (let i = result.length - 1; i >= 0; i--) {
2503
2623
  if (result[i].role === "assistant") {
2504
- result[i] = updater(result[i]);
2624
+ const updated = updater(result[i]);
2625
+ if (updated === result[i]) return messages;
2626
+ result[i] = updated;
2505
2627
  return result;
2506
2628
  }
2507
2629
  }
2508
2630
  return result;
2509
2631
  }
2510
2632
  function updateToolInBlocks(blocks, toolId, updater) {
2511
- return blocks.map((b) => {
2633
+ let changed = false;
2634
+ const newBlocks = blocks.map((b) => {
2512
2635
  if (b.type === "tool" && b.toolCall.id === toolId) {
2513
- 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 };
2514
2640
  }
2515
2641
  return b;
2516
2642
  });
2643
+ return changed ? newBlocks : blocks;
2517
2644
  }
2518
2645
  function tuiReducer(state, action) {
2519
2646
  switch (action.type) {
@@ -2545,186 +2672,190 @@ function tuiReducer(state, action) {
2545
2672
  ]
2546
2673
  };
2547
2674
  case "APPEND_CONTENT": {
2548
- return {
2549
- ...state,
2550
- messages: updateLastAssistant(state.messages, (msg) => {
2551
- const blocks = [...msg.blocks];
2552
- const last = blocks[blocks.length - 1];
2553
- if (last && last.type === "text") {
2554
- blocks[blocks.length - 1] = { type: "text", text: last.text + action.text };
2555
- } else {
2556
- blocks.push({ type: "text", text: action.text });
2557
- }
2558
- return { ...msg, blocks };
2559
- })
2560
- };
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 };
2561
2688
  }
2562
2689
  case "APPEND_THOUGHT": {
2563
- return {
2564
- ...state,
2565
- messages: updateLastAssistant(state.messages, (msg) => {
2566
- const blocks = [...msg.blocks];
2567
- const last = blocks[blocks.length - 1];
2568
- if (last && last.type === "thought") {
2569
- blocks[blocks.length - 1] = { type: "thought", text: last.text + action.text };
2570
- } else {
2571
- blocks.push({ type: "thought", text: action.text });
2572
- }
2573
- return { ...msg, blocks };
2574
- })
2575
- };
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 };
2576
2703
  }
2577
2704
  case "TOOL_EXECUTING": {
2578
- return {
2579
- ...state,
2580
- messages: updateLastAssistant(state.messages, (msg) => {
2581
- const existingIdx = msg.blocks.findIndex(
2582
- (b) => b.type === "tool" && b.toolCall.id === action.id
2583
- );
2584
- if (existingIdx >= 0) {
2585
- return {
2586
- ...msg,
2587
- blocks: updateToolInBlocks(msg.blocks, action.id, (tc) => ({
2588
- ...tc,
2589
- status: "running",
2590
- params: action.params
2591
- }))
2592
- };
2593
- }
2594
- return {
2595
- ...msg,
2596
- blocks: [
2597
- ...msg.blocks,
2598
- {
2599
- type: "tool",
2600
- toolCall: {
2601
- id: action.id,
2602
- name: action.name,
2603
- params: action.params,
2604
- status: "running"
2605
- }
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"
2606
2728
  }
2607
- ]
2608
- };
2609
- })
2610
- };
2729
+ }
2730
+ ]
2731
+ };
2732
+ });
2733
+ if (newMessages2 === state.messages) return state;
2734
+ return { ...state, messages: newMessages2 };
2611
2735
  }
2612
2736
  case "TOOL_STREAMING": {
2613
- return {
2614
- ...state,
2615
- messages: updateLastAssistant(state.messages, (msg) => {
2616
- const existingIdx = msg.blocks.findIndex(
2617
- (b) => b.type === "tool" && b.toolCall.id === action.id
2618
- );
2619
- if (existingIdx >= 0) {
2620
- return {
2621
- ...msg,
2622
- blocks: updateToolInBlocks(msg.blocks, action.id, (tc) => ({
2623
- ...tc,
2624
- streamingContent: action.streamingContent
2625
- }))
2626
- };
2627
- }
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) {
2628
2746
  return {
2629
2747
  ...msg,
2630
- blocks: [
2631
- ...msg.blocks,
2632
- {
2633
- type: "tool",
2634
- toolCall: {
2635
- id: action.id,
2636
- name: action.name,
2637
- params: {},
2638
- status: "running",
2639
- streamingContent: action.streamingContent
2640
- }
2641
- }
2642
- ]
2748
+ blocks: updateToolInBlocks(msg.blocks, id, (tc) => ({
2749
+ ...tc,
2750
+ streamingContent
2751
+ }))
2643
2752
  };
2644
- })
2645
- };
2646
- }
2647
- case "TOOL_RESULT": {
2648
- return {
2649
- ...state,
2650
- messages: updateLastAssistant(state.messages, (msg) => ({
2651
- ...msg,
2652
- blocks: updateToolInBlocks(msg.blocks, action.id, (tc) => ({
2653
- ...tc,
2654
- status: "done",
2655
- resultSummary: action.resultSummary,
2656
- resultContent: action.resultContent
2657
- }))
2658
- }))
2659
- };
2660
- }
2661
- case "TOOL_DENIED": {
2662
- return {
2663
- ...state,
2664
- messages: updateLastAssistant(state.messages, (msg) => ({
2665
- ...msg,
2666
- blocks: updateToolInBlocks(msg.blocks, action.id, (tc) => ({
2667
- ...tc,
2668
- status: "denied",
2669
- denyFeedback: action.feedback
2670
- }))
2671
- }))
2672
- };
2673
- }
2674
- case "TOOL_CONFIRMING": {
2675
- return {
2676
- ...state,
2677
- messages: updateLastAssistant(state.messages, (msg) => ({
2753
+ }
2754
+ return {
2678
2755
  ...msg,
2679
2756
  blocks: [
2680
2757
  ...msg.blocks,
2681
2758
  {
2682
2759
  type: "tool",
2683
2760
  toolCall: {
2684
- id: action.id,
2685
- name: action.name,
2686
- params: action.params,
2687
- status: "confirming"
2761
+ id,
2762
+ name,
2763
+ params: {},
2764
+ status: "running",
2765
+ streamingContent
2688
2766
  }
2689
2767
  }
2690
- ],
2691
- confirmPending: {
2692
- toolName: action.name,
2693
- params: action.params,
2694
- 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
+ }
2695
2814
  }
2696
- }))
2697
- };
2815
+ ],
2816
+ confirmPending: {
2817
+ toolName: action.name,
2818
+ params: action.params,
2819
+ resolve: action.resolve
2820
+ }
2821
+ }));
2822
+ return { ...state, messages: newMessages2 };
2698
2823
  }
2699
2824
  case "CONFIRM_RESPONDED": {
2700
- return {
2701
- ...state,
2702
- messages: updateLastAssistant(state.messages, (msg) => ({
2703
- ...msg,
2704
- confirmPending: void 0
2705
- }))
2706
- };
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 };
2707
2831
  }
2708
2832
  case "FINISH_STREAMING":
2709
- return {
2710
- ...state,
2711
- messages: updateLastAssistant(state.messages, (msg) => ({
2712
- ...msg,
2713
- isStreaming: false
2714
- }))
2715
- };
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 };
2716
2839
  case "SET_RUNNING":
2840
+ if (state.isRunning === action.running) return state;
2717
2841
  return { ...state, isRunning: action.running };
2718
2842
  case "SET_ERROR":
2719
2843
  return { ...state, error: action.error, isRunning: false };
2720
2844
  case "CLEAR_ERROR":
2845
+ if (state.error === void 0) return state;
2721
2846
  return { ...state, error: void 0 };
2722
2847
  case "CLEAR_MESSAGES":
2848
+ if (state.messages.length === 0) return state;
2723
2849
  return { ...state, messages: [] };
2724
2850
  case "SET_TODO_PLAN":
2851
+ if (state.todoPlan === action.plan) return state;
2725
2852
  return { ...state, todoPlan: action.plan };
2726
2853
  case "SET_SUB_AGENT_PROGRESS":
2854
+ if (JSON.stringify(state.subAgentProgress) === JSON.stringify(action.progress)) return state;
2727
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;
2728
2859
  default:
2729
2860
  return state;
2730
2861
  }
@@ -2738,6 +2869,7 @@ var init_state = __esm({
2738
2869
  });
2739
2870
 
2740
2871
  // src/cli/tui/components/ToolCallLine.tsx
2872
+ import React from "react";
2741
2873
  import { Box, Text } from "ink";
2742
2874
  import { jsx, jsxs } from "react/jsx-runtime";
2743
2875
  function getToolParamSummary(name, params) {
@@ -2780,70 +2912,81 @@ function truncateContent(text, maxLines) {
2780
2912
  }
2781
2913
  return result;
2782
2914
  }
2783
- function ToolCallLine({ toolCall }) {
2784
- const { name, params, status, resultContent, denyFeedback } = toolCall;
2785
- const summary = getToolParamSummary(name, params);
2786
- let borderColor = "#504945";
2787
- let titleColor = "#fabd2f";
2788
- let statusColor = "#fabd2f";
2789
- let statusText = "RUNNING";
2790
- if (status === "done") {
2791
- borderColor = "#b8bb26";
2792
- titleColor = "#b8bb26";
2793
- statusColor = "#b8bb26";
2794
- statusText = "DONE";
2795
- } else if (status === "denied") {
2796
- borderColor = "#fb4934";
2797
- titleColor = "#fb4934";
2798
- statusColor = "#fb4934";
2799
- statusText = "DENIED";
2800
- } else if (status === "confirming") {
2801
- borderColor = "#fe8019";
2802
- titleColor = "#fe8019";
2803
- statusColor = "#fe8019";
2804
- statusText = "CONFIRM";
2805
- }
2806
- const contentLines = resultContent ? truncateContent(resultContent, 15) : [];
2807
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 0, marginBottom: 1, width: "100%", children: /* @__PURE__ */ jsxs(
2808
- Box,
2809
- {
2810
- flexDirection: "column",
2811
- borderStyle: "round",
2812
- borderColor,
2813
- paddingX: 1,
2814
- width: "100%",
2815
- children: [
2816
- /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
2817
- /* @__PURE__ */ jsx(Box, { backgroundColor: statusColor, width: 9, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { color: "#282828", bold: true, children: statusText }) }),
2818
- /* @__PURE__ */ jsx(Text, { color: titleColor, bold: true, children: name.toUpperCase() }),
2819
- /* @__PURE__ */ jsx(Text, { color: "#ebdbb2", dimColor: true, italic: true, wrap: "truncate-end", children: summary })
2820
- ] }),
2821
- status === "done" && contentLines.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: contentLines.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "#ebdbb2", wrap: "truncate-end", children: line }, i)) }),
2822
- status === "denied" && denyFeedback && /* @__PURE__ */ jsxs(Box, { gap: 1, marginTop: 0, children: [
2823
- /* @__PURE__ */ jsx(Text, { color: "#fb4934", bold: true, children: "REASON:" }),
2824
- /* @__PURE__ */ jsx(Text, { color: "#ebdbb2", children: denyFeedback })
2825
- ] }),
2826
- status === "confirming" && /* @__PURE__ */ jsx(Box, { marginTop: 0, children: /* @__PURE__ */ jsx(Text, { color: "#fe8019", italic: true, children: "Waiting for your permission..." }) })
2827
- ]
2828
- }
2829
- ) });
2830
- }
2915
+ var ToolCallLine;
2831
2916
  var init_ToolCallLine = __esm({
2832
2917
  "src/cli/tui/components/ToolCallLine.tsx"() {
2833
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
+ });
2834
2967
  }
2835
2968
  });
2836
2969
 
2837
2970
  // src/cli/tui/components/MessageBubble.tsx
2838
- import React from "react";
2839
- 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";
2840
2973
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2841
- var MessageBubble;
2974
+ var MemoizedMarkdown, MemoizedThought, MessageBubble;
2842
2975
  var init_MessageBubble = __esm({
2843
2976
  "src/cli/tui/components/MessageBubble.tsx"() {
2844
2977
  "use strict";
2978
+ init_ui();
2845
2979
  init_ToolCallLine();
2846
- 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 }) {
2847
2990
  const { role, blocks, isStreaming } = message;
2848
2991
  const isUser = role === "user";
2849
2992
  const label = isUser ? "USER" : "AI";
@@ -2860,11 +3003,11 @@ var init_MessageBubble = __esm({
2860
3003
  if (block.type === "text") {
2861
3004
  const text = block.text.trim();
2862
3005
  if (!text && !isStreaming) return null;
2863
- return /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, marginBottom: i < blocks.length - 1 ? 1 : 0, width: "100%", children: /* @__PURE__ */ jsx2(Text2, { color: contentColor, children: text }) }, `text-${i}`);
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}`);
2864
3007
  } else if (block.type === "thought") {
2865
3008
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 0, width: "100%", children: [
2866
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" }) }) }),
2867
- /* @__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 }) })
2868
3011
  ] }, `thought-${i}`);
2869
3012
  } else {
2870
3013
  return /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, width: "100%", children: /* @__PURE__ */ jsx2(ToolCallLine, { toolCall: block.toolCall }) }, block.toolCall.id);
@@ -2877,25 +3020,88 @@ var init_MessageBubble = __esm({
2877
3020
  }
2878
3021
  });
2879
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
+
2880
3066
  // src/cli/tui/components/ChatArea.tsx
2881
- import React2 from "react";
2882
- import { Box as Box3 } from "ink";
2883
- import { jsx as jsx3 } from "react/jsx-runtime";
2884
- 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;
2885
3071
  var init_ChatArea = __esm({
2886
3072
  "src/cli/tui/components/ChatArea.tsx"() {
2887
3073
  "use strict";
2888
3074
  init_MessageBubble();
2889
- ChatArea = React2.memo(function ChatArea2({ messages }) {
2890
- 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;
2891
3097
  });
2892
3098
  }
2893
3099
  });
2894
3100
 
2895
3101
  // src/cli/tui/components/InputArea.tsx
2896
- import { useRef, useState } from "react";
2897
- import { Box as Box4, Text as Text3, useInput } from "ink";
2898
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
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";
2899
3105
  function CleanTextInput({
2900
3106
  onSubmit,
2901
3107
  placeholder,
@@ -2905,6 +3111,7 @@ function CleanTextInput({
2905
3111
  const [value, setValue] = useState("");
2906
3112
  const [cursor, setCursor] = useState(0);
2907
3113
  const lastCtrlCAtRef = useRef(0);
3114
+ const pastedTextsRef = useRef([]);
2908
3115
  useInput((input, key) => {
2909
3116
  if (onScroll) {
2910
3117
  if (key.pageUp || key.upArrow && key.shift) {
@@ -2921,6 +3128,7 @@ function CleanTextInput({
2921
3128
  setValue("");
2922
3129
  setCursor(0);
2923
3130
  lastCtrlCAtRef.current = 0;
3131
+ pastedTextsRef.current = [];
2924
3132
  return;
2925
3133
  }
2926
3134
  const now = Date.now();
@@ -2934,12 +3142,18 @@ function CleanTextInput({
2934
3142
  return;
2935
3143
  }
2936
3144
  if (key.return) {
2937
- const trimmed = value.trim();
3145
+ let trimmed = value.trim();
2938
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
+ });
2939
3152
  onSubmit(trimmed);
2940
3153
  }
2941
3154
  setValue("");
2942
3155
  setCursor(0);
3156
+ pastedTextsRef.current = [];
2943
3157
  return;
2944
3158
  }
2945
3159
  if (key.backspace || key.delete) {
@@ -2970,6 +3184,16 @@ function CleanTextInput({
2970
3184
  setCursor(0);
2971
3185
  return;
2972
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
+ }
2973
3197
  if (key.ctrl || key.meta || key.escape) return;
2974
3198
  if (!input || input.length === 0) return;
2975
3199
  if (input.includes("\x1B") || input.includes("\0")) return;
@@ -2985,94 +3209,119 @@ function CleanTextInput({
2985
3209
  setCursor((prev) => prev + input.length);
2986
3210
  });
2987
3211
  if (value.length === 0) {
2988
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
2989
- /* @__PURE__ */ jsx4(Text3, { inverse: true, children: " " }),
2990
- /* @__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 || "" })
2991
3215
  ] });
2992
3216
  }
2993
3217
  const before = value.slice(0, cursor);
2994
3218
  const at = cursor < value.length ? value[cursor] : " ";
2995
3219
  const after = cursor < value.length ? value.slice(cursor + 1) : "";
2996
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
2997
- /* @__PURE__ */ jsx4(Text3, { children: before }),
2998
- /* @__PURE__ */ jsx4(Text3, { inverse: true, children: at }),
2999
- /* @__PURE__ */ jsx4(Text3, { children: after })
3000
- ] });
3001
- }
3002
- function InputArea({ onSubmit, isRunning, onExitRequest, onScroll }) {
3003
- return /* @__PURE__ */ jsxs3(Box4, { paddingX: 0, children: [
3004
- /* @__PURE__ */ jsx4(Box4, { backgroundColor: isRunning ? "#504945" : "#b8bb26", paddingX: 1, marginRight: 1, children: /* @__PURE__ */ jsx4(Text3, { color: "#282828", bold: true, children: isRunning ? " WAIT " : " INPUT " }) }),
3005
- /* @__PURE__ */ jsx4(Box4, { flexGrow: 1, children: isRunning ? /* @__PURE__ */ jsx4(Box4, { flexGrow: 1, onWheel: (event) => {
3006
- }, children: /* @__PURE__ */ jsx4(Text3, { color: "#a89984", italic: true, children: "Thinking..." }) }) : /* @__PURE__ */ jsx4(
3007
- CleanTextInput,
3008
- {
3009
- onSubmit,
3010
- placeholder: "Type a message or /command...",
3011
- onExitRequest,
3012
- onScroll
3013
- }
3014
- ) })
3220
+ return /* @__PURE__ */ jsxs5(Text4, { wrap: "wrap", children: [
3221
+ before,
3222
+ /* @__PURE__ */ jsx5(Text4, { inverse: true, children: at }),
3223
+ after
3015
3224
  ] });
3016
3225
  }
3226
+ var InputArea;
3017
3227
  var init_InputArea = __esm({
3018
3228
  "src/cli/tui/components/InputArea.tsx"() {
3019
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
+ });
3020
3247
  }
3021
3248
  });
3022
3249
 
3023
3250
  // src/cli/tui/components/StatusBar.tsx
3024
- import { Box as Box5, Text as Text4 } from "ink";
3025
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
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";
3026
3254
  function formatTokens(n) {
3027
3255
  if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
3028
3256
  if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
3029
3257
  return String(n);
3030
3258
  }
3031
- function StatusBar({ isRunning, modelName, todoPlan, subAgentProgress }) {
3032
- const todoProgress = todoPlan ? `${todoPlan.items.filter((i) => i.status === "completed").length}/${todoPlan.items.length}` : null;
3033
- return /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, children: [
3034
- /* @__PURE__ */ jsx5(Box5, { backgroundColor: "#3c3836", paddingX: 1, children: /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", bold: true, children: " ZENCODE " }) }),
3035
- /* @__PURE__ */ jsxs4(Box5, { backgroundColor: "#504945", paddingX: 1, flexGrow: 1, children: [
3036
- /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: modelName }),
3037
- isRunning && !subAgentProgress && /* @__PURE__ */ jsxs4(Fragment2, { children: [
3038
- /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: " \u2502 " }),
3039
- /* @__PURE__ */ jsx5(Text4, { color: "#8ec07c", children: "\u25CF thinking..." })
3040
- ] }),
3041
- subAgentProgress && /* @__PURE__ */ jsxs4(Fragment2, { children: [
3042
- /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: " \u2502 " }),
3043
- /* @__PURE__ */ jsxs4(Text4, { color: "#b8bb26", children: [
3044
- "Agents: ",
3045
- subAgentProgress.completed + subAgentProgress.failed,
3046
- "/",
3047
- subAgentProgress.total
3048
- ] }),
3049
- /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: " \u2502 " }),
3050
- /* @__PURE__ */ jsxs4(Text4, { color: "#83a598", children: [
3051
- "tokens: ",
3052
- formatTokens(subAgentProgress.tokens)
3053
- ] })
3054
- ] }),
3055
- todoProgress && /* @__PURE__ */ jsxs4(Fragment2, { children: [
3056
- /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: " \u2502 " }),
3057
- /* @__PURE__ */ jsxs4(Text4, { color: "#fabd2f", children: [
3058
- "Plan: ",
3059
- todoProgress
3060
- ] })
3061
- ] })
3062
- ] }),
3063
- /* @__PURE__ */ jsx5(Box5, { backgroundColor: "#3c3836", paddingX: 1, children: /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: "/help" }) })
3064
- ] });
3065
- }
3259
+ var StatusBar;
3066
3260
  var init_StatusBar = __esm({
3067
3261
  "src/cli/tui/components/StatusBar.tsx"() {
3068
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
+ });
3069
3318
  }
3070
3319
  });
3071
3320
 
3072
3321
  // src/cli/tui/components/ConfirmPrompt.tsx
3073
- import { useState as useState2 } from "react";
3074
- import { Box as Box6, Text as Text5, useInput as useInput2 } from "ink";
3075
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
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";
3076
3325
  function getToolDetails(toolName, params) {
3077
3326
  const lines = [];
3078
3327
  let label = toolName;
@@ -3118,8 +3367,8 @@ function getToolDetails(toolName, params) {
3118
3367
  return { lines, label };
3119
3368
  }
3120
3369
  function FeedbackInput({ onSubmit }) {
3121
- const [value, setValue] = useState2("");
3122
- const [cursor, setCursor] = useState2(0);
3370
+ const [value, setValue] = useState3("");
3371
+ const [cursor, setCursor] = useState3(0);
3123
3372
  useInput2((input, key) => {
3124
3373
  if (key.return) {
3125
3374
  onSubmit(value);
@@ -3158,23 +3407,23 @@ function FeedbackInput({ onSubmit }) {
3158
3407
  setCursor((prev) => prev + input.length);
3159
3408
  });
3160
3409
  if (value.length === 0) {
3161
- return /* @__PURE__ */ jsxs5(Fragment3, { children: [
3162
- /* @__PURE__ */ jsx6(Text5, { inverse: true, children: " " }),
3163
- /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "\u8F93\u5165\u53CD\u9988\u6307\u4EE4\u7ED9 AI..." })
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..." })
3164
3413
  ] });
3165
3414
  }
3166
3415
  const before = value.slice(0, cursor);
3167
3416
  const at = cursor < value.length ? value[cursor] : " ";
3168
3417
  const after = cursor < value.length ? value.slice(cursor + 1) : "";
3169
- return /* @__PURE__ */ jsxs5(Fragment3, { children: [
3170
- /* @__PURE__ */ jsx6(Text5, { children: before }),
3171
- /* @__PURE__ */ jsx6(Text5, { inverse: true, children: at }),
3172
- /* @__PURE__ */ jsx6(Text5, { children: after })
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 })
3173
3422
  ] });
3174
3423
  }
3175
3424
  function ConfirmPrompt({ confirm, onRespond }) {
3176
- const [selected, setSelected] = useState2(0);
3177
- const [feedbackMode, setFeedbackMode] = useState2(false);
3425
+ const [selected, setSelected] = useState3(0);
3426
+ const [feedbackMode, setFeedbackMode] = useState3(false);
3178
3427
  useInput2((input, key) => {
3179
3428
  if (feedbackMode) return;
3180
3429
  if (key.leftArrow) {
@@ -3197,21 +3446,21 @@ function ConfirmPrompt({ confirm, onRespond }) {
3197
3446
  }, { isActive: !feedbackMode });
3198
3447
  const { lines, label } = getToolDetails(confirm.toolName, confirm.params);
3199
3448
  if (feedbackMode) {
3200
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingX: 0, marginY: 1, children: [
3201
- /* @__PURE__ */ jsx6(Box6, { backgroundColor: "#fe8019", paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { color: "#282828", bold: true, children: " FEEDBACK " }) }),
3202
- /* @__PURE__ */ jsxs5(
3203
- 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,
3204
3453
  {
3205
3454
  flexDirection: "column",
3206
3455
  borderStyle: "round",
3207
3456
  borderColor: "#fe8019",
3208
3457
  paddingX: 1,
3209
3458
  children: [
3210
- /* @__PURE__ */ jsx6(Text5, { bold: true, color: "#fabd2f", children: label }),
3211
- lines.map((line, i) => /* @__PURE__ */ jsx6(Text5, { color: "#a89984", children: line }, i)),
3212
- /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, gap: 1, children: [
3213
- /* @__PURE__ */ jsx6(Text5, { color: "#83a598", bold: true, children: "Reason: " }),
3214
- /* @__PURE__ */ jsx6(
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(
3215
3464
  FeedbackInput,
3216
3465
  {
3217
3466
  onSubmit: (text) => {
@@ -3228,29 +3477,29 @@ function ConfirmPrompt({ confirm, onRespond }) {
3228
3477
  ]
3229
3478
  }
3230
3479
  ),
3231
- /* @__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" }) })
3232
3481
  ] });
3233
3482
  }
3234
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingX: 0, marginY: 1, children: [
3235
- /* @__PURE__ */ jsx6(Box6, { backgroundColor: "#fe8019", paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { color: "#282828", bold: true, children: " CONFIRMATION REQUIRED " }) }),
3236
- /* @__PURE__ */ jsxs5(
3237
- 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,
3238
3487
  {
3239
3488
  flexDirection: "column",
3240
3489
  borderStyle: "round",
3241
3490
  borderColor: "#fe8019",
3242
3491
  paddingX: 1,
3243
3492
  children: [
3244
- /* @__PURE__ */ jsx6(Text5, { bold: true, color: "#fabd2f", children: label }),
3245
- lines.map((line, i) => /* @__PURE__ */ jsx6(Text5, { color: "#a89984", children: line }, i)),
3246
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, gap: 2, justifyContent: "center", children: OPTIONS.map((opt, i) => {
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) => {
3247
3496
  const isSelected = i === selected;
3248
3497
  const optColor = opt.key === "deny" ? "#fb4934" : opt.key === "allow" ? "#b8bb26" : "#8ec07c";
3249
- return /* @__PURE__ */ jsx6(Box6, { children: isSelected ? /* @__PURE__ */ jsxs5(Text5, { bold: true, backgroundColor: optColor, color: "#282828", children: [
3498
+ return /* @__PURE__ */ jsx7(Box7, { children: isSelected ? /* @__PURE__ */ jsxs7(Text6, { bold: true, backgroundColor: optColor, color: "#282828", children: [
3250
3499
  " ",
3251
3500
  opt.label,
3252
3501
  " "
3253
- ] }) : /* @__PURE__ */ jsxs5(Text5, { color: optColor, children: [
3502
+ ] }) : /* @__PURE__ */ jsxs7(Text6, { color: optColor, children: [
3254
3503
  " ",
3255
3504
  opt.label,
3256
3505
  " "
@@ -3259,7 +3508,7 @@ function ConfirmPrompt({ confirm, onRespond }) {
3259
3508
  ]
3260
3509
  }
3261
3510
  ),
3262
- /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "\u2190 \u2192 select \xB7 Enter confirm \xB7 y/n shortcuts \xB7 Tab feedback" }) })
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" }) })
3263
3512
  ] });
3264
3513
  }
3265
3514
  var OPTIONS;
@@ -3275,16 +3524,17 @@ var init_ConfirmPrompt = __esm({
3275
3524
  });
3276
3525
 
3277
3526
  // src/cli/tui/components/TodoPanel.tsx
3278
- import { Box as Box7, Text as Text6 } from "ink";
3279
- 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";
3280
3530
  function getStatusIcon(status) {
3281
3531
  switch (status) {
3282
3532
  case "completed":
3283
- return "\u2705";
3533
+ return "*";
3284
3534
  case "in-progress":
3285
- return "\u23F3";
3535
+ return ">";
3286
3536
  default:
3287
- return "\u26AA";
3537
+ return " ";
3288
3538
  }
3289
3539
  }
3290
3540
  function getStatusColor(status) {
@@ -3297,100 +3547,89 @@ function getStatusColor(status) {
3297
3547
  return "#928374";
3298
3548
  }
3299
3549
  }
3300
- function TodoPanel({ plan }) {
3301
- const completed = plan.items.filter((i) => i.status === "completed").length;
3302
- const total = plan.items.length;
3303
- return /* @__PURE__ */ jsxs6(
3304
- Box7,
3305
- {
3306
- flexDirection: "column",
3307
- borderStyle: "round",
3308
- borderColor: "#83a598",
3309
- paddingX: 1,
3310
- marginY: 1,
3311
- children: [
3312
- /* @__PURE__ */ jsxs6(Box7, { justifyContent: "space-between", marginBottom: 1, children: [
3313
- /* @__PURE__ */ jsx7(Text6, { bold: true, color: "#83a598", children: "\u{1F4CB} PROJECT PLAN" }),
3314
- /* @__PURE__ */ jsxs6(Text6, { color: "#ebdbb2", children: [
3315
- completed,
3316
- "/",
3317
- total,
3318
- " tasks"
3319
- ] })
3320
- ] }),
3321
- plan.items.map((item) => /* @__PURE__ */ jsxs6(Box7, { gap: 1, children: [
3322
- /* @__PURE__ */ jsx7(Text6, { color: getStatusColor(item.status), children: getStatusIcon(item.status) }),
3323
- /* @__PURE__ */ jsx7(
3324
- Text6,
3325
- {
3326
- color: item.status === "completed" ? "#b8bb26" : item.status === "in-progress" ? "#fabd2f" : "#ebdbb2",
3327
- dimColor: item.status === "pending",
3328
- strikethrough: item.status === "completed",
3329
- children: item.title
3330
- }
3331
- )
3332
- ] }, item.id))
3333
- ]
3334
- }
3335
- );
3336
- }
3550
+ var TodoPanel;
3337
3551
  var init_TodoPanel = __esm({
3338
3552
  "src/cli/tui/components/TodoPanel.tsx"() {
3339
3553
  "use strict";
3340
- }
3341
- });
3342
-
3343
- // src/cli/tui/components/Header.tsx
3344
- import { Box as Box8, Text as Text7 } from "ink";
3345
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3346
- function Header({ modelName }) {
3347
- return /* @__PURE__ */ jsx8(
3348
- Box8,
3349
- {
3350
- flexDirection: "column",
3351
- width: "100%",
3352
- borderStyle: "round",
3353
- borderColor: "#504945",
3354
- paddingX: 1,
3355
- marginTop: 0,
3356
- marginBottom: 1,
3357
- children: /* @__PURE__ */ jsxs7(Box8, { justifyContent: "space-between", children: [
3358
- /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
3359
- /* @__PURE__ */ jsx8(Text7, { bold: true, color: "#fe8019", children: "ZEN CODE" }),
3360
- /* @__PURE__ */ jsx8(Text7, { color: "#a89984", dimColor: true, children: "v0.2.1" })
3361
- ] }),
3362
- /* @__PURE__ */ jsx8(Text7, { color: "#83a598", bold: true, children: modelName })
3363
- ] })
3364
- }
3365
- );
3366
- }
3367
- var init_Header = __esm({
3368
- "src/cli/tui/components/Header.tsx"() {
3369
- "use strict";
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
+ });
3370
3599
  }
3371
3600
  });
3372
3601
 
3373
3602
  // src/cli/tui/App.tsx
3374
- import { useReducer, useCallback, useEffect, useRef as useRef2, useState as useState3 } from "react";
3375
- import { Box as Box9, Text as Text8, useInput as useInput3, useStdout } from "ink";
3376
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
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";
3377
3606
  function App({ config, client, agent, registry, todoStore, subAgentTracker, agentRegistry, skillRegistry }) {
3378
- const { stdout } = useStdout();
3379
- const [, setWidth] = useState3(stdout.columns);
3380
- 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;
3381
3613
  const onResize = () => {
3382
- 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);
3383
3622
  };
3384
3623
  stdout.on("resize", onResize);
3385
3624
  return () => {
3386
3625
  stdout.off("resize", onResize);
3626
+ clearTimeout(timer);
3387
3627
  };
3388
3628
  }, [stdout]);
3389
3629
  const [state, dispatch] = useReducer(
3390
3630
  tuiReducer,
3391
3631
  createInitialState(config.model)
3392
3632
  );
3393
- const [resetKey, setResetKey] = useState3(0);
3394
3633
  const currentCallbacksRef = useRef2(null);
3395
3634
  const agentRef = useRef2(agent);
3396
3635
  const todoStoreRef = useRef2(todoStore);
@@ -3398,12 +3637,12 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3398
3637
  agentRef.current = agent;
3399
3638
  todoStoreRef.current = todoStore;
3400
3639
  subAgentTrackerRef.current = subAgentTracker;
3401
- useEffect(() => {
3640
+ useEffect2(() => {
3402
3641
  return todoStoreRef.current.subscribe((plan) => {
3403
3642
  dispatch({ type: "SET_TODO_PLAN", plan });
3404
3643
  });
3405
3644
  }, []);
3406
- useEffect(() => {
3645
+ useEffect2(() => {
3407
3646
  let timer = null;
3408
3647
  let latest = null;
3409
3648
  const unsub = subAgentTrackerRef.current.subscribe((progress) => {
@@ -3433,7 +3672,7 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3433
3672
  (found, msg) => found || msg.confirmPending,
3434
3673
  void 0
3435
3674
  );
3436
- useEffect(() => {
3675
+ useEffect2(() => {
3437
3676
  setStructuredConfirmHandler((toolName, params) => {
3438
3677
  return new Promise((resolve10) => {
3439
3678
  const id = `confirm-${Date.now()}`;
@@ -3493,7 +3732,7 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3493
3732
  await runAgent(expandedPrompt);
3494
3733
  return;
3495
3734
  }
3496
- handleSlashCommand2(text, {
3735
+ handleSlashCommand(text, {
3497
3736
  config,
3498
3737
  agent: agentRef.current,
3499
3738
  registry,
@@ -3536,9 +3775,10 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3536
3775
  return;
3537
3776
  }
3538
3777
  if (input === "c" && key.ctrl) {
3539
- if (state.isRunning) {
3778
+ if (state.isRunning || confirmPending) {
3540
3779
  agentRef.current.interrupt();
3541
3780
  currentCallbacksRef.current?._stopBatcher?.();
3781
+ dispatch({ type: "CONFIRM_RESPONDED", id: "" });
3542
3782
  dispatch({ type: "SET_RUNNING", running: false });
3543
3783
  dispatch({ type: "FINISH_STREAMING" });
3544
3784
  return;
@@ -3548,18 +3788,21 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3548
3788
  process.exit(0);
3549
3789
  }
3550
3790
  });
3551
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", paddingX: 0, width: "100%", children: [
3552
- /* @__PURE__ */ jsxs8(Box9, { paddingX: 1, flexDirection: "column", children: [
3553
- /* @__PURE__ */ jsx9(Header, { modelName: state.modelName }),
3554
- /* @__PURE__ */ jsx9(ChatArea, { messages: state.messages })
3555
- ] }),
3556
- state.error && /* @__PURE__ */ jsxs8(Box9, { borderStyle: "round", borderColor: "#fb4934", paddingX: 1, marginBottom: 1, children: [
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: [
3557
3800
  /* @__PURE__ */ jsx9(Text8, { color: "#fb4934", bold: true, children: "ERROR: " }),
3558
3801
  /* @__PURE__ */ jsx9(Text8, { color: "#fb4934", children: state.error })
3559
3802
  ] }),
3560
3803
  confirmPending && /* @__PURE__ */ jsx9(ConfirmPrompt, { confirm: confirmPending, onRespond: handleConfirmResponse }),
3561
3804
  state.todoPlan && /* @__PURE__ */ jsx9(TodoPanel, { plan: state.todoPlan }),
3562
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(
3805
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 0, children: /* @__PURE__ */ jsx9(
3563
3806
  InputArea,
3564
3807
  {
3565
3808
  onSubmit: handleSubmit,
@@ -3567,7 +3810,7 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3567
3810
  onExitRequest: () => process.exit(0)
3568
3811
  }
3569
3812
  ) }),
3570
- /* @__PURE__ */ jsx9(Box9, { marginTop: 0, children: /* @__PURE__ */ jsx9(
3813
+ /* @__PURE__ */ jsx9(
3571
3814
  StatusBar,
3572
3815
  {
3573
3816
  isRunning: state.isRunning,
@@ -3575,125 +3818,9 @@ function App({ config, client, agent, registry, todoStore, subAgentTracker, agen
3575
3818
  todoPlan: state.todoPlan,
3576
3819
  subAgentProgress: state.subAgentProgress
3577
3820
  }
3578
- ) })
3821
+ )
3579
3822
  ] }, resetKey);
3580
3823
  }
3581
- function handleSlashCommand2(input, ctx) {
3582
- const { config, agent, registry, dispatch, setResetKey, client, todoStore, subAgentTracker, agentRegistry, skillRegistry } = ctx;
3583
- const parts = input.trim().split(/\s+/);
3584
- const command = parts[0];
3585
- switch (command) {
3586
- case "/help":
3587
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3588
- dispatch({ type: "START_ASSISTANT" });
3589
- {
3590
- let helpText = `\u53EF\u7528\u547D\u4EE4:
3591
- /help \u663E\u793A\u6B64\u5E2E\u52A9\u4FE1\u606F
3592
- /skills \u5217\u51FA\u6240\u6709\u53EF\u7528\u6280\u80FD
3593
- /agents \u5217\u51FA\u6240\u6709\u53EF\u7528\u5B50 Agent
3594
- /parallel \u5207\u6362\u5E76\u884C\u5B50 Agent \u529F\u80FD on/off
3595
- /todo \u5207\u6362 todo \u8BA1\u5212\u529F\u80FD on/off
3596
- /clear \u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2
3597
- /info \u663E\u793A\u5F53\u524D\u914D\u7F6E
3598
- Ctrl+C \u53D6\u6D88\u5F53\u524D\u8BF7\u6C42 / \u9000\u51FA
3599
- Ctrl+D \u9000\u51FA`;
3600
- const skills = skillRegistry.list();
3601
- if (skills.length > 0) {
3602
- helpText += `
3603
-
3604
- \u53EF\u7528\u6280\u80FD:
3605
- ${skills.map((s) => ` /${s.name} ${s.description}`).join("\n")}`;
3606
- }
3607
- dispatch({ type: "APPEND_CONTENT", text: helpText });
3608
- }
3609
- dispatch({ type: "FINISH_STREAMING" });
3610
- break;
3611
- case "/skills": {
3612
- const skills = skillRegistry.list();
3613
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3614
- dispatch({ type: "START_ASSISTANT" });
3615
- if (skills.length === 0) {
3616
- dispatch({ type: "APPEND_CONTENT", text: "\u6682\u65E0\u53EF\u7528\u6280\u80FD\u3002\u5728 ~/.zencode/skills/ \u6216 .zencode/skills/ \u653E\u7F6E YAML \u6587\u4EF6\u6DFB\u52A0\u6280\u80FD\u3002" });
3617
- } else {
3618
- const lines = skills.map((s) => ` /${s.name}: ${s.description}`);
3619
- dispatch({ type: "APPEND_CONTENT", text: `\u53EF\u7528\u6280\u80FD (${skills.length}):
3620
- ${lines.join("\n")}` });
3621
- }
3622
- dispatch({ type: "FINISH_STREAMING" });
3623
- break;
3624
- }
3625
- case "/agents": {
3626
- const agents = agentRegistry.list();
3627
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3628
- dispatch({ type: "START_ASSISTANT" });
3629
- if (agents.length === 0) {
3630
- dispatch({ type: "APPEND_CONTENT", text: "\u6682\u65E0\u53EF\u7528\u5B50 Agent\u3002" });
3631
- } else {
3632
- const lines = agents.map((a) => ` ${a.name}: ${a.description} [tools: ${a.tools.join(", ")}]`);
3633
- dispatch({ type: "APPEND_CONTENT", text: `\u53EF\u7528\u5B50 Agent (${agents.length}):
3634
- ${lines.join("\n")}` });
3635
- }
3636
- dispatch({ type: "FINISH_STREAMING" });
3637
- break;
3638
- }
3639
- case "/clear":
3640
- agent.getConversation().clear();
3641
- dispatch({ type: "CLEAR_MESSAGES" });
3642
- setResetKey((prev) => prev + 1);
3643
- break;
3644
- case "/parallel": {
3645
- const current = config.features.parallel_agents;
3646
- const next = current === "on" ? "off" : "on";
3647
- config.features.parallel_agents = next;
3648
- if (next === "off") {
3649
- registry.unregister("spawn-agents");
3650
- } else if (!registry.has("spawn-agents")) {
3651
- registry.register(createSpawnAgentsTool(client, registry, config, subAgentTracker));
3652
- }
3653
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3654
- dispatch({ type: "START_ASSISTANT" });
3655
- dispatch({ type: "APPEND_CONTENT", text: `\u5E76\u884C\u5B50 Agent \u529F\u80FD\u5DF2${next === "on" ? "\u5F00\u542F" : "\u5173\u95ED"}` });
3656
- dispatch({ type: "FINISH_STREAMING" });
3657
- break;
3658
- }
3659
- case "/todo": {
3660
- const current = config.features.todo;
3661
- const next = current === "on" ? "off" : "on";
3662
- config.features.todo = next;
3663
- if (next === "off") {
3664
- registry.unregister("todo");
3665
- } else if (!registry.has("todo")) {
3666
- registry.register(createTodoTool(todoStore));
3667
- }
3668
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3669
- dispatch({ type: "START_ASSISTANT" });
3670
- dispatch({ type: "APPEND_CONTENT", text: `Todo \u8BA1\u5212\u529F\u80FD\u5DF2${next === "on" ? "\u5F00\u542F" : "\u5173\u95ED"}` });
3671
- dispatch({ type: "FINISH_STREAMING" });
3672
- break;
3673
- }
3674
- case "/info":
3675
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3676
- dispatch({ type: "START_ASSISTANT" });
3677
- dispatch({
3678
- type: "APPEND_CONTENT",
3679
- text: `\u6A21\u578B: ${config.model}
3680
- \u57FA\u7840 URL: ${config.base_url}
3681
- \u5B50 Agent: ${agentRegistry.listNames().join(", ") || "\u65E0"}
3682
- \u6280\u80FD: ${skillRegistry.listNames().map((n) => "/" + n).join(", ") || "\u65E0"}`
3683
- });
3684
- dispatch({ type: "FINISH_STREAMING" });
3685
- break;
3686
- default:
3687
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3688
- dispatch({ type: "START_ASSISTANT" });
3689
- dispatch({
3690
- type: "APPEND_CONTENT",
3691
- text: `\u672A\u77E5\u547D\u4EE4: ${command}\u3002\u8F93\u5165 /help \u67E5\u770B\u5E2E\u52A9\u3002`
3692
- });
3693
- dispatch({ type: "FINISH_STREAMING" });
3694
- break;
3695
- }
3696
- }
3697
3824
  var init_App = __esm({
3698
3825
  "src/cli/tui/App.tsx"() {
3699
3826
  "use strict";
@@ -3701,14 +3828,11 @@ var init_App = __esm({
3701
3828
  init_permission();
3702
3829
  init_state();
3703
3830
  init_bridge();
3704
- init_spawn_agents();
3705
- init_todo();
3706
3831
  init_ChatArea();
3707
3832
  init_InputArea();
3708
3833
  init_StatusBar();
3709
3834
  init_ConfirmPrompt();
3710
3835
  init_TodoPanel();
3711
- init_Header();
3712
3836
  }
3713
3837
  });
3714
3838
 
@@ -3902,69 +4026,9 @@ init_spawn_agents();
3902
4026
  init_todo();
3903
4027
  init_dispatch();
3904
4028
  init_bridge();
4029
+ init_ui();
3905
4030
  import * as readline from "readline";
3906
-
3907
- // src/cli/ui.ts
3908
- import chalk2 from "chalk";
3909
- import ora from "ora";
3910
- import { marked } from "marked";
3911
- import * as _markedTerminal from "marked-terminal";
3912
- import { createTwoFilesPatch } from "diff";
3913
- var markedTerminal2 = _markedTerminal.markedTerminal;
3914
- marked.use(markedTerminal2());
3915
- function printStream(text) {
3916
- process.stdout.write(text);
3917
- }
3918
- function printToolCall(toolName, params) {
3919
- let detail = "";
3920
- if (toolName === "bash" && params["command"]) {
3921
- detail = ` ${chalk2.dim(String(params["command"]).slice(0, 80))}`;
3922
- } else if ((toolName === "read-file" || toolName === "write-file" || toolName === "edit-file") && params["path"]) {
3923
- detail = ` ${chalk2.dim(String(params["path"]))}`;
3924
- } else if (toolName === "glob" && params["pattern"]) {
3925
- detail = ` ${chalk2.dim(String(params["pattern"]))}`;
3926
- } else if (toolName === "grep" && params["pattern"]) {
3927
- detail = ` ${chalk2.dim(String(params["pattern"]))}`;
3928
- } else if (toolName === "spawn-agents" && params["tasks"]) {
3929
- const tasks = params["tasks"];
3930
- detail = ` ${chalk2.dim(`${tasks.length} \u4E2A\u5E76\u884C\u4EFB\u52A1`)}`;
3931
- } else if (toolName === "todo" && params["action"]) {
3932
- const action = String(params["action"]);
3933
- const id = params["id"] ? ` [${params["id"]}]` : "";
3934
- detail = ` ${chalk2.dim(`${action}${id}`)}`;
3935
- }
3936
- const icon = toolName === "spawn-agents" ? "\u26A1" : toolName === "todo" ? "\u{1F4CB}" : "\u2699";
3937
- console.log(chalk2.yellow(` ${icon} ${toolName}`) + detail);
3938
- }
3939
- function printToolResult(toolName, result, truncated) {
3940
- if (truncated) {
3941
- console.log(chalk2.dim(` \u2713 ${toolName} (\u8F93\u51FA\u5DF2\u622A\u65AD)`));
3942
- } else {
3943
- const lines = result.split("\n").length;
3944
- console.log(chalk2.dim(` \u2713 ${toolName} (${lines} \u884C)`));
3945
- }
3946
- }
3947
- function printError(message) {
3948
- console.error(chalk2.red(`\u2717 ${message}`));
3949
- }
3950
- function printInfo(message) {
3951
- console.log(chalk2.cyan(`\u2139 ${message}`));
3952
- }
3953
- function printWarning(message) {
3954
- console.log(chalk2.yellow(`\u26A0 ${message}`));
3955
- }
3956
- function printSuccess(message) {
3957
- console.log(chalk2.green(`\u2713 ${message}`));
3958
- }
3959
- function printWelcome(modelName) {
3960
- console.log(chalk2.bold.cyan("\n ZenCode") + chalk2.dim(" - \u6781\u7B80 AI \u7F16\u7A0B\u52A9\u624B\n"));
3961
- console.log(chalk2.dim(` \u6A21\u578B: ${modelName}`));
3962
- console.log(chalk2.dim(` \u8F93\u5165 /help \u67E5\u770B\u547D\u4EE4\uFF0CCtrl+C \u9000\u51FA
3963
- `));
3964
- }
3965
-
3966
- // src/cli/repl.ts
3967
- function handleSlashCommand(input, context) {
4031
+ function handleSlashCommand2(input, context) {
3968
4032
  const parts = input.trim().split(/\s+/);
3969
4033
  const command = parts[0];
3970
4034
  switch (command) {
@@ -4156,7 +4220,7 @@ async function startRepl(options) {
4156
4220
  rl.prompt();
4157
4221
  return;
4158
4222
  }
4159
- const handled = handleSlashCommand(input, {
4223
+ const handled = handleSlashCommand2(input, {
4160
4224
  config,
4161
4225
  registry,
4162
4226
  client,
@@ -4232,6 +4296,7 @@ init_spawn_agents();
4232
4296
  init_todo();
4233
4297
  init_dispatch();
4234
4298
  init_bridge();
4299
+ init_ui();
4235
4300
  async function runOnce(prompt, config) {
4236
4301
  const agentRegistry = new SubAgentConfigRegistry();
4237
4302
  for (const agentConfig of loadAllAgentConfigs()) {
@@ -4295,7 +4360,7 @@ async function runOnce(prompt, config) {
4295
4360
  }
4296
4361
  function createCli() {
4297
4362
  const program2 = new Command();
4298
- program2.name("zencode").description("\u6781\u7B80 CLI AI \u7F16\u7A0B\u5DE5\u5177").version("0.2.3").option("-m, --model <model>", "\u6307\u5B9A\u6A21\u578B\u540D\u79F0").option("-k, --api-key <key>", "API \u5BC6\u94A5").option("-u, --base-url <url>", "API \u57FA\u7840 URL").option("--simple", "\u4F7F\u7528\u7B80\u5355 REPL \u6A21\u5F0F\uFF08\u975E\u5168\u5C4F TUI\uFF09").argument("[prompt...]", "\u76F4\u63A5\u6267\u884C\u7684\u63D0\u793A\u8BCD\uFF08\u975E\u4EA4\u4E92\u5F0F\uFF09").action(async (promptParts, opts) => {
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) => {
4299
4364
  const config = loadConfig(opts);
4300
4365
  if (!config.api_key) {
4301
4366
  printError("\u672A\u8BBE\u7F6E API \u5BC6\u94A5\u3002\u8BF7\u901A\u8FC7\u4EE5\u4E0B\u65B9\u5F0F\u4E4B\u4E00\u8BBE\u7F6E\uFF1A");
@@ -4311,6 +4376,7 @@ function createCli() {
4311
4376
  await startRepl({ config });
4312
4377
  } else {
4313
4378
  const { startTui: startTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
4379
+ process.stdout.write("\x1Bc");
4314
4380
  await startTui2({ config });
4315
4381
  }
4316
4382
  });