@zhongqian97-code/ecode 0.5.59 → 0.5.61

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
+ };
@@ -4,7 +4,7 @@ import {
4
4
  createSessionMetadata,
5
5
  updateSessionMetadata,
6
6
  writeSessionMetadata
7
- } from "./chunk-SIQQG6FT.js";
7
+ } from "./chunk-LODP7T4D.js";
8
8
 
9
9
  // src/skills/loader.ts
10
10
  import { readFile, readdir, stat } from "fs/promises";
@@ -134,6 +134,33 @@ function saveConfig(partial) {
134
134
 
135
135
  // src/providers/openai.ts
136
136
  import OpenAI from "openai";
137
+ function trimToFirstJson(s) {
138
+ try {
139
+ JSON.parse(s);
140
+ return s;
141
+ } catch {
142
+ }
143
+ let depth = 0, inStr = false, esc = false;
144
+ for (let i = 0; i < s.length; i++) {
145
+ const c = s[i];
146
+ if (esc) {
147
+ esc = false;
148
+ continue;
149
+ }
150
+ if (c === "\\" && inStr) {
151
+ esc = true;
152
+ continue;
153
+ }
154
+ if (c === '"') {
155
+ inStr = !inStr;
156
+ continue;
157
+ }
158
+ if (inStr) continue;
159
+ if (c === "{" || c === "[") depth++;
160
+ else if ((c === "}" || c === "]") && --depth === 0) return s.slice(0, i + 1);
161
+ }
162
+ return s;
163
+ }
137
164
  function createOpenAIProvider(profile) {
138
165
  const THINK_END = "</think>";
139
166
  const openai = new OpenAI({
@@ -302,7 +329,10 @@ function createOpenAIProvider(profile) {
302
329
  finishReason: choice.finish_reason,
303
330
  // tcAccumulator 为空说明本轮没有工具调用,传 undefined 而非空数组,
304
331
  // 让调用方用 if (chunk.toolCalls) 做简洁判断
305
- toolCalls: tcAccumulator.size > 0 ? Array.from(tcAccumulator.values()) : void 0,
332
+ toolCalls: tcAccumulator.size > 0 ? Array.from(tcAccumulator.values()).map((tc) => ({
333
+ ...tc,
334
+ arguments: trimToFirstJson(tc.arguments)
335
+ })) : void 0,
306
336
  reasoning: reasoningAccumulator || void 0,
307
337
  // reasoningDetails 仅在流中出现过结构化推理时返回(MiniMax 兼容)
308
338
  reasoningDetails: reasoningDetailsAcc.size > 0 ? Array.from(reasoningDetailsAcc.values()) : void 0,
@@ -3,7 +3,7 @@ const _ew=process.emitWarning.bind(process);process.emitWarning=function(w,...a)
3
3
  import {
4
4
  findSession,
5
5
  listSessions
6
- } from "./chunk-SIQQG6FT.js";
6
+ } from "./chunk-LODP7T4D.js";
7
7
 
8
8
  // src/sessions/command.ts
9
9
  import * as path from "path";
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  cmdSessionsInspect,
6
6
  cmdSessionsList,
7
7
  cmdSessionsReplay
8
- } from "./chunk-N3IDMSG5.js";
8
+ } from "./chunk-ZNCND354.js";
9
9
  import {
10
10
  APPLY_PATCH_TOOL,
11
11
  BASH_TOOL,
@@ -33,7 +33,7 @@ import {
33
33
  todo,
34
34
  webFetch,
35
35
  writeFile
36
- } from "./chunk-V3JQ7HAU.js";
36
+ } from "./chunk-HC62ZZI6.js";
37
37
  import {
38
38
  createProvider,
39
39
  createSessionMetadata,
@@ -42,7 +42,7 @@ import {
42
42
  resolveActiveProfile,
43
43
  updateSessionMetadata,
44
44
  writeSessionMetadata
45
- } from "./chunk-SIQQG6FT.js";
45
+ } from "./chunk-LODP7T4D.js";
46
46
 
47
47
  // src/index.ts
48
48
  import { createRequire } from "module";
@@ -197,7 +197,12 @@ async function runPipe(prompt, llm, out = process.stdout, systemPrompt) {
197
197
  });
198
198
  for (const tc of toolCalls) {
199
199
  emit(out, { type: "tool_call", id: tc.id, name: tc.name, arguments: tc.arguments });
200
- const content = await executeToolCall(tc.name, tc.arguments);
200
+ let content;
201
+ try {
202
+ content = await executeToolCall(tc.name, tc.arguments);
203
+ } catch (err) {
204
+ content = `Tool error: ${String(err)}`;
205
+ }
201
206
  emit(out, { type: "tool_result", toolCallId: tc.id, name: tc.name, content });
202
207
  messages.push({ role: "tool", tool_call_id: tc.id, content });
203
208
  }
@@ -814,7 +819,7 @@ if (rawArgs[0] === "web") {
814
819
  webAutoApprove = true;
815
820
  }
816
821
  }
817
- const { buildServer, generateAccessToken } = await import("./web-3CS3TO2E.js");
822
+ const { buildServer, generateAccessToken } = await import("./web-N7JFJIBC.js");
818
823
  const token = finalConfig.webToken ?? generateAccessToken();
819
824
  const manager = new SessionManager(finalConfig);
820
825
  const __webDirname = dirname(fileURLToPath(import.meta.url));
@@ -898,21 +903,9 @@ Press Ctrl+C to stop.
898
903
  process.exit(0);
899
904
  }
900
905
  if (process.stdout.isTTY) {
901
- process.stdout.write("\x1B[?1049h");
902
- const exitAltScreen = () => process.stdout.write("\x1B[?1049l");
903
- process.on("exit", exitAltScreen);
904
- process.on("SIGINT", () => {
905
- exitAltScreen();
906
- process.exit(0);
907
- });
908
- process.on("SIGTERM", () => {
909
- exitAltScreen();
910
- process.exit(0);
911
- });
912
- process.on("SIGHUP", () => {
913
- exitAltScreen();
914
- process.exit(0);
915
- });
906
+ const { ENTER_ALT_SCREEN_SEQ, installCrashCleanup } = await import("./altScreen-I6W4LBRE.js");
907
+ process.stdout.write(ENTER_ALT_SCREEN_SEQ);
908
+ installCrashCleanup();
916
909
  }
917
910
  const [nodeMaj] = process.versions.node.split(".").map(Number);
918
911
  if (nodeMaj < 20) {
@@ -923,7 +916,7 @@ Node.js 16/18 \u8BF7\u4F7F\u7528 --web \u6216 --pipe \u6A21\u5F0F\u3002
923
916
  );
924
917
  process.exit(1);
925
918
  }
926
- const { App, React, render, createStdinFilter } = await import("./ui-STSJVDXD.js");
919
+ const { App, React, render, createStdinFilter } = await import("./ui-DFEOOA36.js");
927
920
  const stdinFilter = process.stdin.isTTY ? createStdinFilter(process.stdin) : process.stdin;
928
921
  render(
929
922
  React.createElement(App, { config: finalConfig, version: VERSION, autoMode, registry, trustedSkillDirs, initialMessages, stdinFilter }),
@@ -24,7 +24,7 @@ import {
24
24
  todo,
25
25
  webFetch,
26
26
  writeFile
27
- } from "./chunk-V3JQ7HAU.js";
27
+ } from "./chunk-HC62ZZI6.js";
28
28
  import {
29
29
  handleSkillInput,
30
30
  loadJobs,
@@ -39,7 +39,7 @@ import {
39
39
  resolveActiveProfile,
40
40
  updateSessionMetadata,
41
41
  writeSessionMetadata
42
- } from "./chunk-SIQQG6FT.js";
42
+ } from "./chunk-LODP7T4D.js";
43
43
 
44
44
  // src/ui/index.ts
45
45
  import { default as default2 } from "react";
@@ -47,7 +47,7 @@ import { render } from "ink";
47
47
 
48
48
  // src/ui/App.tsx
49
49
  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";
50
+ import { Box as Box6, Text as Text6, useInput as useInput2, useStdout, measureElement } from "ink";
51
51
 
52
52
  // src/skills/executor.ts
53
53
  import { exec } from "child_process";
@@ -296,6 +296,7 @@ function messageToLines(msg, expandTools, terminalWidth) {
296
296
  }
297
297
 
298
298
  // src/ui/mouseSelection.ts
299
+ import stringWidth2 from "string-width";
299
300
  function initialSelectionState() {
300
301
  return { anchor: null, focus: null, dragging: false };
301
302
  }
@@ -328,6 +329,21 @@ function isCellSelected(row, col, state) {
328
329
  if (row === end.row) return col <= end.col;
329
330
  return true;
330
331
  }
332
+ function colToStringIndex(line, col, roundUp) {
333
+ if (col <= 0) return 0;
334
+ let w = 0;
335
+ let idx = 0;
336
+ for (const ch of line) {
337
+ const cw = stringWidth2(ch);
338
+ if (w >= col) return idx;
339
+ if (w + cw > col) {
340
+ return roundUp ? idx + ch.length : idx;
341
+ }
342
+ w += cw;
343
+ idx += ch.length;
344
+ }
345
+ return idx;
346
+ }
331
347
  function extractSelectedText(lines, state) {
332
348
  const norm = normalizeSelection(state);
333
349
  if (!norm) return "";
@@ -335,9 +351,11 @@ function extractSelectedText(lines, state) {
335
351
  const parts = [];
336
352
  for (let r = start.row; r <= end.row; r++) {
337
353
  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));
354
+ const colStartVis = r === start.row ? start.col : 0;
355
+ const colEndVis = r === end.row ? end.col + 1 : Number.POSITIVE_INFINITY;
356
+ const idxStart = colToStringIndex(line, colStartVis, false);
357
+ const idxEnd = colToStringIndex(line, colEndVis, true);
358
+ parts.push(line.slice(idxStart, idxEnd));
341
359
  }
342
360
  return parts.join("\n");
343
361
  }
@@ -403,6 +421,37 @@ function ConversationHistory({
403
421
  // src/ui/Input.tsx
404
422
  import { useState as useState2, useEffect as useEffect2, useRef, forwardRef, useImperativeHandle } from "react";
405
423
  import { Box as Box3, Text as Text3, useInput } from "ink";
424
+
425
+ // src/ui/inputClick.ts
426
+ import stringWidth3 from "string-width";
427
+ var INPUT_PREFIX_WIDTH = 2;
428
+ function colToStringIndex2(line, col) {
429
+ if (col <= 0) return 0;
430
+ let w = 0;
431
+ let idx = 0;
432
+ for (const ch of line) {
433
+ const cw = stringWidth3(ch);
434
+ if (w + cw > col) break;
435
+ w += cw;
436
+ idx += ch.length;
437
+ }
438
+ return idx;
439
+ }
440
+ function clickToCursor(value, localRow, localCol) {
441
+ if (value.length === 0) return 0;
442
+ const lines = value.split("\n");
443
+ if (localRow < 0) return 0;
444
+ if (localRow >= lines.length) return value.length;
445
+ let pos = 0;
446
+ for (let i = 0; i < localRow; i++) {
447
+ pos += lines[i].length + 1;
448
+ }
449
+ const lineCol = Math.max(0, localCol - INPUT_PREFIX_WIDTH);
450
+ pos += colToStringIndex2(lines[localRow], lineCol);
451
+ return pos;
452
+ }
453
+
454
+ // src/ui/Input.tsx
406
455
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
407
456
  var CURSOR_CHAR = "\u258C";
408
457
  var BLINK_INTERVAL_MS = 530;
@@ -430,11 +479,14 @@ function wordForward(s, pos) {
430
479
  var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placeholder }, ref) {
431
480
  const [value, setValue] = useState2("");
432
481
  const [cursorPos, setCursorPos] = useState2(0);
482
+ const [inputSel, setInputSel] = useState2(null);
433
483
  const [cursorVisible, setCursorVisible] = useState2(true);
434
484
  const valueRef = useRef(value);
435
485
  valueRef.current = value;
436
486
  const cursorPosRef = useRef(cursorPos);
437
487
  cursorPosRef.current = cursorPos;
488
+ const inputSelRef = useRef(inputSel);
489
+ inputSelRef.current = inputSel;
438
490
  const onChangeRef = useRef(onChange);
439
491
  onChangeRef.current = onChange;
440
492
  const onSubmitRef = useRef(onSubmit);
@@ -446,7 +498,42 @@ var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placehold
446
498
  cursorPosRef.current = text.length;
447
499
  setValue(text);
448
500
  setCursorPos(text.length);
501
+ inputSelRef.current = null;
502
+ setInputSel(null);
449
503
  (_a = onChangeRef.current) == null ? void 0 : _a.call(onChangeRef, text);
504
+ },
505
+ getValue() {
506
+ return valueRef.current;
507
+ },
508
+ setCursorByClick(line, col) {
509
+ const pos = clickToCursor(valueRef.current, line, col);
510
+ cursorPosRef.current = pos;
511
+ setCursorPos(pos);
512
+ },
513
+ startInputSelection(line, col) {
514
+ const pos = clickToCursor(valueRef.current, line, col);
515
+ const next = { anchor: pos, focus: pos };
516
+ inputSelRef.current = next;
517
+ setInputSel(next);
518
+ },
519
+ extendInputSelection(line, col) {
520
+ const cur = inputSelRef.current;
521
+ if (!cur) return;
522
+ const pos = clickToCursor(valueRef.current, line, col);
523
+ const next = { anchor: cur.anchor, focus: pos };
524
+ inputSelRef.current = next;
525
+ setInputSel(next);
526
+ },
527
+ getInputSelectionText() {
528
+ const cur = inputSelRef.current;
529
+ if (!cur || cur.anchor === cur.focus) return "";
530
+ const s = Math.min(cur.anchor, cur.focus);
531
+ const e = Math.max(cur.anchor, cur.focus);
532
+ return valueRef.current.slice(s, e);
533
+ },
534
+ clearInputSelection() {
535
+ inputSelRef.current = null;
536
+ setInputSel(null);
450
537
  }
451
538
  }));
452
539
  useEffect2(() => {
@@ -474,6 +561,10 @@ var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placehold
474
561
  var _a, _b, _c, _d, _e, _f, _g, _h;
475
562
  const v = valueRef.current;
476
563
  const pos = cursorPosRef.current;
564
+ if (inputSelRef.current) {
565
+ inputSelRef.current = null;
566
+ setInputSel(null);
567
+ }
477
568
  if (key.return && key.shift) {
478
569
  const newValue = v.slice(0, pos) + "\n" + v.slice(pos);
479
570
  setValueSync(newValue);
@@ -601,9 +692,34 @@ var Input = forwardRef(function Input2({ isActive, onSubmit, onChange, placehold
601
692
  }
602
693
  remaining -= lineLen + 1;
603
694
  }
695
+ const selMin = inputSel ? Math.min(inputSel.anchor, inputSel.focus) : null;
696
+ const selMax = inputSel ? Math.max(inputSel.anchor, inputSel.focus) : null;
697
+ const hasSel = selMin !== null && selMax !== null && selMin !== selMax;
604
698
  return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: lines.map((line, idx) => {
605
699
  const prefix = idx === 0 ? "> " : " ";
606
- const showCursor = isActive && cursorVisible && idx === cursorLine;
700
+ const showCursor = isActive && cursorVisible && idx === cursorLine && !hasSel;
701
+ if (hasSel) {
702
+ let lineStartAbs = 0;
703
+ for (let i = 0; i < idx; i++) lineStartAbs += lines[i].length + 1;
704
+ const lineEndAbs = lineStartAbs + line.length;
705
+ const iStart = Math.max(lineStartAbs, selMin);
706
+ const iEnd = Math.min(lineEndAbs, selMax);
707
+ if (iStart >= iEnd) {
708
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
709
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: prefix }),
710
+ /* @__PURE__ */ jsx3(Text3, { children: line })
711
+ ] }, idx);
712
+ }
713
+ const before2 = line.slice(0, iStart - lineStartAbs);
714
+ const selChunk = line.slice(iStart - lineStartAbs, iEnd - lineStartAbs);
715
+ const after2 = line.slice(iEnd - lineStartAbs);
716
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
717
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: prefix }),
718
+ before2 && /* @__PURE__ */ jsx3(Text3, { children: before2 }),
719
+ /* @__PURE__ */ jsx3(Text3, { inverse: true, children: selChunk || " " }),
720
+ after2 && /* @__PURE__ */ jsx3(Text3, { children: after2 })
721
+ ] }, idx);
722
+ }
607
723
  if (!showCursor) {
608
724
  return /* @__PURE__ */ jsxs3(Box3, { children: [
609
725
  /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: prefix }),
@@ -877,7 +993,7 @@ function computeVisibleWindow(totalLines, scrollOffset, historyMaxHeight) {
877
993
  }
878
994
  function mouseRowToLineIndex(opts) {
879
995
  if (opts.visibleCount <= 0) return null;
880
- const bottomRows = STATUS_BAR_ROWS + opts.skillAcRows + opts.fileAcRows + opts.selectedItemsRows + opts.inputRows;
996
+ const bottomRows = opts.bottomChromeRows ?? STATUS_BAR_ROWS + (opts.skillAcRows ?? 0) + (opts.fileAcRows ?? 0) + (opts.selectedItemsRows ?? 0) + (opts.inputRows ?? 1);
881
997
  const innerBoxHeight = opts.terminalRows - bottomRows;
882
998
  const chTopRow = innerBoxHeight - opts.visibleCount;
883
999
  const chBottomRow = innerBoxHeight - 1;
@@ -2457,6 +2573,7 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2457
2573
  const inputHistoryRef = useRef2([]);
2458
2574
  inputHistoryRef.current = inputHistory;
2459
2575
  const historyIndexRef = useRef2(-1);
2576
+ const historyDraftRef = useRef2("");
2460
2577
  const isNavigatingHistoryRef = useRef2(false);
2461
2578
  const totalLines = useMemo(() => {
2462
2579
  const visible = messages.filter((m) => m.role !== "system");
@@ -2473,10 +2590,8 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2473
2590
  historyMaxHeightRef.current = historyMaxHeight;
2474
2591
  const selectionStateRef = useRef2(selectionState);
2475
2592
  selectionStateRef.current = selectionState;
2593
+ const bottomChromeRef = useRef2(null);
2476
2594
  const inputRowsRef = useRef2(1);
2477
- const skillAcRowsRef = useRef2(0);
2478
- const fileAcRowsRef = useRef2(0);
2479
- const selectedItemsRowsRef = useRef2(0);
2480
2595
  const terminalRowsRef = useRef2((stdout == null ? void 0 : stdout.rows) ?? 24);
2481
2596
  terminalRowsRef.current = (stdout == null ? void 0 : stdout.rows) ?? 24;
2482
2597
  const pendingConfirmRef = useRef2(null);
@@ -2541,16 +2656,28 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2541
2656
  automationManagerRef.current.stop();
2542
2657
  };
2543
2658
  }, []);
2659
+ const enableModesSeq = "\x1B[?1002h\x1B[?1006h\x1B[?1004h";
2544
2660
  useEffect3(() => {
2545
2661
  if (!stdout) return;
2546
- stdout.write("\x1B[?1002h\x1B[?1006h");
2662
+ stdout.write(enableModesSeq);
2547
2663
  return () => {
2548
- stdout.write("\x1B[?1002l\x1B[?1006l");
2664
+ stdout.write("\x1B[?1004l\x1B[?1002l\x1B[?1006l");
2549
2665
  };
2550
2666
  }, [stdout]);
2667
+ useEffect3(() => {
2668
+ if (!stdinFilter || !stdout) return;
2669
+ const onResumed = () => {
2670
+ stdout.write(enableModesSeq);
2671
+ };
2672
+ stdinFilter.on("resumed", onResumed);
2673
+ return () => {
2674
+ stdinFilter.off("resumed", onResumed);
2675
+ };
2676
+ }, [stdinFilter, stdout]);
2551
2677
  useEffect3(() => {
2552
2678
  if (!stdinFilter) return;
2553
2679
  const onMouse = (e) => {
2680
+ var _a, _b, _c, _d, _e;
2554
2681
  if (e.kind === "scroll") {
2555
2682
  const step = Math.max(1, Math.floor(historyMaxHeightRef.current / 2));
2556
2683
  if (e.direction === "up") {
@@ -2560,18 +2687,42 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2560
2687
  }
2561
2688
  return;
2562
2689
  }
2690
+ const tRows = terminalRowsRef.current;
2691
+ const inRows = inputRowsRef.current;
2692
+ const inputStartRow = tRows - inRows;
2693
+ if (e.row >= inputStartRow && e.row <= tRows - 1) {
2694
+ const localRow = e.row - inputStartRow;
2695
+ if (e.type === "leftDown") {
2696
+ (_a = inputRef.current) == null ? void 0 : _a.setCursorByClick(localRow, e.col);
2697
+ (_b = inputRef.current) == null ? void 0 : _b.startInputSelection(localRow, e.col);
2698
+ } else if (e.type === "leftDrag") {
2699
+ (_c = inputRef.current) == null ? void 0 : _c.extendInputSelection(localRow, e.col);
2700
+ } else if (e.type === "leftUp") {
2701
+ const text = ((_d = inputRef.current) == null ? void 0 : _d.getInputSelectionText()) ?? "";
2702
+ if (text.length > 0 && stdout) {
2703
+ const b64 = Buffer.from(text, "utf8").toString("base64");
2704
+ stdout.write(`\x1B]52;c;${b64}\x07`);
2705
+ } else {
2706
+ (_e = inputRef.current) == null ? void 0 : _e.clearInputSelection();
2707
+ }
2708
+ }
2709
+ return;
2710
+ }
2563
2711
  const window = computeVisibleWindow(
2564
2712
  totalLinesRef.current,
2565
2713
  scrollOffsetRef.current,
2566
2714
  historyMaxHeightRef.current
2567
2715
  );
2716
+ let bottomRows;
2717
+ if (bottomChromeRef.current) {
2718
+ const m = measureElement(bottomChromeRef.current);
2719
+ if (m && m.height > 0) bottomRows = m.height;
2720
+ }
2568
2721
  const lineIdx = mouseRowToLineIndex({
2569
2722
  mouseRow: e.row,
2570
- terminalRows: terminalRowsRef.current,
2571
- inputRows: inputRowsRef.current,
2572
- skillAcRows: skillAcRowsRef.current,
2573
- fileAcRows: fileAcRowsRef.current,
2574
- selectedItemsRows: selectedItemsRowsRef.current,
2723
+ terminalRows: tRows,
2724
+ bottomChromeRows: bottomRows,
2725
+ inputRows: inRows,
2575
2726
  visibleCount: window.visibleCount,
2576
2727
  hasScrollIndicator: window.hasScrollIndicator
2577
2728
  });
@@ -2614,7 +2765,7 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2614
2765
  };
2615
2766
  }, [fileAcState.query]);
2616
2767
  useInput2((input, key) => {
2617
- var _a, _b, _c, _d, _e;
2768
+ var _a, _b, _c;
2618
2769
  if (key.escape && status === "thinking" && abortControllerRef.current) {
2619
2770
  abortControllerRef.current.abort();
2620
2771
  return;
@@ -2688,28 +2839,48 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
2688
2839
  setScrollOffset((prev) => Math.min(prev + scrollStep, Math.max(0, totalLines - 1)));
2689
2840
  return;
2690
2841
  }
2691
- if (key.ctrl && input === "p") {
2842
+ const currentInput = ((_c = inputRef.current) == null ? void 0 : _c.getValue()) ?? "";
2843
+ const inHistory = historyIndexRef.current >= 0;
2844
+ const canEnterHistory = currentInput === "" || inHistory;
2845
+ const navUp = () => {
2846
+ var _a2;
2692
2847
  const history = inputHistoryRef.current;
2693
2848
  const newIndex = Math.min(historyIndexRef.current + 1, history.length - 1);
2694
- if (newIndex >= 0 && newIndex < history.length) {
2695
- historyIndexRef.current = newIndex;
2696
- isNavigatingHistoryRef.current = true;
2697
- (_c = inputRef.current) == null ? void 0 : _c.fill(history[newIndex]);
2698
- }
2699
- return;
2700
- }
2701
- if (key.ctrl && input === "n") {
2849
+ if (newIndex < 0 || newIndex >= history.length) return;
2850
+ if (historyIndexRef.current === -1) historyDraftRef.current = currentInput;
2851
+ historyIndexRef.current = newIndex;
2852
+ isNavigatingHistoryRef.current = true;
2853
+ (_a2 = inputRef.current) == null ? void 0 : _a2.fill(history[newIndex]);
2854
+ };
2855
+ const navDown = () => {
2856
+ var _a2, _b2;
2702
2857
  const history = inputHistoryRef.current;
2703
2858
  if (historyIndexRef.current > 0) {
2704
2859
  const newIndex = historyIndexRef.current - 1;
2705
2860
  historyIndexRef.current = newIndex;
2706
2861
  isNavigatingHistoryRef.current = true;
2707
- (_d = inputRef.current) == null ? void 0 : _d.fill(history[newIndex]);
2862
+ (_a2 = inputRef.current) == null ? void 0 : _a2.fill(history[newIndex]);
2708
2863
  } else if (historyIndexRef.current === 0) {
2709
2864
  historyIndexRef.current = -1;
2710
2865
  isNavigatingHistoryRef.current = true;
2711
- (_e = inputRef.current) == null ? void 0 : _e.fill("");
2866
+ (_b2 = inputRef.current) == null ? void 0 : _b2.fill(historyDraftRef.current);
2867
+ historyDraftRef.current = "";
2712
2868
  }
2869
+ };
2870
+ if (canEnterHistory && key.upArrow) {
2871
+ navUp();
2872
+ return;
2873
+ }
2874
+ if (canEnterHistory && key.downArrow) {
2875
+ navDown();
2876
+ return;
2877
+ }
2878
+ if (key.ctrl && input === "p") {
2879
+ navUp();
2880
+ return;
2881
+ }
2882
+ if (key.ctrl && input === "n") {
2883
+ navDown();
2713
2884
  return;
2714
2885
  }
2715
2886
  });
@@ -3054,9 +3225,6 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
3054
3225
  const skillSuggestions = registry ? computeSuggestions(registry.list(), acState) : [];
3055
3226
  const acOpen = isOpen(acState, skillSuggestions);
3056
3227
  const facOpen = isOpen2(fileAcState, fileSuggestions);
3057
- skillAcRowsRef.current = acOpen ? skillSuggestions.length + 2 : 0;
3058
- fileAcRowsRef.current = facOpen ? fileSuggestions.length + 2 : 0;
3059
- selectedItemsRowsRef.current = selectedItems.length > 0 ? 1 : 0;
3060
3228
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: "100%", children: [
3061
3229
  /* @__PURE__ */ jsx6(Box6, { flexGrow: 1, flexDirection: "column", justifyContent: "flex-end", children: /* @__PURE__ */ jsx6(
3062
3230
  ConversationHistory,
@@ -3069,46 +3237,48 @@ function App({ config, version, autoMode = false, registry, trustedSkillDirs = [
3069
3237
  selection: selectionState
3070
3238
  }
3071
3239
  ) }),
3072
- /* @__PURE__ */ jsx6(
3073
- StatusBar,
3074
- {
3075
- status,
3076
- toolName,
3077
- confirmPrompt,
3078
- version,
3079
- tokenUsage
3080
- }
3081
- ),
3082
- /* @__PURE__ */ jsx6(
3083
- SkillAutocomplete,
3084
- {
3085
- suggestions: skillSuggestions,
3086
- selectedIndex: acState.selectedIndex,
3087
- isOpen: acOpen
3088
- }
3089
- ),
3090
- /* @__PURE__ */ jsx6(
3091
- FileAutocomplete,
3092
- {
3093
- suggestions: fileSuggestions,
3094
- selectedIndex: fileAcState.selectedIndex,
3095
- isOpen: facOpen
3096
- }
3097
- ),
3098
- selectedItems.length > 0 && /* @__PURE__ */ jsx6(Box6, { paddingLeft: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "cyan", dimColor: true, children: [
3099
- "Selected: ",
3100
- selectedItems.map((i) => i.label).join(", ")
3101
- ] }) }),
3102
- /* @__PURE__ */ jsx6(
3103
- Input_default,
3104
- {
3105
- ref: inputRef,
3106
- isActive: isInputActive,
3107
- onSubmit: handleSubmit,
3108
- onChange: handleInputTextChange,
3109
- placeholder: status === "awaiting_confirm" ? "y / n" : void 0
3110
- }
3111
- )
3240
+ /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", ref: bottomChromeRef, children: [
3241
+ /* @__PURE__ */ jsx6(
3242
+ StatusBar,
3243
+ {
3244
+ status,
3245
+ toolName,
3246
+ confirmPrompt,
3247
+ version,
3248
+ tokenUsage
3249
+ }
3250
+ ),
3251
+ /* @__PURE__ */ jsx6(
3252
+ SkillAutocomplete,
3253
+ {
3254
+ suggestions: skillSuggestions,
3255
+ selectedIndex: acState.selectedIndex,
3256
+ isOpen: acOpen
3257
+ }
3258
+ ),
3259
+ /* @__PURE__ */ jsx6(
3260
+ FileAutocomplete,
3261
+ {
3262
+ suggestions: fileSuggestions,
3263
+ selectedIndex: fileAcState.selectedIndex,
3264
+ isOpen: facOpen
3265
+ }
3266
+ ),
3267
+ selectedItems.length > 0 && /* @__PURE__ */ jsx6(Box6, { paddingLeft: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "cyan", dimColor: true, children: [
3268
+ "Selected: ",
3269
+ selectedItems.map((i) => i.label).join(", ")
3270
+ ] }) }),
3271
+ /* @__PURE__ */ jsx6(
3272
+ Input_default,
3273
+ {
3274
+ ref: inputRef,
3275
+ isActive: isInputActive,
3276
+ onSubmit: handleSubmit,
3277
+ onChange: handleInputTextChange,
3278
+ placeholder: status === "awaiting_confirm" ? "y / n" : void 0
3279
+ }
3280
+ )
3281
+ ] })
3112
3282
  ] });
3113
3283
  }
3114
3284
 
@@ -3174,6 +3344,7 @@ function tokenizeStdin(input, pendingIn) {
3174
3344
  let i = 0;
3175
3345
  let keyboardOut = "";
3176
3346
  const events = [];
3347
+ const focusEvents = [];
3177
3348
  while (i < data.length) {
3178
3349
  const ch = data[i];
3179
3350
  if (ch !== "\x1B") {
@@ -3195,13 +3366,18 @@ function tokenizeStdin(input, pendingIn) {
3195
3366
  i += mLen;
3196
3367
  continue;
3197
3368
  }
3369
+ if (tail.length >= 3 && tail[1] === "[" && (tail[2] === "I" || tail[2] === "O")) {
3370
+ focusEvents.push({ kind: "focus", focused: tail[2] === "I" });
3371
+ i += 3;
3372
+ continue;
3373
+ }
3198
3374
  if (isMousePartial(tail) && tail.length < MAX_MOUSE_SEQ) {
3199
- return { keyboard: keyboardOut, pending: tail, events };
3375
+ return { keyboard: keyboardOut, pending: tail, events, focusEvents };
3200
3376
  }
3201
3377
  keyboardOut += ch;
3202
3378
  i++;
3203
3379
  }
3204
- return { keyboard: keyboardOut, pending: "", events };
3380
+ return { keyboard: keyboardOut, pending: "", events, focusEvents };
3205
3381
  }
3206
3382
 
3207
3383
  // src/ui/stdinFilter.ts
@@ -3209,12 +3385,22 @@ var StdinFilterImpl = class extends Transform {
3209
3385
  isTTY;
3210
3386
  source;
3211
3387
  pending = "";
3212
- constructor(source) {
3388
+ // 焦点状态——cc-haha 把 focus 作 signal 而非 mode switch(§5.4)。
3389
+ // CSI I/O 切换;任何字节到达时若为 false 由 failsafe 强制回 true。
3390
+ focused = true;
3391
+ // resume 检测:最近一次活动时间戳;间隔 > resumeIdleMs 算 SSH 重连 / tmux detach 回来。
3392
+ lastByteAt = 0;
3393
+ resumeIdleMs;
3394
+ constructor(source, opts = {}) {
3213
3395
  super();
3214
3396
  this.source = source;
3215
3397
  this.isTTY = source.isTTY ?? false;
3398
+ this.resumeIdleMs = opts.resumeIdleMs ?? 5e3;
3216
3399
  source.pipe(this);
3217
3400
  }
3401
+ isFocused() {
3402
+ return this.focused;
3403
+ }
3218
3404
  setRawMode(mode) {
3219
3405
  var _a, _b;
3220
3406
  (_b = (_a = this.source).setRawMode) == null ? void 0 : _b.call(_a, mode);
@@ -3229,9 +3415,23 @@ var StdinFilterImpl = class extends Transform {
3229
3415
  }
3230
3416
  _transform(chunk, _encoding, callback) {
3231
3417
  const s = typeof chunk === "string" ? chunk : chunk.toString("binary");
3232
- const { keyboard, pending, events } = tokenizeStdin(s, this.pending);
3418
+ const now = Date.now();
3419
+ if (this.lastByteAt > 0 && now - this.lastByteAt >= this.resumeIdleMs) {
3420
+ this.emit("resumed");
3421
+ }
3422
+ this.lastByteAt = now;
3423
+ const { keyboard, pending, events, focusEvents } = tokenizeStdin(s, this.pending);
3233
3424
  this.pending = pending;
3425
+ for (const ev of focusEvents) {
3426
+ this.focused = ev.focused;
3427
+ this.emit("focus", ev);
3428
+ }
3234
3429
  for (const ev of events) this.emit("mouse", ev);
3430
+ const hadNonFocusActivity = events.length > 0 || keyboard.length > 0;
3431
+ if (hadNonFocusActivity && !this.focused) {
3432
+ this.focused = true;
3433
+ this.emit("focus", { kind: "focus", focused: true });
3434
+ }
3235
3435
  if (keyboard.length > 0) {
3236
3436
  callback(null, Buffer.from(keyboard, "binary"));
3237
3437
  } else {
@@ -3239,8 +3439,8 @@ var StdinFilterImpl = class extends Transform {
3239
3439
  }
3240
3440
  }
3241
3441
  };
3242
- function createStdinFilter(source) {
3243
- return new StdinFilterImpl(source);
3442
+ function createStdinFilter(source, opts) {
3443
+ return new StdinFilterImpl(source, opts);
3244
3444
  }
3245
3445
  export {
3246
3446
  App,
@@ -3,7 +3,7 @@ const _ew=process.emitWarning.bind(process);process.emitWarning=function(w,...a)
3
3
  import {
4
4
  cmdSessionsFork,
5
5
  cmdSessionsReplay
6
- } from "./chunk-N3IDMSG5.js";
6
+ } from "./chunk-ZNCND354.js";
7
7
  import {
8
8
  handleSkillInput,
9
9
  loadJobs,
@@ -17,7 +17,7 @@ import {
17
17
  loadMessagesFromJsonl,
18
18
  resolveActiveProfile,
19
19
  saveConfig
20
- } from "./chunk-SIQQG6FT.js";
20
+ } from "./chunk-LODP7T4D.js";
21
21
 
22
22
  // src/web/server.ts
23
23
  import Fastify from "fastify";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.59",
3
+ "version": "0.5.61",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",