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

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 } 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,MAAM,aAAa,CAAC;AACjG,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,67 @@ 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 fill(template, params) {
79
+ return template.replace(/\{([A-Za-z][A-Za-z0-9_]*)\}/g, (m, k) => k in params ? String(params[k]) : m);
80
+ }
81
+ var LEGACY_TITLE_BOX = [
82
+ "\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",
83
+ "\u2502 ACP Context Analysis \u2502",
84
+ "\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"
85
+ ];
86
+ var MIN_INNER_WIDTH = 45;
87
+ function charDisplayWidth(ch) {
88
+ const cp = ch.codePointAt(0);
89
+ const wide = cp >= 4352 && cp <= 4447 || // Hangul Jamo
90
+ cp >= 11904 && cp <= 42191 || // CJK radicals .. Yi syllables
91
+ cp >= 44032 && cp <= 55203 || // Hangul syllables
92
+ cp >= 63744 && cp <= 64255 || // CJK compatibility ideographs
93
+ cp >= 65072 && cp <= 65103 || // CJK compatibility forms
94
+ cp >= 65280 && cp <= 65376 || // fullwidth forms
95
+ cp >= 65504 && cp <= 65510 || // halfwidth/fullwidth forms
96
+ cp >= 131072;
97
+ return wide ? 2 : 1;
98
+ }
99
+ function displayWidth(s) {
100
+ let w = 0;
101
+ for (const ch of s) w += charDisplayWidth(ch);
102
+ return w;
103
+ }
104
+ function padLabel(s, width) {
105
+ const dw = displayWidth(s);
106
+ return dw >= width ? s : s + " ".repeat(width - dw);
107
+ }
108
+ function renderTitleBox(title) {
109
+ if (title === DEFAULT_PANEL_LABELS.title) return LEGACY_TITLE_BOX;
110
+ const dw = displayWidth(title);
111
+ const inner = Math.max(MIN_INNER_WIDTH, dw + 2);
112
+ const padL = Math.floor((inner - dw) / 2);
113
+ const padR = inner - dw - padL;
114
+ return ["\u256D" + "\u2500".repeat(inner) + "\u256E", `\u2502${" ".repeat(padL)}${title}${" ".repeat(padR)}\u2502`, "\u2570" + "\u2500".repeat(inner) + "\u256F"];
115
+ }
116
+
56
117
  // src/panel/panel.ts
57
118
  function bar(value, total, width = 20) {
58
119
  if (total === 0) return "";
@@ -61,6 +122,7 @@ function bar(value, total, width = 20) {
61
122
  }
62
123
  function buildStatusPanel(input) {
63
124
  const { tokenCount, state, nudge, modelContextLimit } = input;
125
+ const L = { ...DEFAULT_PANEL_LABELS, ...input.labels };
64
126
  const fmt = input.fmtTokens ?? formatCompactTokens;
65
127
  const bd = nudge?.contextBreakdown;
66
128
  const limit = modelContextLimit;
@@ -74,36 +136,36 @@ function buildStatusPanel(input) {
74
136
  const activeBlocksList = state.blocks.filter((b) => b.active);
75
137
  const totalBlocksList = state.blocks;
76
138
  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");
139
+ lines.push(...renderTitleBox(L.title));
80
140
  if (input.version) lines.push(input.version);
81
141
  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`);
142
+ lines.push(fill(L.context, { pct: displayPct, used: fmt(displayTotal), limit: fmt(limit) }));
83
143
  if (nudge && bd) {
84
144
  const growth = bd.growth;
85
145
  if (growth > 0 && displayTotal > 0) {
86
- lines.push(`Growth: +${fmt(growth)} since last nudge`);
146
+ lines.push(fill(L.growth, { growth: fmt(growth) }));
87
147
  }
88
148
  lines.push("");
89
- lines.push(`Sent to LLM (after compression, est.): ${fmt(sentTotal)}${limit > 0 ? ` (${sentPct}% of limit)` : ""}`);
149
+ let sentLine = fill(L.sent, { sent: fmt(sentTotal) });
150
+ if (limit > 0) sentLine += fill(L.sentOfLimit, { pct: sentPct });
151
+ lines.push(sentLine);
90
152
  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`);
153
+ lines.push(fill(L.sessionOnly, { only: fmt(sessionOnly) }));
92
154
  }
93
155
  lines.push("");
94
- lines.push("Token Breakdown (sent view):");
156
+ lines.push(L.breakdown);
95
157
  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 }
158
+ { label: L.catTool, value: bd.tool },
159
+ { label: L.catSysPrompt, value: systemPromptTokens },
160
+ { label: L.catText, value: bd.text },
161
+ { label: L.catCode, value: bd.code },
162
+ { label: L.catSummaries, value: bd.summaries }
101
163
  ];
102
164
  for (const cat of categories) {
103
165
  if (cat.value <= 0) continue;
104
166
  const pct = sentTotal > 0 ? Math.round(cat.value / sentTotal * 100) : 0;
105
167
  const b = bar(cat.value, sentTotal);
106
- lines.push(` ${cat.label.padEnd(10)} ${b} ${String(pct).padStart(3)}% ${fmt(cat.value)}`);
168
+ lines.push(` ${padLabel(cat.label, 10)} ${b} ${String(pct).padStart(3)}% ${fmt(cat.value)}`);
107
169
  }
108
170
  }
109
171
  if (input.cacheUsages) {
@@ -111,17 +173,23 @@ function buildStatusPanel(input) {
111
173
  if (cache.requests > 0 && cache.session !== void 0 && cache.last !== void 0) {
112
174
  lines.push("");
113
175
  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)`
176
+ fill(L.promptCache, {
177
+ last: formatHitRate(cache.last),
178
+ session: formatHitRate(cache.session),
179
+ read: fmt(cache.cacheRead),
180
+ billed: fmt(cache.billedPrompt),
181
+ requests: cache.requests
182
+ })
115
183
  );
116
184
  }
117
185
  }
118
186
  lines.push("");
119
187
  if (nudge) {
120
188
  if (nudge.shouldInject) {
121
- const tierInfo = nudge.tier ? ` [T${nudge.tier} distillation]` : "";
122
- lines.push(`Nudge: ACTIVE${tierInfo} \u2014 ${nudge.reason}`);
189
+ const tierInfo = nudge.tier ? fill(L.nudgeTierInfo, { tier: nudge.tier }) : "";
190
+ lines.push(fill(L.nudgeActive, { tierInfo, reason: nudge.reason }));
123
191
  } else {
124
- lines.push(`Nudge: idle \u2014 ${nudge.reason}`);
192
+ lines.push(fill(L.nudgeIdle, { reason: nudge.reason }));
125
193
  }
126
194
  }
127
195
  const ranges = viableRanges(nudge?.compressibleRanges ?? []);
@@ -132,7 +200,7 @@ function buildStatusPanel(input) {
132
200
  }
133
201
  if (activeBlocksList.length > 0) {
134
202
  lines.push("");
135
- lines.push(`Blocks: ${activeBlocksList.length} active / ${totalBlocksList.length} total (${fmt(state.stats.tokensCompressed)} tokens compressed, cumulative)`);
203
+ lines.push(fill(L.blocksHeader, { active: activeBlocksList.length, total: totalBlocksList.length, tokens: fmt(state.stats.tokensCompressed) }));
136
204
  for (const b of activeBlocksList) {
137
205
  const topic = b.topic ? `: ${b.topic}` : `: ${topicFallback(b.summary || "")}`;
138
206
  const summaryTok = defaultCountTokens(b.summary || "");
@@ -141,20 +209,25 @@ function buildStatusPanel(input) {
141
209
  }
142
210
  } else if (totalBlocksList.length > 0) {
143
211
  lines.push("");
144
- lines.push(`Blocks: 0 active / ${totalBlocksList.length} total (${fmt(state.stats.tokensCompressed)} tokens compressed, cumulative)`);
212
+ lines.push(fill(L.blocksHeader, { active: 0, total: totalBlocksList.length, tokens: fmt(state.stats.tokensCompressed) }));
145
213
  } else {
146
214
  lines.push("");
147
- lines.push("Blocks: none (nothing compressed yet)");
215
+ lines.push(L.blocksNone);
148
216
  }
149
217
  lines.push("");
150
- lines.push("Tag visibility: tags injected to LLM only (deep copy), not persisted in session, not shown in terminal.");
218
+ lines.push(L.tagVisibility);
151
219
  return lines.join("\n");
152
220
  }
153
221
  export {
222
+ DEFAULT_PANEL_LABELS,
154
223
  buildStatusPanel,
155
224
  cacheHitStats,
225
+ displayWidth,
226
+ fill,
156
227
  formatCompactTokens,
157
228
  formatHitRate,
229
+ padLabel,
230
+ renderTitleBox,
158
231
  topicFallback
159
232
  };
160
233
  //# 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/** 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 { DEFAULT_PANEL_LABELS, fill, padLabel, renderTitleBox, 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: PanelLabels = { ...DEFAULT_PANEL_LABELS, ...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,63 @@
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
+ /** Substitute `{name}` placeholders from params. Unknown placeholders are
50
+ * left intact so a typo in a host pack is visible on screen instead of
51
+ * silently dropping information. */
52
+ export declare function fill(template: string, params: Record<string, string | number>): string;
53
+ export declare function displayWidth(s: string): number;
54
+ /** Pad s with trailing spaces to a DISPLAY width (wide chars count as two
55
+ * columns), keeping bar-chart columns aligned for CJK category labels.
56
+ * Identical to String.prototype.padEnd for ASCII input. */
57
+ export declare function padLabel(s: string, width: number): string;
58
+ /** Decorative title box, width-aware so CJK titles stay aligned (wide chars
59
+ * occupy two terminal columns). The exact default title renders the legacy
60
+ * fixed box; any other title gets a computed box that widens past
61
+ * MIN_INNER_WIDTH when the title is long. */
62
+ export declare function renderTitleBox(title: string): string[];
63
+ //# 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;;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,EAAwD,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAErG,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.102",
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",