@armadra/agent 0.6.2 → 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 +63 -0
- package/CHANGELOG.zh-CN.md +44 -0
- package/README.md +168 -575
- package/README.zh-CN.md +165 -596
- 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/providers/discovered-cache.d.ts +10 -4
- package/dist/ai/providers/discovered-cache.js +30 -14
- package/dist/auth/chatgpt/backend-client.d.ts +6 -5
- package/dist/auth/chatgpt/backend-client.js +8 -7
- package/dist/bundle/ama.cjs +1691 -500
- 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 +9 -2
- package/dist/config/key-docs.js +2 -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 +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.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 +45 -8
- package/dist/i18n/messages/agents.d.ts +56 -0
- package/dist/i18n/messages/agents.js +58 -2
- package/dist/i18n/messages/config-keys.d.ts +6 -0
- package/dist/i18n/messages/config-keys.js +16 -10
- package/dist/i18n/messages/config.d.ts +6 -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 +1 -1
- 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 +4 -2
- 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
|
@@ -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,6 +19,11 @@
|
|
|
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
28
|
* - [W5-U] 模型回退中(`model_fallback`)模型项显示 `主模型 → 回退模型`,切回主模型后恢复。
|
|
24
29
|
* - 模型名缩写:宽 < 100 去掉供应商前缀,< 60 再去掉 `@渠道`,< 48 去掉版本后缀(第一个 `-数字` 起)。
|
|
@@ -29,6 +34,7 @@
|
|
|
29
34
|
import type { AgentSession, SessionCacheStats, SessionStats } from "../../agent/types.js";
|
|
30
35
|
import type { StatusLineMode } from "../../config/types.js";
|
|
31
36
|
import type { GitInfo } from "../../git/info.js";
|
|
37
|
+
import { type QuotaView } from "./status-quota.js";
|
|
32
38
|
import { type Component, type Theme } from "../../tui.js";
|
|
33
39
|
export interface StatusBarSource {
|
|
34
40
|
session(): AgentSession;
|
|
@@ -58,6 +64,8 @@ export interface StatusBarSource {
|
|
|
58
64
|
from: string;
|
|
59
65
|
to: string;
|
|
60
66
|
} | undefined;
|
|
67
|
+
/** [W6] 订阅配额(当前模型走 ChatGPT 订阅时);compact 行尾短项用。 */
|
|
68
|
+
quota?(): QuotaView;
|
|
61
69
|
}
|
|
62
70
|
export declare function formatTokens(count: number): string;
|
|
63
71
|
export declare function formatCost(cost: number): string;
|
|
@@ -131,7 +139,7 @@ export declare class StatusBar implements Component {
|
|
|
131
139
|
queued(): number;
|
|
132
140
|
layout(): StatusLineMode;
|
|
133
141
|
private parts;
|
|
134
|
-
/** `目录 ⎇ 分支 短提交 +a −b
|
|
142
|
+
/** `目录 ⎇ 分支 短提交 +a −b`:三项同组、各自按优先级丢弃;full 另带 `↑N ↓N` 并按参考配色。 */
|
|
135
143
|
private gitParts;
|
|
136
144
|
private ctxText;
|
|
137
145
|
render(width: number): string[];
|
|
@@ -19,6 +19,11 @@
|
|
|
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
28
|
* - [W5-U] 模型回退中(`model_fallback`)模型项显示 `主模型 → 回退模型`,切回主模型后恢复。
|
|
24
29
|
* - 模型名缩写:宽 < 100 去掉供应商前缀,< 60 再去掉 `@渠道`,< 48 去掉版本后缀(第一个 `-数字` 起)。
|
|
@@ -29,6 +34,7 @@
|
|
|
29
34
|
import { msg } from "../../i18n/index.js";
|
|
30
35
|
import { permissionModeLabel } from "../../permissions/modes.js";
|
|
31
36
|
import { formatModelRef } from "../../ai/providers/channels.js";
|
|
37
|
+
import { compactQuotaItem } from "./status-quota.js";
|
|
32
38
|
import { Meter, levelColor, truncateToWidth, visibleWidth, } from "../../tui.js";
|
|
33
39
|
export function formatTokens(count) {
|
|
34
40
|
if (count < 1000)
|
|
@@ -209,6 +215,7 @@ const COMPACT = {
|
|
|
209
215
|
preset: 14,
|
|
210
216
|
host: 15,
|
|
211
217
|
hint: 16,
|
|
218
|
+
quota: 17,
|
|
212
219
|
};
|
|
213
220
|
/** full 布局本行的优先级(§1.2 下行表)。 */
|
|
214
221
|
const FULL = { model: 0, ctx: 1, cost: 2, duration: 3, branch: 4, dir: 5, diff: 6, thinking: 7 };
|
|
@@ -279,7 +286,8 @@ export class StatusBar {
|
|
|
279
286
|
theme.fg("warning", abbreviateModel(fallback.to, width));
|
|
280
287
|
right(modelText, p.model, full ? { group: "model" } : {});
|
|
281
288
|
if (state.thinkingLevel !== "off") {
|
|
282
|
-
|
|
289
|
+
const level = full ? theme.fg("user", state.thinkingLevel) : dim(state.thinkingLevel);
|
|
290
|
+
right(level, p.thinking, full ? { group: "model" } : {});
|
|
283
291
|
}
|
|
284
292
|
const usage = full ? {} : usageItems(stats, this.queue, this.source, theme);
|
|
285
293
|
if (usage.tokens !== undefined)
|
|
@@ -297,13 +305,15 @@ export class StatusBar {
|
|
|
297
305
|
const used = stats.tokens.input + stats.tokens.output + stats.tokens.cacheRead > 0;
|
|
298
306
|
if (cost === undefined && used)
|
|
299
307
|
right(dim("$?"), FULL.cost);
|
|
300
|
-
else if (cost !== undefined && cost > 0)
|
|
301
|
-
right(
|
|
308
|
+
else if (cost !== undefined && cost > 0) {
|
|
309
|
+
right(theme.fg("warning", formatCost(cost)), FULL.cost);
|
|
310
|
+
}
|
|
302
311
|
}
|
|
303
312
|
const started = this.source.sessionStartedAt?.() ?? stats.telemetry?.sessionStartedAt;
|
|
304
313
|
if (started !== undefined) {
|
|
305
314
|
const now = this.source.now?.() ?? Date.now();
|
|
306
|
-
|
|
315
|
+
const text = formatDuration(now - started);
|
|
316
|
+
right(full ? theme.fg("tool", text) : dim(text), p.duration, { reserve: 6 });
|
|
307
317
|
}
|
|
308
318
|
if (usage.queue !== undefined)
|
|
309
319
|
right(usage.queue, COMPACT.queue);
|
|
@@ -315,9 +325,13 @@ export class StatusBar {
|
|
|
315
325
|
right(usage.preset, COMPACT.preset);
|
|
316
326
|
if (usage.host !== undefined)
|
|
317
327
|
right(usage.host, COMPACT.host);
|
|
328
|
+
const quota = full ? undefined : this.source.quota?.();
|
|
329
|
+
const quotaItem = typeof quota === "object" ? compactQuotaItem(quota.quota, theme) : undefined;
|
|
330
|
+
if (quotaItem !== undefined)
|
|
331
|
+
right(quotaItem, COMPACT.quota);
|
|
318
332
|
return parts;
|
|
319
333
|
}
|
|
320
|
-
/** `目录 ⎇ 分支 短提交 +a −b
|
|
334
|
+
/** `目录 ⎇ 分支 短提交 +a −b`:三项同组、各自按优先级丢弃;full 另带 `↑N ↓N` 并按参考配色。 */
|
|
321
335
|
gitParts(p, full) {
|
|
322
336
|
const git = this.source.git?.();
|
|
323
337
|
if (git === undefined)
|
|
@@ -325,19 +339,37 @@ export class StatusBar {
|
|
|
325
339
|
const theme = this.theme;
|
|
326
340
|
const g = theme.glyphs;
|
|
327
341
|
const dim = (text) => theme.fg("dim", text);
|
|
342
|
+
const ok = (text) => (full ? theme.fg("success", text) : dim(text));
|
|
328
343
|
const out = [];
|
|
329
|
-
const add = (text, priority) => void out.push({ text
|
|
344
|
+
const add = (text, priority) => void out.push({ text, priority, zone: "right", group: "git" });
|
|
330
345
|
if (git.dir !== "")
|
|
331
|
-
add(git.dir, p.dir);
|
|
346
|
+
add(ok(git.dir), p.dir);
|
|
332
347
|
const info = git.info;
|
|
333
348
|
if (info === undefined)
|
|
334
349
|
return out;
|
|
335
|
-
const head = [
|
|
336
|
-
if (
|
|
337
|
-
|
|
350
|
+
const head = [];
|
|
351
|
+
if (info.branch !== undefined)
|
|
352
|
+
head.push(ok(info.branch));
|
|
353
|
+
if (info.shortHead !== undefined)
|
|
354
|
+
head.push(dim(info.shortHead));
|
|
355
|
+
if (full && info.ahead !== undefined && info.ahead > 0) {
|
|
356
|
+
head.push(theme.fg("code", `${g.arrowUp}${info.ahead}`));
|
|
357
|
+
}
|
|
358
|
+
if (full && info.behind !== undefined && info.behind > 0) {
|
|
359
|
+
head.push(theme.fg("error", `${g.arrowDown}${info.behind}`));
|
|
360
|
+
}
|
|
361
|
+
if (!full) {
|
|
362
|
+
const text = [info.branch, info.shortHead].filter((s) => s !== undefined).join(" ");
|
|
363
|
+
if (text !== "")
|
|
364
|
+
add(dim(`${g.branch} ${text}`), p.branch);
|
|
365
|
+
}
|
|
366
|
+
else if (head.length > 0)
|
|
367
|
+
add(`${dim(g.branch)} ${head.join(" ")}`, p.branch);
|
|
338
368
|
if (info.insertions !== undefined && info.deletions !== undefined) {
|
|
339
369
|
const { insertions: a, deletions: d } = info;
|
|
340
|
-
add(full
|
|
370
|
+
add(full
|
|
371
|
+
? `${dim("(")}${theme.fg("success", `+${a}`)}${dim(",")}${theme.fg("error", `-${d}`)}${dim(")")}`
|
|
372
|
+
: dim(`+${a} ${g.ascii ? "-" : "−"}${d}`), p.diff);
|
|
341
373
|
}
|
|
342
374
|
return out;
|
|
343
375
|
}
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* - 丢弃顺序(§1.2,数字大先丢;`tps` 与 `[-]` 永不丢):tok/耗时 10 → codemode 9 → queue 8 → token 7 →
|
|
15
15
|
* cache 6 → rebill 5 → 预设 4 → 宿主 3 → avg 2 → ttft 1;数字按最宽形状占位,不随数值抖动;
|
|
16
16
|
* - `compact` 时不占行(render 返回空)。
|
|
17
|
+
* - [W6] 配色(用户参考):标签、单位、`•`、括号 dim;速率数字 `tool`(紫);输出量与耗时、avg 数字 accent(蓝);
|
|
18
|
+
* ttft 数字 `tool`;右区用量项不变。
|
|
17
19
|
*/
|
|
18
20
|
import { type Component, type Theme } from "../../tui.js";
|
|
19
21
|
import { type StatusBar, type StatusBarSource } from "./status-bar.js";
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* - 丢弃顺序(§1.2,数字大先丢;`tps` 与 `[-]` 永不丢):tok/耗时 10 → codemode 9 → queue 8 → token 7 →
|
|
15
15
|
* cache 6 → rebill 5 → 预设 4 → 宿主 3 → avg 2 → ttft 1;数字按最宽形状占位,不随数值抖动;
|
|
16
16
|
* - `compact` 时不占行(render 返回空)。
|
|
17
|
+
* - [W6] 配色(用户参考):标签、单位、`•`、括号 dim;速率数字 `tool`(紫);输出量与耗时、avg 数字 accent(蓝);
|
|
18
|
+
* ttft 数字 `tool`;右区用量项不变。
|
|
17
19
|
*/
|
|
18
20
|
import { truncateToWidth } from "../../tui.js";
|
|
19
21
|
import { formatTokens, layoutRow, usageItems, } from "./status-bar.js";
|
|
@@ -68,29 +70,32 @@ export class StatusLine {
|
|
|
68
70
|
if (text !== undefined)
|
|
69
71
|
parts.push({ text, priority, zone: "right" });
|
|
70
72
|
};
|
|
73
|
+
const blue = (text) => theme.fg("accent", text);
|
|
74
|
+
const purple = (text) => theme.fg("tool", text);
|
|
75
|
+
const amountText = (tokens, ms) => `${blue(formatTokens(tokens))}${dim(" tok / ")}${blue(formatSeconds(ms))}`;
|
|
71
76
|
let tps;
|
|
72
77
|
let amount;
|
|
73
78
|
if (live !== undefined) {
|
|
74
79
|
tps = live.tps;
|
|
75
|
-
amount =
|
|
80
|
+
amount = amountText(live.outputTokens, live.elapsedMs);
|
|
76
81
|
}
|
|
77
82
|
else if (last?.doneAt !== undefined) {
|
|
78
83
|
tps = last.tps;
|
|
79
84
|
if (last.outputTokens !== undefined && last.firstTokenAt !== undefined) {
|
|
80
|
-
amount =
|
|
85
|
+
amount = amountText(last.outputTokens, last.doneAt - last.firstTokenAt);
|
|
81
86
|
}
|
|
82
87
|
}
|
|
83
88
|
const label = live !== undefined ? theme.fg("accent", "tps:") : dim("tps:");
|
|
84
|
-
left(`${label} ${
|
|
89
|
+
left(`${label} ${tps === undefined ? dim("—") : `${purple(formatRate(tps))}${dim(" tok/s")}`}`, undefined, RESERVE.tps);
|
|
85
90
|
if (amount !== undefined) {
|
|
86
|
-
left(
|
|
91
|
+
left(amount, PRIORITY.amount, RESERVE.amount, { lead: ` ${theme.glyphs.dot} ` });
|
|
87
92
|
}
|
|
88
93
|
const stat = { group: "stats", lead: " " };
|
|
89
94
|
if (telemetry?.avgTps !== undefined) {
|
|
90
|
-
left(dim(
|
|
95
|
+
left(`${dim("avg ")}${blue(formatRate(telemetry.avgTps))}`, PRIORITY.avg, RESERVE.avg, stat);
|
|
91
96
|
}
|
|
92
97
|
if (last?.ttftMs !== undefined) {
|
|
93
|
-
left(dim(
|
|
98
|
+
left(`${dim("ttft ")}${purple(formatSeconds(last.ttftMs))}`, PRIORITY.ttft, RESERVE.ttft, stat);
|
|
94
99
|
}
|
|
95
100
|
const usage = usageItems(stats, this.bar.queued(), this.source, theme);
|
|
96
101
|
right(usage.tokens, PRIORITY.tokens);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 订阅配额行(full 布局第三行,状态栏下方;compact 只在行尾追加一个短项)。[W6]
|
|
3
|
+
*
|
|
4
|
+
* ```text
|
|
5
|
+
* Session: 10.0% | Reset: 2h 18m | Weekly: 31.0% | Weekly Reset: 6d 5h ≥ 80 列
|
|
6
|
+
* 5h 10% ↻2h18m · wk 31% ↻6d5h < 80 列
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* - 数据:会话的 `quota_update`(codex flavor 的 `x-codex-primary/secondary-*` 头、`codex.rate_limits` 事件;
|
|
10
|
+
* SIWC 只有 429)。primary = 5 小时窗口、secondary = 周窗口;窗口长度不是 5 h / 7 d 时标签换成实际时长。
|
|
11
|
+
* - 只在当前模型走 ChatGPT 订阅(compat `chatgptBackend`)时出现;codex flavor 还没有数据时显示
|
|
12
|
+
* 「配额:首次请求后显示」(第一次请求就会带回配额,先占住行免得行数跳动),SIWC 没有数据时不占行
|
|
13
|
+
* (只有超限才有配额,占位会一直挂着误导)。
|
|
14
|
+
* - 着色:标签与分隔 dim;百分比按阈值(≥ 70% warning、≥ 90% error,否则 success);重置时间 `tool`(紫)。
|
|
15
|
+
* - 重置时间是相对时长(`2h 18m`、`6d 5h`),按渲染时刻算;StatusArea 每分钟重画一次。
|
|
16
|
+
*/
|
|
17
|
+
import type { QuotaUpdateEvent } from "../../agent/types-w6.js";
|
|
18
|
+
import type { StatusLineMode } from "../../config/types.js";
|
|
19
|
+
import { type Component, type Theme } from "../../tui.js";
|
|
20
|
+
import { type Part } from "./status-bar.js";
|
|
21
|
+
/** 配额行的数据:有数据 / 等首次请求 / 不显示(undefined)。 */
|
|
22
|
+
export type QuotaView = {
|
|
23
|
+
quota: QuotaUpdateEvent;
|
|
24
|
+
} | "pending" | undefined;
|
|
25
|
+
/** 第三行改用短格式的宽度。 */
|
|
26
|
+
export declare const QUOTA_NARROW_WIDTH = 80;
|
|
27
|
+
/** 距重置:`6d 5h` / `2h 18m` / `18m`(已过或不足一分钟 `0m`);`tight` 去掉空格。 */
|
|
28
|
+
export declare function formatRemaining(ms: number, tight?: boolean): string;
|
|
29
|
+
/** compact 行尾的短项 `5h 10% wk 31%`(项内只用空格,不含 ` · `,宿主解析不受影响)。 */
|
|
30
|
+
export declare function compactQuotaItem(quota: QuotaUpdateEvent, theme: Theme): string | undefined;
|
|
31
|
+
/** 配额行各项(宽 / 窄两种格式);重置时间先丢,主窗口百分比永不丢。 */
|
|
32
|
+
export declare function quotaParts(quota: QuotaUpdateEvent, width: number, now: number, theme: Theme): Part[];
|
|
33
|
+
export interface QuotaLineSource {
|
|
34
|
+
layout(): StatusLineMode;
|
|
35
|
+
quota(): QuotaView;
|
|
36
|
+
now(): number;
|
|
37
|
+
}
|
|
38
|
+
export declare class QuotaLine implements Component {
|
|
39
|
+
private readonly source;
|
|
40
|
+
private readonly theme;
|
|
41
|
+
constructor(source: QuotaLineSource, theme: Theme);
|
|
42
|
+
render(width: number): string[];
|
|
43
|
+
invalidate(): void;
|
|
44
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 订阅配额行(full 布局第三行,状态栏下方;compact 只在行尾追加一个短项)。[W6]
|
|
3
|
+
*
|
|
4
|
+
* ```text
|
|
5
|
+
* Session: 10.0% | Reset: 2h 18m | Weekly: 31.0% | Weekly Reset: 6d 5h ≥ 80 列
|
|
6
|
+
* 5h 10% ↻2h18m · wk 31% ↻6d5h < 80 列
|
|
7
|
+
* ```
|
|
8
|
+
*
|
|
9
|
+
* - 数据:会话的 `quota_update`(codex flavor 的 `x-codex-primary/secondary-*` 头、`codex.rate_limits` 事件;
|
|
10
|
+
* SIWC 只有 429)。primary = 5 小时窗口、secondary = 周窗口;窗口长度不是 5 h / 7 d 时标签换成实际时长。
|
|
11
|
+
* - 只在当前模型走 ChatGPT 订阅(compat `chatgptBackend`)时出现;codex flavor 还没有数据时显示
|
|
12
|
+
* 「配额:首次请求后显示」(第一次请求就会带回配额,先占住行免得行数跳动),SIWC 没有数据时不占行
|
|
13
|
+
* (只有超限才有配额,占位会一直挂着误导)。
|
|
14
|
+
* - 着色:标签与分隔 dim;百分比按阈值(≥ 70% warning、≥ 90% error,否则 success);重置时间 `tool`(紫)。
|
|
15
|
+
* - 重置时间是相对时长(`2h 18m`、`6d 5h`),按渲染时刻算;StatusArea 每分钟重画一次。
|
|
16
|
+
*/
|
|
17
|
+
import { msg } from "../../i18n/index.js";
|
|
18
|
+
import { levelColor, truncateToWidth, visibleWidth, } from "../../tui.js";
|
|
19
|
+
import { layoutRow } from "./status-bar.js";
|
|
20
|
+
/** 第三行改用短格式的宽度。 */
|
|
21
|
+
export const QUOTA_NARROW_WIDTH = 80;
|
|
22
|
+
const FIVE_HOURS = 300;
|
|
23
|
+
const WEEK = 10_080;
|
|
24
|
+
/** 距重置:`6d 5h` / `2h 18m` / `18m`(已过或不足一分钟 `0m`);`tight` 去掉空格。 */
|
|
25
|
+
export function formatRemaining(ms, tight = false) {
|
|
26
|
+
const minutes = Math.max(0, Math.floor(ms / 60_000));
|
|
27
|
+
const days = Math.floor(minutes / 1440);
|
|
28
|
+
const hours = Math.floor((minutes % 1440) / 60);
|
|
29
|
+
const sep = tight ? "" : " ";
|
|
30
|
+
if (days > 0)
|
|
31
|
+
return `${days}d${sep}${hours}h`;
|
|
32
|
+
if (hours > 0)
|
|
33
|
+
return `${hours}h${sep}${minutes % 60}m`;
|
|
34
|
+
return `${minutes}m`;
|
|
35
|
+
}
|
|
36
|
+
/** 窗口长度的紧凑写法(标签用):`5h`、`7d`、`90m`。 */
|
|
37
|
+
function windowSpan(minutes) {
|
|
38
|
+
if (minutes % 1440 === 0)
|
|
39
|
+
return `${minutes / 1440}d`;
|
|
40
|
+
if (minutes % 60 === 0)
|
|
41
|
+
return `${minutes / 60}h`;
|
|
42
|
+
return `${Math.round(minutes)}m`;
|
|
43
|
+
}
|
|
44
|
+
function labels(w, which) {
|
|
45
|
+
const m = msg().interactive.statusLine.quota;
|
|
46
|
+
const minutes = w.windowMinutes;
|
|
47
|
+
const standard = minutes === undefined || minutes === (which === "primary" ? FIVE_HOURS : WEEK);
|
|
48
|
+
if (!standard)
|
|
49
|
+
return { pct: m.window(windowSpan(minutes)), reset: m.windowReset(windowSpan(minutes)) };
|
|
50
|
+
return which === "primary"
|
|
51
|
+
? { pct: m.session, reset: m.reset }
|
|
52
|
+
: { pct: m.weekly, reset: m.weeklyReset };
|
|
53
|
+
}
|
|
54
|
+
function shortLabel(w, which) {
|
|
55
|
+
const m = msg().interactive.statusLine.quota;
|
|
56
|
+
const minutes = w.windowMinutes;
|
|
57
|
+
if (minutes === FIVE_HOURS || (minutes === undefined && which === "primary"))
|
|
58
|
+
return m.short5h;
|
|
59
|
+
if (minutes === WEEK || (minutes === undefined && which === "secondary"))
|
|
60
|
+
return m.shortWeek;
|
|
61
|
+
return windowSpan(minutes);
|
|
62
|
+
}
|
|
63
|
+
function percent(theme, used, decimal) {
|
|
64
|
+
return theme.fg(levelColor(used / 100), decimal ? `${used.toFixed(1)}%` : `${Math.round(used)}%`);
|
|
65
|
+
}
|
|
66
|
+
function windows(quota) {
|
|
67
|
+
const out = [];
|
|
68
|
+
if (quota.primary !== undefined)
|
|
69
|
+
out.push(["primary", quota.primary]);
|
|
70
|
+
if (quota.secondary !== undefined)
|
|
71
|
+
out.push(["secondary", quota.secondary]);
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/** compact 行尾的短项 `5h 10% wk 31%`(项内只用空格,不含 ` · `,宿主解析不受影响)。 */
|
|
75
|
+
export function compactQuotaItem(quota, theme) {
|
|
76
|
+
const dim = (text) => theme.fg("dim", text);
|
|
77
|
+
const items = windows(quota).map(([which, w]) => `${dim(shortLabel(w, which))} ${percent(theme, w.usedPercent, false)}`);
|
|
78
|
+
return items.length === 0 ? undefined : items.join(" ");
|
|
79
|
+
}
|
|
80
|
+
/** 配额行各项(宽 / 窄两种格式);重置时间先丢,主窗口百分比永不丢。 */
|
|
81
|
+
export function quotaParts(quota, width, now, theme) {
|
|
82
|
+
const dim = (text) => theme.fg("dim", text);
|
|
83
|
+
const narrow = width < QUOTA_NARROW_WIDTH;
|
|
84
|
+
const parts = [];
|
|
85
|
+
windows(quota).forEach(([which, w], i) => {
|
|
86
|
+
const first = i === 0;
|
|
87
|
+
const group = narrow ? which : undefined;
|
|
88
|
+
const name = narrow ? `${shortLabel(w, which)} ` : labels(w, which).pct;
|
|
89
|
+
parts.push({
|
|
90
|
+
text: dim(name) + percent(theme, w.usedPercent, !narrow),
|
|
91
|
+
priority: first ? undefined : 2,
|
|
92
|
+
zone: "left",
|
|
93
|
+
...(group !== undefined ? { group } : {}),
|
|
94
|
+
});
|
|
95
|
+
if (w.resetsAt === undefined)
|
|
96
|
+
return;
|
|
97
|
+
const left = formatRemaining(w.resetsAt - now, narrow);
|
|
98
|
+
parts.push({
|
|
99
|
+
text: narrow
|
|
100
|
+
? theme.fg("tool", `${theme.glyphs.retry}${left}`)
|
|
101
|
+
: dim(labels(w, which).reset) + theme.fg("tool", left),
|
|
102
|
+
priority: first ? 3 : 1,
|
|
103
|
+
zone: "left",
|
|
104
|
+
// 宽格式按最宽形状占位(`23h 59m`),数值变化不让某项时有时无
|
|
105
|
+
reserve: narrow ? 0 : visibleWidth(labels(w, which).reset) + 7,
|
|
106
|
+
...(group !== undefined ? { group } : {}),
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
return parts;
|
|
110
|
+
}
|
|
111
|
+
export class QuotaLine {
|
|
112
|
+
source;
|
|
113
|
+
theme;
|
|
114
|
+
constructor(source, theme) {
|
|
115
|
+
this.source = source;
|
|
116
|
+
this.theme = theme;
|
|
117
|
+
}
|
|
118
|
+
render(width) {
|
|
119
|
+
if (this.source.layout() !== "full")
|
|
120
|
+
return [];
|
|
121
|
+
const view = this.source.quota();
|
|
122
|
+
if (view === undefined)
|
|
123
|
+
return [];
|
|
124
|
+
const theme = this.theme;
|
|
125
|
+
if (view === "pending") {
|
|
126
|
+
return [truncateToWidth(theme.fg("dim", msg().interactive.statusLine.quota.pending), width)];
|
|
127
|
+
}
|
|
128
|
+
const parts = quotaParts(view.quota, width, this.source.now(), theme);
|
|
129
|
+
if (parts.length === 0)
|
|
130
|
+
return [];
|
|
131
|
+
const sep = width < QUOTA_NARROW_WIDTH ? " · " : " | ";
|
|
132
|
+
return [layoutRow(parts, width, theme, { sep })];
|
|
133
|
+
}
|
|
134
|
+
invalidate() { }
|
|
135
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* 后台任务的工具调用立即返回,工具行下面多一行跟随状态(`↳ t2 explore · 完成 · 7 轮 …`,运行中每秒刷新);
|
|
13
13
|
* 完成后父会话收到的 `<task-notification>` 只显示一行(`notificationSummary`),失败 / 停止另有一行提示。
|
|
14
14
|
* 外部 runner(claude / codex)同一组事件,用量可能缺。
|
|
15
|
+
* [W7-C] `subagent_background`(前台转后台):置 `background`,工具行出跟随行。
|
|
15
16
|
*/
|
|
16
17
|
import type { SessionEvent, SubagentStatus } from "../../agent/types.js";
|
|
17
18
|
import type { Usage } from "../../ai/types.js";
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* 后台任务的工具调用立即返回,工具行下面多一行跟随状态(`↳ t2 explore · 完成 · 7 轮 …`,运行中每秒刷新);
|
|
13
13
|
* 完成后父会话收到的 `<task-notification>` 只显示一行(`notificationSummary`),失败 / 停止另有一行提示。
|
|
14
14
|
* 外部 runner(claude / codex)同一组事件,用量可能缺。
|
|
15
|
+
* [W7-C] `subagent_background`(前台转后台):置 `background`,工具行出跟随行。
|
|
15
16
|
*/
|
|
16
17
|
import { msg } from "../../i18n/index.js";
|
|
17
18
|
import { formatElapsed } from "../../tui.js";
|
|
@@ -61,6 +62,13 @@ export class SubagentTracker {
|
|
|
61
62
|
}
|
|
62
63
|
return state;
|
|
63
64
|
}
|
|
65
|
+
case "subagent_background": {
|
|
66
|
+
const state = this.tasks.get(event.taskId);
|
|
67
|
+
if (state === undefined)
|
|
68
|
+
return undefined;
|
|
69
|
+
state.background = true;
|
|
70
|
+
return state;
|
|
71
|
+
}
|
|
64
72
|
case "subagent_end": {
|
|
65
73
|
const state = this.tasks.get(event.taskId);
|
|
66
74
|
if (state === undefined)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 前台子 Agent 任务转后台与栏内停止的界面接线(docs/agents-concurrency-plan.md §2.4、§2.8)。[W7-C]
|
|
3
|
+
*
|
|
4
|
+
* - `Ctrl+B`(`app.tasks.background`):有阻塞中的前台任务(`registry.blocking()`,含 `task_ctl wait`)时
|
|
5
|
+
* 全部转后台(`session.backgroundTask(undefined, "user")`),不看输入框;没有时落回编辑器。
|
|
6
|
+
* - 栏内 `b` / `Ctrl+B`:转后台选中的任务;`x`:1.5 s 内连按两次停止(`StopArm`)。
|
|
7
|
+
* - `/tasks bg [id]`(commands-core.ts,line 模式同):同 Ctrl+B / 指定任务。
|
|
8
|
+
* - Esc 中断只连带前台任务:仍在运行的后台任务写进中断提示(`runningBackground`)。
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentSession } from "../../agent/types.js";
|
|
11
|
+
import type { Keybindings } from "../../tui.js";
|
|
12
|
+
/** 栏内 `x` 两次之间的最长间隔。 */
|
|
13
|
+
export declare const STOP_CONFIRM_MS = 1500;
|
|
14
|
+
/** 正在阻塞父回合的任务(前台运行中、或有 `task_ctl wait` 在等)。 */
|
|
15
|
+
export declare function blockingTasks(session: AgentSession): string[];
|
|
16
|
+
/** 人工转后台:不给 taskId = 全部阻塞中的任务(没有时不调用,返回空)。 */
|
|
17
|
+
export declare function backgroundTasks(session: AgentSession, taskId?: string): string[];
|
|
18
|
+
/** 仍在运行的后台任务(Esc 中断不影响它们)。 */
|
|
19
|
+
export declare function runningBackground(session: AgentSession): string[];
|
|
20
|
+
/** 转后台后的一行提示;没有转任何任务时给「没有可转的」。 */
|
|
21
|
+
export declare function backgroundedText(ids: readonly string[]): string;
|
|
22
|
+
/** `ctrl+b` → `Ctrl+B`(提示行用);动作没有绑定时 undefined。 */
|
|
23
|
+
export declare function keyLabel(keys: Keybindings, action: "app.tasks.background"): string | undefined;
|
|
24
|
+
/** 栏内 `x` 的双击确认:第一次 `arm`(提示「再按 x 停止」),间隔内对同一任务再按为 `stop`。 */
|
|
25
|
+
export declare class StopArm {
|
|
26
|
+
private readonly windowMs;
|
|
27
|
+
private armed;
|
|
28
|
+
constructor(windowMs?: number);
|
|
29
|
+
press(taskId: string, now: number): "arm" | "stop";
|
|
30
|
+
reset(): void;
|
|
31
|
+
}
|