@armadra/agent 0.6.1 → 0.6.3
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 +80 -0
- package/CHANGELOG.zh-CN.md +56 -0
- package/README.md +168 -575
- package/README.zh-CN.md +165 -596
- package/dist/agent/session-settings.d.ts +4 -1
- package/dist/agent/session-settings.js +7 -2
- package/dist/agent/session.d.ts +6 -0
- package/dist/agent/session.js +8 -0
- package/dist/agent/subagent-background.d.ts +75 -0
- package/dist/agent/subagent-background.js +209 -0
- package/dist/agent/subagent-registry.d.ts +24 -5
- package/dist/agent/subagent-registry.js +64 -92
- package/dist/agent/types-w5.d.ts +11 -1
- package/dist/agent/types.d.ts +5 -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 +13 -1
- package/dist/agents/task-record.js +32 -0
- package/dist/agents/types.d.ts +5 -1
- package/dist/ai/apis/chatgpt-backend.d.ts +7 -2
- package/dist/ai/apis/chatgpt-backend.js +12 -4
- package/dist/ai/providers/discovered-cache.d.ts +29 -12
- package/dist/ai/providers/discovered-cache.js +72 -12
- package/dist/ai/providers/registry.js +7 -6
- package/dist/ai/types.d.ts +5 -0
- package/dist/auth/chatgpt/backend-client.d.ts +14 -4
- package/dist/auth/chatgpt/backend-client.js +41 -8
- package/dist/auth/chatgpt/cli.d.ts +2 -1
- package/dist/auth/chatgpt/cli.js +14 -7
- package/dist/auth/chatgpt/follow.d.ts +16 -0
- package/dist/auth/chatgpt/follow.js +51 -0
- package/dist/auth/chatgpt/presets.d.ts +8 -0
- package/dist/auth/chatgpt/presets.js +12 -0
- package/dist/auth/testing/fake-oauth.d.ts +4 -1
- package/dist/auth/testing/fake-oauth.js +24 -1
- package/dist/bundle/ama.cjs +2016 -664
- package/dist/cli/compose-agents.d.ts +2 -1
- package/dist/cli/compose-agents.js +11 -2
- package/dist/cli/compose-providers.d.ts +2 -1
- package/dist/cli/compose-providers.js +6 -3
- package/dist/cli/startup-steps.js +4 -1
- package/dist/cli/subcommands/models-discover.d.ts +5 -2
- package/dist/cli/subcommands/models-discover.js +52 -10
- package/dist/config/json-schema.js +10 -2
- package/dist/config/key-docs.js +9 -3
- 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-w6.d.ts +1 -1
- package/dist/config/schema-w6.js +2 -1
- package/dist/config/schema.js +2 -0
- package/dist/config/settings-registry.js +4 -0
- package/dist/config/types-w5.d.ts +10 -0
- package/dist/config/types-w5.js +2 -0
- package/dist/config/types-w6.d.ts +5 -0
- package/dist/config/types.d.ts +3 -1
- package/dist/git/info.d.ts +19 -0
- package/dist/git/info.js +64 -8
- package/dist/i18n/catalog.d.ts +52 -8
- package/dist/i18n/messages/agents.d.ts +56 -0
- package/dist/i18n/messages/agents.js +58 -2
- package/dist/i18n/messages/auth.d.ts +12 -0
- package/dist/i18n/messages/auth.js +20 -0
- 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.d.ts +23 -16
- package/dist/i18n/messages/interactive.js +25 -0
- package/dist/i18n/messages/print.d.ts +4 -0
- package/dist/i18n/messages/print.js +4 -0
- package/dist/i18n/messages/report.js +4 -4
- package/dist/i18n/messages/settings.d.ts +6 -0
- package/dist/i18n/messages/settings.js +6 -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 +8 -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 +81 -12
- package/dist/modes/interactive/agent-view.d.ts +4 -0
- package/dist/modes/interactive/agent-view.js +14 -1
- 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 +33 -38
- package/dist/modes/interactive/key-dispatch.d.ts +21 -4
- package/dist/modes/interactive/key-dispatch.js +60 -8
- package/dist/modes/interactive/line/line-mode.js +5 -0
- package/dist/modes/interactive/run-indicator.d.ts +15 -0
- package/dist/modes/interactive/run-indicator.js +37 -5
- 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 +9 -1
- package/dist/modes/interactive/status-bar.js +43 -11
- package/dist/modes/interactive/status-line.d.ts +2 -0
- package/dist/modes/interactive/status-line.js +11 -6
- package/dist/modes/interactive/status-quota.d.ts +44 -0
- package/dist/modes/interactive/status-quota.js +135 -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 +2 -1
- package/dist/modes/rpc/commands.js +9 -1
- package/dist/rpc.d.ts +13 -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/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 +8 -3
- package/dist/tui/keybindings.js +8 -3
- package/docs/agents.md +52 -28
- package/docs/en/host-api.md +5 -1
- package/docs/en/providers.md +8 -4
- package/docs/en/rpc.md +19 -7
- package/docs/en/sessions.md +3 -1
- package/docs/en/tui.md +84 -66
- package/docs/host-api.md +5 -1
- package/docs/providers.md +29 -12
- package/docs/rpc.md +19 -7
- package/docs/sessions.md +2 -1
- package/docs/tui-design.md +42 -29
- package/docs/tui.md +67 -49
- package/package.json +1 -1
|
@@ -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;
|
|
@@ -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;
|