@armadra/agent 0.6.2 → 0.6.4
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 +85 -0
- package/CHANGELOG.zh-CN.md +59 -0
- package/README.md +168 -575
- package/README.zh-CN.md +165 -596
- package/dist/agent/queue.d.ts +9 -0
- package/dist/agent/queue.js +27 -0
- package/dist/agent/session-subagent.d.ts +1 -0
- package/dist/agent/session-subagent.js +9 -0
- package/dist/agent/session.d.ts +11 -0
- package/dist/agent/session.js +29 -2
- package/dist/agent/subagent-background.d.ts +75 -0
- package/dist/agent/subagent-background.js +209 -0
- package/dist/agent/subagent-direct.d.ts +5 -2
- package/dist/agent/subagent-direct.js +34 -1
- package/dist/agent/subagent-registry.d.ts +26 -6
- package/dist/agent/subagent-registry.js +66 -94
- package/dist/agent/types-w5.d.ts +11 -1
- package/dist/agent/types.d.ts +12 -0
- package/dist/agents/builtin.js +0 -1
- package/dist/agents/external.js +0 -1
- package/dist/agents/parse.js +4 -3
- package/dist/agents/result.d.ts +7 -1
- package/dist/agents/result.js +20 -1
- package/dist/agents/task-control.d.ts +13 -2
- package/dist/agents/task-record.d.ts +16 -4
- package/dist/agents/task-record.js +32 -0
- package/dist/agents/types.d.ts +5 -1
- package/dist/ai/apis/chatgpt-rate-limits.js +7 -2
- package/dist/ai/providers/discovered-cache.d.ts +10 -4
- package/dist/ai/providers/discovered-cache.js +30 -14
- package/dist/ai/types.d.ts +2 -1
- package/dist/auth/chatgpt/backend-client.d.ts +6 -5
- package/dist/auth/chatgpt/backend-client.js +8 -7
- package/dist/bundle/ama.cjs +2034 -552
- package/dist/cli/compose-agents.d.ts +2 -1
- package/dist/cli/compose-agents.js +11 -2
- package/dist/cli/subcommands/models-discover.d.ts +1 -0
- package/dist/cli/subcommands/models-discover.js +12 -2
- package/dist/config/json-schema.js +10 -2
- package/dist/config/key-docs.js +9 -2
- package/dist/config/merge.d.ts +1 -1
- package/dist/config/merge.js +20 -3
- package/dist/config/schema-w5.js +4 -2
- package/dist/config/schema.js +4 -0
- package/dist/config/settings-registry.js +5 -0
- package/dist/config/types-w5.d.ts +10 -0
- package/dist/config/types-w5.js +2 -0
- package/dist/config/types.d.ts +5 -1
- package/dist/drivers/runner.js +29 -2
- package/dist/git/info.d.ts +19 -0
- package/dist/git/info.js +64 -8
- package/dist/i18n/catalog.d.ts +58 -8
- package/dist/i18n/messages/agents.d.ts +60 -0
- package/dist/i18n/messages/agents.js +62 -2
- package/dist/i18n/messages/config-keys.d.ts +8 -0
- package/dist/i18n/messages/config-keys.js +18 -10
- package/dist/i18n/messages/config.d.ts +8 -0
- package/dist/i18n/messages/interactive-startup.d.ts +0 -16
- package/dist/i18n/messages/interactive-startup.js +0 -16
- package/dist/i18n/messages/interactive-view.d.ts +8 -0
- package/dist/i18n/messages/interactive-view.js +10 -0
- package/dist/i18n/messages/interactive.d.ts +33 -16
- package/dist/i18n/messages/interactive.js +31 -0
- package/dist/i18n/messages/print.d.ts +4 -0
- package/dist/i18n/messages/print.js +4 -0
- package/dist/i18n/messages/report.d.ts +8 -0
- package/dist/i18n/messages/report.js +12 -4
- package/dist/i18n/messages/settings.d.ts +8 -0
- package/dist/i18n/messages/settings.js +8 -0
- package/dist/i18n/messages/subcommands-config.d.ts +4 -0
- package/dist/i18n/messages/subcommands-config.js +4 -0
- package/dist/i18n/messages/subcommands.d.ts +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/modes/commands-core.js +19 -4
- package/dist/modes/interactive/agent-bar.d.ts +3 -1
- package/dist/modes/interactive/agent-bar.js +12 -5
- package/dist/modes/interactive/agent-ui.d.ts +21 -3
- package/dist/modes/interactive/agent-ui.js +82 -12
- package/dist/modes/interactive/agent-view.d.ts +9 -0
- package/dist/modes/interactive/agent-view.js +28 -4
- package/dist/modes/interactive/approval-dock.d.ts +51 -0
- package/dist/modes/interactive/approval-dock.js +112 -0
- package/dist/modes/interactive/approval-ui.d.ts +43 -0
- package/dist/modes/interactive/approval-ui.js +64 -0
- package/dist/modes/interactive/commands.js +4 -1
- package/dist/modes/interactive/event-notices.d.ts +6 -1
- package/dist/modes/interactive/event-notices.js +7 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -2
- package/dist/modes/interactive/interactive-mode.js +46 -39
- package/dist/modes/interactive/interrupt-send.d.ts +25 -0
- package/dist/modes/interactive/interrupt-send.js +32 -0
- package/dist/modes/interactive/key-dispatch.d.ts +29 -5
- package/dist/modes/interactive/key-dispatch.js +97 -8
- package/dist/modes/interactive/line/line-mode.d.ts +1 -0
- package/dist/modes/interactive/line/line-mode.js +34 -7
- package/dist/modes/interactive/run-indicator.d.ts +34 -2
- package/dist/modes/interactive/run-indicator.js +62 -8
- package/dist/modes/interactive/session-events.js +5 -1
- package/dist/modes/interactive/startup-header.d.ts +29 -14
- package/dist/modes/interactive/startup-header.js +93 -59
- package/dist/modes/interactive/startup-logo.d.ts +83 -0
- package/dist/modes/interactive/startup-logo.js +183 -0
- package/dist/modes/interactive/status-area.d.ts +18 -0
- package/dist/modes/interactive/status-area.js +67 -1
- package/dist/modes/interactive/status-bar.d.ts +13 -2
- package/dist/modes/interactive/status-bar.js +60 -17
- package/dist/modes/interactive/status-line.d.ts +11 -8
- package/dist/modes/interactive/status-line.js +52 -39
- package/dist/modes/interactive/status-quota.d.ts +58 -0
- package/dist/modes/interactive/status-quota.js +155 -0
- package/dist/modes/interactive/subagent-view.d.ts +1 -0
- package/dist/modes/interactive/subagent-view.js +8 -0
- package/dist/modes/interactive/task-background.d.ts +31 -0
- package/dist/modes/interactive/task-background.js +68 -0
- package/dist/modes/interactive/tool-view.d.ts +7 -1
- package/dist/modes/interactive/tool-view.js +24 -1
- package/dist/modes/print/print-mode.d.ts +11 -0
- package/dist/modes/print/print-mode.js +36 -1
- package/dist/modes/rpc/commands.d.ts +4 -1
- package/dist/modes/rpc/commands.js +24 -2
- package/dist/rpc.d.ts +20 -0
- package/dist/rpc.js +3 -0
- package/dist/tools/task-ctl.d.ts +2 -0
- package/dist/tools/task-ctl.js +7 -2
- package/dist/tools/task.d.ts +11 -0
- package/dist/tools/task.js +20 -2
- package/dist/tools/types.d.ts +6 -0
- package/dist/tui/components/editor.d.ts +2 -0
- package/dist/tui/components/editor.js +4 -0
- package/dist/tui/components/loader.d.ts +5 -1
- package/dist/tui/components/loader.js +18 -5
- package/dist/tui/keybindings.d.ts +15 -3
- package/dist/tui/keybindings.js +15 -3
- package/docs/agents.md +52 -28
- package/docs/en/host-api.md +5 -1
- package/docs/en/providers.md +1 -1
- package/docs/en/rpc.md +28 -14
- package/docs/en/sessions.md +3 -1
- package/docs/en/tui.md +106 -70
- package/docs/host-api.md +5 -1
- package/docs/providers.md +4 -2
- package/docs/rpc.md +28 -14
- package/docs/session-format.md +1 -1
- package/docs/sessions.md +2 -1
- package/docs/tui-design.md +42 -29
- package/docs/tui.md +106 -70
- package/package.json +1 -1
|
@@ -9,9 +9,16 @@
|
|
|
9
9
|
* 助手在流式输出 → 末块是思考 `思考中`,否则 `回复中 · ↓≈1.2k`(本条输出的估算 token,4 字符 ≈ 1)
|
|
10
10
|
* 其余(等首个 token)→ `思考中`
|
|
11
11
|
* 运行或压缩时 Loader 才挂进槽位;附加项 `Esc 中断`。
|
|
12
|
+
* [W7-A] 有该显示的子 Agent 任务时再带可丢弃的 `↓ Agent 栏`(窄屏先丢它);任务随时出现 / 结束,
|
|
13
|
+
* 每帧对一次,变了才换动词。
|
|
14
|
+
* [W7-C] 有阻塞中的前台任务时可丢弃项前面加 `Ctrl+B 转后台`;有停靠的审批时 `↓ Agent 栏` 换成
|
|
15
|
+
* `↓ 处理审批`。顺序 `Esc 中断 · Ctrl+B 转后台 · ↓ Agent 栏`,窄屏从后往前丢。
|
|
16
|
+
*
|
|
17
|
+
* [打断并发送] 输入框有字时可丢弃项最前面加 `Enter 排队 · Ctrl+X 打断并发送`(`ui.enterWhileRunning:
|
|
18
|
+
* "interrupt"` 时语义互换),顺序 `Esc 中断 · Enter 排队 · Ctrl+X 打断并发送 · Ctrl+B 转后台 · ↓ Agent 栏`。
|
|
12
19
|
*
|
|
13
20
|
* 排队消息:缩进 2 列(挂在当前回合下),`↳ 插话 / 之后 文本` 整体 muted、标签 dim;超过 3 条首行
|
|
14
|
-
* `… 另 N 条`;末行 `Alt+↑ 取回 · Esc
|
|
21
|
+
* `… 另 N 条`;末行 `Alt+↑ 取回 · Esc 回填并中断`,有插话时中间加 `Ctrl+X 立即发送`(interrupt 模式是 Enter)。
|
|
15
22
|
*/
|
|
16
23
|
import type { SessionEvent } from "../../agent/types.js";
|
|
17
24
|
import { Container, Text, type Loader, type Theme } from "../../tui.js";
|
|
@@ -24,7 +31,13 @@ export interface RunIndicatorDeps {
|
|
|
24
31
|
slot: Container;
|
|
25
32
|
tools: ToolTracker;
|
|
26
33
|
render(): void;
|
|
34
|
+
/** [W7-C] `app.tasks.background` 的按键标签(`Ctrl+B`);没有绑定时不提示。 */
|
|
35
|
+
backgroundKey?: string;
|
|
36
|
+
/** `app.message.interrupt` 的按键标签(`Ctrl+X`);没有绑定时不提示。 */
|
|
37
|
+
interruptKey?: string;
|
|
27
38
|
}
|
|
39
|
+
/** 运行中 Enter 的语义(`ui.enterWhileRunning`)。 */
|
|
40
|
+
export type EnterMode = "queue" | "interrupt";
|
|
28
41
|
export declare class RunIndicator {
|
|
29
42
|
private readonly deps;
|
|
30
43
|
running: boolean;
|
|
@@ -32,7 +45,22 @@ export declare class RunIndicator {
|
|
|
32
45
|
private approval;
|
|
33
46
|
private retry;
|
|
34
47
|
private stream;
|
|
48
|
+
/** [W7-A] 有该显示的子 Agent 任务(interactive-mode 在 AgentUi 建好后接上)。 */
|
|
49
|
+
agents: () => boolean;
|
|
50
|
+
/** [W7-C] 有阻塞中的前台任务(Ctrl+B 可转后台)。 */
|
|
51
|
+
background: () => boolean;
|
|
52
|
+
/** [W7-C] 有停靠在 Agent 栏的审批。 */
|
|
53
|
+
docked: () => boolean;
|
|
54
|
+
/** 输入框有字(显示 Enter / 专用键的提示)。 */
|
|
55
|
+
draft: () => boolean;
|
|
56
|
+
/** 运行中 Enter 的语义。 */
|
|
57
|
+
enterMode: () => EnterMode;
|
|
58
|
+
private hintKey;
|
|
35
59
|
constructor(deps: RunIndicatorDeps);
|
|
60
|
+
/** 可丢弃提示变了才换动词(每帧、输入框内容变化时调)。 */
|
|
61
|
+
refreshHints(): void;
|
|
62
|
+
/** 可丢弃的按键提示(按丢弃的逆序排列)。 */
|
|
63
|
+
private optionalHints;
|
|
36
64
|
get busy(): boolean;
|
|
37
65
|
setApproval(open: boolean): void;
|
|
38
66
|
/** 会话切换后清零。 */
|
|
@@ -46,6 +74,10 @@ export declare class RunIndicator {
|
|
|
46
74
|
/** 排队消息(插话 / 之后),挂在 Loader 之上。 */
|
|
47
75
|
export declare class QueueView extends Text {
|
|
48
76
|
private readonly theme;
|
|
49
|
-
|
|
77
|
+
private readonly nowKey;
|
|
78
|
+
/**
|
|
79
|
+
* @param nowKey 立即送出排队插话的键(专用键;interrupt 模式下是 Enter);undefined 不提示。
|
|
80
|
+
*/
|
|
81
|
+
constructor(theme: Theme, nowKey?: () => string | undefined);
|
|
50
82
|
setQueue(steering: readonly string[], followUp: readonly string[]): void;
|
|
51
83
|
}
|
|
@@ -9,9 +9,16 @@
|
|
|
9
9
|
* 助手在流式输出 → 末块是思考 `思考中`,否则 `回复中 · ↓≈1.2k`(本条输出的估算 token,4 字符 ≈ 1)
|
|
10
10
|
* 其余(等首个 token)→ `思考中`
|
|
11
11
|
* 运行或压缩时 Loader 才挂进槽位;附加项 `Esc 中断`。
|
|
12
|
+
* [W7-A] 有该显示的子 Agent 任务时再带可丢弃的 `↓ Agent 栏`(窄屏先丢它);任务随时出现 / 结束,
|
|
13
|
+
* 每帧对一次,变了才换动词。
|
|
14
|
+
* [W7-C] 有阻塞中的前台任务时可丢弃项前面加 `Ctrl+B 转后台`;有停靠的审批时 `↓ Agent 栏` 换成
|
|
15
|
+
* `↓ 处理审批`。顺序 `Esc 中断 · Ctrl+B 转后台 · ↓ Agent 栏`,窄屏从后往前丢。
|
|
16
|
+
*
|
|
17
|
+
* [打断并发送] 输入框有字时可丢弃项最前面加 `Enter 排队 · Ctrl+X 打断并发送`(`ui.enterWhileRunning:
|
|
18
|
+
* "interrupt"` 时语义互换),顺序 `Esc 中断 · Enter 排队 · Ctrl+X 打断并发送 · Ctrl+B 转后台 · ↓ Agent 栏`。
|
|
12
19
|
*
|
|
13
20
|
* 排队消息:缩进 2 列(挂在当前回合下),`↳ 插话 / 之后 文本` 整体 muted、标签 dim;超过 3 条首行
|
|
14
|
-
* `… 另 N 条`;末行 `Alt+↑ 取回 · Esc
|
|
21
|
+
* `… 另 N 条`;末行 `Alt+↑ 取回 · Esc 回填并中断`,有插话时中间加 `Ctrl+X 立即发送`(interrupt 模式是 Enter)。
|
|
15
22
|
*/
|
|
16
23
|
import { msg } from "../../i18n/index.js";
|
|
17
24
|
import { Container, Text } from "../../tui.js";
|
|
@@ -48,8 +55,41 @@ export class RunIndicator {
|
|
|
48
55
|
approval = false;
|
|
49
56
|
retry;
|
|
50
57
|
stream;
|
|
58
|
+
/** [W7-A] 有该显示的子 Agent 任务(interactive-mode 在 AgentUi 建好后接上)。 */
|
|
59
|
+
agents = () => false;
|
|
60
|
+
/** [W7-C] 有阻塞中的前台任务(Ctrl+B 可转后台)。 */
|
|
61
|
+
background = () => false;
|
|
62
|
+
/** [W7-C] 有停靠在 Agent 栏的审批。 */
|
|
63
|
+
docked = () => false;
|
|
64
|
+
/** 输入框有字(显示 Enter / 专用键的提示)。 */
|
|
65
|
+
draft = () => false;
|
|
66
|
+
/** 运行中 Enter 的语义。 */
|
|
67
|
+
enterMode = () => "queue";
|
|
68
|
+
hintKey = "";
|
|
51
69
|
constructor(deps) {
|
|
52
70
|
this.deps = deps;
|
|
71
|
+
deps.loader.onFrame(() => this.refreshHints());
|
|
72
|
+
}
|
|
73
|
+
/** 可丢弃提示变了才换动词(每帧、输入框内容变化时调)。 */
|
|
74
|
+
refreshHints() {
|
|
75
|
+
if (this.busy && !this.approval && this.optionalHints().join("\n") !== this.hintKey)
|
|
76
|
+
this.applyVerb();
|
|
77
|
+
}
|
|
78
|
+
/** 可丢弃的按键提示(按丢弃的逆序排列)。 */
|
|
79
|
+
optionalHints() {
|
|
80
|
+
const down = this.deps.theme.glyphs.arrowDown;
|
|
81
|
+
const key = this.deps.backgroundKey;
|
|
82
|
+
const out = [];
|
|
83
|
+
const send = this.deps.interruptKey;
|
|
84
|
+
if (send !== undefined && this.draft())
|
|
85
|
+
out.push(msg().interactive.view.run.sendHint(this.enterMode(), send));
|
|
86
|
+
if (key !== undefined && this.background())
|
|
87
|
+
out.push(msg().agents.background.runHint(key));
|
|
88
|
+
if (this.docked())
|
|
89
|
+
out.push(msg().agents.approval.runHint(down));
|
|
90
|
+
else if (this.agents())
|
|
91
|
+
out.push(msg().agents.bar.runHint(down));
|
|
92
|
+
return out;
|
|
53
93
|
}
|
|
54
94
|
get busy() {
|
|
55
95
|
return this.running || this.compacting;
|
|
@@ -133,17 +173,21 @@ export class RunIndicator {
|
|
|
133
173
|
const m = msg().interactive.view.run;
|
|
134
174
|
const esc = [m.esc];
|
|
135
175
|
if (this.approval) {
|
|
176
|
+
this.hintKey = "";
|
|
136
177
|
loader.setVerb(m.awaitingApproval);
|
|
137
178
|
return;
|
|
138
179
|
}
|
|
180
|
+
const hints = this.optionalHints();
|
|
181
|
+
this.hintKey = hints.join("\n");
|
|
182
|
+
const optional = hints.length > 0 ? { optional: hints } : {};
|
|
139
183
|
const running = tools.running();
|
|
140
184
|
const top = running.filter((view) => !running.some((p) => p.children.includes(view)));
|
|
141
185
|
if (top.length === 1) {
|
|
142
|
-
loader.setVerb(m.runningTool(top[0].toolName), esc);
|
|
186
|
+
loader.setVerb(m.runningTool(top[0].toolName), esc, optional);
|
|
143
187
|
return;
|
|
144
188
|
}
|
|
145
189
|
if (top.length > 1) {
|
|
146
|
-
loader.setVerb(m.runningTools(top.length), esc);
|
|
190
|
+
loader.setVerb(m.runningTools(top.length), esc, optional);
|
|
147
191
|
return;
|
|
148
192
|
}
|
|
149
193
|
if (this.retry !== undefined) {
|
|
@@ -154,23 +198,28 @@ export class RunIndicator {
|
|
|
154
198
|
return;
|
|
155
199
|
}
|
|
156
200
|
if (this.compacting) {
|
|
157
|
-
loader.setVerb(m.compacting, esc);
|
|
201
|
+
loader.setVerb(m.compacting, esc, optional);
|
|
158
202
|
return;
|
|
159
203
|
}
|
|
160
204
|
const stream = this.stream;
|
|
161
205
|
if (stream !== undefined && !stream.thinking && stream.tokens > 0) {
|
|
162
|
-
loader.setVerb(m.replying, [`↓≈${formatTokens(stream.tokens)}`, ...esc]);
|
|
206
|
+
loader.setVerb(m.replying, [`↓≈${formatTokens(stream.tokens)}`, ...esc], optional);
|
|
163
207
|
return;
|
|
164
208
|
}
|
|
165
|
-
loader.setVerb(m.thinking, esc);
|
|
209
|
+
loader.setVerb(m.thinking, esc, optional);
|
|
166
210
|
}
|
|
167
211
|
}
|
|
168
212
|
/** 排队消息(插话 / 之后),挂在 Loader 之上。 */
|
|
169
213
|
export class QueueView extends Text {
|
|
170
214
|
theme;
|
|
171
|
-
|
|
215
|
+
nowKey;
|
|
216
|
+
/**
|
|
217
|
+
* @param nowKey 立即送出排队插话的键(专用键;interrupt 模式下是 Enter);undefined 不提示。
|
|
218
|
+
*/
|
|
219
|
+
constructor(theme, nowKey = () => undefined) {
|
|
172
220
|
super("");
|
|
173
221
|
this.theme = theme;
|
|
222
|
+
this.nowKey = nowKey;
|
|
174
223
|
}
|
|
175
224
|
setQueue(steering, followUp) {
|
|
176
225
|
const t = this.theme;
|
|
@@ -185,7 +234,12 @@ export class QueueView extends Text {
|
|
|
185
234
|
t.fg("dim", msg().interactive.view.run.queueMore(t.glyphs.ellipsis, rows.length - shown.length)));
|
|
186
235
|
}
|
|
187
236
|
if (rows.length > 0) {
|
|
188
|
-
|
|
237
|
+
const run = msg().interactive.view.run;
|
|
238
|
+
const now = steering.length > 0 ? this.nowKey() : undefined;
|
|
239
|
+
const hint = now === undefined
|
|
240
|
+
? run.queueHint(t.glyphs.arrowUp)
|
|
241
|
+
: run.queueHintNow(t.glyphs.arrowUp, now);
|
|
242
|
+
shown.push(" " + t.fg("dim", hint));
|
|
189
243
|
}
|
|
190
244
|
this.setText(shown.join("\n"));
|
|
191
245
|
}
|
|
@@ -9,8 +9,12 @@ export function createSessionEventHandler(deps) {
|
|
|
9
9
|
const { view, tools, status, area, agentUi, indicator, session, setQueue, notice, render } = deps;
|
|
10
10
|
const images = new ImageBudgetNotices((text) => notice("info", text));
|
|
11
11
|
return (event) => {
|
|
12
|
-
if (area.onEvent(event) || agentUi().onEvent(event))
|
|
12
|
+
if (area.onEvent(event) || agentUi().onEvent(event)) {
|
|
13
|
+
// [W7-A] 子任务出现 / 结束:运行提示行的「↓ Agent 栏」跟着变
|
|
14
|
+
if (event.type === "subagent_start" || event.type === "subagent_end")
|
|
15
|
+
indicator.sync();
|
|
13
16
|
return render();
|
|
17
|
+
}
|
|
14
18
|
images.onEvent(event);
|
|
15
19
|
switch (event.type) {
|
|
16
20
|
case "agent_settled":
|
|
@@ -1,32 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 交互模式启动头(终端界面视觉设计 v1 §3.1)。
|
|
3
3
|
*
|
|
4
|
-
* - `normal
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
4
|
+
* - `normal`:无框。宽度 ≥ 72 时「AMA」字符画(startup-logo.ts)在左、信息列在右;48–71 列字符画在上、
|
|
5
|
+
* 空一行、信息列在下;< 48 列退回两行简洁头(不画字符画)。`ui.logo: "off"` 或 `ui.compact` 时只画信息列,
|
|
6
|
+
* 首行 `✻ ama x.y.z`。
|
|
7
|
+
* - 信息列:标题(版本)、模型 · 思考、目录 · 信任(来源)、模式 · 预设 · codemode、已加载 / 宿主 / 警告
|
|
8
|
+
* (有才画)、按键提示。
|
|
9
|
+
* - `header`:一行 `✻ ama x.y.z · 模型 · 模式 · /help`。`silent` 不输出(调用方不加本组件)。
|
|
10
|
+
* - 着色:字符画按列 accent → user → tool 渐变;`✻` accent、标题粗体;模型 accent;已信任 success / 未信任
|
|
11
|
+
* warning;Bypass 模式 warning;按键提示与分隔点 dim。路径过长时从左截断(`…/armadra-agent`)。
|
|
12
|
+
* - 启动动画通过 `setSweep` 改字符画的扫描位置(undefined = 定格),信息列不变。
|
|
9
13
|
*/
|
|
10
14
|
import type { StartupInfo } from "../../cli/startup-screen.js";
|
|
11
15
|
import { type Component, type Theme } from "../../tui.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
import { type SweepTarget } from "./startup-logo.js";
|
|
17
|
+
/** 字符画与信息列并排的最小宽度。 */
|
|
18
|
+
export declare const HEADER_SIDE_MIN_WIDTH = 72;
|
|
19
|
+
/** 画字符画的最小宽度;更窄时两行简洁头。 */
|
|
20
|
+
export declare const HEADER_LOGO_MIN_WIDTH = 48;
|
|
16
21
|
export interface StartupHeaderOptions {
|
|
17
22
|
theme: Theme;
|
|
18
23
|
level: "normal" | "header";
|
|
19
|
-
/** `ui.compact
|
|
20
|
-
compact?: boolean;
|
|
24
|
+
/** `ui.compact`:不画字符画。 */
|
|
25
|
+
compact?: boolean | undefined;
|
|
26
|
+
/** `ui.logo`,缺省 auto;off 不画字符画。 */
|
|
27
|
+
logo?: "auto" | "off" | undefined;
|
|
21
28
|
}
|
|
22
29
|
/** 从左截断到 `width` 列(保留末尾,前面加省略号)。 */
|
|
23
30
|
export declare function truncateLeft(text: string, width: number, ellipsis?: string): string;
|
|
24
|
-
export declare class StartupHeader implements Component {
|
|
31
|
+
export declare class StartupHeader implements Component, SweepTarget {
|
|
25
32
|
private readonly info;
|
|
26
33
|
private readonly options;
|
|
27
34
|
private cache;
|
|
35
|
+
private sweep;
|
|
28
36
|
constructor(info: StartupInfo, options: StartupHeaderOptions);
|
|
29
37
|
private get theme();
|
|
38
|
+
/** 这个宽度下是否画字符画(动画只在画了时播放)。 */
|
|
39
|
+
hasLogo(width: number): boolean;
|
|
40
|
+
setSweep(sweep: number | undefined): void;
|
|
30
41
|
private title;
|
|
31
42
|
private modeText;
|
|
32
43
|
private trustText;
|
|
@@ -34,7 +45,11 @@ export declare class StartupHeader implements Component {
|
|
|
34
45
|
private codemodeText;
|
|
35
46
|
render(width: number): string[];
|
|
36
47
|
private headerLine;
|
|
37
|
-
|
|
38
|
-
private
|
|
48
|
+
/** < 48 列:两行(版本 · 模型 · 思考 / 模式 · 目录 · 信任),有警告再加一行。 */
|
|
49
|
+
private narrowLines;
|
|
50
|
+
/** 信息列:首行 `title`,其后模型、目录、模式、可选行、按键提示;每行截到 `width`。 */
|
|
51
|
+
private infoLines;
|
|
52
|
+
/** 字符画 + 信息列:够宽并排(顶端对齐),否则上下叠放。 */
|
|
53
|
+
private logoLines;
|
|
39
54
|
invalidate(): void;
|
|
40
55
|
}
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 交互模式启动头(终端界面视觉设计 v1 §3.1)。
|
|
3
3
|
*
|
|
4
|
-
* - `normal
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
4
|
+
* - `normal`:无框。宽度 ≥ 72 时「AMA」字符画(startup-logo.ts)在左、信息列在右;48–71 列字符画在上、
|
|
5
|
+
* 空一行、信息列在下;< 48 列退回两行简洁头(不画字符画)。`ui.logo: "off"` 或 `ui.compact` 时只画信息列,
|
|
6
|
+
* 首行 `✻ ama x.y.z`。
|
|
7
|
+
* - 信息列:标题(版本)、模型 · 思考、目录 · 信任(来源)、模式 · 预设 · codemode、已加载 / 宿主 / 警告
|
|
8
|
+
* (有才画)、按键提示。
|
|
9
|
+
* - `header`:一行 `✻ ama x.y.z · 模型 · 模式 · /help`。`silent` 不输出(调用方不加本组件)。
|
|
10
|
+
* - 着色:字符画按列 accent → user → tool 渐变;`✻` accent、标题粗体;模型 accent;已信任 success / 未信任
|
|
11
|
+
* warning;Bypass 模式 warning;按键提示与分隔点 dim。路径过长时从左截断(`…/armadra-agent`)。
|
|
12
|
+
* - 启动动画通过 `setSweep` 改字符画的扫描位置(undefined = 定格),信息列不变。
|
|
9
13
|
*/
|
|
10
14
|
import { msg } from "../../i18n/index.js";
|
|
11
15
|
import { permissionModeLabel } from "../../permissions/modes.js";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
import { truncateToWidth, visibleWidth } from "../../tui.js";
|
|
17
|
+
import { logoRows, logoWidth, paintLogo } from "./startup-logo.js";
|
|
18
|
+
/** 字符画与信息列并排的最小宽度。 */
|
|
19
|
+
export const HEADER_SIDE_MIN_WIDTH = 72;
|
|
20
|
+
/** 画字符画的最小宽度;更窄时两行简洁头。 */
|
|
21
|
+
export const HEADER_LOGO_MIN_WIDTH = 48;
|
|
22
|
+
/** 字符画与信息列之间的空列。 */
|
|
23
|
+
const GAP = 3;
|
|
17
24
|
/** 从左截断到 `width` 列(保留末尾,前面加省略号)。 */
|
|
18
25
|
export function truncateLeft(text, width, ellipsis = "…") {
|
|
19
26
|
if (visibleWidth(text) <= width)
|
|
@@ -33,6 +40,13 @@ export function truncateLeft(text, width, ellipsis = "…") {
|
|
|
33
40
|
}
|
|
34
41
|
return ellipsis + out;
|
|
35
42
|
}
|
|
43
|
+
/** 按 ` · ` 分段的提示放不下时从末尾整段去掉(至少留第一段,再不够由调用方截断)。 */
|
|
44
|
+
function fitSegments(text, width) {
|
|
45
|
+
const parts = text.split(" · ");
|
|
46
|
+
while (parts.length > 1 && visibleWidth(parts.join(" · ")) > width)
|
|
47
|
+
parts.pop();
|
|
48
|
+
return parts.join(" · ");
|
|
49
|
+
}
|
|
36
50
|
/** 去掉模型引用里的渠道(`@messages`)。 */
|
|
37
51
|
function withoutChannel(model) {
|
|
38
52
|
const at = model.indexOf("@");
|
|
@@ -42,6 +56,7 @@ export class StartupHeader {
|
|
|
42
56
|
info;
|
|
43
57
|
options;
|
|
44
58
|
cache = null;
|
|
59
|
+
sweep;
|
|
45
60
|
constructor(info, options) {
|
|
46
61
|
this.info = info;
|
|
47
62
|
this.options = options;
|
|
@@ -49,6 +64,19 @@ export class StartupHeader {
|
|
|
49
64
|
get theme() {
|
|
50
65
|
return this.options.theme;
|
|
51
66
|
}
|
|
67
|
+
/** 这个宽度下是否画字符画(动画只在画了时播放)。 */
|
|
68
|
+
hasLogo(width) {
|
|
69
|
+
return (this.options.level === "normal" &&
|
|
70
|
+
this.options.logo !== "off" &&
|
|
71
|
+
this.options.compact !== true &&
|
|
72
|
+
width >= HEADER_LOGO_MIN_WIDTH);
|
|
73
|
+
}
|
|
74
|
+
setSweep(sweep) {
|
|
75
|
+
if (sweep === this.sweep)
|
|
76
|
+
return;
|
|
77
|
+
this.sweep = sweep;
|
|
78
|
+
this.cache = null;
|
|
79
|
+
}
|
|
52
80
|
title() {
|
|
53
81
|
const t = this.theme;
|
|
54
82
|
return `${t.fg("accent", t.glyphs.thinking)} ${t.bold(`ama ${this.info.version}`)}`;
|
|
@@ -82,11 +110,15 @@ export class StartupHeader {
|
|
|
82
110
|
render(width) {
|
|
83
111
|
if (this.cache?.width === width)
|
|
84
112
|
return this.cache.lines;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
113
|
+
let lines;
|
|
114
|
+
if (this.options.level === "header")
|
|
115
|
+
lines = [this.headerLine(width)];
|
|
116
|
+
else if (width < HEADER_LOGO_MIN_WIDTH)
|
|
117
|
+
lines = this.narrowLines(width);
|
|
118
|
+
else if (!this.hasLogo(width))
|
|
119
|
+
lines = this.infoLines(width, this.title());
|
|
120
|
+
else
|
|
121
|
+
lines = this.logoLines(width);
|
|
90
122
|
this.cache = { width, lines };
|
|
91
123
|
return lines;
|
|
92
124
|
}
|
|
@@ -101,66 +133,68 @@ export class StartupHeader {
|
|
|
101
133
|
];
|
|
102
134
|
return truncateToWidth(parts.join(sep), width);
|
|
103
135
|
}
|
|
104
|
-
|
|
136
|
+
/** < 48 列:两行(版本 · 模型 · 思考 / 模式 · 目录 · 信任),有警告再加一行。 */
|
|
137
|
+
narrowLines(width) {
|
|
105
138
|
const t = this.theme;
|
|
106
139
|
const sep = t.fg("dim", " · ");
|
|
107
|
-
const ellipsis = t.glyphs.ellipsis;
|
|
108
140
|
const { info } = this;
|
|
109
|
-
const lines = [
|
|
110
|
-
|
|
141
|
+
const lines = [
|
|
142
|
+
[this.title(), t.fg("accent", withoutChannel(info.model)), info.thinking].join(sep),
|
|
143
|
+
];
|
|
144
|
+
const mode = this.modeText();
|
|
111
145
|
const trust = this.trustText(false);
|
|
112
|
-
const room = Math.max(4, width - visibleWidth(trust) -
|
|
113
|
-
lines.push(truncateLeft(info.cwd, room, ellipsis)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (loaded !== undefined)
|
|
118
|
-
lines.push(loaded);
|
|
119
|
-
const m = msg().interactive.startup.header;
|
|
120
|
-
if (info.host !== undefined)
|
|
121
|
-
lines.push(m.host(info.host));
|
|
122
|
-
if (info.warnings > 0)
|
|
123
|
-
lines.push(t.fg("warning", m.warnings(info.warnings)));
|
|
124
|
-
lines.push(t.fg("dim", m.hintCompact));
|
|
146
|
+
const room = Math.max(4, width - visibleWidth(mode) - visibleWidth(trust) - 6);
|
|
147
|
+
lines.push([mode, truncateLeft(info.cwd, room, t.glyphs.ellipsis), trust].join(sep));
|
|
148
|
+
if (info.warnings > 0) {
|
|
149
|
+
lines.push(t.fg("warning", msg().interactive.startup.header.warnings(info.warnings)));
|
|
150
|
+
}
|
|
125
151
|
return lines.map((line) => truncateToWidth(line, width));
|
|
126
152
|
}
|
|
127
|
-
|
|
153
|
+
/** 信息列:首行 `title`,其后模型、目录、模式、可选行、按键提示;每行截到 `width`。 */
|
|
154
|
+
infoLines(width, title) {
|
|
128
155
|
const t = this.theme;
|
|
129
156
|
const sep = t.fg("dim", " · ");
|
|
130
157
|
const { info } = this;
|
|
131
158
|
const m = msg().interactive.startup.header;
|
|
132
|
-
const boxWidth = Math.min(width, HEADER_BOX_MAX_WIDTH);
|
|
133
|
-
// 键列至少 6 列(「已加载」的宽度),没有这一行时也对齐;英文键更宽时按最宽的键
|
|
134
|
-
const keyWidth = Math.max(6, ...[m.keyModel, m.keyDir, m.keyMode, m.keyLoaded, m.keyHost, m.keyWarnings].map((k) => visibleWidth(k)));
|
|
135
|
-
/** 框内文本宽:边框 2 + 内边距 2;键列 + 间隔 1。 */
|
|
136
|
-
const valueWidth = boxWidth - 4 - keyWidth - 1;
|
|
137
159
|
const trust = this.trustText(true);
|
|
138
|
-
const cwdRoom = Math.max(4,
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
.join(sep),
|
|
147
|
-
},
|
|
160
|
+
const cwdRoom = Math.max(4, width - visibleWidth(trust) - 3);
|
|
161
|
+
const lines = [
|
|
162
|
+
title,
|
|
163
|
+
t.fg("accent", info.model) + sep + m.thinking(info.thinking),
|
|
164
|
+
truncateLeft(info.cwd, cwdRoom, t.glyphs.ellipsis) + sep + trust,
|
|
165
|
+
[this.modeText(), m.preset(info.preset), this.codemodeText()]
|
|
166
|
+
.filter((p) => p !== undefined)
|
|
167
|
+
.join(sep),
|
|
148
168
|
];
|
|
149
169
|
const loaded = this.loadedText();
|
|
150
170
|
if (loaded !== undefined)
|
|
151
|
-
|
|
171
|
+
lines.push(loaded);
|
|
152
172
|
if (info.host !== undefined)
|
|
153
|
-
|
|
154
|
-
if (info.warnings > 0)
|
|
155
|
-
|
|
173
|
+
lines.push(m.host(info.host));
|
|
174
|
+
if (info.warnings > 0)
|
|
175
|
+
lines.push(t.fg("warning", m.warnings(info.warnings)));
|
|
176
|
+
lines.push(t.fg("dim", fitSegments(m.hint, width)));
|
|
177
|
+
return lines.map((line) => truncateToWidth(line, width));
|
|
178
|
+
}
|
|
179
|
+
/** 字符画 + 信息列:够宽并排(顶端对齐),否则上下叠放。 */
|
|
180
|
+
logoLines(width) {
|
|
181
|
+
const t = this.theme;
|
|
182
|
+
const logo = paintLogo(t, this.sweep);
|
|
183
|
+
const logoCols = logoWidth(logoRows(t));
|
|
184
|
+
const title = `${t.bold("ama")} ${t.fg("dim", this.info.version)}`;
|
|
185
|
+
if (width < HEADER_SIDE_MIN_WIDTH) {
|
|
186
|
+
return [...logo, "", ...this.infoLines(width, title)];
|
|
187
|
+
}
|
|
188
|
+
const info = this.infoLines(width - logoCols - GAP, title);
|
|
189
|
+
const rows = Math.max(logo.length, info.length);
|
|
190
|
+
const blank = " ".repeat(logoCols);
|
|
191
|
+
const gap = " ".repeat(GAP);
|
|
192
|
+
const lines = [];
|
|
193
|
+
for (let i = 0; i < rows; i++) {
|
|
194
|
+
const right = info[i];
|
|
195
|
+
lines.push(right === undefined ? (logo[i] ?? "") : (logo[i] ?? blank) + gap + right);
|
|
156
196
|
}
|
|
157
|
-
|
|
158
|
-
const kv = new KeyValue(padded, { theme: t, gap: 1, maxKeyRatio: 1 });
|
|
159
|
-
const body = {
|
|
160
|
-
render: (inner) => [this.title(), "", ...kv.render(inner), "", t.fg("dim", m.hint)],
|
|
161
|
-
invalidate: () => kv.invalidate(),
|
|
162
|
-
};
|
|
163
|
-
return new Box(body, { theme: t }).render(boxWidth);
|
|
197
|
+
return lines;
|
|
164
198
|
}
|
|
165
199
|
invalidate() {
|
|
166
200
|
this.cache = null;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 启动头的「AMA」字符画与启动动画(终端界面视觉设计 v1 §3.1.1)。
|
|
3
|
+
*
|
|
4
|
+
* - 字形:缺省 5 行实心块字(`█▀▄`,宽 25);ASCII 回退(`ui.ascii` / `AMA_ASCII=1`)用 figlet 风格的
|
|
5
|
+
* `_ / \ |`(同为 5 行、宽 25)。
|
|
6
|
+
* - 着色:按列三段渐变 accent → user → tool(主题已有颜色,深浅主题 / 16 色都有对应值);无色主题原样输出。
|
|
7
|
+
* - 动画(点亮扫描):未扫到的列 dim,扫描头 3 列 text 粗体高亮,扫过的列定格为渐变色;每帧前进 1 列,
|
|
8
|
+
* 约 30 帧 × 32 ms ≈ 1 s,结束停在定格帧。动画只重画启动头所在行(差分渲染器原地改写),所以只在
|
|
9
|
+
* 整屏内容装得下视口时播放;任何时候 `finish()` 立即定格。
|
|
10
|
+
*/
|
|
11
|
+
import type { Theme, TUI } from "../../tui.js";
|
|
12
|
+
/** 缺省字形(实心块字)。 */
|
|
13
|
+
export declare const LOGO_BLOCK: readonly string[];
|
|
14
|
+
/** 纯 ASCII 字形(只用 `_ / \ |`)。 */
|
|
15
|
+
export declare const LOGO_ASCII: readonly string[];
|
|
16
|
+
/** 扫描头的宽度(列)。 */
|
|
17
|
+
export declare const SWEEP_BAND = 3;
|
|
18
|
+
/** 帧间隔(ms);帧数 = 字形宽 + 扫描头宽,总时长约 1 s(上限 1.2 s)。 */
|
|
19
|
+
export declare const FRAME_MS = 32;
|
|
20
|
+
export declare function logoRows(theme: Theme): readonly string[];
|
|
21
|
+
/** 字形的列宽(逐字符都是 1 列)。 */
|
|
22
|
+
export declare function logoWidth(rows: readonly string[]): number;
|
|
23
|
+
/** 动画总帧数(不含定格帧)。 */
|
|
24
|
+
export declare function sweepFrames(theme: Theme): number;
|
|
25
|
+
/** 画字形:同样式的连续列合成一段,空格不着色;返回的每行都补足到字形宽。 */
|
|
26
|
+
export declare function paintLogo(theme: Theme, sweep?: number): string[];
|
|
27
|
+
/** 动画画到的目标(启动头)。 */
|
|
28
|
+
export interface SweepTarget {
|
|
29
|
+
setSweep(sweep: number | undefined): void;
|
|
30
|
+
}
|
|
31
|
+
export interface LogoAnimationOptions {
|
|
32
|
+
frames: number;
|
|
33
|
+
intervalMs?: number;
|
|
34
|
+
/** 每帧之后请求重绘。 */
|
|
35
|
+
render(): void;
|
|
36
|
+
/** 每帧之前检查:返回 false(内容已超出视口等)立即定格。 */
|
|
37
|
+
canContinue?(): boolean;
|
|
38
|
+
/** 定格之后(走完或提前结束)调用一次。 */
|
|
39
|
+
onDone?(): void;
|
|
40
|
+
}
|
|
41
|
+
/** 一次性的点亮扫描:`start()` 从第 0 帧开始,走完或 `finish()` 后定格,不会再动。 */
|
|
42
|
+
export declare class LogoAnimation {
|
|
43
|
+
private readonly target;
|
|
44
|
+
private readonly options;
|
|
45
|
+
private frame;
|
|
46
|
+
private timer;
|
|
47
|
+
private done;
|
|
48
|
+
constructor(target: SweepTarget, options: LogoAnimationOptions);
|
|
49
|
+
get running(): boolean;
|
|
50
|
+
start(): void;
|
|
51
|
+
private step;
|
|
52
|
+
/** 立即定格(按键、退出、走完);重复调用无副作用。 */
|
|
53
|
+
finish(): void;
|
|
54
|
+
}
|
|
55
|
+
/** 是否播放启动动画的输入(interactive-mode 汇总)。 */
|
|
56
|
+
export interface AnimationGate {
|
|
57
|
+
/** `ui.animation`。 */
|
|
58
|
+
animation: boolean | undefined;
|
|
59
|
+
/** 启动头确实画了 logo(normal 档、`ui.logo` 非 off、非 compact、宽度够)。 */
|
|
60
|
+
hasLogo: boolean;
|
|
61
|
+
colors: number;
|
|
62
|
+
io: {
|
|
63
|
+
stdoutIsTTY: boolean;
|
|
64
|
+
};
|
|
65
|
+
/** 嵌入宿主(profile.host)。 */
|
|
66
|
+
host: boolean;
|
|
67
|
+
/** 启动即提交的提示(`ama "..."`);非空不播放。 */
|
|
68
|
+
prompt: string | undefined;
|
|
69
|
+
env: NodeJS.ProcessEnv;
|
|
70
|
+
rows: number;
|
|
71
|
+
}
|
|
72
|
+
/** 终端低于这个行数不播放(启动头 + 输入框 + 状态栏放不下时差分渲染会把动画帧滚进回滚区)。 */
|
|
73
|
+
export declare const MIN_ANIMATION_ROWS = 16;
|
|
74
|
+
export declare function shouldAnimate(gate: AnimationGate): boolean;
|
|
75
|
+
/** 能播放动画的启动头。 */
|
|
76
|
+
export interface AnimatedHeader extends SweepTarget {
|
|
77
|
+
hasLogo(width: number): boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 界面启动后调用:条件满足就播放点亮扫描并返回动画(退出时 `finish()`),否则返回 undefined、启动头
|
|
81
|
+
* 直接是定格帧。按任意键立即定格——监听器不吞键,按键照常进输入框。
|
|
82
|
+
*/
|
|
83
|
+
export declare function playStartupAnimation(tui: TUI, header: AnimatedHeader | undefined, theme: Theme, conditions: Omit<AnimationGate, "hasLogo" | "colors" | "rows">): LogoAnimation | undefined;
|