@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,237 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /** TUI 入口编排:启动 Ink,统一处理信号、退出码和动态渲染生命周期。 */
3
+ import { render } from "ink";
4
+ import { createTuiInputRouter } from "../tui-input-router.js";
5
+ import { CliTuiApp } from "./app.js";
6
+ import { getTuiStatusLabel, isActiveTuiStatus, isTerminalStatus, readTuiTerminalSize } from "./format.js";
7
+ import { createRoutedTuiStdin, createTuiMouseInputSink, createTuiRenderStdout, createTuiTerminalLifecycle } from "./lifecycle.js";
8
+ import { getTuiRenderModeFallbackNotice, resolveNoColor, resolveReducedMotion, resolveScreenReader, resolveTuiRenderMode } from "./render-mode.js";
9
+ import { createCliTuiStore } from "./store.js";
10
+ /** 将已知的安全会话字段格式化为主屏 recap;输出保持纯文本,兼容 NO_COLOR。 */
11
+ export function formatCliTuiRecap(recap) {
12
+ const canResume = recap.recoverable
13
+ && (recap.finalStatus === "waiting_approval" || recap.finalStatus === "interrupted_recoverable" || recap.finalStatus === "limit_reached");
14
+ const resumeHint = canResume ? ` · Resume: ap resume --session ${recap.sessionId}` : "";
15
+ return `Autopilot recap · Session: ${recap.sessionId} · Completed turns: ${recap.completedTurnCount} · Final status: ${getTuiStatusLabel(recap.finalStatus)}${resumeHint}\n`;
16
+ }
17
+ /** 启动 Ink,统一处理信号、退出码和动态渲染生命周期。 */
18
+ export async function runCliTui(options) {
19
+ const env = options.env ?? process.env;
20
+ const store = createCliTuiStore();
21
+ const noColor = resolveNoColor(env);
22
+ const screenReader = resolveScreenReader(env);
23
+ const renderMode = resolveTuiRenderMode(env);
24
+ const terminalSizeSource = options.streams.stdout;
25
+ const renderStdout = createTuiRenderStdout(terminalSizeSource, readTuiTerminalSize(terminalSizeSource));
26
+ const fallbackNotice = getTuiRenderModeFallbackNotice({ env, platform: process.platform });
27
+ if (fallbackNotice)
28
+ options.streams.stderr.write(fallbackNotice);
29
+ const terminalLifecycle = createTuiTerminalLifecycle(options.streams.stdout, renderMode);
30
+ const mouseInput = createTuiMouseInputSink();
31
+ const routedStdin = createRoutedTuiStdin(options.streams.stdin);
32
+ const inputRouter = createTuiInputRouter();
33
+ const reducedMotion = resolveReducedMotion(env);
34
+ // Ink 的 auto 预检没有覆盖 Windows Terminal;WT 1.24 支持 Kitty keyboard protocol,必须主动启用才能区分 Shift+Enter。
35
+ const kittyKeyboardMode = env.WT_SESSION ? "enabled" : "auto";
36
+ let requestedExitCode;
37
+ let lastResult;
38
+ let instance;
39
+ let lastInterruptAt = 0;
40
+ let pendingWheelNotches = 0;
41
+ let pendingWheelEvent;
42
+ let mouseFlushTimer;
43
+ /** 把同一 16ms 窗口内的滚轮 notch 合并为一次视口状态更新。 */
44
+ function flushMouseWheel() {
45
+ mouseFlushTimer = undefined;
46
+ const event = pendingWheelEvent;
47
+ const wheelNotches = Math.abs(pendingWheelNotches);
48
+ pendingWheelEvent = undefined;
49
+ pendingWheelNotches = 0;
50
+ if (event && wheelNotches > 0)
51
+ mouseInput.dispatch(event, wheelNotches);
52
+ }
53
+ /** 记录无法解析的 SGR 鼠标输入,而不把控制字节泄漏给 Ink prompt。 */
54
+ function recordMouseDiagnostics(diagnostics) {
55
+ for (const diagnostic of diagnostics)
56
+ store.addNotice(`terminal mouse input ignored (${diagnostic})`);
57
+ }
58
+ /** 单点消费真实 stdin:鼠标交给视口,其余字节按原顺序交给 Ink。 */
59
+ function routeRawTuiInput(chunk) {
60
+ const routed = inputRouter.push(String(chunk));
61
+ recordMouseDiagnostics(routed.diagnostics);
62
+ if (routed.passthrough.includes("\u001b[I"))
63
+ terminalLifecycle.reassert();
64
+ if (routed.passthrough)
65
+ routedStdin.write(routed.passthrough);
66
+ for (const event of routed.mouseEvents) {
67
+ if (event.kind !== "wheel_up" && event.kind !== "wheel_down") {
68
+ mouseInput.dispatch(event);
69
+ continue;
70
+ }
71
+ const delta = event.kind === "wheel_up" ? 1 : -1;
72
+ if (pendingWheelNotches !== 0 && Math.sign(pendingWheelNotches) !== delta)
73
+ flushMouseWheel();
74
+ pendingWheelNotches += delta;
75
+ pendingWheelEvent = event;
76
+ if (!mouseFlushTimer)
77
+ mouseFlushTimer = setTimeout(flushMouseWheel, 16);
78
+ }
79
+ }
80
+ /** 输入结束时丢弃残缺 SGR 前缀并关闭 Ink 专用输入流。 */
81
+ function finishRawTuiInput() {
82
+ recordMouseDiagnostics(inputRouter.finish().diagnostics);
83
+ flushMouseWheel();
84
+ routedStdin.end();
85
+ }
86
+ options.streams.stdin.on("data", routeRawTuiInput);
87
+ options.streams.stdin.once("end", finishRawTuiInput);
88
+ function finish(code) {
89
+ requestedExitCode = requestedExitCode ?? code;
90
+ instance?.unmount();
91
+ }
92
+ /** 在 TUI 终端恢复后尽力输出统一安全摘要;输出失败不能掩盖原本的退出或异常。 */
93
+ function writeTuiRecap() {
94
+ if (!options.sessionId)
95
+ return;
96
+ const snapshot = store.getSnapshot();
97
+ const completedTurnCount = snapshot.transcript.filter(function countCompletedTurn(item) {
98
+ return item.kind === "turn" && item.status === "completed";
99
+ }).length;
100
+ try {
101
+ options.streams.stdout.write(`\n${formatCliTuiRecap({
102
+ sessionId: options.sessionId,
103
+ completedTurnCount,
104
+ finalStatus: snapshot.status,
105
+ recoverable: snapshot.recoverable,
106
+ })}`);
107
+ }
108
+ catch {
109
+ // 终端已断开时只保证先完成 restore,recap 为尽力输出。
110
+ }
111
+ }
112
+ function handleResult(result) {
113
+ lastResult = result;
114
+ store.syncProjection(result.projection);
115
+ // 专用视图结果由 app 层打开覆盖视图;禁止复用 notice 路径把报告行截成零散 info。
116
+ if (result.skillOptions || result.permissionOptions || result.modelOptions || result.effortOptions || result.jobView || result.contextView)
117
+ return;
118
+ const lines = result.lines.filter(function keepNotice(line) {
119
+ const trimmed = line.trim();
120
+ // queue 的投递/启动由 pending prompt 和新 Turn 时间线表达,不再重复显示为两行 info。
121
+ return trimmed.length > 0 && !trimmed.startsWith("input queued:") && !trimmed.startsWith("queued input started:");
122
+ });
123
+ for (const line of lines)
124
+ store.addNotice(line);
125
+ if (lines.length === 0 && result.message)
126
+ store.addNotice(result.message);
127
+ }
128
+ /** 向 Runtime 请求当前活动 Turn 取消,但保留 Ink TUI 等待终态并回到 prompt。 */
129
+ function requestCancellation() {
130
+ const current = store.getSnapshot();
131
+ if (!isActiveTuiStatus(current.status) || current.cancellationRequested)
132
+ return;
133
+ store.setCancellationRequested(true);
134
+ void options.controller.dispatch({ type: "cancel_turn" }).then(handleResult).catch(function handleCancelFailure() {
135
+ store.setCancellationRequested(false);
136
+ store.addNotice("turn cancellation failed");
137
+ });
138
+ }
139
+ /** 处理 TUI 内 Ctrl+C:首次取消当前 Turn,取消收口前第二次强制退出。 */
140
+ function requestInterrupt() {
141
+ const now = Date.now();
142
+ const current = store.getSnapshot();
143
+ if (isActiveTuiStatus(current.status)) {
144
+ if (current.cancellationRequested) {
145
+ if (now - lastInterruptAt < 100)
146
+ return;
147
+ options.streams.stderr.write("forced exit before cancellation completed; recovery status unknown\n");
148
+ finish(130);
149
+ terminalLifecycle.restore();
150
+ options.forceExit?.(130);
151
+ return;
152
+ }
153
+ lastInterruptAt = now;
154
+ requestCancellation();
155
+ return;
156
+ }
157
+ finish(130);
158
+ }
159
+ /** 处理 Esc:只取消活动 Turn,不把 Esc 解释为退出进程。 */
160
+ function requestCancel() {
161
+ requestCancellation();
162
+ }
163
+ /** 处理宿主 SIGINT/SIGTERM:取消后以相应进程退出码收口。 */
164
+ function requestSignalStop(code) {
165
+ requestedExitCode = requestedExitCode ?? code;
166
+ store.requestExit(code);
167
+ if (isActiveTuiStatus(store.getSnapshot().status)) {
168
+ requestCancellation();
169
+ return;
170
+ }
171
+ finish(code);
172
+ }
173
+ function handleEof() {
174
+ requestedExitCode = requestedExitCode ?? 0;
175
+ store.requestExit(0);
176
+ const current = store.getSnapshot();
177
+ if (current.status === "running" || current.status === "waiting_approval") {
178
+ void options.controller.dispatch({ type: "cancel_turn" }).then(handleResult).catch(function ignoreEofCancelFailure() {
179
+ finish(0);
180
+ });
181
+ return;
182
+ }
183
+ finish(0);
184
+ }
185
+ function handleSigint() { requestInterrupt(); }
186
+ function handleSigterm() { requestSignalStop(143); }
187
+ options.signalSource.on("SIGINT", handleSigint);
188
+ options.signalSource.on("SIGTERM", handleSigterm);
189
+ const listener = function receiveEvent(event, projection) {
190
+ store.acceptEvent(event, projection);
191
+ if (event.type === "status" && isTerminalStatus(event.status) && requestedExitCode !== undefined)
192
+ finish(requestedExitCode);
193
+ };
194
+ options.controller.setEventListener?.(listener);
195
+ options.controller.setQueuedInputListener?.(function receiveQueuedInputStarted(input) {
196
+ store.markQueuedInputStarted(input);
197
+ });
198
+ const node = (_jsx(CliTuiApp, { store: store, controller: options.controller, initial: options.initial, closeWhenIdle: options.closeWhenIdle, workspaceRoot: options.workspaceRoot, useColor: !noColor && !screenReader, screenReader: screenReader, renderMode: renderMode, reducedMotion: reducedMotion, mouseInput: mouseInput, onResult: handleResult, onRequestInterrupt: requestInterrupt, onRequestCancel: requestCancel, onEof: handleEof, onAppExit: finish, terminalSizeSource: terminalSizeSource, onTerminalSizeChange: renderStdout.setSize }));
199
+ try {
200
+ terminalLifecycle.enter();
201
+ instance = render(node, {
202
+ stdout: renderStdout.stdout,
203
+ stderr: options.streams.stderr,
204
+ stdin: routedStdin,
205
+ exitOnCtrlC: false,
206
+ patchConsole: true,
207
+ // 输入框和动态时间线会改变行高;Windows Terminal 下增量渲染会残留旧帧,
208
+ // 表现为逐字符重复或内容重叠。使用完整帧保证当前交互区域正确。
209
+ incrementalRendering: false,
210
+ isScreenReaderEnabled: screenReader,
211
+ kittyKeyboard: { mode: kittyKeyboardMode },
212
+ });
213
+ // CC 在 AlternateScreen 挂载后再次声明鼠标模式;Ink 初始化完成后重申一次,避免真实终端只保留键盘模式。
214
+ terminalLifecycle.reassert();
215
+ await instance.waitUntilExit();
216
+ }
217
+ finally {
218
+ if (mouseFlushTimer)
219
+ clearTimeout(mouseFlushTimer);
220
+ options.streams.stdin.removeListener("data", routeRawTuiInput);
221
+ options.streams.stdin.removeListener("end", finishRawTuiInput);
222
+ mouseInput.setHandler(undefined);
223
+ routedStdin.end();
224
+ terminalLifecycle.restore();
225
+ writeTuiRecap();
226
+ options.signalSource.removeListener("SIGINT", handleSigint);
227
+ options.signalSource.removeListener("SIGTERM", handleSigterm);
228
+ }
229
+ const projection = options.controller.getProjection();
230
+ const result = lastResult ?? {
231
+ kind: "exit",
232
+ exitCode: requestedExitCode ?? 0,
233
+ lines: [],
234
+ projection,
235
+ };
236
+ return { result, exitCode: requestedExitCode ?? result.exitCode };
237
+ }
@@ -0,0 +1,16 @@
1
+ /** 状态栏与信息行:消费安全状态视图,渲染状态、模型、权限、context 与 workspace。 */
2
+ import type { CliStatusView } from "../types.js";
3
+ import type { CliTuiSnapshot } from "./types.js";
4
+ /** 渲染当前状态栏;配置行只消费安全状态视图,不读取 Runtime 或 checkpoint。 */
5
+ export declare const TuiStatusLine: import("react").NamedExoticComponent<{
6
+ snapshot: CliTuiSnapshot;
7
+ statusView?: CliStatusView;
8
+ useColor: boolean;
9
+ screenReader: boolean;
10
+ selectionCharacters?: number;
11
+ }>;
12
+ /** 将最近的控制台信息固定在输入框上方,避免混入会话时间线或占用回答区域。 */
13
+ export declare const TuiInfoLines: import("react").NamedExoticComponent<{
14
+ messages: string[];
15
+ useColor: boolean;
16
+ }>;
@@ -0,0 +1,37 @@
1
+ import { jsxs as _jsxs, Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ /** 状态栏与信息行:消费安全状态视图,渲染状态、模型、权限、context 与 workspace。 */
3
+ import { memo } from "react";
4
+ import { Box, Text } from "ink";
5
+ import { formatTuiContextStatus } from "../context-report.js";
6
+ import { CLI_TUI_COLORS, TUI_ACCENT_COLOR } from "../design-tokens.js";
7
+ import { getTuiPermissionModeLabel, getTuiStatusLabel } from "./format.js";
8
+ /** 渲染当前状态栏;配置行只消费安全状态视图,不读取 Runtime 或 checkpoint。 */
9
+ export const TuiStatusLine = memo(function TuiStatusLine(props) {
10
+ const suffix = `${props.screenReader ? " · screen reader" : ""}${props.selectionCharacters ? ` · ${props.selectionCharacters} chars selected` : ""}`;
11
+ const runningHint = props.snapshot.status === "running" ? " · Esc interrupt · Enter 引导 · /queue <prompt> 排队" : "";
12
+ let statusLabel = getTuiStatusLabel(props.snapshot.status);
13
+ if (props.snapshot.cancellationRequested)
14
+ statusLabel = "Stopping · press Ctrl+C again to exit";
15
+ const statusColor = props.snapshot.status === "idle" ? undefined : props.useColor ? TUI_ACCENT_COLOR : undefined;
16
+ const view = props.statusView;
17
+ const nextPermissionLabel = view?.nextPermissionMode;
18
+ const permissionLabel = view ? getTuiPermissionModeLabel(view.permissionMode) : undefined;
19
+ const nextPermissionModeLabel = nextPermissionLabel ? getTuiPermissionModeLabel(nextPermissionLabel) : undefined;
20
+ if (!view) {
21
+ const hint = props.snapshot.status === "idle" ? " · /exit to quit" : runningHint;
22
+ return _jsxs(Text, { bold: props.snapshot.status === "waiting_approval", color: statusColor, children: [statusLabel, suffix, hint] });
23
+ }
24
+ const permissionColor = props.useColor && view.permissionMode === "full_access" ? CLI_TUI_COLORS.permissionFullAccess : undefined;
25
+ const nextPermissionColor = props.useColor && nextPermissionLabel === "full_access" ? CLI_TUI_COLORS.permissionFullAccess : undefined;
26
+ // 无数据/无 Session 统一显示 unknown,不猜测窗口或输出伪 0%。
27
+ const contextLabel = formatTuiContextStatus(view.contextUsage ?? null);
28
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { bold: props.snapshot.status === "waiting_approval", color: statusColor, children: [statusLabel, suffix, runningHint] }), _jsxs(Text, { dimColor: props.useColor, children: ["model: ", view.model, " \u00B7 effort: ", view.effort, " \u00B7 ", _jsxs(Text, { color: permissionColor, bold: true, dimColor: false, children: ["permission: ", permissionLabel, " (", view.permissionMode, ")"] }), view.nextModel ? _jsxs(_Fragment, { children: [" \u00B7 next model: ", view.nextModel] }) : null, view.nextEffort ? _jsxs(_Fragment, { children: [" \u00B7 next effort: ", view.nextEffort] }) : null, nextPermissionLabel ? _jsxs(_Fragment, { children: [" \u00B7 next permission: ", _jsxs(Text, { color: nextPermissionColor, bold: true, dimColor: false, children: [nextPermissionModeLabel, " (", nextPermissionLabel, ")"] })] }) : null] }), _jsxs(Text, { dimColor: props.useColor, children: ["context: ", contextLabel] }), _jsxs(Text, { dimColor: props.useColor, children: ["workspace: ", view.workspaceRoot ?? "unavailable"] })] }));
29
+ });
30
+ /** 将最近的控制台信息固定在输入框上方,避免混入会话时间线或占用回答区域。 */
31
+ export const TuiInfoLines = memo(function TuiInfoLines(props) {
32
+ if (props.messages.length === 0)
33
+ return null;
34
+ return (_jsx(Box, { flexDirection: "column", marginTop: 1, children: props.messages.map(function renderInfo(message, index) {
35
+ return _jsxs(Text, { color: props.useColor ? CLI_TUI_COLORS.copilotMuted : undefined, children: ["info: ", message] }, `${index}:${message}`);
36
+ }) }));
37
+ });
@@ -0,0 +1,4 @@
1
+ /** TUI 外部 Store:把安全事件归约为 React 可订阅快照,不读取 Core Store 或 checkpoint。 */
2
+ import type { CliTuiStore } from "./types.js";
3
+ /** 创建 Ink TUI 的外部 Store。 */
4
+ export declare function createCliTuiStore(): CliTuiStore;