@armadra/agent 0.7.1 → 0.7.2
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 +57 -0
- package/CHANGELOG.zh-CN.md +34 -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 +13 -1
- package/dist/agent/session-compaction.js +36 -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 +1643 -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 +23 -0
- package/dist/compaction/prefix-estimate.js +34 -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 +53 -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 +94 -0
- package/dist/i18n/messages/report-context.js +95 -0
- package/dist/i18n/messages/report.d.ts +96 -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 +35 -0
- package/dist/modes/context-report.js +172 -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,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上下文分类明细(`/context`):把投影消息按类别拆开估算。纯函数,只读,不改请求体。
|
|
3
|
+
*
|
|
4
|
+
* 口径与 `estimateMessagesTokens` 一致:每条消息先按 `estimateMessageTokens` 求总量,再把这个总量
|
|
5
|
+
* 分到消息内各部分(文本部分取 `wide + floor(narrow / 4)`,余下的 0–3 个 token 按最大余数补给
|
|
6
|
+
* 字符余数最大的部分;图片每张 `IMAGE_TOKENS`),所以各类别之和恰好等于整组消息的估算。
|
|
7
|
+
*
|
|
8
|
+
* 系统提示与工具声明来自活动分支上的 `system` 消息(首条全量、之后是补丁,补丁也计入——与估算口径
|
|
9
|
+
* 相同):按节名、按工具名汇总;工具表数组的括号与逗号计入工具声明但不归到具体工具。首次请求前
|
|
10
|
+
* 分支上还没有 `system` 消息,这两类为 0(`hasSystem: false`)。
|
|
11
|
+
*
|
|
12
|
+
* 只产出数字与名字(节名、工具名、custom 类型),不带任何正文。
|
|
13
|
+
*/
|
|
14
|
+
import type { AgentMessage } from "../session/types.js";
|
|
15
|
+
export declare const BREAKDOWN_CATEGORIES: readonly ["system", "tools", "user", "assistant", "reasoning", "toolCalls", "toolResults", "images", "summaries", "custom"];
|
|
16
|
+
export type BreakdownCategory = (typeof BREAKDOWN_CATEGORIES)[number];
|
|
17
|
+
export interface BreakdownPart {
|
|
18
|
+
/** 节名 / 工具名 / `compaction`·`branch` / custom 类型 / 图片来源角色。 */
|
|
19
|
+
name: string;
|
|
20
|
+
tokens: number;
|
|
21
|
+
}
|
|
22
|
+
export interface BreakdownEntry {
|
|
23
|
+
category: BreakdownCategory;
|
|
24
|
+
tokens: number;
|
|
25
|
+
/** 计入本类的块数(消息、块、节、图片)。 */
|
|
26
|
+
count: number;
|
|
27
|
+
/** 按名字汇总,从大到小;没有名字的部分(如工具表的括号)只进 `tokens`。 */
|
|
28
|
+
parts: BreakdownPart[];
|
|
29
|
+
}
|
|
30
|
+
export interface ToolResultSize {
|
|
31
|
+
/** 在投影里第几个工具结果(从 1 开始)。 */
|
|
32
|
+
ordinal: number;
|
|
33
|
+
toolName: string;
|
|
34
|
+
tokens: number;
|
|
35
|
+
}
|
|
36
|
+
export interface ContextBreakdown {
|
|
37
|
+
/** 固定顺序(`BREAKDOWN_CATEGORIES`)。 */
|
|
38
|
+
entries: BreakdownEntry[];
|
|
39
|
+
/** = `estimateMessagesTokens(messages)`。 */
|
|
40
|
+
total: number;
|
|
41
|
+
/** 系统提示 + 工具声明。 */
|
|
42
|
+
prefixTokens: number;
|
|
43
|
+
/** 分支上已有 `system` 消息(首次请求后)。 */
|
|
44
|
+
hasSystem: boolean;
|
|
45
|
+
/** 每个工具结果的大小,按出现顺序。 */
|
|
46
|
+
toolResults: ToolResultSize[];
|
|
47
|
+
}
|
|
48
|
+
/** 消息 → 分类明细(各类别之和 = `estimateMessagesTokens(messages)`)。 */
|
|
49
|
+
export declare function breakdownMessages(messages: readonly AgentMessage[]): ContextBreakdown;
|
|
50
|
+
/** 最大的 n 个工具结果(大小相同按出现顺序)。 */
|
|
51
|
+
export declare function largestToolResults(breakdown: ContextBreakdown, n: number): ToolResultSize[];
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 上下文分类明细(`/context`):把投影消息按类别拆开估算。纯函数,只读,不改请求体。
|
|
3
|
+
*
|
|
4
|
+
* 口径与 `estimateMessagesTokens` 一致:每条消息先按 `estimateMessageTokens` 求总量,再把这个总量
|
|
5
|
+
* 分到消息内各部分(文本部分取 `wide + floor(narrow / 4)`,余下的 0–3 个 token 按最大余数补给
|
|
6
|
+
* 字符余数最大的部分;图片每张 `IMAGE_TOKENS`),所以各类别之和恰好等于整组消息的估算。
|
|
7
|
+
*
|
|
8
|
+
* 系统提示与工具声明来自活动分支上的 `system` 消息(首条全量、之后是补丁,补丁也计入——与估算口径
|
|
9
|
+
* 相同):按节名、按工具名汇总;工具表数组的括号与逗号计入工具声明但不归到具体工具。首次请求前
|
|
10
|
+
* 分支上还没有 `system` 消息,这两类为 0(`hasSystem: false`)。
|
|
11
|
+
*
|
|
12
|
+
* 只产出数字与名字(节名、工具名、custom 类型),不带任何正文。
|
|
13
|
+
*/
|
|
14
|
+
import { countText, estimateMessageTokens, IMAGE_TOKENS } from "./estimate.js";
|
|
15
|
+
/** 与 estimate.ts 的字符口径相同(非宽字符每 4 个 1 token)。 */
|
|
16
|
+
const CHARS_PER_TOKEN = 4;
|
|
17
|
+
export const BREAKDOWN_CATEGORIES = [
|
|
18
|
+
"system",
|
|
19
|
+
"tools",
|
|
20
|
+
"user",
|
|
21
|
+
"assistant",
|
|
22
|
+
"reasoning",
|
|
23
|
+
"toolCalls",
|
|
24
|
+
"toolResults",
|
|
25
|
+
"images",
|
|
26
|
+
"summaries",
|
|
27
|
+
"custom",
|
|
28
|
+
];
|
|
29
|
+
function textPiece(category, name, text) {
|
|
30
|
+
const count = countText(text, { wide: 0, narrow: 0 });
|
|
31
|
+
return { category, name, wide: count.wide, narrow: count.narrow, fixed: 0 };
|
|
32
|
+
}
|
|
33
|
+
function imagePiece(name) {
|
|
34
|
+
return { category: "images", name, wide: 0, narrow: 0, fixed: IMAGE_TOKENS };
|
|
35
|
+
}
|
|
36
|
+
function contentPieces(content, category, name, imageSource) {
|
|
37
|
+
if (typeof content === "string")
|
|
38
|
+
return [textPiece(category, name, content)];
|
|
39
|
+
return content.map((block) => block.type === "text" ? textPiece(category, name, block.text) : imagePiece(imageSource));
|
|
40
|
+
}
|
|
41
|
+
/** 一条消息拆成的部分(字符合计与 `estimateMessageTokens` 的计数完全相同)。 */
|
|
42
|
+
function messagePieces(message) {
|
|
43
|
+
switch (message.role) {
|
|
44
|
+
case "system": {
|
|
45
|
+
const pieces = Object.entries(message.sections).map(([name, text]) => textPiece("system", name, text ?? ""));
|
|
46
|
+
const tools = message.toolsAdded;
|
|
47
|
+
if (tools !== undefined) {
|
|
48
|
+
for (const tool of tools)
|
|
49
|
+
pieces.push(textPiece("tools", tool.name, JSON.stringify(tool)));
|
|
50
|
+
// JSON.stringify(数组) 比各元素多出 `[`、`]` 与元素间的逗号
|
|
51
|
+
pieces.push({
|
|
52
|
+
category: "tools",
|
|
53
|
+
name: undefined,
|
|
54
|
+
wide: 0,
|
|
55
|
+
narrow: 2 + Math.max(0, tools.length - 1),
|
|
56
|
+
fixed: 0,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return pieces;
|
|
60
|
+
}
|
|
61
|
+
case "user":
|
|
62
|
+
return contentPieces(message.content, "user", undefined, "user");
|
|
63
|
+
case "toolResult":
|
|
64
|
+
return contentPieces(message.content, "toolResults", message.toolName, "toolResult");
|
|
65
|
+
case "custom":
|
|
66
|
+
return contentPieces(message.content, "custom", message.customType, "custom");
|
|
67
|
+
case "assistant":
|
|
68
|
+
return message.content.map((block) => block.type === "text"
|
|
69
|
+
? textPiece("assistant", undefined, block.text)
|
|
70
|
+
: block.type === "thinking"
|
|
71
|
+
? textPiece("reasoning", undefined, block.thinking)
|
|
72
|
+
: textPiece("toolCalls", block.name, block.name + JSON.stringify(block.arguments)));
|
|
73
|
+
case "compactionSummary":
|
|
74
|
+
return [textPiece("summaries", "compaction", message.summary)];
|
|
75
|
+
case "branchSummary":
|
|
76
|
+
return [textPiece("summaries", "branch", message.summary)];
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** 把消息总量分到各部分(最大余数法),返回与 pieces 对齐的 token 数。 */
|
|
80
|
+
function allocate(pieces, total) {
|
|
81
|
+
const tokens = pieces.map((p) => p.fixed + p.wide + Math.floor(p.narrow / CHARS_PER_TOKEN));
|
|
82
|
+
let left = total - tokens.reduce((sum, n) => sum + n, 0);
|
|
83
|
+
const order = pieces
|
|
84
|
+
.map((p, i) => ({ i, rest: p.narrow % CHARS_PER_TOKEN }))
|
|
85
|
+
.filter((x) => x.rest > 0)
|
|
86
|
+
.sort((a, b) => b.rest - a.rest || a.i - b.i);
|
|
87
|
+
for (const { i } of order) {
|
|
88
|
+
if (left <= 0)
|
|
89
|
+
break;
|
|
90
|
+
tokens[i] = (tokens[i] ?? 0) + 1;
|
|
91
|
+
left--;
|
|
92
|
+
}
|
|
93
|
+
// 理论上不会剩;保险起见补到最大的部分,保证合计与估算一致
|
|
94
|
+
if (left !== 0 && tokens.length > 0) {
|
|
95
|
+
let max = 0;
|
|
96
|
+
for (let i = 1; i < tokens.length; i++)
|
|
97
|
+
if ((tokens[i] ?? 0) > (tokens[max] ?? 0))
|
|
98
|
+
max = i;
|
|
99
|
+
tokens[max] = (tokens[max] ?? 0) + left;
|
|
100
|
+
}
|
|
101
|
+
return tokens;
|
|
102
|
+
}
|
|
103
|
+
/** 消息 → 分类明细(各类别之和 = `estimateMessagesTokens(messages)`)。 */
|
|
104
|
+
export function breakdownMessages(messages) {
|
|
105
|
+
const totals = new Map();
|
|
106
|
+
const parts = new Map();
|
|
107
|
+
for (const category of BREAKDOWN_CATEGORIES) {
|
|
108
|
+
totals.set(category, { tokens: 0, count: 0 });
|
|
109
|
+
parts.set(category, new Map());
|
|
110
|
+
}
|
|
111
|
+
const toolResults = [];
|
|
112
|
+
let total = 0;
|
|
113
|
+
let hasSystem = false;
|
|
114
|
+
for (const message of messages) {
|
|
115
|
+
const messageTokens = estimateMessageTokens(message);
|
|
116
|
+
total += messageTokens;
|
|
117
|
+
if (message.role === "system")
|
|
118
|
+
hasSystem = true;
|
|
119
|
+
if (message.role === "toolResult") {
|
|
120
|
+
toolResults.push({
|
|
121
|
+
ordinal: toolResults.length + 1,
|
|
122
|
+
toolName: message.toolName,
|
|
123
|
+
tokens: messageTokens,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const pieces = messagePieces(message);
|
|
127
|
+
const allocated = allocate(pieces, messageTokens);
|
|
128
|
+
pieces.forEach((piece, i) => {
|
|
129
|
+
const tokens = allocated[i] ?? 0;
|
|
130
|
+
const entry = totals.get(piece.category);
|
|
131
|
+
if (entry === undefined)
|
|
132
|
+
return;
|
|
133
|
+
entry.tokens += tokens;
|
|
134
|
+
if (piece.name !== undefined || piece.category !== "tools")
|
|
135
|
+
entry.count++;
|
|
136
|
+
if (piece.name !== undefined) {
|
|
137
|
+
const named = parts.get(piece.category);
|
|
138
|
+
named?.set(piece.name, (named.get(piece.name) ?? 0) + tokens);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const entries = BREAKDOWN_CATEGORIES.map((category) => {
|
|
143
|
+
const entry = totals.get(category) ?? { tokens: 0, count: 0 };
|
|
144
|
+
const named = [...(parts.get(category) ?? new Map())]
|
|
145
|
+
.map(([name, tokens]) => ({ name, tokens }))
|
|
146
|
+
.sort((a, b) => b.tokens - a.tokens || a.name.localeCompare(b.name));
|
|
147
|
+
return { category, tokens: entry.tokens, count: entry.count, parts: named };
|
|
148
|
+
});
|
|
149
|
+
const prefixTokens = entries
|
|
150
|
+
.filter((e) => e.category === "system" || e.category === "tools")
|
|
151
|
+
.reduce((sum, e) => sum + e.tokens, 0);
|
|
152
|
+
return { entries, total, prefixTokens, hasSystem, toolResults };
|
|
153
|
+
}
|
|
154
|
+
/** 最大的 n 个工具结果(大小相同按出现顺序)。 */
|
|
155
|
+
export function largestToolResults(breakdown, n) {
|
|
156
|
+
return [...breakdown.toolResults]
|
|
157
|
+
.sort((a, b) => b.tokens - a.tokens || a.ordinal - b.ordinal)
|
|
158
|
+
.slice(0, Math.max(0, n));
|
|
159
|
+
}
|
|
@@ -15,6 +15,11 @@ import type { AgentMessage, SessionEntry } from "../session/types.js";
|
|
|
15
15
|
export declare const IMAGE_TOKENS = 1600;
|
|
16
16
|
/** 文本的 token 估算:CJK / 假名 / 谚文每字 1,其余字符 / 4(向上取整)。 */
|
|
17
17
|
export declare function estimateTextTokens(text: string): number;
|
|
18
|
+
interface CharCount {
|
|
19
|
+
wide: number;
|
|
20
|
+
narrow: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function countText(text: string, into: CharCount): CharCount;
|
|
18
23
|
export declare function calculateContextTokens(usage: Usage): number;
|
|
19
24
|
/** 内容的估算(文本按脚本,图片每张 1 600)。 */
|
|
20
25
|
export declare function estimateContentTokens(content: string | readonly ContentBlock[]): number;
|
|
@@ -36,3 +41,4 @@ export declare function estimateContextTokens(messages: readonly AgentMessage[])
|
|
|
36
41
|
* `items` 是 buildProjection(branch).items,`branch` 是同一活动分支。
|
|
37
42
|
*/
|
|
38
43
|
export declare function estimateProjectedTokens(items: readonly ContextItem[], branch: readonly SessionEntry[]): ContextEstimate;
|
|
44
|
+
export {};
|
|
@@ -29,7 +29,7 @@ function isWideCode(code) {
|
|
|
29
29
|
export function estimateTextTokens(text) {
|
|
30
30
|
return tokensOf(countText(text, { wide: 0, narrow: 0 }));
|
|
31
31
|
}
|
|
32
|
-
function countText(text, into) {
|
|
32
|
+
export function countText(text, into) {
|
|
33
33
|
let wide = 0;
|
|
34
34
|
let narrow = 0;
|
|
35
35
|
for (let i = 0; i < text.length; i++) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 启动基线:系统消息落盘之前(第一次请求前)的上下文估算。
|
|
3
|
+
*
|
|
4
|
+
* 系统提示与工具表在第一次请求前才作为首条 `system` 消息写进分支,在此之前投影里没有它们,
|
|
5
|
+
* 状态栏会一直显示 0。这里按「将要发送的那一份」估算:命名节文本 + 工具声明 JSON,与
|
|
6
|
+
* `estimateMessageTokens` 对 `system` 消息的口径一致(CJK 每字 1,其余字符 / 4)。
|
|
7
|
+
*
|
|
8
|
+
* 只用于统计:不写分支、不改请求体,前缀缓存不受影响;压缩阈值也不看它(摘要压不掉这部分)。
|
|
9
|
+
*/
|
|
10
|
+
import type { ToolDecl } from "../ai/types.js";
|
|
11
|
+
import type { ContextEstimate } from "./estimate.js";
|
|
12
|
+
/** 系统提示(已装配的命名节)+ 工具声明的估算。 */
|
|
13
|
+
export declare function estimatePrefixTokens(sections: Readonly<Record<string, string>>, tools: readonly ToolDecl[]): number;
|
|
14
|
+
/** 上下文估算的来源(`SessionStats.context.source`)。 */
|
|
15
|
+
export type ContextSource = "usage" | "estimate" | "prefix";
|
|
16
|
+
export interface SourcedEstimate extends ContextEstimate {
|
|
17
|
+
source: ContextSource;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 给投影估算标来源;投影里还没有系统消息时把前缀基线加进去(`prefix` 懒算,只在需要时调用)。
|
|
21
|
+
* 有 usage 时照旧(usage 已经包含系统提示与工具表)。
|
|
22
|
+
*/
|
|
23
|
+
export declare function withPrefixBaseline(estimate: ContextEstimate, hasSystemMessage: boolean, prefix: () => number): SourcedEstimate;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 启动基线:系统消息落盘之前(第一次请求前)的上下文估算。
|
|
3
|
+
*
|
|
4
|
+
* 系统提示与工具表在第一次请求前才作为首条 `system` 消息写进分支,在此之前投影里没有它们,
|
|
5
|
+
* 状态栏会一直显示 0。这里按「将要发送的那一份」估算:命名节文本 + 工具声明 JSON,与
|
|
6
|
+
* `estimateMessageTokens` 对 `system` 消息的口径一致(CJK 每字 1,其余字符 / 4)。
|
|
7
|
+
*
|
|
8
|
+
* 只用于统计:不写分支、不改请求体,前缀缓存不受影响;压缩阈值也不看它(摘要压不掉这部分)。
|
|
9
|
+
*/
|
|
10
|
+
import { estimateMessageTokens } from "./estimate.js";
|
|
11
|
+
/** 系统提示(已装配的命名节)+ 工具声明的估算。 */
|
|
12
|
+
export function estimatePrefixTokens(sections, tools) {
|
|
13
|
+
const message = { role: "system", sections: { ...sections }, timestamp: 0 };
|
|
14
|
+
if (tools.length > 0)
|
|
15
|
+
message.toolsAdded = [...tools];
|
|
16
|
+
return estimateMessageTokens(message);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 给投影估算标来源;投影里还没有系统消息时把前缀基线加进去(`prefix` 懒算,只在需要时调用)。
|
|
20
|
+
* 有 usage 时照旧(usage 已经包含系统提示与工具表)。
|
|
21
|
+
*/
|
|
22
|
+
export function withPrefixBaseline(estimate, hasSystemMessage, prefix) {
|
|
23
|
+
if (estimate.usageTokens > 0)
|
|
24
|
+
return { ...estimate, source: "usage" };
|
|
25
|
+
if (hasSystemMessage)
|
|
26
|
+
return { ...estimate, source: "estimate" };
|
|
27
|
+
const base = prefix();
|
|
28
|
+
return {
|
|
29
|
+
...estimate,
|
|
30
|
+
tokens: estimate.tokens + base,
|
|
31
|
+
trailingTokens: estimate.trailingTokens + base,
|
|
32
|
+
source: "prefix",
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -281,6 +281,7 @@ function buildBaseSchema() {
|
|
|
281
281
|
animation: bool(),
|
|
282
282
|
restoreOnCancel: bool(),
|
|
283
283
|
enterWhileRunning: oneOf(["queue", "interrupt"]),
|
|
284
|
+
programStatus: oneOf(["auto", "on", "off"]),
|
|
284
285
|
statusLine: oneOf(STATUS_LINE_MODES),
|
|
285
286
|
language: oneOf(LANGUAGE_SETTINGS),
|
|
286
287
|
replyLanguage: str(),
|
package/dist/config/key-docs.js
CHANGED
package/dist/config/schema.js
CHANGED
|
@@ -261,6 +261,7 @@ export function validateConfig(value) {
|
|
|
261
261
|
"animation",
|
|
262
262
|
"restoreOnCancel",
|
|
263
263
|
"enterWhileRunning",
|
|
264
|
+
"programStatus",
|
|
264
265
|
...W5_UI_KEYS,
|
|
265
266
|
...W6_UI_KEYS,
|
|
266
267
|
], (s, p) => {
|
|
@@ -275,6 +276,7 @@ export function validateConfig(value) {
|
|
|
275
276
|
c.boolean(s, "animation", p);
|
|
276
277
|
c.boolean(s, "restoreOnCancel", p);
|
|
277
278
|
c.oneOf(s, "enterWhileRunning", p, ["queue", "interrupt"]);
|
|
279
|
+
c.oneOf(s, "programStatus", p, ["auto", "on", "off"]);
|
|
278
280
|
checkUiW5(c, s, p);
|
|
279
281
|
checkUiW6(c, s, p);
|
|
280
282
|
});
|
|
@@ -35,6 +35,7 @@ const ROWS = [
|
|
|
35
35
|
row("ui.statusLine", "ui", "now", "any"),
|
|
36
36
|
row("ui.restoreOnCancel", "ui", "nextSession", "any"),
|
|
37
37
|
row("ui.enterWhileRunning", "ui", "now", "any"),
|
|
38
|
+
row("ui.programStatus", "ui", "restart", "any"),
|
|
38
39
|
row("ui.quietStartup", "ui", "restart", "any"),
|
|
39
40
|
row("ui.ascii", "ui", "restart", "any", { envOverride: "AMA_ASCII" }),
|
|
40
41
|
row("ui.language", "ui", "restart", "any", { envOverride: "AMA_LANG" }),
|
package/dist/config/types.d.ts
CHANGED
|
@@ -153,6 +153,8 @@ export interface UiConfig extends UiConfigW6 {
|
|
|
153
153
|
restoreOnCancel?: boolean;
|
|
154
154
|
/** 运行中按 Enter:queue(缺省)排队插话,interrupt 打断并立即发送(与 `app.message.interrupt` 互换)。 */
|
|
155
155
|
enterWhileRunning?: "queue" | "interrupt";
|
|
156
|
+
/** 终端程序状态 OSC 7501:auto(缺省)检测通过才发,on 不检测直接发,off 不发。 */
|
|
157
|
+
programStatus?: "auto" | "on" | "off";
|
|
156
158
|
/** [W5-C0] 底部信息行(W5-A):缺省独立终端 full、嵌入宿主(有 profile)compact。 */
|
|
157
159
|
statusLine?: StatusLineMode;
|
|
158
160
|
}
|
|
@@ -83,6 +83,8 @@ class CodexSession {
|
|
|
83
83
|
tokensTotal;
|
|
84
84
|
tokensAtStart;
|
|
85
85
|
contextWindow;
|
|
86
|
+
/** 最近一次请求的总量(`tokenUsage.last.totalTokens`)= 线程当前的上下文占用。 */
|
|
87
|
+
contextTokens;
|
|
86
88
|
closed = false;
|
|
87
89
|
interruptRequested = false;
|
|
88
90
|
constructor(agentId, transport, deps) {
|
|
@@ -234,6 +236,7 @@ class CodexSession {
|
|
|
234
236
|
input: end.inputTokens - (start?.inputTokens ?? 0),
|
|
235
237
|
output: end.outputTokens - (start?.outputTokens ?? 0),
|
|
236
238
|
cacheRead: end.cachedInputTokens - (start?.cachedInputTokens ?? 0),
|
|
239
|
+
...(this.contextTokens !== undefined ? { contextTokens: this.contextTokens } : {}),
|
|
237
240
|
...(this.contextWindow !== undefined ? { contextWindow: this.contextWindow } : {}),
|
|
238
241
|
});
|
|
239
242
|
}
|
|
@@ -257,6 +260,9 @@ class CodexSession {
|
|
|
257
260
|
const total = readTokens(usage["total"]);
|
|
258
261
|
if (total !== undefined)
|
|
259
262
|
this.tokensTotal = total;
|
|
263
|
+
const last = readTokens(usage["last"]);
|
|
264
|
+
if (last !== undefined && last.totalTokens > 0)
|
|
265
|
+
this.contextTokens = last.totalTokens;
|
|
260
266
|
if (typeof usage["modelContextWindow"] === "number")
|
|
261
267
|
this.contextWindow = usage["modelContextWindow"];
|
|
262
268
|
return;
|
package/dist/drivers/runner.js
CHANGED
|
@@ -161,6 +161,8 @@ class ProcessHandle {
|
|
|
161
161
|
agent;
|
|
162
162
|
/** [W6-C0] 本回合工具的骨架(id → 种类 / 状态 / 起止),回合结束写 `turn_trace`。 */
|
|
163
163
|
turnTools = new Map();
|
|
164
|
+
/** 外部 Agent 最近报告的上下文占用与窗口(只记数字,跨回合保留)。 */
|
|
165
|
+
context = {};
|
|
164
166
|
constructor(runner, chosen, request, deps) {
|
|
165
167
|
this.runner = runner;
|
|
166
168
|
this.chosen = chosen;
|
|
@@ -299,6 +301,7 @@ class ProcessHandle {
|
|
|
299
301
|
this.emit({ type: "notice", level: event.level, text: event.text });
|
|
300
302
|
return;
|
|
301
303
|
case "usage":
|
|
304
|
+
this.noteContext(event.contextTokens, event.contextWindow);
|
|
302
305
|
if (event.costUsd !== undefined) {
|
|
303
306
|
turnCost.usd = event.costUsd;
|
|
304
307
|
const budget = this.request.budgetUsd;
|
|
@@ -319,6 +322,19 @@ class ProcessHandle {
|
|
|
319
322
|
return;
|
|
320
323
|
}
|
|
321
324
|
}
|
|
325
|
+
/** 上下文占用或窗口变了:记下并单独发一条只带它们的 usage(任务记录 / Agent 栏即时更新)。 */
|
|
326
|
+
noteContext(tokens, window) {
|
|
327
|
+
const next = { ...this.context };
|
|
328
|
+
if (tokens !== undefined)
|
|
329
|
+
next.contextTokens = tokens;
|
|
330
|
+
if (window !== undefined)
|
|
331
|
+
next.contextWindow = window;
|
|
332
|
+
if (next.contextTokens === this.context.contextTokens &&
|
|
333
|
+
next.contextWindow === this.context.contextWindow)
|
|
334
|
+
return;
|
|
335
|
+
this.context = next;
|
|
336
|
+
this.emit({ type: "usage", ...next });
|
|
337
|
+
}
|
|
322
338
|
now() {
|
|
323
339
|
return (this.deps.now ?? Date.now)();
|
|
324
340
|
}
|
|
@@ -412,6 +428,7 @@ class ProcessHandle {
|
|
|
412
428
|
unit,
|
|
413
429
|
amount,
|
|
414
430
|
...(usage.totalTokens > 0 ? { tokens: usage.totalTokens } : {}),
|
|
431
|
+
...this.context,
|
|
415
432
|
});
|
|
416
433
|
this.emit({ type: "usage", usage, unit, amount });
|
|
417
434
|
}
|
package/dist/drivers/store.d.ts
CHANGED
|
@@ -27,6 +27,9 @@ export interface AgentUsageRecord {
|
|
|
27
27
|
unit: ExternalUsageUnit;
|
|
28
28
|
amount: number;
|
|
29
29
|
tokens?: number;
|
|
30
|
+
/** 回合结束时该 Agent 报告的上下文占用与窗口(只记数字)。 */
|
|
31
|
+
contextTokens?: number;
|
|
32
|
+
contextWindow?: number;
|
|
30
33
|
}
|
|
31
34
|
/** 会话一侧的最小接口(`ToolContext.session` 或 SessionCore 都能提供)。 */
|
|
32
35
|
export interface AgentStoreBackend {
|
package/dist/drivers/store.js
CHANGED
|
@@ -75,6 +75,10 @@ export function aggregateExternal(entries) {
|
|
|
75
75
|
row.amount += u.amount;
|
|
76
76
|
if (typeof u.tokens === "number")
|
|
77
77
|
row.tokens = (row.tokens ?? 0) + u.tokens;
|
|
78
|
+
if (typeof u.contextTokens === "number")
|
|
79
|
+
row.contextTokens = u.contextTokens;
|
|
80
|
+
if (typeof u.contextWindow === "number")
|
|
81
|
+
row.contextWindow = u.contextWindow;
|
|
78
82
|
}
|
|
79
83
|
return { byAgent };
|
|
80
84
|
}
|
package/dist/i18n/catalog.d.ts
CHANGED
|
@@ -541,6 +541,8 @@ declare const en: {
|
|
|
541
541
|
cacheSilent: string;
|
|
542
542
|
sandbox: string;
|
|
543
543
|
cycleHint: string;
|
|
544
|
+
autoCompact: string;
|
|
545
|
+
noWindow: (model: string) => string;
|
|
544
546
|
quota: {
|
|
545
547
|
session: string;
|
|
546
548
|
reset: string;
|
|
@@ -998,6 +1000,7 @@ declare const en: {
|
|
|
998
1000
|
interrupted: string;
|
|
999
1001
|
};
|
|
1000
1002
|
turns: (n: number) => string;
|
|
1003
|
+
context: (percent: string) => string;
|
|
1001
1004
|
agentRunner: (agent: string, runner: string) => string;
|
|
1002
1005
|
background: string;
|
|
1003
1006
|
none: string;
|
|
@@ -1084,6 +1087,7 @@ declare const en: {
|
|
|
1084
1087
|
tools: string;
|
|
1085
1088
|
hooks: string;
|
|
1086
1089
|
session: string;
|
|
1090
|
+
context: string;
|
|
1087
1091
|
cache: string;
|
|
1088
1092
|
statusline: string;
|
|
1089
1093
|
planArgs: string;
|
|
@@ -1139,6 +1143,50 @@ declare const en: {
|
|
|
1139
1143
|
cwdAnswer: string;
|
|
1140
1144
|
notDirectory: (path: string) => string;
|
|
1141
1145
|
};
|
|
1146
|
+
contextReport: {
|
|
1147
|
+
title: string;
|
|
1148
|
+
keyUsed: string;
|
|
1149
|
+
used: (tokens: string, window: string, percent: string | undefined) => string;
|
|
1150
|
+
keyLeft: string;
|
|
1151
|
+
left: (tokens: string) => string;
|
|
1152
|
+
keySource: string;
|
|
1153
|
+
sourceUsage: (usage: string, estimate: string) => string;
|
|
1154
|
+
sourceEstimate: string;
|
|
1155
|
+
keyAutoCompact: string;
|
|
1156
|
+
autoCompact: (at: string, distance: string) => string;
|
|
1157
|
+
autoCompactReached: (at: string) => string;
|
|
1158
|
+
autoCompactOff: string;
|
|
1159
|
+
windowUnknown: string;
|
|
1160
|
+
keyPrune: string;
|
|
1161
|
+
prune: (at: string) => string;
|
|
1162
|
+
breakdown: (total: string) => string;
|
|
1163
|
+
prefixCounted: string;
|
|
1164
|
+
prefixPending: string;
|
|
1165
|
+
categories: {
|
|
1166
|
+
system: string;
|
|
1167
|
+
tools: string;
|
|
1168
|
+
user: string;
|
|
1169
|
+
assistant: string;
|
|
1170
|
+
reasoning: string;
|
|
1171
|
+
toolCalls: string;
|
|
1172
|
+
toolResults: string;
|
|
1173
|
+
images: string;
|
|
1174
|
+
summaries: string;
|
|
1175
|
+
custom: string;
|
|
1176
|
+
};
|
|
1177
|
+
partNames: {
|
|
1178
|
+
compaction: string;
|
|
1179
|
+
branch: string;
|
|
1180
|
+
user: string;
|
|
1181
|
+
toolResult: string;
|
|
1182
|
+
custom: string;
|
|
1183
|
+
};
|
|
1184
|
+
images: (count: number) => string;
|
|
1185
|
+
more: (count: number) => string;
|
|
1186
|
+
largest: (count: number) => string;
|
|
1187
|
+
noToolResults: string;
|
|
1188
|
+
empty: string;
|
|
1189
|
+
};
|
|
1142
1190
|
cache: {
|
|
1143
1191
|
unreported: string;
|
|
1144
1192
|
unknown: string;
|
|
@@ -1196,6 +1244,8 @@ declare const en: {
|
|
|
1196
1244
|
usage: (input: number, output: number, cacheRead: number, cacheWrite: number, cost: string) => string;
|
|
1197
1245
|
keyContext: string;
|
|
1198
1246
|
context: (tokens: string, window: string, percent: string) => string;
|
|
1247
|
+
toAutoCompact: (tokens: string) => string;
|
|
1248
|
+
autoCompactOff: string;
|
|
1199
1249
|
keyAgents: string;
|
|
1200
1250
|
externalTitle: string;
|
|
1201
1251
|
noFingerprint: string;
|
|
@@ -1333,6 +1383,7 @@ declare const en: {
|
|
|
1333
1383
|
"ui.animation": string;
|
|
1334
1384
|
"ui.restoreOnCancel": string;
|
|
1335
1385
|
"ui.enterWhileRunning": string;
|
|
1386
|
+
"ui.programStatus": string;
|
|
1336
1387
|
"ui.statusLine": string;
|
|
1337
1388
|
"ui.language": string;
|
|
1338
1389
|
"ui.replyLanguage": string;
|
|
@@ -1742,6 +1793,7 @@ declare const en: {
|
|
|
1742
1793
|
interrupted: string;
|
|
1743
1794
|
};
|
|
1744
1795
|
turns: (n: number) => string;
|
|
1796
|
+
context: (percent: string) => string;
|
|
1745
1797
|
bar: {
|
|
1746
1798
|
more: (n: number) => string;
|
|
1747
1799
|
keys: (up: string, down: string) => string;
|
|
@@ -2089,6 +2141,7 @@ declare const en: {
|
|
|
2089
2141
|
"ui.statusLine": string;
|
|
2090
2142
|
"ui.restoreOnCancel": string;
|
|
2091
2143
|
"ui.enterWhileRunning": string;
|
|
2144
|
+
"ui.programStatus": string;
|
|
2092
2145
|
"ui.quietStartup": string;
|
|
2093
2146
|
"ui.ascii": string;
|
|
2094
2147
|
"ui.language": string;
|
|
@@ -20,6 +20,8 @@ export declare const en: {
|
|
|
20
20
|
interrupted: string;
|
|
21
21
|
};
|
|
22
22
|
turns: (n: number) => string;
|
|
23
|
+
/** 外部 Agent 报告的上下文占用(`34%`、`0.4%`)。 */
|
|
24
|
+
context: (percent: string) => string;
|
|
23
25
|
bar: {
|
|
24
26
|
more: (n: number) => string;
|
|
25
27
|
keys: (up: string, down: string) => string;
|
|
@@ -90,6 +92,7 @@ export declare const zh: {
|
|
|
90
92
|
interrupted: string;
|
|
91
93
|
};
|
|
92
94
|
turns: (n: number) => string;
|
|
95
|
+
context: (percent: string) => string;
|
|
93
96
|
bar: {
|
|
94
97
|
more: (n: number) => string;
|
|
95
98
|
keys: (up: string, down: string) => string;
|
|
@@ -21,6 +21,8 @@ export const en = {
|
|
|
21
21
|
interrupted: "interrupted",
|
|
22
22
|
},
|
|
23
23
|
turns: (n) => plural(n, "turn"),
|
|
24
|
+
/** 外部 Agent 报告的上下文占用(`34%`、`0.4%`)。 */
|
|
25
|
+
context: (percent) => `ctx ${percent}`,
|
|
24
26
|
bar: {
|
|
25
27
|
more: (n) => `${n} more`,
|
|
26
28
|
keys: (up, down) => `${up}${down} select · Enter open · Esc back`,
|
|
@@ -91,6 +93,7 @@ export const zh = {
|
|
|
91
93
|
interrupted: "已中断",
|
|
92
94
|
},
|
|
93
95
|
turns: (n) => `${n} 轮`,
|
|
96
|
+
context: (percent) => `ctx ${percent}`,
|
|
94
97
|
bar: {
|
|
95
98
|
more: (n) => `另 ${n} 个`,
|
|
96
99
|
keys: (up, down) => `${up}${down} 选择 · Enter 打开 · Esc 返回`,
|
|
@@ -56,6 +56,7 @@ export declare const en: {
|
|
|
56
56
|
"ui.animation": string;
|
|
57
57
|
"ui.restoreOnCancel": string;
|
|
58
58
|
"ui.enterWhileRunning": string;
|
|
59
|
+
"ui.programStatus": string;
|
|
59
60
|
"ui.statusLine": string;
|
|
60
61
|
"ui.language": string;
|
|
61
62
|
"ui.replyLanguage": string;
|
|
@@ -204,6 +205,7 @@ export declare const zh: {
|
|
|
204
205
|
"ui.animation": string;
|
|
205
206
|
"ui.restoreOnCancel": string;
|
|
206
207
|
"ui.enterWhileRunning": string;
|
|
208
|
+
"ui.programStatus": string;
|
|
207
209
|
"ui.statusLine": string;
|
|
208
210
|
"ui.language": string;
|
|
209
211
|
"ui.replyLanguage": string;
|
|
@@ -103,6 +103,7 @@ const keysEn = {
|
|
|
103
103
|
"ui.animation": "false: the spinner stays still while running and redraws only when seconds change; the startup logo does not animate",
|
|
104
104
|
"ui.restoreOnCancel": "When Esc interrupts a run before any reply or tool call in this turn, withdraw the turn and put the message back into the input box",
|
|
105
105
|
"ui.enterWhileRunning": "Enter while a run is in progress: queue (default) queues the message as a steer delivered at the next delivery point; interrupt stops the current turn and sends it at once. The app.message.interrupt key (default Ctrl+X) does the other one",
|
|
106
|
+
"ui.programStatus": "Report run state to the terminal with OSC 7501 (idle / working / blocked / done / error): auto (default) sends only after the terminal answers the detection query (off inside tmux), on sends without detecting (inside tmux wrapped in passthrough; needs allow-passthrough on), off never sends. Interactive TUI only",
|
|
106
107
|
"ui.statusLine": "Bottom info line: full shows two lines (rate line + status line), compact one; toggle at runtime with Ctrl+G or /statusline (effective from wave 5 W5-A)",
|
|
107
108
|
"ui.language": "Interface language: auto decides from LC_ALL / LC_MESSAGES / LANG (zh* is Chinese, anything else English), zh, en; AMA_LANG and --lang override; affects the interface only, text sent to the model is always English",
|
|
108
109
|
"ui.replyLanguage": "Reply language for the model (e.g. Chinese): one English rule is appended to the end of the rules section at session start; zero-byte change when unset; user level only (effective from wave 6)",
|
|
@@ -226,6 +227,7 @@ const keysZh = {
|
|
|
226
227
|
"ui.animation": "false:运行中 spinner 静止,只在秒数变化时重绘;启动字符画不播放动画",
|
|
227
228
|
"ui.restoreOnCancel": "运行中 Esc 中断、本回合还没有任何回复或工具调用时,撤回该回合并把原消息放回输入框",
|
|
228
229
|
"ui.enterWhileRunning": "运行中按 Enter:queue(缺省)排队插话,等下一个投递点送达;interrupt 打断当前回合并立即发送。app.message.interrupt 键(缺省 Ctrl+X)做另一种",
|
|
230
|
+
"ui.programStatus": "用 OSC 7501 把运行状态报告给终端(idle / working / blocked / done / error):auto(缺省)终端回应检测查询后才发(tmux 里不发),on 不检测直接发(tmux 里经 passthrough 包裹,需要 allow-passthrough on),off 不发。只用于交互界面",
|
|
229
231
|
"ui.statusLine": "底部信息行:full 两行(速率行 + 状态行),compact 一行;运行时 Ctrl+G 或 /statusline 切换(第五波 W5-A 起生效)",
|
|
230
232
|
"ui.language": "界面语言:auto 按 LC_ALL / LC_MESSAGES / LANG 判断(zh* 为中文,其余英文),zh,en;AMA_LANG、--lang 覆盖;只影响界面,发给模型的文本固定英文",
|
|
231
233
|
"ui.replyLanguage": "模型回复语言(如 Chinese):会话开始在 rules 节末尾追加一句英文规则;不设时零字节变化;只认用户级(第六波起生效)",
|
|
@@ -56,6 +56,7 @@ export declare const en: {
|
|
|
56
56
|
"ui.animation": string;
|
|
57
57
|
"ui.restoreOnCancel": string;
|
|
58
58
|
"ui.enterWhileRunning": string;
|
|
59
|
+
"ui.programStatus": string;
|
|
59
60
|
"ui.statusLine": string;
|
|
60
61
|
"ui.language": string;
|
|
61
62
|
"ui.replyLanguage": string;
|
|
@@ -316,6 +317,7 @@ export declare const zh: {
|
|
|
316
317
|
"ui.animation": string;
|
|
317
318
|
"ui.restoreOnCancel": string;
|
|
318
319
|
"ui.enterWhileRunning": string;
|
|
320
|
+
"ui.programStatus": string;
|
|
319
321
|
"ui.statusLine": string;
|
|
320
322
|
"ui.language": string;
|
|
321
323
|
"ui.replyLanguage": string;
|