@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
package/dist/tui/approval.js
CHANGED
|
@@ -2,13 +2,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import { TUI_ACCENT_COLOR } from "../design-tokens.js";
|
|
4
4
|
import { getTuiToolLabel } from "./format.js";
|
|
5
|
-
/**
|
|
5
|
+
/** 根据待审批工具收窄菜单;旧记录缺少冻结范围时保持最保守的单次/拒绝。 */
|
|
6
6
|
export function createTuiApprovalOptions(pendingApproval) {
|
|
7
7
|
if (pendingApproval.allowedDecisions) {
|
|
8
|
+
// run_command 的工作区级授权展示为"记住此命令"(精确命令约束,与 Web 审批卡同一文案);
|
|
9
|
+
// todo_write 的单次批准即任务文件集合批准,与 Web 审批卡同一文案。
|
|
8
10
|
const labels = {
|
|
9
|
-
allow_once: "
|
|
11
|
+
allow_once: pendingApproval.toolName === "todo_write"
|
|
12
|
+
? pendingApproval.constraint === "task-ext-files:v1" ? "批准本任务外部文件修改" : "批准本任务文件修改"
|
|
13
|
+
: "允许本次",
|
|
10
14
|
allow_session: "本次会话允许",
|
|
11
|
-
allow_workspace: "此工作区允许",
|
|
15
|
+
allow_workspace: pendingApproval.toolName === "run_command" ? "记住此命令" : "此工作区允许",
|
|
12
16
|
deny: "拒绝",
|
|
13
17
|
};
|
|
14
18
|
return pendingApproval.allowedDecisions.map(function createFrozenOption(decision) { return { decision, label: labels[decision] }; });
|
|
@@ -30,7 +34,7 @@ export function createTuiApprovalOptions(pendingApproval) {
|
|
|
30
34
|
export function TuiApprovalPrompt(props) {
|
|
31
35
|
const options = createTuiApprovalOptions(props.pendingApproval);
|
|
32
36
|
const selectedIndex = Math.max(0, Math.min(props.selectedIndex, options.length - 1));
|
|
33
|
-
return (_jsxs(Box, { borderStyle: "single", borderColor: props.useColor ? TUI_ACCENT_COLOR : undefined, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, color: props.useColor ? TUI_ACCENT_COLOR : undefined, children: "\u9700\u8981\u4F60\u7684\u786E\u8BA4" }), _jsxs(Text, { children: ["\u64CD\u4F5C\uFF1A", props.pendingApproval.display?.operation ?? getTuiToolLabel(props.pendingApproval.toolName)] }), _jsxs(Text, { children: ["\u539F\u56E0\uFF1A", props.pendingApproval.reason] }), props.pendingApproval.display?.resource ? _jsxs(Text, { children: ["\u5F71\u54CD\uFF1A", props.pendingApproval.display.resource] }) : null, options.map(function renderApprovalOption(option, index) {
|
|
37
|
+
return (_jsxs(Box, { borderStyle: "single", borderColor: props.useColor ? TUI_ACCENT_COLOR : undefined, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, color: props.useColor ? TUI_ACCENT_COLOR : undefined, children: "\u9700\u8981\u4F60\u7684\u786E\u8BA4" }), _jsxs(Text, { children: ["\u64CD\u4F5C\uFF1A", props.pendingApproval.display?.operation ?? getTuiToolLabel(props.pendingApproval.toolName)] }), _jsxs(Text, { children: ["\u539F\u56E0\uFF1A", props.pendingApproval.reason] }), props.pendingApproval.display?.resource ? _jsxs(Text, { children: ["\u5F71\u54CD\uFF1A", props.pendingApproval.display.resource] }) : null, props.pendingApproval.display?.cwd ? _jsxs(Text, { children: ["\u5DE5\u4F5C\u76EE\u5F55\uFF1A", props.pendingApproval.display.cwd] }) : null, props.pendingApproval.display?.effect === "external" && props.pendingApproval.display?.hostExecution === true ? (_jsx(Text, { color: props.useColor ? TUI_ACCENT_COLOR : undefined, children: "\u26A0 \u5916\u90E8\u8DEF\u5F84\uFF1A\u547D\u4EE4\u58F0\u660E\u4E86\u5DE5\u4F5C\u533A\u5916\u76EE\u6807\uFF0C\u4F46\u5BBF\u4E3B\u8FDB\u7A0B\u4E0D\u53D7\u8BE5\u6587\u4EF6\u8303\u56F4\u7EA6\u675F\uFF0C\u53EF\u80FD\u540C\u65F6\u5F71\u54CD\u5176\u4ED6\u6587\u4EF6\uFF1B\u6279\u51C6\u4E0D\u6784\u6210\u7CBE\u786E\u8303\u56F4\u4FDD\u8BC1" })) : null, props.pendingApproval.display?.effect === "external" && props.pendingApproval.display?.hostExecution !== true && props.pendingApproval.display?.unknownScope ? (_jsx(Text, { color: props.useColor ? TUI_ACCENT_COLOR : undefined, children: "\u26A0 \u4FEE\u6539\u8303\u56F4\u65E0\u6CD5\u7CBE\u786E\u9650\u5B9A\uFF1A\u8BE5\u547D\u4EE4\u672A\u58F0\u660E\u7CBE\u786E\u5916\u90E8\u76EE\u6807\uFF0C\u8BF7\u5BA1\u67E5\u5B8C\u6574\u547D\u4EE4\u540E\u518D\u51B3\u5B9A" })) : null, props.pendingApproval.display?.effect === "external" && props.pendingApproval.display?.hostExecution !== true && !props.pendingApproval.display?.unknownScope ? (_jsx(Text, { color: props.useColor ? TUI_ACCENT_COLOR : undefined, children: "\u26A0 \u5916\u90E8\u8DEF\u5F84\uFF1A\u76EE\u6807\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u4E4B\u5916\uFF0C\u6388\u6743\u4EC5\u8986\u76D6\u4E0A\u8FF0\u7CBE\u786E\u76EE\u6807" })) : null, props.pendingApproval.display?.hostExecution === true ? (_jsx(Text, { color: props.useColor ? TUI_ACCENT_COLOR : undefined, children: "Windows \u5BBF\u4E3B\u6267\u884C\uFF1A\u672C\u6B21\u547D\u4EE4\u5C06\u5728\u5F53\u524D\u7528\u6237\u6743\u9650\u4E0B\u76F4\u63A5\u8FD0\u884C\uFF0C\u672A\u542F\u7528\u6C99\u7BB1\u9694\u79BB\uFF1B\u6279\u51C6\u4EC5\u5BF9\u672C\u6B21\u8C03\u7528\u6709\u6548\uFF0C\u62D2\u7EDD\u5219\u4E0D\u6267\u884C" })) : null, props.pendingApproval.toolName === "run_command" && options.some(function hasWorkspaceOption(option) { return option.decision === "allow_workspace"; }) ? (_jsx(Text, { dimColor: props.useColor, children: "\u9009\u62E9\u201C\u8BB0\u4F4F\u6B64\u547D\u4EE4\u201D\u4F1A\u8BB0\u4F4F\u672C\u6761\u547D\u4EE4\uFF08\u7A0B\u5E8F\u3001\u53C2\u6570\u4E0E\u5DE5\u4F5C\u76EE\u5F55\u5B8C\u5168\u4E00\u81F4\uFF09\uFF0C\u6B64\u540E\u5728\u672C\u5DE5\u4F5C\u533A\u81EA\u52A8\u653E\u884C\uFF1B\u53C2\u6570\u6216\u76EE\u5F55\u4E0D\u540C\u7684\u547D\u4EE4\u4ECD\u4F1A\u91CD\u65B0\u8BE2\u95EE" })) : null, props.pendingApproval.constraint === "task-ext-files:v1" ? (_jsx(Text, { color: props.useColor ? TUI_ACCENT_COLOR : undefined, children: "\u26A0 \u5916\u90E8\u8DEF\u5F84\uFF1A\u76EE\u6807\u4F4D\u4E8E\u5DE5\u4F5C\u533A\u4E4B\u5916\uFF0C\u4E0E\u5DE5\u4F5C\u533A\u6587\u4EF6\u96C6\u5408\u5206\u5F00\u5355\u72EC\u6279\u51C6\uFF1B\u6388\u6743\u4EC5\u8986\u76D6\u6E05\u5355\u5185\u7CBE\u786E\u6587\u4EF6\uFF0C\u4E0D\u542B\u7236\u76EE\u5F55\u6216\u9012\u5F52\u6743\u9650" })) : null, props.pendingApproval.toolName === "todo_write" && props.pendingApproval.constraint !== "task-ext-files:v1" && options.some(function hasOnceOption(option) { return option.decision === "allow_once"; }) ? (_jsx(Text, { dimColor: props.useColor, children: "\u6279\u51C6\u540E\uFF0C\u672C\u4EFB\u52A1\u5185\u5BF9\u6E05\u5355\u5185\u6587\u4EF6\u7684\u5BF9\u5E94\u4FEE\u6539\u5C06\u81EA\u52A8\u653E\u884C\uFF1B\u6E05\u5355\u5916\u7684\u65B0\u6587\u4EF6\u4ECD\u4F1A\u9010\u6B21\u8BE2\u95EE\uFF1B\u6388\u6743\u4EC5\u8986\u76D6\u5F53\u524D\u4EFB\u52A1" })) : null, props.pendingApproval.constraint === "task-ext-files:v1" && options.some(function hasOnceOption(option) { return option.decision === "allow_once"; }) ? (_jsx(Text, { dimColor: props.useColor, children: "\u6279\u51C6\u540E\uFF0C\u672C\u4EFB\u52A1\u5185\u5BF9\u6E05\u5355\u5185\u7CBE\u786E\u6587\u4EF6\u7684\u5BF9\u5E94\u4FEE\u6539\u5C06\u81EA\u52A8\u653E\u884C\uFF1B\u6E05\u5355\u5916\u76EE\u6807\u4ECD\u9010\u6B21\u8BE2\u95EE\uFF1B\u6388\u6743\u4EC5\u8986\u76D6\u5F53\u524D\u4EFB\u52A1\uFF0C\u65B0\u76EE\u5F55\u6216\u65B0\u6587\u4EF6\u4E0D\u4F1A\u56E0\u6B64\u83B7\u5F97\u6388\u6743" })) : null, props.pendingApproval.constraint === "no-sandbox-exec:v1" ? (_jsx(Text, { color: props.useColor ? "yellow" : undefined, children: "\u6C99\u7BB1\u542F\u52A8\u5931\u8D25\uFF1A\u672C\u6B21\u5C06\u65E0\u6C99\u7BB1\u9694\u79BB\u76F4\u63A5\u6267\u884C\u4E0A\u8FF0\u547D\u4EE4\uFF1B\u6279\u51C6\u4EC5\u5BF9\u672C\u6B21\u8C03\u7528\u6709\u6548\uFF0C\u62D2\u7EDD\u5219\u4EFB\u52A1\u505C\u6B62" })) : null, props.pendingApproval.display?.effect === "external" && props.pendingApproval.toolName !== "run_command" && options.some(function hasWorkspaceOption(option) { return option.decision === "allow_workspace"; }) ? (_jsx(Text, { dimColor: props.useColor, children: "\u9009\u62E9\u201C\u6B64\u5DE5\u4F5C\u533A\u5141\u8BB8\u201D\u5C06\u628A\u8BE5\u89C4\u5219\u5199\u5165\u5DE5\u4F5C\u533A .autopilot/settings.local.json" })) : null, options.map(function renderApprovalOption(option, index) {
|
|
34
38
|
const selected = index === selectedIndex;
|
|
35
39
|
return _jsxs(Text, { inverse: selected && props.useColor, children: [selected ? "›" : " ", " ", option.label, selected && props.screenReader ? "(已选择)" : ""] }, option.decision);
|
|
36
40
|
}), _jsx(Text, { dimColor: props.useColor, children: "\u2191/\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4" })] }));
|
package/dist/tui/constants.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* 代理展示名。颜色语义 token 集中在 design-tokens.ts,这里不重复定义。
|
|
4
4
|
*/
|
|
5
5
|
/** Autopilot 品牌版本号。 */
|
|
6
|
-
export declare const AUTOPILOT_VERSION = "0.
|
|
6
|
+
export declare const AUTOPILOT_VERSION = "0.2.0";
|
|
7
7
|
/** 品牌像素字母的完整单词。 */
|
|
8
8
|
export declare const AUTOPILOT_BRAND_WORD = "AUTOPILOT";
|
|
9
9
|
/** 开场动画总时长(毫秒)。 */
|
package/dist/tui/constants.js
CHANGED
package/dist/tui/format.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 已消费输入文案和终端尺寸读取。本文件不依赖任何 React 组件。
|
|
4
4
|
*/
|
|
5
5
|
import { formatAgentTermination } from "../termination-format.js";
|
|
6
|
+
import { isSandboxUnavailableErrorCode } from "@autopilot-agent/presentation";
|
|
6
7
|
/** 判断一个状态是否已经离开可继续执行的活动阶段;waiting_user_input 是可恢复等待,不算终态。 */
|
|
7
8
|
export function isTerminalStatus(status) {
|
|
8
9
|
return status !== "idle" && status !== "running" && status !== "waiting_approval" && status !== "waiting_user_input";
|
|
@@ -130,8 +131,9 @@ export function getTuiToolFailureMessage(status, errorCode, detail) {
|
|
|
130
131
|
return "需要确认";
|
|
131
132
|
if (status === "question_required")
|
|
132
133
|
return "请在支持结构化回答的 Web 中处理";
|
|
134
|
+
// 2.5:沙箱不可用类拒绝是环境阻塞而非用户拒绝,文案必须区分。
|
|
133
135
|
if (status === "denied")
|
|
134
|
-
return "已拒绝";
|
|
136
|
+
return isSandboxUnavailableErrorCode(errorCode) ? "沙箱不可用,命令未启动" : "已拒绝";
|
|
135
137
|
if (status === "cancelled")
|
|
136
138
|
return "已取消";
|
|
137
139
|
if (status === "timeout")
|
|
@@ -153,7 +155,7 @@ export function getTuiToolFailureMessage(status, errorCode, detail) {
|
|
|
153
155
|
/** 将已由 Controller 成功投递的输入统一为可读的插入提示。 */
|
|
154
156
|
export function formatTuiInputConsumed(event) {
|
|
155
157
|
const label = event.delivery === "queue" ? "[排队]" : "[引导]";
|
|
156
|
-
return `${label}
|
|
158
|
+
return `${label} 已插入当前 turn`;
|
|
157
159
|
}
|
|
158
160
|
/** 将 Core 权限模式转换为状态栏可读的中文标签。 */
|
|
159
161
|
export function getTuiPermissionModeLabel(mode) {
|
package/dist/tui/store.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createInitialCliActivityState, reduceCliActivityState, settleCliActivit
|
|
|
3
3
|
import { TUI_EVENT_PUBLISH_INTERVAL_MS } from "./constants.js";
|
|
4
4
|
import { isTerminalStatus } from "./format.js";
|
|
5
5
|
import { recordTuiPerformanceDiagnostic } from "./performance-diagnostics.js";
|
|
6
|
+
import { captureTuiInputPosition } from "./timeline/turn-items.js";
|
|
6
7
|
/** 创建 TUI 初始快照。 */
|
|
7
8
|
function createInitialTuiSnapshot(now = Date.now()) {
|
|
8
9
|
return {
|
|
@@ -25,6 +26,10 @@ export function createCliTuiStore() {
|
|
|
25
26
|
let promptIndex = 0;
|
|
26
27
|
let scheduledNotify;
|
|
27
28
|
const settledInputIds = new Set();
|
|
29
|
+
const consumedPositions = new Map();
|
|
30
|
+
const cancelledTurns = new Set();
|
|
31
|
+
let submissionOrder = 0;
|
|
32
|
+
const submittedInputIds = new Set();
|
|
28
33
|
const submittedApprovalIds = new Set();
|
|
29
34
|
const listeners = new Set();
|
|
30
35
|
const eventsByTurn = new Map();
|
|
@@ -56,18 +61,40 @@ export function createCliTuiStore() {
|
|
|
56
61
|
listener();
|
|
57
62
|
recordTuiPerformanceDiagnostic("tui_store_published", "snapshot", performance.now() - startedAt);
|
|
58
63
|
}
|
|
59
|
-
/**
|
|
60
|
-
function
|
|
61
|
-
const
|
|
62
|
-
if (!
|
|
64
|
+
/** 更新所属轮的宿主输入事实;晚到回调同时刷新历史快照,不挪到当前另一轮。 */
|
|
65
|
+
function putInput(content, input, position, consumed) {
|
|
66
|
+
const turnId = position.turnId;
|
|
67
|
+
if (!turnId)
|
|
63
68
|
return;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
]
|
|
70
|
-
|
|
69
|
+
const events = [...(eventsByTurn.get(turnId) ?? [])];
|
|
70
|
+
const priorIndex = events.findIndex(function same(event) { return event.type === "tui_input_consumed" && event.inputId === input.inputId; });
|
|
71
|
+
const prior = priorIndex >= 0 ? events[priorIndex] : undefined;
|
|
72
|
+
const event = { type: "tui_input_consumed", seq: 0, turnId, ...input, content, consumed, cancelled: !consumed && cancelledTurns.has(turnId), position: prior?.type === "tui_input_consumed" ? prior.position : position };
|
|
73
|
+
if (priorIndex >= 0)
|
|
74
|
+
events[priorIndex] = event;
|
|
75
|
+
else {
|
|
76
|
+
let publicCount = 0;
|
|
77
|
+
let insertAt = events.length;
|
|
78
|
+
for (let index = 0; index < events.length; index += 1) {
|
|
79
|
+
const current = events[index];
|
|
80
|
+
if (current.type === "tui_input_consumed") {
|
|
81
|
+
if (publicCount >= position.eventCount && (current.position?.order ?? 0) > (position.order ?? 0)) {
|
|
82
|
+
insertAt = index;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
if (publicCount >= position.eventCount) {
|
|
88
|
+
insertAt = index;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
publicCount += 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
events.splice(insertAt, 0, event);
|
|
95
|
+
}
|
|
96
|
+
eventsByTurn.set(turnId, events);
|
|
97
|
+
snapshot = { ...snapshot, liveEvents: snapshot.liveTurnId === turnId ? events : snapshot.liveEvents, transcript: snapshot.transcript.map(function update(item) { return item.kind === "turn" && item.turnId === turnId ? { ...item, events } : item; }) };
|
|
71
98
|
}
|
|
72
99
|
function finalizeTurn(turnId, status) {
|
|
73
100
|
if (finalizedTurns.has(turnId))
|
|
@@ -116,7 +143,7 @@ export function createCliTuiStore() {
|
|
|
116
143
|
const trimmed = content.trim();
|
|
117
144
|
if (!trimmed)
|
|
118
145
|
return undefined;
|
|
119
|
-
// 运行中的 steer/queue
|
|
146
|
+
// 运行中的 steer/queue 由已确认的 inputId 单独记录,避免发送前伪造成功展示。
|
|
120
147
|
if (!displayAsActive)
|
|
121
148
|
return undefined;
|
|
122
149
|
const key = "prompt:" + String(promptIndex++);
|
|
@@ -131,12 +158,20 @@ export function createCliTuiStore() {
|
|
|
131
158
|
});
|
|
132
159
|
return key;
|
|
133
160
|
},
|
|
134
|
-
/**
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
161
|
+
/** 在异步投递之前捕获真实提交位置。 */
|
|
162
|
+
captureInputPosition() { return { ...captureTuiInputPosition(snapshot.liveTurnId, snapshot.liveEvents, snapshot.livePresentation), order: submissionOrder++ }; },
|
|
163
|
+
/** 成功投递后才外显原文,消费与回调的先后次序不改变所属轮。 */
|
|
164
|
+
addPendingPrompt(content, input, position = captureTuiInputPosition(snapshot.liveTurnId, snapshot.liveEvents, snapshot.livePresentation)) {
|
|
165
|
+
if (!content.trim() || submittedInputIds.has(input.inputId))
|
|
138
166
|
return;
|
|
139
|
-
|
|
167
|
+
submittedInputIds.add(input.inputId);
|
|
168
|
+
const consumed = settledInputIds.has(input.inputId);
|
|
169
|
+
const settledPosition = consumedPositions.get(input.inputId);
|
|
170
|
+
const target = input.delivery === "steer" && position.turnId && (!settledPosition || settledPosition.turnId === position.turnId) ? position : settledPosition;
|
|
171
|
+
if (target)
|
|
172
|
+
putInput(content, input, target, consumed);
|
|
173
|
+
if (consumed || (position.turnId && cancelledTurns.has(position.turnId))) {
|
|
174
|
+
publish(snapshot);
|
|
140
175
|
return;
|
|
141
176
|
}
|
|
142
177
|
publish({
|
|
@@ -145,7 +180,7 @@ export function createCliTuiStore() {
|
|
|
145
180
|
...snapshot.pendingInputs,
|
|
146
181
|
{
|
|
147
182
|
key: "pending:" + String(promptIndex++),
|
|
148
|
-
content
|
|
183
|
+
content,
|
|
149
184
|
inputId: input.inputId,
|
|
150
185
|
delivery: input.delivery,
|
|
151
186
|
},
|
|
@@ -157,28 +192,17 @@ export function createCliTuiStore() {
|
|
|
157
192
|
if (settledInputIds.has(input.inputId))
|
|
158
193
|
return;
|
|
159
194
|
settledInputIds.add(input.inputId);
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
...currentEvents,
|
|
167
|
-
{
|
|
168
|
-
type: "tui_input_consumed",
|
|
169
|
-
seq: 0,
|
|
170
|
-
turnId: input.turnId,
|
|
171
|
-
inputId: input.inputId,
|
|
172
|
-
content: input.content,
|
|
173
|
-
delivery: input.delivery,
|
|
174
|
-
},
|
|
175
|
-
];
|
|
176
|
-
eventsByTurn.set(input.turnId, nextEvents);
|
|
195
|
+
const position = { turnId: input.turnId, eventCount: 0, items: [] };
|
|
196
|
+
consumedPositions.set(input.inputId, position);
|
|
197
|
+
const pending = snapshot.pendingInputs.find(function same(item) { return item.inputId === input.inputId; });
|
|
198
|
+
// 领取回调的 content 可能带附件展开,只在原文已经确认时输出正文。
|
|
199
|
+
if (pending)
|
|
200
|
+
putInput(pending.content, input, position, true);
|
|
177
201
|
publish({
|
|
178
202
|
...snapshot,
|
|
179
203
|
pendingInputs: snapshot.pendingInputs.filter(function keepUnstartedInput(item) { return item.inputId !== input.inputId; }),
|
|
180
204
|
liveTurnId: snapshot.liveTurnId === input.turnId ? input.turnId : snapshot.liveTurnId,
|
|
181
|
-
liveEvents: snapshot.
|
|
205
|
+
liveEvents: snapshot.liveEvents,
|
|
182
206
|
});
|
|
183
207
|
},
|
|
184
208
|
/** 用户选择审批范围后立即将等待卡收束为已提交结果;Runtime 后续事件仍是最终执行依据。 */
|
|
@@ -268,18 +292,21 @@ export function createCliTuiStore() {
|
|
|
268
292
|
};
|
|
269
293
|
if (event.type === "input_consumed") {
|
|
270
294
|
settledInputIds.add(event.inputId);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
});
|
|
282
|
-
|
|
295
|
+
if (!consumedPositions.has(event.inputId))
|
|
296
|
+
consumedPositions.set(event.inputId, captureTuiInputPosition(event.turnId, currentEvents, snapshot.livePresentation));
|
|
297
|
+
const pending = nextSnapshot.pendingInputs.find(function same(item) { return item.inputId === event.inputId; });
|
|
298
|
+
nextSnapshot.pendingInputs = nextSnapshot.pendingInputs.filter(function keepPendingInput(item) { return item.inputId !== event.inputId; });
|
|
299
|
+
const host = nextEvents.find(function same(item) { return item.type === "tui_input_consumed" && item.inputId === event.inputId; });
|
|
300
|
+
if (host?.type === "tui_input_consumed")
|
|
301
|
+
nextSnapshot.liveEvents = nextEvents.map(function consume(item) { return item === host ? { ...host, consumed: true } : item; });
|
|
302
|
+
eventsByTurn.set(event.turnId, nextSnapshot.liveEvents);
|
|
303
|
+
if (!host && pending) {
|
|
304
|
+
snapshot = nextSnapshot;
|
|
305
|
+
const position = pending.delivery === "queue" ? { turnId: event.turnId, eventCount: 0, items: [] } : consumedPositions.get(event.inputId);
|
|
306
|
+
consumedPositions.set(event.inputId, position);
|
|
307
|
+
putInput(pending.content, pending, position, true);
|
|
308
|
+
nextSnapshot.liveEvents = snapshot.liveEvents;
|
|
309
|
+
nextSnapshot.transcript = snapshot.transcript;
|
|
283
310
|
}
|
|
284
311
|
}
|
|
285
312
|
snapshot = nextSnapshot;
|
|
@@ -295,6 +322,8 @@ export function createCliTuiStore() {
|
|
|
295
322
|
}
|
|
296
323
|
},
|
|
297
324
|
syncProjection(projection) {
|
|
325
|
+
if (projection.turnId && !eventsByTurn.has(projection.turnId) && projection.events.length > 0)
|
|
326
|
+
eventsByTurn.set(projection.turnId, projection.events.filter(function belongs(event) { return event.turnId === projection.turnId; }));
|
|
298
327
|
const turnIsFinalized = projection.turnId ? finalizedTurns.has(projection.turnId) : false;
|
|
299
328
|
const isNewTurn = projection.turnId !== undefined && projection.turnId !== snapshot.liveTurnId;
|
|
300
329
|
const activePrompt = snapshot.activePrompt && snapshot.activePrompt.turnId === undefined && projection.turnId
|
|
@@ -317,6 +346,7 @@ export function createCliTuiStore() {
|
|
|
317
346
|
liveTurnId: keepLiveTurn ? projection.turnId : undefined,
|
|
318
347
|
liveEvents: keepLiveTurn ? [...(eventsByTurn.get(projection.turnId) ?? [])] : [],
|
|
319
348
|
livePresentation: keepLiveTurn ? projection.presentation : undefined,
|
|
349
|
+
liveMetrics: keepLiveTurn && projection.turnId ? metricsByTurn.get(projection.turnId) : undefined,
|
|
320
350
|
hasLiveApprovalEvent: Boolean(keepLiveTurn && !isNewTurn && snapshot.hasLiveApprovalEvent),
|
|
321
351
|
activity,
|
|
322
352
|
status: projection.status,
|
|
@@ -335,6 +365,12 @@ export function createCliTuiStore() {
|
|
|
335
365
|
setCancellationRequested(requested) {
|
|
336
366
|
if (snapshot.cancellationRequested === requested)
|
|
337
367
|
return;
|
|
368
|
+
if (requested && snapshot.liveTurnId) {
|
|
369
|
+
cancelledTurns.add(snapshot.liveTurnId);
|
|
370
|
+
const events = snapshot.liveEvents.map(function cancel(event) { return event.type === "tui_input_consumed" && event.consumed === false ? { ...event, cancelled: true } : event; });
|
|
371
|
+
eventsByTurn.set(snapshot.liveTurnId, events);
|
|
372
|
+
snapshot = { ...snapshot, liveEvents: events, pendingInputs: [] };
|
|
373
|
+
}
|
|
338
374
|
publish({ ...snapshot, cancellationRequested: requested });
|
|
339
375
|
},
|
|
340
376
|
requestExit(code) {
|
|
@@ -6,6 +6,7 @@ import { TUI_ACCENT_COLOR } from "../../design-tokens.js";
|
|
|
6
6
|
import { TUI_AGENT_LABEL } from "../constants.js";
|
|
7
7
|
import { formatTuiDuration, formatTuiInputConsumed, getTuiApprovalDecisionLabel, isTerminalStatus } from "../format.js";
|
|
8
8
|
import { TuiMarkdown } from "../markdown/markdown.js";
|
|
9
|
+
import { TuiPromptLine } from "../transcript.js";
|
|
9
10
|
import { renderTuiToolTreeEvent } from "./tool-tree.js";
|
|
10
11
|
/** 为增量时间线生成稳定键,避免 answer delta 到达时重建历史节点。 */
|
|
11
12
|
function getCliTuiTimelineEventKey(event) {
|
|
@@ -17,6 +18,9 @@ function getCliTuiTimelineEventKey(event) {
|
|
|
17
18
|
function isCliTuiTimelineAppendOnly(cache, events) {
|
|
18
19
|
if (cache.eventCount === 0)
|
|
19
20
|
return true;
|
|
21
|
+
const hostInputs = events.filter(function host(event) { return event.type === "tui_input_consumed"; });
|
|
22
|
+
if (hostInputs.length !== cache.hostInputs.length || hostInputs.some(function changed(event, index) { return event !== cache.hostInputs[index]; }))
|
|
23
|
+
return false;
|
|
20
24
|
return events.length >= cache.eventCount && events[cache.eventCount - 1] === cache.lastEvent;
|
|
21
25
|
}
|
|
22
26
|
/** 应用一条时间线事件,只修改新增尾部或当前回答块。 */
|
|
@@ -50,7 +54,7 @@ function appendCliTuiTimelineEvent(cache, event) {
|
|
|
50
54
|
}
|
|
51
55
|
/** 首次或 Turn 切换时完整归约时间线;后续更新走尾部增量路径。 */
|
|
52
56
|
function createCliTuiTimelineCache(events) {
|
|
53
|
-
const cache = { eventCount: 0, blocks: [] };
|
|
57
|
+
const cache = { eventCount: 0, blocks: [], hostInputs: events.filter(function host(event) { return event.type === "tui_input_consumed"; }) };
|
|
54
58
|
for (const event of events)
|
|
55
59
|
appendCliTuiTimelineEvent(cache, event);
|
|
56
60
|
cache.eventCount = events.length;
|
|
@@ -80,7 +84,7 @@ function useCliTuiTimelineBlocks(events) {
|
|
|
80
84
|
function TuiLiveEventBlock(props) {
|
|
81
85
|
const event = props.event;
|
|
82
86
|
if (event.type === "tui_input_consumed") {
|
|
83
|
-
return _jsx(Text, { dimColor: props.useColor, children: formatTuiInputConsumed(event) });
|
|
87
|
+
return _jsxs(Box, { flexDirection: "column", children: [_jsx(TuiPromptLine, { content: event.content, useColor: props.useColor, columns: props.maximumWidth + 2 }), _jsx(Text, { dimColor: props.useColor, children: event.cancelled ? "[引导] 已取消" : event.consumed === false ? "[引导] 等待当前步骤结束" : formatTuiInputConsumed(event) })] });
|
|
84
88
|
}
|
|
85
89
|
if (event.type === "tui_approval_submitted") {
|
|
86
90
|
return _jsxs(Text, { dimColor: props.useColor, children: ["Approval submitted \u00B7 ", getTuiApprovalDecisionLabel(event.decision)] });
|
|
@@ -107,8 +111,6 @@ function TuiLiveEventBlock(props) {
|
|
|
107
111
|
return _jsx(Text, { children: "Context organization failed" });
|
|
108
112
|
if (event.type === "context_compact_circuit_open")
|
|
109
113
|
return _jsx(Text, { children: "Context organization temporarily unavailable" });
|
|
110
|
-
if (event.type === "status" && event.status === "failed" && event.reason)
|
|
111
|
-
return _jsxs(Text, { children: ["\u539F\u56E0\uFF1A", event.reason] });
|
|
112
114
|
if (event.type === "status")
|
|
113
115
|
return null;
|
|
114
116
|
return null;
|
|
@@ -132,7 +134,7 @@ function renderTuiLiveTimelineBlock(block, useColor, renderMarkdown, maximumWidt
|
|
|
132
134
|
return null;
|
|
133
135
|
if (block.event.type === "answer_delta")
|
|
134
136
|
return null;
|
|
135
|
-
return _jsx(MemoizedTuiLiveEventBlock, { event: block.event, useColor: useColor });
|
|
137
|
+
return _jsx(MemoizedTuiLiveEventBlock, { event: block.event, useColor: useColor, maximumWidth: maximumWidth });
|
|
136
138
|
}
|
|
137
139
|
/**
|
|
138
140
|
* Ink 在动态区超过一屏后会清空终端并从顶部重绘;把已经确定的块交给
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** 基于共享 Presentation selector 的 CLI 时间线;不读取旧工具 summary、detail 或审批 preview。 */
|
|
2
2
|
import { type AgentPresentationState } from "@autopilot-agent/presentation";
|
|
3
3
|
import { type ReactNode } from "react";
|
|
4
|
+
import type { CliTuiTimelineEvent } from "../types.js";
|
|
4
5
|
/** 详情视图参数:expanded 驱动 Todo 卡展开,toolGroupsExpanded 驱动工具分组展开;screen reader 恒展开全部明细。 */
|
|
5
6
|
type TuiDetailView = {
|
|
6
7
|
expanded: boolean;
|
|
@@ -10,5 +11,5 @@ type TuiDetailView = {
|
|
|
10
11
|
toolGroupsExpanded?: boolean;
|
|
11
12
|
};
|
|
12
13
|
/** 渲染指定 Turn 的共享 Presentation 时间线;无匹配节点时由调用方回退兼容旧事件。 */
|
|
13
|
-
export declare function renderTuiPresentationTimeline(presentation: AgentPresentationState, turnId: string, useColor: boolean, maximumWidth: number, detailView: TuiDetailView): ReactNode[];
|
|
14
|
+
export declare function renderTuiPresentationTimeline(presentation: AgentPresentationState, turnId: string, useColor: boolean, maximumWidth: number, detailView: TuiDetailView, events?: CliTuiTimelineEvent[]): ReactNode[];
|
|
14
15
|
export {};
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/** 基于共享 Presentation selector 的 CLI 时间线;不读取旧工具 summary、detail 或审批 preview。 */
|
|
3
|
-
import { getPresentationCopy,
|
|
3
|
+
import { getPresentationCopy, getToolPresentationProfile, isSandboxUnavailableErrorCode, selectToolCalls } from "@autopilot-agent/presentation";
|
|
4
4
|
import { Box, Text } from "ink";
|
|
5
|
-
import { TUI_ACCENT_BG_COLOR, TUI_ACCENT_COLOR, TUI_CHIP_TEXT_COLOR,
|
|
5
|
+
import { TUI_ACCENT_BG_COLOR, TUI_ACCENT_COLOR, TUI_CHIP_TEXT_COLOR, TUI_TOOL_ERROR_BG_COLOR, TUI_TOOL_ERROR_TEXT_COLOR } from "../../design-tokens.js";
|
|
6
6
|
import { TUI_AGENT_LABEL } from "../constants.js";
|
|
7
7
|
import { TuiDiffStats } from "../diff-stats.js";
|
|
8
8
|
import { getTuiDiffStats } from "../format.js";
|
|
9
9
|
import { TuiDiffBlock } from "../markdown/diff.js";
|
|
10
10
|
import { TuiMarkdown } from "../markdown/markdown.js";
|
|
11
|
+
import { TuiPromptLine } from "../transcript.js";
|
|
11
12
|
import { TuiTodoList } from "../todo-list.js";
|
|
13
|
+
import { getTuiTurnItems } from "./turn-items.js";
|
|
12
14
|
/** 取得 ToolCall 的纯文本状态标志,不依赖颜色表达执行结果。 */
|
|
13
15
|
function getToolStateGlyph(state) {
|
|
14
16
|
if (state === "completed")
|
|
@@ -21,6 +23,16 @@ function getToolStateGlyph(state) {
|
|
|
21
23
|
return "×";
|
|
22
24
|
return "✢";
|
|
23
25
|
}
|
|
26
|
+
/** 取得拒绝、取消与超时文字;执行失败由叉号和错误详情表达。沙箱不可用类拒绝是环境阻塞而非用户拒绝,文案必须区分。 */
|
|
27
|
+
function getToolStateText(state, errorCode) {
|
|
28
|
+
if (state === "denied")
|
|
29
|
+
return isSandboxUnavailableErrorCode(errorCode) ? "沙箱不可用,命令未启动" : "已拒绝";
|
|
30
|
+
if (state === "cancelled")
|
|
31
|
+
return "已取消";
|
|
32
|
+
if (state === "timeout")
|
|
33
|
+
return "已超时";
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
24
36
|
/** 判断工具是否处于失败类终态;失败步骤文字使用错误红并保留文字标签。 */
|
|
25
37
|
function isFailedToolState(state) {
|
|
26
38
|
return state === "failed" || state === "denied" || state === "cancelled" || state === "timeout";
|
|
@@ -42,6 +54,8 @@ function getWebToolDetailText(item) {
|
|
|
42
54
|
}
|
|
43
55
|
/** 将一个共享 selector 节点渲染为 Ink 视图,Tool 节点的 React key 始终绑定 callId。 */
|
|
44
56
|
function renderPresentationItem(item, useColor, maximumWidth, detailView, toolCallIndex) {
|
|
57
|
+
if (item.kind === "todo")
|
|
58
|
+
return _jsx(Box, { flexDirection: "column", children: _jsx(TuiTodoList, { item: item, expanded: detailView.expanded || detailView.screenReader, useColor: useColor, screenReader: detailView.screenReader, columns: maximumWidth + 2, rowLimit: detailView.rowLimit, showToggleHint: detailView.showToggleHint }) }, item.id);
|
|
45
59
|
if (item.kind === "thinking")
|
|
46
60
|
return _jsxs(Text, { dimColor: useColor, children: ["\u2722 ", item.summary] }, item.id);
|
|
47
61
|
if (item.kind === "tool_group") {
|
|
@@ -55,25 +69,28 @@ function renderPresentationItem(item, useColor, maximumWidth, detailView, toolCa
|
|
|
55
69
|
return (_jsxs(Box, { flexDirection: "column", children: [summary, children.map(function renderGroupChild(child) { return _jsx(Box, { paddingLeft: 2, children: renderPresentationItem(child, useColor, maximumWidth, detailView, toolCallIndex) }, child.callId); })] }, item.id));
|
|
56
70
|
}
|
|
57
71
|
if (item.kind === "tool_call") {
|
|
58
|
-
const title = getPresentationCopy(item.titleKey);
|
|
72
|
+
const title = getPresentationCopy(item.state === "failed" ? getToolPresentationProfile(item.toolName).completedTitleKey : item.titleKey);
|
|
59
73
|
const jobProgramLabel = item.job?.action === "start" ? item.job.programLabel?.toUpperCase() : undefined;
|
|
60
74
|
const diffStats = getTuiDiffStats(item.diff);
|
|
61
75
|
const detail = getWebToolDetailText(item) ?? [jobProgramLabel ? undefined : item.target, item.resultSummary].filter(Boolean).join(" · ");
|
|
62
76
|
const glyph = getToolStateGlyph(item.state);
|
|
63
|
-
//
|
|
77
|
+
// 失败沿用完成态工具名称,仅徽标使用错误红;前缀符号独立显示。
|
|
78
|
+
const stateText = getToolStateText(item.state, item.errorCode);
|
|
64
79
|
const head = isFailedToolState(item.state)
|
|
65
|
-
? _jsxs(Text, { color: useColor ?
|
|
80
|
+
? _jsxs(Text, { children: [_jsxs(Text, { color: useColor ? TUI_TOOL_ERROR_BG_COLOR : undefined, children: [glyph, " "] }), _jsxs(Text, { backgroundColor: useColor ? TUI_TOOL_ERROR_BG_COLOR : undefined, color: useColor ? TUI_TOOL_ERROR_TEXT_COLOR : undefined, bold: useColor || undefined, children: ["[", title, "]", stateText ? ` · ${stateText}` : ""] })] })
|
|
66
81
|
: _jsxs(Text, { children: [_jsxs(Text, { dimColor: useColor, children: [glyph, " "] }), _jsxs(Text, { backgroundColor: useColor ? TUI_ACCENT_BG_COLOR : undefined, color: useColor ? TUI_CHIP_TEXT_COLOR : undefined, bold: useColor || undefined, children: ["[", title, "]"] })] });
|
|
67
|
-
return _jsxs(Box, { flexDirection: "column", children: [head, jobProgramLabel ? _jsxs(Text, { children: [" \u2514\u2500 ", _jsx(Text, { bold: true, color: useColor ? TUI_ACCENT_COLOR : undefined, children: jobProgramLabel }), detail ? _jsxs(Text, { dimColor: useColor, children: [" \u00B7 ", detail] }) : null] }) : detail || diffStats ? _jsxs(Text, { children: [" \u2514\u2500 ", detail ? _jsxs(Text, { dimColor: useColor, children: [detail, " "] }) : null, diffStats ? _jsx(TuiDiffStats, { ...diffStats, useColor: useColor }) : null] }) : null, item.errorCode ? _jsxs(Text, { dimColor: useColor, children: [" \u2514\u2500 ", item.errorCode] }) : null, item.job?.action === "start" && (item.job.observedStatus === "starting" || item.job.observedStatus === "running") ? _jsx(Text, { dimColor: useColor, children: " /jobs \u67E5\u770B\u540E\u53F0\u4EFB\u52A1" }) : null, item.diff?.text ? _jsx(TuiDiffBlock, { diff: item.diff.text, keyPrefix: item.callId, useColor: useColor }) : null] }, item.callId);
|
|
82
|
+
return _jsxs(Box, { flexDirection: "column", children: [head, jobProgramLabel ? _jsxs(Text, { children: [" \u2514\u2500 ", _jsx(Text, { bold: true, color: useColor ? TUI_ACCENT_COLOR : undefined, children: jobProgramLabel }), detail ? _jsxs(Text, { dimColor: useColor, children: [" \u00B7 ", detail] }) : null] }) : detail || diffStats ? _jsxs(Text, { children: [" \u2514\u2500 ", detail ? _jsxs(Text, { dimColor: useColor, children: [detail, " "] }) : null, diffStats ? _jsx(TuiDiffStats, { ...diffStats, useColor: useColor }) : null] }) : null, item.errorCode ? _jsxs(Text, { dimColor: useColor, children: [" \u2514\u2500 ", item.errorCode] }) : null, item.executionMode === "unisolated" ? _jsx(Text, { color: useColor ? TUI_TOOL_ERROR_TEXT_COLOR : undefined, children: " \u2514\u2500 \u672A\u9694\u79BB\uFF1A\u5BBF\u4E3B\u76F4\u63A5\u6267\u884C" }) : null, item.diagnostics?.length ? _jsxs(Text, { dimColor: useColor, children: [" \u2514\u2500 \u8BCA\u65AD\uFF1A", item.diagnostics.join(";")] }) : null, item.job?.action === "start" && (item.job.observedStatus === "starting" || item.job.observedStatus === "running") ? _jsx(Text, { dimColor: useColor, children: " /jobs \u67E5\u770B\u540E\u53F0\u4EFB\u52A1" }) : null, item.diff?.text ? _jsx(TuiDiffBlock, { diff: item.diff.text, keyPrefix: item.callId, useColor: useColor }) : null] }, item.callId);
|
|
68
83
|
}
|
|
69
84
|
if (item.kind === "approval") {
|
|
70
85
|
const detail = item.display?.actionPreview ?? item.display?.operation ?? "需要确认";
|
|
71
|
-
|
|
86
|
+
// 历史行与待审卡共用 constraint 判定,普通批准与无沙箱批准不得混淆。
|
|
87
|
+
const noSandbox = item.constraint === "no-sandbox-exec:v1" ? " · 无沙箱直接执行" : "";
|
|
88
|
+
// Talk-Plan1022 S3:宿主执行审批事实与 no-sandbox 降级卡分开呈现,措辞不宣称已执行。
|
|
89
|
+
const hostExecution = item.display?.executionMode === "unisolated" ? " · 将在当前用户权限下运行(未启用沙箱隔离)" : "";
|
|
90
|
+
return _jsxs(Text, { dimColor: useColor, children: ["\u26A0 ", detail, noSandbox, hostExecution, item.state === "resolved" ? " · 已处理" : ""] }, item.id);
|
|
72
91
|
}
|
|
73
92
|
if (item.kind === "user_question")
|
|
74
93
|
return _jsx(Text, { dimColor: useColor, children: "\u8BF7\u5728\u652F\u6301\u7ED3\u6784\u5316\u56DE\u7B54\u7684 Web \u4E2D\u5904\u7406" }, item.id);
|
|
75
|
-
if (item.kind === "todo")
|
|
76
|
-
return _jsx(TuiTodoList, { item: item, expanded: detailView.expanded, useColor: useColor, screenReader: detailView.screenReader, columns: maximumWidth, rowLimit: detailView.rowLimit, showToggleHint: detailView.showToggleHint }, item.id);
|
|
77
94
|
if (item.kind === "assistant_answer") {
|
|
78
95
|
if (item.channel === "process")
|
|
79
96
|
return _jsx(TuiMarkdown, { content: item.content, keyPrefix: item.id, useColor: useColor, maximumWidth: maximumWidth }, item.id);
|
|
@@ -87,7 +104,7 @@ function renderPresentationItem(item, useColor, maximumWidth, detailView, toolCa
|
|
|
87
104
|
return _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: useColor, children: [item.state === "completed" ? "✓" : "⚠", " ", title] }), item.restoredPaths.length > 0 ? _jsxs(Text, { dimColor: useColor, children: [" restored: ", item.restoredPaths.join(", ")] }) : null, item.compensatedPaths.length > 0 ? _jsxs(Text, { dimColor: useColor, children: [" compensated: ", item.compensatedPaths.join(", ")] }) : null, item.conflicts.length > 0 ? _jsxs(Text, { dimColor: useColor, children: [" conflicts (not overwritten): ", item.conflicts.join(", ")] }) : null] }, item.id);
|
|
88
105
|
}
|
|
89
106
|
if (item.kind === "input_notice")
|
|
90
|
-
return _jsxs(Text, { dimColor: useColor, children: ["
|
|
107
|
+
return _jsxs(Text, { dimColor: useColor, children: [item.delivery === "steer" ? "[引导] " : item.delivery === "queue" ? "[排队] " : "", "Input ", item.state] }, item.id);
|
|
91
108
|
if (item.kind === "turn_summary")
|
|
92
109
|
return _jsxs(Text, { dimColor: useColor, children: [getTurnStateGlyph(item.state), " ", getPresentationCopy(`turn.${item.state}`), " \u00B7 ", item.toolCount, " tools"] }, item.id);
|
|
93
110
|
if (item.kind === "job") {
|
|
@@ -100,10 +117,12 @@ function renderPresentationItem(item, useColor, maximumWidth, detailView, toolCa
|
|
|
100
117
|
return null;
|
|
101
118
|
}
|
|
102
119
|
/** 渲染指定 Turn 的共享 Presentation 时间线;无匹配节点时由调用方回退兼容旧事件。 */
|
|
103
|
-
export function renderTuiPresentationTimeline(presentation, turnId, useColor, maximumWidth, detailView) {
|
|
120
|
+
export function renderTuiPresentationTimeline(presentation, turnId, useColor, maximumWidth, detailView, events = []) {
|
|
104
121
|
// 构建 callId 索引供展开分组取回组内调用;分组只按 callId 引用,不携带子节点事实。
|
|
105
122
|
const toolCallIndex = new Map(selectToolCalls(presentation, turnId).map(function indexToolCall(call) { return [call.callId, call]; }));
|
|
106
|
-
return
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
return getTuiTurnItems(presentation, turnId, events).map(function renderSharedTimelineItem(item) {
|
|
124
|
+
if ("type" in item)
|
|
125
|
+
return _jsxs(Box, { flexDirection: "column", children: [_jsx(TuiPromptLine, { content: item.content, useColor: useColor, columns: maximumWidth + 2 }), _jsxs(Text, { dimColor: useColor, children: [item.delivery === "queue" ? "[排队]" : "[引导]", " ", item.cancelled ? "已取消" : item.consumed === false ? "等待当前步骤结束" : "已插入当前 turn"] })] }, `input:${item.inputId}`);
|
|
126
|
+
return renderPresentationItem(item, useColor, maximumWidth, detailView, toolCallIndex);
|
|
127
|
+
});
|
|
109
128
|
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { TUI_ACCENT_COLOR } from "../../design-tokens.js";
|
|
3
|
+
import { TUI_ACCENT_COLOR, TUI_TOOL_ERROR_TEXT_COLOR } from "../../design-tokens.js";
|
|
4
4
|
import { TUI_AGENT_LABEL } from "../constants.js";
|
|
5
5
|
import { formatTuiDuration, formatTuiInputConsumed, getTuiApprovalDecisionLabel, getTuiToolLabel, getTuiTerminationSummary, getTuiTurnFailureReason } from "../format.js";
|
|
6
6
|
import { TuiMarkdown } from "../markdown/markdown.js";
|
|
7
7
|
import { renderTuiToolTreeEvent } from "./tool-tree.js";
|
|
8
8
|
import { renderTuiPresentationTimeline } from "./presentation-timeline.js";
|
|
9
|
+
import { TuiPromptLine } from "../transcript.js";
|
|
10
|
+
import { TuiTodoList } from "../todo-list.js";
|
|
11
|
+
import { getTuiTurnTodo } from "./turn-items.js";
|
|
9
12
|
/** 将安全事件聚合为可读的时间线行,连续 answer_delta 只显示为一个回答块。 */
|
|
10
13
|
export function renderTimeline(events, useColor, timing, maximumWidth = 80, screenReader = false) {
|
|
11
14
|
// 历史区在 Turn 终态只固化一次最终快照;读屏模式继续输出中文状态,不能退化为 glyph-only。
|
|
12
15
|
if (timing?.presentation && timing.turnId && timing.presentation.items.some(function belongsToTurn(item) { return item.kind === "capability_notice" || ("turnId" in item && item.turnId === timing.turnId); })) {
|
|
13
|
-
const nodes = renderTuiPresentationTimeline(timing.presentation, timing.turnId, useColor, maximumWidth, { expanded: true, screenReader });
|
|
16
|
+
const nodes = renderTuiPresentationTimeline(timing.presentation, timing.turnId, useColor, maximumWidth, { expanded: true, screenReader }, events);
|
|
14
17
|
const failureReason = getTuiTurnFailureReason(events);
|
|
15
18
|
const terminationSummary = getTuiTerminationSummary(events, timing?.status);
|
|
16
19
|
if (terminationSummary)
|
|
@@ -56,7 +59,7 @@ export function renderTimeline(events, useColor, timing, maximumWidth = 80, scre
|
|
|
56
59
|
}
|
|
57
60
|
flushAnswer();
|
|
58
61
|
if (event.type === "tui_input_consumed") {
|
|
59
|
-
nodes.push(_jsx(Text, { dimColor: useColor, children: formatTuiInputConsumed(event) }
|
|
62
|
+
nodes.push(_jsxs(Box, { flexDirection: "column", children: [_jsx(TuiPromptLine, { content: event.content, useColor: useColor, columns: maximumWidth + 2 }), _jsx(Text, { dimColor: useColor, children: event.cancelled ? "[引导] 已取消" : event.consumed === false ? "[引导] 等待当前步骤结束" : formatTuiInputConsumed(event) })] }, "input:" + event.inputId));
|
|
60
63
|
}
|
|
61
64
|
else if (event.type === "tui_approval_submitted") {
|
|
62
65
|
nodes.push(_jsxs(Text, { dimColor: useColor, children: ["Approval submitted \u00B7 ", getTuiApprovalDecisionLabel(event.decision)] }, String(event.seq) + ":approval:" + event.approvalId));
|
|
@@ -76,7 +79,7 @@ export function renderTimeline(events, useColor, timing, maximumWidth = 80, scre
|
|
|
76
79
|
else if (event.type === "approval_required") {
|
|
77
80
|
if (submittedApprovalIds.has(event.approvalId))
|
|
78
81
|
continue;
|
|
79
|
-
nodes.push(_jsxs(Box, { borderStyle: "single", borderColor: useColor ? TUI_ACCENT_COLOR : undefined, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, color: useColor ? TUI_ACCENT_COLOR : undefined, children: "Approval required" }), _jsxs(Text, { children: ["\u64CD\u4F5C\uFF1A", getTuiToolLabel(event.toolName)] }), _jsxs(Text, { children: ["\u539F\u56E0\uFF1A", event.reason] }), _jsx(Text, { dimColor: useColor, children: "[y] \u672C\u6B21 [s] \u672C\u6B21\u4F1A\u8BDD [w] \u6B64\u5DE5\u4F5C\u533A [n] \u62D2\u7EDD" })] }, String(event.seq)));
|
|
82
|
+
nodes.push(_jsxs(Box, { borderStyle: "single", borderColor: useColor ? TUI_ACCENT_COLOR : undefined, paddingX: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, color: useColor ? TUI_ACCENT_COLOR : undefined, children: "Approval required" }), _jsxs(Text, { children: ["\u64CD\u4F5C\uFF1A", getTuiToolLabel(event.toolName)] }), _jsxs(Text, { children: ["\u539F\u56E0\uFF1A", event.reason] }), event.display?.executionMode === "unisolated" && _jsx(Text, { color: useColor ? TUI_TOOL_ERROR_TEXT_COLOR : undefined, children: "\u5C06\u5728\u5F53\u524D\u7528\u6237\u6743\u9650\u4E0B\u8FD0\u884C\uFF08\u672A\u542F\u7528\u6C99\u7BB1\u9694\u79BB\uFF09" }), _jsx(Text, { dimColor: useColor, children: "[y] \u672C\u6B21 [s] \u672C\u6B21\u4F1A\u8BDD [w] \u6B64\u5DE5\u4F5C\u533A [n] \u62D2\u7EDD" })] }, String(event.seq)));
|
|
80
83
|
}
|
|
81
84
|
else if (event.type === "input_queued") {
|
|
82
85
|
nodes.push(_jsx(Text, { dimColor: useColor, children: "Additional instruction queued; waiting for the current step" }, String(event.seq)));
|
|
@@ -110,11 +113,18 @@ export function renderTimeline(events, useColor, timing, maximumWidth = 80, scre
|
|
|
110
113
|
continue;
|
|
111
114
|
}
|
|
112
115
|
else if (event.type === "status") {
|
|
113
|
-
|
|
114
|
-
nodes.push(_jsxs(Text, { children: ["\u539F\u56E0\uFF1A", event.reason] }, String(event.seq)));
|
|
116
|
+
continue;
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
119
|
flushAnswer();
|
|
120
|
+
const todo = getTuiTurnTodo(events, timing?.turnId ?? events[0]?.turnId ?? "");
|
|
121
|
+
if (todo)
|
|
122
|
+
nodes.push(_jsx(Box, { flexDirection: "column", children: _jsx(TuiTodoList, { item: todo, expanded: true, useColor: useColor, screenReader: screenReader, columns: maximumWidth + 2 }) }, "turn:todo"));
|
|
123
|
+
const terminationSummary = getTuiTerminationSummary(events, timing?.status);
|
|
124
|
+
if (terminationSummary)
|
|
125
|
+
nodes.push(_jsxs(Text, { children: ["\u7EC8\u6001\uFF1A", terminationSummary] }, "turn:termination"));
|
|
126
|
+
else if (getTuiTurnFailureReason(events))
|
|
127
|
+
nodes.push(_jsxs(Text, { children: ["\u539F\u56E0\uFF1A", getTuiTurnFailureReason(events)] }, "turn:failure-reason"));
|
|
118
128
|
if (timing?.totalDurationMs !== undefined)
|
|
119
129
|
nodes.push(_jsxs(Text, { dimColor: useColor, children: ["* Cogitated for ", formatTuiDuration(timing.totalDurationMs)] }, "turn:total-duration"));
|
|
120
130
|
return nodes.filter(function isVisibleTimelineNode(node) { return node !== undefined; });
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** 合并既有 Presentation 与宿主输入位置,并读取每轮最后一次安全 Todo。 */
|
|
2
|
+
import { type AgentPresentationState, type TimelineItem, type TodoSnapshotItem } from "@autopilot-agent/presentation";
|
|
3
|
+
import type { CliTuiInputPosition, CliTuiTimelineEvent } from "../types.js";
|
|
4
|
+
export type TuiHostInput = Extract<CliTuiTimelineEvent, {
|
|
5
|
+
type: "tui_input_consumed";
|
|
6
|
+
}>;
|
|
7
|
+
/** 保存当前可见事实的稳定边界,连续回答以字符位置保留输入前后的正文。 */
|
|
8
|
+
export declare function captureTuiInputPosition(turnId: string | undefined, events: CliTuiTimelineEvent[], presentation?: AgentPresentationState): CliTuiInputPosition;
|
|
9
|
+
/** 只采用该轮自身的最后一次 todo_updated;显式空列表保持清空语义。 */
|
|
10
|
+
export declare function getTuiTurnTodo(events: CliTuiTimelineEvent[], turnId: string): TodoSnapshotItem | undefined;
|
|
11
|
+
/** 按提交边界切分共享节点,再复用 selector 分组;宿主正文不会被后来的分组或回答吞到前面。 */
|
|
12
|
+
export declare function getTuiTurnItems(presentation: AgentPresentationState, turnId: string, events: CliTuiTimelineEvent[]): Array<TimelineItem | TuiHostInput>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** 合并既有 Presentation 与宿主输入位置,并读取每轮最后一次安全 Todo。 */
|
|
2
|
+
import { capturePresentationInputPosition, reducePresentationInput, selectTurnTimeline } from "@autopilot-agent/presentation";
|
|
3
|
+
/** 保存当前可见事实的稳定边界,连续回答以字符位置保留输入前后的正文。 */
|
|
4
|
+
export function captureTuiInputPosition(turnId, events, presentation) {
|
|
5
|
+
return { turnId, eventCount: events.filter(function publicEvent(event) { return event.type !== "tui_input_consumed"; }).length, items: presentation && turnId ? capturePresentationInputPosition(presentation, turnId).items : [] };
|
|
6
|
+
}
|
|
7
|
+
/** 只采用该轮自身的最后一次 todo_updated;显式空列表保持清空语义。 */
|
|
8
|
+
export function getTuiTurnTodo(events, turnId) {
|
|
9
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
10
|
+
const event = events[index];
|
|
11
|
+
if (event?.type === "todo_updated" && event.turnId === turnId)
|
|
12
|
+
return { id: `todo:${turnId}`, kind: "todo", turnId, todos: event.todos, counts: event.counts };
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
/** 按提交边界切分共享节点,再复用 selector 分组;宿主正文不会被后来的分组或回答吞到前面。 */
|
|
17
|
+
export function getTuiTurnItems(presentation, turnId, events) {
|
|
18
|
+
const inputs = events.filter(function host(event) { return event.type === "tui_input_consumed"; });
|
|
19
|
+
let state = presentation;
|
|
20
|
+
for (const input of inputs) {
|
|
21
|
+
state = reducePresentationInput(state, { kind: "host_event", event: { kind: "input", input: { turnId: input.turnId, inputId: input.inputId, delivery: input.delivery, content: input.content, position: input.position ?? { items: [] }, cancelled: input.cancelled, state: input.consumed === false ? "queued" : "consumed" } } });
|
|
22
|
+
}
|
|
23
|
+
const todo = getTuiTurnTodo(events, turnId);
|
|
24
|
+
// CLI 只采用本轮 events;不把恢复或 Session latest 误当作本轮更新。
|
|
25
|
+
state = { ...state, todosByTurn: { ...state.todosByTurn, [turnId]: todo ?? { id: `todo:${turnId}`, kind: "todo", turnId, todos: [], counts: { completed: 0, pending: 0, inProgress: 0 } } } };
|
|
26
|
+
const inputById = new Map(inputs.map(function pair(input) { return [input.inputId, input]; }));
|
|
27
|
+
return selectTurnTimeline(state, turnId).map(function host(item) { return item.kind === "input_notice" ? inputById.get(item.inputId) ?? item : item; });
|
|
28
|
+
}
|