@armadra/agent 0.6.3 → 0.6.5
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 +29 -0
- package/CHANGELOG.zh-CN.md +21 -0
- 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 +5 -0
- package/dist/agent/session.js +21 -2
- package/dist/agent/subagent-direct.d.ts +5 -2
- package/dist/agent/subagent-direct.js +34 -1
- package/dist/agent/subagent-registry.d.ts +2 -1
- package/dist/agent/subagent-registry.js +2 -2
- package/dist/agent/types.d.ts +7 -0
- package/dist/agents/task-record.d.ts +3 -3
- package/dist/ai/apis/chatgpt-rate-limits.js +7 -2
- package/dist/ai/types.d.ts +2 -1
- package/dist/bundle/ama.cjs +417 -106
- package/dist/config/json-schema.js +1 -0
- package/dist/config/key-docs.js +8 -1
- package/dist/config/schema.js +2 -0
- package/dist/config/settings-registry.js +1 -0
- package/dist/config/types.d.ts +2 -0
- package/dist/drivers/acp/client.d.ts +13 -4
- package/dist/drivers/acp/client.js +17 -6
- package/dist/drivers/acp/types.d.ts +22 -2
- package/dist/drivers/runner.js +29 -2
- package/dist/i18n/catalog.d.ts +13 -0
- package/dist/i18n/messages/agents.d.ts +4 -0
- package/dist/i18n/messages/agents.js +4 -0
- package/dist/i18n/messages/config-keys.d.ts +2 -0
- package/dist/i18n/messages/config-keys.js +2 -0
- package/dist/i18n/messages/config.d.ts +2 -0
- 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 +10 -0
- package/dist/i18n/messages/interactive.js +6 -0
- package/dist/i18n/messages/report.d.ts +8 -0
- package/dist/i18n/messages/report.js +8 -0
- package/dist/i18n/messages/settings.d.ts +2 -0
- package/dist/i18n/messages/settings.js +2 -0
- package/dist/modes/commands-core.js +11 -0
- package/dist/modes/interactive/agent-ui.js +1 -0
- package/dist/modes/interactive/agent-view.d.ts +5 -0
- package/dist/modes/interactive/agent-view.js +14 -3
- package/dist/modes/interactive/interactive-mode.js +13 -1
- 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 +8 -1
- package/dist/modes/interactive/key-dispatch.js +37 -0
- package/dist/modes/interactive/line/line-mode.d.ts +1 -0
- package/dist/modes/interactive/line/line-mode.js +29 -7
- package/dist/modes/interactive/run-indicator.d.ts +19 -2
- package/dist/modes/interactive/run-indicator.js +29 -7
- package/dist/modes/interactive/status-bar.d.ts +4 -1
- package/dist/modes/interactive/status-bar.js +17 -6
- package/dist/modes/interactive/status-line.d.ts +10 -9
- package/dist/modes/interactive/status-line.js +46 -38
- package/dist/modes/interactive/status-quota.d.ts +16 -2
- package/dist/modes/interactive/status-quota.js +54 -34
- package/dist/modes/interactive/task-background.d.ts +2 -2
- package/dist/modes/rpc/commands.d.ts +2 -0
- package/dist/modes/rpc/commands.js +15 -1
- package/dist/rpc.d.ts +7 -0
- package/dist/tools/types.d.ts +6 -0
- package/dist/tui/keybindings.d.ts +7 -0
- package/dist/tui/keybindings.js +7 -0
- package/docs/acp.md +3 -0
- package/docs/en/rpc.md +9 -7
- package/docs/en/tui.md +49 -31
- package/docs/rpc.md +9 -7
- package/docs/session-format.md +1 -1
- package/docs/tui.md +49 -31
- package/package.json +1 -1
|
@@ -2,23 +2,24 @@
|
|
|
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 返回空)。
|
|
17
18
|
* - [W6] 配色(用户参考):标签、单位、`•`、括号 dim;速率数字 `tool`(紫);输出量与耗时、avg 数字 accent(蓝);
|
|
18
|
-
* ttft 数字 `tool
|
|
19
|
+
* ttft 数字 `tool`;用量项 dim;回退模型 warning。
|
|
19
20
|
*/
|
|
20
21
|
import { truncateToWidth } from "../../tui.js";
|
|
21
|
-
import { formatTokens, layoutRow, usageItems, } from "./status-bar.js";
|
|
22
|
+
import { abbreviateModel, formatTokens, layoutRow, usageItems, } from "./status-bar.js";
|
|
22
23
|
/** 速率:< 10 保留一位小数,其余取整。 */
|
|
23
24
|
export function formatRate(tps) {
|
|
24
25
|
return tps < 10 ? tps.toFixed(1) : String(Math.round(tps));
|
|
@@ -34,19 +35,21 @@ export function formatSeconds(ms) {
|
|
|
34
35
|
/** 占位宽度(常见的最宽形状):`tps: 999 tok/s`、`1.2k tok / 59.9s`、`avg 999`、`ttft 9.9s`。 */
|
|
35
36
|
const RESERVE = { tps: 14, amount: 16, avg: 7, ttft: 9 };
|
|
36
37
|
const PRIORITY = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
codemode: 1,
|
|
39
|
+
sandbox: 2,
|
|
40
|
+
fallback: 3,
|
|
40
41
|
preset: 4,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
queue: 5,
|
|
43
|
+
host: 6,
|
|
44
|
+
ttft: 7,
|
|
45
|
+
avg: 8,
|
|
46
|
+
rebill: 9,
|
|
47
|
+
cache: 10,
|
|
48
|
+
tokens: 11,
|
|
49
|
+
amount: 12,
|
|
47
50
|
};
|
|
48
51
|
export const COLLAPSE_HINT = "[-]";
|
|
49
|
-
/** 用户样例:`tps: 100 tok/s • 546 tok / 5.5s (avg 100 · ttft 1.4s)
|
|
52
|
+
/** 用户样例:`tps: 100 tok/s • 546 tok / 5.5s (avg 100 · ttft 1.4s)`;其后的用量项与左区开关以 ` · ` 分隔。 */
|
|
50
53
|
const RATE_STYLE = { groups: { stats: { glue: " · ", open: "(", close: ")" } } };
|
|
51
54
|
export class StatusLine {
|
|
52
55
|
bar;
|
|
@@ -57,7 +60,7 @@ export class StatusLine {
|
|
|
57
60
|
this.source = source;
|
|
58
61
|
this.theme = theme;
|
|
59
62
|
}
|
|
60
|
-
parts() {
|
|
63
|
+
parts(width) {
|
|
61
64
|
const theme = this.theme;
|
|
62
65
|
const dim = (text) => theme.fg("dim", text);
|
|
63
66
|
const stats = this.bar.current();
|
|
@@ -65,10 +68,10 @@ export class StatusLine {
|
|
|
65
68
|
const live = telemetry?.live;
|
|
66
69
|
const last = telemetry?.last;
|
|
67
70
|
const parts = [];
|
|
68
|
-
const
|
|
69
|
-
const
|
|
71
|
+
const metric = (text, priority, reserve, extra = {}) => void parts.push({ text, priority, zone: "right", reserve, ...extra });
|
|
72
|
+
const add = (zone, text, priority) => {
|
|
70
73
|
if (text !== undefined)
|
|
71
|
-
parts.push({ text, priority, zone
|
|
74
|
+
parts.push({ text, priority, zone });
|
|
72
75
|
};
|
|
73
76
|
const blue = (text) => theme.fg("accent", text);
|
|
74
77
|
const purple = (text) => theme.fg("tool", text);
|
|
@@ -86,33 +89,38 @@ export class StatusLine {
|
|
|
86
89
|
}
|
|
87
90
|
}
|
|
88
91
|
const label = live !== undefined ? theme.fg("accent", "tps:") : dim("tps:");
|
|
89
|
-
|
|
92
|
+
metric(`${label} ${tps === undefined ? dim("—") : `${purple(formatRate(tps))}${dim(" tok/s")}`}`, undefined, RESERVE.tps);
|
|
90
93
|
if (amount !== undefined) {
|
|
91
|
-
|
|
94
|
+
metric(amount, PRIORITY.amount, RESERVE.amount, { lead: ` ${theme.glyphs.dot} ` });
|
|
92
95
|
}
|
|
93
96
|
const stat = { group: "stats", lead: " " };
|
|
94
97
|
if (telemetry?.avgTps !== undefined) {
|
|
95
|
-
|
|
98
|
+
metric(`${dim("avg ")}${blue(formatRate(telemetry.avgTps))}`, PRIORITY.avg, RESERVE.avg, stat);
|
|
96
99
|
}
|
|
97
100
|
if (last?.ttftMs !== undefined) {
|
|
98
|
-
|
|
101
|
+
metric(`${dim("ttft ")}${purple(formatSeconds(last.ttftMs))}`, PRIORITY.ttft, RESERVE.ttft, stat);
|
|
99
102
|
}
|
|
100
103
|
const usage = usageItems(stats, this.bar.queued(), this.source, theme);
|
|
101
|
-
right
|
|
102
|
-
right
|
|
103
|
-
right
|
|
104
|
-
right(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
104
|
+
add("right", usage.tokens, PRIORITY.tokens);
|
|
105
|
+
add("right", usage.cache, PRIORITY.cache);
|
|
106
|
+
add("right", usage.rebill, PRIORITY.rebill);
|
|
107
|
+
add("right", dim(COLLAPSE_HINT), undefined);
|
|
108
|
+
add("left", usage.codemode, PRIORITY.codemode);
|
|
109
|
+
add("left", usage.sandbox, PRIORITY.sandbox);
|
|
110
|
+
add("left", usage.preset, PRIORITY.preset);
|
|
111
|
+
const fallback = this.source.fallback?.();
|
|
112
|
+
if (fallback !== undefined) {
|
|
113
|
+
const arrow = theme.glyphs.ascii ? "-> " : "→ ";
|
|
114
|
+
add("left", dim(arrow) + theme.fg("warning", abbreviateModel(fallback.to, width)), PRIORITY.fallback);
|
|
115
|
+
}
|
|
116
|
+
add("left", usage.queue, PRIORITY.queue);
|
|
117
|
+
add("left", usage.host, PRIORITY.host);
|
|
110
118
|
return parts;
|
|
111
119
|
}
|
|
112
120
|
render(width) {
|
|
113
121
|
if (this.bar.layout() !== "full")
|
|
114
122
|
return [];
|
|
115
|
-
return [truncateToWidth(layoutRow(this.parts(), width, this.theme, RATE_STYLE), width)];
|
|
123
|
+
return [truncateToWidth(layoutRow(this.parts(width), width, this.theme, RATE_STYLE), width)];
|
|
116
124
|
}
|
|
117
125
|
invalidate() { }
|
|
118
126
|
}
|
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
* ```
|
|
8
8
|
*
|
|
9
9
|
* - 数据:会话的 `quota_update`(codex flavor 的 `x-codex-primary/secondary-*` 头、`codex.rate_limits` 事件;
|
|
10
|
-
* SIWC 只有 429)。
|
|
10
|
+
* SIWC 只有 429)。[W7] 标签按窗口时长认(300 分钟 = 5 小时 / Session、10080 = 本周 / Weekly,其它用实际时长),
|
|
11
|
+
* 缺时长时按槽位推断(primary 5 小时、secondary 本周);按时长从短到长排;全 0 的窗口不显示。
|
|
11
12
|
* - 只在当前模型走 ChatGPT 订阅(compat `chatgptBackend`)时出现;codex flavor 还没有数据时显示
|
|
12
13
|
* 「配额:首次请求后显示」(第一次请求就会带回配额,先占住行免得行数跳动),SIWC 没有数据时不占行
|
|
13
14
|
* (只有超限才有配额,占位会一直挂着误导)。
|
|
14
15
|
* - 着色:标签与分隔 dim;百分比按阈值(≥ 70% warning、≥ 90% error,否则 success);重置时间 `tool`(紫)。
|
|
15
16
|
* - 重置时间是相对时长(`2h 18m`、`6d 5h`),按渲染时刻算;StatusArea 每分钟重画一次。
|
|
16
17
|
*/
|
|
17
|
-
import type { QuotaUpdateEvent } from "../../agent/types-w6.js";
|
|
18
|
+
import type { QuotaUpdateEvent, QuotaWindow } from "../../agent/types-w6.js";
|
|
18
19
|
import type { StatusLineMode } from "../../config/types.js";
|
|
19
20
|
import { type Component, type Theme } from "../../tui.js";
|
|
20
21
|
import { type Part } from "./status-bar.js";
|
|
@@ -26,6 +27,18 @@ export type QuotaView = {
|
|
|
26
27
|
export declare const QUOTA_NARROW_WIDTH = 80;
|
|
27
28
|
/** 距重置:`6d 5h` / `2h 18m` / `18m`(已过或不足一分钟 `0m`);`tight` 去掉空格。 */
|
|
28
29
|
export declare function formatRemaining(ms: number, tight?: boolean): string;
|
|
30
|
+
/**
|
|
31
|
+
* [W7] 一个窗口显示成什么:按**窗口时长**认(300 = 5 小时、10080 = 本周),不按 primary / secondary 槽位——
|
|
32
|
+
* 有的套餐 codex 把周窗口放在 primary(0.6.3 因此显示成 `7d:`)。缺时长时按槽位推断(primary 5 小时、
|
|
33
|
+
* secondary 本周),与另一个窗口撞名时取另一种;时长 ≤ 0 视同缺失(0.6.3 把它渲染成 `0d`)。
|
|
34
|
+
*/
|
|
35
|
+
type WindowKind = "session" | "weekly" | number;
|
|
36
|
+
interface Shown {
|
|
37
|
+
kind: WindowKind;
|
|
38
|
+
window: QuotaWindow;
|
|
39
|
+
}
|
|
40
|
+
/** 要显示的窗口,按时长从短到长(5 小时在前、本周在后)。 */
|
|
41
|
+
export declare function shownWindows(quota: QuotaUpdateEvent): Shown[];
|
|
29
42
|
/** compact 行尾的短项 `5h 10% wk 31%`(项内只用空格,不含 ` · `,宿主解析不受影响)。 */
|
|
30
43
|
export declare function compactQuotaItem(quota: QuotaUpdateEvent, theme: Theme): string | undefined;
|
|
31
44
|
/** 配额行各项(宽 / 窄两种格式);重置时间先丢,主窗口百分比永不丢。 */
|
|
@@ -42,3 +55,4 @@ export declare class QuotaLine implements Component {
|
|
|
42
55
|
render(width: number): string[];
|
|
43
56
|
invalidate(): void;
|
|
44
57
|
}
|
|
58
|
+
export {};
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* ```
|
|
8
8
|
*
|
|
9
9
|
* - 数据:会话的 `quota_update`(codex flavor 的 `x-codex-primary/secondary-*` 头、`codex.rate_limits` 事件;
|
|
10
|
-
* SIWC 只有 429)。
|
|
10
|
+
* SIWC 只有 429)。[W7] 标签按窗口时长认(300 分钟 = 5 小时 / Session、10080 = 本周 / Weekly,其它用实际时长),
|
|
11
|
+
* 缺时长时按槽位推断(primary 5 小时、secondary 本周);按时长从短到长排;全 0 的窗口不显示。
|
|
11
12
|
* - 只在当前模型走 ChatGPT 订阅(compat `chatgptBackend`)时出现;codex flavor 还没有数据时显示
|
|
12
13
|
* 「配额:首次请求后显示」(第一次请求就会带回配额,先占住行免得行数跳动),SIWC 没有数据时不占行
|
|
13
14
|
* (只有超限才有配额,占位会一直挂着误导)。
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
* - 重置时间是相对时长(`2h 18m`、`6d 5h`),按渲染时刻算;StatusArea 每分钟重画一次。
|
|
16
17
|
*/
|
|
17
18
|
import { msg } from "../../i18n/index.js";
|
|
18
|
-
import { levelColor,
|
|
19
|
+
import { levelColor, visibleWidth } from "../../tui.js";
|
|
19
20
|
import { layoutRow } from "./status-bar.js";
|
|
20
21
|
/** 第三行改用短格式的宽度。 */
|
|
21
22
|
export const QUOTA_NARROW_WIDTH = 80;
|
|
@@ -41,40 +42,57 @@ function windowSpan(minutes) {
|
|
|
41
42
|
return `${minutes / 60}h`;
|
|
42
43
|
return `${Math.round(minutes)}m`;
|
|
43
44
|
}
|
|
44
|
-
function
|
|
45
|
-
const m =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
function minutesOf(w) {
|
|
46
|
+
const m = w.windowMinutes;
|
|
47
|
+
return m !== undefined && Number.isFinite(m) && m > 0 ? m : undefined;
|
|
48
|
+
}
|
|
49
|
+
function kindOf(minutes) {
|
|
50
|
+
if (minutes === FIVE_HOURS)
|
|
51
|
+
return "session";
|
|
52
|
+
if (minutes === WEEK)
|
|
53
|
+
return "weekly";
|
|
54
|
+
return minutes;
|
|
53
55
|
}
|
|
54
|
-
|
|
56
|
+
const SPAN = { session: FIVE_HOURS, weekly: WEEK };
|
|
57
|
+
function labels(kind) {
|
|
55
58
|
const m = msg().interactive.statusLine.quota;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return
|
|
59
|
+
if (kind === "session")
|
|
60
|
+
return { pct: m.session, reset: m.reset, short: m.short5h };
|
|
61
|
+
if (kind === "weekly")
|
|
62
|
+
return { pct: m.weekly, reset: m.weeklyReset, short: m.shortWeek };
|
|
63
|
+
const span = windowSpan(kind);
|
|
64
|
+
return { pct: m.window(span), reset: m.windowReset(span), short: span };
|
|
62
65
|
}
|
|
63
66
|
function percent(theme, used, decimal) {
|
|
64
67
|
return theme.fg(levelColor(used / 100), decimal ? `${used.toFixed(1)}%` : `${Math.round(used)}%`);
|
|
65
68
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
/** 没有任何数据的窗口(0%、无时长、无重置时间)不占位——服务端用全 0 表示「没有这个窗口」。 */
|
|
70
|
+
function hasData(w) {
|
|
71
|
+
return w.usedPercent !== 0 || minutesOf(w) !== undefined || w.resetsAt !== undefined;
|
|
72
|
+
}
|
|
73
|
+
/** 要显示的窗口,按时长从短到长(5 小时在前、本周在后)。 */
|
|
74
|
+
export function shownWindows(quota) {
|
|
75
|
+
const slots = ["primary", "secondary"]
|
|
76
|
+
.map((which) => ({ which, w: quota[which] }))
|
|
77
|
+
.filter((x) => x.w !== undefined)
|
|
78
|
+
.filter((x) => hasData(x.w));
|
|
79
|
+
const known = slots.map((x) => kindOf(minutesOf(x.w)));
|
|
80
|
+
const out = slots.map((x, i) => {
|
|
81
|
+
const own = known[i];
|
|
82
|
+
if (own !== undefined)
|
|
83
|
+
return { kind: own, window: x.w };
|
|
84
|
+
const guess = x.which === "primary" ? "session" : "weekly";
|
|
85
|
+
const taken = known.some((k, j) => j !== i && k === guess);
|
|
86
|
+
const kind = taken ? (guess === "session" ? "weekly" : "session") : guess;
|
|
87
|
+
return { kind, window: x.w };
|
|
88
|
+
});
|
|
89
|
+
const span = (k) => (typeof k === "number" ? k : SPAN[k]);
|
|
90
|
+
return out.sort((a, b) => span(a.kind) - span(b.kind));
|
|
73
91
|
}
|
|
74
92
|
/** compact 行尾的短项 `5h 10% wk 31%`(项内只用空格,不含 ` · `,宿主解析不受影响)。 */
|
|
75
93
|
export function compactQuotaItem(quota, theme) {
|
|
76
94
|
const dim = (text) => theme.fg("dim", text);
|
|
77
|
-
const items =
|
|
95
|
+
const items = shownWindows(quota).map(({ kind, window: w }) => `${dim(labels(kind).short)} ${percent(theme, w.usedPercent, false)}`);
|
|
78
96
|
return items.length === 0 ? undefined : items.join(" ");
|
|
79
97
|
}
|
|
80
98
|
/** 配额行各项(宽 / 窄两种格式);重置时间先丢,主窗口百分比永不丢。 */
|
|
@@ -82,14 +100,15 @@ export function quotaParts(quota, width, now, theme) {
|
|
|
82
100
|
const dim = (text) => theme.fg("dim", text);
|
|
83
101
|
const narrow = width < QUOTA_NARROW_WIDTH;
|
|
84
102
|
const parts = [];
|
|
85
|
-
|
|
103
|
+
shownWindows(quota).forEach(({ kind, window: w }, i) => {
|
|
86
104
|
const first = i === 0;
|
|
87
|
-
const group = narrow ?
|
|
88
|
-
const
|
|
105
|
+
const group = narrow ? `w${i}` : undefined;
|
|
106
|
+
const label = labels(kind);
|
|
107
|
+
const name = narrow ? `${label.short} ` : label.pct;
|
|
89
108
|
parts.push({
|
|
90
109
|
text: dim(name) + percent(theme, w.usedPercent, !narrow),
|
|
91
110
|
priority: first ? undefined : 2,
|
|
92
|
-
zone: "
|
|
111
|
+
zone: "right",
|
|
93
112
|
...(group !== undefined ? { group } : {}),
|
|
94
113
|
});
|
|
95
114
|
if (w.resetsAt === undefined)
|
|
@@ -98,11 +117,11 @@ export function quotaParts(quota, width, now, theme) {
|
|
|
98
117
|
parts.push({
|
|
99
118
|
text: narrow
|
|
100
119
|
? theme.fg("tool", `${theme.glyphs.retry}${left}`)
|
|
101
|
-
: dim(
|
|
120
|
+
: dim(label.reset) + theme.fg("tool", left),
|
|
102
121
|
priority: first ? 3 : 1,
|
|
103
|
-
zone: "
|
|
122
|
+
zone: "right",
|
|
104
123
|
// 宽格式按最宽形状占位(`23h 59m`),数值变化不让某项时有时无
|
|
105
|
-
reserve: narrow ? 0 : visibleWidth(
|
|
124
|
+
reserve: narrow ? 0 : visibleWidth(label.reset) + 7,
|
|
106
125
|
...(group !== undefined ? { group } : {}),
|
|
107
126
|
});
|
|
108
127
|
});
|
|
@@ -123,7 +142,8 @@ export class QuotaLine {
|
|
|
123
142
|
return [];
|
|
124
143
|
const theme = this.theme;
|
|
125
144
|
if (view === "pending") {
|
|
126
|
-
|
|
145
|
+
const text = theme.fg("dim", msg().interactive.statusLine.quota.pending);
|
|
146
|
+
return [layoutRow([{ text, priority: undefined, zone: "right" }], width, theme)];
|
|
127
147
|
}
|
|
128
148
|
const parts = quotaParts(view.quota, width, this.source.now(), theme);
|
|
129
149
|
if (parts.length === 0)
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Esc 中断只连带前台任务:仍在运行的后台任务写进中断提示(`runningBackground`)。
|
|
9
9
|
*/
|
|
10
10
|
import type { AgentSession } from "../../agent/types.js";
|
|
11
|
-
import type { Keybindings } from "../../tui.js";
|
|
11
|
+
import type { ActionId, Keybindings } from "../../tui.js";
|
|
12
12
|
/** 栏内 `x` 两次之间的最长间隔。 */
|
|
13
13
|
export declare const STOP_CONFIRM_MS = 1500;
|
|
14
14
|
/** 正在阻塞父回合的任务(前台运行中、或有 `task_ctl wait` 在等)。 */
|
|
@@ -20,7 +20,7 @@ export declare function runningBackground(session: AgentSession): string[];
|
|
|
20
20
|
/** 转后台后的一行提示;没有转任何任务时给「没有可转的」。 */
|
|
21
21
|
export declare function backgroundedText(ids: readonly string[]): string;
|
|
22
22
|
/** `ctrl+b` → `Ctrl+B`(提示行用);动作没有绑定时 undefined。 */
|
|
23
|
-
export declare function keyLabel(keys: Keybindings, action:
|
|
23
|
+
export declare function keyLabel(keys: Keybindings, action: ActionId): string | undefined;
|
|
24
24
|
/** 栏内 `x` 的双击确认:第一次 `arm`(提示「再按 x 停止」),间隔内对同一任务再按为 `stop`。 */
|
|
25
25
|
export declare class StopArm {
|
|
26
26
|
private readonly windowMs;
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* `plan.unattended`;`approve_fresh` 在这里新建会话并以计划全文开新回合。`get_tasks / get_agents`
|
|
13
13
|
* 读 `RpcContext.tasks / agents`(W5-G 的注册表与发现结果;未装配时回空表)。
|
|
14
14
|
* - [W7-B2] `background_task { taskId? }` → `{ backgrounded }`(`session.backgroundTask`,B1)。
|
|
15
|
+
* - 打断并发送:`prompt / steer` 的 `interrupt: true` → `session.prompt(message, { interrupt })`(中止当前回合、
|
|
16
|
+
* 立刻以「排队的 steer + 本条」开新回合),应答 `started`。
|
|
15
17
|
*/
|
|
16
18
|
import type { AgentInfo } from "../../agents/types.js";
|
|
17
19
|
import type { TaskRegistryView } from "../../tools/types.js";
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* `plan.unattended`;`approve_fresh` 在这里新建会话并以计划全文开新回合。`get_tasks / get_agents`
|
|
13
13
|
* 读 `RpcContext.tasks / agents`(W5-G 的注册表与发现结果;未装配时回空表)。
|
|
14
14
|
* - [W7-B2] `background_task { taskId? }` → `{ backgrounded }`(`session.backgroundTask`,B1)。
|
|
15
|
+
* - 打断并发送:`prompt / steer` 的 `interrupt: true` → `session.prompt(message, { interrupt })`(中止当前回合、
|
|
16
|
+
* 立刻以「排队的 steer + 本条」开新回合),应答 `started`。
|
|
15
17
|
*/
|
|
16
18
|
import { formatModelRef } from "../../ai/providers/channels.js";
|
|
17
19
|
import { AgentSessionImpl } from "../../agent/session.js";
|
|
@@ -82,6 +84,12 @@ function applyPlanAttendance(ctx) {
|
|
|
82
84
|
planController(ctx.session())?.setAttendance(ctx.capabilities.has("plans") ? "client" : "unattended");
|
|
83
85
|
}
|
|
84
86
|
/** 开始一次运行:开始 / 入队 / 被处理后即返回,不等运行结束。 */
|
|
87
|
+
/** `prompt / steer` 的 `interrupt`:可选布尔。 */
|
|
88
|
+
function interruptFlag(value) {
|
|
89
|
+
if (value !== undefined && typeof value !== "boolean")
|
|
90
|
+
throw new AmaError("invalid_arguments", "interrupt must be a boolean");
|
|
91
|
+
return value === true;
|
|
92
|
+
}
|
|
85
93
|
async function startRun(ctx, run) {
|
|
86
94
|
const session = ctx.session();
|
|
87
95
|
let settled = false;
|
|
@@ -155,8 +163,14 @@ export const handlers = {
|
|
|
155
163
|
prompt: (p, ctx) => startRun(ctx, () => ctx.session().prompt(p.message, {
|
|
156
164
|
...(p.images !== undefined ? { images: p.images } : {}),
|
|
157
165
|
...(p.streamingBehavior !== undefined ? { streamingBehavior: p.streamingBehavior } : {}),
|
|
166
|
+
...(interruptFlag(p.interrupt) ? { interrupt: true } : {}),
|
|
158
167
|
})),
|
|
159
|
-
steer: (p, ctx) => startRun(ctx, () =>
|
|
168
|
+
steer: (p, ctx) => startRun(ctx, () => interruptFlag(p.interrupt)
|
|
169
|
+
? ctx.session().prompt(p.message, {
|
|
170
|
+
interrupt: true,
|
|
171
|
+
...(p.images !== undefined ? { images: p.images } : {}),
|
|
172
|
+
})
|
|
173
|
+
: ctx.session().steer(p.message)),
|
|
160
174
|
follow_up: (p, ctx) => startRun(ctx, () => ctx.session().followUp(p.message)),
|
|
161
175
|
abort: async (_p, ctx) => {
|
|
162
176
|
await ctx.session().abort();
|
package/dist/rpc.d.ts
CHANGED
|
@@ -45,14 +45,21 @@ export interface RpcHello {
|
|
|
45
45
|
/** 无参数命令。 */
|
|
46
46
|
export type NoParams = Record<never, never>;
|
|
47
47
|
export interface RpcCommandMap {
|
|
48
|
+
/**
|
|
49
|
+
* `interrupt: true`:运行中中止当前回合(工具按 abort 收尾),立刻以「排队的 steer + 本条」开新回合
|
|
50
|
+
* (user 消息 origin `interrupt`),应答 `started`;空闲时等同不填。优先于 `streamingBehavior`。
|
|
51
|
+
*/
|
|
48
52
|
prompt: {
|
|
49
53
|
message: string;
|
|
50
54
|
images?: ImageBlock[];
|
|
51
55
|
streamingBehavior?: "steer" | "followUp";
|
|
56
|
+
interrupt?: boolean;
|
|
52
57
|
};
|
|
58
|
+
/** `interrupt: true` 同 `prompt`。 */
|
|
53
59
|
steer: {
|
|
54
60
|
message: string;
|
|
55
61
|
images?: ImageBlock[];
|
|
62
|
+
interrupt?: boolean;
|
|
56
63
|
};
|
|
57
64
|
follow_up: {
|
|
58
65
|
message: string;
|
package/dist/tools/types.d.ts
CHANGED
|
@@ -181,6 +181,12 @@ export interface RunnerHandle {
|
|
|
181
181
|
send(text: string): Promise<void>;
|
|
182
182
|
wait(): Promise<SubagentResult>;
|
|
183
183
|
stop(): Promise<void>;
|
|
184
|
+
/**
|
|
185
|
+
* 打断当前回合并立即以 `text` 开下一回合(子 Agent 视图的「打断并发送」);`wait()` 跟到这一回合结束。
|
|
186
|
+
* 驱动能中断回合(ACP `session/cancel`、Claude stream-json interrupt、Codex `turn/interrupt`)且正在
|
|
187
|
+
* 运行时返回 true;否则 false(调用方退回排队)。
|
|
188
|
+
*/
|
|
189
|
+
interrupt?(text: string): Promise<boolean>;
|
|
184
190
|
}
|
|
185
191
|
/** `ama`(AmaRunner)| `claude` | `codex` | `acp:<program>`(ProcessRunner,W5-E)| 宿主 id。 */
|
|
186
192
|
export interface SubagentRunner {
|
|
@@ -36,6 +36,13 @@ export declare const DEFAULT_KEYBINDINGS: {
|
|
|
36
36
|
readonly "app.exit": readonly ["ctrl+d"];
|
|
37
37
|
readonly "app.message.followUp": readonly ["alt+enter"];
|
|
38
38
|
readonly "app.message.dequeue": readonly ["alt+up"];
|
|
39
|
+
/**
|
|
40
|
+
* 运行中打断当前回合并立即发送输入框的文字(连同排队的插话);`ui.enterWhileRunning: "interrupt"` 时与
|
|
41
|
+
* Enter 互换(它变成排队)。缺省 `Ctrl+X`:各终端(macOS Terminal、iTerm2、tmux、Windows Terminal)都原样
|
|
42
|
+
* 送达 0x18,且没有别的绑定;`Ctrl+Enter` 不开 kitty 键盘协议时与 Enter 无法区分,`Ctrl+S` 可能被 XOFF
|
|
43
|
+
* 流控吃掉,`Alt+Enter` 已是 followUp,`Alt+<字母>` 在 macOS 缺省不送 Meta,`Ctrl+]` 在非美式键盘难按。
|
|
44
|
+
*/
|
|
45
|
+
readonly "app.message.interrupt": readonly ["ctrl+x"];
|
|
39
46
|
readonly "app.permission.cycle": readonly ["shift+tab", "tab"];
|
|
40
47
|
readonly "app.tools.expand": readonly ["ctrl+o"];
|
|
41
48
|
readonly "app.model.select": readonly ["ctrl+l"];
|
package/dist/tui/keybindings.js
CHANGED
|
@@ -39,6 +39,13 @@ export const DEFAULT_KEYBINDINGS = {
|
|
|
39
39
|
"app.exit": ["ctrl+d"],
|
|
40
40
|
"app.message.followUp": ["alt+enter"],
|
|
41
41
|
"app.message.dequeue": ["alt+up"],
|
|
42
|
+
/**
|
|
43
|
+
* 运行中打断当前回合并立即发送输入框的文字(连同排队的插话);`ui.enterWhileRunning: "interrupt"` 时与
|
|
44
|
+
* Enter 互换(它变成排队)。缺省 `Ctrl+X`:各终端(macOS Terminal、iTerm2、tmux、Windows Terminal)都原样
|
|
45
|
+
* 送达 0x18,且没有别的绑定;`Ctrl+Enter` 不开 kitty 键盘协议时与 Enter 无法区分,`Ctrl+S` 可能被 XOFF
|
|
46
|
+
* 流控吃掉,`Alt+Enter` 已是 followUp,`Alt+<字母>` 在 macOS 缺省不送 Meta,`Ctrl+]` 在非美式键盘难按。
|
|
47
|
+
*/
|
|
48
|
+
"app.message.interrupt": ["ctrl+x"],
|
|
42
49
|
"app.permission.cycle": ["shift+tab", "tab"],
|
|
43
50
|
"app.tools.expand": ["ctrl+o"],
|
|
44
51
|
"app.model.select": ["ctrl+l"],
|
package/docs/acp.md
CHANGED
|
@@ -64,6 +64,9 @@ stdin 关闭后等已开始的运行结束再退出(0);SIGINT / SIGTERM
|
|
|
64
64
|
- `session/request_permission` 交给 `onPermission`;没有处理器时回首个 `reject_once`(无人值守)。
|
|
65
65
|
- `cancel(sessionId)` 发 `session/cancel`,并让该会话挂起的权限请求回 `cancelled`(规范要求)。
|
|
66
66
|
- 只接受 Agent 自己给出的 `optionId`。
|
|
67
|
+
- 开会话(`newSession` / `resumeSession` / `loadSession`)的 `mcpServers` 缺省为空数组;宿主可传第三个参数
|
|
68
|
+
`{ mcpServers }`(如 stdio 的 `{ name, command, args, env: [{ name, value }] }`),原样转发给 Agent。
|
|
69
|
+
`AcpClient.features.mcpServers === true` 表示支持(旧版没有 `features`)。ama 自己作客户端时仍不传。
|
|
67
70
|
|
|
68
71
|
`AcpDriver` 在客户端之上实现驱动契约(`AgentDriver`):续接优先 `session/resume`,其次 `session/load`(回放的历史丢弃),都不支持就新开并提示;按 ama 模式 `session/set_mode`,只读模式找不到对应模式 id 时拒绝启动。
|
|
69
72
|
|
package/docs/en/rpc.md
CHANGED
|
@@ -41,16 +41,18 @@ A command has the shape `{ "id"?: string, "type": <command name>, ...parameters
|
|
|
41
41
|
|
|
42
42
|
### Prompts
|
|
43
43
|
|
|
44
|
-
| Command | Parameters
|
|
45
|
-
| ------------- |
|
|
46
|
-
| `prompt` | `message: string`, `images?: ImageBlock[]`, `streamingBehavior?: "steer" \| "followUp"` | `{ disposition: "started" \| "queued" \| "handled" }` |
|
|
47
|
-
| `steer` | `message`, `images
|
|
48
|
-
| `follow_up` | `message`, `images?`
|
|
49
|
-
| `abort` | —
|
|
50
|
-
| `clear_queue` | —
|
|
44
|
+
| Command | Parameters | `data` |
|
|
45
|
+
| ------------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
|
|
46
|
+
| `prompt` | `message: string`, `images?: ImageBlock[]`, `streamingBehavior?: "steer" \| "followUp"`, `interrupt?: boolean` | `{ disposition: "started" \| "queued" \| "handled" }` |
|
|
47
|
+
| `steer` | `message`, `images?`, `interrupt?: boolean` | Same as above |
|
|
48
|
+
| `follow_up` | `message`, `images?` | Same as above |
|
|
49
|
+
| `abort` | — | `{}` (answered once idle again; the queue is not cleared) |
|
|
50
|
+
| `clear_queue` | — | `{ steering: string[], followUp: string[] }` (the cleared text) |
|
|
51
51
|
|
|
52
52
|
Prompt commands **do not wait for the run to finish**: they are answered as soon as the session starts running (`before_agent_start` / `agent_start`), or the message is queued or handled (for example a slash command, or a hook block); progress arrives as events. Sending `prompt` while running without `streamingBehavior` fails with `code: "busy"`; with `steer` / `followUp` it is queued. Run failures after the response are reported as `{"type":"notification","level":"error","message":…}`.
|
|
53
53
|
|
|
54
|
+
**Interrupt and send now**: with `interrupt: true` on `prompt` / `steer` (it takes precedence over `streamingBehavior`), a running session first takes the queued steers, stops the current turn (the model stream is cut, running tools finish as interrupted so every tool call has exactly one `aborted by user` result, and the interrupted assistant message is persisted with `stopReason: "aborted"`), then immediately starts a new turn with "queued steers… + this message" (joined by blank lines) and answers `{ disposition: "started" }`; the new user message has `origin: "interrupt"`. followUp messages stay queued and are delivered after the new turn; background sub-agents are not affected. When idle it is the same as leaving it out. A non-boolean `interrupt` → `invalid_arguments`; running with both the message and the queued steers empty → `invalid_arguments` (nothing is interrupted). Event order: `queue_update` (steers taken) → the old turn's `message_end` (aborted) → `agent_settled` → `agent_start` → the response → the new user `message_end` … (golden record `test/fixtures/rpc/interrupt.out.jsonl`). The new request starts with every message of the interrupted request, so the cache keeps hitting. In the SDK: `session.prompt(text, { interrupt: true })` / `session.steer(text, { interrupt: true })`.
|
|
55
|
+
|
|
54
56
|
### State
|
|
55
57
|
|
|
56
58
|
| Command | Parameters | `data` |
|