@armadra/agent 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +99 -0
- package/README.md +142 -49
- package/dist/agent/retry.d.ts +1 -1
- package/dist/agent/retry.js +2 -1
- package/dist/agent/session-cache.js +6 -3
- package/dist/agent/session-classifier.d.ts +18 -0
- package/dist/agent/session-classifier.js +103 -0
- package/dist/agent/session-core.d.ts +13 -0
- package/dist/agent/session-state.js +2 -1
- package/dist/agent/session-sync.js +9 -2
- package/dist/agent/session-tools.js +24 -3
- package/dist/agent/session.d.ts +3 -0
- package/dist/agent/session.js +20 -10
- package/dist/agent/tool-runner.js +15 -2
- package/dist/agent/types.d.ts +9 -1
- package/dist/ai/apis/anthropic-messages.js +3 -2
- package/dist/ai/apis/google-generative-ai.js +3 -2
- package/dist/ai/apis/openai-completions.js +3 -2
- package/dist/ai/apis/openai-responses.js +5 -3
- package/dist/ai/cache/fingerprint.d.ts +1 -1
- package/dist/ai/cache/fingerprint.js +1 -1
- package/dist/ai/cache/reporting.js +2 -1
- package/dist/ai/http.d.ts +28 -6
- package/dist/ai/http.js +41 -8
- package/dist/ai/providers/channels.d.ts +51 -0
- package/dist/ai/providers/channels.js +96 -0
- package/dist/ai/providers/enrich.d.ts +34 -0
- package/dist/ai/providers/enrich.js +86 -0
- package/dist/ai/providers/models-dev-cache.d.ts +53 -0
- package/dist/ai/providers/models-dev-cache.js +147 -0
- package/dist/ai/providers/models-dev.d.ts +99 -0
- package/dist/ai/providers/models-dev.js +315 -0
- package/dist/ai/providers/registry.d.ts +42 -4
- package/dist/ai/providers/registry.js +195 -34
- package/dist/ai/providers/suggest.d.ts +18 -0
- package/dist/ai/providers/suggest.js +72 -0
- package/dist/ai/sse.d.ts +6 -2
- package/dist/ai/sse.js +22 -2
- package/dist/ai/types.d.ts +30 -3
- package/dist/bundle/ama.cjs +17893 -10698
- package/dist/cli/args.d.ts +23 -5
- package/dist/cli/args.js +113 -13
- package/dist/cli/bootstrap.js +28 -3
- package/dist/cli/codemode-notice.d.ts +20 -0
- package/dist/cli/codemode-notice.js +55 -0
- package/dist/cli/compose-providers.js +3 -0
- package/dist/cli/compose-session.d.ts +8 -0
- package/dist/cli/compose-session.js +32 -1
- package/dist/cli/compose-store.d.ts +1 -1
- package/dist/cli/compose-store.js +3 -1
- package/dist/cli/compose.d.ts +17 -5
- package/dist/cli/compose.js +53 -19
- package/dist/cli/default-model.d.ts +38 -1
- package/dist/cli/default-model.js +96 -9
- package/dist/cli/deps.d.ts +38 -0
- package/dist/cli/exit-codes.d.ts +2 -0
- package/dist/cli/exit-codes.js +3 -0
- package/dist/cli/fake-visibility.d.ts +13 -0
- package/dist/cli/fake-visibility.js +26 -0
- package/dist/cli/from-prompt.d.ts +18 -0
- package/dist/cli/from-prompt.js +49 -0
- package/dist/cli/main.d.ts +10 -2
- package/dist/cli/main.js +88 -2
- package/dist/cli/proxy.d.ts +51 -0
- package/dist/cli/proxy.js +135 -0
- package/dist/cli/startup-screen.d.ts +29 -0
- package/dist/cli/startup-screen.js +47 -0
- package/dist/cli/startup-steps.d.ts +1 -1
- package/dist/cli/startup-steps.js +20 -13
- package/dist/cli/subcommands/config.d.ts +29 -4
- package/dist/cli/subcommands/config.js +188 -25
- package/dist/cli/subcommands/context.js +5 -1
- package/dist/cli/subcommands/doctor.js +12 -1
- package/dist/cli/subcommands/init.d.ts +6 -0
- package/dist/cli/subcommands/init.js +23 -0
- package/dist/cli/subcommands/model-meta.d.ts +16 -0
- package/dist/cli/subcommands/model-meta.js +54 -0
- package/dist/cli/subcommands/models-cache-probe.js +1 -1
- package/dist/cli/subcommands/models-discover.d.ts +18 -6
- package/dist/cli/subcommands/models-discover.js +87 -52
- package/dist/cli/subcommands/models.js +26 -18
- package/dist/cli/subcommands/probe-runner.d.ts +96 -0
- package/dist/cli/subcommands/probe-runner.js +264 -0
- package/dist/cli/subcommands/providers-list.d.ts +8 -0
- package/dist/cli/subcommands/providers-list.js +97 -0
- package/dist/cli/subcommands/providers-plan.d.ts +79 -0
- package/dist/cli/subcommands/providers-plan.js +215 -0
- package/dist/cli/subcommands/providers-probe.d.ts +34 -0
- package/dist/cli/subcommands/providers-probe.js +87 -0
- package/dist/cli/subcommands/providers.d.ts +28 -0
- package/dist/cli/subcommands/providers.js +436 -0
- package/dist/cli/subcommands/sessions-export.d.ts +10 -0
- package/dist/cli/subcommands/sessions-export.js +59 -0
- package/dist/cli/subcommands/sessions-search.d.ts +13 -0
- package/dist/cli/subcommands/sessions-search.js +103 -0
- package/dist/cli/subcommands/sessions.d.ts +3 -2
- package/dist/cli/subcommands/sessions.js +22 -1
- package/dist/cli/subcommands/stats.d.ts +17 -0
- package/dist/cli/subcommands/stats.js +198 -0
- package/dist/cli/system-prompt-arg.d.ts +11 -0
- package/dist/cli/system-prompt-arg.js +34 -0
- package/dist/codemode/modes.d.ts +4 -11
- package/dist/codemode/modes.js +5 -27
- package/dist/codemode/tool.d.ts +18 -14
- package/dist/codemode/tool.js +71 -28
- package/dist/config/init.d.ts +34 -0
- package/dist/config/init.js +99 -0
- package/dist/config/json-schema.d.ts +15 -0
- package/dist/config/json-schema.js +215 -0
- package/dist/config/key-docs.d.ts +22 -0
- package/dist/config/key-docs.js +105 -0
- package/dist/config/merge.d.ts +9 -8
- package/dist/config/merge.js +22 -7
- package/dist/config/schema.d.ts +1 -1
- package/dist/config/schema.js +97 -15
- package/dist/config/types.d.ts +57 -11
- package/dist/config/types.js +19 -1
- package/dist/modes/commands-core.js +6 -5
- package/dist/modes/image-input.d.ts +27 -0
- package/dist/modes/image-input.js +78 -0
- package/dist/modes/interactive/approval-dialog.d.ts +21 -5
- package/dist/modes/interactive/approval-dialog.js +106 -27
- package/dist/modes/interactive/commands.d.ts +11 -2
- package/dist/modes/interactive/commands.js +58 -19
- package/dist/modes/interactive/interactive-mode.d.ts +2 -1
- package/dist/modes/interactive/interactive-mode.js +70 -82
- package/dist/modes/interactive/key-dispatch.d.ts +3 -1
- package/dist/modes/interactive/key-dispatch.js +5 -6
- package/dist/modes/interactive/line/line-mode.d.ts +1 -0
- package/dist/modes/interactive/line/line-mode.js +9 -5
- package/dist/modes/interactive/line/line-render.d.ts +4 -0
- package/dist/modes/interactive/line/line-render.js +28 -6
- package/dist/modes/interactive/message-view.d.ts +48 -9
- package/dist/modes/interactive/message-view.js +238 -44
- package/dist/modes/interactive/panels.d.ts +18 -0
- package/dist/modes/interactive/panels.js +143 -0
- package/dist/modes/interactive/pickers.d.ts +23 -2
- package/dist/modes/interactive/pickers.js +48 -15
- package/dist/modes/interactive/run-indicator.d.ts +51 -0
- package/dist/modes/interactive/run-indicator.js +189 -0
- package/dist/modes/interactive/startup-header.d.ts +40 -0
- package/dist/modes/interactive/startup-header.js +169 -0
- package/dist/modes/interactive/startup-ui.d.ts +7 -2
- package/dist/modes/interactive/startup-ui.js +41 -11
- package/dist/modes/interactive/status-bar.d.ts +18 -15
- package/dist/modes/interactive/status-bar.js +98 -56
- package/dist/modes/interactive/tool-summary.d.ts +46 -0
- package/dist/modes/interactive/tool-summary.js +218 -0
- package/dist/modes/interactive/tool-view.d.ts +48 -15
- package/dist/modes/interactive/tool-view.js +203 -145
- package/dist/modes/print/print-mode.d.ts +33 -4
- package/dist/modes/print/print-mode.js +128 -8
- package/dist/modes/rpc/commands.js +12 -2
- package/dist/permissions/auto-safe.d.ts +60 -0
- package/dist/permissions/auto-safe.js +529 -0
- package/dist/permissions/classifier.d.ts +64 -0
- package/dist/permissions/classifier.js +184 -0
- package/dist/permissions/dangerous.d.ts +5 -0
- package/dist/permissions/dangerous.js +1 -1
- package/dist/permissions/modes.d.ts +30 -0
- package/dist/permissions/modes.js +78 -0
- package/dist/permissions/pipeline.d.ts +31 -4
- package/dist/permissions/pipeline.js +196 -6
- package/dist/permissions/protected.d.ts +19 -0
- package/dist/permissions/protected.js +74 -0
- package/dist/permissions/rules.js +3 -0
- package/dist/permissions/types.d.ts +50 -3
- package/dist/permissions/types.js +3 -0
- package/dist/rpc.d.ts +2 -0
- package/dist/sdk.d.ts +9 -3
- package/dist/sdk.js +10 -2
- package/dist/session/export.d.ts +32 -0
- package/dist/session/export.js +187 -0
- package/dist/session/projection.js +5 -1
- package/dist/session/redact.d.ts +15 -0
- package/dist/session/redact.js +55 -0
- package/dist/session/reuse.d.ts +33 -0
- package/dist/session/reuse.js +86 -0
- package/dist/session/scan.d.ts +34 -0
- package/dist/session/scan.js +140 -0
- package/dist/session/search.d.ts +52 -0
- package/dist/session/search.js +211 -0
- package/dist/session/stats-aggregate.d.ts +63 -0
- package/dist/session/stats-aggregate.js +163 -0
- package/dist/session/stats-index.d.ts +26 -0
- package/dist/session/stats-index.js +91 -0
- package/dist/session/stats-scan.d.ts +54 -0
- package/dist/session/stats-scan.js +236 -0
- package/dist/session/types.d.ts +2 -0
- package/dist/tools/image-file.d.ts +31 -0
- package/dist/tools/image-file.js +114 -0
- package/dist/tools/presets.d.ts +35 -8
- package/dist/tools/presets.js +56 -17
- package/dist/tools/read.d.ts +4 -8
- package/dist/tools/read.js +15 -64
- package/dist/tui/component.d.ts +8 -2
- package/dist/tui/component.js +3 -1
- package/dist/tui/components/box.d.ts +6 -1
- package/dist/tui/components/box.js +16 -6
- package/dist/tui/components/card.d.ts +23 -0
- package/dist/tui/components/card.js +37 -0
- package/dist/tui/components/editor-history.d.ts +6 -0
- package/dist/tui/components/editor-history.js +45 -0
- package/dist/tui/components/editor-paste.d.ts +1 -1
- package/dist/tui/components/editor-paste.js +4 -4
- package/dist/tui/components/editor.d.ts +11 -5
- package/dist/tui/components/editor.js +52 -58
- package/dist/tui/components/key-value.d.ts +3 -0
- package/dist/tui/components/key-value.js +16 -6
- package/dist/tui/components/loader.d.ts +37 -7
- package/dist/tui/components/loader.js +84 -21
- package/dist/tui/components/markdown.d.ts +5 -1
- package/dist/tui/components/markdown.js +45 -21
- package/dist/tui/components/meter.d.ts +3 -3
- package/dist/tui/components/meter.js +13 -11
- package/dist/tui/components/select-list.d.ts +23 -1
- package/dist/tui/components/select-list.js +76 -13
- package/dist/tui/glyphs.d.ts +68 -0
- package/dist/tui/glyphs.js +114 -0
- package/dist/tui/theme.d.ts +30 -6
- package/dist/tui/theme.js +103 -17
- package/dist/tui.d.ts +4 -2
- package/dist/tui.js +3 -1
- package/docs/codemode.md +23 -9
- package/docs/hooks.md +10 -10
- package/docs/permissions.md +148 -0
- package/docs/providers.md +241 -3
- package/docs/rpc.md +38 -38
- package/docs/session-format.md +3 -2
- package/docs/sessions.md +134 -0
- package/docs/tui.md +140 -61
- package/package.json +3 -1
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 消息区(设计 §12.5)。[B7]
|
|
3
3
|
*
|
|
4
|
-
* 一个纵向 `Container
|
|
5
|
-
*
|
|
4
|
+
* 一个纵向 `Container`,块与块之间空一行(终端界面视觉设计 v1 §1 原则 2:相邻工具调用之间不空行,
|
|
5
|
+
* `ui.compact` 时全部不空行):
|
|
6
|
+
* - 用户消息 `› 文本`(续行缩进 2 列);插话 / 之后 / 宿主注入按 `UserMessage.origin` 标
|
|
7
|
+
* `↳ 插话` / `↳ 之后` / `↳ 宿主`(整行 muted,其它 origin 原样显示);
|
|
6
8
|
* - 助手消息按内容块渲染:文本走 `Markdown`(`ui.markdown: false` 时纯文本),流式时只改末块
|
|
7
|
-
* (Markdown 自己按块缓存);思考块按 `ui.showThinking`:`collapsed`(缺省)一行
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* (Markdown 自己按块缓存);思考块按 `ui.showThinking`:`collapsed`(缺省)一行 `✻ 思考 · N token`
|
|
10
|
+
* (流式中 `✻ 思考中…`),Ctrl+O 展开为标题 + 缩进 2 列的正文(最多 60 行);`full` 总是展开且不限行;
|
|
11
|
+
* `hidden` 不显示;
|
|
12
|
+
* - 压缩摘要卡(左竖条 Card)、重试提示、Hook 阻止提示、宿主 / 系统通知;字形取 `theme.glyphs`;
|
|
10
13
|
* - `replay()` 把已有消息(恢复会话、切换会话、/tree 之后)重新画出来,工具调用交给调用方构造。
|
|
11
14
|
*
|
|
12
15
|
* 滚动交给终端回滚:消息区只追加,切换会话时整体清空重画。
|
|
13
16
|
*/
|
|
14
17
|
import type { AssistantMessage, ContentBlock, ToolCallBlock, UserMessage } from "../../ai/types.js";
|
|
15
|
-
import type { CompactionResult, ToolResultMessage } from "../../agent/types.js";
|
|
18
|
+
import type { CompactionResult, SessionStats, ToolResultMessage } from "../../agent/types.js";
|
|
16
19
|
import type { AgentMessage } from "../../session/types.js";
|
|
17
20
|
import { Container, type Component, type Theme } from "../../tui.js";
|
|
18
21
|
export type ThinkingDisplay = "full" | "collapsed" | "hidden";
|
|
@@ -22,6 +25,27 @@ export interface MessageViewOptions {
|
|
|
22
25
|
showThinking?: ThinkingDisplay;
|
|
23
26
|
/** false:助手文本不做 Markdown 渲染。 */
|
|
24
27
|
markdown?: boolean;
|
|
28
|
+
/** `ui.compact`:块间不空行。 */
|
|
29
|
+
compact?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 退出摘要(§3.14):`─ 会话 <id> · 时长 · N 回合 · ↑in ↓out · cache x% · $y(重计费 $z)` 与
|
|
33
|
+
* ` 恢复:ama --resume <id>`。整体 dim,会话 id 与恢复命令正文色;0 回合只到「回合」;会话没落盘不给恢复行。
|
|
34
|
+
*/
|
|
35
|
+
export declare function exitSummaryLines(stats: Pick<SessionStats, "sessionId" | "sessionFile" | "userMessages" | "tokens" | "cost" | "cache" | "cacheHitRate">, elapsedMs: number, theme: Theme): string[];
|
|
36
|
+
/** 思考块展开时的正文行数上限(`full` 不限)。 */
|
|
37
|
+
export declare const THINKING_EXPANDED_LINES = 60;
|
|
38
|
+
/** origin → 中文标签(未知 origin 原样显示)。 */
|
|
39
|
+
export declare const ORIGIN_LABELS: Readonly<Record<string, string>>;
|
|
40
|
+
/** 首行带前缀、续行按 `indent` 缩进的文本(用户消息、排队消息)。 */
|
|
41
|
+
export declare class PrefixedText implements Component {
|
|
42
|
+
private readonly prefix;
|
|
43
|
+
private readonly body;
|
|
44
|
+
private readonly indent;
|
|
45
|
+
private cache;
|
|
46
|
+
constructor(prefix: string, body: string, indent?: number);
|
|
47
|
+
render(width: number): string[];
|
|
48
|
+
invalidate(): void;
|
|
25
49
|
}
|
|
26
50
|
/** 粗估 token(4 字符 ≈ 1 token),只用于思考块的流式计数。 */
|
|
27
51
|
export declare function estimateTokens(text: string): number;
|
|
@@ -32,7 +56,10 @@ export declare class AssistantView extends Container {
|
|
|
32
56
|
private readonly parts;
|
|
33
57
|
private readonly status;
|
|
34
58
|
private streaming;
|
|
59
|
+
private thinkingExpanded;
|
|
35
60
|
constructor(options: MessageViewOptions);
|
|
61
|
+
/** Ctrl+O:展开 / 折叠本消息的思考块(`collapsed` 模式下)。 */
|
|
62
|
+
setThinkingExpanded(expanded: boolean): void;
|
|
36
63
|
update(message: AssistantMessage): void;
|
|
37
64
|
/** 收尾:错误 / 中断 / 长度截断提示,思考计数换成用量里的值。 */
|
|
38
65
|
finish(message: AssistantMessage): void;
|
|
@@ -47,25 +74,37 @@ export declare class MessageView extends Container {
|
|
|
47
74
|
private readonly options;
|
|
48
75
|
private current;
|
|
49
76
|
private readonly blocks;
|
|
77
|
+
/** 工具调用块(相邻的不空行)。 */
|
|
78
|
+
private readonly toolBlocks;
|
|
79
|
+
private readonly assistants;
|
|
80
|
+
private thinkingExpanded;
|
|
50
81
|
constructor(options: MessageViewOptions);
|
|
51
82
|
private get theme();
|
|
52
|
-
/**
|
|
83
|
+
/** 追加一块(与上一块之间空一行;`compact` 不空行)。 */
|
|
53
84
|
add(component: Component): void;
|
|
85
|
+
/** 追加工具调用块:紧跟在另一个工具调用后面时不空行。 */
|
|
86
|
+
addTool(component: Component): void;
|
|
87
|
+
private push;
|
|
54
88
|
/** 去掉一块(空的助手消息等)。 */
|
|
55
89
|
remove(component: Component): void;
|
|
56
90
|
reset(): void;
|
|
57
91
|
get blockCount(): number;
|
|
58
|
-
/**
|
|
92
|
+
/** Ctrl+O:所有助手消息的思考块一起展开 / 折叠(之后的新消息沿用)。 */
|
|
93
|
+
setThinkingExpanded(expanded: boolean): void;
|
|
94
|
+
/** 纯文本启动画面(line 模式风格):首行加粗,其余变暗。交互模式用 StartupHeader。 */
|
|
59
95
|
addHeader(lines: readonly string[]): void;
|
|
60
96
|
addUser(message: Pick<UserMessage, "content" | "origin">): void;
|
|
97
|
+
private newAssistant;
|
|
61
98
|
startAssistant(message: AssistantMessage): AssistantView;
|
|
62
99
|
updateAssistant(message: AssistantMessage): void;
|
|
63
100
|
endAssistant(message: AssistantMessage): void;
|
|
64
101
|
addNotice(level: NoticeLevel, message: string): void;
|
|
65
102
|
addHookBlocked(reason: string): void;
|
|
66
103
|
addRetry(attempt: number, maxAttempts: number, delayMs: number, error: string): void;
|
|
104
|
+
/** 退出前追加的会话摘要(留在终端回滚里)。 */
|
|
105
|
+
addExitSummary(lines: readonly string[]): void;
|
|
67
106
|
addRetryFailed(error: string | undefined): void;
|
|
68
|
-
/** 压缩 /
|
|
107
|
+
/** 压缩 / 分支摘要卡(左竖条):标题 + token 变化(有则显示)+ 摘要前 3 行。 */
|
|
69
108
|
addSummaryCard(summary: string, options?: {
|
|
70
109
|
title?: string;
|
|
71
110
|
tokensBefore?: number;
|
|
@@ -1,17 +1,159 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 消息区(设计 §12.5)。[B7]
|
|
3
3
|
*
|
|
4
|
-
* 一个纵向 `Container
|
|
5
|
-
*
|
|
4
|
+
* 一个纵向 `Container`,块与块之间空一行(终端界面视觉设计 v1 §1 原则 2:相邻工具调用之间不空行,
|
|
5
|
+
* `ui.compact` 时全部不空行):
|
|
6
|
+
* - 用户消息 `› 文本`(续行缩进 2 列);插话 / 之后 / 宿主注入按 `UserMessage.origin` 标
|
|
7
|
+
* `↳ 插话` / `↳ 之后` / `↳ 宿主`(整行 muted,其它 origin 原样显示);
|
|
6
8
|
* - 助手消息按内容块渲染:文本走 `Markdown`(`ui.markdown: false` 时纯文本),流式时只改末块
|
|
7
|
-
* (Markdown 自己按块缓存);思考块按 `ui.showThinking`:`collapsed`(缺省)一行
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* (Markdown 自己按块缓存);思考块按 `ui.showThinking`:`collapsed`(缺省)一行 `✻ 思考 · N token`
|
|
10
|
+
* (流式中 `✻ 思考中…`),Ctrl+O 展开为标题 + 缩进 2 列的正文(最多 60 行);`full` 总是展开且不限行;
|
|
11
|
+
* `hidden` 不显示;
|
|
12
|
+
* - 压缩摘要卡(左竖条 Card)、重试提示、Hook 阻止提示、宿主 / 系统通知;字形取 `theme.glyphs`;
|
|
10
13
|
* - `replay()` 把已有消息(恢复会话、切换会话、/tree 之后)重新画出来,工具调用交给调用方构造。
|
|
11
14
|
*
|
|
12
15
|
* 滚动交给终端回滚:消息区只追加,切换会话时整体清空重画。
|
|
13
16
|
*/
|
|
14
|
-
import {
|
|
17
|
+
import { Card, Container, Markdown, Spacer, Text, visibleWidth, wrapTextWithAnsi, } from "../../tui.js";
|
|
18
|
+
import { formatCost, formatTokens } from "./status-bar.js";
|
|
19
|
+
/** 退出摘要的时长:不足 1 分钟按秒,否则按分钟。 */
|
|
20
|
+
function sessionDuration(ms) {
|
|
21
|
+
const seconds = Math.max(0, Math.round(ms / 1000));
|
|
22
|
+
return seconds < 60 ? `${seconds} 秒` : `${Math.round(seconds / 60)} 分钟`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 退出摘要(§3.14):`─ 会话 <id> · 时长 · N 回合 · ↑in ↓out · cache x% · $y(重计费 $z)` 与
|
|
26
|
+
* ` 恢复:ama --resume <id>`。整体 dim,会话 id 与恢复命令正文色;0 回合只到「回合」;会话没落盘不给恢复行。
|
|
27
|
+
*/
|
|
28
|
+
export function exitSummaryLines(stats, elapsedMs, theme) {
|
|
29
|
+
const g = theme.glyphs;
|
|
30
|
+
const dim = (s) => theme.fg("dim", s);
|
|
31
|
+
const id = stats.sessionId.slice(0, 8);
|
|
32
|
+
const parts = [
|
|
33
|
+
dim(`${g.rule} 会话 `) + theme.fg("text", id),
|
|
34
|
+
dim(sessionDuration(elapsedMs)),
|
|
35
|
+
dim(`${stats.userMessages} 回合`),
|
|
36
|
+
];
|
|
37
|
+
const t = stats.tokens;
|
|
38
|
+
const prompt = t.input + t.cacheRead + t.cacheWrite;
|
|
39
|
+
if (stats.userMessages > 0 && prompt + t.output > 0) {
|
|
40
|
+
parts.push(dim(`${g.arrowUp}${formatTokens(prompt)} ${g.arrowDown}${formatTokens(t.output)}`));
|
|
41
|
+
const rate = stats.cache?.hitRate ?? stats.cacheHitRate;
|
|
42
|
+
if (rate !== undefined)
|
|
43
|
+
parts.push(dim(`cache ${Math.round(rate * 100)}%`));
|
|
44
|
+
if (stats.cost !== undefined && stats.cost > 0) {
|
|
45
|
+
const rebill = stats.cache?.reBilledUsd;
|
|
46
|
+
const extra = rebill !== undefined && rebill > 0 ? `(重计费 ${formatCost(rebill)})` : "";
|
|
47
|
+
parts.push(dim(formatCost(stats.cost) + extra));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const lines = [parts.join(dim(" · "))];
|
|
51
|
+
if (stats.userMessages > 0 && stats.sessionFile !== undefined) {
|
|
52
|
+
lines.push(dim(" 恢复:") + theme.fg("text", `ama --resume ${id}`));
|
|
53
|
+
}
|
|
54
|
+
return lines;
|
|
55
|
+
}
|
|
56
|
+
/** 思考块展开时的正文行数上限(`full` 不限)。 */
|
|
57
|
+
export const THINKING_EXPANDED_LINES = 60;
|
|
58
|
+
/** origin → 中文标签(未知 origin 原样显示)。 */
|
|
59
|
+
export const ORIGIN_LABELS = {
|
|
60
|
+
steer: "插话",
|
|
61
|
+
followUp: "之后",
|
|
62
|
+
host: "宿主",
|
|
63
|
+
};
|
|
64
|
+
/** 首行带前缀、续行按 `indent` 缩进的文本(用户消息、排队消息)。 */
|
|
65
|
+
export class PrefixedText {
|
|
66
|
+
prefix;
|
|
67
|
+
body;
|
|
68
|
+
indent;
|
|
69
|
+
cache = null;
|
|
70
|
+
constructor(prefix, body, indent = 2) {
|
|
71
|
+
this.prefix = prefix;
|
|
72
|
+
this.body = body;
|
|
73
|
+
this.indent = indent;
|
|
74
|
+
}
|
|
75
|
+
render(width) {
|
|
76
|
+
if (this.cache?.width === width)
|
|
77
|
+
return this.cache.lines;
|
|
78
|
+
const prefixWidth = Math.max(this.indent, visibleWidth(this.prefix));
|
|
79
|
+
const pieces = wrapTextWithAnsi(this.body, Math.max(1, width - prefixWidth));
|
|
80
|
+
const pad = " ".repeat(this.indent);
|
|
81
|
+
const lines = pieces.map((line, i) => (i === 0 ? this.prefix + line : pad + line));
|
|
82
|
+
if (lines.length === 0)
|
|
83
|
+
lines.push(this.prefix);
|
|
84
|
+
this.cache = { width, lines };
|
|
85
|
+
return lines;
|
|
86
|
+
}
|
|
87
|
+
invalidate() {
|
|
88
|
+
this.cache = null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** 思考块:折叠一行 / 展开(标题 + 缩进正文)。 */
|
|
92
|
+
class ThinkingView {
|
|
93
|
+
theme;
|
|
94
|
+
display;
|
|
95
|
+
text = "";
|
|
96
|
+
tokens = 0;
|
|
97
|
+
streaming = true;
|
|
98
|
+
redacted = false;
|
|
99
|
+
expanded = false;
|
|
100
|
+
cache = null;
|
|
101
|
+
constructor(theme, display) {
|
|
102
|
+
this.theme = theme;
|
|
103
|
+
this.display = display;
|
|
104
|
+
}
|
|
105
|
+
set(text, tokens, streaming, redacted) {
|
|
106
|
+
if (text === this.text &&
|
|
107
|
+
tokens === this.tokens &&
|
|
108
|
+
streaming === this.streaming &&
|
|
109
|
+
redacted === this.redacted)
|
|
110
|
+
return;
|
|
111
|
+
this.text = text;
|
|
112
|
+
this.tokens = tokens;
|
|
113
|
+
this.streaming = streaming;
|
|
114
|
+
this.redacted = redacted;
|
|
115
|
+
this.cache = null;
|
|
116
|
+
}
|
|
117
|
+
setExpanded(expanded) {
|
|
118
|
+
if (expanded === this.expanded)
|
|
119
|
+
return;
|
|
120
|
+
this.expanded = expanded;
|
|
121
|
+
this.cache = null;
|
|
122
|
+
}
|
|
123
|
+
get open() {
|
|
124
|
+
return !this.redacted && (this.display === "full" || this.expanded);
|
|
125
|
+
}
|
|
126
|
+
render(width) {
|
|
127
|
+
if (this.cache?.width === width)
|
|
128
|
+
return this.cache.lines;
|
|
129
|
+
const t = this.theme;
|
|
130
|
+
const g = t.glyphs;
|
|
131
|
+
const style = (s) => t.fg("dim", t.italic(s));
|
|
132
|
+
const title = this.streaming
|
|
133
|
+
? `${g.thinking} 思考中${g.ellipsis}`
|
|
134
|
+
: `${g.thinking} 思考 · ${formatTokens(this.tokens)} token`;
|
|
135
|
+
const lines = [
|
|
136
|
+
style(title) + (this.open && !this.streaming ? " " + t.fg("dim", g.collapse) : ""),
|
|
137
|
+
];
|
|
138
|
+
if (this.open && this.text.trim() !== "") {
|
|
139
|
+
const all = this.text
|
|
140
|
+
.trim()
|
|
141
|
+
.split("\n")
|
|
142
|
+
.flatMap((line) => wrapTextWithAnsi(line, Math.max(1, width - 2)));
|
|
143
|
+
const limit = this.display === "full" ? all.length : THINKING_EXPANDED_LINES;
|
|
144
|
+
for (const line of all.slice(0, limit))
|
|
145
|
+
lines.push(" " + t.fg("muted", t.italic(line)));
|
|
146
|
+
if (all.length > limit) {
|
|
147
|
+
lines.push(" " + t.fg("dim", `${g.ellipsis} 另 ${all.length - limit} 行`));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this.cache = { width, lines };
|
|
151
|
+
return lines;
|
|
152
|
+
}
|
|
153
|
+
invalidate() {
|
|
154
|
+
this.cache = null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
15
157
|
/** 粗估 token(4 字符 ≈ 1 token),只用于思考块的流式计数。 */
|
|
16
158
|
export function estimateTokens(text) {
|
|
17
159
|
return Math.ceil(text.length / 4);
|
|
@@ -29,10 +171,19 @@ export class AssistantView extends Container {
|
|
|
29
171
|
parts = new Map();
|
|
30
172
|
status = new Text("");
|
|
31
173
|
streaming = true;
|
|
174
|
+
thinkingExpanded = false;
|
|
32
175
|
constructor(options) {
|
|
33
176
|
super();
|
|
34
177
|
this.options = options;
|
|
35
178
|
}
|
|
179
|
+
/** Ctrl+O:展开 / 折叠本消息的思考块(`collapsed` 模式下)。 */
|
|
180
|
+
setThinkingExpanded(expanded) {
|
|
181
|
+
this.thinkingExpanded = expanded;
|
|
182
|
+
for (const part of this.parts.values()) {
|
|
183
|
+
if (part instanceof ThinkingView)
|
|
184
|
+
part.setExpanded(expanded);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
36
187
|
update(message) {
|
|
37
188
|
const { theme } = this.options;
|
|
38
189
|
const display = this.options.showThinking ?? "collapsed";
|
|
@@ -43,18 +194,21 @@ export class AssistantView extends Container {
|
|
|
43
194
|
if (block.type === "thinking") {
|
|
44
195
|
if (display === "hidden" || (block.thinking === "" && block.redacted !== true))
|
|
45
196
|
return;
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
197
|
+
const view = this.part(index, () => {
|
|
198
|
+
const created = new ThinkingView(theme, display);
|
|
199
|
+
created.setExpanded(this.thinkingExpanded);
|
|
200
|
+
return created;
|
|
201
|
+
});
|
|
202
|
+
const single = message.content.filter((b) => b.type === "thinking").length === 1;
|
|
203
|
+
const tokens = !this.streaming && single && message.usage.reasoning !== undefined
|
|
204
|
+
? message.usage.reasoning
|
|
205
|
+
: estimateTokens(block.thinking);
|
|
206
|
+
// 后面已经有正文 / 工具调用:这段思考已结束
|
|
207
|
+
const later = message.content
|
|
208
|
+
.slice(index + 1)
|
|
209
|
+
.some((b) => (b.type === "text" ? b.text !== "" : b.type === "toolCall"));
|
|
210
|
+
view.set(block.thinking, tokens, this.streaming && !later, block.redacted === true);
|
|
211
|
+
order.push(view);
|
|
58
212
|
return;
|
|
59
213
|
}
|
|
60
214
|
if (block.text.trim() === "")
|
|
@@ -64,14 +218,15 @@ export class AssistantView extends Container {
|
|
|
64
218
|
view.setText(block.text);
|
|
65
219
|
order.push(view);
|
|
66
220
|
});
|
|
221
|
+
const gap = this.options.compact !== true;
|
|
67
222
|
this.clear();
|
|
68
223
|
order.forEach((child, i) => {
|
|
69
|
-
if (i > 0)
|
|
224
|
+
if (i > 0 && gap)
|
|
70
225
|
this.addChild(new Spacer());
|
|
71
226
|
this.addChild(child);
|
|
72
227
|
});
|
|
73
228
|
if (this.status.getText() !== "") {
|
|
74
|
-
if (order.length > 0)
|
|
229
|
+
if (order.length > 0 && gap)
|
|
75
230
|
this.addChild(new Spacer());
|
|
76
231
|
this.addChild(this.status);
|
|
77
232
|
}
|
|
@@ -81,7 +236,7 @@ export class AssistantView extends Container {
|
|
|
81
236
|
this.streaming = false;
|
|
82
237
|
const { theme } = this.options;
|
|
83
238
|
if (message.stopReason === "error") {
|
|
84
|
-
this.status.setText(theme.fg("error",
|
|
239
|
+
this.status.setText(theme.fg("error", `${theme.glyphs.fail} ${message.errorMessage ?? "模型调用失败"}`));
|
|
85
240
|
}
|
|
86
241
|
else if (message.stopReason === "aborted") {
|
|
87
242
|
this.status.setText(theme.fg("dim", "已中断"));
|
|
@@ -107,6 +262,10 @@ export class MessageView extends Container {
|
|
|
107
262
|
options;
|
|
108
263
|
current;
|
|
109
264
|
blocks = [];
|
|
265
|
+
/** 工具调用块(相邻的不空行)。 */
|
|
266
|
+
toolBlocks = new WeakSet();
|
|
267
|
+
assistants = [];
|
|
268
|
+
thinkingExpanded = false;
|
|
110
269
|
constructor(options) {
|
|
111
270
|
super();
|
|
112
271
|
this.options = options;
|
|
@@ -114,9 +273,19 @@ export class MessageView extends Container {
|
|
|
114
273
|
get theme() {
|
|
115
274
|
return this.options.theme;
|
|
116
275
|
}
|
|
117
|
-
/**
|
|
276
|
+
/** 追加一块(与上一块之间空一行;`compact` 不空行)。 */
|
|
118
277
|
add(component) {
|
|
119
|
-
|
|
278
|
+
this.push(component, this.options.compact !== true);
|
|
279
|
+
}
|
|
280
|
+
/** 追加工具调用块:紧跟在另一个工具调用后面时不空行。 */
|
|
281
|
+
addTool(component) {
|
|
282
|
+
const last = this.blocks[this.blocks.length - 1];
|
|
283
|
+
const gap = this.options.compact !== true && !(last !== undefined && this.toolBlocks.has(last));
|
|
284
|
+
this.toolBlocks.add(component);
|
|
285
|
+
this.push(component, gap);
|
|
286
|
+
}
|
|
287
|
+
push(component, gap) {
|
|
288
|
+
if (this.blocks.length > 0 && gap)
|
|
120
289
|
this.addChild(new Spacer());
|
|
121
290
|
this.blocks.push(component);
|
|
122
291
|
this.addChild(component);
|
|
@@ -141,12 +310,19 @@ export class MessageView extends Container {
|
|
|
141
310
|
reset() {
|
|
142
311
|
this.clear();
|
|
143
312
|
this.blocks.length = 0;
|
|
313
|
+
this.assistants.length = 0;
|
|
144
314
|
this.current = undefined;
|
|
145
315
|
}
|
|
146
316
|
get blockCount() {
|
|
147
317
|
return this.blocks.length;
|
|
148
318
|
}
|
|
149
|
-
/**
|
|
319
|
+
/** Ctrl+O:所有助手消息的思考块一起展开 / 折叠(之后的新消息沿用)。 */
|
|
320
|
+
setThinkingExpanded(expanded) {
|
|
321
|
+
this.thinkingExpanded = expanded;
|
|
322
|
+
for (const view of this.assistants)
|
|
323
|
+
view.setThinkingExpanded(expanded);
|
|
324
|
+
}
|
|
325
|
+
/** 纯文本启动画面(line 模式风格):首行加粗,其余变暗。交互模式用 StartupHeader。 */
|
|
150
326
|
addHeader(lines) {
|
|
151
327
|
if (lines.length === 0)
|
|
152
328
|
return;
|
|
@@ -155,15 +331,24 @@ export class MessageView extends Container {
|
|
|
155
331
|
this.add(new Text(text.join("\n")));
|
|
156
332
|
}
|
|
157
333
|
addUser(message) {
|
|
334
|
+
const t = this.theme;
|
|
158
335
|
const text = contentText(message.content).replace(/\s+$/, "");
|
|
159
336
|
const origin = message.origin;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
337
|
+
if (origin === undefined || origin === "user") {
|
|
338
|
+
this.add(new PrefixedText(t.fg("user", t.bold(t.glyphs.prompt)) + " ", text));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const label = ORIGIN_LABELS[origin] ?? origin;
|
|
342
|
+
this.add(new PrefixedText(t.fg("dim", t.glyphs.queued) + " " + t.fg("muted", `${label} `), t.fg("muted", text)));
|
|
164
343
|
}
|
|
165
|
-
|
|
344
|
+
newAssistant() {
|
|
166
345
|
const view = new AssistantView(this.options);
|
|
346
|
+
view.setThinkingExpanded(this.thinkingExpanded);
|
|
347
|
+
this.assistants.push(view);
|
|
348
|
+
return view;
|
|
349
|
+
}
|
|
350
|
+
startAssistant(message) {
|
|
351
|
+
const view = this.newAssistant();
|
|
167
352
|
view.update(message);
|
|
168
353
|
this.current = view;
|
|
169
354
|
this.add(view);
|
|
@@ -184,39 +369,48 @@ export class MessageView extends Container {
|
|
|
184
369
|
this.remove(view);
|
|
185
370
|
}
|
|
186
371
|
addNotice(level, message) {
|
|
372
|
+
const g = this.theme.glyphs;
|
|
187
373
|
const color = level === "error" ? "error" : level === "warn" ? "warning" : "dim";
|
|
188
|
-
const mark = level === "error" ?
|
|
374
|
+
const mark = level === "error" ? `${g.fail} ` : level === "warn" ? `${g.warn} ` : "";
|
|
189
375
|
this.add(new Text(this.theme.fg(color, mark + message)));
|
|
190
376
|
}
|
|
191
377
|
addHookBlocked(reason) {
|
|
192
|
-
|
|
378
|
+
const glyph = this.theme.glyphs.blocked;
|
|
379
|
+
this.add(new Text(this.theme.fg("warning", `${glyph} Hook 阻止:${reason}`)));
|
|
193
380
|
}
|
|
194
381
|
addRetry(attempt, maxAttempts, delayMs, error) {
|
|
195
382
|
const seconds = Math.max(1, Math.round(delayMs / 1000));
|
|
196
|
-
|
|
383
|
+
const glyph = this.theme.glyphs.retry;
|
|
384
|
+
this.add(new Text(this.theme.fg("warning", `${glyph} 重试 ${attempt}/${maxAttempts}(${seconds}s 后):${error}`)));
|
|
385
|
+
}
|
|
386
|
+
/** 退出前追加的会话摘要(留在终端回滚里)。 */
|
|
387
|
+
addExitSummary(lines) {
|
|
388
|
+
this.add(new Text(lines.join("\n")));
|
|
197
389
|
}
|
|
198
390
|
addRetryFailed(error) {
|
|
199
391
|
this.addNotice("error", `重试失败${error !== undefined ? `:${error}` : ""}`);
|
|
200
392
|
}
|
|
201
|
-
/** 压缩 /
|
|
393
|
+
/** 压缩 / 分支摘要卡(左竖条):标题 + token 变化(有则显示)+ 摘要前 3 行。 */
|
|
202
394
|
addSummaryCard(summary, options = {}) {
|
|
203
|
-
const
|
|
395
|
+
const t = this.theme;
|
|
204
396
|
const lines = summary
|
|
205
397
|
.trim()
|
|
206
398
|
.split("\n")
|
|
207
399
|
.filter((l) => l.trim() !== "");
|
|
208
400
|
const head = lines.slice(0, 3);
|
|
209
|
-
const body =
|
|
401
|
+
const body = head.map((l) => t.fg("muted", l));
|
|
402
|
+
if (lines.length > head.length) {
|
|
403
|
+
body.push(t.fg("dim", `${t.glyphs.ellipsis} 另 ${lines.length - head.length} 行`));
|
|
404
|
+
}
|
|
405
|
+
let subtitle;
|
|
210
406
|
if (options.tokensBefore !== undefined) {
|
|
211
|
-
const after = options.tokensAfter !== undefined ? ` → ${options.tokensAfter}` : "";
|
|
212
|
-
|
|
407
|
+
const after = options.tokensAfter !== undefined ? ` → ${formatTokens(options.tokensAfter)}` : "";
|
|
408
|
+
subtitle = `${formatTokens(options.tokensBefore)}${after} token`;
|
|
213
409
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
body.push(dim(`…(另 ${lines.length - head.length} 行)`));
|
|
217
|
-
this.add(new Box(new Text(body.join("\n")), {
|
|
410
|
+
this.add(new Card(new Text(body.join("\n")), {
|
|
411
|
+
theme: t,
|
|
218
412
|
title: options.title ?? "上下文已压缩",
|
|
219
|
-
|
|
413
|
+
...(subtitle !== undefined ? { subtitle } : {}),
|
|
220
414
|
}));
|
|
221
415
|
}
|
|
222
416
|
addCompaction(result) {
|
|
@@ -238,7 +432,7 @@ export class MessageView extends Container {
|
|
|
238
432
|
this.addUser(message);
|
|
239
433
|
break;
|
|
240
434
|
case "assistant": {
|
|
241
|
-
const view =
|
|
435
|
+
const view = this.newAssistant();
|
|
242
436
|
view.finish(message);
|
|
243
437
|
if (!view.isEmpty)
|
|
244
438
|
this.add(view);
|
|
@@ -247,7 +441,7 @@ export class MessageView extends Container {
|
|
|
247
441
|
continue;
|
|
248
442
|
const tool = hooks.tool(block, results.get(block.id));
|
|
249
443
|
if (tool !== undefined)
|
|
250
|
-
this.
|
|
444
|
+
this.addTool(tool);
|
|
251
445
|
}
|
|
252
446
|
break;
|
|
253
447
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 消息区面板(终端界面视觉设计 v1 §3.13):`/session`、`/cache`、`/permissions` 用左竖条卡片 + 键值表
|
|
3
|
+
* 渲染,而不是把纯文本拍进通知。line 模式与 RPC 仍用 session-report 的文本版。
|
|
4
|
+
*
|
|
5
|
+
* - `/session`:标题 `会话 <id 前 8 位> · <文件>`;模型 / 消息 / 用量 / 上下文(余量表,阈值着色),
|
|
6
|
+
* 空一行后「缓存」段(与 `/cache` 共用 `cacheRows`);
|
|
7
|
+
* - `/permissions`:权限模式、判定顺序(折行对齐值列)、规则(allow success、deny error、来源 dim)、
|
|
8
|
+
* 最近的 auto 判定。
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentSession } from "../../agent/types.js";
|
|
11
|
+
import type { Runtime } from "../../cli/runtime.js";
|
|
12
|
+
import { type Component, type Theme } from "../../tui.js";
|
|
13
|
+
export declare function sessionPanel(session: AgentSession, theme: Theme, options?: {
|
|
14
|
+
now?: number;
|
|
15
|
+
home?: string;
|
|
16
|
+
}): Component;
|
|
17
|
+
export declare function cachePanel(session: AgentSession, theme: Theme, now?: number): Component;
|
|
18
|
+
export declare function permissionsPanel(runtime: Pick<Runtime, "permission">, session: AgentSession, theme: Theme): Component;
|