@armadra/agent 0.7.1 → 0.7.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 +61 -0
- package/CHANGELOG.zh-CN.md +38 -0
- package/dist/agent/session-cache.d.ts +4 -2
- package/dist/agent/session-cache.js +5 -3
- package/dist/agent/session-compaction.d.ts +19 -1
- package/dist/agent/session-compaction.js +42 -0
- package/dist/agent/session-core.d.ts +2 -0
- package/dist/agent/session-state.d.ts +9 -3
- package/dist/agent/session-state.js +8 -6
- package/dist/agent/session.js +3 -3
- package/dist/agent/types-w5.d.ts +3 -0
- package/dist/agent/types.d.ts +18 -0
- package/dist/agents/task-record.d.ts +4 -0
- package/dist/agents/task-record.js +18 -0
- package/dist/ai/context.d.ts +7 -2
- package/dist/ai/context.js +45 -7
- package/dist/ai/providers/catalog-data.js +1 -1
- package/dist/bundle/ama.cjs +1663 -486
- package/dist/compaction/breakdown.d.ts +51 -0
- package/dist/compaction/breakdown.js +159 -0
- package/dist/compaction/estimate.d.ts +6 -0
- package/dist/compaction/estimate.js +1 -1
- package/dist/compaction/prefix-estimate.d.ts +25 -0
- package/dist/compaction/prefix-estimate.js +38 -0
- package/dist/config/json-schema.js +1 -0
- package/dist/config/key-docs.js +1 -0
- 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/native/codex-app-server.js +6 -0
- package/dist/drivers/runner.js +17 -0
- package/dist/drivers/store.d.ts +3 -0
- package/dist/drivers/store.js +4 -0
- package/dist/i18n/catalog.d.ts +55 -0
- package/dist/i18n/messages/agents.d.ts +3 -0
- package/dist/i18n/messages/agents.js +3 -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.d.ts +6 -0
- package/dist/i18n/messages/interactive.js +6 -0
- package/dist/i18n/messages/panels.d.ts +3 -0
- package/dist/i18n/messages/panels.js +5 -2
- package/dist/i18n/messages/report-context.d.ts +98 -0
- package/dist/i18n/messages/report-context.js +99 -0
- package/dist/i18n/messages/report.d.ts +100 -0
- package/dist/i18n/messages/report.js +13 -2
- package/dist/i18n/messages/settings.d.ts +2 -0
- package/dist/i18n/messages/settings.js +2 -0
- package/dist/i18n/messages/subcommands.js +2 -2
- package/dist/modes/acp/acp-events.d.ts +17 -4
- package/dist/modes/acp/acp-events.js +32 -6
- package/dist/modes/commands-core.js +4 -0
- package/dist/modes/context-report.d.ts +37 -0
- package/dist/modes/context-report.js +188 -0
- package/dist/modes/interactive/agent-bar.d.ts +2 -0
- package/dist/modes/interactive/agent-bar.js +6 -0
- package/dist/modes/interactive/commands.d.ts +2 -1
- package/dist/modes/interactive/commands.js +10 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode.js +6 -1
- package/dist/modes/interactive/panels.d.ts +2 -1
- package/dist/modes/interactive/panels.js +13 -5
- package/dist/modes/interactive/program-status-ui.d.ts +72 -0
- package/dist/modes/interactive/program-status-ui.js +229 -0
- package/dist/modes/interactive/session-events.d.ts +4 -0
- package/dist/modes/interactive/session-events.js +24 -2
- package/dist/modes/interactive/status-bar.d.ts +21 -3
- package/dist/modes/interactive/status-bar.js +64 -25
- package/dist/modes/interactive/status-ctx.d.ts +55 -0
- package/dist/modes/interactive/status-ctx.js +125 -0
- package/dist/modes/interactive/status-line.d.ts +2 -1
- package/dist/modes/interactive/status-line.js +3 -2
- package/dist/modes/interactive/tasks-report.js +4 -0
- package/dist/modes/print/print-mode.js +12 -0
- package/dist/modes/session-report.d.ts +10 -2
- package/dist/modes/session-report.js +26 -2
- package/dist/sdk.d.ts +1 -1
- package/dist/tools/types.d.ts +6 -0
- package/dist/tui/components/meter.d.ts +2 -0
- package/dist/tui/components/meter.js +1 -1
- package/dist/tui/program-status.d.ts +90 -0
- package/dist/tui/program-status.js +259 -0
- package/docs/acp.md +4 -4
- package/docs/agents.md +1 -1
- package/docs/en/acp.md +4 -4
- package/docs/en/providers.md +5 -3
- package/docs/en/rpc.md +3 -0
- package/docs/en/sessions.md +23 -0
- package/docs/en/tui.md +79 -22
- package/docs/providers.md +8 -5
- package/docs/rpc.md +1 -0
- package/docs/session-format.md +3 -3
- package/docs/sessions.md +22 -0
- package/docs/tui-design.md +2 -2
- package/docs/tui.md +79 -22
- package/package.json +1 -1
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 终端程序状态协议 OSC 7501(Program Status Protocol rev 0.3,2026-10-07):
|
|
3
|
+
* https://www.superlogical.com/rex/docs/build/program-status
|
|
4
|
+
*
|
|
5
|
+
* - 编码(纯函数):`ESC ] 7501 ; key=value:… ESC \`。state 必填;id 每段 `[A-Za-z0-9_.+-]{1,32}`、
|
|
6
|
+
* ≤ 8 层、≤ 128 字节;kind 只随 blocked、progress(0–100 整数)只随 working / blocked;app 同 id 段;
|
|
7
|
+
* title / msg 去控制字符、压成一行后按 UTF-8 字节截断(192 / 2048,不切坏字符),再 base64。
|
|
8
|
+
* 字段不合法就丢掉该字段(state / id 不合法整条不发),不发终端会整条丢弃的坏报告;整条 ≤ 4096 字节。
|
|
9
|
+
* - 检测:发 `OSC 7501 ; ?` 紧跟 `CSI c`(DA1);先收到 `?` 回复 = 支持,DA1 回复先到或超时 = 不支持。
|
|
10
|
+
* 两种回复都在输入解析里吞掉(`handleInput`),不会当成按键进输入框;迟到的回复同样吞掉。
|
|
11
|
+
* - tmux:缺省丢弃未知 OSC,`on` 时用 DCS passthrough(`ESC P tmux; … ESC \`,内容里 ESC 翻倍)包裹,
|
|
12
|
+
* 需要 `set -g allow-passthrough on`;tmux 不把外层终端对透传查询的回复转给窗格,所以 tmux 里 `auto`
|
|
13
|
+
* 不检测、等同 `off`。
|
|
14
|
+
* - 发射器:每个 id 记最后一次写出的报告,同值不重写;同一 state 下只换 msg / title 的更新按节流合并。
|
|
15
|
+
*/
|
|
16
|
+
export const PROGRAM_STATUS_QUERY = "\x1b]7501;?\x1b\\";
|
|
17
|
+
export const DA1_QUERY = "\x1b[c";
|
|
18
|
+
export const MAX_SEQUENCE_BYTES = 4096;
|
|
19
|
+
export const MAX_TITLE_BYTES = 192;
|
|
20
|
+
export const MAX_MSG_BYTES = 2048;
|
|
21
|
+
const MAX_ID_BYTES = 128;
|
|
22
|
+
const MAX_ID_DEPTH = 8;
|
|
23
|
+
const SEGMENT = /^[A-Za-z0-9_.+-]{1,32}$/;
|
|
24
|
+
const STATES = ["idle", "working", "done", "blocked", "error", "clear"];
|
|
25
|
+
const KINDS = ["permission", "question", "auth"];
|
|
26
|
+
const ELLIPSIS = "…";
|
|
27
|
+
/** 控制字符(C0、DEL、C1)换成空格,空白压成一个,去首尾。 */
|
|
28
|
+
export function sanitizeLine(text) {
|
|
29
|
+
return text
|
|
30
|
+
.replace(/[\x00-\x1f\x7f-\x9f]/g, " ")
|
|
31
|
+
.replace(/\s+/g, " ")
|
|
32
|
+
.trim();
|
|
33
|
+
}
|
|
34
|
+
/** 按 UTF-8 字节截断到 maxBytes(按码点切,不出半个字符);截断时末尾带 `…`。 */
|
|
35
|
+
export function truncateUtf8(text, maxBytes) {
|
|
36
|
+
if (Buffer.byteLength(text, "utf8") <= maxBytes)
|
|
37
|
+
return text;
|
|
38
|
+
const budget = maxBytes - Buffer.byteLength(ELLIPSIS, "utf8");
|
|
39
|
+
let used = 0;
|
|
40
|
+
let out = "";
|
|
41
|
+
for (const ch of text) {
|
|
42
|
+
const n = Buffer.byteLength(ch, "utf8");
|
|
43
|
+
if (used + n > budget)
|
|
44
|
+
break;
|
|
45
|
+
used += n;
|
|
46
|
+
out += ch;
|
|
47
|
+
}
|
|
48
|
+
return out + ELLIPSIS;
|
|
49
|
+
}
|
|
50
|
+
/** id 是否合法(空串不合法;根记录不带 id)。 */
|
|
51
|
+
export function validId(id) {
|
|
52
|
+
if (Buffer.byteLength(id, "utf8") > MAX_ID_BYTES)
|
|
53
|
+
return false;
|
|
54
|
+
const segments = id.split("/");
|
|
55
|
+
return segments.length <= MAX_ID_DEPTH && segments.every((s) => SEGMENT.test(s));
|
|
56
|
+
}
|
|
57
|
+
/** 任意字符串 → 合法的单段 id(非法字符换 `_`,截到 n 个字符);空串给 `x`。 */
|
|
58
|
+
export function idSegment(raw, n = 12) {
|
|
59
|
+
const s = raw.replace(/[^A-Za-z0-9_.+-]/g, "_").slice(0, n);
|
|
60
|
+
return s === "" ? "x" : s;
|
|
61
|
+
}
|
|
62
|
+
function encodeText(text, maxBytes) {
|
|
63
|
+
if (text === undefined)
|
|
64
|
+
return undefined;
|
|
65
|
+
const line = sanitizeLine(text);
|
|
66
|
+
if (line === "")
|
|
67
|
+
return undefined;
|
|
68
|
+
return Buffer.from(truncateUtf8(line, maxBytes), "utf8").toString("base64");
|
|
69
|
+
}
|
|
70
|
+
/** 报告 → pairs(不含 OSC 包裹);state / id 不合法返回 undefined。 */
|
|
71
|
+
export function encodePairs(report) {
|
|
72
|
+
if (!STATES.includes(report.state))
|
|
73
|
+
return undefined;
|
|
74
|
+
if (report.id !== undefined && !validId(report.id))
|
|
75
|
+
return undefined;
|
|
76
|
+
const pairs = [`state=${report.state}`];
|
|
77
|
+
if (report.id !== undefined)
|
|
78
|
+
pairs.push(`id=${report.id}`);
|
|
79
|
+
if (report.state === "blocked" && report.kind !== undefined && KINDS.includes(report.kind))
|
|
80
|
+
pairs.push(`kind=${report.kind}`);
|
|
81
|
+
const p = report.progress;
|
|
82
|
+
if ((report.state === "working" || report.state === "blocked") &&
|
|
83
|
+
p !== undefined &&
|
|
84
|
+
Number.isInteger(p) &&
|
|
85
|
+
p >= 0 &&
|
|
86
|
+
p <= 100)
|
|
87
|
+
pairs.push(`progress=${p}`);
|
|
88
|
+
if (report.app !== undefined && SEGMENT.test(report.app))
|
|
89
|
+
pairs.push(`app=${report.app}`);
|
|
90
|
+
if (report.state !== "clear") {
|
|
91
|
+
const title = encodeText(report.title, MAX_TITLE_BYTES);
|
|
92
|
+
if (title !== undefined)
|
|
93
|
+
pairs.push(`title=${title}`);
|
|
94
|
+
const msg = encodeText(report.msg, MAX_MSG_BYTES);
|
|
95
|
+
if (msg !== undefined)
|
|
96
|
+
pairs.push(`msg=${msg}`);
|
|
97
|
+
}
|
|
98
|
+
// 各字段已有上限,整条最多约 3.2 KB;仍超限就依次丢 msg、title
|
|
99
|
+
while (osc(pairs.join(":")).length > MAX_SEQUENCE_BYTES) {
|
|
100
|
+
const last = pairs.at(-1);
|
|
101
|
+
if (!last.startsWith("msg=") && !last.startsWith("title="))
|
|
102
|
+
return undefined;
|
|
103
|
+
pairs.pop();
|
|
104
|
+
}
|
|
105
|
+
return pairs.join(":");
|
|
106
|
+
}
|
|
107
|
+
function osc(body) {
|
|
108
|
+
return `\x1b]7501;${body}\x1b\\`;
|
|
109
|
+
}
|
|
110
|
+
/** 完整的 OSC 7501 序列;报告不合法返回 undefined(不发坏报告)。 */
|
|
111
|
+
export function encodeProgramStatus(report) {
|
|
112
|
+
const pairs = encodePairs(report);
|
|
113
|
+
return pairs === undefined ? undefined : osc(pairs);
|
|
114
|
+
}
|
|
115
|
+
/** tmux DCS passthrough:`ESC P tmux; <内容,ESC 翻倍> ESC \`。 */
|
|
116
|
+
export function tmuxPassthrough(sequence) {
|
|
117
|
+
return `\x1bPtmux;${sequence.replaceAll("\x1b", "\x1b\x1b")}\x1b\\`;
|
|
118
|
+
}
|
|
119
|
+
/** 终端对 `OSC 7501 ; ?` 的回复(`?` 之后的内容忽略)。 */
|
|
120
|
+
export function isProgramStatusReply(sequence) {
|
|
121
|
+
return /^\x1b\]7501;\?/.test(sequence);
|
|
122
|
+
}
|
|
123
|
+
/** DA1 回复 `CSI ? … c`。 */
|
|
124
|
+
export function isDa1Reply(sequence) {
|
|
125
|
+
return /^\x1b\[\?[\d;]*c$/.test(sequence);
|
|
126
|
+
}
|
|
127
|
+
/** 发射器:检测、同值去重、节流;只在 `supported` 后写。 */
|
|
128
|
+
export class ProgramStatusEmitter {
|
|
129
|
+
options;
|
|
130
|
+
support = "unknown";
|
|
131
|
+
tmux;
|
|
132
|
+
desired = new Map();
|
|
133
|
+
sent = new Map();
|
|
134
|
+
timers = new Map();
|
|
135
|
+
probeTimer;
|
|
136
|
+
now;
|
|
137
|
+
constructor(options) {
|
|
138
|
+
this.options = options;
|
|
139
|
+
this.tmux = Boolean(options.env?.["TMUX"]);
|
|
140
|
+
this.now = options.now ?? Date.now;
|
|
141
|
+
}
|
|
142
|
+
/** 当前是否会写出报告。 */
|
|
143
|
+
get supported() {
|
|
144
|
+
return this.support === "yes";
|
|
145
|
+
}
|
|
146
|
+
/** 按模式开始:on 直接启用,auto 发检测查询(tmux 里不检测,等同 off),off 什么都不做。 */
|
|
147
|
+
start() {
|
|
148
|
+
if (this.support !== "unknown")
|
|
149
|
+
return;
|
|
150
|
+
const mode = this.options.mode;
|
|
151
|
+
if (mode === "off" || (mode === "auto" && this.tmux)) {
|
|
152
|
+
this.support = "no";
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (mode === "on")
|
|
156
|
+
return this.enable();
|
|
157
|
+
this.support = "probing";
|
|
158
|
+
this.options.write(PROGRAM_STATUS_QUERY + DA1_QUERY);
|
|
159
|
+
this.probeTimer = setTimeout(() => this.settleProbe(false), this.options.timeoutMs ?? 300);
|
|
160
|
+
this.probeTimer.unref?.();
|
|
161
|
+
}
|
|
162
|
+
/** 输入解析里调用:吞掉检测回复(返回 true),其余返回 false。 */
|
|
163
|
+
handleInput(sequence) {
|
|
164
|
+
if (isProgramStatusReply(sequence)) {
|
|
165
|
+
if (this.support === "probing")
|
|
166
|
+
this.settleProbe(true);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
if (isDa1Reply(sequence)) {
|
|
170
|
+
if (this.support === "probing")
|
|
171
|
+
this.settleProbe(false);
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
/** 设定某个 id(缺省根记录)的状态;`clear` 同时忘掉该 id 及其下层的记录。 */
|
|
177
|
+
report(report) {
|
|
178
|
+
const key = report.id ?? "";
|
|
179
|
+
const full = this.options.app !== undefined ? { ...report, app: this.options.app } : report;
|
|
180
|
+
if (report.state === "clear") {
|
|
181
|
+
for (const id of [...this.desired.keys()])
|
|
182
|
+
if (key === "" || id === key || id.startsWith(`${key}/`))
|
|
183
|
+
this.forget(id);
|
|
184
|
+
}
|
|
185
|
+
this.desired.set(key, full);
|
|
186
|
+
if (this.support === "yes")
|
|
187
|
+
this.flush(key);
|
|
188
|
+
if (report.state === "clear")
|
|
189
|
+
this.desired.delete(key);
|
|
190
|
+
}
|
|
191
|
+
/** 停止计时器(不再写任何东西)。 */
|
|
192
|
+
dispose() {
|
|
193
|
+
if (this.probeTimer !== undefined)
|
|
194
|
+
clearTimeout(this.probeTimer);
|
|
195
|
+
this.probeTimer = undefined;
|
|
196
|
+
for (const timer of this.timers.values())
|
|
197
|
+
clearTimeout(timer);
|
|
198
|
+
this.timers.clear();
|
|
199
|
+
if (this.support === "probing")
|
|
200
|
+
this.support = "no";
|
|
201
|
+
}
|
|
202
|
+
settleProbe(ok) {
|
|
203
|
+
if (this.probeTimer !== undefined)
|
|
204
|
+
clearTimeout(this.probeTimer);
|
|
205
|
+
this.probeTimer = undefined;
|
|
206
|
+
if (ok)
|
|
207
|
+
this.enable();
|
|
208
|
+
else
|
|
209
|
+
this.support = "no";
|
|
210
|
+
}
|
|
211
|
+
enable() {
|
|
212
|
+
this.support = "yes";
|
|
213
|
+
for (const key of this.desired.keys())
|
|
214
|
+
this.flush(key);
|
|
215
|
+
}
|
|
216
|
+
forget(id) {
|
|
217
|
+
this.desired.delete(id);
|
|
218
|
+
this.sent.delete(id);
|
|
219
|
+
const timer = this.timers.get(id);
|
|
220
|
+
if (timer !== undefined)
|
|
221
|
+
clearTimeout(timer);
|
|
222
|
+
this.timers.delete(id);
|
|
223
|
+
}
|
|
224
|
+
flush(key) {
|
|
225
|
+
const report = this.desired.get(key);
|
|
226
|
+
if (report === undefined)
|
|
227
|
+
return;
|
|
228
|
+
const body = encodePairs(report);
|
|
229
|
+
if (body === undefined)
|
|
230
|
+
return;
|
|
231
|
+
const last = this.sent.get(key);
|
|
232
|
+
if (last?.body === body)
|
|
233
|
+
return;
|
|
234
|
+
const throttle = this.options.throttleMs ?? 500;
|
|
235
|
+
const head = `${report.state}:${report.kind ?? ""}`;
|
|
236
|
+
const wait = last !== undefined && last.head === head ? last.at + throttle - this.now() : 0;
|
|
237
|
+
if (wait > 0 && report.state !== "clear") {
|
|
238
|
+
if (!this.timers.has(key)) {
|
|
239
|
+
const timer = setTimeout(() => {
|
|
240
|
+
this.timers.delete(key);
|
|
241
|
+
this.flush(key);
|
|
242
|
+
}, wait);
|
|
243
|
+
timer.unref?.();
|
|
244
|
+
this.timers.set(key, timer);
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const timer = this.timers.get(key);
|
|
249
|
+
if (timer !== undefined)
|
|
250
|
+
clearTimeout(timer);
|
|
251
|
+
this.timers.delete(key);
|
|
252
|
+
const sequence = osc(body);
|
|
253
|
+
this.options.write(this.tmux ? tmuxPassthrough(sequence) : sequence);
|
|
254
|
+
if (report.state === "clear")
|
|
255
|
+
this.sent.delete(key);
|
|
256
|
+
else
|
|
257
|
+
this.sent.set(key, { body, head, at: this.now() });
|
|
258
|
+
}
|
|
259
|
+
}
|
package/docs/acp.md
CHANGED
|
@@ -60,13 +60,13 @@ ama --mode acp # 与 -p 互斥;其余参数(--model、-
|
|
|
60
60
|
| 工具开始 | `tool_call_update`(`in_progress`) |
|
|
61
61
|
| 工具结束(含内层) | `tool_call_update`(`completed` / `failed`);`content` 为 `[diff?, 文本]`:edit / write 带 `diff`(`path`、`oldText`、`newText`,新文件 `oldText: null`),文本只带前 4 KB;`locations[].line` 是首个改动行;不发 `rawOutput` |
|
|
62
62
|
| `todo` 更新 | `plan` |
|
|
63
|
-
|
|
|
63
|
+
| 每条助手消息结束、每轮结束 | `usage_update`(上下文已用、窗口、会话累计美元);与上次同值不重发 |
|
|
64
64
|
| 权限模式变化 | `current_mode_update` |
|
|
65
|
-
| 模型 / 思考级别变化 | `config_option_update
|
|
66
|
-
| 会话开出(new / load / resume)之后 | `available_commands_update` 与 `
|
|
65
|
+
| 模型 / 思考级别变化 | `config_option_update`(全部配置项);换模型时另发一条 `usage_update`(窗口可能变了) |
|
|
66
|
+
| 会话开出(new / load / resume)之后 | `available_commands_update`、`config_option_update` 与 `usage_update`(load 在回放之后),客户端不用等第一轮结束 |
|
|
67
67
|
| 回合结束之后 | `session_info_update`(`updatedAt`;标题与上次不同才带 `title`) |
|
|
68
68
|
|
|
69
|
-
diff 的改前 / 改后全文只随实时事件走,不写进会话文件:任一侧超过 256 KiB 不带 diff,`session/load` 回放的工具结果只有前 4 KB 文本。模式列表的 `name` 是显示名(如 `Manual`、`Accept edits`),`description` 随界面语言。`session/prompt` 的结果带本回合 token 用量 `usage`(`inputTokens`、`outputTokens`、`cachedReadTokens`、`cachedWriteTokens`、`totalTokens`);该字段在 schema 1.24.1 里仍是 UNSTABLE(只在不稳定 schema 中),客户端可以忽略,以 `usage_update`
|
|
69
|
+
diff 的改前 / 改后全文只随实时事件走,不写进会话文件:任一侧超过 256 KiB 不带 diff,`session/load` 回放的工具结果只有前 4 KB 文本。模式列表的 `name` 是显示名(如 `Manual`、`Accept edits`),`description` 随界面语言。`session/prompt` 的结果带本回合 token 用量 `usage`(`inputTokens`、`outputTokens`、`cachedReadTokens`、`cachedWriteTokens`、`totalTokens`);该字段在 schema 1.24.1 里仍是 UNSTABLE(只在不稳定 schema 中),客户端可以忽略,以 `usage_update` 为准。模型窗口未知时(如没有 `contextWindow` 的自定义模型)不发 `usage_update`:schema 里 `size` 必填,ama 不编造窗口。
|
|
70
70
|
|
|
71
71
|
`toolCallId` 在会话内唯一:上游供应商给的 id 若在后续回合重复(个别兼容接口的兜底 id、测试用假模型),线上 id 加 `#2`、`#3`… 区分,之后的状态更新、审批与回放都按这个映射(客户端按 id 合并条目,不区分会把不同调用合成一条)。
|
|
72
72
|
|
package/docs/agents.md
CHANGED
|
@@ -256,7 +256,7 @@ ACP Agent 不给 `modes`、改用 category `mode` 的配置项表达模式时,
|
|
|
256
256
|
- **信任**:只在 ama 已信任的目录里起外部 Agent(`claude -p` 会跳过目录信任对话框并执行项目 hooks 与配置);未信任时 `task` 报错并提示 `ama trust`。
|
|
257
257
|
- **并发**:外部 Agent 总共 `agents.maxConcurrent`(缺省 3),每个 Agent 另有上限(`agents.<id>.maxConcurrent`,claude 缺省 2);超出排队,排队可被中断。与 ama 子会话的并发分开计。
|
|
258
258
|
- **预算**:`task` 的 `budgetUsd`(Claude 透传 `--max-budget-usd`,其余按用量累计、超限中断);`agents.sessionBudgetUsd` 是本会话外部 Agent 的美元总额,用尽后不再启动。Codex 订阅 token、Copilot premium request 按各自单位记,不换算美元;Claude 的 `total_cost_usd` 是它自己的估算。
|
|
259
|
-
- **记账**:每回合写 `custom{ama.agent-usage}`,会话建立写 `custom{ama.agent-session}`(外部 CLI 自己的会话 id,用于续聊);`/session` 与 `get_session_stats` 的 `external` 段按 Agent
|
|
259
|
+
- **记账**:每回合写 `custom{ama.agent-usage}`,会话建立写 `custom{ama.agent-session}`(外部 CLI 自己的会话 id,用于续聊);`/session` 与 `get_session_stats` 的 `external` 段按 Agent 汇总。外部 Agent 报告了上下文占用与窗口(ACP 的 `usage_update`、Codex app-server 的 `tokenUsage`)时,只记这两个数字:任务记录与 `ama.agent-usage` 带 `contextTokens / contextWindow`,`external.byAgent` 取最近一次,`/tasks` 与 Agent 栏的任务行显示 `ctx 34%`(低于 10% 保留一位小数);回合中途变化也会即时更新。ama 会话只存最终文本、工具摘要、用量与引用,外部 Agent 的原始事件只在内存里显示,不落盘。
|
|
260
260
|
- **看门狗**:单回合缺省 30 分钟;中断后 15 秒内没有回合结束就关 stdin 再结束进程树;空闲 10 分钟关进程,能续接的下次续聊时以 resume 重开。外部 Agent 进程登记在 `<数据目录>/drivers/pids.json`,ama 异常退出留下的孤儿在下次使用时清理(核对命令行,pid 被复用的不杀)。
|
|
261
261
|
|
|
262
262
|
### 嵌入宿主
|
package/docs/en/acp.md
CHANGED
|
@@ -60,13 +60,13 @@ One `ama --mode acp` process can keep several sessions open (Zed's threads share
|
|
|
60
60
|
| Tool starts | `tool_call_update` (`in_progress`) |
|
|
61
61
|
| Tool ends (inner calls included) | `tool_call_update` (`completed` / `failed`); `content` is `[diff?, text]`: edit / write carry a `diff` (`path`, `oldText`, `newText`; `oldText: null` for a new file), the text is limited to the first 4 KB; `locations[].line` is the first changed line; no `rawOutput` |
|
|
62
62
|
| `todo` update | `plan` |
|
|
63
|
-
| End of each turn
|
|
63
|
+
| End of each assistant message and each turn | `usage_update` (context used, window size, session cost in USD); not resent when unchanged |
|
|
64
64
|
| Permission mode change | `current_mode_update` |
|
|
65
|
-
| Model / thinking level change | `config_option_update` (all options)
|
|
66
|
-
| After a session opens (new / load / resume) | `available_commands_update` and `
|
|
65
|
+
| Model / thinking level change | `config_option_update` (all options); a model change also sends a `usage_update` (the window may differ) |
|
|
66
|
+
| After a session opens (new / load / resume) | `available_commands_update`, `config_option_update` and `usage_update` (for load, after the replay), so clients need not wait for the first turn |
|
|
67
67
|
| After a turn | `session_info_update` (`updatedAt`; `title` only when it differs from the last one) |
|
|
68
68
|
|
|
69
|
-
The full before / after text of a diff only travels with live events and is never written to the session file: above 256 KiB on either side there is no diff, and tool results replayed by `session/load` carry only their first 4 KB of text. The `name` of each mode is its display name (such as `Manual` or `Accept edits`); the `description` follows the UI language. The `session/prompt` result carries the turn's token usage `usage` (`inputTokens`, `outputTokens`, `cachedReadTokens`, `cachedWriteTokens`, `totalTokens`); in schema 1.24.1 this field is still UNSTABLE (only in the unstable schema), so clients may ignore it and rely on `usage_update`.
|
|
69
|
+
The full before / after text of a diff only travels with live events and is never written to the session file: above 256 KiB on either side there is no diff, and tool results replayed by `session/load` carry only their first 4 KB of text. The `name` of each mode is its display name (such as `Manual` or `Accept edits`); the `description` follows the UI language. The `session/prompt` result carries the turn's token usage `usage` (`inputTokens`, `outputTokens`, `cachedReadTokens`, `cachedWriteTokens`, `totalTokens`); in schema 1.24.1 this field is still UNSTABLE (only in the unstable schema), so clients may ignore it and rely on `usage_update`. When the model's context window is unknown (such as a custom model without `contextWindow`), no `usage_update` is sent: `size` is required by the schema and ama does not make up a window.
|
|
70
70
|
|
|
71
71
|
`toolCallId` is unique within a session: when the upstream provider reuses an id in a later turn (fallback ids of a few compatible APIs, the fake test model), the wire id gets `#2`, `#3`, … and later status updates, approvals and replay follow that mapping (clients merge entries by id, so without this different calls would collapse into one).
|
|
72
72
|
|
package/docs/en/providers.md
CHANGED
|
@@ -400,7 +400,7 @@ Inference order: conservative defaults ← inference table (provider id, then ba
|
|
|
400
400
|
| `requiresReasoningContentOnAssistantMessages` | Earlier assistant messages of reasoning models carry `reasoning_content` (DeepSeek) |
|
|
401
401
|
| `requiresToolResultName` | Tool result messages carry `name` (Mistral) |
|
|
402
402
|
| `requiresAssistantAfterToolResult` | Insert an assistant message when a user message directly follows a tool result |
|
|
403
|
-
| `supportsMidConvoSystemMessages` | Later
|
|
403
|
+
| `supportsMidConvoSystemMessages` | Later prompt patches go back as system messages in place; off (default): trailing context, see "Caching" |
|
|
404
404
|
| `cacheControlFormat` | `anthropic`: put `cache_control` on the system prompt, the last tool and the last user/tool message |
|
|
405
405
|
| `supportsStrictTools` | Send `strict: true` for strictly compatible tool schemas |
|
|
406
406
|
| `supportsStore` | Send `store: false` |
|
|
@@ -439,6 +439,8 @@ Most usage in long tasks is cache reads: once the prefix changes, every later re
|
|
|
439
439
|
|
|
440
440
|
Prefix stability is guaranteed by assembly: the system prompt sections have a fixed order without timestamps, tools are sorted by name, and mid-session changes are only appended at the end as system patches ([session-format.md](../session-format.md), Chinese).
|
|
441
441
|
|
|
442
|
+
Section patches after the conversation has started (AGENTS.md, Skills or SessionStart hook output changed on resume, host instructions refreshed, the memory section re-rendered after compaction) **do not rewrite the leading system prompt**: protocols without `supportsMidConvoSystemMessages` render them as a user message wrapped in `<system-reminder>` at the patch's position, so the previous request stays a byte-for-byte prefix. The only exception is a patch that removes tools: the tool list itself changed and the prefix is lost anyway, so all patches are folded back into the head as before. Measured on DeepSeek, a mid-conversation system message is accepted and the prefix cache holds, but the model still answers from the leading one, so DeepSeek keeps the switch off ([cache-midconvo-2026-10-09](../benchmarks/cache-midconvo-2026-10-09.md), Chinese). At the next compaction these patches are folded into the head together with the system checkpoint.
|
|
443
|
+
|
|
442
444
|
### Request fields
|
|
443
445
|
|
|
444
446
|
| Protocol | Field | Condition |
|
|
@@ -476,7 +478,7 @@ If any cache field appears in the raw usage (even 0) → `Usage.cacheReported =
|
|
|
476
478
|
|
|
477
479
|
### Model catalog `promptCache`
|
|
478
480
|
|
|
479
|
-
Only publicly documented values are written (seconds / tokens): all Anthropic models `short 300 / long 3600`, with `minTokens` 512–4096 by model; all OpenAI models `short 300 / long 86400 / minTokens 1024`; all Kimi models `short 300
|
|
481
|
+
Only publicly documented values are written (seconds / tokens): all Anthropic models `short 300 / long 3600`, with `minTokens` 512–4096 by model; all OpenAI models `short 300 / long 86400 / minTokens 1024`; all Kimi models `short 300`; all DeepSeek models `short 3600 / minTokens 2048` (the vendor only says unused cache is cleared after "a few hours to a few days", so the conservative 1 hour is used; cache reads were measured in 2048-token blocks). Zhipu, Qwen, Groq, xAI, Mistral, OpenRouter and Google promise no TTL, so it stays empty: no warming and no early pruning on a guessed lifetime; only miss attribution assumes 10 minutes for implicit caching. You can fill it in yourself in `models[]` / `modelOverrides[]`; DeepSeek models on relays do not inherit the official catalog values, fill them in the same way if needed.
|
|
480
482
|
|
|
481
483
|
### Session layer: fingerprints, misses and three states
|
|
482
484
|
|
|
@@ -541,7 +543,7 @@ ama models cache-probe <provider/id> [--tokens 2048] [--gap-ms 3000] [--json] [-
|
|
|
541
543
|
|
|
542
544
|
It sends a deterministic fixed prefix (about `--tokens` tokens) + `Reply with: ok`, `maxTokens: 16`, twice `--gap-ms` apart, and decides:
|
|
543
545
|
|
|
544
|
-
- `reported`: the second cacheRead ≥ 50% of the prefix; when the catalog has no `promptCache`, it
|
|
546
|
+
- `reported`: the second cacheRead ≥ 50% of the prefix; when the catalog has no `promptCache`, it explains that the lifetime is unknown (no warming, no early pruning) and to fill it in only with a lifetime the upstream documents, without suggesting a guessed value — a value that is too short fires warming and early pruning while the cache is still valid;
|
|
545
547
|
- `silent`: reads and writes were 0 both times; it suggests setting `compat.cacheReporting: "silent"`. When the response has cache fields that stay 0, it also hints at a possible write delay (kimi-k2.5 on the same relay read 0 twice 3 seconds apart, but the full prefix on the second request 8 seconds apart), so retry with a larger `--gap-ms`;
|
|
546
548
|
- `inconclusive`: a small read or only writes, most likely a cache granularity or TTL issue.
|
|
547
549
|
|
package/docs/en/rpc.md
CHANGED
|
@@ -285,6 +285,7 @@ The `data` of `get_session_stats` is `SessionStats`:
|
|
|
285
285
|
| `tokens` | `{ input, output, cacheRead, cacheWrite, total }`, including warming requests |
|
|
286
286
|
| `cost` | USD; absent when any message lacks a cost (the interface shows `$?`) |
|
|
287
287
|
| `contextTokens` / `contextWindow` / `contextPercent` | Current context estimate, window and usage (0–100); absent when the model has no window |
|
|
288
|
+
| `context` | Estimate source and auto-compaction thresholds (fields: sessions.md) |
|
|
288
289
|
| `cacheHitRate` | The legacy hit rate: cacheRead / (input + cacheRead + cacheWrite), every request in the denominator |
|
|
289
290
|
| `cache` | `SessionCacheStats` (example below), present only when the session-layer cache controller is wired |
|
|
290
291
|
|
|
@@ -334,6 +335,8 @@ The `data` of `get_session_stats` is `SessionStats`:
|
|
|
334
335
|
|
|
335
336
|
`cache_miss` gives `detail` only for `prefix_changed` (`system` / `tools`). The values and meanings of `reason` for `cache_warm{stopped}` are in [tui.md](tui.md) "Cache and context". The result object of `ama -p --output-format json` also has a `cache` field of the same shape.
|
|
336
337
|
|
|
338
|
+
It also carries `context: { tokens, window, percent }` (context used, window size and usage 0–100, defined like `contextTokens` / `contextWindow` / `contextPercent` in the table above); an unknown item is omitted (for a model without a window only `tokens` is present), and when all are unknown there is no `context`.
|
|
339
|
+
|
|
337
340
|
## Approvals
|
|
338
341
|
|
|
339
342
|
1. When a tool call needs confirmation, the server sends `permission_request`. `preview` (optional) is the pre-execution preview: `{ kind: "bash" | "write" | "edit" | "other", lines: string[], severity: "info" | "warn" | "danger", affected?: { path, exists, bytes?, files? }[] }`; `lines` are already laid out without colors and can be shown as is. The preview is read-only and bounded, and absent when it cannot be computed.
|
package/docs/en/sessions.md
CHANGED
|
@@ -61,6 +61,29 @@ those notification turns are written to the same session file.
|
|
|
61
61
|
- A per-file summary is cached in `<data dir>/stats-index.json`, invalidated by file mtime and size; a time-zone change invalidates the whole index; a full scan also drops files that no longer exist. `--no-cache` neither reads nor writes it.
|
|
62
62
|
- Measured (local machine, `src/session/stats-perf.test.ts`): 1000 sessions, 67 MB (12 turns, 24 tool calls and 2 KB tool results each), about 160 ms cold and about 15 ms with the index.
|
|
63
63
|
|
|
64
|
+
## Context usage of the current session (`getStats()` / `get_session_stats`)
|
|
65
|
+
|
|
66
|
+
`ama stats` reports historical billed usage; how much context the running session has left is in the context fields of
|
|
67
|
+
`SessionStats` (SDK `session.getStats()`, passed through unchanged by RPC `get_session_stats`; the status bar and `/session` read
|
|
68
|
+
the same fields):
|
|
69
|
+
|
|
70
|
+
| Field | Meaning |
|
|
71
|
+
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
72
|
+
| `contextTokens` | Estimated tokens of context the next request will carry (see `context.source` for how) |
|
|
73
|
+
| `contextWindow` | The current model's window; **absent** when the model has no window information (fix it with `ama models discover` or by setting `contextWindow` for the model in config) |
|
|
74
|
+
| `contextPercent` | `contextTokens / contextWindow`, 0–100 with one decimal, capped at 100; **absent** without a window (the UI shows `ctx ?`) |
|
|
75
|
+
| `context.source` | `usage`: usage of the last valid assistant message + an estimate of the messages after it; `estimate`: no trustworthy usage, the whole context is estimated; `prefix`: the baseline before the first request |
|
|
76
|
+
| `context.usageTokens` | The part taken from usage; 0 for `estimate` / `prefix` |
|
|
77
|
+
| `context.trailingTokens` | The estimated part (messages after the usage, or the whole estimate / baseline) |
|
|
78
|
+
| `context.autoCompactAt` | Threshold of automatic summarization (tier two) = window − `compaction.reserveTokens`; **absent** when auto-compaction is off, tripped after repeated failures, or there is no window |
|
|
79
|
+
| `context.pruneAt` | Threshold of tool-result pruning (tier one) = 0.7 × (window − `reserveTokens`); absent under the same conditions |
|
|
80
|
+
|
|
81
|
+
- **Estimation**: CJK ideographs / kana / hangul count 1 token each, other characters / 4, images 1,600 each; usage uses `totalTokens` (or input + output + cache reads / writes, output included).
|
|
82
|
+
- **Before the first request** (`prefix`): the system prompt and tool table are written to the session only at the first request, so until then the estimate is taken from what is about to be sent (section texts + tool declaration JSON); a new session is not at 0 the moment it opens. This is for stats only: nothing is written to the session and the request body does not change.
|
|
83
|
+
- **After compaction / context edits** (`estimate`): the last usage no longer describes the current context, so the whole context is estimated until the next request brings new usage; the UI puts `≈` in front of the number.
|
|
84
|
+
- **An absent field means "unknown"**, not 0: `contextWindow`, `contextPercent`, `context.autoCompactAt` and `context.pruneAt` are left out of the JSON when absent. Older versions or custom session implementations may not have `context`; reading `contextTokens` is enough there.
|
|
85
|
+
- `tokens` (`{ input, output, cacheRead, cacheWrite, total }`) is the **session total** of billed usage, including requests that never enter the context (compaction summaries, cache warming); do not read it as context usage.
|
|
86
|
+
|
|
64
87
|
## Search: `ama sessions search`
|
|
65
88
|
|
|
66
89
|
```
|
package/docs/en/tui.md
CHANGED
|
@@ -39,8 +39,8 @@ The visual spec (colors, glyphs, screen-by-screen mockups) is in [tui-design.md]
|
|
|
39
39
|
────────────────────────────────────────────────
|
|
40
40
|
› Type a message, / commands, @ files, Shift+Enter newline ← input box (placeholder)
|
|
41
41
|
────────────────────────────────────────────────
|
|
42
|
-
codemode on tps: 100 tok/s • 546 tok / 5.5s (avg 100 · ttft 1.4s) ·
|
|
43
|
-
Accept edits | shift+tab to cycle claude-opus-5-5 medium | Ctx 3.0% | proj ⎇ main 5ae9e54 ↑2 (+12,-3) | $0.26 | 2h24m
|
|
42
|
+
codemode on tps: 100 tok/s • 546 tok / 5.5s (avg 100 · ttft 1.4s) · Σ↑12k ↓1.2k R9.6k · cache 80% ♨ · [-] ← rate line (full)
|
|
43
|
+
Accept edits | shift+tab to cycle claude-opus-5-5 medium | Ctx 3.0% 8.2k/272k auto | proj ⎇ main 5ae9e54 ↑2 (+12,-3) | $0.26 | 2h24m
|
|
44
44
|
Session: 10.0% | Reset: 2h 18m | Weekly: 31.0% | Weekly Reset: 6d 5h ← subscription quota line (full, ChatGPT subscription model)
|
|
45
45
|
```
|
|
46
46
|
|
|
@@ -50,16 +50,17 @@ Accept edits | shift+tab to cycle claude-opus-5-5 medium | Ctx 3.0% | proj
|
|
|
50
50
|
- **Notices**: `✗` errors, `↻ retry n/m`, `!` warnings (cache misses, remaining context), `⛔` hook blocks, host notifications, explanations of denied or timed-out approvals; compaction / branch summaries are left-bar cards (`▎ context compacted 128k → 24k tokens`).
|
|
51
51
|
- **Running**: `⠋ verb · elapsed · …`, with the verb taken from the deepest current state: waiting for confirmation (approval open), `running bash` / `running 3 tools`, `retry 2/3 · in 2s`, compacting context, replying `· ↓≈1.2k` (estimated tokens of this output), thinking. While a foreground sub-agent task blocks the turn, `Ctrl+B to background` is appended; while the agent bar has tasks, `↓ Agent bar` is appended (`↓ handle approval` instead when an approval is docked): `⠏ running task · 4s · Esc to interrupt · Ctrl+B to background · ↓ Agent bar`; items are dropped whole from the end when the line does not fit. With text in the input box, `Enter queue · Ctrl+X interrupt & send` comes first (`Enter interrupt & send · Ctrl+X queue` with `ui.enterWhileRunning: "interrupt"`); with queued steers the queue's last line reads `Alt+↑ take back · Ctrl+X send now · Esc refill and interrupt`.
|
|
52
52
|
- **Status bar**: the mode is always on the far left; `full` splits into two sides — the left column holds state and switches (permission mode, the `shift+tab` hint, codemode, sandbox, preset, fallback), the right column holds metrics and the model (tps, usage, model, Ctx, git, cost, duration, quota), right-aligned, and a line with nothing on the left sits flush right; the status bar is the last line except for the subscription quota line in the `full` layout (`compact` always keeps it last). In `compact` the separator is always `·` (embedding hosts parse it), in `full` it is `|`. The layout follows `ui.statusLine`: `full` (two lines) by default in a standalone terminal, `compact` (one line, same layout as before) by default in an embedding host with a profile; `Ctrl+G` or `/statusline [full|compact]` switches at runtime for this session only. With `full` the input box is the 4th line from the bottom (`compact` keeps it 3rd from the bottom).
|
|
53
|
-
- **`full` top line (rate line)**: the left side holds switches: `codemode on|only` (with `net!` when the network is not isolated) · `sandbox` · `preset <name>` (when not default) · `→ <fallback model>` (during a fallback, yellow) · queue count · host status; it is empty when there are none. The right side is `tps: <rate> tok/s • <output tokens> tok / <elapsed> (avg <session average> · ttft <time to first token>) ·
|
|
54
|
-
- **`full` bottom line**: on the left `permission mode | shift+tab to switch`, on the right `model thinking-level | Ctx 3.0% | <dir name> ⎇ <branch> <short commit> ↑N ↓N (+a,-d) | $cost | session duration` (Ctx with one decimal, no meter even when wide); when narrow, these drop in order: the switch hint, thinking level, line changes, directory name, branch and commit, duration, cost, context, model.
|
|
53
|
+
- **`full` top line (rate line)**: the left side holds switches: `codemode on|only` (with `net!` when the network is not isolated) · `sandbox` · `preset <name>` (when not default) · `→ <fallback model>` (during a fallback, yellow) · queue count · host status; it is empty when there are none. The right side is `tps: <rate> tok/s • <output tokens> tok / <elapsed> (avg <session average> · ttft <time to first token>) · Σ↑<input> ↓<output> R<cache read> W<cache write> · cache · re-billing · [-]`. While streaming the rate is the instantaneous value over the last 2 s (`tps:` in the accent color); afterwards it is the request's average; whole replies generated in under 0.25 s get no rate and show `—`; elapsed time starts at the first token; `Σ` marks the **session total** of billed tokens (not context usage), `↑` input includes cache reads and writes, which are also listed as `R` / `W` (hidden when 0; `Σ` is `sum ` in ASCII); in ASCII `•` becomes `*` and `→` becomes `->`; `[-]` at the end hints that it folds. Only chat requests count (compaction summaries, warming and the classifier do not). When narrow, the right-side metrics drop first (output / elapsed, tokens, cache, re-billing, avg, ttft), then the left-side switches (host status, queue count, preset, fallback, sandbox, codemode); `tps` and `[-]` never drop.
|
|
54
|
+
- **`full` bottom line**: on the left `permission mode | shift+tab to switch`, on the right `model thinking-level | Ctx 3.0% 8.2k/272k auto | <dir name> ⎇ <branch> <short commit> ↑N ↓N (+a,-d) | $cost | session duration` (Ctx with one decimal, no meter even when wide). The Ctx part is the current context: the percentage, used / window, and `auto` while auto-compaction is on (hidden when it is off, tripped after repeated failures, or the window is unknown); the numbers get a leading `≈` when they do not come from usage reported by the model (the baseline estimated from the system prompt and tool declarations before the first request, or the full estimate after a compaction until the next request); without a window it reads `Ctx ? 8.2k`. When narrow, these drop in order: `auto`, the switch hint, thinking level, `/window`, line changes, directory name, branch and commit, used tokens, duration, cost, the context percentage, model.
|
|
55
55
|
- **Subscription quota line** (third `full` line, below the status bar): when the current model uses a ChatGPT subscription (the `chatgpt` provider) it shows `Session: <used %> | Reset: <time to reset> | Weekly: <used %> | Weekly Reset: <time to reset>` (Chinese labels in the Chinese interface), from the latest `quota_update` (the codex flavor's `x-codex-primary/secondary-*` response headers and `codex.rate_limits` events; siwc only has it after a 429). Reset times are relative (`2h 18m`, `6d 5h`) and refresh once a minute. Before the first request the codex flavor shows "Quota: shown after the first request" (the first request brings the quota back, so holding the line avoids the row count jumping); siwc without data takes no line (it only gets a quota when over the limit, so a placeholder would stay forever); non-subscription models show nothing. Below 80 columns it compresses to `5h 10% ↻2h18m · wk 31% ↻6d5h`, dropping the reset times first when narrower. The quota line is right-aligned. Labels follow the window length rather than the primary / secondary slot: 300 minutes is `Session`, 10080 minutes is `Weekly`, any other length shows the length itself (`1d: `); without a length, primary counts as Session and secondary as Weekly (or the other one if that name is taken); windows are ordered shortest first. A window with 0 % used, no length and no reset time is the server saying "no such window" and is not shown (some plans get a single weekly window from codex, sent as primary). `Ctrl+G` / `/statusline compact` folds the quota line together with the rate line (`compact` stays a single line; hosts anchor on "last line = status bar").
|
|
56
|
-
- **Colors** (`full`): labels, units, separators and parentheses dim gray; the rate number purple, output / elapsed / avg blue, ttft purple; model and thinking level blue; Ctx
|
|
56
|
+
- **Colors** (`full`): labels, units, separators and parentheses dim gray; the rate number purple, output / elapsed / avg blue, ttft purple; model and thinking level blue; Ctx by threshold green / yellow / red (yellow once past the tier-one pruning threshold — 0.7 × (window − reserve), a little under 70%; red at ≥ 90%; ≥ 70% yellow when there is no threshold), quota percentages yellow at ≥ 70% and red at ≥ 90%; directory and branch green, short commit dim, `↑N` ahead orange, `↓N` behind red, `(+a,-d)` green / red; cost yellow; duration and reset times purple. All come from theme semantic colors with dark / light and 16-color mappings; `NO_COLOR` and ASCII drop the colors and keep the structure. `compact` colors are unchanged.
|
|
57
57
|
- **`compact`**: one line; on the right model · thinking level · `↑ ↓` · cache · cost · re-billing · context usage · dir ⎇ branch commit +a −b · session duration · queue count · codemode · preset · host status · a short subscription quota item (`5h 10% wk 31%`, only with quota data; no `·` inside the item); when narrow, these drop in order: the quota item, the switch hint, host status, preset, re-billing, cost, cache, tokens, thinking level, queue count, codemode, line changes, directory name, branch and commit, duration, context, model.
|
|
58
58
|
- **git**: branch and short commit are read directly from `.git/HEAD` (worktrees understood; detached shows only the short commit; outside git the whole part is omitted, leaving only the directory name). `+a −b` is the working tree (staged included) line diff against HEAD, computed in the background with `git diff --numstat HEAD` after a turn ends, a writing tool finishes, a rewind or `/tree`, at most once every 10 seconds; if it takes longer than 2 seconds or fails, line changes are hidden for the rest of the session; `AMA_STATUS_GIT=0` turns it off. In `full`, `↑N` / `↓N` count the commits the current branch is ahead of / behind its upstream: when the branch has an upstream in the git config (`branch.<name>.merge`), the same throttled cycle then runs `git rev-list --left-right --count @{upstream}...HEAD` (same 2-second timeout; after a timeout it stops for the session); zero, no upstream or detached shows nothing; ASCII uses `^N` / `vN`.
|
|
59
59
|
- **Cost** includes sub-tasks, warming, the classifier and external agent usage priced in USD (other units only in `/session`); **duration** counts from when this process opened the current session (`Ns` / `Nm` / `NhMm`).
|
|
60
60
|
- When bash commands run in the OS sandbox (`sandbox.bash: "auto"` and available on this machine, see [sandbox.md](../sandbox.md), Chinese), the usage items gain a sandbox marker, dropped first together with codemode when space runs out.
|
|
61
61
|
- During a model fallback (`fallbackModel`: when the main model is overloaded or retries are exhausted, one retry with the fallback model) the `compact` model item shows `main model → fallback model` (the fallback in yellow), while `full` keeps only the main model there and shows `→ fallback model` on the left of the rate line; it disappears once the fallback model replies and the main model is restored, and the message area gets an explanatory line.
|
|
62
|
-
-
|
|
62
|
+
- **Context refresh**: it updates right after a message is submitted (the new message counted as an estimate); while streaming, once the reply carries usage (some protocols report input tokens at the start) it switches to the measured value of the in-flight request, at most twice a second; after the reply ends, a compaction or a model change it goes back to the session stats.
|
|
63
|
+
- Model names abbreviate with width (provider dropped below 100 columns, channel below 60, version suffix below 48); `compact` at ≥ 110 columns shows context as a meter `ctx ▮▮▮▯▯▯▯▯▯▯ 34%`, keeping one decimal below 1% (`ctx ≈0.4%` rather than 0%), with the same `≈` for estimates; changing numbers reserve their widest shape, so items never flicker in and out as values change. In ASCII mode `⎇` → `git`, `−` → `-`, `♨` → `~`, `↻` → `@`.
|
|
63
64
|
- **Exit**: a session summary line and the resume command are appended at the end of the message area and stay in the terminal scrollback:
|
|
64
65
|
|
|
65
66
|
```
|
|
@@ -78,7 +79,7 @@ Status bar items:
|
|
|
78
79
|
| `cache` "not reported" | The endpoint does not report cache usage (`silent`: reads and writes were 0 for 3 comparable requests in a row, or `compat.cacheReporting: "silent"`); such requests stay out of the hit-rate denominator |
|
|
79
80
|
| `♨` | Warming timer running (during long tool runs the prefix is replayed per TTL; `/cache warm` switches it) |
|
|
80
81
|
| `rebill $0.11` | Re-billing caused by cache misses in this session; token count for models without prices; hidden when 0 |
|
|
81
|
-
| `ctx 72%` | Context usage:
|
|
82
|
+
| `ctx 72%` | Context usage: yellow past the pruning threshold, red at ≥ 90%; `≈` an estimate; `ctx ?` no window information |
|
|
82
83
|
| `codemode only` | codemode active (`on` / `only`); a red `net!` is appended when the network is not isolated (Node 22 / 24 without an OS sandbox, see sandbox.md) |
|
|
83
84
|
|
|
84
85
|
The message area (stderr with an `ama: ` prefix in line mode) shows one line in only two cases; `cache.missNotices: false` turns them off:
|
|
@@ -91,10 +92,10 @@ The message area (stderr with an `ama: ` prefix in line mode) shows one line in
|
|
|
91
92
|
|
|
92
93
|
```
|
|
93
94
|
▎ Session 3f2a9c1e ~/.local/share/ama/sessions/…/3f2a9c1e.jsonl
|
|
94
|
-
▎ Model
|
|
95
|
-
▎ Messages
|
|
96
|
-
▎
|
|
97
|
-
▎ Context
|
|
95
|
+
▎ Model anthropic/claude-sonnet-4-5 · thinking medium · permission Accept edits
|
|
96
|
+
▎ Messages user 7 · assistant 9 · tool calls 23
|
|
97
|
+
▎ Total usage input 3.4k · output 9.4k · cache read 118k · cache write 6.2k · $0.42
|
|
98
|
+
▎ Context ▮▮▮▯▯▯▯▯▯▯ 34% · 68k / 200k · ≈ 116k until auto-compaction
|
|
98
99
|
▎
|
|
99
100
|
▎ Cache
|
|
100
101
|
▎ Input 3.4k = cache read 2.2k (65%) + uncached 1.2k
|
|
@@ -219,6 +220,38 @@ Design in [rewind-plan.md](../rewind-plan.md) (Chinese). Every user message that
|
|
|
219
220
|
- `/plan`: the current plan panel (see "Plan approval" below); `/plan <goal>` enters Plan mode and sends the goal; `/plan approve [mode|fresh]` and `/plan reject` approve / discard directly without a dialog.
|
|
220
221
|
- `/tasks`: focus the agent bar, `/tasks <id>` opens the sub-agent view; `/agents`: the available sub-agent types (see "Sub-agents" below).
|
|
221
222
|
- `/paste`: same as `Ctrl+V`.
|
|
223
|
+
- `/context`: a per-category breakdown of the context (see the next section).
|
|
224
|
+
|
|
225
|
+
### /context
|
|
226
|
+
|
|
227
|
+
`/context` answers "what is taking up the context". Interactive mode draws a left-bar panel; line mode, and interactive mode without a panel, print the same content as plain text:
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
▎ Context
|
|
231
|
+
▎ Used 10.6k / 200k (5.3%)
|
|
232
|
+
▎ Left 189k tokens
|
|
233
|
+
▎ Source reported usage 9k + estimated 1.6k
|
|
234
|
+
▎ Auto-compact at 184k, ≈ 173k to go
|
|
235
|
+
▎ Pruning old tool results trimmed from 129k
|
|
236
|
+
▎
|
|
237
|
+
▎ By category (estimated, 5.8k)
|
|
238
|
+
▎ System prompt and tool declarations are counted from the stored system messages; the full estimate can differ from reported usage.
|
|
239
|
+
▎ System prompt 600 ▮▯▯▯▯▯▯▯▯▯ 10% rules 500 · preamble 100 · cwd 0
|
|
240
|
+
▎ Tool declarations 333 ▮▯▯▯▯▯▯▯▯▯ 6% bash 241 · read 91
|
|
241
|
+
▎ …
|
|
242
|
+
▎ Tool results 3.2k ▮▮▮▮▮▯▯▯▯▯ 55% read 3k · bash 203
|
|
243
|
+
▎ Images 1.6k ▮▮▮▯▯▯▯▯▯▯ 27% 1 image · user 1.6k
|
|
244
|
+
▎
|
|
245
|
+
▎ Largest tool results (top 5)
|
|
246
|
+
▎ #1 3k read
|
|
247
|
+
▎ #2 203 bash
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
- **Top**: used / window and percentage (the same measure as `/session`: the latest usable usage plus an estimate of the messages after it), tokens left, the source ("reported usage X + estimated Y"; "estimated in full" when there is no usable usage yet, or a compaction / context edit happened after it), the auto-compact trigger (window − `compaction.reserveTokens`; "off" when disabled, "window unknown" without a window) and where tier-one pruning starts.
|
|
251
|
+
- **By category**: system prompt (per section), tool declarations (per tool), user messages, assistant text, reasoning, tool-call arguments (per tool), tool results (summed per tool name), images (1,600 each), summaries (compaction / branch) and custom messages, each with an estimated token count, share and bar. The categories add up to the character-based estimate of the whole projection (one token per CJK character, other characters / 4), so the total in the heading can differ from "Used" at the top, which prefers the usage reported by the model.
|
|
252
|
+
- **System prompt and tool declarations** come from the system messages stored in the session (the first full one and later patches, matching the estimate); before the first request the branch has no system message yet, so both categories are estimated from what the first request will send (the same baseline as the status bar); "Used" matches the status bar and the source reads "system prompt and tools only, estimated before the first request".
|
|
253
|
+
- **Bottom**: only the ordinal (which tool result in the projection), tool name and size of the 5 largest tool results — **never their content**.
|
|
254
|
+
- Read-only: no session entries are written and the request body is untouched, so the prompt-cache prefix is unaffected.
|
|
222
255
|
|
|
223
256
|
## Entering Bypass
|
|
224
257
|
|
|
@@ -449,10 +482,33 @@ Requires memory to be enabled (`ama memory enable` or `--memory`, see [memory.md
|
|
|
449
482
|
|
|
450
483
|
`ui.quietStartup` / `--quiet-startup`: `normal` shows an "AMA" logo with an info column: version, model and thinking level, directory (`~` abbreviated) and trust state, permission mode / preset / codemode, loaded context files / Skills / prompt templates / hooks, warning count and common keys. At 72 columns or wider the logo sits on the left and the info on the right; at 48–71 columns the logo is on top; below 48 columns a two-line header is shown instead (version · model · thinking / mode · directory · trust). `ui.logo: "off"` or `ui.compact` shows only the info column. The logo takes the theme's accent → user → tool colors letter by letter; ASCII mode swaps in a glyph made of `_ / \ |`. On startup a one-off "light-up" sweep plays for about a second (the glyph starts dim, a highlight band sweeps left to right, then it settles); it redraws in place and leaves no frames in the scrollback, and any key settles it at once while the key still goes to the input box. The settled frame is shown directly with `ui.animation: false`, `NO_COLOR` / a colorless terminal, a non-TTY, an embedding host (profile.host), a `CI` environment, a prompt given on the command line (`ama "…"`), a terminal shorter than 16 rows or content taller than one screen; the line interface, `-p`, RPC and ACP draw no startup header. `header` is a single line `✻ ama version · model · mode · /help` (the profile default); `silent` shows nothing. When `--resume` has no id, the model has no key, the session directory does not exist or project resources need trust, a small selection / input prompt appears before the interface starts, collapsing into one line on screen once answered.
|
|
451
484
|
|
|
485
|
+
## Terminal program status (OSC 7501)
|
|
486
|
+
|
|
487
|
+
The interactive interface reports its run state to the terminal with the [OSC 7501 Program Status Protocol](https://www.superlogical.com/rex/docs/build/program-status) (rev 0.3), so a supporting terminal can show "running / waiting for you / done / failed" in tabs, sidebars or notifications. Reports always carry `app=ama`; each report replaces that record as a whole.
|
|
488
|
+
|
|
489
|
+
| When | Root record |
|
|
490
|
+
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
491
|
+
| Startup, switching sessions (`/new` `/resume` `/fork`) | `idle` |
|
|
492
|
+
| A turn starts | `working`, msg is the current activity (Thinking / Replying / Running bash / Compacting context; throttled to 500 ms, unchanged values not resent) |
|
|
493
|
+
| An approval needs a person (including sub-agents' ones) | `blocked` `kind=permission`, msg is "tool · target" |
|
|
494
|
+
| A plan awaits approval | `blocked` `kind=question` |
|
|
495
|
+
| A turn failed because the login expired (`auth_expired`) | `blocked` `kind=auth`, msg is the first line of the error; cleared when the next turn starts or the model changes |
|
|
496
|
+
| The approval / plan is handled | back to `working` (or whatever the turn ended with) |
|
|
497
|
+
| A turn ends normally | `done`; any key pressed in ama afterwards goes back to `idle` |
|
|
498
|
+
| A turn fails | `error`, msg is the first line of the error |
|
|
499
|
+
| The user interrupts (Esc etc.) | `idle` |
|
|
500
|
+
| Leaving interactive mode | `clear` without an id (removes all of this program's records) |
|
|
501
|
+
|
|
502
|
+
- Sub-agent tasks get child records `id=task/<short id>` with the task description as title: `working` (msg is the tool in use) → `blocked` while an approval waits → `done` when finished, `error` on failure or the turn limit, `idle` when interrupted; finished task records are cleared when the next main turn starts.
|
|
503
|
+
- Exiting sends `clear`, not `done`: when the user quits, the result is already on screen and a lingering "done" record would only be noise.
|
|
504
|
+
- `ui.programStatus`: `auto` (default) sends `OSC 7501 ; ?` and `CSI c` (DA1) at startup and only reports once the `?` reply arrives; if DA1 answers first or nothing arrives within 300 ms it stays silent. Both replies (late ones too) are swallowed by the input parser and never reach the input box. `on` sends without detecting (the spec allows it: conforming terminals ignore unknown OSCs). `off` neither detects nor sends. Changes take effect after a restart.
|
|
505
|
+
- tmux: it drops unknown OSCs by default, and the spec does not cover multiplexers. tmux does not forward the outer terminal's reply to a passthrough query to the pane, so detection is unreliable and `auto` equals `off` inside tmux; to use it set `on` and `set -g allow-passthrough on` in tmux, and reports are wrapped in DCS passthrough (`ESC P tmux; … ESC \`).
|
|
506
|
+
- Interactive interface only. The stdout of `-p`, RPC and ACP is data or protocol, so nothing is sent there; `-p` sends nothing even when stderr is a terminal: it is a one-shot process whose result is the exit code, the next prompt drops working records anyway, and a leftover done / error is only noise for scripted runs.
|
|
507
|
+
|
|
452
508
|
## In tmux / Armadra terminal nodes
|
|
453
509
|
|
|
454
510
|
- Bracketed paste: enabled at startup; pasted multi-line content enters the input box as a whole (folded into a paste placeholder with the line count beyond 10 lines or 1 000 characters), and an Enter right after a paste sends directly, which suits writes from external programs.
|
|
455
|
-
-
|
|
511
|
+
- Apart from program status detection (see the previous section; none inside tmux), terminal capabilities are not queried, and mouse and the Kitty keyboard protocol are not enabled, so no replies get mixed into input; tmux ≥ 3.4 passes synchronized output through, and older versions display fine too.
|
|
456
512
|
- tmux's default prefix `C-b` is taken by the tmux client: to move tasks to the background press `C-b C-b` (`send-prefix` passes it through), or `↓` into the agent bar and press `b`; entering the bar uses `↓`, which the prefix does not affect.
|
|
457
513
|
- When the window size changes the last screen is redrawn in full; history in the scrollback is unaffected.
|
|
458
514
|
- Automatic fallback: non-TTY, `TERM=dumb`, `--no-tui` or a failed terminal initialization use line mode, with the same commands and approval prompts.
|
|
@@ -461,16 +517,17 @@ Requires memory to be enabled (`ama memory enable` or `--memory`, see [memory.md
|
|
|
461
517
|
|
|
462
518
|
The `ui` section of `config.json` (settable at project level too):
|
|
463
519
|
|
|
464
|
-
| Key
|
|
465
|
-
|
|
|
466
|
-
| `ui.theme`
|
|
467
|
-
| `ui.ascii`
|
|
468
|
-
| `ui.compact`
|
|
469
|
-
| `ui.logo`
|
|
470
|
-
| `ui.animation`
|
|
471
|
-
| `ui.markdown`
|
|
472
|
-
| `ui.showThinking`
|
|
473
|
-
| `ui.quietStartup`
|
|
520
|
+
| Key | Default | Effect |
|
|
521
|
+
| ------------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
522
|
+
| `ui.theme` | `dark` | `dark` / `light` / `auto`; auto only looks at `COLORFGBG` (no terminal query) and uses dark when unsure; configuring it explicitly is recommended |
|
|
523
|
+
| `ui.ascii` | auto-detected | ASCII glyphs (`›` → `>`, `⏺` → `*`, `⎿` → `L`, box lines → `+ - \|`, a 4-frame spinner) |
|
|
524
|
+
| `ui.compact` | `false` | No blank lines between message blocks, no logo in the startup header |
|
|
525
|
+
| `ui.logo` | `auto` | The "AMA" logo in the startup header; `off` shows only the info column |
|
|
526
|
+
| `ui.animation` | `true` | `false`: the spinner stays still as `·` while running and redraws only when seconds change; the startup logo does not animate |
|
|
527
|
+
| `ui.markdown` | `true` | `false`: assistant text is not rendered as Markdown |
|
|
528
|
+
| `ui.showThinking` | `collapsed` | See "Layout" |
|
|
529
|
+
| `ui.quietStartup` | `normal` | See "Startup screen" |
|
|
530
|
+
| `ui.programStatus` | `auto` | Terminal program status OSC 7501: `auto` / `on` / `off`, see "Terminal program status" |
|
|
474
531
|
|
|
475
532
|
- **ASCII mode**: `AMA_ASCII=1` (or `ui.ascii: true`) forces it on, `AMA_ASCII=0` forces it off; auto-detection turns it on when the locale (`LC_ALL` > `LC_CTYPE` > `LANG`) is set but lacks UTF-8, with `TERM=linux`, or on Windows without `WT_SESSION` or `TERM_PROGRAM` (legacy conhost). Windows Terminal uses Unicode.
|
|
476
533
|
- **Misaligned characters**: `⏺` (U+23FA), `⎿` and `▎` render two cells wide in some fonts (emoji fallback fonts in particular), while width is computed per wcwidth (one cell), causing misaligned columns or ghosting; switch to a monospace font or set `AMA_ASCII=1`.
|