@wukonglabs/wukong 0.0.19 → 0.0.21

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 (135) hide show
  1. package/dist/cli/help.js +1 -1
  2. package/dist/cli/session-commands.d.ts.map +1 -1
  3. package/dist/cli/session-commands.js +4 -3
  4. package/dist/cli/session-commands.js.map +1 -1
  5. package/dist/index.js +73 -12
  6. package/dist/index.js.map +1 -1
  7. package/dist/tui/activity-verb.d.ts +29 -0
  8. package/dist/tui/activity-verb.d.ts.map +1 -0
  9. package/dist/tui/activity-verb.js +81 -0
  10. package/dist/tui/activity-verb.js.map +1 -0
  11. package/dist/tui/app.d.ts +29 -28
  12. package/dist/tui/app.d.ts.map +1 -1
  13. package/dist/tui/app.js +259 -132
  14. package/dist/tui/app.js.map +1 -1
  15. package/dist/tui/diff-count.d.ts +14 -0
  16. package/dist/tui/diff-count.d.ts.map +1 -0
  17. package/dist/tui/diff-count.js +21 -0
  18. package/dist/tui/diff-count.js.map +1 -0
  19. package/dist/tui/hint-bar.d.ts +12 -0
  20. package/dist/tui/hint-bar.d.ts.map +1 -0
  21. package/dist/tui/hint-bar.js +14 -0
  22. package/dist/tui/hint-bar.js.map +1 -0
  23. package/dist/tui/history-entry.d.ts +11 -0
  24. package/dist/tui/history-entry.d.ts.map +1 -0
  25. package/dist/tui/history-entry.js +35 -0
  26. package/dist/tui/history-entry.js.map +1 -0
  27. package/dist/tui/history-item.d.ts +40 -0
  28. package/dist/tui/history-item.d.ts.map +1 -0
  29. package/dist/tui/history-item.js +8 -0
  30. package/dist/tui/history-item.js.map +1 -0
  31. package/dist/tui/keys-help.d.ts +5 -0
  32. package/dist/tui/keys-help.d.ts.map +1 -0
  33. package/dist/tui/keys-help.js +36 -0
  34. package/dist/tui/keys-help.js.map +1 -0
  35. package/dist/tui/live-block.d.ts +54 -0
  36. package/dist/tui/live-block.d.ts.map +1 -0
  37. package/dist/tui/live-block.js +72 -0
  38. package/dist/tui/live-block.js.map +1 -0
  39. package/dist/tui/main-area.d.ts +40 -0
  40. package/dist/tui/main-area.d.ts.map +1 -0
  41. package/dist/tui/main-area.js +9 -0
  42. package/dist/tui/main-area.js.map +1 -0
  43. package/dist/tui/markdown.d.ts +14 -0
  44. package/dist/tui/markdown.d.ts.map +1 -0
  45. package/dist/tui/markdown.js +134 -0
  46. package/dist/tui/markdown.js.map +1 -0
  47. package/dist/tui/notice-panel.d.ts +20 -0
  48. package/dist/tui/notice-panel.d.ts.map +1 -0
  49. package/dist/tui/notice-panel.js +19 -0
  50. package/dist/tui/notice-panel.js.map +1 -0
  51. package/dist/tui/response-panel.d.ts +12 -16
  52. package/dist/tui/response-panel.d.ts.map +1 -1
  53. package/dist/tui/response-panel.js +8 -11
  54. package/dist/tui/response-panel.js.map +1 -1
  55. package/dist/tui/run-ink.d.ts +12 -0
  56. package/dist/tui/run-ink.d.ts.map +1 -1
  57. package/dist/tui/run-ink.js +2 -1
  58. package/dist/tui/run-ink.js.map +1 -1
  59. package/dist/tui/slash/menu.d.ts +16 -0
  60. package/dist/tui/slash/menu.d.ts.map +1 -0
  61. package/dist/tui/slash/menu.js +11 -0
  62. package/dist/tui/slash/menu.js.map +1 -0
  63. package/dist/tui/slash/parse.d.ts +21 -0
  64. package/dist/tui/slash/parse.d.ts.map +1 -0
  65. package/dist/tui/slash/parse.js +32 -0
  66. package/dist/tui/slash/parse.js.map +1 -0
  67. package/dist/tui/slash/registry.d.ts +20 -0
  68. package/dist/tui/slash/registry.d.ts.map +1 -0
  69. package/dist/tui/slash/registry.js +28 -0
  70. package/dist/tui/slash/registry.js.map +1 -0
  71. package/dist/tui/spinner.d.ts +11 -0
  72. package/dist/tui/spinner.d.ts.map +1 -0
  73. package/dist/tui/spinner.js +21 -0
  74. package/dist/tui/spinner.js.map +1 -0
  75. package/dist/tui/status-bar.d.ts +42 -0
  76. package/dist/tui/status-bar.d.ts.map +1 -0
  77. package/dist/tui/status-bar.js +32 -0
  78. package/dist/tui/status-bar.js.map +1 -0
  79. package/dist/tui/tail-visual.d.ts +11 -0
  80. package/dist/tui/tail-visual.d.ts.map +1 -0
  81. package/dist/tui/tail-visual.js +30 -0
  82. package/dist/tui/tail-visual.js.map +1 -0
  83. package/dist/tui/text-input.d.ts +20 -22
  84. package/dist/tui/text-input.d.ts.map +1 -1
  85. package/dist/tui/text-input.js +211 -81
  86. package/dist/tui/text-input.js.map +1 -1
  87. package/dist/tui/theme/context.d.ts +16 -0
  88. package/dist/tui/theme/context.d.ts.map +1 -0
  89. package/dist/tui/theme/context.js +28 -0
  90. package/dist/tui/theme/context.js.map +1 -0
  91. package/dist/tui/theme/persist.d.ts +11 -0
  92. package/dist/tui/theme/persist.d.ts.map +1 -0
  93. package/dist/tui/theme/persist.js +41 -0
  94. package/dist/tui/theme/persist.js.map +1 -0
  95. package/dist/tui/theme/theme.d.ts +26 -0
  96. package/dist/tui/theme/theme.d.ts.map +1 -0
  97. package/dist/tui/theme/theme.js +51 -0
  98. package/dist/tui/theme/theme.js.map +1 -0
  99. package/dist/tui/thinking-panel.d.ts +11 -20
  100. package/dist/tui/thinking-panel.d.ts.map +1 -1
  101. package/dist/tui/thinking-panel.js +29 -7
  102. package/dist/tui/thinking-panel.js.map +1 -1
  103. package/dist/tui/tool-cards.d.ts +5 -17
  104. package/dist/tui/tool-cards.d.ts.map +1 -1
  105. package/dist/tui/tool-cards.js +4 -5
  106. package/dist/tui/tool-cards.js.map +1 -1
  107. package/dist/tui/tool-summary.d.ts +2 -0
  108. package/dist/tui/tool-summary.d.ts.map +1 -0
  109. package/dist/tui/tool-summary.js +93 -0
  110. package/dist/tui/tool-summary.js.map +1 -0
  111. package/dist/tui/use-elapsed.d.ts +2 -0
  112. package/dist/tui/use-elapsed.d.ts.map +1 -0
  113. package/dist/tui/use-elapsed.js +22 -0
  114. package/dist/tui/use-elapsed.js.map +1 -0
  115. package/dist/tui/use-plan-state.d.ts +20 -1
  116. package/dist/tui/use-plan-state.d.ts.map +1 -1
  117. package/dist/tui/use-plan-state.js +52 -46
  118. package/dist/tui/use-plan-state.js.map +1 -1
  119. package/dist/tui/use-terminal-width.d.ts +2 -0
  120. package/dist/tui/use-terminal-width.d.ts.map +1 -0
  121. package/dist/tui/use-terminal-width.js +23 -0
  122. package/dist/tui/use-terminal-width.js.map +1 -0
  123. package/dist/tui/use-viewport.d.ts +15 -0
  124. package/dist/tui/use-viewport.d.ts.map +1 -0
  125. package/dist/tui/use-viewport.js +37 -0
  126. package/dist/tui/use-viewport.js.map +1 -0
  127. package/dist/tui/welcome.d.ts +29 -0
  128. package/dist/tui/welcome.d.ts.map +1 -0
  129. package/dist/tui/welcome.js +41 -0
  130. package/dist/tui/welcome.js.map +1 -0
  131. package/package.json +3 -2
  132. package/dist/tui/header.d.ts +0 -24
  133. package/dist/tui/header.d.ts.map +0 -1
  134. package/dist/tui/header.js +0 -26
  135. package/dist/tui/header.js.map +0 -1
package/dist/tui/app.d.ts CHANGED
@@ -1,22 +1,21 @@
1
1
  /**
2
- * TUI 主壳 —— 组合 Header / PlanPanel / ThinkingPanel / ToolCards / ResponsePanel / TextInput。
2
+ * TUI 主壳 —— 组合 StatusBar / PlanPanel / ThinkingPanel / ToolCards / ResponsePanel /
3
+ * TextInput / HintBar / NoticePanel。
3
4
  *
4
- * ## 职责
5
- *
6
- * 1. 持有 TUI 状态(输入、流式文本、thinking、活跃 tool call、step、cache 命中率)
7
- * 2. 订阅 EventBus:tool_call_start/end、step_start、cache_stat、plan_update(PlanPanel 自行订阅)
8
- * 3. 接收 onStream 的 content_delta + thinking_delta(通过 streamSinkRef 桥接)
9
- * 4. 调用 sendMessage 跑 agent;完成后落 lastCompleted
10
- *
11
- * ## M2-M5 增强
12
- *
13
- * - M2:Header 加 step counter + cache 命中率
14
- * - M3:ToolCards 独立组件,>5 个折叠
15
- * - M4:PlanPanel 订阅 plan_update
16
- * - M5:ThinkingPanel 订阅 thinking_delta(通过 onStream 桥接)
5
+ * 本次改造(22-tui-revamp-plan.md)集成了:
6
+ * - M0:输入框锁死修复(abort 状态自动复位,见 ABORT_RESET_MS)
7
+ * - M1:Slash 命令菜单(TextInput 内部导航 + dispatchSlash)
8
+ * - M2:状态行 + Spinner + 实时计时(runStartAt + StatusBar)
9
+ * - M3:流式响应逐字回归(ResponsePanel 视觉 tail)
10
+ * - M4:Tool 卡片人话摘要(tool-cards + tool-summary)
11
+ * - M5:输入框圆角边框 + 独立 HintBar
12
+ * - M6:主题系统(ThemeProvider 在 run-ink 包 App,本文件用 useTheme)
13
+ * - M7:/think 把 thinking 推进 <Static>
14
+ * - M8:NoticePanel 独立承载错误
15
+ * - M9:? / /keys 键位帮助
17
16
  */
18
17
  import React from 'react';
19
- import { CompletedRun } from './response-panel.js';
18
+ import type { CompletedRun } from './response-panel.js';
20
19
  import type { EventBus, StreamEvent, ToolAuthorizer } from '@wukonglabs/wukong-agent-sdk';
21
20
  /** 流式 sink:App 在 sendMessage 前设置,runInteractive 包成 onStream 传给 agent */
22
21
  export type StreamSink = (event: StreamEvent) => void;
@@ -25,33 +24,35 @@ export interface SinkRef {
25
24
  current: StreamSink | null;
26
25
  }
27
26
  export interface AppHandlers {
28
- /** 发送消息给 agent;返回 run 的结果 */
29
27
  sendMessage: (input: string) => Promise<CompletedRun>;
30
- /** EventBus 订阅(用于 tool_call 等事件) */
31
28
  eventBus: EventBus;
32
- /** 清空当前 session 历史 */
33
29
  clearSession: () => Promise<void>;
34
- /** 最大 step 数(用于 Header 显示);可选 */
35
30
  maxSteps?: number;
36
- /**
37
- * 触发中断请求(§3.3 M3):TextInput 的 Ctrl+C 调用此方法。
38
- * 由 index.ts 注入 abortController.requestAbort,实现两次 Ctrl+C 语义。
39
- */
40
31
  requestAbort?: () => void;
41
- /** 查询当前是否已收到首次中断(用于 App 显示提示) */
42
32
  isAbortRequested?: () => boolean;
43
- /** 工具授权器(§3.6);交互模式注入,CI 模式不需要 */
44
33
  authorizer?: ToolAuthorizer;
34
+ /** M1:/compact 手动压缩(可选,未启用时命令返回提示) */
35
+ compactIfNeeded?: () => Promise<void>;
45
36
  }
46
37
  export interface AppProps {
47
38
  model: string;
48
39
  sessionUuidShort: string;
49
40
  workspaceRoot: string;
50
41
  handlers: AppHandlers;
51
- /** 初始历史(从持久化文件读取) */
52
42
  initialHistory?: string[];
53
- /** 由 index.ts 创建的 mutable ref;App 在每次 sendMessage 前写入 sink */
54
43
  streamSinkRef: SinkRef;
44
+ /** CLI 版本号,欢迎屏展示用 */
45
+ appVersion?: string;
46
+ /** 已解析到的配置文件路径(若有),欢迎屏展示用 */
47
+ configPath?: string;
48
+ /** 当前会话已有记录数 */
49
+ sessionRecordCount?: number;
50
+ /** 当前会话是否本次刚创建 */
51
+ sessionCreated?: boolean;
52
+ /** 已注册工具数量,欢迎屏展示用 */
53
+ toolsCount?: number;
54
+ /** 已加载且可用的技能数量,欢迎屏展示用 */
55
+ skillsLoaded?: number;
55
56
  }
56
- export declare function App({ model, sessionUuidShort, workspaceRoot, handlers, initialHistory, streamSinkRef, }: AppProps): React.ReactElement;
57
+ export declare function App({ model, sessionUuidShort, workspaceRoot, handlers, initialHistory, streamSinkRef, appVersion, configPath, sessionRecordCount, sessionCreated, toolsCount, skillsLoaded, }: AppProps): React.ReactElement;
57
58
  //# sourceMappingURL=app.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAGxE,OAAO,EAAgC,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAQjF,OAAO,KAAK,EAEV,QAAQ,EACR,WAAW,EAGX,cAAc,EACf,MAAM,8BAA8B,CAAC;AAEtC,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;AAEtD,2DAA2D;AAC3D,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;CAC5B;AAsDD,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACtD,oCAAoC;IACpC,QAAQ,EAAE,QAAQ,CAAC;IACnB,sBAAsB;IACtB,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,iCAAiC;IACjC,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC;IACjC,kCAAkC;IAClC,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,WAAW,CAAC;IACtB,qBAAqB;IACrB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,8DAA8D;IAC9D,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,wBAAgB,GAAG,CAAC,EAClB,KAAK,EACL,gBAAgB,EAChB,aAAa,EACb,QAAQ,EACR,cAAmB,EACnB,aAAa,GACd,EAAE,QAAQ,GAAG,KAAK,CAAC,YAAY,CAuR/B"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAGxE,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAgBvE,OAAO,KAAK,EAEV,QAAQ,EACR,WAAW,EAGX,cAAc,EACf,MAAM,8BAA8B,CAAC;AAEtC,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;AAEtD,2DAA2D;AAC3D,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;CAC5B;AAiBD,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACtD,QAAQ,EAAE,QAAQ,CAAC;IACnB,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC;IACjC,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,sCAAsC;IACtC,eAAe,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,WAAW,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;IACvB,qBAAqB;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB;IAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB;IAClB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,qBAAqB;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,GAAG,CAAC,EAClB,KAAK,EACL,gBAAgB,EAChB,aAAa,EACb,QAAQ,EACR,cAAmB,EACnB,aAAa,EACb,UAAU,EACV,UAAU,EACV,kBAAsB,EACtB,cAAsB,EACtB,UAAU,EACV,YAAY,GACb,EAAE,QAAQ,GAAG,KAAK,CAAC,YAAY,CA4b/B"}
package/dist/tui/app.js CHANGED
@@ -1,94 +1,91 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
- * TUI 主壳 —— 组合 Header / PlanPanel / ThinkingPanel / ToolCards / ResponsePanel / TextInput。
3
+ * TUI 主壳 —— 组合 StatusBar / PlanPanel / ThinkingPanel / ToolCards / ResponsePanel /
4
+ * TextInput / HintBar / NoticePanel。
4
5
  *
5
- * ## 职责
6
- *
7
- * 1. 持有 TUI 状态(输入、流式文本、thinking、活跃 tool call、step、cache 命中率)
8
- * 2. 订阅 EventBus:tool_call_start/end、step_start、cache_stat、plan_update(PlanPanel 自行订阅)
9
- * 3. 接收 onStream 的 content_delta + thinking_delta(通过 streamSinkRef 桥接)
10
- * 4. 调用 sendMessage 跑 agent;完成后落 lastCompleted
11
- *
12
- * ## M2-M5 增强
13
- *
14
- * - M2:Header 加 step counter + cache 命中率
15
- * - M3:ToolCards 独立组件,>5 个折叠
16
- * - M4:PlanPanel 订阅 plan_update
17
- * - M5:ThinkingPanel 订阅 thinking_delta(通过 onStream 桥接)
6
+ * 本次改造(22-tui-revamp-plan.md)集成了:
7
+ * - M0:输入框锁死修复(abort 状态自动复位,见 ABORT_RESET_MS)
8
+ * - M1:Slash 命令菜单(TextInput 内部导航 + dispatchSlash)
9
+ * - M2:状态行 + Spinner + 实时计时(runStartAt + StatusBar)
10
+ * - M3:流式响应逐字回归(ResponsePanel 视觉 tail)
11
+ * - M4:Tool 卡片人话摘要(tool-cards + tool-summary)
12
+ * - M5:输入框圆角边框 + 独立 HintBar
13
+ * - M6:主题系统(ThemeProvider 在 run-ink 包 App,本文件用 useTheme)
14
+ * - M7:/think 把 thinking 推进 <Static>
15
+ * - M8:NoticePanel 独立承载错误
16
+ * - M9:? / /keys 键位帮助
18
17
  */
19
18
  import { useState, useCallback, useEffect, useRef } from 'react';
20
- import { Box, Text, Static, useApp, useStdout } from 'ink';
21
- import { Header } from './header.js';
22
- import { ResponsePanel } from './response-panel.js';
23
- import { ToolCards } from './tool-cards.js';
24
- import { PlanPanel } from './plan-panel.js';
25
- import { ThinkingPanel } from './thinking-panel.js';
19
+ import { Box, Text, useApp, useStdout } from 'ink';
26
20
  import { TextInput } from './text-input.js';
27
21
  import { useInputHistory } from './use-input-history.js';
28
- import { usePlanState } from './use-plan-state.js';
29
22
  import { PermissionCard } from './permission-card.js';
30
- /** 取文本末尾 N 行(限制动态区流式文本高度,避免超过终端高度触发重绘故障) */
31
- function tailLines(text, maxLines) {
32
- const lines = text.split('\n');
33
- if (lines.length <= maxLines)
34
- return text;
35
- return lines.slice(lines.length - maxLines).join('\n');
36
- }
37
- /**
38
- * 动态区高度硬约束(总和 12 行)。
39
- *
40
- * 背景:Ink 的动态区通过"光标上移 N 行 + 清行 + 重画"实现就地更新;
41
- * 当动态区总高度超过终端可视高度时,"上移 N 行"会推到 scrollback 里,
42
- * 部分终端(如 vscode 内嵌终端)在 scrollback 区里执行"清行"会渲染出
43
- * 贯穿终端全宽的空线框,视觉上就是"满屏空白线"。
44
- *
45
- * 修复:把动态区的每个组件都压到极限低高度,总和 12 行,基本能容纳
46
- * 到任何终端(包括 vscode 20 行的小终端)的可视区内。
47
- *
48
- * 分配:
49
- * Header 1 行(去掉了 borderStyle)
50
- * PlanPanel 最多 4 行(1 摘要 + 3 todo)
51
- * Thinking 1 行(永远折叠为摘要)
52
- * ToolCards 最多 6 行(1 头 + 2 卡片 × 最多 2 行 + 1 折叠提示;done 卡压成 1 行)
53
- * Response 最多 3 行(streamingText tail)
54
- * TextInput 1-2
55
- * 快捷键提示 1 行
56
- * marginTop 1 行
57
- * ─────────
58
- * 合计 ≤ 19 行(vscode 内嵌终端最小约 20 行,勉强留余量)
59
- *
60
- * 注:tools/thinking 现在会保留到用户下一次提交才清空(而不是 run 结束就清),
61
- * 便于用户回看本轮"用了什么工具、思考了什么"。折叠靠 maxVisible 保证不会
62
- * 让 tools 数量爆炸击穿高度上限。
63
- */
64
- const RESPONSE_TAIL_LINES = 3;
65
- const PLAN_MAX_VISIBLE = 3;
66
- const TOOLS_MAX_VISIBLE = 2;
67
- export function App({ model, sessionUuidShort, workspaceRoot, handlers, initialHistory = [], streamSinkRef, }) {
23
+ import { HintBar } from './hint-bar.js';
24
+ import { NoticePanel } from './notice-panel.js';
25
+ import { useTheme } from './theme/context.js';
26
+ import { isThemeName } from './theme/theme.js';
27
+ import { KEYS_HELP } from './keys-help.js';
28
+ import { parseSlashCommand } from './slash/parse.js';
29
+ import { MainArea } from './main-area.js';
30
+ import { Welcome } from './welcome.js';
31
+ import { useViewport } from './use-viewport.js';
32
+ import { useElapsed } from './use-elapsed.js';
33
+ import { usePlanState } from './use-plan-state.js';
34
+ /** §5.0 M0:abort 窗口超时(ms),与 sigint-handler resetTimeoutMs 保持一致 */
35
+ const ABORT_RESET_MS = 3000;
36
+ const HELP_TEXT = `可用命令:
37
+ /help 本帮助
38
+ /status 显示当前配置(模型 / workspace / session)
39
+ /clear 清空对话历史
40
+ /model 显示当前模型
41
+ /session 显示 session 信息
42
+ /theme 切换主题(dark/light/mono)
43
+ /compact 手动触发压缩
44
+ /think 展开 Thinking 到滚动历史
45
+ /keys 键位帮助
46
+ 快捷键:Enter 发送 · Shift+Enter 换行 · Ctrl+R 反查 · Ctrl+C 中断 · ? 键位帮助 · / 命令菜单`;
47
+ export function App({ model, sessionUuidShort, workspaceRoot, handlers, initialHistory = [], streamSinkRef, appVersion, configPath, sessionRecordCount = 0, sessionCreated = false, toolsCount, skillsLoaded, }) {
68
48
  const { exit } = useApp();
69
49
  const { write } = useStdout();
50
+ const { themeName, setThemeName } = useTheme();
70
51
  const [input, setInput] = useState('');
71
52
  const [streamingText, setStreamingText] = useState('');
72
53
  const [streaming, setStreaming] = useState(false);
73
54
  const [thinkingText, setThinkingText] = useState('');
74
55
  const [thinkingStreaming, setThinkingStreaming] = useState(false);
75
- // 注:动态区高度硬约束下,thinking 永远折叠为 1 行摘要,不再支持 t 键展开。
76
- // 完整 thinking 内容在 stream 完成后可通过 session trace 查看。
56
+ // 跟踪 thinkingText 最新值,避免 useCallback stale closure(handleSubmit 不依赖 thinkingText)
57
+ const thinkingTextRef = useRef('');
58
+ // 每次 thinkingText 变化时同步 ref
59
+ thinkingTextRef.current = thinkingText;
77
60
  const [busy, setBusy] = useState(false);
78
61
  const [activeTools, setActiveTools] = useState([]);
62
+ // 跟踪 activeTools 最新值,catch 路径需要读取残留工具
63
+ const activeToolsRef = useRef([]);
64
+ activeToolsRef.current = activeTools;
79
65
  const [history, setHistory] = useState([]);
80
66
  const historyIdRef = useRef(0);
81
- const [error, setError] = useState(undefined);
67
+ const [welcomeSessionRecordCount, setWelcomeSessionRecordCount] = useState(sessionRecordCount);
68
+ const [notice, setNotice] = useState(null);
82
69
  const [lastDurationMs, setLastDurationMs] = useState(undefined);
83
- const [lastCompleted, setLastCompleted] = useState(undefined);
84
70
  const [currentStep, setCurrentStep] = useState(0);
85
71
  const [cacheHitRate, setCacheHitRate] = useState(undefined);
86
72
  const [abortRequested, setAbortRequested] = useState(false);
73
+ const [runStartAt, setRunStartAt] = useState(null);
87
74
  const [exiting, setExiting] = useState(false);
88
75
  const [pendingAuth, setPendingAuth] = useState(null);
76
+ const inputHistory = useInputHistory(initialHistory);
77
+ // 视口:固定 header + 弹性主区 + 固定 footer(24-tui-redesign-viewport.md)
78
+ const viewport = useViewport();
79
+ // 集中计算本轮耗时,供 StatusBar 和 LiveBlock 活动行共用(避免重复 setInterval)
80
+ const elapsedSec = useElapsed(busy ? runStartAt ?? null : null);
81
+ // 派生"等待 LLM 首 token"窗口 —— busy=true 但还没收到任何 chunk、也没工具调用。
82
+ // 仅有这一段时间主区会一片空白(无 thinking/streaming/tools),LiveBlock 必须显式
83
+ // 渲染等待行,否则用户感受是屏幕"卡"了 0.5-3s。
84
+ const awaitingLlm = busy && !thinkingStreaming && !streaming && activeTools.length === 0;
85
+ // 实时 Plan 树:订阅 eventBus 的 plan_update;resetSignal 用于 /clear 清空
86
+ const [planReset, setPlanReset] = useState(0);
87
+ const todos = usePlanState(handlers.eventBus, planReset);
89
88
  // §3.6:把 authorizer 的 uiCallback 桥接到 PermissionCard
90
- // 当 authorizer.requestAuthorization 调 uiCallback 时,设置 pendingAuth + 返回 promise
91
- // PermissionCard 的 onSubmit resolve promise,清 pendingAuth
92
89
  useEffect(() => {
93
90
  if (!handlers.authorizer)
94
91
  return;
@@ -98,7 +95,6 @@ export function App({ model, sessionUuidShort, workspaceRoot, handlers, initialH
98
95
  });
99
96
  });
100
97
  return () => {
101
- // 卸载时清掉 uiCallback(避免 stale)
102
98
  handlers.authorizer?.setUiCallback(async () => ({ approved: true, decision: 'once' }));
103
99
  };
104
100
  }, [handlers.authorizer]);
@@ -108,20 +104,26 @@ export function App({ model, sessionUuidShort, workspaceRoot, handlers, initialH
108
104
  return null;
109
105
  });
110
106
  }, []);
111
- const inputHistory = useInputHistory(initialHistory);
112
- const todos = usePlanState(handlers.eventBus);
113
- // §3.3 M3:Ctrl+C 处理(由 TextInput 触发)
114
- const handleAbort = useCallback(() => {
115
- handlers.requestAbort?.();
116
- // 标记状态以显示提示(首次中断后展示)
117
- if (handlers.isAbortRequested?.()) {
118
- setAbortRequested(true);
119
- }
120
- else {
121
- // requestAbort 可能已触发首次,下次再查
122
- setAbortRequested(true);
123
- }
124
- }, [handlers]);
107
+ // §5.0 M0:abort 窗口超时自动复位(备选方案,仅改 app.tsx)
108
+ useEffect(() => {
109
+ if (!abortRequested)
110
+ return;
111
+ const timer = setTimeout(() => setAbortRequested(false), ABORT_RESET_MS);
112
+ return () => clearTimeout(timer);
113
+ }, [abortRequested]);
114
+ // 通知自动消失(M8)
115
+ useEffect(() => {
116
+ if (!notice)
117
+ return;
118
+ const id = setTimeout(() => setNotice(null), 5000);
119
+ return () => clearTimeout(id);
120
+ }, [notice]);
121
+ const showNotice = useCallback((kind, text) => {
122
+ setNotice({ kind, text, timestamp: Date.now() });
123
+ }, []);
124
+ const appendHistory = useCallback((item) => {
125
+ setHistory((prev) => [...prev, { id: historyIdRef.current++, ...item }]);
126
+ }, []);
125
127
  // ── 订阅 EventBus ──
126
128
  useEffect(() => {
127
129
  const unsub = handlers.eventBus.on((event) => {
@@ -138,22 +140,33 @@ export function App({ model, sessionUuidShort, workspaceRoot, handlers, initialH
138
140
  name: event.data.name,
139
141
  step: event.data.step,
140
142
  startedAt: Date.now(),
143
+ argsJson: event.data.argumentsJson,
141
144
  };
142
145
  setActiveTools((prev) => [...prev, tool]);
143
146
  break;
144
147
  }
145
148
  case 'tool_call_end': {
146
- // 只更新为已完成状态,保留在列表中直到下一轮 run 开始时清空。
147
- // 这样用户能看到本轮 run 中触发过的所有工具(含耗时/结果预览),
148
- // 而不是"闪一下就没了"。清空点见 handleSubmit 里 setActiveTools([])
149
- setActiveTools((prev) => prev.map((t) => t.toolCallId === event.data.toolCallId
150
- ? {
151
- ...t,
152
- endedAt: Date.now(),
153
- success: event.data.success,
154
- resultPreview: event.data.resultPreview,
149
+ const { toolCallId, success, resultPreview } = event.data;
150
+ setActiveTools((prev) => {
151
+ const t = prev.find((x) => x.toolCallId === toolCallId);
152
+ if (t) {
153
+ // 用 queueMicrotask 把 append 推迟到本次 setState 之后,避免嵌套 setState 警告
154
+ queueMicrotask(() => appendHistory({
155
+ kind: 'tool',
156
+ text: '',
157
+ tool: {
158
+ name: t.name,
159
+ argsJson: t.argsJson,
160
+ resultPreview,
161
+ success,
162
+ durationMs: Date.now() - t.startedAt,
163
+ step: t.step,
164
+ },
165
+ }));
155
166
  }
156
- : t));
167
+ // 从动态区活跃列表移除(已固化进 Static)
168
+ return prev.filter((x) => x.toolCallId !== toolCallId);
169
+ });
157
170
  break;
158
171
  }
159
172
  default:
@@ -162,17 +175,86 @@ export function App({ model, sessionUuidShort, workspaceRoot, handlers, initialH
162
175
  });
163
176
  return unsub;
164
177
  }, [handlers.eventBus]);
178
+ // ── Slash 命令派发(M1)──
179
+ const dispatchSlash = useCallback(async (name, arg) => {
180
+ switch (name) {
181
+ case 'exit':
182
+ case 'quit':
183
+ setExiting(true);
184
+ write('\n👋 再见!\n');
185
+ exit();
186
+ return;
187
+ case 'clear':
188
+ await handlers.clearSession();
189
+ setHistory([]);
190
+ setPlanReset((n) => n + 1);
191
+ return;
192
+ case 'help':
193
+ appendHistory({ kind: 'help', text: HELP_TEXT });
194
+ return;
195
+ case 'model':
196
+ appendHistory({ kind: 'info', text: `当前模型:${model}` });
197
+ return;
198
+ case 'session':
199
+ appendHistory({ kind: 'info', text: `Session: ${sessionUuidShort}` });
200
+ return;
201
+ case 'status':
202
+ appendHistory({
203
+ kind: 'info',
204
+ text: `当前配置:\n model: ${model}\n workspace: ${workspaceRoot}\n session: ${sessionUuidShort}\n theme: ${themeName}`,
205
+ });
206
+ return;
207
+ case 'theme':
208
+ if (!arg) {
209
+ appendHistory({ kind: 'info', text: `当前主题:${themeName}` });
210
+ }
211
+ else if (isThemeName(arg)) {
212
+ setThemeName(arg);
213
+ appendHistory({ kind: 'info', text: `已切换主题:${arg}` });
214
+ }
215
+ else {
216
+ appendHistory({ kind: 'info', text: `未知主题:${arg}(可选 dark/light/mono)` });
217
+ }
218
+ return;
219
+ case 'compact':
220
+ if (handlers.compactIfNeeded) {
221
+ await handlers.compactIfNeeded();
222
+ appendHistory({ kind: 'info', text: '已手动触发压缩' });
223
+ }
224
+ else {
225
+ appendHistory({ kind: 'info', text: '压缩功能未启用' });
226
+ }
227
+ return;
228
+ case 'think':
229
+ if (thinkingText) {
230
+ appendHistory({ kind: 'thinking', text: thinkingText });
231
+ setThinkingText('');
232
+ }
233
+ else {
234
+ appendHistory({ kind: 'info', text: '当前没有 thinking 内容' });
235
+ }
236
+ return;
237
+ case 'keys':
238
+ appendHistory({ kind: 'keys-help', text: KEYS_HELP });
239
+ return;
240
+ default:
241
+ appendHistory({ kind: 'info', text: `未知命令: /${name}` });
242
+ }
243
+ }, [handlers, model, workspaceRoot, sessionUuidShort, themeName, setThemeName, thinkingText, write, exit, appendHistory]);
165
244
  // ── 提交输入 ──
166
245
  const handleSubmit = useCallback(async (value) => {
167
246
  if (exiting)
168
247
  return;
248
+ // M1:slash 命令优先于普通发送
249
+ const parsed = parseSlashCommand(value);
250
+ if (parsed.isSlash && parsed.name) {
251
+ await dispatchSlash(parsed.name, parsed.arg);
252
+ return;
253
+ }
169
254
  const trimmed = value.trim();
170
255
  if (!trimmed)
171
256
  return;
172
- // §3.3 M3:若已请求中断,不再接受新输入(等当前 run 退出)
173
- if (abortRequested)
174
- return;
175
- // 内置命令
257
+ // 内置命令(向后兼容,保留字面量)
176
258
  if (trimmed.toLowerCase() === 'exit' || trimmed.toLowerCase() === 'quit') {
177
259
  setExiting(true);
178
260
  write('\n👋 再见!\n');
@@ -182,30 +264,27 @@ export function App({ model, sessionUuidShort, workspaceRoot, handlers, initialH
182
264
  if (trimmed.toLowerCase() === 'clear') {
183
265
  await handlers.clearSession();
184
266
  setHistory([]);
267
+ setWelcomeSessionRecordCount(0);
185
268
  return;
186
269
  }
187
- // 把用户输入固化到滚动历史(Static),不参与后续重绘
188
- setHistory((prev) => [
189
- ...prev,
190
- { id: historyIdRef.current++, kind: 'user', text: trimmed },
191
- ]);
270
+ maybePinWelcome();
271
+ appendHistory({ kind: 'user', text: trimmed });
192
272
  setBusy(true);
193
- setError(undefined);
273
+ setNotice(null);
194
274
  setStreamingText('');
195
275
  setStreaming(false);
196
276
  setThinkingText('');
197
277
  setThinkingStreaming(false);
198
- // 新 run 开始时清空上一轮的 tools / 完成提示;这样上一轮的过程感会一直保留到用户
199
- // 下一次提交为止,而不是运行完立刻消失(见 tool_call_end 分支的注释)。
200
278
  setActiveTools([]);
201
- setLastCompleted(undefined);
202
279
  setCurrentStep(0);
203
280
  setCacheHitRate(undefined);
204
- // 设置流式 sink,让 index.ts 的 onStream 把 content_delta + thinking_delta 推进来
281
+ setRunStartAt(Date.now());
205
282
  streamSinkRef.current = (event) => {
206
283
  if (event.type === 'content_delta' && event.content) {
207
284
  setStreamingText((prev) => prev + event.content);
208
285
  setStreaming(true);
286
+ // 内容开始流式 → thinking 阶段已结束,LiveBlock 应隐藏 thinking 内容
287
+ setThinkingStreaming(false);
209
288
  }
210
289
  else if (event.type === 'thinking_delta' && event.thinking) {
211
290
  setThinkingText((prev) => prev + event.thinking);
@@ -218,51 +297,99 @@ export function App({ model, sessionUuidShort, workspaceRoot, handlers, initialH
218
297
  else if (event.type === 'error') {
219
298
  setStreaming(false);
220
299
  setThinkingStreaming(false);
221
- setError(event.error ?? '流式错误');
300
+ showNotice('error', event.error ?? '流式错误');
222
301
  }
223
302
  };
224
303
  try {
225
304
  const completed = await handlers.sendMessage(trimmed);
226
- // 固化助手响应到滚动历史(Static),清空动态区的流式/思考文本
227
- setHistory((prev) => [
228
- ...prev,
229
- {
230
- id: historyIdRef.current++,
231
- kind: 'assistant',
232
- text: completed.response,
233
- stats: {
234
- steps: completed.steps,
235
- totalTokens: completed.totalTokens,
236
- durationMs: completed.durationMs,
237
- },
305
+ // Thinking 不再自动固化进 Static:LiveBlock 在 thinkingStreaming=false
306
+ // 时已自动收起,避免滚动历史里塞一长串思考.
307
+ // 想保留时跑 /think 即可.这里只负责清空 thinkingText.
308
+ setThinkingText('');
309
+ appendHistory({
310
+ kind: 'assistant',
311
+ text: completed.response,
312
+ stats: {
313
+ steps: completed.steps,
314
+ totalTokens: completed.totalTokens,
315
+ durationMs: completed.durationMs,
238
316
  },
239
- ]);
317
+ });
240
318
  setLastDurationMs(completed.durationMs);
241
- setLastCompleted(completed);
242
319
  setStreamingText('');
243
320
  setStreaming(false);
244
- // 不清 thinkingText / activeTools / lastCompleted:让"本轮响应已完成 + thinking 摘要
245
- // + 触发过的工具卡片"保留到用户下一次提交(handleSubmit 顶部统一清空)。
246
- // 这样用户 run 结束后仍能看到明确的完成信号,而不是动态区突然清空、疑似渲染卡住。
247
321
  setThinkingStreaming(false);
248
322
  setCurrentStep(0);
249
323
  }
250
324
  catch (err) {
251
- setError(err instanceof Error ? err.message : String(err));
325
+ // 异常/中断时将残留的 thinking 和未固化工具也写入历史,避免丢失
326
+ const tt = thinkingTextRef.current;
327
+ if (tt) {
328
+ appendHistory({ kind: 'thinking', text: tt });
329
+ setThinkingText('');
330
+ }
331
+ const remaining = activeToolsRef.current;
332
+ if (remaining.length > 0) {
333
+ for (const t of remaining) {
334
+ appendHistory({
335
+ kind: 'tool',
336
+ text: '',
337
+ tool: {
338
+ name: t.name,
339
+ argsJson: t.argsJson,
340
+ resultPreview: undefined,
341
+ success: false,
342
+ durationMs: Date.now() - t.startedAt,
343
+ step: t.step,
344
+ },
345
+ });
346
+ }
347
+ setActiveTools([]);
348
+ }
349
+ showNotice('error', err instanceof Error ? err.message : String(err));
252
350
  }
253
351
  finally {
254
352
  streamSinkRef.current = null;
255
353
  setBusy(false);
354
+ setRunStartAt(null);
355
+ // §5.0 M0:run 结束清 abort 标志,输入框恢复可用
356
+ setAbortRequested(false);
256
357
  }
257
- }, [exiting, handlers, exit, write, streamSinkRef]);
258
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: history, children: (item) => _jsx(HistoryEntry, { item: item }, item.id) }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { model: model, sessionUuidShort: sessionUuidShort, workspaceRoot: workspaceRoot, busy: busy, lastDurationMs: lastDurationMs, currentStep: currentStep, maxSteps: handlers.maxSteps, cacheHitRate: cacheHitRate }), _jsx(PlanPanel, { todos: todos, maxVisible: PLAN_MAX_VISIBLE }), _jsx(ThinkingPanel, { thinking: thinkingText, streaming: thinkingStreaming, expanded: false }), _jsx(ToolCards, { tools: activeTools, maxVisible: TOOLS_MAX_VISIBLE }), _jsx(ResponsePanel, { streamingText: tailLines(streamingText, RESPONSE_TAIL_LINES), streaming: streaming, activeTools: [], lastCompleted: lastCompleted, error: error }), abortRequested && (_jsx(Box, { children: _jsx(Text, { color: "yellow", children: "\u26A0\uFE0F \u5DF2\u8BF7\u6C42\u4E2D\u65AD,\u7B49\u5F53\u524D\u6B65\u9AA4\u5B8C\u6210\u540E\u9000\u51FA;\u518D\u6309 Ctrl+C \u7ACB\u5373\u9000\u51FA" }) })), pendingAuth ? (_jsx(Box, { marginTop: 1, children: _jsx(PermissionCard, { request: pendingAuth.request, onSubmit: handleAuthSubmit }) })) : (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, multiline: true, history: inputHistory, placeholder: "\u8F93\u5165\u6D88\u606F(Enter \u53D1\u9001,Shift+Enter \u6362\u884C,Ctrl+R \u53CD\u67E5\u5386\u53F2,exit \u9000\u51FA)", disabled: busy || abortRequested, onAbort: handleAbort }), _jsx(Text, { dimColor: true, children: "[Enter: \u53D1\u9001] [Shift+Enter: \u6362\u884C] [Ctrl+C: \u4E2D\u65AD/\u9000\u51FA] [Ctrl+R: \u53CD\u67E5\u5386\u53F2] [Ctrl+L: \u6E05\u5C4F]" })] }))] })] }));
358
+ }, [exiting, handlers, exit, write, dispatchSlash, appendHistory, showNotice]);
359
+ // §5.0 M0:Ctrl+C 处理( TextInput 触发)
360
+ const handleAbort = useCallback(() => {
361
+ handlers.requestAbort?.();
362
+ setAbortRequested(true);
363
+ }, [handlers]);
364
+ const handleExit = useCallback(() => {
365
+ setExiting(true);
366
+ write('\n👋 再见!\n');
367
+ exit();
368
+ }, [write, exit]);
369
+ const placeholder = abortRequested
370
+ ? '正在中断当前任务,请等它退出…'
371
+ : '输入消息或 / 打开命令菜单…';
372
+ const welcomeCard = (_jsx(Welcome, { appVersion: appVersion, cwd: workspaceRoot, configPath: configPath, model: model, sessionUuidShort: sessionUuidShort, sessionRecordCount: welcomeSessionRecordCount, sessionCreated: sessionCreated, toolsCount: toolsCount, skillsLoaded: skillsLoaded }));
373
+ /** 首条用户消息发出时,将欢迎卡作为历史首项固化进 Static */
374
+ const maybePinWelcome = useCallback(() => {
375
+ setHistory((prev) => {
376
+ if (prev.length > 0)
377
+ return prev; // 已有历史(欢迎已钉过或 /clear 后重建)
378
+ const welcomeItem = {
379
+ id: historyIdRef.current++,
380
+ kind: 'welcome',
381
+ text: '',
382
+ welcomeEl: (_jsx(Welcome, { appVersion: appVersion, cwd: workspaceRoot, configPath: configPath, model: model, sessionUuidShort: sessionUuidShort, sessionRecordCount: welcomeSessionRecordCount, sessionCreated: sessionCreated, toolsCount: toolsCount, skillsLoaded: skillsLoaded })),
383
+ };
384
+ return [welcomeItem];
385
+ });
386
+ }, [appVersion, workspaceRoot, configPath, model, sessionUuidShort, welcomeSessionRecordCount, sessionCreated, toolsCount, skillsLoaded]);
387
+ return (_jsxs(Box, { flexDirection: "column", children: [history.length === 0 && !busy ? (welcomeCard) : (_jsx(MainArea, { history: history, todos: todos, thinkingText: thinkingText, thinkingStreaming: thinkingStreaming, activeTools: activeTools, streamingText: streamingText, streaming: streaming, awaitingLlm: awaitingLlm, mainHeight: viewport.mainHeight, cols: viewport.cols, elapsedSec: elapsedSec, currentStep: currentStep, maxSteps: handlers.maxSteps })), abortRequested && _jsx(AbortHint, {}), notice && _jsx(NoticePanel, { notice: notice }), pendingAuth ? (_jsx(Box, { marginTop: 1, children: _jsx(PermissionCard, { request: pendingAuth.request, onSubmit: handleAuthSubmit }) })) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, multiline: true, history: inputHistory, placeholder: placeholder, disabled: busy, onAbort: handleAbort, onExit: handleExit, onSlash: (n, a) => {
388
+ void dispatchSlash(n, a);
389
+ } }) }), _jsx(HintBar, { mode: busy ? 'busy' : 'normal' })] }))] }));
259
390
  }
260
- /** 渲染一条已固化的对话记录(用于 Static) */
261
- function HistoryEntry({ item }) {
262
- if (item.kind === 'user') {
263
- return (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: '> ' }), _jsx(Text, { children: item.text })] }));
264
- }
265
- const s = item.stats;
266
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: "Wukong:" }), _jsx(Text, { children: item.text }), s && (_jsxs(Text, { dimColor: true, children: [' [步骤: ', s.steps, ' | Token: ', s.totalTokens.prompt + s.totalTokens.completion, ' (prompt=', s.totalTokens.prompt, ', completion=', s.totalTokens.completion, ')', ' | 耗时: ', (s.durationMs / 1000).toFixed(1), "s", ']'] }))] }));
391
+ /** 中断提示(24-tui-redesign-viewport.md §4.9):拆成两行,窄终端不折行错乱 */
392
+ function AbortHint() {
393
+ return (_jsxs(Box, { flexDirection: "column", marginY: 0, children: [_jsx(Text, { color: "yellow", children: "\u26A0\uFE0F \u5DF2\u8BF7\u6C42\u4E2D\u65AD \u00B7 \u5F53\u524D step \u7ED3\u675F\u540E\u505C\u6B62" }), _jsx(Text, { color: "yellow", dimColor: true, children: " \u7ACB\u5373\u5F3A\u9000:\u518D\u6309\u4E00\u6B21 Ctrl+C" })] }));
267
394
  }
268
395
  //# sourceMappingURL=app.js.map