acp-kernel 0.0.64-pr.254.101 → 0.0.64-pr.255.103

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.
@@ -2,6 +2,8 @@ export { topicFallback } from "./topic.js";
2
2
  export { formatCompactTokens } from "./format.js";
3
3
  export { buildStatusPanel } from "./panel.js";
4
4
  export type { StatusPanelInput } from "./panel.js";
5
+ export { DEFAULT_PANEL_LABELS, displayWidth, fill, padLabel, renderTitleBox, resolvePanelLabels } from "./labels.js";
6
+ export type { PanelLabels } from "./labels.js";
5
7
  export { cacheHitStats, formatHitRate } from "./cache.js";
6
8
  export type { CacheUsageSample, CacheHitSummary } from "./cache.js";
7
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/panel/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/panel/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACrH,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC1D,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
@@ -53,6 +53,76 @@ function formatHitRate(rate) {
53
53
  return `${(Math.max(0, Math.min(1, rate)) * 100).toFixed(1)}%`;
54
54
  }
55
55
 
56
+ // src/panel/labels.ts
57
+ var DEFAULT_PANEL_LABELS = {
58
+ title: "ACP Context Analysis",
59
+ context: "Context (session accounting, host footer scale): {pct}% ({used} / {limit}) \u2014 includes compressed originals; shrinks slower than the sent view",
60
+ growth: "Growth: +{growth} since last nudge",
61
+ sent: "Sent to LLM (after compression, est.): {sent}",
62
+ sentOfLimit: " ({pct}% of limit)",
63
+ sessionOnly: "Session-only (compressed originals, est.): {only} \u2014 pruned from every request; the footer/nudge still count them",
64
+ breakdown: "Token Breakdown (sent view):",
65
+ catTool: "Tool",
66
+ catSysPrompt: "SysPrompt",
67
+ catText: "Text",
68
+ catCode: "Code",
69
+ catSummaries: "Summaries",
70
+ promptCache: "Prompt cache (provider-reported): {last} last \xB7 {session} session avg \u2014 {read} of {billed} billed prompt tokens served from cache ({requests} req)",
71
+ nudgeActive: "Nudge: ACTIVE{tierInfo} \u2014 {reason}",
72
+ nudgeTierInfo: " [T{tier} distillation]",
73
+ nudgeIdle: "Nudge: idle \u2014 {reason}",
74
+ blocksHeader: "Blocks: {active} active / {total} total ({tokens} tokens compressed, cumulative)",
75
+ blocksNone: "Blocks: none (nothing compressed yet)",
76
+ tagVisibility: "Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal."
77
+ };
78
+ function resolvePanelLabels(labels) {
79
+ const out = { ...DEFAULT_PANEL_LABELS };
80
+ if (!labels) return out;
81
+ for (const key of Object.keys(labels)) {
82
+ const value = labels[key];
83
+ if (typeof value === "string") out[key] = value;
84
+ }
85
+ return out;
86
+ }
87
+ function fill(template, params) {
88
+ return template.replace(/\{([A-Za-z][A-Za-z0-9_]*)\}/g, (m, k) => k in params ? String(params[k]) : m);
89
+ }
90
+ var LEGACY_TITLE_BOX = [
91
+ "\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E",
92
+ "\u2502 ACP Context Analysis \u2502",
93
+ "\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F"
94
+ ];
95
+ var MIN_INNER_WIDTH = 45;
96
+ function charDisplayWidth(ch) {
97
+ const cp = ch.codePointAt(0);
98
+ const wide = cp >= 4352 && cp <= 4447 || // Hangul Jamo
99
+ cp >= 11904 && cp <= 42191 || // CJK radicals .. Yi syllables
100
+ cp >= 44032 && cp <= 55203 || // Hangul syllables
101
+ cp >= 63744 && cp <= 64255 || // CJK compatibility ideographs
102
+ cp >= 65072 && cp <= 65103 || // CJK compatibility forms
103
+ cp >= 65280 && cp <= 65376 || // fullwidth forms
104
+ cp >= 65504 && cp <= 65510 || // halfwidth/fullwidth forms
105
+ cp >= 131072;
106
+ return wide ? 2 : 1;
107
+ }
108
+ function displayWidth(s) {
109
+ let w = 0;
110
+ for (const ch of s) w += charDisplayWidth(ch);
111
+ return w;
112
+ }
113
+ function padLabel(s, width) {
114
+ const dw = displayWidth(s);
115
+ return dw >= width ? s : s + " ".repeat(width - dw);
116
+ }
117
+ function renderTitleBox(title) {
118
+ if (title === DEFAULT_PANEL_LABELS.title) return LEGACY_TITLE_BOX;
119
+ const dw = displayWidth(title);
120
+ const inner = Math.max(MIN_INNER_WIDTH, dw + 2);
121
+ const padL = Math.floor((inner - dw) / 2);
122
+ const padR = inner - dw - padL;
123
+ return ["\u256D" + "\u2500".repeat(inner) + "\u256E", `\u2502${" ".repeat(padL)}${title}${" ".repeat(padR)}\u2502`, "\u2570" + "\u2500".repeat(inner) + "\u256F"];
124
+ }
125
+
56
126
  // src/panel/panel.ts
57
127
  function bar(value, total, width = 20) {
58
128
  if (total === 0) return "";
@@ -61,6 +131,7 @@ function bar(value, total, width = 20) {
61
131
  }
62
132
  function buildStatusPanel(input) {
63
133
  const { tokenCount, state, nudge, modelContextLimit } = input;
134
+ const L = resolvePanelLabels(input.labels);
64
135
  const fmt = input.fmtTokens ?? formatCompactTokens;
65
136
  const bd = nudge?.contextBreakdown;
66
137
  const limit = modelContextLimit;
@@ -74,36 +145,36 @@ function buildStatusPanel(input) {
74
145
  const activeBlocksList = state.blocks.filter((b) => b.active);
75
146
  const totalBlocksList = state.blocks;
76
147
  const lines = [];
77
- lines.push("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E");
78
- lines.push("\u2502 ACP Context Analysis \u2502");
79
- lines.push("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F");
148
+ lines.push(...renderTitleBox(L.title));
80
149
  if (input.version) lines.push(input.version);
81
150
  lines.push("");
82
- lines.push(`Context (session accounting, host footer scale): ${displayPct}% (${fmt(displayTotal)} / ${fmt(limit)}) \u2014 includes compressed originals; shrinks slower than the sent view`);
151
+ lines.push(fill(L.context, { pct: displayPct, used: fmt(displayTotal), limit: fmt(limit) }));
83
152
  if (nudge && bd) {
84
153
  const growth = bd.growth;
85
154
  if (growth > 0 && displayTotal > 0) {
86
- lines.push(`Growth: +${fmt(growth)} since last nudge`);
155
+ lines.push(fill(L.growth, { growth: fmt(growth) }));
87
156
  }
88
157
  lines.push("");
89
- lines.push(`Sent to LLM (after compression, est.): ${fmt(sentTotal)}${limit > 0 ? ` (${sentPct}% of limit)` : ""}`);
158
+ let sentLine = fill(L.sent, { sent: fmt(sentTotal) });
159
+ if (limit > 0) sentLine += fill(L.sentOfLimit, { pct: sentPct });
160
+ lines.push(sentLine);
90
161
  if (input.unprunedTokens !== void 0 && sessionOnly > 0) {
91
- lines.push(`Session-only (compressed originals, est.): ${fmt(sessionOnly)} \u2014 pruned from every request; the footer/nudge still count them`);
162
+ lines.push(fill(L.sessionOnly, { only: fmt(sessionOnly) }));
92
163
  }
93
164
  lines.push("");
94
- lines.push("Token Breakdown (sent view):");
165
+ lines.push(L.breakdown);
95
166
  const categories = [
96
- { label: "Tool", value: bd.tool },
97
- { label: "SysPrompt", value: systemPromptTokens },
98
- { label: "Text", value: bd.text },
99
- { label: "Code", value: bd.code },
100
- { label: "Summaries", value: bd.summaries }
167
+ { label: L.catTool, value: bd.tool },
168
+ { label: L.catSysPrompt, value: systemPromptTokens },
169
+ { label: L.catText, value: bd.text },
170
+ { label: L.catCode, value: bd.code },
171
+ { label: L.catSummaries, value: bd.summaries }
101
172
  ];
102
173
  for (const cat of categories) {
103
174
  if (cat.value <= 0) continue;
104
175
  const pct = sentTotal > 0 ? Math.round(cat.value / sentTotal * 100) : 0;
105
176
  const b = bar(cat.value, sentTotal);
106
- lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct).padStart(3)}% ${fmt(cat.value)}`);
177
+ lines.push(` ${padLabel(cat.label, 10)} ${b} ${String(pct).padStart(3)}% ${fmt(cat.value)}`);
107
178
  }
108
179
  }
109
180
  if (input.cacheUsages) {
@@ -111,17 +182,23 @@ function buildStatusPanel(input) {
111
182
  if (cache.requests > 0 && cache.session !== void 0 && cache.last !== void 0) {
112
183
  lines.push("");
113
184
  lines.push(
114
- `Prompt cache (provider-reported): ${formatHitRate(cache.last)} last \xB7 ${formatHitRate(cache.session)} session avg \u2014 ${fmt(cache.cacheRead)} of ${fmt(cache.billedPrompt)} billed prompt tokens served from cache (${cache.requests} req)`
185
+ fill(L.promptCache, {
186
+ last: formatHitRate(cache.last),
187
+ session: formatHitRate(cache.session),
188
+ read: fmt(cache.cacheRead),
189
+ billed: fmt(cache.billedPrompt),
190
+ requests: cache.requests
191
+ })
115
192
  );
116
193
  }
117
194
  }
118
195
  lines.push("");
119
196
  if (nudge) {
120
197
  if (nudge.shouldInject) {
121
- const tierInfo = nudge.tier ? ` [T${nudge.tier} distillation]` : "";
122
- lines.push(`Nudge: ACTIVE${tierInfo} \u2014 ${nudge.reason}`);
198
+ const tierInfo = nudge.tier ? fill(L.nudgeTierInfo, { tier: nudge.tier }) : "";
199
+ lines.push(fill(L.nudgeActive, { tierInfo, reason: nudge.reason }));
123
200
  } else {
124
- lines.push(`Nudge: idle \u2014 ${nudge.reason}`);
201
+ lines.push(fill(L.nudgeIdle, { reason: nudge.reason }));
125
202
  }
126
203
  }
127
204
  const ranges = viableRanges(nudge?.compressibleRanges ?? []);
@@ -132,7 +209,7 @@ function buildStatusPanel(input) {
132
209
  }
133
210
  if (activeBlocksList.length > 0) {
134
211
  lines.push("");
135
- lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt(state.stats.tokensCompressed)} tokens compressed, cumulative)`);
212
+ lines.push(fill(L.blocksHeader, { active: activeBlocksList.length, total: totalBlocksList.length, tokens: fmt(state.stats.tokensCompressed) }));
136
213
  for (const b of activeBlocksList) {
137
214
  const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || "")}`;
138
215
  const summaryTok = defaultCountTokens(b.summary || "");
@@ -141,20 +218,26 @@ function buildStatusPanel(input) {
141
218
  }
142
219
  } else if (totalBlocksList.length > 0) {
143
220
  lines.push("");
144
- lines.push(`Blocks: 0 active / ${totalBlocksList.length} total (${fmt(state.stats.tokensCompressed)} tokens compressed, cumulative)`);
221
+ lines.push(fill(L.blocksHeader, { active: 0, total: totalBlocksList.length, tokens: fmt(state.stats.tokensCompressed) }));
145
222
  } else {
146
223
  lines.push("");
147
- lines.push("Blocks: none (nothing compressed yet)");
224
+ lines.push(L.blocksNone);
148
225
  }
149
226
  lines.push("");
150
- lines.push("Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.");
227
+ lines.push(L.tagVisibility);
151
228
  return lines.join("\n");
152
229
  }
153
230
  export {
231
+ DEFAULT_PANEL_LABELS,
154
232
  buildStatusPanel,
155
233
  cacheHitStats,
234
+ displayWidth,
235
+ fill,
156
236
  formatCompactTokens,
157
237
  formatHitRate,
238
+ padLabel,
239
+ renderTitleBox,
240
+ resolvePanelLabels,
158
241
  topicFallback
159
242
  };
160
243
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/panel/topic.ts","../../src/panel/format.ts","../../src/panel/cache.ts","../../src/panel/panel.ts"],"sourcesContent":["/** Short topic for a block when the model did not provide one: first sentence\n * segment, leading quotes stripped, truncated to 30 chars with an ellipsis.\n * Used wherever block lists are rendered (panels, search results). */\nexport function topicFallback(summary: string): string {\n const first = summary.split(/[.\\n]/)[0] ?? \"\";\n const t = first.trim().replace(/^[\"'`]+/, \"\").trim();\n return t.length <= 30 ? t : `${t.slice(0, 30).trimEnd()}…`;\n}\n","/** Compact token formatting for user-facing panels: lowercase k/M with the\n * same thresholds as the hosts' footers (<1000 → raw, <10000 → one decimal\n * k, <1e6 → rounded k, <1e7 → one decimal M). */\nexport function formatCompactTokens(count: number): string {\n if (count < 1000) return count.toString();\n if (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n if (count < 1000000) return `${Math.round(count / 1000)}k`;\n if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n return `${Math.round(count / 1000000)}M`;\n}\n","/** Cache hit-rate math for the /acp status panel.\n *\n * Hosts feed per-request prompt-cache usage samples (from assistant\n * messages' provider-reported `usage`); the kernel derives the display\n * numbers. A request COUNTS only when the provider reported cache\n * activity for it (`cacheRead + cacheWrite > 0`): providers without\n * prompt-cache reporting (0/0 with plain `input`) must not drag the\n * average to a fabricated 0%. */\n\nexport interface CacheUsageSample {\n /** Non-cached conversation input tokens billed for the request. */\n input: number;\n /** Conversation tokens served from the prompt cache. */\n cacheRead: number;\n /** Conversation tokens written to the prompt cache (cache creation). */\n cacheWrite: number;\n}\n\nexport interface CacheHitSummary {\n /** Session hit rate (0..1), weighted by billed prompt tokens.\n * Undefined when no request reported cache activity. */\n session: number | undefined;\n /** Hit rate (0..1) of the most recent request that reported cache\n * activity. Undefined when no request reported any. */\n last: number | undefined;\n /** Counted requests (provider reported cache activity). */\n requests: number;\n /** Cumulative tokens served from cache. */\n cacheRead: number;\n /** Cumulative billed prompt tokens (input + cacheRead + cacheWrite). */\n billedPrompt: number;\n}\n\nfunction num(v: unknown): number {\n return typeof v === \"number\" && Number.isFinite(v) && v > 0 ? v : 0;\n}\n\n/** Aggregate per-request cache usage into panel display numbers. */\nexport function cacheHitStats(usages: readonly CacheUsageSample[]): CacheHitSummary {\n let cacheRead = 0;\n let billedPrompt = 0;\n let requests = 0;\n let last: number | undefined;\n for (const u of usages) {\n const read = num(u?.cacheRead);\n const write = num(u?.cacheWrite);\n const input = num(u?.input);\n // No cache signal on this request → excluded entirely (see header).\n if (read + write <= 0) continue;\n const total = read + write + input;\n if (total <= 0) continue;\n cacheRead += read;\n billedPrompt += total;\n requests += 1;\n last = read / total;\n }\n return {\n session: requests > 0 ? cacheRead / billedPrompt : undefined,\n last,\n requests,\n cacheRead,\n billedPrompt,\n };\n}\n\n/** Format a 0..1 rate as a percentage with one decimal (\"92.3%\"). */\nexport function formatHitRate(rate: number): string {\n return `${(Math.max(0, Math.min(1, rate)) * 100).toFixed(1)}%`;\n}\n","import { defaultCountTokens } from \"../tokenize.js\";\nimport { formatRanges } from \"../nudge-text.js\";\nimport type { CompressionState, NudgeDecision } from \"../types.js\";\nimport { formatCompactTokens } from \"./format.js\";\nimport { topicFallback } from \"./topic.js\";\nimport { viableRanges } from \"../viable.js\";\nimport { cacheHitStats, formatHitRate, type CacheUsageSample } from \"./cache.js\";\n\nexport interface StatusPanelInput {\n /** Adapter identifier for the header, e.g. \"billion-context-omp@0.1.6\".\n * Omit to hide the version line. */\n version?: string;\n /** Host session accounting — the SAME number the host footer displays.\n * It includes compressed originals (summaries stay in the window), so it\n * shrinks slower than the sent view when compression prunes the\n * per-request projection. */\n tokenCount: number;\n /** Measured token count of the host system prompt (host-specific to\n * obtain; the kernel breakdown does not see it). */\n systemPromptTokens: number;\n /** Kernel state (blocks drive the Blocks section). */\n state: CompressionState;\n /** The nudge decision from core.processTurn for this turn, if any. The\n * panel applies the viability filter to compressibleRanges itself. */\n nudge: NudgeDecision | undefined;\n /** Configured model context window, in tokens. */\n modelContextLimit: number;\n /** Estimate of the FULL (unpruned) core-message projection, computed\n * with the SAME countTokens the core uses (kernel default = CJK-aware\n * defaultCountTokens) — the same estimation scale as the kernel\n * breakdown. When provided, the panel derives `Session-only` on that\n * scale (unpruned − sent). Without it the line is omitted: subtracting\n * the host's provider-scale number from an estimate-scale number\n * invents a third, meaningless scale (issue #18 \"看板统计的和拆分的有差异\"). */\n unprunedTokens?: number;\n /** Per-request prompt-cache usage (from assistant messages' provider-\n * reported `usage`). Requests without cache reporting are excluded by\n * cacheHitStats; when no counted request remains, the section is\n * omitted entirely. Omit the field to hide the section. */\n cacheUsages?: ReadonlyArray<CacheUsageSample>;\n /** Token formatter override (defaults to formatCompactTokens). */\n fmtTokens?: (n: number) => string;\n}\n\nfunction bar(value: number, total: number, width: number = 20): string {\n if (total === 0) return \"\";\n const filled = Math.max(0, Math.min(width, Math.round((value / total) * width)));\n return \"█\".repeat(filled) + \"░\".repeat(width - filled);\n}\n\n/** Render the /acp status panel. Three token numbers, each labeled with\n * its own scale, never mixed in arithmetic:\n * - Session accounting (host footer scale): the host's reported context\n * size INCLUDING compressed originals (summaries stay in the window), so\n * it shrinks slower than the sent view when compression prunes the\n * per-request projection.\n * - Sent view (estimated, kernel countTokens scale): what actually\n * reaches the LLM after compression (kernel's classification over the\n * pruned projection + measured system prompt). This is the number\n * compression controls.\n * - Session-only (estimated, kernel countTokens scale): unpruned\n * projection − sent view; the compressed originals pruned from every\n * request.\n * Subtracting the host number from an estimate produced numbers that\n * reconciled with neither scale (\"Framework 390K\", \"session-only 29k vs\n * 112k compressed\") — that is what issue #18 reported. */\nexport function buildStatusPanel(input: StatusPanelInput): string {\n const { tokenCount, state, nudge, modelContextLimit } = input;\n const fmt = input.fmtTokens ?? formatCompactTokens;\n const bd = nudge?.contextBreakdown;\n const limit = modelContextLimit;\n const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;\n const systemPromptTokens = input.systemPromptTokens;\n const sentTotal = classified + systemPromptTokens;\n // Same-scale derivation only: both sides use the core's countTokens\n // estimate (kernel default = CJK-aware defaultCountTokens). The host\n // footer's tokenCount (provider-anchored, session-tree) is displayed as\n // its own line and never fed into an arithmetic difference with these.\n const sessionOnly = input.unprunedTokens !== undefined ? Math.max(0, input.unprunedTokens - sentTotal) : 0;\n const displayTotal = tokenCount;\n const displayPct = limit > 0 ? Math.round((displayTotal / limit) * 100) : 0;\n const sentPct = limit > 0 ? Math.round((sentTotal / limit) * 100) : 0;\n const activeBlocksList = state.blocks.filter((b) => b.active);\n const totalBlocksList = state.blocks;\n\n const lines: string[] = [];\n\n lines.push(\"╭─────────────────────────────────────────────╮\");\n lines.push(\"│ ACP Context Analysis │\");\n lines.push(\"╰─────────────────────────────────────────────╯\");\n if (input.version) lines.push(input.version);\n lines.push(\"\");\n lines.push(`Context (session accounting, host footer scale): ${displayPct}% (${fmt(displayTotal)} / ${fmt(limit)}) — includes compressed originals; shrinks slower than the sent view`);\n\n if (nudge && bd) {\n const growth = bd.growth;\n if (growth > 0 && displayTotal > 0) {\n lines.push(`Growth: +${fmt(growth)} since last nudge`);\n }\n lines.push(\"\");\n lines.push(`Sent to LLM (after compression, est.): ${fmt(sentTotal)}${limit > 0 ? ` (${sentPct}% of limit)` : \"\"}`);\n if (input.unprunedTokens !== undefined && sessionOnly > 0) {\n lines.push(`Session-only (compressed originals, est.): ${fmt(sessionOnly)} — pruned from every request; the footer/nudge still count them`);\n }\n lines.push(\"\");\n lines.push(\"Token Breakdown (sent view):\");\n\n const categories: Array<{ label: string; value: number }> = [\n { label: \"Tool\", value: bd.tool },\n { label: \"SysPrompt\", value: systemPromptTokens },\n { label: \"Text\", value: bd.text },\n { label: \"Code\", value: bd.code },\n { label: \"Summaries\", value: bd.summaries },\n ];\n\n for (const cat of categories) {\n if (cat.value <= 0) continue;\n const pct = sentTotal > 0 ? Math.round((cat.value / sentTotal) * 100) : 0;\n const b = bar(cat.value, sentTotal);\n lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct).padStart(3)}% ${fmt(cat.value)}`);\n }\n }\n\n if (input.cacheUsages) {\n const cache = cacheHitStats(input.cacheUsages);\n if (cache.requests > 0 && cache.session !== undefined && cache.last !== undefined) {\n lines.push(\"\");\n lines.push(\n `Prompt cache (provider-reported): ${formatHitRate(cache.last)} last · ${formatHitRate(cache.session)} session avg — ${fmt(cache.cacheRead)} of ${fmt(cache.billedPrompt)} billed prompt tokens served from cache (${cache.requests} req)`,\n );\n }\n }\n\n lines.push(\"\");\n\n if (nudge) {\n if (nudge.shouldInject) {\n const tierInfo = nudge.tier ? ` [T${nudge.tier} distillation]` : \"\";\n lines.push(`Nudge: ACTIVE${tierInfo} — ${nudge.reason}`);\n } else {\n lines.push(`Nudge: idle — ${nudge.reason}`);\n }\n }\n\n const ranges = viableRanges(nudge?.compressibleRanges ?? []);\n const protectedRanges = nudge?.protectedRanges ?? [];\n if (ranges.length > 0 || protectedRanges.length > 0) {\n lines.push(\"\");\n lines.push(formatRanges(ranges, protectedRanges));\n }\n\n if (activeBlocksList.length > 0) {\n lines.push(\"\");\n lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt(state.stats.tokensCompressed)} tokens compressed, cumulative)`);\n for (const b of activeBlocksList) {\n const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || \"\")}`;\n const summaryTok = defaultCountTokens(b.summary || \"\");\n const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;\n lines.push(` [${b.blockId}] T${b.tier} ${fmt(origTok)}→${fmt(summaryTok)}${topic}`);\n }\n } else if (totalBlocksList.length > 0) {\n lines.push(\"\");\n lines.push(`Blocks: 0 active / ${totalBlocksList.length} total (${fmt(state.stats.tokensCompressed)} tokens compressed, cumulative)`);\n } else {\n lines.push(\"\");\n lines.push(\"Blocks: none (nothing compressed yet)\");\n }\n\n lines.push(\"\");\n lines.push(\"Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.\");\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/panel/topic.ts","../../src/panel/format.ts","../../src/panel/cache.ts","../../src/panel/labels.ts","../../src/panel/panel.ts"],"sourcesContent":["/** Short topic for a block when the model did not provide one: first sentence\n * segment, leading quotes stripped, truncated to 30 chars with an ellipsis.\n * Used wherever block lists are rendered (panels, search results). */\nexport function topicFallback(summary: string): string {\n const first = summary.split(/[.\\n]/)[0] ?? \"\";\n const t = first.trim().replace(/^[\"'`]+/, \"\").trim();\n return t.length <= 30 ? t : `${t.slice(0, 30).trimEnd()}…`;\n}\n","/** Compact token formatting for user-facing panels: lowercase k/M with the\n * same thresholds as the hosts' footers (<1000 → raw, <10000 → one decimal\n * k, <1e6 → rounded k, <1e7 → one decimal M). */\nexport function formatCompactTokens(count: number): string {\n if (count < 1000) return count.toString();\n if (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n if (count < 1000000) return `${Math.round(count / 1000)}k`;\n if (count < 10000000) return `${(count / 1000000).toFixed(1)}M`;\n return `${Math.round(count / 1000000)}M`;\n}\n","/** Cache hit-rate math for the /acp status panel.\n *\n * Hosts feed per-request prompt-cache usage samples (from assistant\n * messages' provider-reported `usage`); the kernel derives the display\n * numbers. A request COUNTS only when the provider reported cache\n * activity for it (`cacheRead + cacheWrite > 0`): providers without\n * prompt-cache reporting (0/0 with plain `input`) must not drag the\n * average to a fabricated 0%. */\n\nexport interface CacheUsageSample {\n /** Non-cached conversation input tokens billed for the request. */\n input: number;\n /** Conversation tokens served from the prompt cache. */\n cacheRead: number;\n /** Conversation tokens written to the prompt cache (cache creation). */\n cacheWrite: number;\n}\n\nexport interface CacheHitSummary {\n /** Session hit rate (0..1), weighted by billed prompt tokens.\n * Undefined when no request reported cache activity. */\n session: number | undefined;\n /** Hit rate (0..1) of the most recent request that reported cache\n * activity. Undefined when no request reported any. */\n last: number | undefined;\n /** Counted requests (provider reported cache activity). */\n requests: number;\n /** Cumulative tokens served from cache. */\n cacheRead: number;\n /** Cumulative billed prompt tokens (input + cacheRead + cacheWrite). */\n billedPrompt: number;\n}\n\nfunction num(v: unknown): number {\n return typeof v === \"number\" && Number.isFinite(v) && v > 0 ? v : 0;\n}\n\n/** Aggregate per-request cache usage into panel display numbers. */\nexport function cacheHitStats(usages: readonly CacheUsageSample[]): CacheHitSummary {\n let cacheRead = 0;\n let billedPrompt = 0;\n let requests = 0;\n let last: number | undefined;\n for (const u of usages) {\n const read = num(u?.cacheRead);\n const write = num(u?.cacheWrite);\n const input = num(u?.input);\n // No cache signal on this request → excluded entirely (see header).\n if (read + write <= 0) continue;\n const total = read + write + input;\n if (total <= 0) continue;\n cacheRead += read;\n billedPrompt += total;\n requests += 1;\n last = read / total;\n }\n return {\n session: requests > 0 ? cacheRead / billedPrompt : undefined,\n last,\n requests,\n cacheRead,\n billedPrompt,\n };\n}\n\n/** Format a 0..1 rate as a percentage with one decimal (\"92.3%\"). */\nexport function formatHitRate(rate: number): string {\n return `${(Math.max(0, Math.min(1, rate)) * 100).toFixed(1)}%`;\n}\n","/** Display labels for buildStatusPanel. The English defaults below are the\n * key source of truth; hosts pass Partial<PanelLabels> to localize the panel\n * (billion-context-pi issue #153). This is a DISPLAY-ONLY surface: these\n * strings render in the terminal and never reach the model, so translating\n * them cannot affect compression/nudge behavior.\n *\n * Templates use `{name}` placeholders filled by fill(). Keys omitted by a\n * host pack fall back to the English default per key (partial packs degrade\n * gracefully instead of failing wholesale). */\nexport interface PanelLabels {\n /** Title rendered inside the decorative box. */\n title: string;\n /** Params: pct (number), used / limit (formatted token counts). */\n context: string;\n /** Params: growth (formatted). Line rendered only when growth > 0. */\n growth: string;\n /** Params: sent (formatted). */\n sent: string;\n /** Params: pct (number). Appended to the sent line only when a context\n * limit is configured; omit the placeholder in a pack to drop it. */\n sentOfLimit: string;\n /** Params: only (formatted). Line rendered only when unprunedTokens is\n * provided and the derived value is positive. */\n sessionOnly: string;\n breakdown: string;\n catTool: string;\n catSysPrompt: string;\n catText: string;\n catCode: string;\n catSummaries: string;\n /** Params: last / session (hit-rate strings), read / billed (formatted),\n * requests (number). */\n promptCache: string;\n /** Params: tierInfo (string, empty when no tier), reason (verbatim model-\n * facing text — keep it in your template verbatim). */\n nudgeActive: string;\n /** Params: tier (number). */\n nudgeTierInfo: string;\n /** Params: reason (verbatim model-facing text). */\n nudgeIdle: string;\n /** Params: active / total (numbers), tokens (formatted). Used for both the\n * active and zero-active block counts. */\n blocksHeader: string;\n /** Rendered when no blocks exist at all. */\n blocksNone: string;\n tagVisibility: string;\n}\n\nexport const DEFAULT_PANEL_LABELS: PanelLabels = {\n title: \"ACP Context Analysis\",\n context: \"Context (session accounting, host footer scale): {pct}% ({used} / {limit}) — includes compressed originals; shrinks slower than the sent view\",\n growth: \"Growth: +{growth} since last nudge\",\n sent: \"Sent to LLM (after compression, est.): {sent}\",\n sentOfLimit: \" ({pct}% of limit)\",\n sessionOnly: \"Session-only (compressed originals, est.): {only} — pruned from every request; the footer/nudge still count them\",\n breakdown: \"Token Breakdown (sent view):\",\n catTool: \"Tool\",\n catSysPrompt: \"SysPrompt\",\n catText: \"Text\",\n catCode: \"Code\",\n catSummaries: \"Summaries\",\n promptCache: \"Prompt cache (provider-reported): {last} last · {session} session avg — {read} of {billed} billed prompt tokens served from cache ({requests} req)\",\n nudgeActive: \"Nudge: ACTIVE{tierInfo} — {reason}\",\n nudgeTierInfo: \" [T{tier} distillation]\",\n nudgeIdle: \"Nudge: idle — {reason}\",\n blocksHeader: \"Blocks: {active} active / {total} total ({tokens} tokens compressed, cumulative)\",\n blocksNone: \"Blocks: none (nothing compressed yet)\",\n tagVisibility: \"Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.\",\n};\n\n/** Merge a partial label pack over the English defaults, ignoring entries\n * whose value is not a string (explicit `undefined` from a config merge,\n * `null`, …). A plain `{ ...defaults, ...pack }` spread lets one undefined\n * key blank out its default and crash the display-width renderers. */\nexport function resolvePanelLabels(labels?: Partial<PanelLabels>): PanelLabels {\n const out: PanelLabels = { ...DEFAULT_PANEL_LABELS };\n if (!labels) return out;\n for (const key of Object.keys(labels) as Array<keyof PanelLabels>) {\n const value = labels[key];\n if (typeof value === \"string\") out[key] = value;\n }\n return out;\n}\n\n/** Substitute `{name}` placeholders from params. Unknown placeholders are\n * left intact so a typo in a host pack is visible on screen instead of\n * silently dropping information. */\nexport function fill(template: string, params: Record<string, string | number>): string {\n return template.replace(/\\{([A-Za-z][A-Za-z0-9_]*)\\}/g, (m, k: string) => (k in params ? String(params[k]) : m));\n}\n\n// Legacy fixed-width title box (inner width 45). Frozen so the default\n// output stays byte-identical for hosts that don't pass labels.\nconst LEGACY_TITLE_BOX = [\n \"╭─────────────────────────────────────────────╮\",\n \"│ ACP Context Analysis │\",\n \"╰─────────────────────────────────────────────╯\",\n];\nconst MIN_INNER_WIDTH = 45;\n\nfunction charDisplayWidth(ch: string): number {\n const cp = ch.codePointAt(0)!;\n const wide =\n (cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo\n (cp >= 0x2e80 && cp <= 0xa4cf) || // CJK radicals .. Yi syllables\n (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul syllables\n (cp >= 0xf900 && cp <= 0xfaff) || // CJK compatibility ideographs\n (cp >= 0xfe30 && cp <= 0xfe4f) || // CJK compatibility forms\n (cp >= 0xff00 && cp <= 0xff60) || // fullwidth forms\n (cp >= 0xffe0 && cp <= 0xffe6) || // halfwidth/fullwidth forms\n cp >= 0x20000; // CJK extensions and beyond\n return wide ? 2 : 1;\n}\n\nexport function displayWidth(s: string): number {\n let w = 0;\n for (const ch of s) w += charDisplayWidth(ch);\n return w;\n}\n\n/** Pad s with trailing spaces to a DISPLAY width (wide chars count as two\n * columns), keeping bar-chart columns aligned for CJK category labels.\n * Identical to String.prototype.padEnd for ASCII input. */\nexport function padLabel(s: string, width: number): string {\n const dw = displayWidth(s);\n return dw >= width ? s : s + \" \".repeat(width - dw);\n}\n\n/** Decorative title box, width-aware so CJK titles stay aligned (wide chars\n * occupy two terminal columns). The exact default title renders the legacy\n * fixed box; any other title gets a computed box that widens past\n * MIN_INNER_WIDTH when the title is long. */\nexport function renderTitleBox(title: string): string[] {\n if (title === DEFAULT_PANEL_LABELS.title) return LEGACY_TITLE_BOX;\n const dw = displayWidth(title);\n const inner = Math.max(MIN_INNER_WIDTH, dw + 2);\n const padL = Math.floor((inner - dw) / 2);\n const padR = inner - dw - padL;\n return [\"╭\" + \"─\".repeat(inner) + \"╮\", `│${\" \".repeat(padL)}${title}${\" \".repeat(padR)}│`, \"╰\" + \"─\".repeat(inner) + \"╯\"];\n}\n","import { defaultCountTokens } from \"../tokenize.js\";\nimport { formatRanges } from \"../nudge-text.js\";\nimport type { CompressionState, NudgeDecision } from \"../types.js\";\nimport { formatCompactTokens } from \"./format.js\";\nimport { topicFallback } from \"./topic.js\";\nimport { viableRanges } from \"../viable.js\";\nimport { cacheHitStats, formatHitRate, type CacheUsageSample } from \"./cache.js\";\nimport { fill, padLabel, renderTitleBox, resolvePanelLabels, type PanelLabels } from \"./labels.js\";\n\nexport interface StatusPanelInput {\n /** Adapter identifier for the header, e.g. \"billion-context-omp@0.1.6\".\n * Omit to hide the version line. */\n version?: string;\n /** Host session accounting — the SAME number the host footer displays.\n * It includes compressed originals (summaries stay in the window), so it\n * shrinks slower than the sent view when compression prunes the\n * per-request projection. */\n tokenCount: number;\n /** Measured token count of the host system prompt (host-specific to\n * obtain; the kernel breakdown does not see it). */\n systemPromptTokens: number;\n /** Kernel state (blocks drive the Blocks section). */\n state: CompressionState;\n /** The nudge decision from core.processTurn for this turn, if any. The\n * panel applies the viability filter to compressibleRanges itself. */\n nudge: NudgeDecision | undefined;\n /** Configured model context window, in tokens. */\n modelContextLimit: number;\n /** Estimate of the FULL (unpruned) core-message projection, computed\n * with the SAME countTokens the core uses (kernel default = CJK-aware\n * defaultCountTokens) — the same estimation scale as the kernel\n * breakdown. When provided, the panel derives `Session-only` on that\n * scale (unpruned − sent). Without it the line is omitted: subtracting\n * the host's provider-scale number from an estimate-scale number\n * invents a third, meaningless scale (issue #18 \"看板统计的和拆分的有差异\"). */\n unprunedTokens?: number;\n /** Localized display labels; every key optional, per-key fallback to the\n * English defaults (DEFAULT_PANEL_LABELS). Display-only — model-facing\n * text is unaffected. */\n labels?: Partial<PanelLabels>;\n /** Per-request prompt-cache usage (from assistant messages' provider-\n * reported `usage`). Requests without cache reporting are excluded by\n * cacheHitStats; when no counted request remains, the section is\n * omitted entirely. Omit the field to hide the section. */\n cacheUsages?: ReadonlyArray<CacheUsageSample>;\n /** Token formatter override (defaults to formatCompactTokens). */\n fmtTokens?: (n: number) => string;\n}\n\nfunction bar(value: number, total: number, width: number = 20): string {\n if (total === 0) return \"\";\n const filled = Math.max(0, Math.min(width, Math.round((value / total) * width)));\n return \"█\".repeat(filled) + \"░\".repeat(width - filled);\n}\n\n/** Render the /acp status panel. Three token numbers, each labeled with\n * its own scale, never mixed in arithmetic:\n * - Session accounting (host footer scale): the host's reported context\n * size INCLUDING compressed originals (summaries stay in the window), so\n * it shrinks slower than the sent view when compression prunes the\n * per-request projection.\n * - Sent view (estimated, kernel countTokens scale): what actually\n * reaches the LLM after compression (kernel's classification over the\n * pruned projection + measured system prompt). This is the number\n * compression controls.\n * - Session-only (estimated, kernel countTokens scale): unpruned\n * projection − sent view; the compressed originals pruned from every\n * request.\n * Subtracting the host number from an estimate produced numbers that\n * reconciled with neither scale (\"Framework 390K\", \"session-only 29k vs\n * 112k compressed\") — that is what issue #18 reported. */\nexport function buildStatusPanel(input: StatusPanelInput): string {\n const { tokenCount, state, nudge, modelContextLimit } = input;\n const L = resolvePanelLabels(input.labels);\n const fmt = input.fmtTokens ?? formatCompactTokens;\n const bd = nudge?.contextBreakdown;\n const limit = modelContextLimit;\n const classified = bd ? bd.system + bd.tool + bd.summaries + bd.code + bd.text : 0;\n const systemPromptTokens = input.systemPromptTokens;\n const sentTotal = classified + systemPromptTokens;\n // Same-scale derivation only: both sides use the core's countTokens\n // estimate (kernel default = CJK-aware defaultCountTokens). The host\n // footer's tokenCount (provider-anchored, session-tree) is displayed as\n // its own line and never fed into an arithmetic difference with these.\n const sessionOnly = input.unprunedTokens !== undefined ? Math.max(0, input.unprunedTokens - sentTotal) : 0;\n const displayTotal = tokenCount;\n const displayPct = limit > 0 ? Math.round((displayTotal / limit) * 100) : 0;\n const sentPct = limit > 0 ? Math.round((sentTotal / limit) * 100) : 0;\n const activeBlocksList = state.blocks.filter((b) => b.active);\n const totalBlocksList = state.blocks;\n\n const lines: string[] = [];\n\n lines.push(...renderTitleBox(L.title));\n if (input.version) lines.push(input.version);\n lines.push(\"\");\n lines.push(fill(L.context, { pct: displayPct, used: fmt(displayTotal), limit: fmt(limit) }));\n\n if (nudge && bd) {\n const growth = bd.growth;\n if (growth > 0 && displayTotal > 0) {\n lines.push(fill(L.growth, { growth: fmt(growth) }));\n }\n lines.push(\"\");\n let sentLine = fill(L.sent, { sent: fmt(sentTotal) });\n if (limit > 0) sentLine += fill(L.sentOfLimit, { pct: sentPct });\n lines.push(sentLine);\n if (input.unprunedTokens !== undefined && sessionOnly > 0) {\n lines.push(fill(L.sessionOnly, { only: fmt(sessionOnly) }));\n }\n lines.push(\"\");\n lines.push(L.breakdown);\n\n const categories: Array<{ label: string; value: number }> = [\n { label: L.catTool, value: bd.tool },\n { label: L.catSysPrompt, value: systemPromptTokens },\n { label: L.catText, value: bd.text },\n { label: L.catCode, value: bd.code },\n { label: L.catSummaries, value: bd.summaries },\n ];\n\n for (const cat of categories) {\n if (cat.value <= 0) continue;\n const pct = sentTotal > 0 ? Math.round((cat.value / sentTotal) * 100) : 0;\n const b = bar(cat.value, sentTotal);\n lines.push(` ${padLabel(cat.label, 10)} ${b} ${String(pct).padStart(3)}% ${fmt(cat.value)}`);\n }\n }\n\n if (input.cacheUsages) {\n const cache = cacheHitStats(input.cacheUsages);\n if (cache.requests > 0 && cache.session !== undefined && cache.last !== undefined) {\n lines.push(\"\");\n lines.push(\n fill(L.promptCache, {\n last: formatHitRate(cache.last),\n session: formatHitRate(cache.session),\n read: fmt(cache.cacheRead),\n billed: fmt(cache.billedPrompt),\n requests: cache.requests,\n }),\n );\n }\n }\n\n lines.push(\"\");\n\n if (nudge) {\n if (nudge.shouldInject) {\n const tierInfo = nudge.tier ? fill(L.nudgeTierInfo, { tier: nudge.tier }) : \"\";\n lines.push(fill(L.nudgeActive, { tierInfo, reason: nudge.reason }));\n } else {\n lines.push(fill(L.nudgeIdle, { reason: nudge.reason }));\n }\n }\n\n const ranges = viableRanges(nudge?.compressibleRanges ?? []);\n const protectedRanges = nudge?.protectedRanges ?? [];\n if (ranges.length > 0 || protectedRanges.length > 0) {\n lines.push(\"\");\n lines.push(formatRanges(ranges, protectedRanges));\n }\n\n if (activeBlocksList.length > 0) {\n lines.push(\"\");\n lines.push(fill(L.blocksHeader, { active: activeBlocksList.length, total: totalBlocksList.length, tokens: fmt(state.stats.tokensCompressed) }));\n for (const b of activeBlocksList) {\n const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || \"\")}`;\n const summaryTok = defaultCountTokens(b.summary || \"\");\n const origTok = b.compressedTokens > 0 ? b.compressedTokens : summaryTok;\n lines.push(` [${b.blockId}] T${b.tier} ${fmt(origTok)}→${fmt(summaryTok)}${topic}`);\n }\n } else if (totalBlocksList.length > 0) {\n lines.push(\"\");\n lines.push(fill(L.blocksHeader, { active: 0, total: totalBlocksList.length, tokens: fmt(state.stats.tokensCompressed) }));\n } else {\n lines.push(\"\");\n lines.push(L.blocksNone);\n }\n\n lines.push(\"\");\n lines.push(L.tagVisibility);\n\n return lines.join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,68 @@
1
+ /** Display labels for buildStatusPanel. The English defaults below are the
2
+ * key source of truth; hosts pass Partial<PanelLabels> to localize the panel
3
+ * (billion-context-pi issue #153). This is a DISPLAY-ONLY surface: these
4
+ * strings render in the terminal and never reach the model, so translating
5
+ * them cannot affect compression/nudge behavior.
6
+ *
7
+ * Templates use `{name}` placeholders filled by fill(). Keys omitted by a
8
+ * host pack fall back to the English default per key (partial packs degrade
9
+ * gracefully instead of failing wholesale). */
10
+ export interface PanelLabels {
11
+ /** Title rendered inside the decorative box. */
12
+ title: string;
13
+ /** Params: pct (number), used / limit (formatted token counts). */
14
+ context: string;
15
+ /** Params: growth (formatted). Line rendered only when growth > 0. */
16
+ growth: string;
17
+ /** Params: sent (formatted). */
18
+ sent: string;
19
+ /** Params: pct (number). Appended to the sent line only when a context
20
+ * limit is configured; omit the placeholder in a pack to drop it. */
21
+ sentOfLimit: string;
22
+ /** Params: only (formatted). Line rendered only when unprunedTokens is
23
+ * provided and the derived value is positive. */
24
+ sessionOnly: string;
25
+ breakdown: string;
26
+ catTool: string;
27
+ catSysPrompt: string;
28
+ catText: string;
29
+ catCode: string;
30
+ catSummaries: string;
31
+ /** Params: last / session (hit-rate strings), read / billed (formatted),
32
+ * requests (number). */
33
+ promptCache: string;
34
+ /** Params: tierInfo (string, empty when no tier), reason (verbatim model-
35
+ * facing text — keep it in your template verbatim). */
36
+ nudgeActive: string;
37
+ /** Params: tier (number). */
38
+ nudgeTierInfo: string;
39
+ /** Params: reason (verbatim model-facing text). */
40
+ nudgeIdle: string;
41
+ /** Params: active / total (numbers), tokens (formatted). Used for both the
42
+ * active and zero-active block counts. */
43
+ blocksHeader: string;
44
+ /** Rendered when no blocks exist at all. */
45
+ blocksNone: string;
46
+ tagVisibility: string;
47
+ }
48
+ export declare const DEFAULT_PANEL_LABELS: PanelLabels;
49
+ /** Merge a partial label pack over the English defaults, ignoring entries
50
+ * whose value is not a string (explicit `undefined` from a config merge,
51
+ * `null`, …). A plain `{ ...defaults, ...pack }` spread lets one undefined
52
+ * key blank out its default and crash the display-width renderers. */
53
+ export declare function resolvePanelLabels(labels?: Partial<PanelLabels>): PanelLabels;
54
+ /** Substitute `{name}` placeholders from params. Unknown placeholders are
55
+ * left intact so a typo in a host pack is visible on screen instead of
56
+ * silently dropping information. */
57
+ export declare function fill(template: string, params: Record<string, string | number>): string;
58
+ export declare function displayWidth(s: string): number;
59
+ /** Pad s with trailing spaces to a DISPLAY width (wide chars count as two
60
+ * columns), keeping bar-chart columns aligned for CJK category labels.
61
+ * Identical to String.prototype.padEnd for ASCII input. */
62
+ export declare function padLabel(s: string, width: number): string;
63
+ /** Decorative title box, width-aware so CJK titles stay aligned (wide chars
64
+ * occupy two terminal columns). The exact default title renders the legacy
65
+ * fixed box; any other title gets a computed box that widens past
66
+ * MIN_INNER_WIDTH when the title is long. */
67
+ export declare function renderTitleBox(title: string): string[];
68
+ //# sourceMappingURL=labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"labels.d.ts","sourceRoot":"","sources":["../../src/panel/labels.ts"],"names":[],"mappings":"AAAA;;;;;;;;gDAQgD;AAChD,MAAM,WAAW,WAAW;IAC1B,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb;0EACsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB;sDACkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB;6BACyB;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB;4DACwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,6BAA6B;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB;+CAC2C;IAC3C,YAAY,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,oBAAoB,EAAE,WAoBlC,CAAC;AAEF;;;uEAGuE;AACvE,wBAAgB,kBAAkB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,CAQ7E;AAED;;qCAEqC;AACrC,wBAAgB,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAEtF;AAyBD,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAI9C;AAED;;4DAE4D;AAC5D,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED;;;8CAG8C;AAC9C,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAOtD"}
@@ -1,5 +1,6 @@
1
1
  import type { CompressionState, NudgeDecision } from "../types.js";
2
2
  import { type CacheUsageSample } from "./cache.js";
3
+ import { type PanelLabels } from "./labels.js";
3
4
  export interface StatusPanelInput {
4
5
  /** Adapter identifier for the header, e.g. "billion-context-omp@0.1.6".
5
6
  * Omit to hide the version line. */
@@ -27,6 +28,10 @@ export interface StatusPanelInput {
27
28
  * the host's provider-scale number from an estimate-scale number
28
29
  * invents a third, meaningless scale (issue #18 "看板统计的和拆分的有差异"). */
29
30
  unprunedTokens?: number;
31
+ /** Localized display labels; every key optional, per-key fallback to the
32
+ * English defaults (DEFAULT_PANEL_LABELS). Display-only — model-facing
33
+ * text is unaffected. */
34
+ labels?: Partial<PanelLabels>;
30
35
  /** Per-request prompt-cache usage (from assistant messages' provider-
31
36
  * reported `usage`). Requests without cache reporting are excluded by
32
37
  * cacheHitStats; when no counted request remains, the section is
@@ -1 +1 @@
1
- {"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/panel/panel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAInE,OAAO,EAAgC,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEjF,MAAM,WAAW,gBAAgB;IAC/B;yCACqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;kCAG8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB;yDACqD;IACrD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,KAAK,EAAE,gBAAgB,CAAC;IACxB;2EACuE;IACvE,KAAK,EAAE,aAAa,GAAG,SAAS,CAAC;IACjC,kDAAkD;IAClD,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;;;yEAMqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;gEAG4D;IAC5D,WAAW,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC9C,kEAAkE;IAClE,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CACnC;AAQD;;;;;;;;;;;;;;;2DAe2D;AAC3D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CA0GhE"}
1
+ {"version":3,"file":"panel.d.ts","sourceRoot":"","sources":["../../src/panel/panel.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAInE,OAAO,EAAgC,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAsD,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAEnG,MAAM,WAAW,gBAAgB;IAC/B;yCACqC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;kCAG8B;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB;yDACqD;IACrD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sDAAsD;IACtD,KAAK,EAAE,gBAAgB,CAAC;IACxB;2EACuE;IACvE,KAAK,EAAE,aAAa,GAAG,SAAS,CAAC;IACjC,kDAAkD;IAClD,iBAAiB,EAAE,MAAM,CAAC;IAC1B;;;;;;yEAMqE;IACrE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;8BAE0B;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9B;;;gEAG4D;IAC5D,WAAW,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC9C,kEAAkE;IAClE,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CACnC;AAQD;;;;;;;;;;;;;;;2DAe2D;AAC3D,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAiHhE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acp-kernel",
3
- "version": "0.0.64-pr.254.101",
3
+ "version": "0.0.64-pr.255.103",
4
4
  "description": "Framework-agnostic context-compression engine (model-driven, 3-tier LSM). Pure core: no host dependency.",
5
5
  "license": "MIT",
6
6
  "author": "ranxianglei",