@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.
Files changed (97) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/CHANGELOG.zh-CN.md +34 -0
  3. package/dist/agent/session-cache.d.ts +4 -2
  4. package/dist/agent/session-cache.js +5 -3
  5. package/dist/agent/session-compaction.d.ts +13 -1
  6. package/dist/agent/session-compaction.js +36 -0
  7. package/dist/agent/session-core.d.ts +2 -0
  8. package/dist/agent/session-state.d.ts +9 -3
  9. package/dist/agent/session-state.js +8 -6
  10. package/dist/agent/session.js +3 -3
  11. package/dist/agent/types-w5.d.ts +3 -0
  12. package/dist/agent/types.d.ts +18 -0
  13. package/dist/agents/task-record.d.ts +4 -0
  14. package/dist/agents/task-record.js +18 -0
  15. package/dist/ai/context.d.ts +7 -2
  16. package/dist/ai/context.js +45 -7
  17. package/dist/ai/providers/catalog-data.js +1 -1
  18. package/dist/bundle/ama.cjs +1643 -486
  19. package/dist/compaction/breakdown.d.ts +51 -0
  20. package/dist/compaction/breakdown.js +159 -0
  21. package/dist/compaction/estimate.d.ts +6 -0
  22. package/dist/compaction/estimate.js +1 -1
  23. package/dist/compaction/prefix-estimate.d.ts +23 -0
  24. package/dist/compaction/prefix-estimate.js +34 -0
  25. package/dist/config/json-schema.js +1 -0
  26. package/dist/config/key-docs.js +1 -0
  27. package/dist/config/schema.js +2 -0
  28. package/dist/config/settings-registry.js +1 -0
  29. package/dist/config/types.d.ts +2 -0
  30. package/dist/drivers/native/codex-app-server.js +6 -0
  31. package/dist/drivers/runner.js +17 -0
  32. package/dist/drivers/store.d.ts +3 -0
  33. package/dist/drivers/store.js +4 -0
  34. package/dist/i18n/catalog.d.ts +53 -0
  35. package/dist/i18n/messages/agents.d.ts +3 -0
  36. package/dist/i18n/messages/agents.js +3 -0
  37. package/dist/i18n/messages/config-keys.d.ts +2 -0
  38. package/dist/i18n/messages/config-keys.js +2 -0
  39. package/dist/i18n/messages/config.d.ts +2 -0
  40. package/dist/i18n/messages/interactive.d.ts +6 -0
  41. package/dist/i18n/messages/interactive.js +6 -0
  42. package/dist/i18n/messages/panels.d.ts +3 -0
  43. package/dist/i18n/messages/panels.js +5 -2
  44. package/dist/i18n/messages/report-context.d.ts +94 -0
  45. package/dist/i18n/messages/report-context.js +95 -0
  46. package/dist/i18n/messages/report.d.ts +96 -0
  47. package/dist/i18n/messages/report.js +13 -2
  48. package/dist/i18n/messages/settings.d.ts +2 -0
  49. package/dist/i18n/messages/settings.js +2 -0
  50. package/dist/i18n/messages/subcommands.js +2 -2
  51. package/dist/modes/acp/acp-events.d.ts +17 -4
  52. package/dist/modes/acp/acp-events.js +32 -6
  53. package/dist/modes/commands-core.js +4 -0
  54. package/dist/modes/context-report.d.ts +35 -0
  55. package/dist/modes/context-report.js +172 -0
  56. package/dist/modes/interactive/agent-bar.d.ts +2 -0
  57. package/dist/modes/interactive/agent-bar.js +6 -0
  58. package/dist/modes/interactive/commands.d.ts +2 -1
  59. package/dist/modes/interactive/commands.js +10 -1
  60. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  61. package/dist/modes/interactive/interactive-mode.js +6 -1
  62. package/dist/modes/interactive/panels.d.ts +2 -1
  63. package/dist/modes/interactive/panels.js +13 -5
  64. package/dist/modes/interactive/program-status-ui.d.ts +72 -0
  65. package/dist/modes/interactive/program-status-ui.js +229 -0
  66. package/dist/modes/interactive/session-events.d.ts +4 -0
  67. package/dist/modes/interactive/session-events.js +24 -2
  68. package/dist/modes/interactive/status-bar.d.ts +21 -3
  69. package/dist/modes/interactive/status-bar.js +64 -25
  70. package/dist/modes/interactive/status-ctx.d.ts +55 -0
  71. package/dist/modes/interactive/status-ctx.js +125 -0
  72. package/dist/modes/interactive/status-line.d.ts +2 -1
  73. package/dist/modes/interactive/status-line.js +3 -2
  74. package/dist/modes/interactive/tasks-report.js +4 -0
  75. package/dist/modes/print/print-mode.js +12 -0
  76. package/dist/modes/session-report.d.ts +10 -2
  77. package/dist/modes/session-report.js +26 -2
  78. package/dist/sdk.d.ts +1 -1
  79. package/dist/tools/types.d.ts +6 -0
  80. package/dist/tui/components/meter.d.ts +2 -0
  81. package/dist/tui/components/meter.js +1 -1
  82. package/dist/tui/program-status.d.ts +90 -0
  83. package/dist/tui/program-status.js +259 -0
  84. package/docs/acp.md +4 -4
  85. package/docs/agents.md +1 -1
  86. package/docs/en/acp.md +4 -4
  87. package/docs/en/providers.md +5 -3
  88. package/docs/en/rpc.md +3 -0
  89. package/docs/en/sessions.md +23 -0
  90. package/docs/en/tui.md +79 -22
  91. package/docs/providers.md +8 -5
  92. package/docs/rpc.md +1 -0
  93. package/docs/session-format.md +3 -3
  94. package/docs/sessions.md +22 -0
  95. package/docs/tui-design.md +2 -2
  96. package/docs/tui.md +79 -22
  97. package/package.json +1 -1
@@ -0,0 +1,125 @@
1
+ /**
2
+ * 状态栏的上下文项(status-bar.ts 的一部分,单独成文件控制行数)。
3
+ *
4
+ * - full:`Ctx 3.0% 8.2k/272k auto`——百分比一位小数;已用量 / 窗口是绝对量;`auto` = 自动压缩开着
5
+ * (关闭、熔断或窗口未知时不显示)。成员各自按优先级丢弃:先丢 `auto`、`/窗口`,再丢已用量,百分比最后丢;
6
+ * 会变的数字按最宽形状占位(不抖动)。
7
+ * - compact:`ctx 34%`,≥ 110 列换余量表;0 < 占用 < 1% 时保留一位小数(不显示成 0%)。
8
+ * - 上下文量不是来自 usage(压缩后新 usage 之前、启动前缀基线)时数字前加 `≈`(ASCII `~`)。
9
+ * - 着色:warning 与档一裁剪阈值对齐(`pruneAt / 窗口`,缺省 0.7),≥ 0.9 error。
10
+ * - 流式中:助手消息的 usage 已经给出在途请求的上下文量时,按 ≤ 2 Hz 采样显示它,消息结束后回到统计值。
11
+ */
12
+ import { calculateContextTokens } from "../../compaction/estimate.js";
13
+ import { msg } from "../../i18n/index.js";
14
+ import { Meter, levelColor } from "../../tui.js";
15
+ import { contextEstimated } from "../session-report.js";
16
+ import { formatTokens } from "./status-bar.js";
17
+ /** 流式 usage 的采样间隔(与 telemetry_tick 同节奏,≤ 2 Hz)。 */
18
+ export const LIVE_CTX_INTERVAL_MS = 500;
19
+ /** ctx 改用余量表的最小宽度(compact)。 */
20
+ const METER_WIDTH = 110;
21
+ /** 上下文百分比:full 一位小数;compact 取整,但 0 < p < 1 时保留一位。 */
22
+ export function ctxPercentText(percent, decimal) {
23
+ return decimal || (percent > 0 && percent < 1)
24
+ ? `${percent.toFixed(1)}%`
25
+ : `${Math.round(percent)}%`;
26
+ }
27
+ /** 上下文着色的 warning 阈值:与档一裁剪阈值对齐(`pruneAt / 窗口`),缺省 0.7。 */
28
+ export function ctxWarnAt(stats) {
29
+ const at = stats.context?.pruneAt;
30
+ const window = stats.contextWindow;
31
+ return at === undefined || window === undefined || window <= 0 ? 0.7 : Math.min(0.9, at / window);
32
+ }
33
+ /** 上下文量不是来自 usage(全量估算 / 启动前缀基线):`≈`(ASCII `~`),否则空串。 */
34
+ export function ctxApprox(stats, ascii = false) {
35
+ return contextEstimated(stats) ? (ascii ? "~" : "≈") : "";
36
+ }
37
+ /** 在途请求的上下文量(message_update 的 usage)。 */
38
+ export class LiveContext {
39
+ now;
40
+ tokens;
41
+ at = Number.NEGATIVE_INFINITY;
42
+ constructor(now) {
43
+ this.now = now;
44
+ }
45
+ /** 记下 usage 给出的上下文量;距上次采样 < 500 ms 时不更新。返回是否更新了。 */
46
+ note(usage) {
47
+ if (usage === undefined)
48
+ return false;
49
+ const tokens = calculateContextTokens(usage);
50
+ if (tokens <= 0)
51
+ return false;
52
+ const now = this.now();
53
+ if (this.tokens !== undefined && now - this.at < LIVE_CTX_INTERVAL_MS)
54
+ return false;
55
+ this.tokens = tokens;
56
+ this.at = now;
57
+ return true;
58
+ }
59
+ clear() {
60
+ this.tokens = undefined;
61
+ this.at = Number.NEGATIVE_INFINITY;
62
+ }
63
+ /** 统计值 → 显示值(有在途值时以它为准,不再是估算)。 */
64
+ view(stats, ascii) {
65
+ const view = {
66
+ tokens: stats.contextTokens,
67
+ window: stats.contextWindow,
68
+ percent: stats.contextPercent,
69
+ approx: ctxApprox(stats, ascii),
70
+ warnAt: ctxWarnAt(stats),
71
+ auto: stats.context?.autoCompactAt !== undefined,
72
+ };
73
+ const window = stats.contextWindow;
74
+ if (this.tokens !== undefined) {
75
+ view.tokens = this.tokens;
76
+ view.approx = "";
77
+ if (window !== undefined && window > 0)
78
+ view.percent = Math.min(100, Math.round((this.tokens / window) * 1000) / 10);
79
+ }
80
+ return view;
81
+ }
82
+ }
83
+ /** compact:`ctx 34%`,宽屏换余量表。 */
84
+ export function compactCtxText(ctx, width, theme) {
85
+ const { percent } = ctx;
86
+ if (percent === undefined)
87
+ return theme.fg("dim", "ctx ?");
88
+ if (width >= METER_WIDTH) {
89
+ const meter = new Meter(percent / 100, {
90
+ label: theme.fg("dim", "ctx"),
91
+ theme,
92
+ warnAt: ctx.warnAt,
93
+ percent: (ratio) => ctx.approx + ctxPercentText(ratio * 100, false),
94
+ });
95
+ return meter.render(80)[0] ?? "";
96
+ }
97
+ const color = levelColor(percent / 100, { warnAt: ctx.warnAt });
98
+ return theme.fg("dim", "ctx ") + theme.fg(color, ctx.approx + ctxPercentText(percent, false));
99
+ }
100
+ /** full:`Ctx 3.0%` + ` 8.2k` + `/272k` + ` auto`(同组 `ctx`,组内不加连接符)。 */
101
+ export function fullCtxParts(ctx, theme, p) {
102
+ const dim = (text) => theme.fg("dim", text);
103
+ const out = [];
104
+ const add = (text, priority, reserve) => void out.push({
105
+ text,
106
+ priority,
107
+ zone: "right",
108
+ group: "ctx",
109
+ ...(reserve === undefined ? {} : { reserve }),
110
+ });
111
+ const { percent } = ctx;
112
+ if (percent === undefined)
113
+ add(dim("Ctx ?"), p.ctx, 10);
114
+ else {
115
+ const color = levelColor(percent / 100, { warnAt: ctx.warnAt });
116
+ add(dim("Ctx ") + theme.fg(color, ctx.approx + ctxPercentText(percent, true)), p.ctx, 10);
117
+ }
118
+ if (ctx.tokens !== undefined)
119
+ add(dim(` ${formatTokens(ctx.tokens)}`), p.ctxUsed, 5);
120
+ if (ctx.tokens !== undefined && ctx.window !== undefined)
121
+ add(dim(`/${formatTokens(ctx.window)}`), p.ctxWindow, 5);
122
+ if (ctx.auto)
123
+ add(dim(` ${msg().interactive.statusLine.autoCompact}`), p.ctxAuto);
124
+ return out;
125
+ }
@@ -8,7 +8,8 @@
8
8
  * - [W7] 左右分区:左 = 状态 / 开关(`codemode on|only`〔网络未隔离追加 `net!`〕· `沙箱` · `preset 名` ·
9
9
  * `→ 回退模型` · `queue N` · [宿主]),没有开关时左区为空、右区右对齐;右 = 度量:
10
10
  * `tps: <速率>`(流式中是最近 2 s 窗口的瞬时值、前缀 accent;结束后是该请求的平均值;还没有请求时 `tps: —`)
11
- * • `<输出 token> tok / <耗时>`(首 token 起算)`(avg <会话均速> · ttft <首 token 延迟>)` · `↑ ↓` · cache ·
11
+ * • `<输出 token> tok / <耗时>`(首 token 起算)`(avg <会话均速> · ttft <首 token 延迟>)` · `Σ↑ ↓ R W`(会话累计
12
+ * 计费量,不是上下文;R / W 为缓存读写,为 0 不显示) · cache ·
12
13
  * rebill,行尾 `[-]`(折叠提示:Ctrl+G 或 `/statusline compact`);`•` 走字形表(ASCII `*`);
13
14
  * - 数据:`StatusBar.current()` 的 `telemetry` 与用量(同一次 getStats);流式中 `telemetry_tick` 时刷新;
14
15
  * - 丢弃顺序(数字大先丢;`tps` 与 `[-]` 永不丢):右区度量先丢——tok/耗时 12 → token 11 → cache 10 →
@@ -8,7 +8,8 @@
8
8
  * - [W7] 左右分区:左 = 状态 / 开关(`codemode on|only`〔网络未隔离追加 `net!`〕· `沙箱` · `preset 名` ·
9
9
  * `→ 回退模型` · `queue N` · [宿主]),没有开关时左区为空、右区右对齐;右 = 度量:
10
10
  * `tps: <速率>`(流式中是最近 2 s 窗口的瞬时值、前缀 accent;结束后是该请求的平均值;还没有请求时 `tps: —`)
11
- * • `<输出 token> tok / <耗时>`(首 token 起算)`(avg <会话均速> · ttft <首 token 延迟>)` · `↑ ↓` · cache ·
11
+ * • `<输出 token> tok / <耗时>`(首 token 起算)`(avg <会话均速> · ttft <首 token 延迟>)` · `Σ↑ ↓ R W`(会话累计
12
+ * 计费量,不是上下文;R / W 为缓存读写,为 0 不显示) · cache ·
12
13
  * rebill,行尾 `[-]`(折叠提示:Ctrl+G 或 `/statusline compact`);`•` 走字形表(ASCII `*`);
13
14
  * - 数据:`StatusBar.current()` 的 `telemetry` 与用量(同一次 getStats);流式中 `telemetry_tick` 时刷新;
14
15
  * - 丢弃顺序(数字大先丢;`tps` 与 `[-]` 永不丢):右区度量先丢——tok/耗时 12 → token 11 → cache 10 →
@@ -100,7 +101,7 @@ export class StatusLine {
100
101
  if (last?.ttftMs !== undefined) {
101
102
  metric(`${dim("ttft ")}${purple(formatSeconds(last.ttftMs))}`, PRIORITY.ttft, RESERVE.ttft, stat);
102
103
  }
103
- const usage = usageItems(stats, this.bar.queued(), this.source, theme);
104
+ const usage = usageItems(stats, this.bar.queued(), this.source, theme, true);
104
105
  add("right", usage.tokens, PRIORITY.tokens);
105
106
  add("right", usage.cache, PRIORITY.cache);
106
107
  add("right", usage.rebill, PRIORITY.rebill);
@@ -6,6 +6,7 @@
6
6
  * - 类型:`cachedAgentInfos(sessionId) ?? sessionAgents(sessionId)`(外部 Agent 带安装状态与版本,W5-EG)。
7
7
  */
8
8
  import { cachedAgentInfos } from "../../agents/external.js";
9
+ import { formatTaskContext } from "../../agents/task-record.js";
9
10
  import { registryOf, sessionAgents, taskRegistryView } from "../../agent/subagent-registry.js";
10
11
  import { msg } from "../../i18n/index.js";
11
12
  import { formatTokenCount, formatUsd } from "../session-report.js";
@@ -38,6 +39,9 @@ export function taskFacts(task, now) {
38
39
  const usage = task.usage;
39
40
  if (usage !== undefined && usage.input + usage.output + usage.cacheRead > 0)
40
41
  facts.push(`↑${formatTokenCount(usage.input + usage.cacheRead + usage.cacheWrite)} ↓${formatTokenCount(usage.output)}`);
42
+ const context = formatTaskContext(task);
43
+ if (context !== undefined)
44
+ facts.push(msg().panels.tasks.context(context));
41
45
  if (task.costUsd !== undefined && task.costUsd > 0)
42
46
  facts.push(formatUsd(task.costUsd));
43
47
  return facts;
@@ -215,6 +215,7 @@ export async function runPrintMode(runtime, context) {
215
215
  usage: stats.tokens,
216
216
  cost: stats.cost,
217
217
  cacheHitRate: stats.cacheHitRate,
218
+ ...contextOf(stats),
218
219
  ...(stats.cache !== undefined ? { cache: stats.cache } : {}),
219
220
  ...(stats.tasks !== undefined ? { tasks: stats.tasks } : {}),
220
221
  ...(denied.length > 0 ? { deniedTools: denied } : {}),
@@ -309,3 +310,14 @@ export function describeDenied(denied) {
309
310
  const tools = [...counts].map(([name, n]) => `${name} ×${n}`);
310
311
  return msg().print.print.denied(denied.length, tools, denied[0]?.reason ?? "");
311
312
  }
313
+ /** json 结果的 `context`:上下文用量、窗口与占用(0–100);各项未知时省略,全都未知时不带 `context`。 */
314
+ function contextOf(stats) {
315
+ const context = {};
316
+ if (stats.contextTokens !== undefined)
317
+ context["tokens"] = stats.contextTokens;
318
+ if (stats.contextWindow !== undefined)
319
+ context["window"] = stats.contextWindow;
320
+ if (stats.contextPercent !== undefined)
321
+ context["percent"] = stats.contextPercent;
322
+ return Object.keys(context).length > 0 ? { context } : {};
323
+ }
@@ -2,18 +2,26 @@
2
2
  * 会话报告与缓存提示文本(第三波 §1.10)。[W3-C2]
3
3
  *
4
4
  * line 模式、交互模式与 RPC 共用的纯文本层,不含渲染:
5
- * - `/session`:会话、模型、消息、用量、上下文 + 「缓存」段(`describeSession`);
5
+ * - `/session`:会话、模型、消息、累计用量、上下文(估算值带 `≈`,末尾是距自动压缩的余量)+ 「缓存」段
6
+ * (`describeSession`);
6
7
  * - `/cache`:只有缓存段(`describeCache`);`/cache fingerprint`:最近一次真实请求的前缀指纹;
7
8
  * - 消息区 / stderr 的一行提示:缓存未命中(只提示 ≥ 20k token 或 ≥ $0.10 的那次)、
8
9
  * 上下文跨越 70% / 90%(`cache.missNotices` 为 false 时都不提示,统计不受影响)。
9
10
  *
10
11
  * 两列对齐用 B9a-2 的 `KeyValue`(按足够宽的宽度渲染再去行尾空白,结果是纯文本)。
11
12
  */
12
- import type { AgentSession, SessionEvent } from "../agent/types.js";
13
+ import type { AgentSession, SessionEvent, SessionStats } from "../agent/types.js";
13
14
  import type { CacheMiss, CacheReporting, WarmerStatus } from "../ai/cache/types.js";
14
15
  import { type KeyValueRow } from "../tui/components/key-value.js";
15
16
  /** `950`、`38.2k`、`150k`、`1.05M`(比状态栏多一位精度,面板与提示用)。 */
16
17
  export declare function formatTokenCount(count: number): string;
18
+ /** 上下文量不是来自 usage(压缩后新 usage 之前的全量估算、第一次请求前的前缀基线)。 */
19
+ export declare function contextEstimated(stats: Pick<SessionStats, "context">): boolean;
20
+ /**
21
+ * 上下文行末尾的自动压缩说明:`距自动压缩 ≈ N`(到档二阈值还差多少);自动压缩关闭或熔断时
22
+ * `自动压缩关闭`;窗口未知或统计没有 `context` 明细时 undefined。
23
+ */
24
+ export declare function autoCompactText(stats: Pick<SessionStats, "context" | "contextTokens" | "contextWindow">): string | undefined;
17
25
  export declare function formatUsd(cost: number | undefined): string;
18
26
  export declare function formatPercent(rate: number | undefined): string;
19
27
  /** `45s`、`2m 10s`、`7 分钟`(≥ 10 分钟只到分钟)。 */
@@ -2,7 +2,8 @@
2
2
  * 会话报告与缓存提示文本(第三波 §1.10)。[W3-C2]
3
3
  *
4
4
  * line 模式、交互模式与 RPC 共用的纯文本层,不含渲染:
5
- * - `/session`:会话、模型、消息、用量、上下文 + 「缓存」段(`describeSession`);
5
+ * - `/session`:会话、模型、消息、累计用量、上下文(估算值带 `≈`,末尾是距自动压缩的余量)+ 「缓存」段
6
+ * (`describeSession`);
6
7
  * - `/cache`:只有缓存段(`describeCache`);`/cache fingerprint`:最近一次真实请求的前缀指纹;
7
8
  * - 消息区 / stderr 的一行提示:缓存未命中(只提示 ≥ 20k token 或 ≥ $0.10 的那次)、
8
9
  * 上下文跨越 70% / 90%(`cache.missNotices` 为 false 时都不提示,统计不受影响)。
@@ -26,6 +27,24 @@ export function formatTokenCount(count) {
26
27
  return `${Math.round(count / 1000)}k`;
27
28
  return `${trim((count / 1_000_000).toFixed(2))}M`;
28
29
  }
30
+ /** 上下文量不是来自 usage(压缩后新 usage 之前的全量估算、第一次请求前的前缀基线)。 */
31
+ export function contextEstimated(stats) {
32
+ const source = stats.context?.source;
33
+ return source !== undefined && source !== "usage";
34
+ }
35
+ /**
36
+ * 上下文行末尾的自动压缩说明:`距自动压缩 ≈ N`(到档二阈值还差多少);自动压缩关闭或熔断时
37
+ * `自动压缩关闭`;窗口未知或统计没有 `context` 明细时 undefined。
38
+ */
39
+ export function autoCompactText(stats) {
40
+ const m = msg().report.session;
41
+ if (stats.context === undefined || stats.contextWindow === undefined)
42
+ return undefined;
43
+ const at = stats.context.autoCompactAt;
44
+ if (at === undefined)
45
+ return m.autoCompactOff;
46
+ return m.toAutoCompact(formatTokenCount(Math.max(0, at - (stats.contextTokens ?? 0))));
47
+ }
29
48
  export function formatUsd(cost) {
30
49
  if (cost === undefined)
31
50
  return "$?";
@@ -284,7 +303,12 @@ export function describeSession(session, now = Date.now()) {
284
303
  },
285
304
  {
286
305
  key: m.keyContext,
287
- value: m.context(String(stats.contextTokens ?? "?"), String(stats.contextWindow ?? "?"), percent),
306
+ value: [
307
+ m.context(`${contextEstimated(stats) ? "≈" : ""}${stats.contextTokens ?? "?"}`, String(stats.contextWindow ?? "?"), percent),
308
+ autoCompactText(stats),
309
+ ]
310
+ .filter((part) => part !== undefined)
311
+ .join(" · "),
288
312
  },
289
313
  ];
290
314
  const tasks = taskStatsText(session);
package/dist/sdk.d.ts CHANGED
@@ -33,7 +33,7 @@ import type { ApprovalDecision, ApprovalRequest, PermissionMode } from "./permis
33
33
  import { SessionManager } from "./session/manager.js";
34
34
  import type { ToolDefinition } from "./tools/types.js";
35
35
  /** [W3-C2] 统计类型:`session.getStats()`、RPC `get_session_stats`、`-p --output-format json` 的 `cache`。 */
36
- export type { SessionCacheStats, SessionStats } from "./agent/types.js";
36
+ export type { SessionCacheStats, SessionContextStats, SessionStats } from "./agent/types.js";
37
37
  /** [RW-B] 回滚:`session.rewindPoints()`、`session.rewind()`、`summarizeFrom / summarizeUpTo`。 */
38
38
  export type { CodeRestoreResult, RewindMode, RewindPoint, RewindRequest, RewindResult, } from "./checkpoints/types.js";
39
39
  export type { CacheMiss, CacheMissReason, CacheReporting, WarmerStatus, WarmingMode, } from "./ai/cache/types.js";
@@ -166,6 +166,9 @@ export type SubagentEvent = {
166
166
  /** 无美元单位的外部 Agent 按各自单位记(不换算)。 */
167
167
  unit?: "usd" | "tokens" | "requests";
168
168
  amount?: number;
169
+ /** 外部 Agent 报告的上下文占用与窗口(回合中途也会单独发一条只带它们的 usage)。 */
170
+ contextTokens?: number;
171
+ contextWindow?: number;
169
172
  } | {
170
173
  type: "notice";
171
174
  level: "info" | "warn";
@@ -217,6 +220,9 @@ export interface TaskInfo {
217
220
  turns?: number;
218
221
  usage?: Usage;
219
222
  costUsd?: number;
223
+ /** 外部 Agent 最近报告的上下文占用与窗口(token,只记数字);驱动不报时缺省。 */
224
+ contextTokens?: number;
225
+ contextWindow?: number;
220
226
  outputFile?: string;
221
227
  sessionRef?: SubagentSessionRef;
222
228
  }
@@ -14,6 +14,8 @@ export interface MeterOptions {
14
14
  warnAt?: number;
15
15
  dangerAt?: number;
16
16
  theme?: Theme;
17
+ /** 百分比文本(缺省四舍五入到整数,`72%`);value 为 undefined 时不调用。 */
18
+ percent?: (ratio: number) => string;
17
19
  }
18
20
  export declare const METER_FULL: string;
19
21
  export declare const METER_EMPTY: string;
@@ -32,7 +32,7 @@ export class Meter {
32
32
  render(width) {
33
33
  const { theme, label, cells = 10 } = this.options;
34
34
  const ratio = this.value === undefined ? undefined : Math.min(1, Math.max(0, this.value));
35
- const percent = ratio === undefined ? "—" : `${Math.round(ratio * 100)}%`;
35
+ const percent = ratio === undefined ? "—" : (this.options.percent?.(ratio) ?? `${Math.round(ratio * 100)}%`);
36
36
  const color = (c, text) => (theme ? theme.fg(c, text) : text);
37
37
  const head = label === undefined || label === "" ? "" : `${label} `;
38
38
  const filled = ratio === undefined ? 0 : Math.round(ratio * cells);
@@ -0,0 +1,90 @@
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 type ProgramState = "idle" | "working" | "done" | "blocked" | "error" | "clear";
17
+ export type BlockedKind = "permission" | "question" | "auth";
18
+ export type ProgramStatusMode = "auto" | "on" | "off";
19
+ export interface ProgramStatusReport {
20
+ state: ProgramState;
21
+ /** 层级 id(`task/abc`);缺省是根记录。 */
22
+ id?: string;
23
+ kind?: BlockedKind;
24
+ progress?: number;
25
+ app?: string;
26
+ title?: string;
27
+ msg?: string;
28
+ }
29
+ export declare const PROGRAM_STATUS_QUERY = "\u001B]7501;?\u001B\\";
30
+ export declare const DA1_QUERY = "\u001B[c";
31
+ export declare const MAX_SEQUENCE_BYTES = 4096;
32
+ export declare const MAX_TITLE_BYTES = 192;
33
+ export declare const MAX_MSG_BYTES = 2048;
34
+ /** 控制字符(C0、DEL、C1)换成空格,空白压成一个,去首尾。 */
35
+ export declare function sanitizeLine(text: string): string;
36
+ /** 按 UTF-8 字节截断到 maxBytes(按码点切,不出半个字符);截断时末尾带 `…`。 */
37
+ export declare function truncateUtf8(text: string, maxBytes: number): string;
38
+ /** id 是否合法(空串不合法;根记录不带 id)。 */
39
+ export declare function validId(id: string): boolean;
40
+ /** 任意字符串 → 合法的单段 id(非法字符换 `_`,截到 n 个字符);空串给 `x`。 */
41
+ export declare function idSegment(raw: string, n?: number): string;
42
+ /** 报告 → pairs(不含 OSC 包裹);state / id 不合法返回 undefined。 */
43
+ export declare function encodePairs(report: ProgramStatusReport): string | undefined;
44
+ /** 完整的 OSC 7501 序列;报告不合法返回 undefined(不发坏报告)。 */
45
+ export declare function encodeProgramStatus(report: ProgramStatusReport): string | undefined;
46
+ /** tmux DCS passthrough:`ESC P tmux; <内容,ESC 翻倍> ESC \`。 */
47
+ export declare function tmuxPassthrough(sequence: string): string;
48
+ /** 终端对 `OSC 7501 ; ?` 的回复(`?` 之后的内容忽略)。 */
49
+ export declare function isProgramStatusReply(sequence: string): boolean;
50
+ /** DA1 回复 `CSI ? … c`。 */
51
+ export declare function isDa1Reply(sequence: string): boolean;
52
+ export interface ProgramStatusEmitterOptions {
53
+ write(data: string): void;
54
+ mode: ProgramStatusMode;
55
+ /** 用来判断 tmux(`TMUX`)。 */
56
+ env?: NodeJS.ProcessEnv;
57
+ /** 检测超时,缺省 300 ms。 */
58
+ timeoutMs?: number;
59
+ /** 同一 state 下只改 msg / title 的最小间隔,缺省 500 ms。 */
60
+ throttleMs?: number;
61
+ now?: () => number;
62
+ /** 固定加的 app 键(缺省不加)。 */
63
+ app?: string;
64
+ }
65
+ /** 发射器:检测、同值去重、节流;只在 `supported` 后写。 */
66
+ export declare class ProgramStatusEmitter {
67
+ private readonly options;
68
+ private support;
69
+ private readonly tmux;
70
+ private readonly desired;
71
+ private readonly sent;
72
+ private readonly timers;
73
+ private probeTimer;
74
+ private readonly now;
75
+ constructor(options: ProgramStatusEmitterOptions);
76
+ /** 当前是否会写出报告。 */
77
+ get supported(): boolean;
78
+ /** 按模式开始:on 直接启用,auto 发检测查询(tmux 里不检测,等同 off),off 什么都不做。 */
79
+ start(): void;
80
+ /** 输入解析里调用:吞掉检测回复(返回 true),其余返回 false。 */
81
+ handleInput(sequence: string): boolean;
82
+ /** 设定某个 id(缺省根记录)的状态;`clear` 同时忘掉该 id 及其下层的记录。 */
83
+ report(report: ProgramStatusReport): void;
84
+ /** 停止计时器(不再写任何东西)。 */
85
+ dispose(): void;
86
+ private settleProbe;
87
+ private enable;
88
+ private forget;
89
+ private flush;
90
+ }