@autopilot-agent/cli 0.0.0-stage → 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.
Files changed (144) hide show
  1. package/README.md +52 -2
  2. package/dist/activity-animation.d.ts +59 -0
  3. package/dist/activity-animation.js +148 -0
  4. package/dist/animation-clock.d.ts +22 -0
  5. package/dist/animation-clock.js +76 -0
  6. package/dist/composition-root.d.ts +105 -0
  7. package/dist/composition-root.js +600 -0
  8. package/dist/context-report.d.ts +20 -0
  9. package/dist/context-report.js +173 -0
  10. package/dist/controller.d.ts +79 -0
  11. package/dist/controller.js +741 -0
  12. package/dist/design-tokens.d.ts +38 -0
  13. package/dist/design-tokens.js +38 -0
  14. package/dist/error-diagnostics.d.ts +8 -0
  15. package/dist/error-diagnostics.js +62 -0
  16. package/dist/event-projector.d.ts +26 -0
  17. package/dist/event-projector.js +335 -0
  18. package/dist/file-mention-context.d.ts +34 -0
  19. package/dist/file-mention-context.js +173 -0
  20. package/dist/index.d.ts +19 -0
  21. package/dist/index.js +14 -0
  22. package/dist/main.d.ts +40 -0
  23. package/dist/main.js +543 -0
  24. package/dist/parser.d.ts +17 -0
  25. package/dist/parser.js +400 -0
  26. package/dist/renderer.d.ts +9 -0
  27. package/dist/renderer.js +62 -0
  28. package/dist/scroll-viewport.d.ts +22 -0
  29. package/dist/scroll-viewport.js +62 -0
  30. package/dist/termination-format.d.ts +4 -0
  31. package/dist/termination-format.js +18 -0
  32. package/dist/timeline-lines.d.ts +38 -0
  33. package/dist/timeline-lines.js +104 -0
  34. package/dist/tui/app.d.ts +30 -0
  35. package/dist/tui/app.js +413 -0
  36. package/dist/tui/approval.d.ts +19 -0
  37. package/dist/tui/approval.js +41 -0
  38. package/dist/tui/brand/brand-header.d.ts +20 -0
  39. package/dist/tui/brand/brand-header.js +22 -0
  40. package/dist/tui/brand/intro.d.ts +12 -0
  41. package/dist/tui/brand/intro.js +88 -0
  42. package/dist/tui/brand/pixel.d.ts +8 -0
  43. package/dist/tui/brand/pixel.js +50 -0
  44. package/dist/tui/constants.d.ts +56 -0
  45. package/dist/tui/constants.js +57 -0
  46. package/dist/tui/context.d.ts +11 -0
  47. package/dist/tui/context.js +12 -0
  48. package/dist/tui/diff-stats.d.ts +8 -0
  49. package/dist/tui/diff-stats.js +7 -0
  50. package/dist/tui/format.d.ts +75 -0
  51. package/dist/tui/format.js +178 -0
  52. package/dist/tui/hooks/use-file-mention-index.d.ts +15 -0
  53. package/dist/tui/hooks/use-file-mention-index.js +59 -0
  54. package/dist/tui/hooks/use-pickers.d.ts +47 -0
  55. package/dist/tui/hooks/use-pickers.js +288 -0
  56. package/dist/tui/hooks/use-prompt-input.d.ts +54 -0
  57. package/dist/tui/hooks/use-prompt-input.js +258 -0
  58. package/dist/tui/hooks/use-status-view.d.ts +8 -0
  59. package/dist/tui/hooks/use-status-view.js +13 -0
  60. package/dist/tui/hooks/use-viewport.d.ts +22 -0
  61. package/dist/tui/hooks/use-viewport.js +117 -0
  62. package/dist/tui/input/file-mention.d.ts +33 -0
  63. package/dist/tui/input/file-mention.js +112 -0
  64. package/dist/tui/input/graphemes.d.ts +5 -0
  65. package/dist/tui/input/graphemes.js +10 -0
  66. package/dist/tui/input/keys.d.ts +16 -0
  67. package/dist/tui/input/keys.js +58 -0
  68. package/dist/tui/input/prompt-layout.d.ts +27 -0
  69. package/dist/tui/input/prompt-layout.js +84 -0
  70. package/dist/tui/input-area.d.ts +53 -0
  71. package/dist/tui/input-area.js +89 -0
  72. package/dist/tui/jobs.d.ts +10 -0
  73. package/dist/tui/jobs.js +24 -0
  74. package/dist/tui/lifecycle.d.ts +23 -0
  75. package/dist/tui/lifecycle.js +114 -0
  76. package/dist/tui/markdown/diff.d.ts +8 -0
  77. package/dist/tui/markdown/diff.js +19 -0
  78. package/dist/tui/markdown/inline.d.ts +4 -0
  79. package/dist/tui/markdown/inline.js +32 -0
  80. package/dist/tui/markdown/markdown.d.ts +17 -0
  81. package/dist/tui/markdown/markdown.js +117 -0
  82. package/dist/tui/markdown/table.d.ts +19 -0
  83. package/dist/tui/markdown/table.js +74 -0
  84. package/dist/tui/performance-diagnostics.d.ts +7 -0
  85. package/dist/tui/performance-diagnostics.js +57 -0
  86. package/dist/tui/pickers/effort.d.ts +10 -0
  87. package/dist/tui/pickers/effort.js +12 -0
  88. package/dist/tui/pickers/file-mention.d.ts +11 -0
  89. package/dist/tui/pickers/file-mention.js +22 -0
  90. package/dist/tui/pickers/model.d.ts +10 -0
  91. package/dist/tui/pickers/model.js +12 -0
  92. package/dist/tui/pickers/permission.d.ts +10 -0
  93. package/dist/tui/pickers/permission.js +13 -0
  94. package/dist/tui/pickers/skill.d.ts +13 -0
  95. package/dist/tui/pickers/skill.js +25 -0
  96. package/dist/tui/pickers/slash.d.ts +10 -0
  97. package/dist/tui/pickers/slash.js +11 -0
  98. package/dist/tui/prompt-input.d.ts +11 -0
  99. package/dist/tui/prompt-input.js +73 -0
  100. package/dist/tui/render-mode.d.ts +20 -0
  101. package/dist/tui/render-mode.js +31 -0
  102. package/dist/tui/run.d.ts +6 -0
  103. package/dist/tui/run.js +237 -0
  104. package/dist/tui/status-line.d.ts +16 -0
  105. package/dist/tui/status-line.js +37 -0
  106. package/dist/tui/store.d.ts +4 -0
  107. package/dist/tui/store.js +380 -0
  108. package/dist/tui/timeline/live.d.ts +16 -0
  109. package/dist/tui/timeline/live.js +153 -0
  110. package/dist/tui/timeline/presentation-timeline.d.ts +15 -0
  111. package/dist/tui/timeline/presentation-timeline.js +128 -0
  112. package/dist/tui/timeline/progress.d.ts +12 -0
  113. package/dist/tui/timeline/progress.js +43 -0
  114. package/dist/tui/timeline/static-timeline.d.ts +13 -0
  115. package/dist/tui/timeline/static-timeline.js +131 -0
  116. package/dist/tui/timeline/tool-tree.d.ts +7 -0
  117. package/dist/tui/timeline/tool-tree.js +25 -0
  118. package/dist/tui/timeline/turn-items.d.ts +12 -0
  119. package/dist/tui/timeline/turn-items.js +28 -0
  120. package/dist/tui/timeline/turn.d.ts +19 -0
  121. package/dist/tui/timeline/turn.js +21 -0
  122. package/dist/tui/todo-list.d.ts +50 -0
  123. package/dist/tui/todo-list.js +120 -0
  124. package/dist/tui/transcript.d.ts +24 -0
  125. package/dist/tui/transcript.js +34 -0
  126. package/dist/tui/trust-dialog.d.ts +51 -0
  127. package/dist/tui/trust-dialog.js +127 -0
  128. package/dist/tui/types.d.ts +189 -0
  129. package/dist/tui/types.js +5 -0
  130. package/dist/tui/viewport/selection.d.ts +27 -0
  131. package/dist/tui/viewport/selection.js +79 -0
  132. package/dist/tui/viewport/sources.d.ts +29 -0
  133. package/dist/tui/viewport/sources.js +592 -0
  134. package/dist/tui/viewport/viewport.d.ts +12 -0
  135. package/dist/tui/viewport/viewport.js +91 -0
  136. package/dist/tui-input-history.d.ts +23 -0
  137. package/dist/tui-input-history.js +42 -0
  138. package/dist/tui-input-router.d.ts +24 -0
  139. package/dist/tui-input-router.js +115 -0
  140. package/dist/tui.d.ts +15 -0
  141. package/dist/tui.js +13 -0
  142. package/dist/types.d.ts +467 -0
  143. package/dist/types.js +7 -0
  144. package/package.json +58 -4
@@ -0,0 +1,128 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** 基于共享 Presentation selector 的 CLI 时间线;不读取旧工具 summary、detail 或审批 preview。 */
3
+ import { getPresentationCopy, getToolPresentationProfile, isSandboxUnavailableErrorCode, selectToolCalls } from "@autopilot-agent/presentation";
4
+ import { Box, Text } from "ink";
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
+ import { TUI_AGENT_LABEL } from "../constants.js";
7
+ import { TuiDiffStats } from "../diff-stats.js";
8
+ import { getTuiDiffStats } from "../format.js";
9
+ import { TuiDiffBlock } from "../markdown/diff.js";
10
+ import { TuiMarkdown } from "../markdown/markdown.js";
11
+ import { TuiPromptLine } from "../transcript.js";
12
+ import { TuiTodoList } from "../todo-list.js";
13
+ import { getTuiTurnItems } from "./turn-items.js";
14
+ /** 取得 ToolCall 的纯文本状态标志,不依赖颜色表达执行结果。 */
15
+ function getToolStateGlyph(state) {
16
+ if (state === "completed")
17
+ return "•";
18
+ if (state === "denied")
19
+ return "×";
20
+ if (state === "cancelled" || state === "timeout")
21
+ return "⚠";
22
+ if (state === "failed")
23
+ return "×";
24
+ return "✢";
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
+ }
36
+ /** 判断工具是否处于失败类终态;失败步骤文字使用错误红并保留文字标签。 */
37
+ function isFailedToolState(state) {
38
+ return state === "failed" || state === "denied" || state === "cancelled" || state === "timeout";
39
+ }
40
+ /** 取得 Turn 的纯文本终态标志,不把失败、取消和上限混为成功。 */
41
+ function getTurnStateGlyph(state) {
42
+ return state === "completed" ? "✓" : state === "interrupted" || state === "paused" ? "⚠" : "×";
43
+ }
44
+ /** 从 Core 冻结的 Web 明细生成 TUI 安全文案,不读取 query、URL 路径或网页正文。 */
45
+ function getWebToolDetailText(item) {
46
+ if (item.detail?.kind === "web_search") {
47
+ const summary = item.detail.sourceCount === 0 ? "搜索服务未返回结构化引用" : `找到 ${item.detail.sourceCount} 个公开来源`;
48
+ return `${summary}${item.detail.truncated ? " · 结果已截断" : ""}`;
49
+ }
50
+ if (item.detail?.kind === "web_fetch") {
51
+ return `${item.detail.host} · ${item.detail.contentType}${item.detail.truncated ? " · 内容已截断" : ""}`;
52
+ }
53
+ return undefined;
54
+ }
55
+ /** 将一个共享 selector 节点渲染为 Ink 视图,Tool 节点的 React key 始终绑定 callId。 */
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);
59
+ if (item.kind === "thinking")
60
+ return _jsxs(Text, { dimColor: useColor, children: ["\u2722 ", item.summary] }, item.id);
61
+ if (item.kind === "tool_group") {
62
+ const summary = (_jsxs(Text, { children: [_jsx(Text, { dimColor: useColor, children: "\u2022 Exploring codebase \u00B7 " }), _jsx(Text, { color: useColor ? TUI_ACCENT_COLOR : undefined, children: item.toolCallIds.length }), _jsx(Text, { dimColor: useColor, children: " operations" })] }, item.id));
63
+ // 分组默认只显示摘要;Ctrl+O 详情态或 screen reader 下展开渲染组内各调用。
64
+ if (!detailView.toolGroupsExpanded && !detailView.screenReader)
65
+ return summary;
66
+ const children = item.toolCallIds
67
+ .map(function findGroupChild(callId) { return toolCallIndex?.get(callId); })
68
+ .filter(function isKnownCall(child) { return child !== undefined; });
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));
70
+ }
71
+ if (item.kind === "tool_call") {
72
+ const title = getPresentationCopy(item.state === "failed" ? getToolPresentationProfile(item.toolName).completedTitleKey : item.titleKey);
73
+ const jobProgramLabel = item.job?.action === "start" ? item.job.programLabel?.toUpperCase() : undefined;
74
+ const diffStats = getTuiDiffStats(item.diff);
75
+ const detail = getWebToolDetailText(item) ?? [jobProgramLabel ? undefined : item.target, item.resultSummary].filter(Boolean).join(" · ");
76
+ const glyph = getToolStateGlyph(item.state);
77
+ // 失败沿用完成态工具名称,仅徽标使用错误红;前缀符号独立显示。
78
+ const stateText = getToolStateText(item.state, item.errorCode);
79
+ const head = isFailedToolState(item.state)
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}` : ""] })] })
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, "]"] })] });
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);
83
+ }
84
+ if (item.kind === "approval") {
85
+ const detail = item.display?.actionPreview ?? item.display?.operation ?? "需要确认";
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);
91
+ }
92
+ if (item.kind === "user_question")
93
+ return _jsx(Text, { dimColor: useColor, children: "\u8BF7\u5728\u652F\u6301\u7ED3\u6784\u5316\u56DE\u7B54\u7684 Web \u4E2D\u5904\u7406" }, item.id);
94
+ if (item.kind === "assistant_answer") {
95
+ if (item.channel === "process")
96
+ return _jsx(TuiMarkdown, { content: item.content, keyPrefix: item.id, useColor: useColor, maximumWidth: maximumWidth }, item.id);
97
+ return _jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, color: useColor ? TUI_ACCENT_COLOR : undefined, children: ["\u2022 ", TUI_AGENT_LABEL] }), _jsx(TuiMarkdown, { content: item.content, keyPrefix: item.id, useColor: useColor, maximumWidth: maximumWidth })] }, item.id);
98
+ }
99
+ if (item.kind === "context_notice")
100
+ return _jsx(Text, { dimColor: useColor, children: item.state === "compacted" ? "Context organized" : `Context ${item.state}` }, item.id);
101
+ if (item.kind === "undo") {
102
+ const isPartial = item.state === "failed" && (item.restoredPaths.length > 0 || item.compensatedPaths.length > 0);
103
+ const title = item.state === "completed" ? "Undo completed" : isPartial ? "Undo incomplete" : "Undo failed";
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);
105
+ }
106
+ if (item.kind === "input_notice")
107
+ return _jsxs(Text, { dimColor: useColor, children: [item.delivery === "steer" ? "[引导] " : item.delivery === "queue" ? "[排队] " : "", "Input ", item.state] }, item.id);
108
+ if (item.kind === "turn_summary")
109
+ return _jsxs(Text, { dimColor: useColor, children: [getTurnStateGlyph(item.state), " ", getPresentationCopy(`turn.${item.state}`), " \u00B7 ", item.toolCount, " tools"] }, item.id);
110
+ if (item.kind === "job") {
111
+ if (item.state === "interrupted")
112
+ return _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: useColor, children: "\u26A0 Job interrupted" }), _jsx(Text, { dimColor: useColor, children: " Process cannot be reattached after restart." })] }, item.id);
113
+ return _jsxs(Text, { dimColor: useColor, children: ["Job ", item.state, item.summary ? ` · ${item.summary}` : ""] }, item.id);
114
+ }
115
+ if (item.kind === "capability_notice")
116
+ return _jsx(Text, { dimColor: useColor, children: item.summary ?? item.state }, item.id);
117
+ return null;
118
+ }
119
+ /** 渲染指定 Turn 的共享 Presentation 时间线;无匹配节点时由调用方回退兼容旧事件。 */
120
+ export function renderTuiPresentationTimeline(presentation, turnId, useColor, maximumWidth, detailView, events = []) {
121
+ // 构建 callId 索引供展开分组取回组内调用;分组只按 callId 引用,不携带子节点事实。
122
+ const toolCallIndex = new Map(selectToolCalls(presentation, turnId).map(function indexToolCall(call) { return [call.callId, call]; }));
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
+ });
128
+ }
@@ -0,0 +1,12 @@
1
+ /** 当前思考进度汇总;只统计已发出的 tool_started,避免结果重复计数。 */
2
+ import { type ReactNode } from "react";
3
+ import type { CliTurnStatus } from "../../types.js";
4
+ import type { CliTuiTimelineEvent, CliTuiTurnMetrics } from "../types.js";
5
+ /** 汇总 Claude Code 风格的当前思考进度;只统计已发出的 tool_started,避免结果重复计数。 */
6
+ export declare function TuiLiveProgressSummary(props: {
7
+ events: CliTuiTimelineEvent[];
8
+ status: CliTurnStatus;
9
+ metrics?: CliTuiTurnMetrics;
10
+ useColor: boolean;
11
+ activeTodoForm?: string;
12
+ }): ReactNode;
@@ -0,0 +1,43 @@
1
+ import { jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** 当前思考进度汇总;只统计已发出的 tool_started,避免结果重复计数。 */
3
+ import { useEffect, useState } from "react";
4
+ import { Text } from "ink";
5
+ import { formatTuiDuration } from "../format.js";
6
+ /** 汇总 Claude Code 风格的当前思考进度;只统计已发出的 tool_started,避免结果重复计数。 */
7
+ export function TuiLiveProgressSummary(props) {
8
+ const [now, setNow] = useState(() => Date.now());
9
+ const active = props.status === "running" || props.status === "waiting_approval";
10
+ useEffect(function refreshProgressSummary() {
11
+ if (!active)
12
+ return;
13
+ const timer = setInterval(function updateProgressSummaryClock() { setNow(Date.now()); }, 1_000);
14
+ return function stopProgressSummaryClock() { clearInterval(timer); };
15
+ }, [active]);
16
+ if (!active || !props.metrics)
17
+ return null;
18
+ // Todo 的 activeForm 是模型声明的稳定活动文案,优先于宿主猜测的工具动词短语。
19
+ if (props.activeTodoForm) {
20
+ return _jsxs(Text, { dimColor: props.useColor, children: [props.activeTodoForm, " \u00B7 ", formatTuiDuration(Math.max(0, now - props.metrics.startedAt)), "\u2026"] });
21
+ }
22
+ const counts = new Map();
23
+ for (const event of props.events) {
24
+ if (event.type !== "tool_started")
25
+ continue;
26
+ counts.set(event.toolName, (counts.get(event.toolName) ?? 0) + 1);
27
+ }
28
+ const phrases = [];
29
+ const phrase = function makePhrase(toolName, verb, singular) {
30
+ const count = counts.get(toolName) ?? 0;
31
+ if (count === 0)
32
+ return;
33
+ phrases.push(`${verb} ${count} ${count === 1 ? singular : singular + "s"}`);
34
+ };
35
+ phrase("glob_files", "listing", "directory");
36
+ phrase("grep_files", "searching", "code search");
37
+ phrase("read_file", "reading", "file");
38
+ phrase("run_command", "running", "shell command");
39
+ phrase("write_file", "writing", "file");
40
+ phrase("edit_file", "editing", "file");
41
+ const thinking = `Thinking for ${formatTuiDuration(Math.max(0, now - props.metrics.startedAt))}`;
42
+ return _jsxs(Text, { dimColor: props.useColor, children: [thinking, phrases.length > 0 ? `, ${phrases.join(", ")}` : "", "\u2026"] });
43
+ }
@@ -0,0 +1,13 @@
1
+ /** 历史 Turn 时间线:把安全事件聚合为可读时间线行,连续 answer_delta 只显示为一个回答块。 */
2
+ import { type ReactNode } from "react";
3
+ import type { CliTurnStatus } from "../../types.js";
4
+ import type { CliTuiTimelineEvent } from "../types.js";
5
+ import type { AgentPresentationState } from "@autopilot-agent/presentation";
6
+ /** 将安全事件聚合为可读的时间线行,连续 answer_delta 只显示为一个回答块。 */
7
+ export declare function renderTimeline(events: CliTuiTimelineEvent[], useColor: boolean, timing?: {
8
+ thoughtDurationMs?: number;
9
+ totalDurationMs?: number;
10
+ presentation?: AgentPresentationState;
11
+ turnId?: string;
12
+ status?: CliTurnStatus;
13
+ }, maximumWidth?: number, screenReader?: boolean): ReactNode[];
@@ -0,0 +1,131 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { TUI_ACCENT_COLOR, TUI_TOOL_ERROR_TEXT_COLOR } from "../../design-tokens.js";
4
+ import { TUI_AGENT_LABEL } from "../constants.js";
5
+ import { formatTuiDuration, formatTuiInputConsumed, getTuiApprovalDecisionLabel, getTuiToolLabel, getTuiTerminationSummary, getTuiTurnFailureReason } from "../format.js";
6
+ import { TuiMarkdown } from "../markdown/markdown.js";
7
+ import { renderTuiToolTreeEvent } from "./tool-tree.js";
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";
12
+ /** 将安全事件聚合为可读的时间线行,连续 answer_delta 只显示为一个回答块。 */
13
+ export function renderTimeline(events, useColor, timing, maximumWidth = 80, screenReader = false) {
14
+ // 历史区在 Turn 终态只固化一次最终快照;读屏模式继续输出中文状态,不能退化为 glyph-only。
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); })) {
16
+ const nodes = renderTuiPresentationTimeline(timing.presentation, timing.turnId, useColor, maximumWidth, { expanded: true, screenReader }, events);
17
+ const failureReason = getTuiTurnFailureReason(events);
18
+ const terminationSummary = getTuiTerminationSummary(events, timing?.status);
19
+ if (terminationSummary)
20
+ nodes.push(_jsxs(Text, { children: ["\u7EC8\u6001\uFF1A", terminationSummary] }, "turn:termination"));
21
+ else if (failureReason)
22
+ nodes.push(_jsxs(Text, { children: ["\u539F\u56E0\uFF1A", failureReason] }, "turn:failure-reason"));
23
+ if (timing.totalDurationMs !== undefined)
24
+ nodes.push(_jsxs(Text, { dimColor: useColor, children: ["* Cogitated for ", formatTuiDuration(timing.totalDurationMs)] }, "turn:total-duration"));
25
+ return nodes;
26
+ }
27
+ const nodes = [];
28
+ let answer = "";
29
+ let answerSeq = 0;
30
+ let lastAnswerNodeIndex;
31
+ const submittedApprovalIds = new Set(events.flatMap(function readSubmittedApproval(event) {
32
+ return event.type === "tui_approval_submitted" ? [event.approvalId] : [];
33
+ }));
34
+ function flushAnswer() {
35
+ if (!answer)
36
+ return;
37
+ const isFirstAnswer = answerSeq === 0;
38
+ nodes.push(_jsxs(Box, { flexDirection: "column", children: [isFirstAnswer && timing?.thoughtDurationMs !== undefined ? _jsxs(Text, { dimColor: useColor, children: ["Thought for ", formatTuiDuration(timing.thoughtDurationMs)] }) : null, _jsxs(Text, { bold: true, color: useColor ? TUI_ACCENT_COLOR : undefined, children: ["\u2022 ", TUI_AGENT_LABEL] }), _jsx(TuiMarkdown, { content: answer, keyPrefix: "answer:" + String(answerSeq), useColor: useColor, maximumWidth: maximumWidth })] }, "answer:" + String(answerSeq++)));
39
+ lastAnswerNodeIndex = nodes.length - 1;
40
+ answer = "";
41
+ }
42
+ /** 收到 Core reset/discard 后移除已经固化的暂定回答,避免每个工具步骤都留下重复报告。 */
43
+ function discardAnswer() {
44
+ answer = "";
45
+ if (lastAnswerNodeIndex === undefined)
46
+ return;
47
+ nodes[lastAnswerNodeIndex] = undefined;
48
+ lastAnswerNodeIndex = undefined;
49
+ answerSeq = Math.max(0, answerSeq - 1);
50
+ }
51
+ for (const event of events) {
52
+ if (event.type === "answer_delta") {
53
+ answer += event.delta;
54
+ continue;
55
+ }
56
+ if (event.type === "answer_reset") {
57
+ discardAnswer();
58
+ continue;
59
+ }
60
+ flushAnswer();
61
+ if (event.type === "tui_input_consumed") {
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));
63
+ }
64
+ else if (event.type === "tui_approval_submitted") {
65
+ nodes.push(_jsxs(Text, { dimColor: useColor, children: ["Approval submitted \u00B7 ", getTuiApprovalDecisionLabel(event.decision)] }, String(event.seq) + ":approval:" + event.approvalId));
66
+ }
67
+ else if (event.type === "turn_started") {
68
+ continue;
69
+ }
70
+ else if (event.type === "reasoning_summary") {
71
+ continue;
72
+ }
73
+ else if (event.type === "reasoning_hidden") {
74
+ continue;
75
+ }
76
+ else if (event.type === "tool_started" || event.type === "tool_result") {
77
+ nodes.push(_jsx(Box, { children: renderTuiToolTreeEvent(event, useColor) }, String(event.seq)));
78
+ }
79
+ else if (event.type === "approval_required") {
80
+ if (submittedApprovalIds.has(event.approvalId))
81
+ continue;
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)));
83
+ }
84
+ else if (event.type === "input_queued") {
85
+ nodes.push(_jsx(Text, { dimColor: useColor, children: "Additional instruction queued; waiting for the current step" }, String(event.seq)));
86
+ }
87
+ else if (event.type === "todo_updated") {
88
+ // Todo 展示由共享 Presentation 的单卡负责,事件流本身不产生重复的静态行。
89
+ continue;
90
+ }
91
+ else if (event.type === "input_joined" || event.type === "input_consumed") {
92
+ continue;
93
+ }
94
+ else if (event.type === "model_recovery") {
95
+ nodes.push(_jsxs(Text, { dimColor: useColor, children: ["Recovering \u00B7 ", event.reason] }, String(event.seq)));
96
+ }
97
+ else if (event.type === "context_overflow") {
98
+ nodes.push(_jsx(Text, { children: "Context limit reached; organizing before continuing" }, String(event.seq)));
99
+ }
100
+ else if (event.type === "context_compact_started") {
101
+ nodes.push(_jsx(Text, { dimColor: useColor, children: "Organizing context" }, String(event.seq)));
102
+ }
103
+ else if (event.type === "context_compacted") {
104
+ nodes.push(_jsx(Text, { children: "Context organized" }, String(event.seq)));
105
+ }
106
+ else if (event.type === "context_compact_failed") {
107
+ nodes.push(_jsx(Text, { children: "Context organization failed" }, String(event.seq)));
108
+ }
109
+ else if (event.type === "context_compact_circuit_open") {
110
+ nodes.push(_jsx(Text, { children: "Context organization temporarily unavailable" }, String(event.seq)));
111
+ }
112
+ else if (event.type === "active_skills") {
113
+ continue;
114
+ }
115
+ else if (event.type === "status") {
116
+ continue;
117
+ }
118
+ }
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"));
128
+ if (timing?.totalDurationMs !== undefined)
129
+ nodes.push(_jsxs(Text, { dimColor: useColor, children: ["* Cogitated for ", formatTuiDuration(timing.totalDurationMs)] }, "turn:total-duration"));
130
+ return nodes.filter(function isVisibleTimelineNode(node) { return node !== undefined; });
131
+ }
@@ -0,0 +1,7 @@
1
+ /** 工具进度树节点:父节点是徽标 chip 与参数 chip,子节点是结果行与 unified diff。 */
2
+ import { type ReactNode } from "react";
3
+ import type { CliTuiTimelineEvent } from "../types.js";
4
+ /** 按进度树展示工具:徽标持久强调,参数 chip 与徽标同行并右端截断,结果行保留 └─ 连线与 ✓/✗ 文字标签。 */
5
+ export declare function renderTuiToolTreeEvent(event: Extract<CliTuiTimelineEvent, {
6
+ type: "tool_started" | "tool_result";
7
+ }>, useColor: boolean): ReactNode;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from "ink";
3
+ import { TUI_ACCENT_BG_COLOR, TUI_ACCENT_COLOR, TUI_CHIP_TEXT_COLOR, TUI_TOOL_ERROR_COLOR, TUI_TOOL_SUCCESS_COLOR } from "../../design-tokens.js";
4
+ import { TuiDiffStats } from "../diff-stats.js";
5
+ import { cleanTuiToolSummary, getTuiDiffStats, getTuiToolLabel, getTuiToolLineParts } from "../format.js";
6
+ import { TuiDiffBlock } from "../markdown/diff.js";
7
+ /** 渲染工具徽标 chip:强调背景 + 深色粗体在活动与完成态持久显示;NO_COLOR 退化为纯方括号文本。 */
8
+ function TuiToolBadge(props) {
9
+ if (!props.useColor)
10
+ return _jsx(Text, { children: props.badge });
11
+ return _jsx(Text, { backgroundColor: TUI_ACCENT_BG_COLOR, color: TUI_CHIP_TEXT_COLOR, bold: true, children: props.badge });
12
+ }
13
+ /** 按进度树展示工具:徽标持久强调,参数 chip 与徽标同行并右端截断,结果行保留 └─ 连线与 ✓/✗ 文字标签。 */
14
+ export function renderTuiToolTreeEvent(event, useColor) {
15
+ if (event.type === "tool_started") {
16
+ const detail = cleanTuiToolSummary(event.toolName, event.summary).trim();
17
+ return (_jsx(Box, { flexDirection: "column", children: _jsxs(Text, { wrap: "truncate-end", children: ["├─ ", _jsx(TuiToolBadge, { badge: `[${getTuiToolLabel(event.toolName)}]`, useColor: useColor }), detail ? _jsxs(_Fragment, { children: [" ", _jsxs(Text, { color: useColor ? TUI_ACCENT_COLOR : undefined, children: ["[", detail, "]"] })] }) : null] }) }));
18
+ }
19
+ const diffStats = getTuiDiffStats(event.diff ? { text: event.diff } : undefined);
20
+ const parts = getTuiToolLineParts(event.toolName, event.summary, event.status, event.errorCode);
21
+ if (event.status !== "success") {
22
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: useColor ? TUI_TOOL_ERROR_COLOR : undefined, children: `└─ ${parts.glyph ? `${parts.glyph} ` : ""}${parts.badge}${parts.rest}` }), event.diff ? _jsx(TuiDiffBlock, { diff: event.diff, keyPrefix: String(event.seq), useColor: useColor }) : null] }));
23
+ }
24
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["└─ ", _jsx(Text, { color: useColor ? TUI_TOOL_SUCCESS_COLOR : undefined, children: parts.glyph }), " ", _jsx(TuiToolBadge, { badge: parts.badge, useColor: useColor }), parts.rest, diffStats ? _jsxs(_Fragment, { children: [" ", _jsx(TuiDiffStats, { ...diffStats, useColor: useColor })] }) : null] }), event.diff ? _jsx(TuiDiffBlock, { diff: event.diff, keyPrefix: String(event.seq), useColor: useColor }) : null] }));
25
+ }
@@ -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
+ }
@@ -0,0 +1,19 @@
1
+ /** 渲染当前 Turn;它会随安全事件更新,完成后整体进入历史树。 */
2
+ import type { CliProjection, CliTurnStatus } from "../../types.js";
3
+ import type { AgentPresentationState } from "@autopilot-agent/presentation";
4
+ import type { CliTuiTimelineEvent, CliTuiTurnMetrics } from "../types.js";
5
+ /** 渲染当前 Turn;memo 保证输入光标更新不会触发 live timeline 组件执行。 */
6
+ export declare const TuiLiveTurn: import("react").NamedExoticComponent<{
7
+ turnId?: string;
8
+ events: CliTuiTimelineEvent[];
9
+ presentation?: AgentPresentationState;
10
+ pendingApproval?: CliProjection["pendingApproval"];
11
+ approvalSelectedIndex: number;
12
+ status: CliTurnStatus;
13
+ metrics?: CliTuiTurnMetrics;
14
+ useColor: boolean;
15
+ screenReader: boolean;
16
+ todoExpanded: boolean;
17
+ todoRowLimit?: number;
18
+ maximumWidth: number;
19
+ }>;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** 渲染当前 Turn;它会随安全事件更新,完成后整体进入历史树。 */
3
+ import { memo } from "react";
4
+ import { Box, Text } from "ink";
5
+ import { TuiApprovalPrompt } from "../approval.js";
6
+ import { formatTuiDuration, getTuiTerminationSummary, getTuiTurnFailureReason } from "../format.js";
7
+ import { TuiLiveTimeline } from "./live.js";
8
+ import { renderTuiPresentationTimeline } from "./presentation-timeline.js";
9
+ import { TuiTodoList } from "../todo-list.js";
10
+ import { getTuiTurnTodo } from "./turn-items.js";
11
+ /** 渲染当前 Turn;memo 保证输入光标更新不会触发 live timeline 组件执行。 */
12
+ export const TuiLiveTurn = memo(function TuiLiveTurn(props) {
13
+ if (!props.turnId || props.events.length === 0)
14
+ return null;
15
+ const hasPresentation = props.presentation?.items.some(function belongsToTurn(item) { return item.kind === "capability_notice" || ("turnId" in item && item.turnId === props.turnId); }) ?? false;
16
+ const failureReason = getTuiTurnFailureReason(props.events);
17
+ const terminationSummary = getTuiTerminationSummary(props.events, props.status);
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
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Agent Todo 任务链的 CLI 视图:紧凑/展开两种形态共用同一份结构化行数据,
3
+ * Ink 动态区与备用屏视口都从这里取行,避免两套展示实现。
4
+ */
5
+ import { type ReactNode } from "react";
6
+ import type { TodoItem } from "@autopilot-agent/core";
7
+ import { type AgentPresentationState, type TodoSnapshotItem } from "@autopilot-agent/presentation";
8
+ /** Todo 行的状态与安全文本;样式由 Ink 或视口 spans 分别映射,hidden 行表示被截断的隐藏计数。 */
9
+ export interface TuiTodoDisplayRow {
10
+ status: TodoItem["status"];
11
+ text: string;
12
+ hidden?: boolean;
13
+ }
14
+ /** Todo 视图的完整展示数据;紧凑态只有 header,展开态附任务行与隐藏计数。 */
15
+ export interface TuiTodoDisplay {
16
+ header: string;
17
+ activeForm?: string;
18
+ rows: TuiTodoDisplayRow[];
19
+ hidden: number;
20
+ }
21
+ /** 取得 Todo 状态的中文文字标签,供 screen reader 与 NO_COLOR 场景朗读。 */
22
+ export declare function getTuiTodoStatusLabel(status: TodoItem["status"]): string;
23
+ /** 从共享 Presentation 读取当前 Turn 任务链的 in_progress activeForm,作为活动状态的稳定文案。 */
24
+ export declare function getTuiTodoActiveForm(presentation: AgentPresentationState | undefined, turnId: string): string | undefined;
25
+ /** 取得 Todo 状态的纯文本 glyph,颜色不是唯一状态载体。 */
26
+ export declare function getTuiTodoStatusGlyph(status: TodoItem["status"]): string;
27
+ /** 按显示宽度做 grapheme 安全截断,中英文与组合 emoji 不会截出半字。 */
28
+ export declare function truncateTuiTodoText(text: string, maximumWidth: number): string;
29
+ /** 按优先级(in_progress → 最近完成 → pending)在行数上限内挑选可见任务,保持原清单顺序展示。 */
30
+ export declare function orderTuiTodoItems(todos: TodoItem[], limit?: number): {
31
+ shown: TodoItem[];
32
+ hidden: number;
33
+ };
34
+ /** 生成 Todo 视图的共享展示数据;紧凑态一行,展开态为 header + 任务行 + 隐藏计数。 */
35
+ export declare function getTuiTodoDisplay(item: TodoSnapshotItem, options: {
36
+ expanded: boolean;
37
+ screenReader: boolean;
38
+ columns: number;
39
+ rowLimit?: number;
40
+ }): TuiTodoDisplay | undefined;
41
+ /** 渲染 Todo 任务链的 Ink 视图;screen reader 时每行前缀中文状态文字。 */
42
+ export declare function TuiTodoList(props: {
43
+ item: TodoSnapshotItem;
44
+ expanded: boolean;
45
+ useColor: boolean;
46
+ screenReader: boolean;
47
+ columns: number;
48
+ rowLimit?: number;
49
+ showToggleHint?: boolean;
50
+ }): ReactNode;