@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
|
@@ -0,0 +1,183 @@
|
|
|
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
|
+
/** 缺省字形(实心块字)。 */
|
|
12
|
+
export const LOGO_BLOCK = [
|
|
13
|
+
" ▄███▄ ██▄ ▄██ ▄███▄",
|
|
14
|
+
"██▀ ▀██ ███▄ ▄███ ██▀ ▀██",
|
|
15
|
+
"███████ ██ ▀█▀ ██ ███████",
|
|
16
|
+
"██ ██ ██ ██ ██ ██",
|
|
17
|
+
"▀▀ ▀▀ ▀▀ ▀▀ ▀▀ ▀▀",
|
|
18
|
+
];
|
|
19
|
+
/** 纯 ASCII 字形(只用 `_ / \ |`)。 */
|
|
20
|
+
export const LOGO_ASCII = [
|
|
21
|
+
" _ __ __ _",
|
|
22
|
+
" / \\ | \\/ | / \\",
|
|
23
|
+
" / _ \\ | |\\/| | / _ \\",
|
|
24
|
+
" / ___ \\| | | |/ ___ \\",
|
|
25
|
+
"/_/ \\_\\_| |_/_/ \\_\\",
|
|
26
|
+
];
|
|
27
|
+
/** 扫描头的宽度(列)。 */
|
|
28
|
+
export const SWEEP_BAND = 3;
|
|
29
|
+
/** 帧间隔(ms);帧数 = 字形宽 + 扫描头宽,总时长约 1 s(上限 1.2 s)。 */
|
|
30
|
+
export const FRAME_MS = 32;
|
|
31
|
+
const GRADIENT = ["accent", "user", "tool"];
|
|
32
|
+
/** 每个字母起始列(渐变按字母分段,不把一个字母劈成两色)。 */
|
|
33
|
+
const LETTER_STARTS = { block: [0, 8, 18], ascii: [0, 8, 16] };
|
|
34
|
+
export function logoRows(theme) {
|
|
35
|
+
return theme.glyphs.ascii ? LOGO_ASCII : LOGO_BLOCK;
|
|
36
|
+
}
|
|
37
|
+
function letterColor(col, ascii) {
|
|
38
|
+
const starts = LETTER_STARTS[ascii ? "ascii" : "block"];
|
|
39
|
+
let index = 0;
|
|
40
|
+
for (let i = 0; i < starts.length; i++)
|
|
41
|
+
if (col >= starts[i])
|
|
42
|
+
index = i;
|
|
43
|
+
return GRADIENT[index];
|
|
44
|
+
}
|
|
45
|
+
/** 字形的列宽(逐字符都是 1 列)。 */
|
|
46
|
+
export function logoWidth(rows) {
|
|
47
|
+
return Math.max(...rows.map((row) => [...row].length));
|
|
48
|
+
}
|
|
49
|
+
/** 动画总帧数(不含定格帧)。 */
|
|
50
|
+
export function sweepFrames(theme) {
|
|
51
|
+
return logoWidth(logoRows(theme)) + SWEEP_BAND;
|
|
52
|
+
}
|
|
53
|
+
/** 第 `col` 列的样式;`sweep` 为扫描头所在列,undefined 为定格。 */
|
|
54
|
+
function styleAt(col, ascii, sweep) {
|
|
55
|
+
const settled = { color: letterColor(col, ascii), bold: true };
|
|
56
|
+
if (sweep === undefined || col <= sweep - SWEEP_BAND)
|
|
57
|
+
return settled;
|
|
58
|
+
if (col <= sweep)
|
|
59
|
+
return { color: "text", bold: true };
|
|
60
|
+
return { color: "dim", bold: false };
|
|
61
|
+
}
|
|
62
|
+
/** 画字形:同样式的连续列合成一段,空格不着色;返回的每行都补足到字形宽。 */
|
|
63
|
+
export function paintLogo(theme, sweep) {
|
|
64
|
+
const rows = logoRows(theme);
|
|
65
|
+
const width = logoWidth(rows);
|
|
66
|
+
return rows.map((row) => {
|
|
67
|
+
const chars = [...row.padEnd(width)];
|
|
68
|
+
let out = "";
|
|
69
|
+
let run = "";
|
|
70
|
+
let runStyle;
|
|
71
|
+
const flush = () => {
|
|
72
|
+
if (run === "")
|
|
73
|
+
return;
|
|
74
|
+
if (runStyle === undefined)
|
|
75
|
+
out += run;
|
|
76
|
+
else {
|
|
77
|
+
const colored = theme.fg(runStyle.color, run);
|
|
78
|
+
out += runStyle.bold ? theme.bold(colored) : colored;
|
|
79
|
+
}
|
|
80
|
+
run = "";
|
|
81
|
+
};
|
|
82
|
+
chars.forEach((ch, col) => {
|
|
83
|
+
const style = ch === " " ? undefined : styleAt(col, theme.glyphs.ascii, sweep);
|
|
84
|
+
if (style?.color !== runStyle?.color || style?.bold !== runStyle?.bold) {
|
|
85
|
+
flush();
|
|
86
|
+
runStyle = style;
|
|
87
|
+
}
|
|
88
|
+
run += ch;
|
|
89
|
+
});
|
|
90
|
+
flush();
|
|
91
|
+
return out;
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
/** 一次性的点亮扫描:`start()` 从第 0 帧开始,走完或 `finish()` 后定格,不会再动。 */
|
|
95
|
+
export class LogoAnimation {
|
|
96
|
+
target;
|
|
97
|
+
options;
|
|
98
|
+
frame = 0;
|
|
99
|
+
timer;
|
|
100
|
+
done = false;
|
|
101
|
+
constructor(target, options) {
|
|
102
|
+
this.target = target;
|
|
103
|
+
this.options = options;
|
|
104
|
+
}
|
|
105
|
+
get running() {
|
|
106
|
+
return !this.done && this.timer !== undefined;
|
|
107
|
+
}
|
|
108
|
+
start() {
|
|
109
|
+
if (this.done || this.timer !== undefined)
|
|
110
|
+
return;
|
|
111
|
+
this.step();
|
|
112
|
+
}
|
|
113
|
+
step() {
|
|
114
|
+
this.timer = undefined;
|
|
115
|
+
if (this.frame >= this.options.frames || this.options.canContinue?.() === false) {
|
|
116
|
+
this.finish();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.target.setSweep(this.frame++);
|
|
120
|
+
this.options.render();
|
|
121
|
+
this.timer = setTimeout(() => this.step(), this.options.intervalMs ?? FRAME_MS);
|
|
122
|
+
this.timer.unref?.();
|
|
123
|
+
}
|
|
124
|
+
/** 立即定格(按键、退出、走完);重复调用无副作用。 */
|
|
125
|
+
finish() {
|
|
126
|
+
if (this.done)
|
|
127
|
+
return;
|
|
128
|
+
this.done = true;
|
|
129
|
+
if (this.timer !== undefined)
|
|
130
|
+
clearTimeout(this.timer);
|
|
131
|
+
this.timer = undefined;
|
|
132
|
+
this.target.setSweep(undefined);
|
|
133
|
+
this.options.render();
|
|
134
|
+
this.options.onDone?.();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** 终端低于这个行数不播放(启动头 + 输入框 + 状态栏放不下时差分渲染会把动画帧滚进回滚区)。 */
|
|
138
|
+
export const MIN_ANIMATION_ROWS = 16;
|
|
139
|
+
export function shouldAnimate(gate) {
|
|
140
|
+
const ci = gate.env["CI"];
|
|
141
|
+
return (gate.animation !== false &&
|
|
142
|
+
gate.hasLogo &&
|
|
143
|
+
gate.colors > 0 &&
|
|
144
|
+
gate.io.stdoutIsTTY &&
|
|
145
|
+
!gate.host &&
|
|
146
|
+
(gate.prompt ?? "").trim() === "" &&
|
|
147
|
+
(ci === undefined || ci === "" || ci === "0" || ci === "false") &&
|
|
148
|
+
gate.rows >= MIN_ANIMATION_ROWS);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 界面启动后调用:条件满足就播放点亮扫描并返回动画(退出时 `finish()`),否则返回 undefined、启动头
|
|
152
|
+
* 直接是定格帧。按任意键立即定格——监听器不吞键,按键照常进输入框。
|
|
153
|
+
*/
|
|
154
|
+
export function playStartupAnimation(tui, header, theme, conditions) {
|
|
155
|
+
const term = tui.terminal;
|
|
156
|
+
const columns = term.columns;
|
|
157
|
+
if (header === undefined)
|
|
158
|
+
return undefined;
|
|
159
|
+
const gate = {
|
|
160
|
+
...conditions,
|
|
161
|
+
hasLogo: header.hasLogo(term.columns),
|
|
162
|
+
colors: theme.caps.colors,
|
|
163
|
+
rows: term.rows,
|
|
164
|
+
};
|
|
165
|
+
// 整屏内容装得下视口,动画帧才只在原地改写、不进回滚区
|
|
166
|
+
const fits = () => tui.render(term.columns).length <= term.rows;
|
|
167
|
+
if (!shouldAnimate(gate) || !fits())
|
|
168
|
+
return undefined;
|
|
169
|
+
let offInput = () => undefined;
|
|
170
|
+
const animation = new LogoAnimation(header, {
|
|
171
|
+
frames: sweepFrames(theme),
|
|
172
|
+
render: () => tui.requestRender(),
|
|
173
|
+
// 改了尺寸(差分渲染要整屏重画)也立即定格
|
|
174
|
+
canContinue: () => term.columns === columns && fits(),
|
|
175
|
+
onDone: () => offInput(),
|
|
176
|
+
});
|
|
177
|
+
offInput = tui.addInputListener(() => {
|
|
178
|
+
animation.finish();
|
|
179
|
+
return false;
|
|
180
|
+
});
|
|
181
|
+
animation.start();
|
|
182
|
+
return animation;
|
|
183
|
+
}
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* session_rewound、/tree 之后;结果变化时重画。
|
|
9
9
|
* - [W5-U] `model_fallback` 之后状态栏模型项显示 `主模型 → 回退模型`,切回主模型(`model_changed`)后消失。
|
|
10
10
|
* - `telemetry_tick`(流式中 ≤ 2 Hz)只重取统计并重画(速率行),状态栏的内容这时不变。
|
|
11
|
+
* - [W6] 订阅配额(status-quota.ts):记住最近一次 `quota_update`(账户级,换会话不清;换到别的供应商不显示),
|
|
12
|
+
* full 时在状态栏下方占第三行、compact 时在行尾追加短项;第三行显示中每分钟重画一次(重置倒计时),
|
|
13
|
+
* 不显示时不挂计时器。Ctrl+G 折叠时第三行与速率行一起收起。
|
|
11
14
|
*/
|
|
12
15
|
import type { AgentSession, SessionEvent } from "../../agent/types.js";
|
|
13
16
|
import type { Runtime } from "../../cli/runtime.js";
|
|
@@ -15,6 +18,9 @@ import type { StatusLineMode } from "../../config/types.js";
|
|
|
15
18
|
import { type Component, type Theme } from "../../tui.js";
|
|
16
19
|
import { StatusBar } from "./status-bar.js";
|
|
17
20
|
import { StatusLine } from "./status-line.js";
|
|
21
|
+
import { QuotaLine, type QuotaView } from "./status-quota.js";
|
|
22
|
+
/** 配额行重画间隔(倒计时只到分钟)。 */
|
|
23
|
+
export declare const QUOTA_TICK_MS = 60000;
|
|
18
24
|
/** 一行提示;空时不占行。 */
|
|
19
25
|
export declare class HintLine implements Component {
|
|
20
26
|
private text;
|
|
@@ -42,6 +48,10 @@ export declare class StatusArea {
|
|
|
42
48
|
readonly hint: HintLine;
|
|
43
49
|
readonly bar: StatusBar;
|
|
44
50
|
readonly rate: StatusLine;
|
|
51
|
+
/** [W6] 订阅配额行(full 第三行)。 */
|
|
52
|
+
readonly quota: QuotaLine;
|
|
53
|
+
private lastQuota;
|
|
54
|
+
private ticker;
|
|
45
55
|
private mode;
|
|
46
56
|
private startedAt;
|
|
47
57
|
private git;
|
|
@@ -52,6 +62,13 @@ export declare class StatusArea {
|
|
|
52
62
|
private fallback;
|
|
53
63
|
private bashSandbox;
|
|
54
64
|
constructor(deps: StatusAreaDeps);
|
|
65
|
+
/** [W6] 当前模型走 ChatGPT 订阅时的配额;codex 还没数据为 pending,SIWC 没数据或非订阅模型不显示。 */
|
|
66
|
+
quotaView(): QuotaView;
|
|
67
|
+
private backendMemo;
|
|
68
|
+
/** 模型的 ChatGPT 订阅后端(渠道 compat `chatgptBackend`);按引用记住上次结果。 */
|
|
69
|
+
private backendOf;
|
|
70
|
+
/** 第三行显示且有重置时间时每分钟重画;否则不挂计时器。 */
|
|
71
|
+
private syncTicker;
|
|
55
72
|
layout(): StatusLineMode;
|
|
56
73
|
/** [W6-A] 界面配置(Agent 栏开关、子 Agent 视图的消息显示选项)。 */
|
|
57
74
|
ui(): NonNullable<Runtime["config"]["ui"]>;
|
|
@@ -64,6 +81,7 @@ export declare class StatusArea {
|
|
|
64
81
|
/** 返回 true:事件已处理完(只需重画),调用方不再分派。 */
|
|
65
82
|
onEvent(event: SessionEvent): boolean;
|
|
66
83
|
dispose(): void;
|
|
84
|
+
private disposeGit;
|
|
67
85
|
private gitView;
|
|
68
86
|
private watchGit;
|
|
69
87
|
}
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
* session_rewound、/tree 之后;结果变化时重画。
|
|
9
9
|
* - [W5-U] `model_fallback` 之后状态栏模型项显示 `主模型 → 回退模型`,切回主模型(`model_changed`)后消失。
|
|
10
10
|
* - `telemetry_tick`(流式中 ≤ 2 Hz)只重取统计并重画(速率行),状态栏的内容这时不变。
|
|
11
|
+
* - [W6] 订阅配额(status-quota.ts):记住最近一次 `quota_update`(账户级,换会话不清;换到别的供应商不显示),
|
|
12
|
+
* full 时在状态栏下方占第三行、compact 时在行尾追加短项;第三行显示中每分钟重画一次(重置倒计时),
|
|
13
|
+
* 不显示时不挂计时器。Ctrl+G 折叠时第三行与速率行一起收起。
|
|
11
14
|
*/
|
|
12
15
|
import { msg } from "../../i18n/index.js";
|
|
13
16
|
import { basename } from "node:path";
|
|
@@ -20,8 +23,11 @@ import { effectiveCodemodeMode } from "../../tools/presets.js";
|
|
|
20
23
|
import { truncateToWidth } from "../../tui.js";
|
|
21
24
|
import { StatusBar } from "./status-bar.js";
|
|
22
25
|
import { StatusLine } from "./status-line.js";
|
|
26
|
+
import { QuotaLine } from "./status-quota.js";
|
|
23
27
|
/** 结束后可能改了工作区的工具。 */
|
|
24
28
|
const WRITE_TOOLS = new Set(["write", "edit", "bash", "task"]);
|
|
29
|
+
/** 配额行重画间隔(倒计时只到分钟)。 */
|
|
30
|
+
export const QUOTA_TICK_MS = 60_000;
|
|
25
31
|
/** 一行提示;空时不占行。 */
|
|
26
32
|
export class HintLine {
|
|
27
33
|
text = "";
|
|
@@ -38,6 +44,10 @@ export class StatusArea {
|
|
|
38
44
|
hint = new HintLine();
|
|
39
45
|
bar;
|
|
40
46
|
rate;
|
|
47
|
+
/** [W6] 订阅配额行(full 第三行)。 */
|
|
48
|
+
quota;
|
|
49
|
+
lastQuota;
|
|
50
|
+
ticker;
|
|
41
51
|
mode;
|
|
42
52
|
startedAt;
|
|
43
53
|
git;
|
|
@@ -79,10 +89,53 @@ export class StatusArea {
|
|
|
79
89
|
.session()
|
|
80
90
|
.getTools()
|
|
81
91
|
.some((tool) => tool.name === "bash"),
|
|
92
|
+
quota: () => this.quotaView(),
|
|
82
93
|
};
|
|
83
94
|
this.bar = new StatusBar(source, deps.theme);
|
|
84
95
|
this.rate = new StatusLine(this.bar, source, deps.theme);
|
|
96
|
+
this.quota = new QuotaLine({ layout: () => this.mode, quota: () => this.quotaView(), now: () => deps.now() }, deps.theme);
|
|
85
97
|
this.watchGit();
|
|
98
|
+
this.syncTicker();
|
|
99
|
+
}
|
|
100
|
+
/** [W6] 当前模型走 ChatGPT 订阅时的配额;codex 还没数据为 pending,SIWC 没数据或非订阅模型不显示。 */
|
|
101
|
+
quotaView() {
|
|
102
|
+
const model = this.deps.session().state.model;
|
|
103
|
+
if (model === undefined)
|
|
104
|
+
return undefined;
|
|
105
|
+
const backend = this.backendOf(formatModelRef(model));
|
|
106
|
+
if (backend === undefined)
|
|
107
|
+
return undefined;
|
|
108
|
+
const quota = this.lastQuota ?? this.deps.session().getStats().subscription?.quota;
|
|
109
|
+
if (quota !== undefined && quota.provider === model.provider)
|
|
110
|
+
return { quota };
|
|
111
|
+
return backend === "codex" ? "pending" : undefined;
|
|
112
|
+
}
|
|
113
|
+
backendMemo;
|
|
114
|
+
/** 模型的 ChatGPT 订阅后端(渠道 compat `chatgptBackend`);按引用记住上次结果。 */
|
|
115
|
+
backendOf(ref) {
|
|
116
|
+
if (this.backendMemo?.ref === ref)
|
|
117
|
+
return this.backendMemo.backend;
|
|
118
|
+
const found = this.deps.runtime.providers.findModel(ref);
|
|
119
|
+
const value = found.ok
|
|
120
|
+
? found.model.compat?.chatgptBackend
|
|
121
|
+
: undefined;
|
|
122
|
+
const backend = value === "codex" || value === "siwc" ? value : undefined;
|
|
123
|
+
this.backendMemo = { ref, backend };
|
|
124
|
+
return backend;
|
|
125
|
+
}
|
|
126
|
+
/** 第三行显示且有重置时间时每分钟重画;否则不挂计时器。 */
|
|
127
|
+
syncTicker() {
|
|
128
|
+
const view = this.mode === "full" ? this.quotaView() : undefined;
|
|
129
|
+
const ticking = typeof view === "object" &&
|
|
130
|
+
(view.quota.primary?.resetsAt !== undefined || view.quota.secondary?.resetsAt !== undefined);
|
|
131
|
+
if (ticking && this.ticker === undefined) {
|
|
132
|
+
this.ticker = setInterval(() => this.deps.render(), QUOTA_TICK_MS);
|
|
133
|
+
this.ticker.unref?.();
|
|
134
|
+
}
|
|
135
|
+
else if (!ticking && this.ticker !== undefined) {
|
|
136
|
+
clearInterval(this.ticker);
|
|
137
|
+
this.ticker = undefined;
|
|
138
|
+
}
|
|
86
139
|
}
|
|
87
140
|
layout() {
|
|
88
141
|
return this.mode;
|
|
@@ -95,6 +148,7 @@ export class StatusArea {
|
|
|
95
148
|
if (mode === this.mode)
|
|
96
149
|
return;
|
|
97
150
|
this.mode = mode;
|
|
151
|
+
this.syncTicker();
|
|
98
152
|
(this.deps.redraw ?? this.deps.render)();
|
|
99
153
|
}
|
|
100
154
|
toggle() {
|
|
@@ -110,6 +164,7 @@ export class StatusArea {
|
|
|
110
164
|
this.fallback = undefined;
|
|
111
165
|
this.watchGit();
|
|
112
166
|
this.bar.refresh();
|
|
167
|
+
this.syncTicker();
|
|
113
168
|
}
|
|
114
169
|
refreshGit() {
|
|
115
170
|
void this.git?.refresh();
|
|
@@ -120,6 +175,10 @@ export class StatusArea {
|
|
|
120
175
|
case "telemetry_tick":
|
|
121
176
|
this.bar.refresh();
|
|
122
177
|
return true;
|
|
178
|
+
case "quota_update":
|
|
179
|
+
this.lastQuota = event;
|
|
180
|
+
this.syncTicker();
|
|
181
|
+
return true;
|
|
123
182
|
case "agent_settled":
|
|
124
183
|
case "session_rewound":
|
|
125
184
|
this.refreshGit();
|
|
@@ -141,6 +200,7 @@ export class StatusArea {
|
|
|
141
200
|
if (formatModelRef(model) === this.fallback.from)
|
|
142
201
|
this.fallback = undefined;
|
|
143
202
|
}
|
|
203
|
+
this.syncTicker();
|
|
144
204
|
return false;
|
|
145
205
|
}
|
|
146
206
|
default:
|
|
@@ -148,6 +208,12 @@ export class StatusArea {
|
|
|
148
208
|
}
|
|
149
209
|
}
|
|
150
210
|
dispose() {
|
|
211
|
+
this.disposeGit();
|
|
212
|
+
if (this.ticker !== undefined)
|
|
213
|
+
clearInterval(this.ticker);
|
|
214
|
+
this.ticker = undefined;
|
|
215
|
+
}
|
|
216
|
+
disposeGit() {
|
|
151
217
|
this.offGit();
|
|
152
218
|
this.git?.dispose();
|
|
153
219
|
this.git = undefined;
|
|
@@ -162,7 +228,7 @@ export class StatusArea {
|
|
|
162
228
|
const cwd = this.deps.session().state.cwd;
|
|
163
229
|
if (cwd === this.gitCwd && this.git !== undefined)
|
|
164
230
|
return;
|
|
165
|
-
this.
|
|
231
|
+
this.disposeGit();
|
|
166
232
|
this.gitCwd = cwd;
|
|
167
233
|
const git = new GitInfoWatcher(cwd, this.deps.env !== undefined ? { env: this.deps.env } : {});
|
|
168
234
|
this.git = git;
|
|
@@ -19,8 +19,16 @@
|
|
|
19
19
|
* - 费用 = `stats.cost` + 外部 Agent 以美元计的用量(`stats.external`,W5-E;其它单位只在 /session 显示)。
|
|
20
20
|
* - 着色:整行 dim;模式名正文色(Bypass permissions warning、Plan accent);模型 accent;ctx 按阈值
|
|
21
21
|
* success / warning / error;rebill、queue warning;`net!` error。宽 ≥ 110 时 ctx 用余量表。
|
|
22
|
+
* [W6] full 本行按用户参考配色:思考级别 `user`(浅蓝)、目录与分支 success、短提交 dim、`↑N` 领先 `code`(橙)/
|
|
23
|
+
* `↓N` 落后 error、`(+a,-d)` 的 +a success / -d error、费用 warning、时长 `tool`(紫);compact 着色不变。
|
|
24
|
+
* - [W6] git 段在短提交后显示领先 / 落后上游 `↑N ↓N`(只 full;为 0 不显示,没有上游不显示)。
|
|
25
|
+
* - [W6] compact 在行尾(宿主状态之后)追加订阅配额短项 `5h 10% wk 31%`(status-quota.ts),最先丢;
|
|
26
|
+
* 既有记号顺序不变。full 的配额在状态栏下方单独一行。
|
|
22
27
|
* - [W5-U] bash 在操作系统沙箱里跑时(S2)用量类项多一个 `沙箱`(与 codemode 同一丢弃优先级)。
|
|
23
|
-
* - [W5-U] 模型回退中(`model_fallback
|
|
28
|
+
* - [W5-U] 模型回退中(`model_fallback`)compact 模型项显示 `主模型 → 回退模型`,切回主模型后恢复;[W7] full
|
|
29
|
+
* 本行只显示主模型,`→ 回退模型` 作为开关类项在速率行左区。
|
|
30
|
+
* - [W7] full 左右分区:左 = 状态(权限模式、`shift+tab` 提示),右 = 模型与度量;开关类项在速率行左区,
|
|
31
|
+
* 配额在第三行右区(status-line.ts / status-quota.ts)。
|
|
24
32
|
* - 模型名缩写:宽 < 100 去掉供应商前缀,< 60 再去掉 `@渠道`,< 48 去掉版本后缀(第一个 `-数字` 起)。
|
|
25
33
|
* - ASCII:`⎇` → `git`(字形表 `branch`)、`−` → `-`、`♨` → `~`。
|
|
26
34
|
* - 宽度不够时按优先级丢弃(数字大的先丢,表见 §1.2);会变的数字按最宽形状占位(`reserve`),
|
|
@@ -29,6 +37,7 @@
|
|
|
29
37
|
import type { AgentSession, SessionCacheStats, SessionStats } from "../../agent/types.js";
|
|
30
38
|
import type { StatusLineMode } from "../../config/types.js";
|
|
31
39
|
import type { GitInfo } from "../../git/info.js";
|
|
40
|
+
import { type QuotaView } from "./status-quota.js";
|
|
32
41
|
import { type Component, type Theme } from "../../tui.js";
|
|
33
42
|
export interface StatusBarSource {
|
|
34
43
|
session(): AgentSession;
|
|
@@ -58,6 +67,8 @@ export interface StatusBarSource {
|
|
|
58
67
|
from: string;
|
|
59
68
|
to: string;
|
|
60
69
|
} | undefined;
|
|
70
|
+
/** [W6] 订阅配额(当前模型走 ChatGPT 订阅时);compact 行尾短项用。 */
|
|
71
|
+
quota?(): QuotaView;
|
|
61
72
|
}
|
|
62
73
|
export declare function formatTokens(count: number): string;
|
|
63
74
|
export declare function formatCost(cost: number): string;
|
|
@@ -131,7 +142,7 @@ export declare class StatusBar implements Component {
|
|
|
131
142
|
queued(): number;
|
|
132
143
|
layout(): StatusLineMode;
|
|
133
144
|
private parts;
|
|
134
|
-
/** `目录 ⎇ 分支 短提交 +a −b
|
|
145
|
+
/** `目录 ⎇ 分支 短提交 +a −b`:三项同组、各自按优先级丢弃;full 另带 `↑N ↓N` 并按参考配色。 */
|
|
135
146
|
private gitParts;
|
|
136
147
|
private ctxText;
|
|
137
148
|
render(width: number): string[];
|
|
@@ -19,8 +19,16 @@
|
|
|
19
19
|
* - 费用 = `stats.cost` + 外部 Agent 以美元计的用量(`stats.external`,W5-E;其它单位只在 /session 显示)。
|
|
20
20
|
* - 着色:整行 dim;模式名正文色(Bypass permissions warning、Plan accent);模型 accent;ctx 按阈值
|
|
21
21
|
* success / warning / error;rebill、queue warning;`net!` error。宽 ≥ 110 时 ctx 用余量表。
|
|
22
|
+
* [W6] full 本行按用户参考配色:思考级别 `user`(浅蓝)、目录与分支 success、短提交 dim、`↑N` 领先 `code`(橙)/
|
|
23
|
+
* `↓N` 落后 error、`(+a,-d)` 的 +a success / -d error、费用 warning、时长 `tool`(紫);compact 着色不变。
|
|
24
|
+
* - [W6] git 段在短提交后显示领先 / 落后上游 `↑N ↓N`(只 full;为 0 不显示,没有上游不显示)。
|
|
25
|
+
* - [W6] compact 在行尾(宿主状态之后)追加订阅配额短项 `5h 10% wk 31%`(status-quota.ts),最先丢;
|
|
26
|
+
* 既有记号顺序不变。full 的配额在状态栏下方单独一行。
|
|
22
27
|
* - [W5-U] bash 在操作系统沙箱里跑时(S2)用量类项多一个 `沙箱`(与 codemode 同一丢弃优先级)。
|
|
23
|
-
* - [W5-U] 模型回退中(`model_fallback
|
|
28
|
+
* - [W5-U] 模型回退中(`model_fallback`)compact 模型项显示 `主模型 → 回退模型`,切回主模型后恢复;[W7] full
|
|
29
|
+
* 本行只显示主模型,`→ 回退模型` 作为开关类项在速率行左区。
|
|
30
|
+
* - [W7] full 左右分区:左 = 状态(权限模式、`shift+tab` 提示),右 = 模型与度量;开关类项在速率行左区,
|
|
31
|
+
* 配额在第三行右区(status-line.ts / status-quota.ts)。
|
|
24
32
|
* - 模型名缩写:宽 < 100 去掉供应商前缀,< 60 再去掉 `@渠道`,< 48 去掉版本后缀(第一个 `-数字` 起)。
|
|
25
33
|
* - ASCII:`⎇` → `git`(字形表 `branch`)、`−` → `-`、`♨` → `~`。
|
|
26
34
|
* - 宽度不够时按优先级丢弃(数字大的先丢,表见 §1.2);会变的数字按最宽形状占位(`reserve`),
|
|
@@ -29,6 +37,7 @@
|
|
|
29
37
|
import { msg } from "../../i18n/index.js";
|
|
30
38
|
import { permissionModeLabel } from "../../permissions/modes.js";
|
|
31
39
|
import { formatModelRef } from "../../ai/providers/channels.js";
|
|
40
|
+
import { compactQuotaItem } from "./status-quota.js";
|
|
32
41
|
import { Meter, levelColor, truncateToWidth, visibleWidth, } from "../../tui.js";
|
|
33
42
|
export function formatTokens(count) {
|
|
34
43
|
if (count < 1000)
|
|
@@ -115,9 +124,10 @@ export function layoutRow(parts, width, theme, style = {}) {
|
|
|
115
124
|
let kept = [...parts];
|
|
116
125
|
const zone = (z) => kept.filter((p) => p.zone === z);
|
|
117
126
|
const fits = () => {
|
|
118
|
-
const
|
|
127
|
+
const left = zone("left");
|
|
119
128
|
const right = zone("right");
|
|
120
|
-
|
|
129
|
+
const between = left.length === 0 || right.length === 0 ? 0 : sep.length;
|
|
130
|
+
return join(left)[1] + between + join(right)[1] <= width;
|
|
121
131
|
};
|
|
122
132
|
while (!fits()) {
|
|
123
133
|
const droppable = kept.filter((p) => p.priority !== undefined);
|
|
@@ -129,7 +139,14 @@ export function layoutRow(parts, width, theme, style = {}) {
|
|
|
129
139
|
const l = join(zone("left"))[0];
|
|
130
140
|
const r = join(zone("right"))[0];
|
|
131
141
|
const gap = width - visibleWidth(l) - visibleWidth(r);
|
|
132
|
-
|
|
142
|
+
// 左区为空时右区右对齐([W7] full 的速率行没有开关、配额行)
|
|
143
|
+
const line = r === ""
|
|
144
|
+
? l
|
|
145
|
+
: l === ""
|
|
146
|
+
? " ".repeat(Math.max(0, gap)) + r
|
|
147
|
+
: gap >= MIN_GAP
|
|
148
|
+
? l + " ".repeat(gap) + r
|
|
149
|
+
: l + dim(sep) + r;
|
|
133
150
|
return truncateToWidth(line, width);
|
|
134
151
|
}
|
|
135
152
|
/** 按终端宽度缩写模型名(§3.10)。 */
|
|
@@ -209,6 +226,7 @@ const COMPACT = {
|
|
|
209
226
|
preset: 14,
|
|
210
227
|
host: 15,
|
|
211
228
|
hint: 16,
|
|
229
|
+
quota: 17,
|
|
212
230
|
};
|
|
213
231
|
/** full 布局本行的优先级(§1.2 下行表)。 */
|
|
214
232
|
const FULL = { model: 0, ctx: 1, cost: 2, duration: 3, branch: 4, dir: 5, diff: 6, thinking: 7 };
|
|
@@ -270,8 +288,8 @@ export class StatusBar {
|
|
|
270
288
|
}
|
|
271
289
|
const model = state.model === undefined ? "?" : abbreviateModel(formatModelRef(state.model), width);
|
|
272
290
|
const p = full ? FULL : COMPACT;
|
|
273
|
-
|
|
274
|
-
|
|
291
|
+
// 回退中:compact 显示 `主模型 → 回退模型`(回退模型 warning);full 本行只留主模型,`→ 回退模型` 在速率行左区
|
|
292
|
+
const fallback = full ? undefined : this.source.fallback?.();
|
|
275
293
|
const modelText = fallback === undefined
|
|
276
294
|
? theme.fg("accent", model)
|
|
277
295
|
: theme.fg("accent", abbreviateModel(fallback.from, width)) +
|
|
@@ -279,7 +297,8 @@ export class StatusBar {
|
|
|
279
297
|
theme.fg("warning", abbreviateModel(fallback.to, width));
|
|
280
298
|
right(modelText, p.model, full ? { group: "model" } : {});
|
|
281
299
|
if (state.thinkingLevel !== "off") {
|
|
282
|
-
|
|
300
|
+
const level = full ? theme.fg("user", state.thinkingLevel) : dim(state.thinkingLevel);
|
|
301
|
+
right(level, p.thinking, full ? { group: "model" } : {});
|
|
283
302
|
}
|
|
284
303
|
const usage = full ? {} : usageItems(stats, this.queue, this.source, theme);
|
|
285
304
|
if (usage.tokens !== undefined)
|
|
@@ -297,13 +316,15 @@ export class StatusBar {
|
|
|
297
316
|
const used = stats.tokens.input + stats.tokens.output + stats.tokens.cacheRead > 0;
|
|
298
317
|
if (cost === undefined && used)
|
|
299
318
|
right(dim("$?"), FULL.cost);
|
|
300
|
-
else if (cost !== undefined && cost > 0)
|
|
301
|
-
right(
|
|
319
|
+
else if (cost !== undefined && cost > 0) {
|
|
320
|
+
right(theme.fg("warning", formatCost(cost)), FULL.cost);
|
|
321
|
+
}
|
|
302
322
|
}
|
|
303
323
|
const started = this.source.sessionStartedAt?.() ?? stats.telemetry?.sessionStartedAt;
|
|
304
324
|
if (started !== undefined) {
|
|
305
325
|
const now = this.source.now?.() ?? Date.now();
|
|
306
|
-
|
|
326
|
+
const text = formatDuration(now - started);
|
|
327
|
+
right(full ? theme.fg("tool", text) : dim(text), p.duration, { reserve: 6 });
|
|
307
328
|
}
|
|
308
329
|
if (usage.queue !== undefined)
|
|
309
330
|
right(usage.queue, COMPACT.queue);
|
|
@@ -315,9 +336,13 @@ export class StatusBar {
|
|
|
315
336
|
right(usage.preset, COMPACT.preset);
|
|
316
337
|
if (usage.host !== undefined)
|
|
317
338
|
right(usage.host, COMPACT.host);
|
|
339
|
+
const quota = full ? undefined : this.source.quota?.();
|
|
340
|
+
const quotaItem = typeof quota === "object" ? compactQuotaItem(quota.quota, theme) : undefined;
|
|
341
|
+
if (quotaItem !== undefined)
|
|
342
|
+
right(quotaItem, COMPACT.quota);
|
|
318
343
|
return parts;
|
|
319
344
|
}
|
|
320
|
-
/** `目录 ⎇ 分支 短提交 +a −b
|
|
345
|
+
/** `目录 ⎇ 分支 短提交 +a −b`:三项同组、各自按优先级丢弃;full 另带 `↑N ↓N` 并按参考配色。 */
|
|
321
346
|
gitParts(p, full) {
|
|
322
347
|
const git = this.source.git?.();
|
|
323
348
|
if (git === undefined)
|
|
@@ -325,19 +350,37 @@ export class StatusBar {
|
|
|
325
350
|
const theme = this.theme;
|
|
326
351
|
const g = theme.glyphs;
|
|
327
352
|
const dim = (text) => theme.fg("dim", text);
|
|
353
|
+
const ok = (text) => (full ? theme.fg("success", text) : dim(text));
|
|
328
354
|
const out = [];
|
|
329
|
-
const add = (text, priority) => void out.push({ text
|
|
355
|
+
const add = (text, priority) => void out.push({ text, priority, zone: "right", group: "git" });
|
|
330
356
|
if (git.dir !== "")
|
|
331
|
-
add(git.dir, p.dir);
|
|
357
|
+
add(ok(git.dir), p.dir);
|
|
332
358
|
const info = git.info;
|
|
333
359
|
if (info === undefined)
|
|
334
360
|
return out;
|
|
335
|
-
const head = [
|
|
336
|
-
if (
|
|
337
|
-
|
|
361
|
+
const head = [];
|
|
362
|
+
if (info.branch !== undefined)
|
|
363
|
+
head.push(ok(info.branch));
|
|
364
|
+
if (info.shortHead !== undefined)
|
|
365
|
+
head.push(dim(info.shortHead));
|
|
366
|
+
if (full && info.ahead !== undefined && info.ahead > 0) {
|
|
367
|
+
head.push(theme.fg("code", `${g.arrowUp}${info.ahead}`));
|
|
368
|
+
}
|
|
369
|
+
if (full && info.behind !== undefined && info.behind > 0) {
|
|
370
|
+
head.push(theme.fg("error", `${g.arrowDown}${info.behind}`));
|
|
371
|
+
}
|
|
372
|
+
if (!full) {
|
|
373
|
+
const text = [info.branch, info.shortHead].filter((s) => s !== undefined).join(" ");
|
|
374
|
+
if (text !== "")
|
|
375
|
+
add(dim(`${g.branch} ${text}`), p.branch);
|
|
376
|
+
}
|
|
377
|
+
else if (head.length > 0)
|
|
378
|
+
add(`${dim(g.branch)} ${head.join(" ")}`, p.branch);
|
|
338
379
|
if (info.insertions !== undefined && info.deletions !== undefined) {
|
|
339
380
|
const { insertions: a, deletions: d } = info;
|
|
340
|
-
add(full
|
|
381
|
+
add(full
|
|
382
|
+
? `${dim("(")}${theme.fg("success", `+${a}`)}${dim(",")}${theme.fg("error", `-${d}`)}${dim(")")}`
|
|
383
|
+
: dim(`+${a} ${g.ascii ? "-" : "−"}${d}`), p.diff);
|
|
341
384
|
}
|
|
342
385
|
return out;
|
|
343
386
|
}
|
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
* 速率行(第五波 §1.1,D1、D2):`ui.statusLine: "full"` 时在状态栏上方多一行。[W5-A]
|
|
3
3
|
*
|
|
4
4
|
* ```text
|
|
5
|
-
* tps: 100 tok/s • 546 tok / 5.5s (avg 100 · ttft 1.4s)
|
|
5
|
+
* codemode on · preset x tps: 100 tok/s • 546 tok / 5.5s (avg 100 · ttft 1.4s) · ↑12.3k ↓1.2k · cache 83% ♨ · [-]
|
|
6
6
|
* ```
|
|
7
7
|
*
|
|
8
|
-
* -
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* - [W7] 左右分区:左 = 状态 / 开关(`codemode on|only`〔网络未隔离追加 `net!`〕· `沙箱` · `preset 名` ·
|
|
9
|
+
* `→ 回退模型` · `queue N` · [宿主]),没有开关时左区为空、右区右对齐;右 = 度量:
|
|
10
|
+
* `tps: <速率>`(流式中是最近 2 s 窗口的瞬时值、前缀 accent;结束后是该请求的平均值;还没有请求时 `tps: —`)
|
|
11
|
+
* • `<输出 token> tok / <耗时>`(首 token 起算)`(avg <会话均速> · ttft <首 token 延迟>)` · `↑ ↓` · cache ·
|
|
12
|
+
* rebill,行尾 `[-]`(折叠提示:Ctrl+G 或 `/statusline compact`);`•` 走字形表(ASCII `*`);
|
|
13
13
|
* - 数据:`StatusBar.current()` 的 `telemetry` 与用量(同一次 getStats);流式中 `telemetry_tick` 时刷新;
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
14
|
+
* - 丢弃顺序(数字大先丢;`tps` 与 `[-]` 永不丢):右区度量先丢——tok/耗时 12 → token 11 → cache 10 →
|
|
15
|
+
* rebill 9 → avg 8 → ttft 7,左区开关最后丢——宿主 6 → queue 5 → 预设 4 → 回退 3 → 沙箱 2 → codemode 1;
|
|
16
|
+
* 数字按最宽形状占位,不随数值抖动;
|
|
16
17
|
* - `compact` 时不占行(render 返回空)。
|
|
18
|
+
* - [W6] 配色(用户参考):标签、单位、`•`、括号 dim;速率数字 `tool`(紫);输出量与耗时、avg 数字 accent(蓝);
|
|
19
|
+
* ttft 数字 `tool`;用量项 dim;回退模型 warning。
|
|
17
20
|
*/
|
|
18
21
|
import { type Component, type Theme } from "../../tui.js";
|
|
19
22
|
import { type StatusBar, type StatusBarSource } from "./status-bar.js";
|