@autopilot-agent/cli 0.0.0-stage → 0.1.0
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/README.md +180 -2
- package/dist/activity-animation.d.ts +57 -0
- package/dist/activity-animation.js +145 -0
- package/dist/animation-clock.d.ts +22 -0
- package/dist/animation-clock.js +76 -0
- package/dist/composition-root.d.ts +105 -0
- package/dist/composition-root.js +588 -0
- package/dist/context-report.d.ts +20 -0
- package/dist/context-report.js +174 -0
- package/dist/controller.d.ts +79 -0
- package/dist/controller.js +681 -0
- package/dist/design-tokens.d.ts +34 -0
- package/dist/design-tokens.js +34 -0
- package/dist/error-diagnostics.d.ts +8 -0
- package/dist/error-diagnostics.js +62 -0
- package/dist/event-projector.d.ts +26 -0
- package/dist/event-projector.js +333 -0
- package/dist/file-mention-context.d.ts +34 -0
- package/dist/file-mention-context.js +173 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +14 -0
- package/dist/main.d.ts +40 -0
- package/dist/main.js +543 -0
- package/dist/parser.d.ts +17 -0
- package/dist/parser.js +391 -0
- package/dist/renderer.d.ts +9 -0
- package/dist/renderer.js +48 -0
- package/dist/scroll-viewport.d.ts +22 -0
- package/dist/scroll-viewport.js +62 -0
- package/dist/termination-format.d.ts +4 -0
- package/dist/termination-format.js +17 -0
- package/dist/timeline-lines.d.ts +38 -0
- package/dist/timeline-lines.js +104 -0
- package/dist/tui/app.d.ts +30 -0
- package/dist/tui/app.js +410 -0
- package/dist/tui/approval.d.ts +19 -0
- package/dist/tui/approval.js +37 -0
- package/dist/tui/brand/brand-header.d.ts +20 -0
- package/dist/tui/brand/brand-header.js +22 -0
- package/dist/tui/brand/intro.d.ts +12 -0
- package/dist/tui/brand/intro.js +88 -0
- package/dist/tui/brand/pixel.d.ts +8 -0
- package/dist/tui/brand/pixel.js +50 -0
- package/dist/tui/constants.d.ts +56 -0
- package/dist/tui/constants.js +57 -0
- package/dist/tui/context.d.ts +11 -0
- package/dist/tui/context.js +12 -0
- package/dist/tui/diff-stats.d.ts +8 -0
- package/dist/tui/diff-stats.js +7 -0
- package/dist/tui/format.d.ts +75 -0
- package/dist/tui/format.js +176 -0
- package/dist/tui/hooks/use-file-mention-index.d.ts +15 -0
- package/dist/tui/hooks/use-file-mention-index.js +59 -0
- package/dist/tui/hooks/use-pickers.d.ts +47 -0
- package/dist/tui/hooks/use-pickers.js +288 -0
- package/dist/tui/hooks/use-prompt-input.d.ts +54 -0
- package/dist/tui/hooks/use-prompt-input.js +258 -0
- package/dist/tui/hooks/use-status-view.d.ts +8 -0
- package/dist/tui/hooks/use-status-view.js +13 -0
- package/dist/tui/hooks/use-viewport.d.ts +22 -0
- package/dist/tui/hooks/use-viewport.js +117 -0
- package/dist/tui/input/file-mention.d.ts +33 -0
- package/dist/tui/input/file-mention.js +112 -0
- package/dist/tui/input/graphemes.d.ts +5 -0
- package/dist/tui/input/graphemes.js +10 -0
- package/dist/tui/input/keys.d.ts +16 -0
- package/dist/tui/input/keys.js +58 -0
- package/dist/tui/input/prompt-layout.d.ts +27 -0
- package/dist/tui/input/prompt-layout.js +84 -0
- package/dist/tui/input-area.d.ts +53 -0
- package/dist/tui/input-area.js +89 -0
- package/dist/tui/jobs.d.ts +10 -0
- package/dist/tui/jobs.js +24 -0
- package/dist/tui/lifecycle.d.ts +23 -0
- package/dist/tui/lifecycle.js +114 -0
- package/dist/tui/markdown/diff.d.ts +8 -0
- package/dist/tui/markdown/diff.js +19 -0
- package/dist/tui/markdown/inline.d.ts +4 -0
- package/dist/tui/markdown/inline.js +32 -0
- package/dist/tui/markdown/markdown.d.ts +17 -0
- package/dist/tui/markdown/markdown.js +117 -0
- package/dist/tui/markdown/table.d.ts +19 -0
- package/dist/tui/markdown/table.js +74 -0
- package/dist/tui/performance-diagnostics.d.ts +7 -0
- package/dist/tui/performance-diagnostics.js +57 -0
- package/dist/tui/pickers/effort.d.ts +10 -0
- package/dist/tui/pickers/effort.js +12 -0
- package/dist/tui/pickers/file-mention.d.ts +11 -0
- package/dist/tui/pickers/file-mention.js +22 -0
- package/dist/tui/pickers/model.d.ts +10 -0
- package/dist/tui/pickers/model.js +12 -0
- package/dist/tui/pickers/permission.d.ts +10 -0
- package/dist/tui/pickers/permission.js +13 -0
- package/dist/tui/pickers/skill.d.ts +13 -0
- package/dist/tui/pickers/skill.js +25 -0
- package/dist/tui/pickers/slash.d.ts +10 -0
- package/dist/tui/pickers/slash.js +11 -0
- package/dist/tui/prompt-input.d.ts +11 -0
- package/dist/tui/prompt-input.js +73 -0
- package/dist/tui/render-mode.d.ts +20 -0
- package/dist/tui/render-mode.js +31 -0
- package/dist/tui/run.d.ts +6 -0
- package/dist/tui/run.js +237 -0
- package/dist/tui/status-line.d.ts +16 -0
- package/dist/tui/status-line.js +37 -0
- package/dist/tui/store.d.ts +4 -0
- package/dist/tui/store.js +344 -0
- package/dist/tui/timeline/live.d.ts +16 -0
- package/dist/tui/timeline/live.js +151 -0
- package/dist/tui/timeline/presentation-timeline.d.ts +14 -0
- package/dist/tui/timeline/presentation-timeline.js +109 -0
- package/dist/tui/timeline/progress.d.ts +12 -0
- package/dist/tui/timeline/progress.js +43 -0
- package/dist/tui/timeline/static-timeline.d.ts +13 -0
- package/dist/tui/timeline/static-timeline.js +121 -0
- package/dist/tui/timeline/tool-tree.d.ts +7 -0
- package/dist/tui/timeline/tool-tree.js +25 -0
- package/dist/tui/timeline/turn.d.ts +19 -0
- package/dist/tui/timeline/turn.js +21 -0
- package/dist/tui/todo-list.d.ts +50 -0
- package/dist/tui/todo-list.js +120 -0
- package/dist/tui/transcript.d.ts +24 -0
- package/dist/tui/transcript.js +34 -0
- package/dist/tui/trust-dialog.d.ts +51 -0
- package/dist/tui/trust-dialog.js +127 -0
- package/dist/tui/types.d.ts +175 -0
- package/dist/tui/types.js +5 -0
- package/dist/tui/viewport/selection.d.ts +27 -0
- package/dist/tui/viewport/selection.js +79 -0
- package/dist/tui/viewport/sources.d.ts +29 -0
- package/dist/tui/viewport/sources.js +585 -0
- package/dist/tui/viewport/viewport.d.ts +12 -0
- package/dist/tui/viewport/viewport.js +87 -0
- package/dist/tui-input-history.d.ts +23 -0
- package/dist/tui-input-history.js +42 -0
- package/dist/tui-input-router.d.ts +24 -0
- package/dist/tui-input-router.js +115 -0
- package/dist/tui.d.ts +15 -0
- package/dist/tui.js +13 -0
- package/dist/types.d.ts +454 -0
- package/dist/types.js +7 -0
- package/package.json +58 -4
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** 品牌开场动画:logo 帧、运动位置、像素字母逐列显现与着色渲染。 */
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
/** Autopilot 启动画面的侧脸、转头和眨眼帧。 */
|
|
4
|
+
export declare function getAutopilotLogoRows(elapsedMs: number): string[];
|
|
5
|
+
/** 计算 logo 的运动位置;终点固定在像素字母末尾之后,避免覆盖最后几个字母。 */
|
|
6
|
+
export declare function getAutopilotLogoX(elapsedMs: number): number;
|
|
7
|
+
/** 生成启动动画的像素字母:字母逐列出现,最后用亮色字符扫过。 */
|
|
8
|
+
export declare function createAutopilotBrandRows(elapsedMs: number): string[];
|
|
9
|
+
/** 渲染静态 logo;眼睛保持绿色,其余轮廓使用紫色品牌 token。 */
|
|
10
|
+
export declare function renderAutopilotLogo(rows: string[], useColor: boolean): ReactNode;
|
|
11
|
+
/** 渲染开场动画;像素字母为蓝色,运动中的 logo 为紫色,眼睛为绿色。 */
|
|
12
|
+
export declare function renderAutopilotIntroRows(elapsedMs: number, useColor: boolean): ReactNode;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/** 品牌开场动画:logo 帧、运动位置、像素字母逐列显现与着色渲染。 */
|
|
2
|
+
import { CLI_TUI_COLORS } from "../../design-tokens.js";
|
|
3
|
+
import { clampTuiProgress } from "../format.js";
|
|
4
|
+
import { AUTOPILOT_BRAND_WORD, AUTOPILOT_INTRO_WIDTH, AUTOPILOT_LOGO_FINAL_X, AUTOPILOT_LOGO_HEIGHT, AUTOPILOT_WORD_START, AUTOPILOT_WORD_WIDTH, } from "../constants.js";
|
|
5
|
+
import { AUTOPILOT_PIXEL_FONT, renderAutopilotColoredRows } from "./pixel.js";
|
|
6
|
+
/** Autopilot 启动画面的侧脸、转头和眨眼帧。 */
|
|
7
|
+
export function getAutopilotLogoRows(elapsedMs) {
|
|
8
|
+
if (elapsedMs < 2_100)
|
|
9
|
+
return [" ▄▄▄ ", " █ █ ", " █ · █ ", " █▄▄▄██ ", " ███ ", " ▀▀ ", " "];
|
|
10
|
+
if (elapsedMs < 2_550)
|
|
11
|
+
return [" ▄▄▄ ", " █ █ ", " █ · █ ", " █▄▄▄██ ", " ███ ", " ▀▀ ", " "];
|
|
12
|
+
if (elapsedMs < 2_800)
|
|
13
|
+
return [" ▄▄▄▄▄ ", " █ █", "█ · · █", "█ ▀ █", "█ ─── █", " █▄▄▄▄▄█ ", " ▀▀▀ "];
|
|
14
|
+
if (elapsedMs < 3_050)
|
|
15
|
+
return [" ▄▄▄▄▄ ", " █ █", "█ ─ ─ █", "█ ▀ █", "█ ─── █", " █▄▄▄▄▄█ ", " ▀▀▀ "];
|
|
16
|
+
return [" ▄▄▄▄▄ ", " █ █", "█ ● ● █", "█ ▀ █", "█ ─── █", " █▄▄▄▄▄█ ", " ▀▀▀ "];
|
|
17
|
+
}
|
|
18
|
+
/** 计算 logo 的运动位置;终点固定在像素字母末尾之后,避免覆盖最后几个字母。 */
|
|
19
|
+
export function getAutopilotLogoX(elapsedMs) {
|
|
20
|
+
return Math.round(AUTOPILOT_LOGO_FINAL_X * clampTuiProgress(elapsedMs / 1_350));
|
|
21
|
+
}
|
|
22
|
+
/** 生成启动动画的像素字母:字母逐列出现,最后用亮色字符扫过。 */
|
|
23
|
+
export function createAutopilotBrandRows(elapsedMs) {
|
|
24
|
+
const wordWidth = AUTOPILOT_WORD_WIDTH;
|
|
25
|
+
const totalWidth = AUTOPILOT_INTRO_WIDTH;
|
|
26
|
+
const revealColumns = Math.round(wordWidth * clampTuiProgress((elapsedMs - 180) / 1_050));
|
|
27
|
+
const scanProgress = clampTuiProgress((elapsedMs - 1_650) / 850);
|
|
28
|
+
const scanColumn = elapsedMs < 1_650 ? -1 : Math.round((wordWidth - 1) * scanProgress);
|
|
29
|
+
const logoX = getAutopilotLogoX(elapsedMs);
|
|
30
|
+
const logoRows = getAutopilotLogoRows(elapsedMs);
|
|
31
|
+
const rows = Array.from({ length: AUTOPILOT_LOGO_HEIGHT }, function createBlankRow() {
|
|
32
|
+
return Array.from({ length: totalWidth }, function createBlankCell() { return " "; });
|
|
33
|
+
});
|
|
34
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
|
|
35
|
+
const row = rows[rowIndex];
|
|
36
|
+
const logoRow = logoRows[rowIndex] ?? "";
|
|
37
|
+
const letters = AUTOPILOT_BRAND_WORD.split("");
|
|
38
|
+
for (let letterIndex = 0; letterIndex < letters.length; letterIndex += 1) {
|
|
39
|
+
const glyph = AUTOPILOT_PIXEL_FONT[letters[letterIndex] ?? ""];
|
|
40
|
+
const glyphRow = glyph?.[rowIndex] ?? "00000";
|
|
41
|
+
for (let pixelIndex = 0; pixelIndex < glyphRow.length; pixelIndex += 1) {
|
|
42
|
+
const wordColumn = letterIndex * 6 + pixelIndex;
|
|
43
|
+
if (wordColumn >= revealColumns || glyphRow[pixelIndex] !== "1")
|
|
44
|
+
continue;
|
|
45
|
+
const outputColumn = AUTOPILOT_WORD_START + wordColumn;
|
|
46
|
+
row[outputColumn] = Math.abs(wordColumn - scanColumn) <= 1 ? "▐" : "▌";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
for (let logoIndex = 0; logoIndex < logoRow.length; logoIndex += 1) {
|
|
50
|
+
const logoCell = logoRow[logoIndex];
|
|
51
|
+
if (logoCell && logoCell !== " ")
|
|
52
|
+
row[logoX + logoIndex] = logoCell;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// 每一帧都返回相同宽度;trimEnd 会让右侧内容随像素字母显现而横向移动。
|
|
56
|
+
return rows.map(function padBrandRow(row) { return row.join("").padEnd(totalWidth, " "); });
|
|
57
|
+
}
|
|
58
|
+
/** 渲染静态 logo;眼睛保持绿色,其余轮廓使用紫色品牌 token。 */
|
|
59
|
+
export function renderAutopilotLogo(rows, useColor) {
|
|
60
|
+
return renderAutopilotColoredRows(rows, function colorAutopilotLogoCell(char) {
|
|
61
|
+
if (!useColor || char === " ")
|
|
62
|
+
return undefined;
|
|
63
|
+
if (char === "·" || char === "●")
|
|
64
|
+
return CLI_TUI_COLORS.copilotGreen;
|
|
65
|
+
if (char === "─" || char === "▀" || char === "▄")
|
|
66
|
+
return CLI_TUI_COLORS.copilotBlue;
|
|
67
|
+
return CLI_TUI_COLORS.copilotPurple;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/** 渲染开场动画;像素字母为蓝色,运动中的 logo 为紫色,眼睛为绿色。 */
|
|
71
|
+
export function renderAutopilotIntroRows(elapsedMs, useColor) {
|
|
72
|
+
const rows = createAutopilotBrandRows(elapsedMs);
|
|
73
|
+
const logoRows = getAutopilotLogoRows(elapsedMs);
|
|
74
|
+
const logoX = getAutopilotLogoX(elapsedMs);
|
|
75
|
+
return renderAutopilotColoredRows(rows, function colorAutopilotIntroCell(char, column, rowIndex) {
|
|
76
|
+
if (!useColor || char === " ")
|
|
77
|
+
return undefined;
|
|
78
|
+
const logoCell = logoRows[rowIndex]?.[column - logoX];
|
|
79
|
+
if (logoCell && logoCell !== " ") {
|
|
80
|
+
if (logoCell === "·" || logoCell === "●")
|
|
81
|
+
return CLI_TUI_COLORS.copilotGreen;
|
|
82
|
+
if (logoCell === "─" || logoCell === "▀" || logoCell === "▄")
|
|
83
|
+
return CLI_TUI_COLORS.copilotBlue;
|
|
84
|
+
return CLI_TUI_COLORS.copilotPurple;
|
|
85
|
+
}
|
|
86
|
+
return char === "▐" ? CLI_TUI_COLORS.copilotBlueBright : CLI_TUI_COLORS.copilotBlue;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** 品牌像素字体与渲染纯函数:静态品牌行生成 + 按字符着色渲染,供 header/intro/viewport 共用。 */
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
/** Autopilot 品牌像素字体;每个字母 5×7,由 0/1 表示亮灭。 */
|
|
4
|
+
export declare const AUTOPILOT_PIXEL_FONT: Record<string, readonly string[]>;
|
|
5
|
+
/** 根据字符来源把品牌动画拆成 Copilot-inspired 的蓝、紫、绿三组 token。 */
|
|
6
|
+
export declare function renderAutopilotColoredRows(rows: string[], colorForCell: (char: string, column: number, rowIndex: number) => string | undefined, maximumWidth?: number): ReactNode;
|
|
7
|
+
/** 生成静态品牌的纯文本行,供内联品牌和备用屏时间线共用。 */
|
|
8
|
+
export declare function createAutopilotStaticWordRows(): string[];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { AUTOPILOT_BRAND_WORD, AUTOPILOT_LOGO_HEIGHT, AUTOPILOT_WORD_WIDTH } from "../constants.js";
|
|
4
|
+
/** Autopilot 品牌像素字体;每个字母 5×7,由 0/1 表示亮灭。 */
|
|
5
|
+
export const AUTOPILOT_PIXEL_FONT = {
|
|
6
|
+
A: ["01110", "10001", "10001", "11111", "10001", "10001", "10001"],
|
|
7
|
+
I: ["11111", "00100", "00100", "00100", "00100", "00100", "11111"],
|
|
8
|
+
L: ["10000", "10000", "10000", "10000", "10000", "10000", "11111"],
|
|
9
|
+
O: ["01110", "10001", "10001", "10001", "10001", "10001", "01110"],
|
|
10
|
+
P: ["11110", "10001", "10001", "11110", "10000", "10000", "10000"],
|
|
11
|
+
T: ["11111", "00100", "00100", "00100", "00100", "00100", "00100"],
|
|
12
|
+
U: ["10001", "10001", "10001", "10001", "10001", "10001", "01110"],
|
|
13
|
+
};
|
|
14
|
+
/** 根据字符来源把品牌动画拆成 Copilot-inspired 的蓝、紫、绿三组 token。 */
|
|
15
|
+
export function renderAutopilotColoredRows(rows, colorForCell, maximumWidth) {
|
|
16
|
+
const width = maximumWidth === undefined ? undefined : Math.max(1, Math.floor(maximumWidth));
|
|
17
|
+
return (_jsx(Box, { flexDirection: "column", width: width, overflow: width === undefined ? undefined : "hidden", children: rows.map(function renderAutopilotRow(row, rowIndex) {
|
|
18
|
+
const visibleRow = width === undefined ? row : row.slice(0, width);
|
|
19
|
+
const segments = [];
|
|
20
|
+
for (let column = 0; column < visibleRow.length; column += 1) {
|
|
21
|
+
const char = visibleRow[column] ?? " ";
|
|
22
|
+
const color = colorForCell(char, column, rowIndex);
|
|
23
|
+
const previous = segments[segments.length - 1];
|
|
24
|
+
if (previous && previous.color === color)
|
|
25
|
+
previous.text += char;
|
|
26
|
+
else
|
|
27
|
+
segments.push({ text: char, color });
|
|
28
|
+
}
|
|
29
|
+
return (_jsx(Text, { wrap: width === undefined ? undefined : "truncate-end", children: segments.map(function renderAutopilotSegment(segment, segmentIndex) {
|
|
30
|
+
return _jsx(Text, { color: segment.color, children: segment.text }, String(rowIndex) + ":" + String(segmentIndex));
|
|
31
|
+
}) }, String(rowIndex)));
|
|
32
|
+
}) }));
|
|
33
|
+
}
|
|
34
|
+
/** 生成静态品牌的纯文本行,供内联品牌和备用屏时间线共用。 */
|
|
35
|
+
export function createAutopilotStaticWordRows() {
|
|
36
|
+
const rows = Array.from({ length: AUTOPILOT_LOGO_HEIGHT }, function createWordRow() {
|
|
37
|
+
return Array.from({ length: AUTOPILOT_WORD_WIDTH }, function createWordCell() { return " "; });
|
|
38
|
+
});
|
|
39
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
|
|
40
|
+
for (let letterIndex = 0; letterIndex < AUTOPILOT_BRAND_WORD.length; letterIndex += 1) {
|
|
41
|
+
const glyph = AUTOPILOT_PIXEL_FONT[AUTOPILOT_BRAND_WORD[letterIndex] ?? ""];
|
|
42
|
+
const glyphRow = glyph?.[rowIndex] ?? "00000";
|
|
43
|
+
for (let pixelIndex = 0; pixelIndex < glyphRow.length; pixelIndex += 1) {
|
|
44
|
+
if (glyphRow[pixelIndex] === "1")
|
|
45
|
+
rows[rowIndex][letterIndex * 6 + pixelIndex] = "█";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return rows.map(function joinWordRow(row) { return row.join(""); });
|
|
50
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI 的非颜色常量:Autopilot 品牌像素几何、终端控制序列、输入相关常量与
|
|
3
|
+
* 代理展示名。颜色语义 token 集中在 design-tokens.ts,这里不重复定义。
|
|
4
|
+
*/
|
|
5
|
+
/** Autopilot 品牌版本号。 */
|
|
6
|
+
export declare const AUTOPILOT_VERSION = "0.1.0";
|
|
7
|
+
/** 品牌像素字母的完整单词。 */
|
|
8
|
+
export declare const AUTOPILOT_BRAND_WORD = "AUTOPILOT";
|
|
9
|
+
/** 开场动画总时长(毫秒)。 */
|
|
10
|
+
export declare const AUTOPILOT_INTRO_DURATION_MS = 4500;
|
|
11
|
+
/** 品牌 logo 的像素宽度。 */
|
|
12
|
+
export declare const AUTOPILOT_LOGO_WIDTH = 9;
|
|
13
|
+
/** 品牌 logo 的像素高度。 */
|
|
14
|
+
export declare const AUTOPILOT_LOGO_HEIGHT = 7;
|
|
15
|
+
/** 品牌单词相对行首的起始列。 */
|
|
16
|
+
export declare const AUTOPILOT_WORD_START = 2;
|
|
17
|
+
/** 品牌单词与 logo 之间的像素间距。 */
|
|
18
|
+
export declare const AUTOPILOT_BRAND_GAP = 3;
|
|
19
|
+
/** 品牌像素单词的总显示宽度(每字母 6 列,减最后 1 列)。 */
|
|
20
|
+
export declare const AUTOPILOT_WORD_WIDTH: number;
|
|
21
|
+
/** logo 最终落点:单词结束后再留品牌间距。 */
|
|
22
|
+
export declare const AUTOPILOT_LOGO_FINAL_X: number;
|
|
23
|
+
/** 开场动画的总宽度。 */
|
|
24
|
+
export declare const AUTOPILOT_INTRO_WIDTH: number;
|
|
25
|
+
/** 代理在 TUI 中的展示名。 */
|
|
26
|
+
export declare const TUI_AGENT_LABEL = "Autopilot";
|
|
27
|
+
/** Node 20+ 的 Unicode grapheme 分词器,保证一个组合 emoji 只占一个输入光标位置。 */
|
|
28
|
+
export declare const TUI_INPUT_SEGMENTER: Intl.Segmenter;
|
|
29
|
+
/** prompt 前缀字符。 */
|
|
30
|
+
export declare const TUI_PROMPT_PREFIX = "\u203A ";
|
|
31
|
+
/** 输入光标的可视字符。 */
|
|
32
|
+
export declare const TUI_CURSOR_CHARACTER = "\u258F";
|
|
33
|
+
/** 输入光标的终端显示宽度。 */
|
|
34
|
+
export declare const TUI_CURSOR_WIDTH: number;
|
|
35
|
+
/** 启用 bracketed paste 的控制序列。 */
|
|
36
|
+
export declare const TUI_BRACKETED_PASTE_ENABLE = "\u001B[?2004h";
|
|
37
|
+
/** 关闭 bracketed paste 的控制序列。 */
|
|
38
|
+
export declare const TUI_BRACKETED_PASTE_DISABLE = "\u001B[?2004l";
|
|
39
|
+
/** 启用终端 focus 报告的控制序列。 */
|
|
40
|
+
export declare const TUI_TERMINAL_FOCUS_ENABLE = "\u001B[?1004h";
|
|
41
|
+
/** 关闭终端 focus 报告的控制序列。 */
|
|
42
|
+
export declare const TUI_TERMINAL_FOCUS_DISABLE = "\u001B[?1004l";
|
|
43
|
+
/** 进入备用屏并清屏的控制序列。 */
|
|
44
|
+
export declare const TUI_ALTERNATE_SCREEN_ENABLE = "\u001B[?1049h\u001B[2J\u001B[H";
|
|
45
|
+
/** 退出备用屏的控制序列。 */
|
|
46
|
+
export declare const TUI_ALTERNATE_SCREEN_DISABLE = "\u001B[?1049l";
|
|
47
|
+
/** inline TUI 缩窄时清除当前视口和已保存的 scrollback,避免旧帧可被向上滚动看到。 */
|
|
48
|
+
export declare const TUI_INLINE_RESIZE_CLEAR = "\u001B[2J\u001B[3J\u001B[H";
|
|
49
|
+
/** 启用鼠标追踪(含 SGR 扩展模式)的控制序列。 */
|
|
50
|
+
export declare const TUI_MOUSE_TRACKING_ENABLE = "\u001B[?1000h\u001B[?1002h\u001B[?1003h\u001B[?1006h";
|
|
51
|
+
/** 关闭鼠标追踪的控制序列。 */
|
|
52
|
+
export declare const TUI_MOUSE_TRACKING_DISABLE = "\u001B[?1006l\u001B[?1003l\u001B[?1002l\u001B[?1000l";
|
|
53
|
+
/** Store 批量发布 answer delta 的间隔(毫秒)。 */
|
|
54
|
+
export declare const TUI_EVENT_PUBLISH_INTERVAL_MS = 32;
|
|
55
|
+
/** OSC 52 剪贴板的最大字节数。 */
|
|
56
|
+
export declare const TUI_OSC52_MAX_BYTES = 100000;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI 的非颜色常量:Autopilot 品牌像素几何、终端控制序列、输入相关常量与
|
|
3
|
+
* 代理展示名。颜色语义 token 集中在 design-tokens.ts,这里不重复定义。
|
|
4
|
+
*/
|
|
5
|
+
import stringWidth from "string-width";
|
|
6
|
+
/** Autopilot 品牌版本号。 */
|
|
7
|
+
export const AUTOPILOT_VERSION = "0.1.0";
|
|
8
|
+
/** 品牌像素字母的完整单词。 */
|
|
9
|
+
export const AUTOPILOT_BRAND_WORD = "AUTOPILOT";
|
|
10
|
+
/** 开场动画总时长(毫秒)。 */
|
|
11
|
+
export const AUTOPILOT_INTRO_DURATION_MS = 4_500;
|
|
12
|
+
/** 品牌 logo 的像素宽度。 */
|
|
13
|
+
export const AUTOPILOT_LOGO_WIDTH = 9;
|
|
14
|
+
/** 品牌 logo 的像素高度。 */
|
|
15
|
+
export const AUTOPILOT_LOGO_HEIGHT = 7;
|
|
16
|
+
/** 品牌单词相对行首的起始列。 */
|
|
17
|
+
export const AUTOPILOT_WORD_START = 2;
|
|
18
|
+
/** 品牌单词与 logo 之间的像素间距。 */
|
|
19
|
+
export const AUTOPILOT_BRAND_GAP = 3;
|
|
20
|
+
/** 品牌像素单词的总显示宽度(每字母 6 列,减最后 1 列)。 */
|
|
21
|
+
export const AUTOPILOT_WORD_WIDTH = AUTOPILOT_BRAND_WORD.length * 6 - 1;
|
|
22
|
+
/** logo 最终落点:单词结束后再留品牌间距。 */
|
|
23
|
+
export const AUTOPILOT_LOGO_FINAL_X = AUTOPILOT_WORD_START + AUTOPILOT_WORD_WIDTH + AUTOPILOT_BRAND_GAP;
|
|
24
|
+
/** 开场动画的总宽度。 */
|
|
25
|
+
export const AUTOPILOT_INTRO_WIDTH = AUTOPILOT_LOGO_FINAL_X + AUTOPILOT_LOGO_WIDTH;
|
|
26
|
+
/** 代理在 TUI 中的展示名。 */
|
|
27
|
+
export const TUI_AGENT_LABEL = "Autopilot";
|
|
28
|
+
/** Node 20+ 的 Unicode grapheme 分词器,保证一个组合 emoji 只占一个输入光标位置。 */
|
|
29
|
+
export const TUI_INPUT_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
30
|
+
/** prompt 前缀字符。 */
|
|
31
|
+
export const TUI_PROMPT_PREFIX = "› ";
|
|
32
|
+
/** 输入光标的可视字符。 */
|
|
33
|
+
export const TUI_CURSOR_CHARACTER = "▏";
|
|
34
|
+
/** 输入光标的终端显示宽度。 */
|
|
35
|
+
export const TUI_CURSOR_WIDTH = stringWidth(TUI_CURSOR_CHARACTER);
|
|
36
|
+
/** 启用 bracketed paste 的控制序列。 */
|
|
37
|
+
export const TUI_BRACKETED_PASTE_ENABLE = "\u001b[?2004h";
|
|
38
|
+
/** 关闭 bracketed paste 的控制序列。 */
|
|
39
|
+
export const TUI_BRACKETED_PASTE_DISABLE = "\u001b[?2004l";
|
|
40
|
+
/** 启用终端 focus 报告的控制序列。 */
|
|
41
|
+
export const TUI_TERMINAL_FOCUS_ENABLE = "\u001b[?1004h";
|
|
42
|
+
/** 关闭终端 focus 报告的控制序列。 */
|
|
43
|
+
export const TUI_TERMINAL_FOCUS_DISABLE = "\u001b[?1004l";
|
|
44
|
+
/** 进入备用屏并清屏的控制序列。 */
|
|
45
|
+
export const TUI_ALTERNATE_SCREEN_ENABLE = "\u001b[?1049h\u001b[2J\u001b[H";
|
|
46
|
+
/** 退出备用屏的控制序列。 */
|
|
47
|
+
export const TUI_ALTERNATE_SCREEN_DISABLE = "\u001b[?1049l";
|
|
48
|
+
/** inline TUI 缩窄时清除当前视口和已保存的 scrollback,避免旧帧可被向上滚动看到。 */
|
|
49
|
+
export const TUI_INLINE_RESIZE_CLEAR = "\u001b[2J\u001b[3J\u001b[H";
|
|
50
|
+
/** 启用鼠标追踪(含 SGR 扩展模式)的控制序列。 */
|
|
51
|
+
export const TUI_MOUSE_TRACKING_ENABLE = "\u001b[?1000h\u001b[?1002h\u001b[?1003h\u001b[?1006h";
|
|
52
|
+
/** 关闭鼠标追踪的控制序列。 */
|
|
53
|
+
export const TUI_MOUSE_TRACKING_DISABLE = "\u001b[?1006l\u001b[?1003l\u001b[?1002l\u001b[?1000l";
|
|
54
|
+
/** Store 批量发布 answer delta 的间隔(毫秒)。 */
|
|
55
|
+
export const TUI_EVENT_PUBLISH_INTERVAL_MS = 32;
|
|
56
|
+
/** OSC 52 剪贴板的最大字节数。 */
|
|
57
|
+
export const TUI_OSC52_MAX_BYTES = 100_000;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** /context 的覆盖视图:渲染 Context Usage 安全报告,并提供底部灰色 Esc 退出提示。 */
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import type { ContextUsageView } from "@autopilot-agent/core";
|
|
4
|
+
/** 渲染当前 Context 报告覆盖视图;按键由 app.tsx 统一分发,避免创建第二套输入器。 */
|
|
5
|
+
export declare function TuiContextReport(props: {
|
|
6
|
+
usage: ContextUsageView | null;
|
|
7
|
+
expanded: boolean;
|
|
8
|
+
columns: number;
|
|
9
|
+
useColor: boolean;
|
|
10
|
+
screenReader: boolean;
|
|
11
|
+
}): ReactNode;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import { formatContextReportLines } from "../context-report.js";
|
|
4
|
+
/** 渲染当前 Context 报告覆盖视图;按键由 app.tsx 统一分发,避免创建第二套输入器。 */
|
|
5
|
+
export function TuiContextReport(props) {
|
|
6
|
+
// screen reader / NO_COLOR 跳过装饰网格但保留完整文本;与原 transcript 视图共用同一格式化函数。
|
|
7
|
+
const decorative = props.useColor && !(props.screenReader ?? false);
|
|
8
|
+
const lines = formatContextReportLines(props.usage, props.expanded, { grid: decorative, columns: props.columns });
|
|
9
|
+
return _jsxs(Box, { flexDirection: "column", width: props.columns, children: [lines.map(function renderReportLine(line, index) {
|
|
10
|
+
return _jsx(Text, { children: line }, String(index));
|
|
11
|
+
}), _jsxs(Text, { dimColor: props.useColor, children: ["Esc \u8FD4\u56DE\u804A\u5929", props.screenReader ? " · context report" : ""] })] });
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Text } from "ink";
|
|
3
|
+
import { TUI_DIFF_ADDED_COLOR, TUI_DIFF_REMOVED_COLOR } from "../design-tokens.js";
|
|
4
|
+
/** 渲染带有文本标记的新增/删除统计;NO_COLOR 下仍保留完整正负号。 */
|
|
5
|
+
export function TuiDiffStats(props) {
|
|
6
|
+
return _jsxs(Text, { children: ["(", _jsxs(Text, { color: props.useColor ? TUI_DIFF_ADDED_COLOR : undefined, children: ["+", props.added] }), ", ", _jsxs(Text, { color: props.useColor ? TUI_DIFF_REMOVED_COLOR : undefined, children: ["-", props.removed] }), ")"] });
|
|
7
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI 纯函数格式化工具:状态/工具/审批/权限标签、耗时与进度、工具行文案、
|
|
3
|
+
* 已消费输入文案和终端尺寸读取。本文件不依赖任何 React 组件。
|
|
4
|
+
*/
|
|
5
|
+
import type { CliAction, CliPublicEvent, CliStatusView, CliTurnStatus } from "../types.js";
|
|
6
|
+
import type { CliTuiTimelineEvent } from "./types.js";
|
|
7
|
+
/** 判断一个状态是否已经离开可继续执行的活动阶段;waiting_user_input 是可恢复等待,不算终态。 */
|
|
8
|
+
export declare function isTerminalStatus(status: CliTurnStatus): boolean;
|
|
9
|
+
/** 判断 Turn 是否仍可由 Ctrl+C 或 Esc 请求 Runtime 取消。 */
|
|
10
|
+
export declare function isActiveTuiStatus(status: CliTurnStatus): boolean;
|
|
11
|
+
/** 将内部生命周期状态转换为用户视角的短状态。 */
|
|
12
|
+
export declare function getTuiStatusLabel(status: CliTurnStatus): string;
|
|
13
|
+
/** 从 CLI 安全事件中读取失败终态原因,不访问 Runtime 原始错误。 */
|
|
14
|
+
export declare function getTuiTurnFailureReason(events: CliTuiTimelineEvent[]): string | undefined;
|
|
15
|
+
/** 从 CLI 安全状态事件读取终态原因、恢复性与预算摘要;只解释最新生命周期状态,当前状态非终态时不复活旧 pause 的 termination。 */
|
|
16
|
+
export declare function getTuiTerminationSummary(events: CliTuiTimelineEvent[], currentStatus?: CliTurnStatus): string | undefined;
|
|
17
|
+
/** 将内部工具名映射为面向用户的动作名称。 */
|
|
18
|
+
export declare function getTuiToolLabel(toolName: string): string;
|
|
19
|
+
/** 清理工具摘要中的内部名称与重复前缀。 */
|
|
20
|
+
export declare function cleanTuiToolSummary(toolName: string, summary: string): string;
|
|
21
|
+
/** 将 0..1 的动画进度限制在可渲染范围。 */
|
|
22
|
+
export declare function clampTuiProgress(value: number): number;
|
|
23
|
+
/** 将耗时格式化为稳定的 h/m/s,避免长任务只显示一个不断增长的秒数。 */
|
|
24
|
+
export declare function formatTuiDuration(durationMs: number): string;
|
|
25
|
+
/** 工具结果行的分段:glyph 是状态文字标签,badge 是 [动作名] 徽标文本,rest 是徽标后的详情或失败原因。 */
|
|
26
|
+
export interface TuiToolLineParts {
|
|
27
|
+
glyph?: string;
|
|
28
|
+
badge: string;
|
|
29
|
+
rest: string;
|
|
30
|
+
}
|
|
31
|
+
/** 将工具执行结果拆分为 glyph/徽标/详情三段;彩色 chip 渲染与纯文本出口共用同一份布局事实。 */
|
|
32
|
+
export declare function getTuiToolLineParts(toolName: string, summary: string, status?: Extract<CliPublicEvent, {
|
|
33
|
+
type: "tool_result";
|
|
34
|
+
}>["status"], errorCode?: string): TuiToolLineParts;
|
|
35
|
+
/** 将工具执行结果映射为稳定、简短的用户视角结果行;徽标固定为 [动作名],NO_COLOR 下即退化形态。 */
|
|
36
|
+
export declare function formatTuiToolLine(toolName: string, summary: string, status?: Extract<CliPublicEvent, {
|
|
37
|
+
type: "tool_result";
|
|
38
|
+
}>["status"], errorCode?: string): string;
|
|
39
|
+
/** 读取 edit diff 的新增/删除行数,兼容旧事件只携带文本的情况。 */
|
|
40
|
+
export declare function getTuiDiffStats(diff?: {
|
|
41
|
+
text?: string;
|
|
42
|
+
added?: number;
|
|
43
|
+
removed?: number;
|
|
44
|
+
}): {
|
|
45
|
+
added: number;
|
|
46
|
+
removed: number;
|
|
47
|
+
} | undefined;
|
|
48
|
+
/** 将 edit diff 统计格式化为 NO_COLOR 与纯文本出口共用的稳定文本。 */
|
|
49
|
+
export declare function formatTuiDiffStats(diff?: {
|
|
50
|
+
text?: string;
|
|
51
|
+
added?: number;
|
|
52
|
+
removed?: number;
|
|
53
|
+
}): string | undefined;
|
|
54
|
+
/** 将内部错误码收敛为用户能理解的结果,不把 Core/工具实现细节直接打印到终端。 */
|
|
55
|
+
export declare function getTuiToolFailureMessage(status: Extract<CliPublicEvent, {
|
|
56
|
+
type: "tool_result";
|
|
57
|
+
}>["status"], errorCode?: string, detail?: string): string;
|
|
58
|
+
/** 将已由 Controller 成功投递的输入统一为可读的插入提示。 */
|
|
59
|
+
export declare function formatTuiInputConsumed(event: Extract<CliTuiTimelineEvent, {
|
|
60
|
+
type: "tui_input_consumed";
|
|
61
|
+
}>): string;
|
|
62
|
+
/** 将 Core 权限模式转换为状态栏可读的中文标签。 */
|
|
63
|
+
export declare function getTuiPermissionModeLabel(mode: CliStatusView["permissionMode"]): string;
|
|
64
|
+
/** 将审批决定映射为稳定中文标签;历史时间线与动态时间线共用同一份映射。 */
|
|
65
|
+
export declare function getTuiApprovalDecisionLabel(decision: Extract<CliAction, {
|
|
66
|
+
type: "approve";
|
|
67
|
+
}>["decision"]): string;
|
|
68
|
+
/** 将终端流尺寸收敛为 TUI 布局使用的列/行数;宽度保留最小 30、最大 120 的既有约束。 */
|
|
69
|
+
export declare function readTuiTerminalSize(stdout: {
|
|
70
|
+
columns?: number;
|
|
71
|
+
rows?: number;
|
|
72
|
+
}): {
|
|
73
|
+
columns: number;
|
|
74
|
+
rows: number;
|
|
75
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI 纯函数格式化工具:状态/工具/审批/权限标签、耗时与进度、工具行文案、
|
|
3
|
+
* 已消费输入文案和终端尺寸读取。本文件不依赖任何 React 组件。
|
|
4
|
+
*/
|
|
5
|
+
import { formatAgentTermination } from "../termination-format.js";
|
|
6
|
+
/** 判断一个状态是否已经离开可继续执行的活动阶段;waiting_user_input 是可恢复等待,不算终态。 */
|
|
7
|
+
export function isTerminalStatus(status) {
|
|
8
|
+
return status !== "idle" && status !== "running" && status !== "waiting_approval" && status !== "waiting_user_input";
|
|
9
|
+
}
|
|
10
|
+
/** 判断 Turn 是否仍可由 Ctrl+C 或 Esc 请求 Runtime 取消。 */
|
|
11
|
+
export function isActiveTuiStatus(status) {
|
|
12
|
+
return status === "running" || status === "waiting_approval";
|
|
13
|
+
}
|
|
14
|
+
/** 将内部生命周期状态转换为用户视角的短状态。 */
|
|
15
|
+
export function getTuiStatusLabel(status) {
|
|
16
|
+
switch (status) {
|
|
17
|
+
case "idle": return "Ready";
|
|
18
|
+
case "running": return "Working";
|
|
19
|
+
case "waiting_approval": return "Waiting for approval";
|
|
20
|
+
case "waiting_user_input": return "Waiting for Web answer";
|
|
21
|
+
case "interrupted": return "Stopped";
|
|
22
|
+
case "interrupted_recoverable": return "Paused — resume available";
|
|
23
|
+
case "completed": return "Completed";
|
|
24
|
+
case "failed": return "Failed";
|
|
25
|
+
case "limit_reached": return "Limit reached — resume available";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** 从 CLI 安全事件中读取失败终态原因,不访问 Runtime 原始错误。 */
|
|
29
|
+
export function getTuiTurnFailureReason(events) {
|
|
30
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
31
|
+
const event = events[index];
|
|
32
|
+
if (event?.type === "status" && event.status === "failed" && event.reason)
|
|
33
|
+
return event.reason;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
/** 只有真实中断或失败终态才允许解释 termination;进行中与等待状态不得从旧事件复活终态。 */
|
|
38
|
+
function isRealTurnEndStatus(status) {
|
|
39
|
+
return status === "interrupted" || status === "interrupted_recoverable" || status === "failed" || status === "limit_reached";
|
|
40
|
+
}
|
|
41
|
+
/** 从 CLI 安全状态事件读取终态原因、恢复性与预算摘要;只解释最新生命周期状态,当前状态非终态时不复活旧 pause 的 termination。 */
|
|
42
|
+
export function getTuiTerminationSummary(events, currentStatus) {
|
|
43
|
+
if (currentStatus !== undefined && !isRealTurnEndStatus(currentStatus))
|
|
44
|
+
return undefined;
|
|
45
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
46
|
+
const event = events[index];
|
|
47
|
+
if (event?.type === "status")
|
|
48
|
+
return event.termination && isRealTurnEndStatus(event.status) ? formatAgentTermination(event.termination) : undefined;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
/** 将内部工具名映射为面向用户的动作名称。 */
|
|
53
|
+
export function getTuiToolLabel(toolName) {
|
|
54
|
+
switch (toolName) {
|
|
55
|
+
case "glob_files": return "查找文件";
|
|
56
|
+
case "grep_files": return "搜索代码";
|
|
57
|
+
case "read_file": return "查看文件";
|
|
58
|
+
case "write_file": return "写入文件";
|
|
59
|
+
case "edit_file": return "修改文件";
|
|
60
|
+
case "run_command": return "运行命令";
|
|
61
|
+
default: return "处理任务";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/** 清理工具摘要中的内部名称与重复前缀。 */
|
|
65
|
+
export function cleanTuiToolSummary(toolName, summary) {
|
|
66
|
+
// run_command 的 started summary 已由 Core 脱敏为 `$ command`,这是树状进度中唯一需要保留的命令细节。
|
|
67
|
+
if (toolName === "run_command")
|
|
68
|
+
return summary;
|
|
69
|
+
const withoutToolName = summary.replace(new RegExp(`^${toolName}:\\s*`, "i"), "");
|
|
70
|
+
if (toolName === "glob_files")
|
|
71
|
+
return withoutToolName.replace(/^Searching files:\s*/i, "");
|
|
72
|
+
if (toolName === "grep_files")
|
|
73
|
+
return withoutToolName.replace(/^Searching code:\s*/i, "");
|
|
74
|
+
if (toolName === "read_file")
|
|
75
|
+
return withoutToolName.replace(/^Reading\s+/i, "");
|
|
76
|
+
return withoutToolName;
|
|
77
|
+
}
|
|
78
|
+
/** 将 0..1 的动画进度限制在可渲染范围。 */
|
|
79
|
+
export function clampTuiProgress(value) {
|
|
80
|
+
return Math.max(0, Math.min(1, value));
|
|
81
|
+
}
|
|
82
|
+
/** 将耗时格式化为稳定的 h/m/s,避免长任务只显示一个不断增长的秒数。 */
|
|
83
|
+
export function formatTuiDuration(durationMs) {
|
|
84
|
+
const totalSeconds = Math.round(Math.max(0, durationMs) / 1000);
|
|
85
|
+
if (totalSeconds === 0)
|
|
86
|
+
return "<1s";
|
|
87
|
+
const hours = Math.floor(totalSeconds / 3_600);
|
|
88
|
+
const minutes = Math.floor((totalSeconds % 3_600) / 60);
|
|
89
|
+
const seconds = totalSeconds % 60;
|
|
90
|
+
if (hours > 0)
|
|
91
|
+
return `${hours}h ${String(minutes).padStart(2, "0")}m ${String(seconds).padStart(2, "0")}s`;
|
|
92
|
+
if (minutes > 0)
|
|
93
|
+
return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
94
|
+
return `${seconds}s`;
|
|
95
|
+
}
|
|
96
|
+
/** 将工具执行结果拆分为 glyph/徽标/详情三段;彩色 chip 渲染与纯文本出口共用同一份布局事实。 */
|
|
97
|
+
export function getTuiToolLineParts(toolName, summary, status, errorCode) {
|
|
98
|
+
const label = getTuiToolLabel(toolName);
|
|
99
|
+
const detail = cleanTuiToolSummary(toolName, summary).trim();
|
|
100
|
+
if (status === undefined)
|
|
101
|
+
return { badge: `[${label}]`, rest: detail ? ` · ${detail}` : "" };
|
|
102
|
+
if (status === "success")
|
|
103
|
+
return { glyph: "✓", badge: `[${label}]`, rest: toolName === "run_command" ? "" : detail ? ` · ${detail}` : "" };
|
|
104
|
+
return { glyph: "✗", badge: `[${label}]`, rest: ` · ${getTuiToolFailureMessage(status, errorCode, detail)}` };
|
|
105
|
+
}
|
|
106
|
+
/** 将工具执行结果映射为稳定、简短的用户视角结果行;徽标固定为 [动作名],NO_COLOR 下即退化形态。 */
|
|
107
|
+
export function formatTuiToolLine(toolName, summary, status, errorCode) {
|
|
108
|
+
const parts = getTuiToolLineParts(toolName, summary, status, errorCode);
|
|
109
|
+
return `${parts.glyph ? `${parts.glyph} ` : ""}${parts.badge}${parts.rest}`;
|
|
110
|
+
}
|
|
111
|
+
/** 读取 edit diff 的新增/删除行数,兼容旧事件只携带文本的情况。 */
|
|
112
|
+
export function getTuiDiffStats(diff) {
|
|
113
|
+
if (!diff)
|
|
114
|
+
return undefined;
|
|
115
|
+
const lines = diff.text?.replace(/\r\n?/g, "\n").split("\n");
|
|
116
|
+
const added = diff.added ?? lines?.filter(function countAdded(line) { return line.startsWith("+") && !line.startsWith("+++"); }).length;
|
|
117
|
+
const removed = diff.removed ?? lines?.filter(function countRemoved(line) { return line.startsWith("-") && !line.startsWith("---"); }).length;
|
|
118
|
+
if (added === undefined && removed === undefined)
|
|
119
|
+
return undefined;
|
|
120
|
+
return { added: added ?? 0, removed: removed ?? 0 };
|
|
121
|
+
}
|
|
122
|
+
/** 将 edit diff 统计格式化为 NO_COLOR 与纯文本出口共用的稳定文本。 */
|
|
123
|
+
export function formatTuiDiffStats(diff) {
|
|
124
|
+
const statistics = getTuiDiffStats(diff);
|
|
125
|
+
return statistics ? `(+${statistics.added}, -${statistics.removed})` : undefined;
|
|
126
|
+
}
|
|
127
|
+
/** 将内部错误码收敛为用户能理解的结果,不把 Core/工具实现细节直接打印到终端。 */
|
|
128
|
+
export function getTuiToolFailureMessage(status, errorCode, detail) {
|
|
129
|
+
if (status === "approval_required")
|
|
130
|
+
return "需要确认";
|
|
131
|
+
if (status === "question_required")
|
|
132
|
+
return "请在支持结构化回答的 Web 中处理";
|
|
133
|
+
if (status === "denied")
|
|
134
|
+
return "已拒绝";
|
|
135
|
+
if (status === "cancelled")
|
|
136
|
+
return "已取消";
|
|
137
|
+
if (status === "timeout")
|
|
138
|
+
return "执行超时";
|
|
139
|
+
if (status === "error" && detail?.startsWith("Exit "))
|
|
140
|
+
return "命令执行失败";
|
|
141
|
+
switch (errorCode) {
|
|
142
|
+
case "FILE_NOT_READ": return "读取文件失败";
|
|
143
|
+
case "FILE_NOT_WRITTEN": return "写入文件失败";
|
|
144
|
+
case "FILE_EDIT_FAILED": return "修改文件失败";
|
|
145
|
+
case "PERMISSION_DENIED": return "权限不足";
|
|
146
|
+
case "TOOL_EXECUTION_FAILED":
|
|
147
|
+
case "COMMAND_FAILED":
|
|
148
|
+
return "工具执行失败";
|
|
149
|
+
default:
|
|
150
|
+
return detail && !/^[A-Z][A-Z0-9_]+$/.test(detail) ? detail : "工具执行失败";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** 将已由 Controller 成功投递的输入统一为可读的插入提示。 */
|
|
154
|
+
export function formatTuiInputConsumed(event) {
|
|
155
|
+
const label = event.delivery === "queue" ? "[排队]" : "[引导]";
|
|
156
|
+
return `${label} you: ${event.content} · 已插入当前 turn`;
|
|
157
|
+
}
|
|
158
|
+
/** 将 Core 权限模式转换为状态栏可读的中文标签。 */
|
|
159
|
+
export function getTuiPermissionModeLabel(mode) {
|
|
160
|
+
if (mode === "auto_approve")
|
|
161
|
+
return "为我审批";
|
|
162
|
+
if (mode === "full_access")
|
|
163
|
+
return "完全访问";
|
|
164
|
+
return "询问";
|
|
165
|
+
}
|
|
166
|
+
/** 将审批决定映射为稳定中文标签;历史时间线与动态时间线共用同一份映射。 */
|
|
167
|
+
export function getTuiApprovalDecisionLabel(decision) {
|
|
168
|
+
return decision === "allow_once" ? "本次" : decision === "allow_session" ? "本次会话" : decision === "allow_workspace" ? "此工作区" : "拒绝";
|
|
169
|
+
}
|
|
170
|
+
/** 将终端流尺寸收敛为 TUI 布局使用的列/行数;宽度保留最小 30、最大 120 的既有约束。 */
|
|
171
|
+
export function readTuiTerminalSize(stdout) {
|
|
172
|
+
return {
|
|
173
|
+
columns: Math.max(30, Math.min(Number(stdout.columns ?? 80) - 2, 120)),
|
|
174
|
+
rows: Math.max(8, Number(stdout.rows ?? 24)),
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TUI @文件提及的索引预热:挂载后调用一次 Core `listWorkspaceFiles`,模块级缓存供整个会话复用。
|
|
3
|
+
* 空查询候选由「工作区一层目录(尾随 /)+ 全部文件」合并稳定排序构成;失败静默降级为空候选。
|
|
4
|
+
*/
|
|
5
|
+
/** useFileMentionIndex 的返回契约;ready 为 false 时 @ 菜单不激活。 */
|
|
6
|
+
export interface FileMentionIndex {
|
|
7
|
+
/** 目录(尾随 /)与文件的稳定排序候选池。 */
|
|
8
|
+
entries: string[];
|
|
9
|
+
/** 索引是否已就绪(成功或失败终态)。 */
|
|
10
|
+
ready: boolean;
|
|
11
|
+
}
|
|
12
|
+
/** 清空模块级 @ 索引缓存;仅供测试在用例间隔离。 */
|
|
13
|
+
export declare function resetFileMentionIndexCache(): void;
|
|
14
|
+
/** 挂载后预热 @ 文件索引;workspaceRoot 缺失或 Core 枚举失败时静默降级为空候选,不影响普通输入。 */
|
|
15
|
+
export declare function useFileMentionIndex(workspaceRoot: string | undefined): FileMentionIndex;
|