@autopilot-agent/cli 0.1.0 → 0.2.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 +27 -155
- package/dist/activity-animation.d.ts +2 -0
- package/dist/activity-animation.js +8 -5
- package/dist/composition-root.js +13 -1
- package/dist/context-report.d.ts +1 -1
- package/dist/context-report.js +2 -3
- package/dist/controller.js +60 -0
- package/dist/design-tokens.d.ts +4 -0
- package/dist/design-tokens.js +4 -0
- package/dist/event-projector.js +4 -2
- package/dist/parser.js +13 -4
- package/dist/renderer.js +16 -2
- package/dist/termination-format.js +1 -0
- package/dist/tui/app.js +11 -8
- package/dist/tui/approval.d.ts +1 -1
- package/dist/tui/approval.js +8 -4
- package/dist/tui/constants.d.ts +1 -1
- package/dist/tui/constants.js +1 -1
- package/dist/tui/format.js +4 -2
- package/dist/tui/store.js +84 -48
- package/dist/tui/timeline/live.js +7 -5
- package/dist/tui/timeline/presentation-timeline.d.ts +2 -1
- package/dist/tui/timeline/presentation-timeline.js +34 -15
- package/dist/tui/timeline/static-timeline.js +16 -6
- package/dist/tui/timeline/turn-items.d.ts +12 -0
- package/dist/tui/timeline/turn-items.js +28 -0
- package/dist/tui/timeline/turn.js +6 -6
- package/dist/tui/transcript.d.ts +1 -1
- package/dist/tui/transcript.js +2 -2
- package/dist/tui/types.d.ts +15 -1
- package/dist/tui/viewport/sources.d.ts +2 -2
- package/dist/tui/viewport/sources.js +72 -65
- package/dist/tui/viewport/viewport.js +5 -1
- package/dist/types.d.ts +13 -0
- package/package.json +4 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/** 渲染当前 Turn;它会随安全事件更新,完成后整体进入历史树。 */
|
|
3
3
|
import { memo } from "react";
|
|
4
4
|
import { Box, Text } from "ink";
|
|
@@ -6,8 +6,8 @@ import { TuiApprovalPrompt } from "../approval.js";
|
|
|
6
6
|
import { formatTuiDuration, getTuiTerminationSummary, getTuiTurnFailureReason } from "../format.js";
|
|
7
7
|
import { TuiLiveTimeline } from "./live.js";
|
|
8
8
|
import { renderTuiPresentationTimeline } from "./presentation-timeline.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { TuiTodoList } from "../todo-list.js";
|
|
10
|
+
import { getTuiTurnTodo } from "./turn-items.js";
|
|
11
11
|
/** 渲染当前 Turn;memo 保证输入光标更新不会触发 live timeline 组件执行。 */
|
|
12
12
|
export const TuiLiveTurn = memo(function TuiLiveTurn(props) {
|
|
13
13
|
if (!props.turnId || props.events.length === 0)
|
|
@@ -15,7 +15,7 @@ export const TuiLiveTurn = memo(function TuiLiveTurn(props) {
|
|
|
15
15
|
const hasPresentation = props.presentation?.items.some(function belongsToTurn(item) { return item.kind === "capability_notice" || ("turnId" in item && item.turnId === props.turnId); }) ?? false;
|
|
16
16
|
const failureReason = getTuiTurnFailureReason(props.events);
|
|
17
17
|
const terminationSummary = getTuiTerminationSummary(props.events, props.status);
|
|
18
|
-
return (_jsxs(Box, { flexDirection: "column", children: [
|
|
19
|
-
? renderTuiPresentationTimeline(props.presentation, props.turnId, props.useColor, props.maximumWidth, { expanded: props.todoExpanded, screenReader: props.screenReader, rowLimit: props.todoRowLimit, showToggleHint: true, toolGroupsExpanded: props.todoExpanded })
|
|
20
|
-
: _jsx(TuiLiveTimeline, { turnId: props.turnId, events: props.events, status: props.status, metrics: props.metrics, useColor: props.useColor, maximumWidth: props.maximumWidth }), terminationSummary ? _jsxs(Text, { children: ["\u7EC8\u6001\uFF1A", terminationSummary] }) : hasPresentation && failureReason ? _jsxs(Text, { children: ["\u539F\u56E0\uFF1A", failureReason] }) : null, props.pendingApproval ? _jsx(TuiApprovalPrompt, { pendingApproval: props.pendingApproval, selectedIndex: props.approvalSelectedIndex, useColor: props.useColor, screenReader: props.screenReader }) : null, props.metrics?.completedAt !== undefined ? _jsxs(Text, { dimColor: props.useColor, children: ["* Cogitated for ", formatTuiDuration(Math.max(0, props.metrics.completedAt - props.metrics.startedAt))] }) : null] }));
|
|
18
|
+
return (_jsxs(Box, { flexDirection: "column", children: [props.presentation && hasPresentation
|
|
19
|
+
? renderTuiPresentationTimeline(props.presentation, props.turnId, props.useColor, props.maximumWidth, { expanded: props.todoExpanded, screenReader: props.screenReader, rowLimit: props.todoRowLimit, showToggleHint: true, toolGroupsExpanded: props.todoExpanded }, props.events)
|
|
20
|
+
: _jsxs(_Fragment, { children: [_jsx(TuiLiveTimeline, { turnId: props.turnId, events: props.events, status: props.status, metrics: props.metrics, useColor: props.useColor, maximumWidth: props.maximumWidth }), getTuiTurnTodo(props.events, props.turnId) ? _jsx(TuiTodoList, { item: getTuiTurnTodo(props.events, props.turnId), expanded: props.todoExpanded || props.screenReader, screenReader: props.screenReader, useColor: props.useColor, columns: props.maximumWidth + 2, rowLimit: props.todoRowLimit, showToggleHint: true }) : null] }), terminationSummary ? _jsxs(Text, { children: ["\u7EC8\u6001\uFF1A", terminationSummary] }) : hasPresentation && failureReason ? _jsxs(Text, { children: ["\u539F\u56E0\uFF1A", failureReason] }) : null, props.pendingApproval ? _jsx(TuiApprovalPrompt, { pendingApproval: props.pendingApproval, selectedIndex: props.approvalSelectedIndex, useColor: props.useColor, screenReader: props.screenReader }) : null, props.metrics?.completedAt !== undefined ? _jsxs(Text, { dimColor: props.useColor, children: ["* Cogitated for ", formatTuiDuration(Math.max(0, props.metrics.completedAt - props.metrics.startedAt))] }) : null] }));
|
|
21
21
|
});
|
package/dist/tui/transcript.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export declare function TuiTranscriptItem(props: {
|
|
|
17
17
|
}): ReactNode;
|
|
18
18
|
/** 历史节点保持引用稳定;回答流动时只重新计算当前 Turn。 */
|
|
19
19
|
export declare const MemoizedTuiTranscriptItem: import("react").MemoExoticComponent<typeof TuiTranscriptItem>;
|
|
20
|
-
/**
|
|
20
|
+
/** queue 在等待区显示原文;steer 正文已按提交位置留在所属轮。 */
|
|
21
21
|
export declare function TuiPendingInputItem(props: {
|
|
22
22
|
item: CliTuiPendingInput;
|
|
23
23
|
useColor: boolean;
|
package/dist/tui/transcript.js
CHANGED
|
@@ -27,8 +27,8 @@ export function TuiTranscriptItem(props) {
|
|
|
27
27
|
}
|
|
28
28
|
/** 历史节点保持引用稳定;回答流动时只重新计算当前 Turn。 */
|
|
29
29
|
export const MemoizedTuiTranscriptItem = memo(TuiTranscriptItem);
|
|
30
|
-
/**
|
|
30
|
+
/** queue 在等待区显示原文;steer 正文已按提交位置留在所属轮。 */
|
|
31
31
|
export function TuiPendingInputItem(props) {
|
|
32
32
|
const label = props.item.delivery === "queue" ? "[排队]" : "[引导]";
|
|
33
|
-
return (
|
|
33
|
+
return (_jsxs(Box, { flexDirection: "column", backgroundColor: props.useColor ? TUI_WAITING_BACKGROUND_COLOR : undefined, children: [props.item.delivery === "queue" ? _jsx(Text, { children: props.item.content }) : null, _jsxs(Text, { dimColor: props.useColor, children: ["\u21B3 ", label, " \u7B49\u5F85\u5F53\u524D\u6B65\u9AA4\u7ED3\u675F"] })] }, props.item.key));
|
|
34
34
|
}
|
package/dist/tui/types.d.ts
CHANGED
|
@@ -35,6 +35,9 @@ export type CliTuiTimelineEvent = CliPublicEvent | {
|
|
|
35
35
|
inputId: string;
|
|
36
36
|
content: string;
|
|
37
37
|
delivery: "steer" | "queue";
|
|
38
|
+
consumed?: boolean;
|
|
39
|
+
cancelled?: boolean;
|
|
40
|
+
position?: CliTuiInputPosition;
|
|
38
41
|
} | {
|
|
39
42
|
type: "tui_approval_submitted";
|
|
40
43
|
seq: number;
|
|
@@ -51,6 +54,16 @@ export interface CliTuiPendingInput {
|
|
|
51
54
|
inputId: string;
|
|
52
55
|
delivery: "steer" | "queue";
|
|
53
56
|
}
|
|
57
|
+
/** 提交瞬间的本地位置;只引用安全展示节点,不保存附件展开或私有消息。 */
|
|
58
|
+
export interface CliTuiInputPosition {
|
|
59
|
+
turnId?: string;
|
|
60
|
+
eventCount: number;
|
|
61
|
+
order?: number;
|
|
62
|
+
items: Array<{
|
|
63
|
+
id: string;
|
|
64
|
+
answerLength?: number;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
54
67
|
/** Turn 的本地展示计时;只用于 TUI 文案,不进入 Runtime 或持久化事件。 */
|
|
55
68
|
export interface CliTuiTurnMetrics {
|
|
56
69
|
startedAt: number;
|
|
@@ -92,10 +105,11 @@ export interface CliTuiStore {
|
|
|
92
105
|
getSnapshot(): CliTuiSnapshot;
|
|
93
106
|
subscribe(listener: () => void): () => void;
|
|
94
107
|
recordPrompt(content: string, displayAsActive?: boolean): string | undefined;
|
|
108
|
+
captureInputPosition(): CliTuiInputPosition;
|
|
95
109
|
addPendingPrompt(content: string, input: {
|
|
96
110
|
inputId: string;
|
|
97
111
|
delivery: "steer" | "queue";
|
|
98
|
-
}): void;
|
|
112
|
+
}, position?: CliTuiInputPosition): void;
|
|
99
113
|
markQueuedInputStarted(input: CliQueuedInputStarted): void;
|
|
100
114
|
recordApprovalSubmission(decision: Extract<CliAction, {
|
|
101
115
|
type: "approve";
|
|
@@ -7,7 +7,7 @@ export declare function formatTuiViewportMarkdown(content: string, maximumWidth:
|
|
|
7
7
|
/** 将历史版本的 unified diff 直角卡片转换为备用屏可裁剪的文字源,并保留边框与逐行语义颜色。 */
|
|
8
8
|
export declare function createTuiViewportDiffSource(itemId: string, diff: string, contentWidth: number): TimelineLineSource;
|
|
9
9
|
/** 将已完成与当前动态时间线统一变为可裁剪的视觉行来源。 */
|
|
10
|
-
export declare function createTuiViewportSources(snapshot: CliTuiSnapshot, statusView: CliStatusView | undefined, approvalSelectedIndex: number, contentWidth: number, toolGroupsExpanded?: boolean, decorativeGrid?: boolean): TimelineLineSource[];
|
|
10
|
+
export declare function createTuiViewportSources(snapshot: CliTuiSnapshot, statusView: CliStatusView | undefined, approvalSelectedIndex: number, contentWidth: number, toolGroupsExpanded?: boolean, decorativeGrid?: boolean, todoRowLimit?: number): TimelineLineSource[];
|
|
11
11
|
/** 备用屏 source 缓存计数,区分历史命中与 live tail 重建。 */
|
|
12
12
|
export interface TuiViewportSourceCacheStats {
|
|
13
13
|
brandBuilds: number;
|
|
@@ -21,7 +21,7 @@ export interface TuiViewportSourceCacheStats {
|
|
|
21
21
|
}
|
|
22
22
|
/** 按 transcript item 与 live tail 增量生成备用屏 source 的内部接口。 */
|
|
23
23
|
export interface TuiViewportSourceCache {
|
|
24
|
-
getSources(snapshot: CliTuiSnapshot, statusView: CliStatusView | undefined, approvalSelectedIndex: number, contentWidth: number, toolGroupsExpanded?: boolean, decorativeGrid?: boolean): TimelineLineSource[];
|
|
24
|
+
getSources(snapshot: CliTuiSnapshot, statusView: CliStatusView | undefined, approvalSelectedIndex: number, contentWidth: number, toolGroupsExpanded?: boolean, decorativeGrid?: boolean, todoRowLimit?: number): TimelineLineSource[];
|
|
25
25
|
getStats(): TuiViewportSourceCacheStats;
|
|
26
26
|
clear(): void;
|
|
27
27
|
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/** 备用屏时间线来源:把安全事件转为可裁剪的纯文本视觉行来源。 */
|
|
2
2
|
import stringWidth from "string-width";
|
|
3
3
|
import wrapAnsi from "wrap-ansi";
|
|
4
|
-
import { WEB_SEARCH_DATA_FLOW_NOTICE, getPresentationCopy,
|
|
4
|
+
import { WEB_SEARCH_DATA_FLOW_NOTICE, getPresentationCopy, getToolPresentationProfile, isSandboxUnavailableErrorCode, selectToolCalls } from "@autopilot-agent/presentation";
|
|
5
5
|
import { createAutopilotStaticWordRows } from "../brand/pixel.js";
|
|
6
6
|
import { AUTOPILOT_VERSION, TUI_AGENT_LABEL } from "../constants.js";
|
|
7
|
-
import { getTuiTodoDisplay, getTuiTodoStatusGlyph } from "../todo-list.js";
|
|
8
7
|
import { cleanTuiToolSummary, formatTuiDiffStats, formatTuiDuration, formatTuiInputConsumed, formatTuiToolLine, getTuiDiffStats, getTuiStatusLabel, getTuiToolLabel, getTuiTurnFailureReason, getTuiTerminationSummary, } from "../format.js";
|
|
9
8
|
import { getTuiMarkdownTableColumnWidths, isLooseMarkdownTable, isMarkdownTableDivider, normalizeMarkdownTableCells, splitMarkdownTableRow, } from "../markdown/table.js";
|
|
9
|
+
import { getTuiTurnItems, getTuiTurnTodo } from "../timeline/turn-items.js";
|
|
10
|
+
import { getTuiTodoDisplay, getTuiTodoStatusLabel } from "../todo-list.js";
|
|
10
11
|
/** 将对用户安全公开的时间线事件转换为备用屏内的纯文本行。 */
|
|
11
12
|
function formatTuiViewportEvent(event) {
|
|
12
13
|
if (event.type === "answer_delta")
|
|
@@ -213,46 +214,26 @@ function createTuiViewportBrandSource(statusView) {
|
|
|
213
214
|
],
|
|
214
215
|
};
|
|
215
216
|
}
|
|
216
|
-
/** 将 Todo 任务链转换为备用屏文本源;备用屏空间充足,始终以展开形态显示最终清单。 */
|
|
217
|
-
function createTuiTodoViewportSource(itemId, item, contentWidth) {
|
|
218
|
-
const display = getTuiTodoDisplay(item, { expanded: true, screenReader: false, columns: contentWidth });
|
|
219
|
-
if (!display)
|
|
220
|
-
return undefined;
|
|
221
|
-
const output = [];
|
|
222
|
-
const spans = [];
|
|
223
|
-
let sourceOffset = 0;
|
|
224
|
-
/** 追加一行 Todo 文本并按状态记录相对样式范围。 */
|
|
225
|
-
function appendTodoLine(line, style) {
|
|
226
|
-
output.push(line);
|
|
227
|
-
if (style && line)
|
|
228
|
-
spans.push({ start: sourceOffset, end: sourceOffset + line.length, style });
|
|
229
|
-
sourceOffset += line.length + 1;
|
|
230
|
-
}
|
|
231
|
-
appendTodoLine(display.header, undefined);
|
|
232
|
-
for (const row of display.rows) {
|
|
233
|
-
if (row.hidden)
|
|
234
|
-
appendTodoLine(`+${row.text}`, "muted");
|
|
235
|
-
else
|
|
236
|
-
appendTodoLine(`${getTuiTodoStatusGlyph(row.status)} ${row.text}`, row.status === "in_progress" ? "accent" : row.status === "completed" ? "muted" : undefined);
|
|
237
|
-
}
|
|
238
|
-
return { itemId, text: output.join("\n"), spans };
|
|
239
|
-
}
|
|
240
217
|
/** 将共享 Presentation selector 转成备用屏文本源,与 inline TUI 复用同一事实和顺序。 */
|
|
241
|
-
function createTuiPresentationViewportSources(presentation, turnId, itemId, contentWidth, toolGroupsExpanded) {
|
|
218
|
+
function createTuiPresentationViewportSources(presentation, turnId, itemId, contentWidth, toolGroupsExpanded, events, live, rowLimit = 10) {
|
|
242
219
|
const sources = [];
|
|
243
220
|
// 构建 callId 索引供展开分组取回组内调用。
|
|
244
221
|
const callIndex = new Map(selectToolCalls(presentation, turnId).map(function indexToolCall(call) { return [call.callId, call]; }));
|
|
245
222
|
let index = 0;
|
|
246
|
-
for (const item of
|
|
223
|
+
for (const item of getTuiTurnItems(presentation, turnId, events)) {
|
|
224
|
+
if ("type" in item) {
|
|
225
|
+
sources.push({ itemId: `${itemId}:input:${item.inputId}`, text: item.content, spans: createTuiViewportTextSpans(item.content, "accent") });
|
|
226
|
+
sources.push({ itemId: `${itemId}:input:${item.inputId}:state`, text: item.cancelled ? "[引导] 已取消" : item.consumed === false ? "[引导] 等待当前步骤结束" : formatTuiInputConsumed(item) });
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
247
229
|
if (item.kind !== "capability_notice" && (!("turnId" in item) || item.turnId !== turnId))
|
|
248
230
|
continue;
|
|
249
231
|
const sourceId = `${itemId}:presentation:${String(index++)}`;
|
|
250
232
|
if (item.kind === "todo") {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
sources.push(todoSource);
|
|
233
|
+
sources.push(...createTuiTodoSources(item, sourceId, contentWidth, live ? toolGroupsExpanded : true, live, rowLimit));
|
|
234
|
+
continue;
|
|
254
235
|
}
|
|
255
|
-
|
|
236
|
+
if (item.kind === "thinking")
|
|
256
237
|
sources.push({ itemId: sourceId, text: `✢ ${item.summary}`, spans: createTuiViewportTextSpans(`✢ ${item.summary}`, "muted") });
|
|
257
238
|
else if (item.kind === "tool_group") {
|
|
258
239
|
const text = `• Exploring codebase · ${item.toolCallIds.length} operations`;
|
|
@@ -279,13 +260,15 @@ function createTuiPresentationViewportSources(presentation, turnId, itemId, cont
|
|
|
279
260
|
const detail = `${[jobProgramLabel ? undefined : item.target, item.resultSummary].filter(Boolean).join(" · ")}${diffStatsText ? ` ${diffStatsText}` : ""}`;
|
|
280
261
|
const detailLine = jobProgramLabel ? `\n └─ ${jobProgramLabel}${detail ? ` · ${detail}` : ""}` : detail ? `\n └─ ${detail}` : "";
|
|
281
262
|
const jobHint = item.job?.action === "start" && (item.job.observedStatus === "starting" || item.job.observedStatus === "running") ? "\n /jobs 查看后台任务" : "";
|
|
282
|
-
const title = getPresentationCopy(item.titleKey);
|
|
283
|
-
|
|
263
|
+
const title = getPresentationCopy(item.state === "failed" ? getToolPresentationProfile(item.toolName).completedTitleKey : item.titleKey);
|
|
264
|
+
// 2.5:沙箱不可用类拒绝是环境阻塞而非用户拒绝,文案必须区分。
|
|
265
|
+
const stateText = item.state === "denied" ? (isSandboxUnavailableErrorCode(item.errorCode) ? "沙箱不可用,命令未启动" : "已拒绝") : item.state === "cancelled" ? "已取消" : item.state === "timeout" ? "已超时" : undefined;
|
|
266
|
+
const badgeText = `[${title}]${stateText ? ` · ${stateText}` : ""}`;
|
|
267
|
+
const text = `${item.state === "failed" ? "×" : "•"} ${badgeText}${detailLine}${item.errorCode ? `\n └─ ${item.errorCode}` : ""}${jobHint}`;
|
|
284
268
|
const programStart = jobProgramLabel ? text.indexOf(jobProgramLabel) : -1;
|
|
285
269
|
const hintStart = jobHint ? text.length - jobHint.trimStart().length : -1;
|
|
286
270
|
const badgeStart = 2;
|
|
287
|
-
const badgeEnd = badgeStart +
|
|
288
|
-
const firstLineEnd = text.indexOf("\n") < 0 ? text.length : text.indexOf("\n");
|
|
271
|
+
const badgeEnd = badgeStart + badgeText.length;
|
|
289
272
|
const spans = createTuiViewportDiffStatsSpans(text, diffStats);
|
|
290
273
|
if (programStart >= 0 && jobProgramLabel)
|
|
291
274
|
spans.push({ start: programStart, end: programStart + jobProgramLabel.length, style: "accent" });
|
|
@@ -297,7 +280,7 @@ function createTuiPresentationViewportSources(presentation, turnId, itemId, cont
|
|
|
297
280
|
spans.push({ start: badgeStart, end: badgeEnd, style: "chip-active" });
|
|
298
281
|
}
|
|
299
282
|
else {
|
|
300
|
-
spans.push({ start:
|
|
283
|
+
spans.push({ start: badgeStart, end: badgeEnd, style: "chip-error" });
|
|
301
284
|
}
|
|
302
285
|
if (hintStart >= 0)
|
|
303
286
|
spans.push({ start: hintStart, end: text.length, style: "muted" });
|
|
@@ -320,7 +303,7 @@ function createTuiPresentationViewportSources(presentation, turnId, itemId, cont
|
|
|
320
303
|
sources.push({ itemId: sourceId, text, spans: createTuiViewportTextSpans(text, "accent") });
|
|
321
304
|
}
|
|
322
305
|
else if (item.kind === "input_notice") {
|
|
323
|
-
const text =
|
|
306
|
+
const text = `${item.delivery === "steer" ? "[引导] " : item.delivery === "queue" ? "[排队] " : ""}Input ${item.state}`;
|
|
324
307
|
sources.push({ itemId: sourceId, text, spans: createTuiViewportTextSpans(text, "muted") });
|
|
325
308
|
}
|
|
326
309
|
else if (item.kind === "context_notice")
|
|
@@ -332,11 +315,19 @@ function createTuiPresentationViewportSources(presentation, turnId, itemId, cont
|
|
|
332
315
|
}
|
|
333
316
|
return sources;
|
|
334
317
|
}
|
|
318
|
+
/** Todo 行复用既有紧凑/展开数据;历史不输出失效快捷键。 */
|
|
319
|
+
function createTuiTodoSources(item, itemId, contentWidth, expanded, live, rowLimit = 10) {
|
|
320
|
+
const display = getTuiTodoDisplay(item, { expanded, screenReader: false, columns: contentWidth + 1, rowLimit });
|
|
321
|
+
if (!display)
|
|
322
|
+
return [];
|
|
323
|
+
const text = [display.header, ...display.rows.map(function row(row) { return row.hidden ? `+${row.text}` : `[${getTuiTodoStatusLabel(row.status)}] ${row.text}`; }), ...(live ? [`Ctrl+O ${expanded ? "收起" : "展开"}`] : [])].join("\n");
|
|
324
|
+
return [{ itemId, text }];
|
|
325
|
+
}
|
|
335
326
|
/** 将一个历史或 live Turn 的安全事件转换为可缓存的备用屏 source 列表。 */
|
|
336
327
|
function createTuiViewportEventSources(itemId, events, contentWidth, timing, toolGroupsExpanded = false) {
|
|
337
328
|
const sources = [];
|
|
338
329
|
if (timing?.presentation && timing.turnId && timing.presentation.items.some(function belongsToTurn(item) { return item.kind === "capability_notice" || ("turnId" in item && item.turnId === timing.turnId); })) {
|
|
339
|
-
sources.push(...createTuiPresentationViewportSources(timing.presentation, timing.turnId, itemId, contentWidth, toolGroupsExpanded));
|
|
330
|
+
sources.push(...createTuiPresentationViewportSources(timing.presentation, timing.turnId, itemId, contentWidth, toolGroupsExpanded, events, timing.status === "running" || timing.status === "waiting_approval", timing.todoRowLimit));
|
|
340
331
|
const failureReason = getTuiTurnFailureReason(events);
|
|
341
332
|
const terminationSummary = getTuiTerminationSummary(events, timing?.status);
|
|
342
333
|
if (terminationSummary) {
|
|
@@ -405,8 +396,15 @@ function createTuiViewportEventSources(itemId, events, contentWidth, timing, too
|
|
|
405
396
|
continue;
|
|
406
397
|
}
|
|
407
398
|
flushAnswer();
|
|
399
|
+
if (event.type === "tui_input_consumed") {
|
|
400
|
+
sources.push({ itemId: `${itemId}:input:${event.inputId}`, text: event.content, spans: createTuiViewportTextSpans(event.content, "accent") });
|
|
401
|
+
sources.push({ itemId: `${itemId}:input:${event.inputId}:state`, text: event.cancelled ? "[引导] 已取消" : event.consumed === false ? "[引导] 等待当前步骤结束" : formatTuiInputConsumed(event) });
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (event.type === "status")
|
|
405
|
+
continue;
|
|
408
406
|
const text = formatTuiViewportEvent(event);
|
|
409
|
-
const style = event.type === "tool_started" || event.type === "reasoning_summary" ? "muted" :
|
|
407
|
+
const style = event.type === "tool_started" || event.type === "reasoning_summary" ? "muted" : undefined;
|
|
410
408
|
if (text) {
|
|
411
409
|
const spans = event.type === "tool_result"
|
|
412
410
|
? createTuiViewportDiffStatsSpans(text, getTuiDiffStats(event.diff ? { text: event.diff } : undefined))
|
|
@@ -422,6 +420,15 @@ function createTuiViewportEventSources(itemId, events, contentWidth, timing, too
|
|
|
422
420
|
}
|
|
423
421
|
}
|
|
424
422
|
flushAnswer();
|
|
423
|
+
const todo = getTuiTurnTodo(events, timing?.turnId ?? events[0]?.turnId ?? "");
|
|
424
|
+
const live = timing?.status === "running" || timing?.status === "waiting_approval";
|
|
425
|
+
if (todo)
|
|
426
|
+
sources.push(...createTuiTodoSources(todo, `${itemId}:todo`, contentWidth, live ? toolGroupsExpanded : true, live, timing?.todoRowLimit));
|
|
427
|
+
const terminationSummary = getTuiTerminationSummary(events, timing?.status);
|
|
428
|
+
if (terminationSummary)
|
|
429
|
+
sources.push({ itemId: `${itemId}:termination`, text: `终态:${terminationSummary}` });
|
|
430
|
+
else if (getTuiTurnFailureReason(events))
|
|
431
|
+
sources.push({ itemId: `${itemId}:failure-reason`, text: `原因:${getTuiTurnFailureReason(events)}` });
|
|
425
432
|
if (timing?.totalDurationMs !== undefined) {
|
|
426
433
|
const text = `* Cogitated for ${formatTuiDuration(timing.totalDurationMs)}`;
|
|
427
434
|
sources.push({ itemId: `${itemId}:total-duration`, text, spans: createTuiViewportTextSpans(text, "muted") });
|
|
@@ -429,7 +436,7 @@ function createTuiViewportEventSources(itemId, events, contentWidth, timing, too
|
|
|
429
436
|
return sources;
|
|
430
437
|
}
|
|
431
438
|
/** 将已完成与当前动态时间线统一变为可裁剪的视觉行来源。 */
|
|
432
|
-
export function createTuiViewportSources(snapshot, statusView, approvalSelectedIndex, contentWidth, toolGroupsExpanded = false, decorativeGrid = false) {
|
|
439
|
+
export function createTuiViewportSources(snapshot, statusView, approvalSelectedIndex, contentWidth, toolGroupsExpanded = false, decorativeGrid = false, todoRowLimit = 10) {
|
|
433
440
|
const sources = [];
|
|
434
441
|
sources.push(createTuiViewportBrandSource(statusView));
|
|
435
442
|
function appendEventGroup(itemId, events, timing, expandGroups = false) {
|
|
@@ -438,7 +445,7 @@ export function createTuiViewportSources(snapshot, statusView, approvalSelectedI
|
|
|
438
445
|
let renderedActivePrompt = false;
|
|
439
446
|
for (const item of snapshot.transcript) {
|
|
440
447
|
if (item.kind === "prompt") {
|
|
441
|
-
sources.push({ itemId: item.key, text: item.content });
|
|
448
|
+
sources.push({ itemId: item.key, text: item.content, spans: createTuiViewportTextSpans(item.content, "accent") });
|
|
442
449
|
if (item.key === snapshot.activePrompt?.key)
|
|
443
450
|
renderedActivePrompt = true;
|
|
444
451
|
}
|
|
@@ -447,17 +454,18 @@ export function createTuiViewportSources(snapshot, statusView, approvalSelectedI
|
|
|
447
454
|
}
|
|
448
455
|
}
|
|
449
456
|
if (snapshot.activePrompt && !renderedActivePrompt)
|
|
450
|
-
sources.push({ itemId: snapshot.activePrompt.key, text: snapshot.activePrompt.content });
|
|
457
|
+
sources.push({ itemId: snapshot.activePrompt.key, text: snapshot.activePrompt.content, spans: createTuiViewportTextSpans(snapshot.activePrompt.content, "accent") });
|
|
451
458
|
const liveCompletedAt = snapshot.liveMetrics?.completedAt;
|
|
452
|
-
appendEventGroup(snapshot.liveTurnId ?? "live", snapshot.liveEvents,
|
|
453
|
-
|
|
459
|
+
appendEventGroup(snapshot.liveTurnId ?? "live", snapshot.liveEvents, {
|
|
460
|
+
todoRowLimit,
|
|
461
|
+
thoughtDurationMs: snapshot.liveMetrics?.firstAnswerAt === undefined ? undefined : Math.max(0, snapshot.liveMetrics?.firstAnswerAt - snapshot.liveMetrics.startedAt),
|
|
454
462
|
totalDurationMs: liveCompletedAt === undefined ? undefined : Math.max(0, liveCompletedAt - snapshot.liveMetrics.startedAt),
|
|
455
463
|
presentation: snapshot.livePresentation,
|
|
456
464
|
turnId: snapshot.liveTurnId,
|
|
457
465
|
status: snapshot.status,
|
|
458
466
|
}, toolGroupsExpanded);
|
|
459
|
-
for (const item of snapshot.pendingInputs)
|
|
460
|
-
sources.push({ itemId: item.key, text: `${item.delivery === "queue" ? "[
|
|
467
|
+
for (const item of snapshot.pendingInputs.filter(function queue(item) { return item.delivery === "queue"; }))
|
|
468
|
+
sources.push({ itemId: item.key, text: `${item.delivery === "queue" ? item.content + "\n[排队]" : "[引导]"} 等待当前步骤结束` });
|
|
461
469
|
return sources;
|
|
462
470
|
}
|
|
463
471
|
/** 创建备用屏 source 增量缓存;历史 item 不变时只复用其 source,live tail 单独更新。 */
|
|
@@ -489,7 +497,7 @@ export function createTuiViewportSourceCache() {
|
|
|
489
497
|
return previous.sources;
|
|
490
498
|
}
|
|
491
499
|
const sources = item.kind === "prompt"
|
|
492
|
-
? [{ itemId: item.key, text: item.content }]
|
|
500
|
+
? [{ itemId: item.key, text: item.content, spans: createTuiViewportTextSpans(item.content, "accent") }]
|
|
493
501
|
: item.kind === "turn"
|
|
494
502
|
? createTuiViewportEventSources(item.key, item.events, contentWidth, item)
|
|
495
503
|
: [];
|
|
@@ -501,7 +509,7 @@ export function createTuiViewportSourceCache() {
|
|
|
501
509
|
function getActivePromptSource(prompt) {
|
|
502
510
|
if (activePrompt && activePrompt.key === prompt.key && activePrompt.content === prompt.content)
|
|
503
511
|
return activePrompt.sources;
|
|
504
|
-
const sources = [{ itemId: prompt.key, text: prompt.content }];
|
|
512
|
+
const sources = [{ itemId: prompt.key, text: prompt.content, spans: createTuiViewportTextSpans(prompt.content, "accent") }];
|
|
505
513
|
activePrompt = { key: prompt.key, content: prompt.content, sources };
|
|
506
514
|
return sources;
|
|
507
515
|
}
|
|
@@ -512,36 +520,35 @@ export function createTuiViewportSourceCache() {
|
|
|
512
520
|
stats.pendingHits += 1;
|
|
513
521
|
return previous.sources;
|
|
514
522
|
}
|
|
515
|
-
const sources = [{ itemId: item.key, text: `${item.delivery === "queue" ? "[
|
|
523
|
+
const sources = [{ itemId: item.key, text: `${item.delivery === "queue" ? item.content + "\n[排队]" : "[引导]"} 等待当前步骤结束` }];
|
|
516
524
|
pending.set(item.key, { key: item.key, content: item.content, delivery: item.delivery, sources });
|
|
517
525
|
stats.pendingBuilds += 1;
|
|
518
526
|
return sources;
|
|
519
527
|
}
|
|
520
528
|
/** 获取当前 live tail source;历史 transcript 不参与本次重建。 */
|
|
521
|
-
function getLiveSources(snapshot, contentWidth, toolGroupsExpanded) {
|
|
529
|
+
function getLiveSources(snapshot, contentWidth, toolGroupsExpanded, todoRowLimit) {
|
|
522
530
|
const liveTurnId = snapshot.liveTurnId;
|
|
523
531
|
const metrics = snapshot.liveMetrics;
|
|
524
|
-
if (live && live.turnId === liveTurnId && live.events === snapshot.liveEvents && live.presentation === snapshot.livePresentation && live.metrics === metrics && live.width === contentWidth && live.toolGroupsExpanded === toolGroupsExpanded) {
|
|
532
|
+
if (live && live.turnId === liveTurnId && live.events === snapshot.liveEvents && live.presentation === snapshot.livePresentation && live.metrics === metrics && live.width === contentWidth && live.toolGroupsExpanded === toolGroupsExpanded && live.todoRowLimit === todoRowLimit) {
|
|
525
533
|
stats.liveHits += 1;
|
|
526
534
|
return live.sources;
|
|
527
535
|
}
|
|
528
536
|
const liveCompletedAt = metrics?.completedAt;
|
|
529
|
-
const timing =
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
: undefined;
|
|
537
|
+
const timing = {
|
|
538
|
+
todoRowLimit,
|
|
539
|
+
thoughtDurationMs: metrics?.firstAnswerAt === undefined ? undefined : Math.max(0, metrics?.firstAnswerAt - metrics.startedAt),
|
|
540
|
+
totalDurationMs: liveCompletedAt === undefined ? undefined : Math.max(0, liveCompletedAt - metrics.startedAt),
|
|
541
|
+
presentation: snapshot.livePresentation,
|
|
542
|
+
turnId: liveTurnId,
|
|
543
|
+
status: snapshot.status,
|
|
544
|
+
};
|
|
538
545
|
const sources = createTuiViewportEventSources(liveTurnId ?? "live", snapshot.liveEvents, contentWidth, timing, toolGroupsExpanded);
|
|
539
|
-
live = { turnId: liveTurnId, events: snapshot.liveEvents, presentation: snapshot.livePresentation, metrics, width: contentWidth, toolGroupsExpanded, sources };
|
|
546
|
+
live = { turnId: liveTurnId, events: snapshot.liveEvents, presentation: snapshot.livePresentation, metrics, width: contentWidth, toolGroupsExpanded, todoRowLimit, sources };
|
|
540
547
|
stats.liveBuilds += 1;
|
|
541
548
|
return sources;
|
|
542
549
|
}
|
|
543
550
|
/** 返回按稳定历史、live tail 和 pending 输入拼接的当前 source 顺序。 */
|
|
544
|
-
function getSources(snapshot, statusView, _approvalSelectedIndex, contentWidth, toolGroupsExpanded = false, decorativeGrid = false) {
|
|
551
|
+
function getSources(snapshot, statusView, _approvalSelectedIndex, contentWidth, toolGroupsExpanded = false, decorativeGrid = false, todoRowLimit = 10) {
|
|
545
552
|
invalidateWidth(contentWidth);
|
|
546
553
|
if (!brand || brand.statusView !== statusView) {
|
|
547
554
|
brand = { statusView, source: createTuiViewportBrandSource(statusView) };
|
|
@@ -561,8 +568,8 @@ export function createTuiViewportSourceCache() {
|
|
|
561
568
|
}
|
|
562
569
|
if (snapshot.activePrompt && !renderedActivePrompt)
|
|
563
570
|
sources.push(...getActivePromptSource(snapshot.activePrompt));
|
|
564
|
-
sources.push(...getLiveSources(snapshot, contentWidth, toolGroupsExpanded));
|
|
565
|
-
for (const item of snapshot.pendingInputs)
|
|
571
|
+
sources.push(...getLiveSources(snapshot, contentWidth, toolGroupsExpanded, todoRowLimit));
|
|
572
|
+
for (const item of snapshot.pendingInputs.filter(function queue(item) { return item.delivery === "queue"; }))
|
|
566
573
|
sources.push(...getPendingSource(item));
|
|
567
574
|
return sources;
|
|
568
575
|
}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
/** 备用屏视口:按应用层视口裁剪时间线并渲染自绘滚动条。 */
|
|
3
3
|
import { memo } from "react";
|
|
4
4
|
import { Box, Text } from "ink";
|
|
5
|
-
import { CLI_TUI_COLORS, TUI_ACCENT_BG_COLOR, TUI_ACCENT_COLOR, TUI_CHIP_TEXT_COLOR, TUI_DIFF_ADDED_COLOR, TUI_DIFF_REMOVED_COLOR, TUI_TOOL_ERROR_COLOR } from "../../design-tokens.js";
|
|
5
|
+
import { CLI_TUI_COLORS, TUI_ACCENT_BG_COLOR, TUI_ACCENT_COLOR, TUI_CHIP_TEXT_COLOR, TUI_DIFF_ADDED_COLOR, TUI_DIFF_REMOVED_COLOR, TUI_TOOL_ERROR_BG_COLOR, TUI_TOOL_ERROR_COLOR, TUI_TOOL_ERROR_TEXT_COLOR } from "../../design-tokens.js";
|
|
6
6
|
import { getTuiVisualLineSelectionRange } from "./selection.js";
|
|
7
7
|
/** 将备用屏时间线的品牌样式映射回 Design.md 规定的 ANSI token。 */
|
|
8
8
|
function getTuiViewportSpanColor(style, useColor) {
|
|
@@ -28,6 +28,8 @@ function getTuiViewportSpanColor(style, useColor) {
|
|
|
28
28
|
return TUI_TOOL_ERROR_COLOR;
|
|
29
29
|
if (style === "chip-active")
|
|
30
30
|
return TUI_CHIP_TEXT_COLOR;
|
|
31
|
+
if (style === "chip-error")
|
|
32
|
+
return TUI_TOOL_ERROR_TEXT_COLOR;
|
|
31
33
|
return undefined;
|
|
32
34
|
}
|
|
33
35
|
/** 取得备用屏 span 的背景与粗体强调;活动工具徽标 chip 使用强调背景 + 深色粗体文字。 */
|
|
@@ -36,6 +38,8 @@ function getTuiViewportSpanEmphasis(style, useColor) {
|
|
|
36
38
|
return {};
|
|
37
39
|
if (style === "chip-active")
|
|
38
40
|
return { backgroundColor: TUI_ACCENT_BG_COLOR, bold: true };
|
|
41
|
+
if (style === "chip-error")
|
|
42
|
+
return { backgroundColor: TUI_TOOL_ERROR_BG_COLOR, bold: true };
|
|
39
43
|
return {};
|
|
40
44
|
}
|
|
41
45
|
/** 在视口的已换行文本中保留来源样式,并与鼠标划选反显组合。 */
|
package/dist/types.d.ts
CHANGED
|
@@ -117,6 +117,15 @@ export type CliAction =
|
|
|
117
117
|
} | {
|
|
118
118
|
type: "set_permission_mode";
|
|
119
119
|
mode: CliPermissionMode;
|
|
120
|
+
}
|
|
121
|
+
/** 查看当前 Session/Workspace 可见的持久授权(Talk-Plan1013 Stage 4)。 */
|
|
122
|
+
| {
|
|
123
|
+
type: "list_permission_grants";
|
|
124
|
+
}
|
|
125
|
+
/** 按 toolName+constraint 精确撤销一条持久授权;constraint 由用户从列表复制,不做模糊匹配。 */
|
|
126
|
+
| {
|
|
127
|
+
type: "revoke_permission_grant";
|
|
128
|
+
constraint: string;
|
|
120
129
|
} | {
|
|
121
130
|
type: "open_model_picker";
|
|
122
131
|
} | {
|
|
@@ -348,6 +357,8 @@ export type CliPublicEvent = {
|
|
|
348
357
|
toolName: string;
|
|
349
358
|
reason: string;
|
|
350
359
|
risk?: string;
|
|
360
|
+
/** 冻结审批约束;Plain/JSONL 与历史行据此区分普通批准与无沙箱批准。 */
|
|
361
|
+
constraint?: string;
|
|
351
362
|
allowedDecisions?: Array<Extract<CliAction, {
|
|
352
363
|
type: "approve";
|
|
353
364
|
}>["decision"]>;
|
|
@@ -436,6 +447,8 @@ export interface CliProjection {
|
|
|
436
447
|
toolName: string;
|
|
437
448
|
reason: string;
|
|
438
449
|
risk?: string;
|
|
450
|
+
/** 冻结审批约束;沙箱降级卡(no-sandbox-exec:v1)据此展示无隔离警告。 */
|
|
451
|
+
constraint?: string;
|
|
439
452
|
allowedDecisions?: Array<Extract<CliAction, {
|
|
440
453
|
type: "approve";
|
|
441
454
|
}>["decision"]>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autopilot-agent/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -41,9 +41,9 @@
|
|
|
41
41
|
"test:watch": "vitest --config vitest.config.ts"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@autopilot-agent/adapters": "0.
|
|
45
|
-
"@autopilot-agent/core": "0.
|
|
46
|
-
"@autopilot-agent/presentation": "0.
|
|
44
|
+
"@autopilot-agent/adapters": "0.2.0",
|
|
45
|
+
"@autopilot-agent/core": "0.2.0",
|
|
46
|
+
"@autopilot-agent/presentation": "0.2.0",
|
|
47
47
|
"ink": "6.8.0",
|
|
48
48
|
"react": "19.2.4",
|
|
49
49
|
"string-width": "^8.1.1",
|