@armadra/agent 0.7.2 → 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 CHANGED
@@ -5,6 +5,10 @@ English · [简体中文](CHANGELOG.zh-CN.md)
5
5
  > This file is in English starting with 0.6.0. Release notes for 0.1 through 0.5.1 are in Chinese in
6
6
  > [CHANGELOG.zh-CN.md](CHANGELOG.zh-CN.md). New entries go into both files.
7
7
 
8
+ ## 0.7.3 (2026-10-09)
9
+
10
+ - **`/context` before the first request**: it showed `0 / <window>` and left the system prompt and tool declarations out while the status bar already showed the baseline; it now estimates both from what the first request will send (shared with the status-bar baseline), and "Used" always matches the status bar and `getStats()`.
11
+
8
12
  ## 0.7.2 (2026-10-09)
9
13
 
10
14
  - **Prompt cache survives mid-session context changes**: when AGENTS.md, Skills or SessionStart hook output changed on
@@ -4,6 +4,10 @@
4
4
 
5
5
  > 从 0.6.0 起 [CHANGELOG.md](CHANGELOG.md) 为英文,本文件保留中文记录(0.1–0.5.1 的完整历史在此)。新条目两份都要加。
6
6
 
7
+ ## 0.7.3(2026-10-09)
8
+
9
+ - **首次请求前的 `/context`**:状态栏已显示启动基线时,它仍是 `0 / <窗口>`、不计系统提示与工具声明;现在按首次请求将要发送的内容估算这两类(与状态栏基线共用同一份装配),「已用」始终与状态栏和 `getStats()` 一致。
10
+
7
11
  ## 0.7.2(2026-10-09)
8
12
 
9
13
  - **会话中途上下文变化不再打断提示缓存**:resume 时 AGENTS.md、Skills 或 SessionStart Hook 输出变了、宿主 instructions 刷新、压缩后记忆节
@@ -21,6 +21,7 @@
21
21
  */
22
22
  import { CompactionBreaker } from "../compaction/breaker.js";
23
23
  import { type ContextEstimate } from "../compaction/estimate.js";
24
+ import type { SystemMessage } from "../ai/types.js";
24
25
  import { type PrunePolicy } from "../compaction/prune-tier.js";
25
26
  import type { BranchSummaryEntry } from "../session/types.js";
26
27
  import type { SessionCore } from "./session-core.js";
@@ -88,3 +89,8 @@ export declare class CompactionController {
88
89
  leaveBranch(targetId: string | null, instructions: string | undefined, signal: AbortSignal): Promise<BranchSummaryEntry | undefined>;
89
90
  private summarize;
90
91
  }
92
+ /**
93
+ * 首次请求前将要发送的首条 `system` 消息(系统提示 + 工具声明,与真正发送时同样装配)。只用于统计:
94
+ * `getStats` 的启动基线与 `/context` 的分类明细共用它,口径一致;不写分支、不改请求体。
95
+ */
96
+ export declare function pendingPrefixMessage(core: SessionCore): SystemMessage;
@@ -22,7 +22,7 @@
22
22
  import { AmaError } from "../errors.js";
23
23
  import { CompactionBreaker } from "../compaction/breaker.js";
24
24
  import { estimateMessageTokens, estimateProjectedTokens, } from "../compaction/estimate.js";
25
- import { estimatePrefixTokens, withPrefixBaseline } from "../compaction/prefix-estimate.js";
25
+ import { prefixSystemMessage, withPrefixBaseline } from "../compaction/prefix-estimate.js";
26
26
  import { planPrune, prunePolicy } from "../compaction/prune-tier.js";
27
27
  import { createProtection, skillLocations } from "../compaction/protect.js";
28
28
  import { buildPostCompactBlock } from "../compaction/post-compact.js";
@@ -96,14 +96,7 @@ export class CompactionController {
96
96
  }
97
97
  /** 将要发送的系统提示 + 工具声明的估算(与首条 system 消息同样装配,不落盘)。 */
98
98
  prefixTokens() {
99
- const core = this.core;
100
- const tools = core
101
- .activeToolNames()
102
- .map((name) => core.activeTool(name))
103
- .filter((tool) => tool !== undefined);
104
- const system = core.childBase?.().system ?? core.options.system ?? {};
105
- const sections = definedSections(assembleSections({ ...system, tools, cwd: core.cwd }));
106
- return estimatePrefixTokens(sections, toolDecls(tools));
99
+ return estimateMessageTokens(pendingPrefixMessage(this.core));
107
100
  }
108
101
  budget() {
109
102
  const window = this.core.model().contextWindow;
@@ -411,3 +404,16 @@ export class CompactionController {
411
404
  }
412
405
  }
413
406
  }
407
+ /**
408
+ * 首次请求前将要发送的首条 `system` 消息(系统提示 + 工具声明,与真正发送时同样装配)。只用于统计:
409
+ * `getStats` 的启动基线与 `/context` 的分类明细共用它,口径一致;不写分支、不改请求体。
410
+ */
411
+ export function pendingPrefixMessage(core) {
412
+ const tools = core
413
+ .activeToolNames()
414
+ .map((name) => core.activeTool(name))
415
+ .filter((tool) => tool !== undefined);
416
+ const system = core.childBase?.().system ?? core.options.system ?? {};
417
+ const sections = definedSections(assembleSections({ ...system, tools, cwd: core.cwd }));
418
+ return prefixSystemMessage(sections, toolDecls(tools));
419
+ }
@@ -41,7 +41,7 @@ var AMA_VERSION;
41
41
  var init_version = __esm({
42
42
  "src/version.ts"() {
43
43
  "use strict";
44
- AMA_VERSION = true ? "0.7.2" : readPackageVersion();
44
+ AMA_VERSION = true ? "0.7.3" : readPackageVersion();
45
45
  }
46
46
  });
47
47
 
@@ -2992,6 +2992,7 @@ var init_report_context = __esm({
2992
2992
  keySource: "Source",
2993
2993
  sourceUsage: (usage, estimate) => `reported usage ${usage} + estimated ${estimate}`,
2994
2994
  sourceEstimate: "estimated in full (no usable usage yet)",
2995
+ sourcePrefix: "system prompt and tools only, estimated before the first request",
2995
2996
  keyAutoCompact: "Auto-compact",
2996
2997
  autoCompact: (at, distance2) => `at ${at}, ≈ ${distance2} to go`,
2997
2998
  autoCompactReached: (at) => `at ${at}, reached`,
@@ -3002,6 +3003,7 @@ var init_report_context = __esm({
3002
3003
  breakdown: (total) => `By category (estimated, ${total})`,
3003
3004
  prefixCounted: "System prompt and tool declarations are counted from the stored system messages; the full estimate can differ from reported usage.",
3004
3005
  prefixPending: "System prompt and tool declarations are not counted until the first request.",
3006
+ prefixEstimated: "System prompt and tool declarations are estimated from what the first request will send.",
3005
3007
  categories: {
3006
3008
  system: "System prompt",
3007
3009
  tools: "Tool declarations",
@@ -3036,6 +3038,7 @@ var init_report_context = __esm({
3036
3038
  keySource: "来源",
3037
3039
  sourceUsage: (usage, estimate) => `usage 实测 ${usage} + 估算 ${estimate}`,
3038
3040
  sourceEstimate: "全量估算(还没有可用的 usage)",
3041
+ sourcePrefix: "首次请求前,只有系统提示与工具声明的估算",
3039
3042
  keyAutoCompact: "自动压缩",
3040
3043
  autoCompact: (at, distance2) => `${at} 触发,还差 ≈ ${distance2}`,
3041
3044
  autoCompactReached: (at) => `${at} 触发,已达到`,
@@ -3046,6 +3049,7 @@ var init_report_context = __esm({
3046
3049
  breakdown: (total) => `按类别(估算,${total})`,
3047
3050
  prefixCounted: "系统提示与工具声明按会话里存的 system 消息计入;全量估算可能与 usage 实测不同。",
3048
3051
  prefixPending: "系统提示与工具声明在首次请求后才计入。",
3052
+ prefixEstimated: "系统提示与工具声明按首次请求将要发送的内容估算。",
3049
3053
  categories: {
3050
3054
  system: "系统提示",
3051
3055
  tools: "工具声明",
@@ -22051,10 +22055,10 @@ var init_breaker = __esm({
22051
22055
  });
22052
22056
 
22053
22057
  // src/compaction/prefix-estimate.ts
22054
- function estimatePrefixTokens(sections, tools) {
22058
+ function prefixSystemMessage(sections, tools) {
22055
22059
  const message = { role: "system", sections: { ...sections }, timestamp: 0 };
22056
22060
  if (tools.length > 0) message.toolsAdded = [...tools];
22057
- return estimateMessageTokens(message);
22061
+ return message;
22058
22062
  }
22059
22063
  function withPrefixBaseline(estimate, hasSystemMessage, prefix) {
22060
22064
  if (estimate.usageTokens > 0) return { ...estimate, source: "usage" };
@@ -22070,7 +22074,6 @@ function withPrefixBaseline(estimate, hasSystemMessage, prefix) {
22070
22074
  var init_prefix_estimate = __esm({
22071
22075
  "src/compaction/prefix-estimate.ts"() {
22072
22076
  "use strict";
22073
- init_estimate();
22074
22077
  }
22075
22078
  });
22076
22079
 
@@ -22403,6 +22406,12 @@ var init_system_prompt = __esm({
22403
22406
  });
22404
22407
 
22405
22408
  // src/agent/session-compaction.ts
22409
+ function pendingPrefixMessage(core) {
22410
+ const tools = core.activeToolNames().map((name) => core.activeTool(name)).filter((tool) => tool !== void 0);
22411
+ const system = core.childBase?.().system ?? core.options.system ?? {};
22412
+ const sections = definedSections(assembleSections({ ...system, tools, cwd: core.cwd }));
22413
+ return prefixSystemMessage(sections, toolDecls(tools));
22414
+ }
22406
22415
  var DEFAULT_COMPACTION_SETTINGS, KEEP_RECENT_MAX_RATIO, HOOK_CONTEXT_CUSTOM_TYPE, CompactionController;
22407
22416
  var init_session_compaction = __esm({
22408
22417
  "src/agent/session-compaction.ts"() {
@@ -22484,11 +22493,7 @@ var init_session_compaction = __esm({
22484
22493
  }
22485
22494
  /** 将要发送的系统提示 + 工具声明的估算(与首条 system 消息同样装配,不落盘)。 */
22486
22495
  prefixTokens() {
22487
- const core = this.core;
22488
- const tools = core.activeToolNames().map((name) => core.activeTool(name)).filter((tool) => tool !== void 0);
22489
- const system = core.childBase?.().system ?? core.options.system ?? {};
22490
- const sections = definedSections(assembleSections({ ...system, tools, cwd: core.cwd }));
22491
- return estimatePrefixTokens(sections, toolDecls(tools));
22496
+ return estimateMessageTokens(pendingPrefixMessage(this.core));
22492
22497
  }
22493
22498
  budget() {
22494
22499
  const window2 = this.core.model().contextWindow;
@@ -52549,20 +52554,28 @@ function contextSnapshot(session) {
52549
52554
  let messages = session.messages;
52550
52555
  let estimate;
52551
52556
  let compaction = {};
52557
+ let prefixPending = false;
52552
52558
  if (session instanceof AgentSessionImpl) {
52553
52559
  const branch = session.manager.branch();
52554
52560
  const items = buildProjection(branch).items;
52555
52561
  messages = items.map((item) => item.message);
52556
52562
  estimate = estimateProjectedTokens(items, branch);
52557
52563
  compaction = session.options.compaction ?? {};
52564
+ if (!messages.some((message) => "role" in message && message.role === "system")) {
52565
+ prefixPending = true;
52566
+ messages = [pendingPrefixMessage(session), ...messages];
52567
+ }
52558
52568
  } else estimate = estimateContextTokens(messages);
52559
- const window2 = session.getStats().contextWindow;
52569
+ const stats = session.getStats();
52570
+ if (stats.contextTokens !== void 0) estimate = { ...estimate, tokens: stats.contextTokens };
52571
+ const window2 = stats.contextWindow;
52560
52572
  const reserve = compaction.reserveTokens ?? DEFAULT_COMPACTION_SETTINGS.reserveTokens;
52561
52573
  const budget = window2 === void 0 ? void 0 : Math.max(0, window2 - reserve);
52562
52574
  const percent3 = window2 === void 0 || window2 <= 0 ? void 0 : Math.min(100, Math.round(estimate.tokens / window2 * 1e3) / 10);
52563
52575
  return {
52564
52576
  breakdown: breakdownMessages(messages),
52565
52577
  estimate,
52578
+ prefixPending,
52566
52579
  window: window2,
52567
52580
  percent: percent3,
52568
52581
  autoCompaction: session.state.autoCompaction,
@@ -52590,7 +52603,7 @@ function headerRows(snap) {
52590
52603
  });
52591
52604
  rows.push({
52592
52605
  key: m.keySource,
52593
- value: estimate.lastUsageIndex === null ? m.sourceEstimate : m.sourceUsage(
52606
+ value: snap.prefixPending ? m.sourcePrefix : estimate.lastUsageIndex === null ? m.sourceEstimate : m.sourceUsage(
52594
52607
  formatTokenCount(estimate.usageTokens),
52595
52608
  formatTokenCount(estimate.trailingTokens)
52596
52609
  )
@@ -52648,7 +52661,9 @@ function describeContext(session) {
52648
52661
  const { breakdown } = snap;
52649
52662
  const lines2 = [m.title, ...renderRows(headerRows(snap), " "), ""];
52650
52663
  lines2.push(m.breakdown(formatTokenCount(breakdown.total)));
52651
- lines2.push(` ${breakdown.hasSystem ? m.prefixCounted : m.prefixPending}`);
52664
+ lines2.push(
52665
+ ` ${snap.prefixPending ? m.prefixEstimated : breakdown.hasSystem ? m.prefixCounted : m.prefixPending}`
52666
+ );
52652
52667
  if (breakdown.total === 0) lines2.push(` ${m.empty}`);
52653
52668
  else lines2.push(...renderRows(categoryRows(breakdown, void 0), " "));
52654
52669
  lines2.push("", m.largest(CONTEXT_TOP_RESULTS));
@@ -52669,7 +52684,12 @@ function contextPanel(session, theme) {
52669
52684
  theme.bold(m.breakdown(formatTokenCount(breakdown.total))),
52670
52685
  new Indent(
52671
52686
  new Stack([
52672
- new Text(theme.fg("dim", breakdown.hasSystem ? m.prefixCounted : m.prefixPending)),
52687
+ new Text(
52688
+ theme.fg(
52689
+ "dim",
52690
+ snap.prefixPending ? m.prefixEstimated : breakdown.hasSystem ? m.prefixCounted : m.prefixPending
52691
+ )
52692
+ ),
52673
52693
  breakdown.total === 0 ? theme.fg("dim", m.empty) : table2(categoryRows(breakdown, theme), true)
52674
52694
  ]),
52675
52695
  2
@@ -7,8 +7,10 @@
7
7
  *
8
8
  * 只用于统计:不写分支、不改请求体,前缀缓存不受影响;压缩阈值也不看它(摘要压不掉这部分)。
9
9
  */
10
- import type { ToolDecl } from "../ai/types.js";
10
+ import type { SystemMessage, ToolDecl } from "../ai/types.js";
11
11
  import type { ContextEstimate } from "./estimate.js";
12
+ /** 将要作为首条 `system` 消息发送的内容(只用于统计,不落盘)。 */
13
+ export declare function prefixSystemMessage(sections: Readonly<Record<string, string>>, tools: readonly ToolDecl[]): SystemMessage;
12
14
  /** 系统提示(已装配的命名节)+ 工具声明的估算。 */
13
15
  export declare function estimatePrefixTokens(sections: Readonly<Record<string, string>>, tools: readonly ToolDecl[]): number;
14
16
  /** 上下文估算的来源(`SessionStats.context.source`)。 */
@@ -8,12 +8,16 @@
8
8
  * 只用于统计:不写分支、不改请求体,前缀缓存不受影响;压缩阈值也不看它(摘要压不掉这部分)。
9
9
  */
10
10
  import { estimateMessageTokens } from "./estimate.js";
11
- /** 系统提示(已装配的命名节)+ 工具声明的估算。 */
12
- export function estimatePrefixTokens(sections, tools) {
11
+ /** 将要作为首条 `system` 消息发送的内容(只用于统计,不落盘)。 */
12
+ export function prefixSystemMessage(sections, tools) {
13
13
  const message = { role: "system", sections: { ...sections }, timestamp: 0 };
14
14
  if (tools.length > 0)
15
15
  message.toolsAdded = [...tools];
16
- return estimateMessageTokens(message);
16
+ return message;
17
+ }
18
+ /** 系统提示(已装配的命名节)+ 工具声明的估算。 */
19
+ export function estimatePrefixTokens(sections, tools) {
20
+ return estimateMessageTokens(prefixSystemMessage(sections, tools));
17
21
  }
18
22
  /**
19
23
  * 给投影估算标来源;投影里还没有系统消息时把前缀基线加进去(`prefix` 懒算,只在需要时调用)。
@@ -1152,6 +1152,7 @@ declare const en: {
1152
1152
  keySource: string;
1153
1153
  sourceUsage: (usage: string, estimate: string) => string;
1154
1154
  sourceEstimate: string;
1155
+ sourcePrefix: string;
1155
1156
  keyAutoCompact: string;
1156
1157
  autoCompact: (at: string, distance: string) => string;
1157
1158
  autoCompactReached: (at: string) => string;
@@ -1162,6 +1163,7 @@ declare const en: {
1162
1163
  breakdown: (total: string) => string;
1163
1164
  prefixCounted: string;
1164
1165
  prefixPending: string;
1166
+ prefixEstimated: string;
1165
1167
  categories: {
1166
1168
  system: string;
1167
1169
  tools: string;
@@ -13,6 +13,7 @@ export declare const en: {
13
13
  keySource: string;
14
14
  sourceUsage: (usage: string, estimate: string) => string;
15
15
  sourceEstimate: string;
16
+ sourcePrefix: string;
16
17
  keyAutoCompact: string;
17
18
  autoCompact: (at: string, distance: string) => string;
18
19
  autoCompactReached: (at: string) => string;
@@ -23,6 +24,7 @@ export declare const en: {
23
24
  breakdown: (total: string) => string;
24
25
  prefixCounted: string;
25
26
  prefixPending: string;
27
+ prefixEstimated: string;
26
28
  categories: {
27
29
  system: string;
28
30
  tools: string;
@@ -57,6 +59,7 @@ export declare const zh: {
57
59
  keySource: string;
58
60
  sourceUsage: (usage: string, estimate: string) => string;
59
61
  sourceEstimate: string;
62
+ sourcePrefix: string;
60
63
  keyAutoCompact: string;
61
64
  autoCompact: (at: string, distance: string) => string;
62
65
  autoCompactReached: (at: string) => string;
@@ -67,6 +70,7 @@ export declare const zh: {
67
70
  breakdown: (total: string) => string;
68
71
  prefixCounted: string;
69
72
  prefixPending: string;
73
+ prefixEstimated: string;
70
74
  categories: {
71
75
  system: string;
72
76
  tools: string;
@@ -14,6 +14,7 @@ export const en = {
14
14
  keySource: "Source",
15
15
  sourceUsage: (usage, estimate) => `reported usage ${usage} + estimated ${estimate}`,
16
16
  sourceEstimate: "estimated in full (no usable usage yet)",
17
+ sourcePrefix: "system prompt and tools only, estimated before the first request",
17
18
  keyAutoCompact: "Auto-compact",
18
19
  autoCompact: (at, distance) => `at ${at}, ≈ ${distance} to go`,
19
20
  autoCompactReached: (at) => `at ${at}, reached`,
@@ -24,6 +25,7 @@ export const en = {
24
25
  breakdown: (total) => `By category (estimated, ${total})`,
25
26
  prefixCounted: "System prompt and tool declarations are counted from the stored system messages; the full estimate can differ from reported usage.",
26
27
  prefixPending: "System prompt and tool declarations are not counted until the first request.",
28
+ prefixEstimated: "System prompt and tool declarations are estimated from what the first request will send.",
27
29
  categories: {
28
30
  system: "System prompt",
29
31
  tools: "Tool declarations",
@@ -58,6 +60,7 @@ export const zh = {
58
60
  keySource: "来源",
59
61
  sourceUsage: (usage, estimate) => `usage 实测 ${usage} + 估算 ${estimate}`,
60
62
  sourceEstimate: "全量估算(还没有可用的 usage)",
63
+ sourcePrefix: "首次请求前,只有系统提示与工具声明的估算",
61
64
  keyAutoCompact: "自动压缩",
62
65
  autoCompact: (at, distance) => `${at} 触发,还差 ≈ ${distance}`,
63
66
  autoCompactReached: (at) => `${at} 触发,已达到`,
@@ -68,6 +71,7 @@ export const zh = {
68
71
  breakdown: (total) => `按类别(估算,${total})`,
69
72
  prefixCounted: "系统提示与工具声明按会话里存的 system 消息计入;全量估算可能与 usage 实测不同。",
70
73
  prefixPending: "系统提示与工具声明在首次请求后才计入。",
74
+ prefixEstimated: "系统提示与工具声明按首次请求将要发送的内容估算。",
71
75
  categories: {
72
76
  system: "系统提示",
73
77
  tools: "工具声明",
@@ -97,6 +97,7 @@ export declare const en: {
97
97
  keySource: string;
98
98
  sourceUsage: (usage: string, estimate: string) => string;
99
99
  sourceEstimate: string;
100
+ sourcePrefix: string;
100
101
  keyAutoCompact: string;
101
102
  autoCompact: (at: string, distance: string) => string;
102
103
  autoCompactReached: (at: string) => string;
@@ -107,6 +108,7 @@ export declare const en: {
107
108
  breakdown: (total: string) => string;
108
109
  prefixCounted: string;
109
110
  prefixPending: string;
111
+ prefixEstimated: string;
110
112
  categories: {
111
113
  system: string;
112
114
  tools: string;
@@ -339,6 +341,7 @@ export declare const zh: {
339
341
  keySource: string;
340
342
  sourceUsage: (usage: string, estimate: string) => string;
341
343
  sourceEstimate: string;
344
+ sourcePrefix: string;
342
345
  keyAutoCompact: string;
343
346
  autoCompact: (at: string, distance: string) => string;
344
347
  autoCompactReached: (at: string) => string;
@@ -349,6 +352,7 @@ export declare const zh: {
349
352
  breakdown: (total: string) => string;
350
353
  prefixCounted: string;
351
354
  prefixPending: string;
355
+ prefixEstimated: string;
352
356
  categories: {
353
357
  system: string;
354
358
  tools: string;
@@ -18,6 +18,8 @@ export declare const CONTEXT_TOP_RESULTS = 5;
18
18
  export interface ContextSnapshot {
19
19
  breakdown: ContextBreakdown;
20
20
  estimate: ContextEstimate;
21
+ /** 首次请求前:系统提示与工具声明按将要发送的那一份估算(与状态栏的启动基线同一口径)。 */
22
+ prefixPending: boolean;
21
23
  window: number | undefined;
22
24
  /** 0–100(一位小数,封顶 100);无窗口时 undefined。 */
23
25
  percent: number | undefined;
@@ -10,7 +10,7 @@
10
10
  * 统计只读:取活动分支的投影(与 `getStats().contextTokens` 同一口径),不改请求体、不写条目。
11
11
  */
12
12
  import { AgentSessionImpl } from "../agent/session.js";
13
- import { DEFAULT_COMPACTION_SETTINGS } from "../agent/session-compaction.js";
13
+ import { DEFAULT_COMPACTION_SETTINGS, pendingPrefixMessage } from "../agent/session-compaction.js";
14
14
  import { breakdownMessages, largestToolResults, } from "../compaction/breakdown.js";
15
15
  import { estimateContextTokens, estimateProjectedTokens, } from "../compaction/estimate.js";
16
16
  import { prunePolicy } from "../compaction/prune-tier.js";
@@ -28,16 +28,25 @@ export function contextSnapshot(session) {
28
28
  let messages = session.messages;
29
29
  let estimate;
30
30
  let compaction = {};
31
+ let prefixPending = false;
31
32
  if (session instanceof AgentSessionImpl) {
32
33
  const branch = session.manager.branch();
33
34
  const items = buildProjection(branch).items;
34
35
  messages = items.map((item) => item.message);
35
36
  estimate = estimateProjectedTokens(items, branch);
36
37
  compaction = (session.options.compaction ?? {});
38
+ if (!messages.some((message) => "role" in message && message.role === "system")) {
39
+ prefixPending = true;
40
+ messages = [pendingPrefixMessage(session), ...messages];
41
+ }
37
42
  }
38
43
  else
39
44
  estimate = estimateContextTokens(messages);
40
- const window = session.getStats().contextWindow;
45
+ const stats = session.getStats();
46
+ // 「已用」与状态栏、getStats 同一口径(含启动基线)
47
+ if (stats.contextTokens !== undefined)
48
+ estimate = { ...estimate, tokens: stats.contextTokens };
49
+ const window = stats.contextWindow;
41
50
  const reserve = compaction.reserveTokens ?? DEFAULT_COMPACTION_SETTINGS.reserveTokens;
42
51
  const budget = window === undefined ? undefined : Math.max(0, window - reserve);
43
52
  const percent = window === undefined || window <= 0
@@ -46,6 +55,7 @@ export function contextSnapshot(session) {
46
55
  return {
47
56
  breakdown: breakdownMessages(messages),
48
57
  estimate,
58
+ prefixPending,
49
59
  window,
50
60
  percent,
51
61
  autoCompaction: session.state.autoCompaction,
@@ -69,9 +79,11 @@ function headerRows(snap) {
69
79
  });
70
80
  rows.push({
71
81
  key: m.keySource,
72
- value: estimate.lastUsageIndex === null
73
- ? m.sourceEstimate
74
- : m.sourceUsage(formatTokenCount(estimate.usageTokens), formatTokenCount(estimate.trailingTokens)),
82
+ value: snap.prefixPending
83
+ ? m.sourcePrefix
84
+ : estimate.lastUsageIndex === null
85
+ ? m.sourceEstimate
86
+ : m.sourceUsage(formatTokenCount(estimate.usageTokens), formatTokenCount(estimate.trailingTokens)),
75
87
  });
76
88
  let auto;
77
89
  if (!snap.autoCompaction)
@@ -132,7 +144,7 @@ export function describeContext(session) {
132
144
  const { breakdown } = snap;
133
145
  const lines = [m.title, ...renderRows(headerRows(snap), " "), ""];
134
146
  lines.push(m.breakdown(formatTokenCount(breakdown.total)));
135
- lines.push(` ${breakdown.hasSystem ? m.prefixCounted : m.prefixPending}`);
147
+ lines.push(` ${snap.prefixPending ? m.prefixEstimated : breakdown.hasSystem ? m.prefixCounted : m.prefixPending}`);
136
148
  if (breakdown.total === 0)
137
149
  lines.push(` ${m.empty}`);
138
150
  else
@@ -157,7 +169,11 @@ export function contextPanel(session, theme) {
157
169
  "",
158
170
  theme.bold(m.breakdown(formatTokenCount(breakdown.total))),
159
171
  new Indent(new Stack([
160
- new Text(theme.fg("dim", breakdown.hasSystem ? m.prefixCounted : m.prefixPending)),
172
+ new Text(theme.fg("dim", snap.prefixPending
173
+ ? m.prefixEstimated
174
+ : breakdown.hasSystem
175
+ ? m.prefixCounted
176
+ : m.prefixPending)),
161
177
  breakdown.total === 0
162
178
  ? theme.fg("dim", m.empty)
163
179
  : table(categoryRows(breakdown, theme), true),
package/docs/en/tui.md CHANGED
@@ -249,7 +249,7 @@ Design in [rewind-plan.md](../rewind-plan.md) (Chinese). Every user message that
249
249
 
250
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
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, both categories are 0 and the panel says they are counted after the first request.
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
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
254
  - Read-only: no session entries are written and the request body is untouched, so the prompt-cache prefix is unaffected.
255
255
 
package/docs/tui.md CHANGED
@@ -243,7 +243,7 @@ line 模式(`--no-tui`)运行中输入 `/interrupt <文本>` 效果相同(
243
243
 
244
244
  - **顶部**:已用 / 窗口与百分比(与 `/session` 同一口径:最后一条可用 usage + 其后消息的估算)、剩余、来源(「usage 实测 X + 估算 Y」;还没有可用 usage,或 usage 之后发生过压缩 / 上下文编辑时为「全量估算」)、自动压缩触发点(窗口 − `compaction.reserveTokens`,关闭时显示「关闭」,没有窗口时「窗口未知」)与档一裁剪的起点。
245
245
  - **按类别**:系统提示(按节)、工具声明(按工具)、用户消息、助手文本、推理、工具调用参数(按工具)、工具结果(按工具名汇总)、附件图片(每张按 1 600 计)、摘要(压缩 / 分支)、自定义消息;每类给 token 估算、占比与条形。各类之和等于整个投影的字符估算(CJK 每字 1,其余 / 4),所以标题里的合计可能与顶部的「已用」不同——后者优先采信模型回报的 usage。
246
- - **系统提示与工具声明**来自会话里存的 system 消息(首条全量、之后的补丁也计入,与估算口径一致);首次请求前分支上还没有 system 消息,这两类为 0,面板注明「首次请求后才计入」。
246
+ - **系统提示与工具声明**来自会话里存的 system 消息(首条全量、之后的补丁也计入,与估算口径一致);首次请求前分支上还没有 system 消息,这两类按首次请求将要发送的内容估算(与状态栏的启动基线同一口径),「已用」与状态栏一致,来源标为「首次请求前,只有系统提示与工具声明的估算」。
247
247
  - **底部**只列最大的 5 个工具结果的序号(投影里第几个工具结果)、工具名与大小,**不显示正文**。
248
248
  - 统计只读:不写会话条目、不改请求体,提示词缓存前缀不受影响。
249
249
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@armadra/agent",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "description": "A coding and coordination agent that runs standalone or embedded in Armadra",
5
5
  "type": "module",
6
6
  "keywords": [