@zhongqian97-code/ecode 0.5.60 → 0.5.62

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.
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ const _ew=process.emitWarning.bind(process);process.emitWarning=function(w,...a){if((w?.message??w)?.includes?.('punycode'))return;_ew(w,...a);};
3
+
4
+ // src/ui/altScreen.ts
5
+ import { writeSync as fsWriteSync } from "fs";
6
+ var ENTER_ALT_SCREEN_SEQ = "\x1B[?1049h\x1B[H";
7
+ var EXIT_ALT_SCREEN_SEQ = "\x1B[?1049l";
8
+ var DISABLE_TERMINAL_MODES_SEQ = "\x1B[?1004l\x1B[?1002l\x1B[?1006l";
9
+ function installCrashCleanup(opts = {}) {
10
+ const writeSync = opts.writeSync ?? ((s) => {
11
+ try {
12
+ fsWriteSync(1, s);
13
+ } catch {
14
+ }
15
+ });
16
+ const signals = opts.signals ?? ["SIGINT", "SIGTERM", "SIGHUP"];
17
+ let cleaned = false;
18
+ const cleanup = () => {
19
+ if (cleaned) return;
20
+ cleaned = true;
21
+ writeSync(DISABLE_TERMINAL_MODES_SEQ);
22
+ writeSync(EXIT_ALT_SCREEN_SEQ);
23
+ };
24
+ const onExit = () => {
25
+ cleanup();
26
+ };
27
+ process.on("exit", onExit);
28
+ const sigHandlers = signals.map((sig) => {
29
+ const h = () => {
30
+ cleanup();
31
+ process.exit(0);
32
+ };
33
+ process.on(sig, h);
34
+ return [sig, h];
35
+ });
36
+ return () => {
37
+ process.off("exit", onExit);
38
+ for (const [sig, h] of sigHandlers) process.off(sig, h);
39
+ };
40
+ }
41
+ export {
42
+ DISABLE_TERMINAL_MODES_SEQ,
43
+ ENTER_ALT_SCREEN_SEQ,
44
+ EXIT_ALT_SCREEN_SEQ,
45
+ installCrashCleanup
46
+ };
@@ -126,6 +126,23 @@ async function loadSkillsFromDir(dir) {
126
126
  return skills.sort((a, b) => a.name.localeCompare(b.name));
127
127
  }
128
128
 
129
+ // src/llm.ts
130
+ function buildAssistantMessage(assistantText, toolCalls, assistantReasoning) {
131
+ const msg = {
132
+ role: "assistant",
133
+ content: assistantText || (toolCalls.length > 0 ? null : ""),
134
+ reasoning_content: assistantReasoning ?? ""
135
+ };
136
+ if (toolCalls.length > 0) {
137
+ msg.tool_calls = toolCalls.map((tc) => ({
138
+ id: tc.id,
139
+ type: "function",
140
+ function: { name: tc.name, arguments: tc.arguments }
141
+ }));
142
+ }
143
+ return msg;
144
+ }
145
+
129
146
  // src/tools/read.ts
130
147
  import * as fs from "fs/promises";
131
148
  var READ_TOOL = {
@@ -1708,6 +1725,7 @@ ${prompt}` : prompt;
1708
1725
  export {
1709
1726
  isTrustedSkillPath,
1710
1727
  loadSkillsFromDir,
1728
+ buildAssistantMessage,
1711
1729
  READ_TOOL,
1712
1730
  readFile3 as readFile,
1713
1731
  GLOB_TOOL,
package/dist/index.js CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  WEB_FETCH_TOOL,
21
21
  WRITE_TOOL,
22
22
  applyPatch,
23
+ buildAssistantMessage,
23
24
  classifyCommand,
24
25
  createLogger,
25
26
  createLoggerAtPath,
@@ -33,7 +34,7 @@ import {
33
34
  todo,
34
35
  webFetch,
35
36
  writeFile
36
- } from "./chunk-HC62ZZI6.js";
37
+ } from "./chunk-ZLF4HLQ7.js";
37
38
  import {
38
39
  createProvider,
39
40
  createSessionMetadata,
@@ -185,14 +186,7 @@ async function runPipe(prompt, llm, out = process.stdout, systemPrompt) {
185
186
  }
186
187
  if (toolCalls.length > 0) {
187
188
  messages.push({
188
- role: "assistant",
189
- content: assistantText || null,
190
- tool_calls: toolCalls.map((tc) => ({
191
- id: tc.id,
192
- type: "function",
193
- function: { name: tc.name, arguments: tc.arguments }
194
- })),
195
- ...lastReasoning ? { reasoning_content: lastReasoning } : {},
189
+ ...buildAssistantMessage(assistantText, toolCalls, lastReasoning),
196
190
  ...lastReasoningDetails ? { reasoning_details: lastReasoningDetails } : {}
197
191
  });
198
192
  for (const tc of toolCalls) {
@@ -456,14 +450,7 @@ var SessionRuntime = class {
456
450
  if (toolCalls.length > 0) {
457
451
  this._status = "tool_calling";
458
452
  this.messages.push({
459
- role: "assistant",
460
- content: assistantText || null,
461
- tool_calls: toolCalls.map((tc) => ({
462
- id: tc.id,
463
- type: "function",
464
- function: { name: tc.name, arguments: tc.arguments }
465
- })),
466
- ...assistantReasoning ? { reasoning_content: assistantReasoning } : {},
453
+ ...buildAssistantMessage(assistantText, toolCalls, assistantReasoning),
467
454
  ...lastReasoningDetails ? { reasoning_details: lastReasoningDetails } : {}
468
455
  });
469
456
  (_a = this.logger) == null ? void 0 : _a.append({
@@ -486,11 +473,7 @@ var SessionRuntime = class {
486
473
  }
487
474
  } else {
488
475
  if (assistantText) {
489
- this.messages.push({
490
- role: "assistant",
491
- content: assistantText,
492
- ...assistantReasoning ? { reasoning_content: assistantReasoning } : {}
493
- });
476
+ this.messages.push(buildAssistantMessage(assistantText, [], assistantReasoning));
494
477
  (_c = this.logger) == null ? void 0 : _c.append({ ts: (/* @__PURE__ */ new Date()).toISOString(), role: "assistant", content: assistantText || null });
495
478
  }
496
479
  break;
@@ -903,21 +886,9 @@ Press Ctrl+C to stop.
903
886
  process.exit(0);
904
887
  }
905
888
  if (process.stdout.isTTY) {
906
- process.stdout.write("\x1B[?1049h\x1B[H");
907
- const exitAltScreen = () => process.stdout.write("\x1B[?1049l");
908
- process.on("exit", exitAltScreen);
909
- process.on("SIGINT", () => {
910
- exitAltScreen();
911
- process.exit(0);
912
- });
913
- process.on("SIGTERM", () => {
914
- exitAltScreen();
915
- process.exit(0);
916
- });
917
- process.on("SIGHUP", () => {
918
- exitAltScreen();
919
- process.exit(0);
920
- });
889
+ const { ENTER_ALT_SCREEN_SEQ, installCrashCleanup } = await import("./altScreen-I6W4LBRE.js");
890
+ process.stdout.write(ENTER_ALT_SCREEN_SEQ);
891
+ installCrashCleanup();
921
892
  }
922
893
  const [nodeMaj] = process.versions.node.split(".").map(Number);
923
894
  if (nodeMaj < 20) {
@@ -928,7 +899,7 @@ Node.js 16/18 \u8BF7\u4F7F\u7528 --web \u6216 --pipe \u6A21\u5F0F\u3002
928
899
  );
929
900
  process.exit(1);
930
901
  }
931
- const { App, React, render, createStdinFilter } = await import("./ui-3HB6NDEE.js");
902
+ const { App, React, render, createStdinFilter } = await import("./ui-445CQEQR.js");
932
903
  const stdinFilter = process.stdin.isTTY ? createStdinFilter(process.stdin) : process.stdin;
933
904
  render(
934
905
  React.createElement(App, { config: finalConfig, version: VERSION, autoMode, registry, trustedSkillDirs, initialMessages, stdinFilter }),
@@ -12,6 +12,7 @@ import {
12
12
  WEB_FETCH_TOOL,
13
13
  WRITE_TOOL,
14
14
  applyPatch,
15
+ buildAssistantMessage,
15
16
  createLogger,
16
17
  editFile,
17
18
  executeBash,
@@ -24,7 +25,7 @@ import {
24
25
  todo,
25
26
  webFetch,
26
27
  writeFile
27
- } from "./chunk-HC62ZZI6.js";
28
+ } from "./chunk-ZLF4HLQ7.js";
28
29
  import {
29
30
  handleSkillInput,
30
31
  loadJobs,
@@ -47,7 +48,7 @@ import { render } from "ink";
47
48
 
48
49
  // src/ui/App.tsx
49
50
  import { useState as useState3, useCallback, useRef as useRef2, useEffect as useEffect3, useMemo } from "react";
50
- import { Box as Box6, Text as Text6, useInput as useInput2, useStdout } from "ink";
51
+ import { Box as Box6, Text as Text6, useInput as useInput2, useStdout, measureElement } from "ink";
51
52
 
52
53
  // src/skills/executor.ts
53
54
  import { exec } from "child_process";
@@ -296,6 +297,7 @@ function messageToLines(msg, expandTools, terminalWidth) {
296
297
  }
297
298
 
298
299
  // src/ui/mouseSelection.ts
300
+ import stringWidth2 from "string-width";
299
301
  function initialSelectionState() {
300
302
  return { anchor: null, focus: null, dragging: false };
301
303
  }
@@ -328,6 +330,21 @@ function isCellSelected(row, col, state) {
328
330
  if (row === end.row) return col <= end.col;
329
331
  return true;
330
332
  }
333
+ function colToStringIndex(line, col, roundUp) {
334
+ if (col <= 0) return 0;
335
+ let w = 0;
336
+ let idx = 0;
337
+ for (const ch of line) {
338
+ const cw = stringWidth2(ch);
339
+ if (w >= col) return idx;
340
+ if (w + cw > col) {
341
+ return roundUp ? idx + ch.length : idx;
342
+ }
343
+ w += cw;
344
+ idx += ch.length;
345
+ }
346
+ return idx;
347
+ }
331
348
  function extractSelectedText(lines, state) {
332
349
  const norm = normalizeSelection(state);
333
350
  if (!norm) return "";
@@ -335,9 +352,11 @@ function extractSelectedText(lines, state) {
335
352
  const parts = [];
336
353
  for (let r = start.row; r <= end.row; r++) {
337
354
  const line = lines[r] ?? "";
338
- const colStart = r === start.row ? start.col : 0;
339
- const colEnd = r === end.row ? Math.min(end.col + 1, line.length) : line.length;
340
- parts.push(line.slice(colStart, colEnd));
355
+ const colStartVis = r === start.row ? start.col : 0;
356
+ const colEndVis = r === end.row ? end.col + 1 : Number.POSITIVE_INFINITY;
357
+ const idxStart = colToStringIndex(line, colStartVis, false);
358
+ const idxEnd = colToStringIndex(line, colEndVis, true);
359
+ parts.push(line.slice(idxStart, idxEnd));
341
360
  }
342
361
  return parts.join("\n");
343
362
  }
@@ -405,7 +424,20 @@ import { useState as useState2, useEffect as useEffect2, useRef, forwardRef, use
405
424
  import { Box as Box3, Text as Text3, useInput } from "ink";
406
425
 
407
426
  // src/ui/inputClick.ts
427
+ import stringWidth3 from "string-width";
408
428
  var INPUT_PREFIX_WIDTH = 2;
429
+ function colToStringIndex2(line, col) {
430
+ if (col <= 0) return 0;
431
+ let w = 0;
432
+ let idx = 0;
433
+ for (const ch of line) {
434
+ const cw = stringWidth3(ch);
435
+ if (w + cw > col) break;
436
+ w += cw;
437
+ idx += ch.length;
438
+ }
439
+ return idx;
440
+ }
409
441
  function clickToCursor(value, localRow, localCol) {
410
442
  if (value.length === 0) return 0;
411
443
  const lines = value.split("\n");
@@ -416,7 +448,7 @@ function clickToCursor(value, localRow, localCol) {
416
448
  pos += lines[i].length + 1;
417
449
  }
418
450
  const lineCol = Math.max(0, localCol - INPUT_PREFIX_WIDTH);
419
- pos += Math.min(lineCol, lines[localRow].length);
451
+ pos += colToStringIndex2(lines[localRow], lineCol);
420
452
  return pos;
421
453
  }
422
454
 
@@ -962,7 +994,7 @@ function computeVisibleWindow(totalLines, scrollOffset, historyMaxHeight) {
962
994
  }
963
995
  function mouseRowToLineIndex(opts) {
964
996
  if (opts.visibleCount <= 0) return null;
965
- const bottomRows = STATUS_BAR_ROWS + opts.skillAcRows + opts.fileAcRows + opts.selectedItemsRows + opts.inputRows;
997
+ const bottomRows = opts.bottomChromeRows ?? STATUS_BAR_ROWS + (opts.skillAcRows ?? 0) + (opts.fileAcRows ?? 0) + (opts.selectedItemsRows ?? 0) + (opts.inputRows ?? 1);
966
998
  const innerBoxHeight = opts.terminalRows - bottomRows;
967
999
  const chTopRow = innerBoxHeight - opts.visibleCount;
968
1000
  const chBottomRow = innerBoxHeight - 1;
@@ -2542,6 +2574,7 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2542
2574
  const inputHistoryRef = useRef2([]);
2543
2575
  inputHistoryRef.current = inputHistory;
2544
2576
  const historyIndexRef = useRef2(-1);
2577
+ const historyDraftRef = useRef2("");
2545
2578
  const isNavigatingHistoryRef = useRef2(false);
2546
2579
  const totalLines = useMemo(() => {
2547
2580
  const visible = messages.filter((m) => m.role !== "system");
@@ -2558,10 +2591,8 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2558
2591
  historyMaxHeightRef.current = historyMaxHeight;
2559
2592
  const selectionStateRef = useRef2(selectionState);
2560
2593
  selectionStateRef.current = selectionState;
2594
+ const bottomChromeRef = useRef2(null);
2561
2595
  const inputRowsRef = useRef2(1);
2562
- const skillAcRowsRef = useRef2(0);
2563
- const fileAcRowsRef = useRef2(0);
2564
- const selectedItemsRowsRef = useRef2(0);
2565
2596
  const terminalRowsRef = useRef2((stdout == null ? void 0 : stdout.rows) ?? 24);
2566
2597
  terminalRowsRef.current = (stdout == null ? void 0 : stdout.rows) ?? 24;
2567
2598
  const pendingConfirmRef = useRef2(null);
@@ -2626,13 +2657,24 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2626
2657
  automationManagerRef.current.stop();
2627
2658
  };
2628
2659
  }, []);
2660
+ const enableModesSeq = "\x1B[?1002h\x1B[?1006h\x1B[?1004h";
2629
2661
  useEffect3(() => {
2630
2662
  if (!stdout) return;
2631
- stdout.write("\x1B[?1002h\x1B[?1006h");
2663
+ stdout.write(enableModesSeq);
2632
2664
  return () => {
2633
- stdout.write("\x1B[?1002l\x1B[?1006l");
2665
+ stdout.write("\x1B[?1004l\x1B[?1002l\x1B[?1006l");
2634
2666
  };
2635
2667
  }, [stdout]);
2668
+ useEffect3(() => {
2669
+ if (!stdinFilter || !stdout) return;
2670
+ const onResumed = () => {
2671
+ stdout.write(enableModesSeq);
2672
+ };
2673
+ stdinFilter.on("resumed", onResumed);
2674
+ return () => {
2675
+ stdinFilter.off("resumed", onResumed);
2676
+ };
2677
+ }, [stdinFilter, stdout]);
2636
2678
  useEffect3(() => {
2637
2679
  if (!stdinFilter) return;
2638
2680
  const onMouse = (e) => {
@@ -2672,13 +2714,16 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2672
2714
  scrollOffsetRef.current,
2673
2715
  historyMaxHeightRef.current
2674
2716
  );
2717
+ let bottomRows;
2718
+ if (bottomChromeRef.current) {
2719
+ const m = measureElement(bottomChromeRef.current);
2720
+ if (m && m.height > 0) bottomRows = m.height;
2721
+ }
2675
2722
  const lineIdx = mouseRowToLineIndex({
2676
2723
  mouseRow: e.row,
2677
2724
  terminalRows: tRows,
2725
+ bottomChromeRows: bottomRows,
2678
2726
  inputRows: inRows,
2679
- skillAcRows: skillAcRowsRef.current,
2680
- fileAcRows: fileAcRowsRef.current,
2681
- selectedItemsRows: selectedItemsRowsRef.current,
2682
2727
  visibleCount: window.visibleCount,
2683
2728
  hasScrollIndicator: window.hasScrollIndicator
2684
2729
  });
@@ -2721,7 +2766,7 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2721
2766
  };
2722
2767
  }, [fileAcState.query]);
2723
2768
  useInput2((input, key) => {
2724
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
2769
+ var _a, _b, _c;
2725
2770
  if (key.escape && status === "thinking" && abortControllerRef.current) {
2726
2771
  abortControllerRef.current.abort();
2727
2772
  return;
@@ -2796,52 +2841,47 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2796
2841
  return;
2797
2842
  }
2798
2843
  const currentInput = ((_c = inputRef.current) == null ? void 0 : _c.getValue()) ?? "";
2799
- if (currentInput === "" && key.upArrow) {
2844
+ const inHistory = historyIndexRef.current >= 0;
2845
+ const canEnterHistory = currentInput === "" || inHistory;
2846
+ const navUp = () => {
2847
+ var _a2;
2800
2848
  const history = inputHistoryRef.current;
2801
2849
  const newIndex = Math.min(historyIndexRef.current + 1, history.length - 1);
2802
- if (newIndex >= 0 && newIndex < history.length) {
2803
- historyIndexRef.current = newIndex;
2804
- isNavigatingHistoryRef.current = true;
2805
- (_d = inputRef.current) == null ? void 0 : _d.fill(history[newIndex]);
2806
- }
2807
- return;
2808
- }
2809
- if (currentInput === "" && key.downArrow) {
2850
+ if (newIndex < 0 || newIndex >= history.length) return;
2851
+ if (historyIndexRef.current === -1) historyDraftRef.current = currentInput;
2852
+ historyIndexRef.current = newIndex;
2853
+ isNavigatingHistoryRef.current = true;
2854
+ (_a2 = inputRef.current) == null ? void 0 : _a2.fill(history[newIndex]);
2855
+ };
2856
+ const navDown = () => {
2857
+ var _a2, _b2;
2810
2858
  const history = inputHistoryRef.current;
2811
2859
  if (historyIndexRef.current > 0) {
2812
2860
  const newIndex = historyIndexRef.current - 1;
2813
2861
  historyIndexRef.current = newIndex;
2814
2862
  isNavigatingHistoryRef.current = true;
2815
- (_e = inputRef.current) == null ? void 0 : _e.fill(history[newIndex]);
2863
+ (_a2 = inputRef.current) == null ? void 0 : _a2.fill(history[newIndex]);
2816
2864
  } else if (historyIndexRef.current === 0) {
2817
2865
  historyIndexRef.current = -1;
2818
2866
  isNavigatingHistoryRef.current = true;
2819
- (_f = inputRef.current) == null ? void 0 : _f.fill("");
2867
+ (_b2 = inputRef.current) == null ? void 0 : _b2.fill(historyDraftRef.current);
2868
+ historyDraftRef.current = "";
2820
2869
  }
2870
+ };
2871
+ if (canEnterHistory && key.upArrow) {
2872
+ navUp();
2873
+ return;
2874
+ }
2875
+ if (canEnterHistory && key.downArrow) {
2876
+ navDown();
2821
2877
  return;
2822
2878
  }
2823
2879
  if (key.ctrl && input === "p") {
2824
- const history = inputHistoryRef.current;
2825
- const newIndex = Math.min(historyIndexRef.current + 1, history.length - 1);
2826
- if (newIndex >= 0 && newIndex < history.length) {
2827
- historyIndexRef.current = newIndex;
2828
- isNavigatingHistoryRef.current = true;
2829
- (_g = inputRef.current) == null ? void 0 : _g.fill(history[newIndex]);
2830
- }
2880
+ navUp();
2831
2881
  return;
2832
2882
  }
2833
2883
  if (key.ctrl && input === "n") {
2834
- const history = inputHistoryRef.current;
2835
- if (historyIndexRef.current > 0) {
2836
- const newIndex = historyIndexRef.current - 1;
2837
- historyIndexRef.current = newIndex;
2838
- isNavigatingHistoryRef.current = true;
2839
- (_h = inputRef.current) == null ? void 0 : _h.fill(history[newIndex]);
2840
- } else if (historyIndexRef.current === 0) {
2841
- historyIndexRef.current = -1;
2842
- isNavigatingHistoryRef.current = true;
2843
- (_i = inputRef.current) == null ? void 0 : _i.fill("");
2844
- }
2884
+ navDown();
2845
2885
  return;
2846
2886
  }
2847
2887
  });
@@ -2930,18 +2970,11 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2930
2970
  throw err;
2931
2971
  }
2932
2972
  if (toolCalls.length > 0) {
2933
- const assistantMsg = {
2934
- role: "assistant",
2935
- // content 可能为空字符串(纯工具调用),存 null 符合 OpenAI API 规范
2936
- content: assistantText || null,
2937
- tool_calls: toolCalls.map((tc) => ({
2938
- id: tc.id,
2939
- type: "function",
2940
- function: { name: tc.name, arguments: tc.arguments }
2941
- })),
2942
- // 仅在存在思考内容时扩展 reasoning_content 字段
2943
- ...assistantReasoning ? { reasoning_content: assistantReasoning } : {}
2944
- };
2973
+ const assistantMsg = buildAssistantMessage(
2974
+ assistantText,
2975
+ toolCalls,
2976
+ assistantReasoning
2977
+ );
2945
2978
  setMessages((prev) => {
2946
2979
  const last = prev[prev.length - 1];
2947
2980
  const withoutStreaming = (last == null ? void 0 : last.role) === "assistant" && !last.tool_calls ? prev.slice(0, -1) : prev;
@@ -3017,11 +3050,7 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
3017
3050
  setMessages((prev) => {
3018
3051
  const last = prev[prev.length - 1];
3019
3052
  if ((last == null ? void 0 : last.role) === "assistant" && !last.tool_calls) {
3020
- const updated = {
3021
- role: "assistant",
3022
- content: assistantText,
3023
- ...assistantReasoning ? { reasoning_content: assistantReasoning } : {}
3024
- };
3053
+ const updated = buildAssistantMessage(assistantText, [], assistantReasoning);
3025
3054
  return [...prev.slice(0, -1), updated];
3026
3055
  }
3027
3056
  return prev;
@@ -3029,11 +3058,7 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
3029
3058
  if (assistantText) {
3030
3059
  currentMessages = [
3031
3060
  ...currentMessages,
3032
- {
3033
- role: "assistant",
3034
- content: assistantText,
3035
- ...assistantReasoning ? { reasoning_content: assistantReasoning } : {}
3036
- }
3061
+ buildAssistantMessage(assistantText, [], assistantReasoning)
3037
3062
  ];
3038
3063
  }
3039
3064
  }
@@ -3186,9 +3211,6 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
3186
3211
  const skillSuggestions = registry ? computeSuggestions(registry.list(), acState) : [];
3187
3212
  const acOpen = isOpen(acState, skillSuggestions);
3188
3213
  const facOpen = isOpen2(fileAcState, fileSuggestions);
3189
- skillAcRowsRef.current = acOpen ? skillSuggestions.length + 2 : 0;
3190
- fileAcRowsRef.current = facOpen ? fileSuggestions.length + 2 : 0;
3191
- selectedItemsRowsRef.current = selectedItems.length > 0 ? 1 : 0;
3192
3214
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: "100%", children: [
3193
3215
  /* @__PURE__ */ jsx6(Box6, { flexGrow: 1, flexDirection: "column", justifyContent: "flex-end", children: /* @__PURE__ */ jsx6(
3194
3216
  ConversationHistory,
@@ -3201,46 +3223,48 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
3201
3223
  selection: selectionState
3202
3224
  }
3203
3225
  ) }),
3204
- /* @__PURE__ */ jsx6(
3205
- StatusBar,
3206
- {
3207
- status,
3208
- toolName,
3209
- confirmPrompt,
3210
- version,
3211
- tokenUsage
3212
- }
3213
- ),
3214
- /* @__PURE__ */ jsx6(
3215
- SkillAutocomplete,
3216
- {
3217
- suggestions: skillSuggestions,
3218
- selectedIndex: acState.selectedIndex,
3219
- isOpen: acOpen
3220
- }
3221
- ),
3222
- /* @__PURE__ */ jsx6(
3223
- FileAutocomplete,
3224
- {
3225
- suggestions: fileSuggestions,
3226
- selectedIndex: fileAcState.selectedIndex,
3227
- isOpen: facOpen
3228
- }
3229
- ),
3230
- selectedItems.length > 0 && /* @__PURE__ */ jsx6(Box6, { paddingLeft: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "cyan", dimColor: true, children: [
3231
- "Selected: ",
3232
- selectedItems.map((i) => i.label).join(", ")
3233
- ] }) }),
3234
- /* @__PURE__ */ jsx6(
3235
- Input_default,
3236
- {
3237
- ref: inputRef,
3238
- isActive: isInputActive,
3239
- onSubmit: handleSubmit,
3240
- onChange: handleInputTextChange,
3241
- placeholder: status === "awaiting_confirm" ? "y / n" : void 0
3242
- }
3243
- )
3226
+ /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", ref: bottomChromeRef, children: [
3227
+ /* @__PURE__ */ jsx6(
3228
+ StatusBar,
3229
+ {
3230
+ status,
3231
+ toolName,
3232
+ confirmPrompt,
3233
+ version,
3234
+ tokenUsage
3235
+ }
3236
+ ),
3237
+ /* @__PURE__ */ jsx6(
3238
+ SkillAutocomplete,
3239
+ {
3240
+ suggestions: skillSuggestions,
3241
+ selectedIndex: acState.selectedIndex,
3242
+ isOpen: acOpen
3243
+ }
3244
+ ),
3245
+ /* @__PURE__ */ jsx6(
3246
+ FileAutocomplete,
3247
+ {
3248
+ suggestions: fileSuggestions,
3249
+ selectedIndex: fileAcState.selectedIndex,
3250
+ isOpen: facOpen
3251
+ }
3252
+ ),
3253
+ selectedItems.length > 0 && /* @__PURE__ */ jsx6(Box6, { paddingLeft: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "cyan", dimColor: true, children: [
3254
+ "Selected: ",
3255
+ selectedItems.map((i) => i.label).join(", ")
3256
+ ] }) }),
3257
+ /* @__PURE__ */ jsx6(
3258
+ Input_default,
3259
+ {
3260
+ ref: inputRef,
3261
+ isActive: isInputActive,
3262
+ onSubmit: handleSubmit,
3263
+ onChange: handleInputTextChange,
3264
+ placeholder: status === "awaiting_confirm" ? "y / n" : void 0
3265
+ }
3266
+ )
3267
+ ] })
3244
3268
  ] });
3245
3269
  }
3246
3270
 
@@ -3306,6 +3330,7 @@ function tokenizeStdin(input, pendingIn) {
3306
3330
  let i = 0;
3307
3331
  let keyboardOut = "";
3308
3332
  const events = [];
3333
+ const focusEvents = [];
3309
3334
  while (i < data.length) {
3310
3335
  const ch = data[i];
3311
3336
  if (ch !== "\x1B") {
@@ -3327,13 +3352,18 @@ function tokenizeStdin(input, pendingIn) {
3327
3352
  i += mLen;
3328
3353
  continue;
3329
3354
  }
3355
+ if (tail.length >= 3 && tail[1] === "[" && (tail[2] === "I" || tail[2] === "O")) {
3356
+ focusEvents.push({ kind: "focus", focused: tail[2] === "I" });
3357
+ i += 3;
3358
+ continue;
3359
+ }
3330
3360
  if (isMousePartial(tail) && tail.length < MAX_MOUSE_SEQ) {
3331
- return { keyboard: keyboardOut, pending: tail, events };
3361
+ return { keyboard: keyboardOut, pending: tail, events, focusEvents };
3332
3362
  }
3333
3363
  keyboardOut += ch;
3334
3364
  i++;
3335
3365
  }
3336
- return { keyboard: keyboardOut, pending: "", events };
3366
+ return { keyboard: keyboardOut, pending: "", events, focusEvents };
3337
3367
  }
3338
3368
 
3339
3369
  // src/ui/stdinFilter.ts
@@ -3341,12 +3371,22 @@ var StdinFilterImpl = class extends Transform {
3341
3371
  isTTY;
3342
3372
  source;
3343
3373
  pending = "";
3344
- constructor(source) {
3374
+ // 焦点状态——cc-haha 把 focus 作 signal 而非 mode switch(§5.4)。
3375
+ // CSI I/O 切换;任何字节到达时若为 false 由 failsafe 强制回 true。
3376
+ focused = true;
3377
+ // resume 检测:最近一次活动时间戳;间隔 > resumeIdleMs 算 SSH 重连 / tmux detach 回来。
3378
+ lastByteAt = 0;
3379
+ resumeIdleMs;
3380
+ constructor(source, opts = {}) {
3345
3381
  super();
3346
3382
  this.source = source;
3347
3383
  this.isTTY = source.isTTY ?? false;
3384
+ this.resumeIdleMs = opts.resumeIdleMs ?? 5e3;
3348
3385
  source.pipe(this);
3349
3386
  }
3387
+ isFocused() {
3388
+ return this.focused;
3389
+ }
3350
3390
  setRawMode(mode) {
3351
3391
  var _a, _b;
3352
3392
  (_b = (_a = this.source).setRawMode) == null ? void 0 : _b.call(_a, mode);
@@ -3361,9 +3401,23 @@ var StdinFilterImpl = class extends Transform {
3361
3401
  }
3362
3402
  _transform(chunk, _encoding, callback) {
3363
3403
  const s = typeof chunk === "string" ? chunk : chunk.toString("binary");
3364
- const { keyboard, pending, events } = tokenizeStdin(s, this.pending);
3404
+ const now = Date.now();
3405
+ if (this.lastByteAt > 0 && now - this.lastByteAt >= this.resumeIdleMs) {
3406
+ this.emit("resumed");
3407
+ }
3408
+ this.lastByteAt = now;
3409
+ const { keyboard, pending, events, focusEvents } = tokenizeStdin(s, this.pending);
3365
3410
  this.pending = pending;
3411
+ for (const ev of focusEvents) {
3412
+ this.focused = ev.focused;
3413
+ this.emit("focus", ev);
3414
+ }
3366
3415
  for (const ev of events) this.emit("mouse", ev);
3416
+ const hadNonFocusActivity = events.length > 0 || keyboard.length > 0;
3417
+ if (hadNonFocusActivity && !this.focused) {
3418
+ this.focused = true;
3419
+ this.emit("focus", { kind: "focus", focused: true });
3420
+ }
3367
3421
  if (keyboard.length > 0) {
3368
3422
  callback(null, Buffer.from(keyboard, "binary"));
3369
3423
  } else {
@@ -3371,8 +3425,8 @@ var StdinFilterImpl = class extends Transform {
3371
3425
  }
3372
3426
  }
3373
3427
  };
3374
- function createStdinFilter(source) {
3375
- return new StdinFilterImpl(source);
3428
+ function createStdinFilter(source, opts) {
3429
+ return new StdinFilterImpl(source, opts);
3376
3430
  }
3377
3431
  export {
3378
3432
  App,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.60",
3
+ "version": "0.5.62",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",