@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.
- package/dist/cli/help.js +1 -1
- package/dist/cli/session-commands.d.ts.map +1 -1
- package/dist/cli/session-commands.js +4 -3
- package/dist/cli/session-commands.js.map +1 -1
- package/dist/index.js +73 -12
- package/dist/index.js.map +1 -1
- package/dist/tui/activity-verb.d.ts +29 -0
- package/dist/tui/activity-verb.d.ts.map +1 -0
- package/dist/tui/activity-verb.js +81 -0
- package/dist/tui/activity-verb.js.map +1 -0
- package/dist/tui/app.d.ts +29 -28
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +259 -132
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/diff-count.d.ts +14 -0
- package/dist/tui/diff-count.d.ts.map +1 -0
- package/dist/tui/diff-count.js +21 -0
- package/dist/tui/diff-count.js.map +1 -0
- package/dist/tui/hint-bar.d.ts +12 -0
- package/dist/tui/hint-bar.d.ts.map +1 -0
- package/dist/tui/hint-bar.js +14 -0
- package/dist/tui/hint-bar.js.map +1 -0
- package/dist/tui/history-entry.d.ts +11 -0
- package/dist/tui/history-entry.d.ts.map +1 -0
- package/dist/tui/history-entry.js +35 -0
- package/dist/tui/history-entry.js.map +1 -0
- package/dist/tui/history-item.d.ts +40 -0
- package/dist/tui/history-item.d.ts.map +1 -0
- package/dist/tui/history-item.js +8 -0
- package/dist/tui/history-item.js.map +1 -0
- package/dist/tui/keys-help.d.ts +5 -0
- package/dist/tui/keys-help.d.ts.map +1 -0
- package/dist/tui/keys-help.js +36 -0
- package/dist/tui/keys-help.js.map +1 -0
- package/dist/tui/live-block.d.ts +54 -0
- package/dist/tui/live-block.d.ts.map +1 -0
- package/dist/tui/live-block.js +72 -0
- package/dist/tui/live-block.js.map +1 -0
- package/dist/tui/main-area.d.ts +40 -0
- package/dist/tui/main-area.d.ts.map +1 -0
- package/dist/tui/main-area.js +9 -0
- package/dist/tui/main-area.js.map +1 -0
- package/dist/tui/markdown.d.ts +14 -0
- package/dist/tui/markdown.d.ts.map +1 -0
- package/dist/tui/markdown.js +134 -0
- package/dist/tui/markdown.js.map +1 -0
- package/dist/tui/notice-panel.d.ts +20 -0
- package/dist/tui/notice-panel.d.ts.map +1 -0
- package/dist/tui/notice-panel.js +19 -0
- package/dist/tui/notice-panel.js.map +1 -0
- package/dist/tui/response-panel.d.ts +12 -16
- package/dist/tui/response-panel.d.ts.map +1 -1
- package/dist/tui/response-panel.js +8 -11
- package/dist/tui/response-panel.js.map +1 -1
- package/dist/tui/run-ink.d.ts +12 -0
- package/dist/tui/run-ink.d.ts.map +1 -1
- package/dist/tui/run-ink.js +2 -1
- package/dist/tui/run-ink.js.map +1 -1
- package/dist/tui/slash/menu.d.ts +16 -0
- package/dist/tui/slash/menu.d.ts.map +1 -0
- package/dist/tui/slash/menu.js +11 -0
- package/dist/tui/slash/menu.js.map +1 -0
- package/dist/tui/slash/parse.d.ts +21 -0
- package/dist/tui/slash/parse.d.ts.map +1 -0
- package/dist/tui/slash/parse.js +32 -0
- package/dist/tui/slash/parse.js.map +1 -0
- package/dist/tui/slash/registry.d.ts +20 -0
- package/dist/tui/slash/registry.d.ts.map +1 -0
- package/dist/tui/slash/registry.js +28 -0
- package/dist/tui/slash/registry.js.map +1 -0
- package/dist/tui/spinner.d.ts +11 -0
- package/dist/tui/spinner.d.ts.map +1 -0
- package/dist/tui/spinner.js +21 -0
- package/dist/tui/spinner.js.map +1 -0
- package/dist/tui/status-bar.d.ts +42 -0
- package/dist/tui/status-bar.d.ts.map +1 -0
- package/dist/tui/status-bar.js +32 -0
- package/dist/tui/status-bar.js.map +1 -0
- package/dist/tui/tail-visual.d.ts +11 -0
- package/dist/tui/tail-visual.d.ts.map +1 -0
- package/dist/tui/tail-visual.js +30 -0
- package/dist/tui/tail-visual.js.map +1 -0
- package/dist/tui/text-input.d.ts +20 -22
- package/dist/tui/text-input.d.ts.map +1 -1
- package/dist/tui/text-input.js +211 -81
- package/dist/tui/text-input.js.map +1 -1
- package/dist/tui/theme/context.d.ts +16 -0
- package/dist/tui/theme/context.d.ts.map +1 -0
- package/dist/tui/theme/context.js +28 -0
- package/dist/tui/theme/context.js.map +1 -0
- package/dist/tui/theme/persist.d.ts +11 -0
- package/dist/tui/theme/persist.d.ts.map +1 -0
- package/dist/tui/theme/persist.js +41 -0
- package/dist/tui/theme/persist.js.map +1 -0
- package/dist/tui/theme/theme.d.ts +26 -0
- package/dist/tui/theme/theme.d.ts.map +1 -0
- package/dist/tui/theme/theme.js +51 -0
- package/dist/tui/theme/theme.js.map +1 -0
- package/dist/tui/thinking-panel.d.ts +11 -20
- package/dist/tui/thinking-panel.d.ts.map +1 -1
- package/dist/tui/thinking-panel.js +29 -7
- package/dist/tui/thinking-panel.js.map +1 -1
- package/dist/tui/tool-cards.d.ts +5 -17
- package/dist/tui/tool-cards.d.ts.map +1 -1
- package/dist/tui/tool-cards.js +4 -5
- package/dist/tui/tool-cards.js.map +1 -1
- package/dist/tui/tool-summary.d.ts +2 -0
- package/dist/tui/tool-summary.d.ts.map +1 -0
- package/dist/tui/tool-summary.js +93 -0
- package/dist/tui/tool-summary.js.map +1 -0
- package/dist/tui/use-elapsed.d.ts +2 -0
- package/dist/tui/use-elapsed.d.ts.map +1 -0
- package/dist/tui/use-elapsed.js +22 -0
- package/dist/tui/use-elapsed.js.map +1 -0
- package/dist/tui/use-plan-state.d.ts +20 -1
- package/dist/tui/use-plan-state.d.ts.map +1 -1
- package/dist/tui/use-plan-state.js +52 -46
- package/dist/tui/use-plan-state.js.map +1 -1
- package/dist/tui/use-terminal-width.d.ts +2 -0
- package/dist/tui/use-terminal-width.d.ts.map +1 -0
- package/dist/tui/use-terminal-width.js +23 -0
- package/dist/tui/use-terminal-width.js.map +1 -0
- package/dist/tui/use-viewport.d.ts +15 -0
- package/dist/tui/use-viewport.d.ts.map +1 -0
- package/dist/tui/use-viewport.js +37 -0
- package/dist/tui/use-viewport.js.map +1 -0
- package/dist/tui/welcome.d.ts +29 -0
- package/dist/tui/welcome.d.ts.map +1 -0
- package/dist/tui/welcome.js +41 -0
- package/dist/tui/welcome.js.map +1 -0
- package/package.json +3 -2
- package/dist/tui/header.d.ts +0 -24
- package/dist/tui/header.d.ts.map +0 -1
- package/dist/tui/header.js +0 -26
- package/dist/tui/header.js.map +0 -1
package/dist/tui/app.d.ts
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TUI 主壳 —— 组合
|
|
2
|
+
* TUI 主壳 —— 组合 StatusBar / PlanPanel / ThinkingPanel / ToolCards / ResponsePanel /
|
|
3
|
+
* TextInput / HintBar / NoticePanel。
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
package/dist/tui/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/tui/app.tsx"],"names":[],"mappings":"AAAA
|
|
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 主壳 —— 组合
|
|
3
|
+
* TUI 主壳 —— 组合 StatusBar / PlanPanel / ThinkingPanel / ToolCards / ResponsePanel /
|
|
4
|
+
* TextInput / HintBar / NoticePanel。
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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,
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
//
|
|
76
|
-
|
|
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 [
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}, [
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
...prev,
|
|
190
|
-
{ id: historyIdRef.current++, kind: 'user', text: trimmed },
|
|
191
|
-
]);
|
|
270
|
+
maybePinWelcome();
|
|
271
|
+
appendHistory({ kind: 'user', text: trimmed });
|
|
192
272
|
setBusy(true);
|
|
193
|
-
|
|
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
|
-
|
|
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
|
-
|
|
300
|
+
showNotice('error', event.error ?? '流式错误');
|
|
222
301
|
}
|
|
223
302
|
};
|
|
224
303
|
try {
|
|
225
304
|
const completed = await handlers.sendMessage(trimmed);
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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,
|
|
258
|
-
|
|
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
|
-
/**
|
|
261
|
-
function
|
|
262
|
-
|
|
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
|