@zhongqian97-code/ecode 0.2.2 → 0.2.4
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.
- package/dist/index.js +141 -133
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -95,8 +95,8 @@ function loadConfig() {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// src/ui/App.tsx
|
|
98
|
-
import { useState as useState3, useCallback, useRef as useRef2, useEffect as useEffect3 } from "react";
|
|
99
|
-
import { Box as Box5, useInput as useInput2 } from "ink";
|
|
98
|
+
import { useState as useState3, useCallback, useRef as useRef2, useEffect as useEffect3, useMemo } from "react";
|
|
99
|
+
import { Box as Box5, useInput as useInput2, useStdout } from "ink";
|
|
100
100
|
|
|
101
101
|
// src/llm.ts
|
|
102
102
|
import OpenAI from "openai";
|
|
@@ -472,162 +472,123 @@ function StatusBar({
|
|
|
472
472
|
|
|
473
473
|
// src/ui/ConversationHistory.tsx
|
|
474
474
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
475
|
-
|
|
475
|
+
|
|
476
|
+
// src/ui/messageLines.ts
|
|
476
477
|
var TOOL_RESULT_MAX_LINES = 3;
|
|
477
|
-
function
|
|
478
|
-
const
|
|
479
|
-
if (
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
content
|
|
490
|
-
] }) })
|
|
491
|
-
);
|
|
492
|
-
}
|
|
493
|
-
function AssistantMessage({
|
|
494
|
-
content,
|
|
495
|
-
tool_calls,
|
|
496
|
-
reasoning_content,
|
|
497
|
-
expandTools
|
|
498
|
-
}) {
|
|
499
|
-
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 0, children: [
|
|
500
|
-
reasoning_content && reasoning_content.length > 0 && (expandTools ? (
|
|
501
|
-
// 展开:显示 <thinking> 标题和完整推理内容
|
|
502
|
-
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
503
|
-
/* @__PURE__ */ jsx2(Text2, { color: "gray", children: "<thinking>" }),
|
|
504
|
-
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: reasoning_content })
|
|
505
|
-
] })
|
|
506
|
-
) : (
|
|
507
|
-
// 折叠:一行简短占位符,告知用户有隐藏的思考内容
|
|
508
|
-
/* @__PURE__ */ jsx2(Text2, { color: "gray", children: "[+ thinking]" })
|
|
509
|
-
)),
|
|
510
|
-
content && content.trim().length > 0 && /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: content }),
|
|
511
|
-
tool_calls && tool_calls.length > 0 && (expandTools ? (
|
|
512
|
-
// 展开:逐条渲染每个工具调用
|
|
513
|
-
tool_calls.map((tc, idx) => {
|
|
514
|
-
let argsDisplay = tc.function.arguments;
|
|
515
|
-
try {
|
|
516
|
-
const parsed = JSON.parse(tc.function.arguments);
|
|
517
|
-
if (typeof parsed === "object" && parsed !== null) {
|
|
518
|
-
argsDisplay = Object.values(parsed).map(String).join(", ");
|
|
519
|
-
}
|
|
520
|
-
} catch {
|
|
521
|
-
}
|
|
522
|
-
return (
|
|
523
|
-
// 黄色 ⚙ 图标 + 工具名 + 格式化参数
|
|
524
|
-
/* @__PURE__ */ jsxs2(Text2, { color: "yellow", children: [
|
|
525
|
-
"\u2699 \u8C03\u7528\u5DE5\u5177: ",
|
|
526
|
-
tc.function.name,
|
|
527
|
-
"(",
|
|
528
|
-
argsDisplay,
|
|
529
|
-
")"
|
|
530
|
-
] }, idx)
|
|
531
|
-
);
|
|
532
|
-
})
|
|
533
|
-
) : (
|
|
534
|
-
// 折叠:用计数占位符替代,避免占用多行
|
|
535
|
-
/* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
536
|
-
"[+ ",
|
|
537
|
-
tool_calls.length,
|
|
538
|
-
" \u4E2A\u5DE5\u5177\u8C03\u7528]"
|
|
539
|
-
] })
|
|
540
|
-
))
|
|
541
|
-
] });
|
|
542
|
-
}
|
|
543
|
-
function ToolMessage({
|
|
544
|
-
content,
|
|
545
|
-
expandTools
|
|
546
|
-
}) {
|
|
547
|
-
if (!expandTools) {
|
|
548
|
-
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "row", marginBottom: 0, children: /* @__PURE__ */ jsx2(Text2, { color: "gray", children: "[+ \u5DE5\u5177\u7ED3\u679C]" }) });
|
|
478
|
+
function wrapText(text, terminalWidth) {
|
|
479
|
+
const logicalLines = text.split("\n");
|
|
480
|
+
if (terminalWidth <= 0) return logicalLines;
|
|
481
|
+
const result = [];
|
|
482
|
+
for (const line of logicalLines) {
|
|
483
|
+
if (line.length === 0) {
|
|
484
|
+
result.push("");
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
for (let i = 0; i < line.length; i += terminalWidth) {
|
|
488
|
+
result.push(line.slice(i, i + terminalWidth));
|
|
489
|
+
}
|
|
549
490
|
}
|
|
550
|
-
|
|
551
|
-
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "row", marginBottom: 0, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", children: [
|
|
552
|
-
"[\u5DE5\u5177\u7ED3\u679C] ",
|
|
553
|
-
truncated
|
|
554
|
-
] }) });
|
|
491
|
+
return result;
|
|
555
492
|
}
|
|
556
|
-
function
|
|
493
|
+
function messageToLines(msg, expandTools, terminalWidth) {
|
|
557
494
|
if (msg.role === "user") {
|
|
558
|
-
return
|
|
495
|
+
return wrapText(`> ${msg.content}`, terminalWidth).map((text) => ({
|
|
496
|
+
text,
|
|
497
|
+
color: "white"
|
|
498
|
+
}));
|
|
559
499
|
}
|
|
560
500
|
if (msg.role === "assistant") {
|
|
561
|
-
const
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
if (assistantMsg.reasoning_content && assistantMsg.reasoning_content.length > 0) {
|
|
501
|
+
const lines = [];
|
|
502
|
+
const aMsg = msg;
|
|
503
|
+
if (aMsg.reasoning_content && aMsg.reasoning_content.length > 0) {
|
|
565
504
|
if (expandTools) {
|
|
566
|
-
|
|
505
|
+
lines.push({ text: "<thinking>", color: "gray" });
|
|
506
|
+
for (const t of wrapText(aMsg.reasoning_content, terminalWidth)) {
|
|
507
|
+
lines.push({ text: t, dimColor: true });
|
|
508
|
+
}
|
|
567
509
|
} else {
|
|
568
|
-
|
|
510
|
+
lines.push({ text: "[+ thinking]", color: "gray" });
|
|
569
511
|
}
|
|
570
512
|
}
|
|
571
|
-
|
|
572
|
-
|
|
513
|
+
if (aMsg.content && aMsg.content.trim().length > 0) {
|
|
514
|
+
for (const t of wrapText(aMsg.content, terminalWidth)) {
|
|
515
|
+
lines.push({ text: t, color: "cyan" });
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (aMsg.tool_calls && aMsg.tool_calls.length > 0) {
|
|
573
519
|
if (expandTools) {
|
|
574
|
-
|
|
520
|
+
for (const tc of aMsg.tool_calls) {
|
|
521
|
+
let argsDisplay = tc.function.arguments;
|
|
522
|
+
try {
|
|
523
|
+
const parsed = JSON.parse(tc.function.arguments);
|
|
524
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
525
|
+
argsDisplay = Object.values(parsed).map(String).join(", ");
|
|
526
|
+
}
|
|
527
|
+
} catch {
|
|
528
|
+
}
|
|
529
|
+
const lineText = `\u2699 \u8C03\u7528\u5DE5\u5177: ${tc.function.name}(${argsDisplay})`;
|
|
530
|
+
for (const t of wrapText(lineText, terminalWidth)) {
|
|
531
|
+
lines.push({ text: t, color: "yellow" });
|
|
532
|
+
}
|
|
533
|
+
}
|
|
575
534
|
} else {
|
|
576
|
-
|
|
535
|
+
lines.push({
|
|
536
|
+
text: `[+ ${aMsg.tool_calls.length} \u4E2A\u5DE5\u5177\u8C03\u7528]`,
|
|
537
|
+
color: "gray"
|
|
538
|
+
});
|
|
577
539
|
}
|
|
578
540
|
}
|
|
579
|
-
return
|
|
541
|
+
return lines.length > 0 ? lines : [{ text: "" }];
|
|
580
542
|
}
|
|
581
543
|
if (msg.role === "tool") {
|
|
582
544
|
if (!expandTools) {
|
|
583
|
-
return
|
|
545
|
+
return [{ text: "[+ \u5DE5\u5177\u7ED3\u679C]", color: "gray" }];
|
|
584
546
|
}
|
|
585
|
-
|
|
547
|
+
const rawLines = msg.content.split("\n");
|
|
548
|
+
const truncated = rawLines.length > TOOL_RESULT_MAX_LINES ? rawLines.slice(0, TOOL_RESULT_MAX_LINES - 1).join("\n") + "\n\u2026" : msg.content;
|
|
549
|
+
return [
|
|
550
|
+
{ text: "[\u5DE5\u5177\u7ED3\u679C]", color: "gray" },
|
|
551
|
+
...wrapText(truncated, terminalWidth).map((text) => ({
|
|
552
|
+
text,
|
|
553
|
+
color: "gray"
|
|
554
|
+
}))
|
|
555
|
+
];
|
|
586
556
|
}
|
|
587
|
-
return
|
|
557
|
+
return [];
|
|
588
558
|
}
|
|
559
|
+
|
|
560
|
+
// src/ui/ConversationHistory.tsx
|
|
561
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
589
562
|
function ConversationHistory({
|
|
590
563
|
messages,
|
|
591
564
|
maxHeight = 20,
|
|
592
|
-
expandTools = false
|
|
565
|
+
expandTools = false,
|
|
566
|
+
terminalWidth = 0,
|
|
567
|
+
scrollOffset = 0
|
|
593
568
|
}) {
|
|
594
569
|
const visible = messages.filter(
|
|
595
570
|
(m) => m.role !== "system"
|
|
596
571
|
);
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
for (let i = visible.length - 1; i >= 0; i--) {
|
|
600
|
-
const lines = estimateLines(visible[i], expandTools);
|
|
601
|
-
if (totalLines + lines > maxHeight) break;
|
|
602
|
-
totalLines += lines;
|
|
603
|
-
startIdx = i;
|
|
604
|
-
}
|
|
605
|
-
const displayMessages = visible.slice(startIdx);
|
|
606
|
-
return (
|
|
607
|
-
// 纵向堆叠所有消息
|
|
608
|
-
/* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: displayMessages.map((msg, idx) => {
|
|
609
|
-
if (msg.role === "user") {
|
|
610
|
-
return /* @__PURE__ */ jsx2(UserMessage, { content: msg.content }, idx);
|
|
611
|
-
}
|
|
612
|
-
if (msg.role === "assistant") {
|
|
613
|
-
const assistantMsg = msg;
|
|
614
|
-
return /* @__PURE__ */ jsx2(
|
|
615
|
-
AssistantMessage,
|
|
616
|
-
{
|
|
617
|
-
content: assistantMsg.content,
|
|
618
|
-
tool_calls: assistantMsg.tool_calls,
|
|
619
|
-
reasoning_content: assistantMsg.reasoning_content,
|
|
620
|
-
expandTools
|
|
621
|
-
},
|
|
622
|
-
idx
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
if (msg.role === "tool") {
|
|
626
|
-
return /* @__PURE__ */ jsx2(ToolMessage, { content: msg.content, expandTools }, idx);
|
|
627
|
-
}
|
|
628
|
-
return null;
|
|
629
|
-
}) })
|
|
572
|
+
const allLines = visible.flatMap(
|
|
573
|
+
(msg) => messageToLines(msg, expandTools, terminalWidth)
|
|
630
574
|
);
|
|
575
|
+
const totalLines = allLines.length;
|
|
576
|
+
const end = Math.max(0, Math.min(totalLines, totalLines - scrollOffset));
|
|
577
|
+
let start = Math.max(0, end - maxHeight);
|
|
578
|
+
let linesAbove = start;
|
|
579
|
+
if (linesAbove > 0 && maxHeight > 1) {
|
|
580
|
+
start = Math.max(0, end - (maxHeight - 1));
|
|
581
|
+
linesAbove = start;
|
|
582
|
+
}
|
|
583
|
+
const visibleLines = allLines.slice(start, end);
|
|
584
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
585
|
+
linesAbove > 0 && /* @__PURE__ */ jsxs2(Text2, { color: "gray", dimColor: true, children: [
|
|
586
|
+
"\u2191 ",
|
|
587
|
+
linesAbove,
|
|
588
|
+
" \u884C \xB7 PageUp/Down \u6EDA\u52A8"
|
|
589
|
+
] }),
|
|
590
|
+
visibleLines.map((line, idx) => /* @__PURE__ */ jsx2(Text2, { color: line.color, dimColor: line.dimColor, children: line.text }, idx))
|
|
591
|
+
] });
|
|
631
592
|
}
|
|
632
593
|
|
|
633
594
|
// src/ui/Input.tsx
|
|
@@ -788,7 +749,9 @@ function dismiss(state) {
|
|
|
788
749
|
|
|
789
750
|
// src/ui/App.tsx
|
|
790
751
|
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
791
|
-
function App({ config: config2, version: version2, autoMode: autoMode2 = false, registry: registry2 }) {
|
|
752
|
+
function App({ config: config2, version: version2, autoMode: autoMode2 = false, registry: registry2, llmClient }) {
|
|
753
|
+
const { stdout } = useStdout();
|
|
754
|
+
const historyMaxHeight = Math.max(5, (stdout?.rows ?? 24) - 4);
|
|
792
755
|
const [messages, setMessages] = useState3([]);
|
|
793
756
|
const [status, setStatus] = useState3("idle");
|
|
794
757
|
const contextLimit = getContextLimit(config2.model, config2.contextLimit);
|
|
@@ -800,8 +763,16 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
800
763
|
const [toolName, setToolName] = useState3(void 0);
|
|
801
764
|
const [confirmPrompt, setConfirmPrompt] = useState3(void 0);
|
|
802
765
|
const [expandTools, setExpandTools] = useState3(false);
|
|
766
|
+
const [scrollOffset, setScrollOffset] = useState3(0);
|
|
767
|
+
const totalLines = useMemo(() => {
|
|
768
|
+
const visible = messages.filter((m) => m.role !== "system");
|
|
769
|
+
return visible.reduce(
|
|
770
|
+
(acc, msg) => acc + messageToLines(msg, expandTools, stdout?.columns ?? 0).length,
|
|
771
|
+
0
|
|
772
|
+
);
|
|
773
|
+
}, [messages, expandTools, stdout?.columns]);
|
|
803
774
|
const pendingConfirmRef = useRef2(null);
|
|
804
|
-
const llmRef = useRef2(createLLMClient(config2));
|
|
775
|
+
const llmRef = useRef2(llmClient ?? createLLMClient(config2));
|
|
805
776
|
const inputRef = useRef2(null);
|
|
806
777
|
const [acState, setAcState] = useState3(getInitialState());
|
|
807
778
|
const loggerRef = useRef2(null);
|
|
@@ -855,6 +826,16 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
855
826
|
}
|
|
856
827
|
if (key.tab) {
|
|
857
828
|
setExpandTools((prev) => !prev);
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
const scrollStep = Math.max(1, Math.floor(historyMaxHeight / 2));
|
|
832
|
+
if (key.pageUp) {
|
|
833
|
+
setScrollOffset((prev) => Math.min(prev + scrollStep, Math.max(0, totalLines - 1)));
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
if (key.pageDown) {
|
|
837
|
+
setScrollOffset((prev) => Math.max(0, prev - scrollStep));
|
|
838
|
+
return;
|
|
858
839
|
}
|
|
859
840
|
});
|
|
860
841
|
const confirm = useCallback((prompt) => {
|
|
@@ -999,6 +980,7 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
999
980
|
return;
|
|
1000
981
|
}
|
|
1001
982
|
if (!trimmed) return;
|
|
983
|
+
setScrollOffset(0);
|
|
1002
984
|
if (registry2) {
|
|
1003
985
|
const skillResult = handleSkillInput(trimmed, registry2);
|
|
1004
986
|
if (skillResult.type === "error") {
|
|
@@ -1046,7 +1028,16 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
|
|
|
1046
1028
|
const skillSuggestions = registry2 ? computeSuggestions(registry2.list(), acState) : [];
|
|
1047
1029
|
const acOpen = isOpen(acState, skillSuggestions);
|
|
1048
1030
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", height: "100%", children: [
|
|
1049
|
-
/* @__PURE__ */ jsx5(
|
|
1031
|
+
/* @__PURE__ */ jsx5(
|
|
1032
|
+
ConversationHistory,
|
|
1033
|
+
{
|
|
1034
|
+
messages,
|
|
1035
|
+
expandTools,
|
|
1036
|
+
maxHeight: historyMaxHeight,
|
|
1037
|
+
terminalWidth: stdout?.columns,
|
|
1038
|
+
scrollOffset
|
|
1039
|
+
}
|
|
1040
|
+
),
|
|
1050
1041
|
/* @__PURE__ */ jsx5(
|
|
1051
1042
|
StatusBar,
|
|
1052
1043
|
{
|
|
@@ -1205,4 +1196,21 @@ for (const dir of [builtinSkillsDir, userSkillsDir, projectSkillsDir]) {
|
|
|
1205
1196
|
const skills = await loadSkillsFromDir(dir);
|
|
1206
1197
|
for (const skill of skills) registry.register(skill);
|
|
1207
1198
|
}
|
|
1199
|
+
if (process.stdout.isTTY) {
|
|
1200
|
+
process.stdout.write("\x1B[?1049h");
|
|
1201
|
+
const exitAltScreen = () => process.stdout.write("\x1B[?1049l");
|
|
1202
|
+
process.on("exit", exitAltScreen);
|
|
1203
|
+
process.on("SIGINT", () => {
|
|
1204
|
+
exitAltScreen();
|
|
1205
|
+
process.exit(0);
|
|
1206
|
+
});
|
|
1207
|
+
process.on("SIGTERM", () => {
|
|
1208
|
+
exitAltScreen();
|
|
1209
|
+
process.exit(0);
|
|
1210
|
+
});
|
|
1211
|
+
process.on("SIGHUP", () => {
|
|
1212
|
+
exitAltScreen();
|
|
1213
|
+
process.exit(0);
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1208
1216
|
render(React4.createElement(App, { config: finalConfig, version: VERSION, autoMode, registry }));
|