dsh-token-total 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -5,9 +5,30 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  //#region \0rolldown/runtime.js
8
+ var __create = Object.create;
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __getProtoOf = Object.getPrototypeOf;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
14
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod, mod.exports), cb = null), mod.exports);
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
17
+ key = keys[i];
18
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
19
+ get: ((k) => from[k]).bind(null, key),
20
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
21
+ });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
26
+ value: mod,
27
+ enumerable: true
28
+ }) : target, mod));
9
29
  //#endregion
10
30
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
31
+ _deepseek_ai_dsh_client_ui_primitives = __toESM(_deepseek_ai_dsh_client_ui_primitives, 1);
11
32
  let react = require("react");
12
33
  let react_dom = require("react-dom");
13
34
  let react_jsx_runtime = require("react/jsx-runtime");
@@ -30783,6 +30804,80 @@ window.__ModuleLoader__.load({
30783
30804
  });
30784
30805
  }
30785
30806
  //#endregion
30807
+ //#region src/client/stats-icons.tsx
30808
+ /**
30809
+ * The stat-pill glyphs.
30810
+ *
30811
+ * The shell freezes its primitives table, and that table's icon names moved
30812
+ * with the host line: 0.2.1-rc.2 exports `IconGaugeOutlineRegular` /
30813
+ * `IconDatabaseOutlineRegular` (plus the `Medium` weight) and no longer
30814
+ * exports the `…Outline16` names this plugin used to import. A missing named
30815
+ * export is `undefined` at runtime, and React then throws "Element type is
30816
+ * invalid" on the entry's first render — the shell's slot boundary catches it,
30817
+ * ABDICATES the entry, and the shadowed official pill silently returns. That
30818
+ * failure mode is invisible in types (the package's own declarations still
30819
+ * describe the old names) and looks exactly like "the plugin did nothing".
30820
+ *
30821
+ * So the glyph is resolved from the runtime table by preference order and
30822
+ * falls back to an inline drawing of the same shape when no name exists, which
30823
+ * keeps one published build rendering on every host line.
30824
+ */
30825
+ const table = _deepseek_ai_dsh_client_ui_primitives;
30826
+ /** First exported function component among `names`, or null when the shell
30827
+ * exports none of them (an older or newer icon roster). */
30828
+ function resolveIcon(...names) {
30829
+ for (const name of names) {
30830
+ const candidate = table[name];
30831
+ if (typeof candidate === "function") return candidate;
30832
+ }
30833
+ return null;
30834
+ }
30835
+ const resolvedGauge = resolveIcon("IconGaugeOutlineRegular", "IconGaugeOutlineMedium", "IconGaugeOutline16");
30836
+ const resolvedDatabase = resolveIcon("IconDatabaseOutlineRegular", "IconDatabaseOutlineMedium", "IconDatabaseOutline16");
30837
+ /** Gauge glyph for the activity pill (counts + throughput). */
30838
+ function GaugeIcon() {
30839
+ if (resolvedGauge !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(resolvedGauge, {});
30840
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
30841
+ viewBox: "0 0 16 16",
30842
+ "aria-hidden": "true",
30843
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
30844
+ d: "M8 2.5a5.5 5.5 0 0 1 5.5 5.5M8 8l2.6-2.6M2.5 8A5.5 5.5 0 0 1 8 2.5",
30845
+ fill: "none",
30846
+ stroke: "currentColor",
30847
+ strokeWidth: "1.3",
30848
+ strokeLinecap: "round"
30849
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
30850
+ cx: "8",
30851
+ cy: "8",
30852
+ r: "1",
30853
+ fill: "currentColor"
30854
+ })]
30855
+ });
30856
+ }
30857
+ /** Database glyph for the token-usage pill. */
30858
+ function DatabaseIcon() {
30859
+ if (resolvedDatabase !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(resolvedDatabase, {});
30860
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
30861
+ viewBox: "0 0 16 16",
30862
+ "aria-hidden": "true",
30863
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("ellipse", {
30864
+ cx: "8",
30865
+ cy: "3.6",
30866
+ rx: "4.6",
30867
+ ry: "1.9",
30868
+ fill: "none",
30869
+ stroke: "currentColor",
30870
+ strokeWidth: "1.3"
30871
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
30872
+ d: "M3.4 3.6v8.8c0 1.05 2.06 1.9 4.6 1.9s4.6-.85 4.6-1.9V3.6M3.4 8c0 1.05 2.06 1.9 4.6 1.9s4.6-.85 4.6-1.9",
30873
+ fill: "none",
30874
+ stroke: "currentColor",
30875
+ strokeWidth: "1.3",
30876
+ strokeLinecap: "round"
30877
+ })]
30878
+ });
30879
+ }
30880
+ //#endregion
30786
30881
  //#region src/client/stat-dialog.ts
30787
30882
  /**
30788
30883
  * One trigger-anchored stat dialog seat for the composer-dock pills.
@@ -30866,29 +30961,51 @@ window.__ModuleLoader__.load({
30866
30961
  function billedInputTokens(usage) {
30867
30962
  return usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
30868
30963
  }
30964
+ /** Round a cache-read ratio to an integer percentage, ties rounded up (official). */
30965
+ function roundedIntegerPercent(cacheReadTokens, denominator) {
30966
+ const denominatorQuotient = Math.floor(denominator / 200);
30967
+ const denominatorRemainder = denominator % 200;
30968
+ let lower = 0;
30969
+ let upper = 100;
30970
+ while (lower < upper) {
30971
+ const candidate = Math.floor((lower + upper + 1) / 2);
30972
+ const factor = candidate * 2 - 1;
30973
+ if (cacheReadTokens >= factor * denominatorQuotient + Math.ceil(factor * denominatorRemainder / 200)) lower = candidate;
30974
+ else upper = candidate - 1;
30975
+ }
30976
+ return lower;
30977
+ }
30869
30978
  /**
30870
- * Plugin readout: the same ratio with exactly two decimals (85.25% → "85.25",
30871
- * full hit → "100.00"); returns the display digits without the percent sign.
30979
+ * Display-ready cache-hit share of prompt-side input (official replica):
30980
+ * integer text when integer rounding stays below 100, otherwise the minimum
30981
+ * decimal precision that still rounds below 100; full hit returns 100, and
30982
+ * no billed input returns null.
30872
30983
  */
30873
- function cacheHitPercentPrecise(usage) {
30984
+ function cacheHitPercent(usage) {
30874
30985
  const denominator = billedInputTokens(usage);
30875
30986
  if (denominator === 0) return null;
30876
- if (usage.uncachedInputTokens + usage.cacheWriteTokens === 0) return "100.00";
30877
- const percent = Math.round(usage.cacheReadTokens * 1e4 / denominator) / 100;
30878
- return Math.min(percent, 99.99).toFixed(2);
30879
- }
30880
- /** Plugin readout: the five-item breakdown of one usage sample. */
30881
- function tokenBreakdown(usage) {
30882
- const cacheHit = usage.cacheReadTokens;
30883
- const cacheMiss = usage.uncachedInputTokens + usage.cacheWriteTokens;
30884
- const input = cacheHit + cacheMiss;
30885
- return {
30886
- total: input + usage.outputTokens,
30887
- input,
30888
- cacheHit,
30889
- cacheMiss,
30890
- output: usage.outputTokens
30891
- };
30987
+ const missedInputTokens = usage.uncachedInputTokens + usage.cacheWriteTokens;
30988
+ if (missedInputTokens === 0) return "100";
30989
+ const integerPercent = roundedIntegerPercent(usage.cacheReadTokens, denominator);
30990
+ if (integerPercent < 100) return String(integerPercent);
30991
+ let decimalPlaces = 1;
30992
+ let scaledDoubleGap = missedInputTokens * 200;
30993
+ const denominatorTens = Math.floor(denominator / 10);
30994
+ while (scaledDoubleGap <= denominatorTens) {
30995
+ scaledDoubleGap *= 10;
30996
+ decimalPlaces += 1;
30997
+ }
30998
+ const denominatorOnes = denominator % 10;
30999
+ let roundedLoss = 5;
31000
+ for (let loss = 1; loss < 5; loss += 1) {
31001
+ const factor = loss * 2 + 1;
31002
+ const threshold = factor * denominatorTens + Math.floor(factor * denominatorOnes / 10);
31003
+ if (scaledDoubleGap <= threshold) {
31004
+ roundedLoss = loss;
31005
+ break;
31006
+ }
31007
+ }
31008
+ return `99.${"9".repeat(decimalPlaces - 1)}${10 - roundedLoss}`;
30892
31009
  }
30893
31010
  /** Compact token count: 517 / 12.2K / 517K / 1.2M (official formatTokens).
30894
31011
  * Named formatTokensCompact (not formatTokens) — the panel's format.ts
@@ -31285,7 +31402,7 @@ window.__ModuleLoader__.load({
31285
31402
  }
31286
31403
  //#endregion
31287
31404
  //#region \0token-total-css:/home/runner/work/dsh-token-total/dsh-token-total/src/client/StatsLineEnhanced.module.css?inline
31288
- const css$1 = "/* Session stats pills under the composer (shadow of the official StatsPills):\n a gauge pill (counts + output speed, click opens the time dialog) and a\n database pill (total tokens + cache hit, click opens the usage dialog).\n Replicated from the official StatsPills.module_348665330.css_348665330 + stat-dialog.module_348665330.css_348665330\n (13/20 tertiary tier, hover-pill affordance, shared dialog skin).\n\n Host contract: the row serves two containers at once, and picks between them\n at attach time — the component marks which one it landed in (see\n StatsLineEnhanced's `markDockContainer`):\n - through 0.1.6-alpha.1 the dock slot renders straight into InputBar's own\n column, and this row owns the content-width clamp, its side gutters and\n its 4px top pad — the official StatsPills.module_348665330.css_348665330 of that line;\n - 0.1.6-alpha.2 renders it inside InputBar's flex `.dock_348665330` beside the resident\n ContextMeter, which took all three jobs over, so the row only has to\n shrink inside it (the official stylesheet's current form).\n Both forms are kept side by side because one published build has to render\n correctly on either host line. */\n\n.root_348665330 {\n display: flex;\n justify-content: center;\n gap: 12px;\n box-sizing: border-box;\n /* <= 0.1.6-alpha.1: this row is the composer column's content-width box. */\n width: 100%;\n max-width: var(--dsh-chat-content-width);\n margin: 0 auto;\n padding: 4px calc(var(--dsh-composer-side-clearance) + 16px) 0px;\n font-size: var(--dsh-content-font-size-secondary, 13px);\n line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));\n}\n\n/* 0.1.6-alpha.2+: InputBar's `.dock_348665330` owns the centring, the 12px gap, the top\n pad and the side clearance. */\n.root_348665330[data-dock-row] {\n width: auto;\n min-width: 0;\n max-width: 100%;\n margin: 0;\n padding: 0;\n}\n\n/* Dialog anchor: shrink-wraps its pill so the panel clamp measures the pill. */\n.anchor_348665330 {\n display: inline-flex;\n min-width: 0;\n}\n\n/* Split-mode anchor (DSH >= 0.2.1-alpha.1). The host split the single `stats`\n dock entry into one entry per pill and let its own `.dock_348665330` flex row own the\n layout, so the anchor IS the entry root and carries the text tier `.root_348665330`\n used to set — the official StatsPills.module_348665330.css_348665330 moved exactly this pair of\n declarations from `.root_348665330` onto `.anchor_348665330`. The tier stays off the plain\n `.anchor_348665330` because the pre-split row must keep its own `.root_348665330` tier. */\n.dockAnchor_348665330 {\n font-size: calc(var(--dsh-content-font-size-secondary, 13px) - 1px);\n line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));\n}\n\n.pill_348665330 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n box-sizing: border-box;\n max-width: 100%;\n padding: 1px 8px;\n border: none;\n border-radius: 24px;\n background: transparent;\n color: var(--dsw-alias-label-tertiary);\n font: inherit;\n font-variant-numeric: tabular-nums;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.pill_348665330 svg {\n width: 14px;\n height: 14px;\n flex: none;\n}\n\n/* Only the button form (a pill with dialog rows to open) invites interaction;\n the static span form keeps the resting tertiary reading. */\nbutton.pill_348665330 {\n cursor: pointer;\n}\n\nbutton.pill_348665330:hover,\nbutton.pill_348665330[aria-expanded='true'] {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.label_348665330 {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sep_348665330 {\n color: var(--dsw-alias-separator-primary);\n margin: 0 6px;\n}\n\n/* Reserved slot for the speed figure: the official pill's figure only changes\n when a step settles, but the live reading refreshes on every beat. A width\n that tracked the digits would reflow the centred row on each of them and drag\n the counts pill beside it along; 10ch covers \"1200 tok/s\", the widest shape\n formatTokensPerSecond can produce. */\n.speed_348665330 {\n display: inline-block;\n min-width: 10ch;\n}\n\n/* Marks a usage figure that now includes subagent spend. Deliberately quiet:\n it rides inside the official pill's label and must not read as a badge or\n compete with the token count beside it. */\n.subagentMark_348665330 {\n color: var(--dsw-alias-label-tertiary);\n font-variant-numeric: tabular-nums;\n}\n\n/* Portal dialog surface: fixed in the viewport, left/top supplied inline from\n the anchored-position clamp (12px viewport margin, layers above overlays).\n Material = the official stat-dialog PAIRING, which dsh 0.1.7's menu\n redesign made mandatory: `--dsw-specific-menu` is translucent there\n (rgba 58%/45%), so the blur token must ride along — the 0.1.5-era recipe\n this file copied was a single opaque line, and a fill-only copy reads as\n bare transparency over the page (issue #6). */\n.dialogPanel_348665330 {\n position: fixed;\n z-index: 1100;\n box-sizing: border-box;\n width: max-content;\n min-width: min(300px, calc(100vw - 24px));\n max-width: min(440px, calc(100vw - 24px));\n padding: 16px;\n border: 0;\n border-radius: 12px;\n background: var(--dsw-specific-menu);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-prominent);\n font-size: 12px;\n line-height: 18px;\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n}\n\n/* Dialog heading row: the section name left, the headline value right. */\n.dialogTitle_348665330 {\n display: flex;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 8px;\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n/* Rule under the heading, above its rows. */\n.dialogTitleRule_348665330 {\n margin-bottom: 10px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.dialogTitleValue_348665330 {\n font-variant-numeric: tabular-nums;\n}\n\n/* Heading glyph seated left of the section name. */\n.dialogTitleLabel_348665330 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n\n.dialogTitleLabel_348665330 svg {\n width: 14px;\n height: 14px;\n flex: none;\n}\n\n.dialogDetails_348665330 {\n display: grid;\n grid-template-columns: minmax(76px, auto) minmax(0, 1fr);\n gap: 6px 16px;\n margin: 0;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dialogDetails_348665330 dt,\n.dialogDetails_348665330 dd {\n min-width: 0;\n margin: 0;\n}\n\n.dialogDetails_348665330 dd {\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n";
31405
+ const css$1 = "/* Session stats pills under the composer (shadow of the official StatsPills):\n a gauge pill (counts + output speed, click opens the time dialog) and a\n database pill (total tokens + cache hit, click opens the usage dialog).\n Replicated from the official StatsPills.module_348665330.css_348665330 + stat-dialog.module_348665330.css_348665330\n (13/20 tertiary tier, hover-pill affordance, shared dialog skin).\n\n Host contract: the row serves two containers at once, and picks between them\n at attach time — the component marks which one it landed in (see\n StatsLineEnhanced's `markDockContainer`):\n - through 0.1.6-alpha.1 the dock slot renders straight into InputBar's own\n column, and this row owns the content-width clamp, its side gutters and\n its 4px top pad — the official StatsPills.module_348665330.css_348665330 of that line;\n - 0.1.6-alpha.2 renders it inside InputBar's flex `.dock_348665330` beside the resident\n ContextMeter, which took all three jobs over, so the row only has to\n shrink inside it (the official stylesheet's current form).\n Both forms are kept side by side because one published build has to render\n correctly on either host line. */\n\n.root_348665330 {\n display: flex;\n justify-content: center;\n gap: 12px;\n box-sizing: border-box;\n /* <= 0.1.6-alpha.1: this row is the composer column's content-width box. */\n width: 100%;\n max-width: var(--dsh-chat-content-width);\n margin: 0 auto;\n padding: 4px calc(var(--dsh-composer-side-clearance) + 16px) 0px;\n font-size: var(--dsh-content-font-size-secondary, 13px);\n line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));\n}\n\n/* 0.1.6-alpha.2+: InputBar's `.dock_348665330` owns the centring, the 12px gap, the top\n pad and the side clearance. */\n.root_348665330[data-dock-row] {\n width: auto;\n min-width: 0;\n max-width: 100%;\n margin: 0;\n padding: 0;\n}\n\n/* Dialog anchor: shrink-wraps its pill so the panel clamp measures the pill. */\n.anchor_348665330 {\n display: inline-flex;\n min-width: 0;\n}\n\n/* Split-mode anchor (DSH >= 0.2.1-alpha.1). The host split the single `stats`\n dock entry into one entry per pill and let its own `.dock_348665330` flex row own the\n layout, so the anchor IS the entry root and carries the text tier `.root_348665330`\n used to set — the official StatsPills.module_348665330.css_348665330 moved exactly this pair of\n declarations from `.root_348665330` onto `.anchor_348665330`. The tier stays off the plain\n `.anchor_348665330` because the pre-split row must keep its own `.root_348665330` tier. */\n.dockAnchor_348665330 {\n font-size: var(--dsh-content-font-size-secondary, 13px);\n line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));\n}\n\n.pill_348665330 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n box-sizing: border-box;\n max-width: 100%;\n padding: 1px 8px;\n border: none;\n border-radius: var(--dsw-radius-sm);\n background: none;\n color: var(--dsw-alias-label-tertiary);\n font: inherit;\n font-variant-numeric: tabular-nums;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.pill_348665330 svg {\n width: 14px;\n height: 14px;\n flex: none;\n}\n\n/* Only the button form (a pill with dialog rows to open) invites interaction;\n the static span form keeps the resting tertiary reading. */\nbutton.pill_348665330 {\n cursor: pointer;\n}\n\nbutton.pill_348665330:hover,\nbutton.pill_348665330[aria-expanded='true'] {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.label_348665330 {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sep_348665330 {\n color: var(--dsw-alias-separator-primary);\n margin: 0 6px;\n}\n\n/* The live throughput reading sits inline exactly like the official figure:\n no reserved width, no box of its own. The earlier `min-width: 10ch` slot kept\n the centred row from shifting while the number grew, but it also left a\n permanent gap before the separator — the official pill has no such slot, and\n native parity wins over that jitter fix. */\n.speed_348665330 {\n display: inline;\n}\n\n/* Portal dialog surface: fixed in the viewport, left/top supplied inline from\n the anchored-position clamp (12px viewport margin, layers above overlays).\n Material = the official stat-dialog PAIRING, which dsh 0.1.7's menu\n redesign made mandatory: `--dsw-specific-menu` is translucent there\n (rgba 58%/45%), so the blur token must ride along — the 0.1.5-era recipe\n this file copied was a single opaque line, and a fill-only copy reads as\n bare transparency over the page (issue #6). */\n.dialogPanel_348665330 {\n position: fixed;\n z-index: 1100;\n box-sizing: border-box;\n width: max-content;\n min-width: min(300px, calc(100vw - 24px));\n max-width: min(440px, calc(100vw - 24px));\n padding: 12px;\n border: 0;\n border-radius: var(--dsw-radius-lg);\n background: var(--dsw-specific-menu);\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-prominent);\n font-size: var(--dsh-content-font-size-secondary, 13px);\n line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n}\n\n/* Dialog heading row: the section name left, the headline value right. */\n.dialogTitle_348665330 {\n display: flex;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 8px;\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n/* Rule under the heading, above its rows. */\n.dialogTitleRule_348665330 {\n margin-bottom: 8px;\n border-top: 0.5px solid var(--dsw-alias-border-l2);\n}\n\n.dialogTitleValue_348665330 {\n font-variant-numeric: tabular-nums;\n}\n\n/* Heading glyph seated left of the section name. */\n.dialogTitleLabel_348665330 {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-width: 0;\n}\n\n.dialogTitleLabel_348665330 svg {\n width: 14px;\n height: 14px;\n flex: none;\n}\n\n.dialogDetails_348665330 {\n display: grid;\n grid-template-columns: minmax(76px, auto) minmax(0, 1fr);\n gap: 4px 12px;\n margin: 0;\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dialogDetails_348665330 dt,\n.dialogDetails_348665330 dd {\n min-width: 0;\n margin: 0;\n}\n\n.dialogDetails_348665330 dd {\n color: var(--dsw-alias-label-secondary);\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n";
31289
31406
  const tagId$1 = "dsh-token-total/StatsLineEnhanced.module.css";
31290
31407
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId$1 + "\"]") === null) {
31291
31408
  const tag = document.createElement("style");
@@ -31305,7 +31422,6 @@ window.__ModuleLoader__.load({
31305
31422
  "label": "label_348665330",
31306
31423
  "sep": "sep_348665330",
31307
31424
  "speed": "speed_348665330",
31308
- "subagentMark": "subagentMark_348665330",
31309
31425
  "dialogPanel": "dialogPanel_348665330",
31310
31426
  "dialogTitle": "dialogTitle_348665330",
31311
31427
  "dialogTitleRule": "dialogTitleRule_348665330",
@@ -31340,9 +31456,12 @@ window.__ModuleLoader__.load({
31340
31456
  * Which pair to register is read from the dock's entry ledger in
31341
31457
  * src/client/index.tsx.
31342
31458
  *
31343
- * Cache-hit precision, the full session token breakdown and streaming
31344
- * throughput are always enabled; no stored preferences gate these readouts.
31345
- * The token dialog includes the cache-miss row alongside every billing bucket.
31459
+ * Streaming throughput is always enabled; no stored preference gates it. The
31460
+ * three columns are style-for-style the official ones: same tier, radius,
31461
+ * colours, same dialog rows and the same integer cache-hit figure. The one
31462
+ * difference is the middle figure's SCOPE — it totals the whole session tree
31463
+ * (this agent plus every subagent it spawned), which the native projection
31464
+ * cannot see.
31346
31465
  *
31347
31466
  * Streaming throughput replaces the gauge pill's speed
31348
31467
  * figure with a live estimate while a step is still streaming and drops back to
@@ -31372,19 +31491,16 @@ window.__ModuleLoader__.load({
31372
31491
  function anchorClass(dock) {
31373
31492
  return dock ? `${StatsLineEnhanced_module_default.anchor} ${StatsLineEnhanced_module_default.dockAnchor}` : StatsLineEnhanced_module_default.anchor;
31374
31493
  }
31375
- /** The separator + figure shape both speed arms render into the gauge pill.
31376
- * The figure sits in a width-reserved slot (css.speed): the live reading
31377
- * changes every second, and a slot that tracked its digits would reflow the
31378
- * centred row — and with it the counts pill — on every refresh. The slot is
31379
- * rendered even with no figure at all (an empty session has no official figure
31380
- * to fall back to until its first step settles), so the row's layout never
31381
- * depends on whether a number is available. */
31494
+ /** The separator + figure shape both speed arms render into the gauge pill:
31495
+ * exactly the official pill's inline shape — separator and figure appear only
31496
+ * when there is a figure, and the figure occupies no reserved width, so the
31497
+ * row reads and reflows like the native one. */
31382
31498
  function SpeedFigure({ text }) {
31383
31499
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [text !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31384
31500
  className: StatsLineEnhanced_module_default.sep,
31385
31501
  "aria-hidden": true,
31386
31502
  children: "·"
31387
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31503
+ }), text !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31388
31504
  className: StatsLineEnhanced_module_default.speed,
31389
31505
  "data-stats-speed": true,
31390
31506
  children: text
@@ -31521,7 +31637,7 @@ window.__ModuleLoader__.load({
31521
31637
  className: anchorClass(dock),
31522
31638
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
31523
31639
  className: StatsLineEnhanced_module_default.pill,
31524
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGaugeOutline16, {}), label]
31640
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), label]
31525
31641
  })
31526
31642
  });
31527
31643
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -31537,7 +31653,7 @@ window.__ModuleLoader__.load({
31537
31653
  onClick: () => {
31538
31654
  setOpen(!open);
31539
31655
  },
31540
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGaugeOutline16, {}), label]
31656
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), label]
31541
31657
  }), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
31542
31658
  ref: panelRef,
31543
31659
  className: StatsLineEnhanced_module_default.dialogPanel,
@@ -31550,7 +31666,7 @@ window.__ModuleLoader__.load({
31550
31666
  className: StatsLineEnhanced_module_default.dialogTitle,
31551
31667
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
31552
31668
  className: StatsLineEnhanced_module_default.dialogTitleLabel,
31553
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconGaugeOutline16, {}), t("stats.dialog.title")]
31669
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GaugeIcon, {}), t("stats.dialog.title")]
31554
31670
  })
31555
31671
  }),
31556
31672
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -31573,13 +31689,10 @@ window.__ModuleLoader__.load({
31573
31689
  }
31574
31690
  function UsagePill({ usage, tree, t, dialog, dock }) {
31575
31691
  const { open, setOpen, rootRef, panelRef, pos } = useStatDialog(dialog);
31576
- const display = sessionUsageForDisplay(usage, tree?.tree ?? null);
31577
- const subagentTotals = display.subagents;
31578
- const showSubagents = display.subagentSessions > 0;
31579
- const billed = display.total;
31692
+ const billed = sessionUsageForDisplay(usage, tree?.tree ?? null).total;
31580
31693
  const total = billedTotal(billed);
31581
31694
  const totalText = t("message.turnUsage.count", { count: formatTokensCompact(total) });
31582
- const cacheHit = cacheHitPercentPrecise(billed);
31695
+ const cacheHit = cacheHitPercent(billed);
31583
31696
  const cacheHitText = cacheHit !== null ? t("stats.cacheHit", { percent: cacheHit }) : null;
31584
31697
  const exactCount = (value) => t("message.turnUsage.count", { count: formatTokens(value) });
31585
31698
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -31595,26 +31708,13 @@ window.__ModuleLoader__.load({
31595
31708
  onClick: () => {
31596
31709
  setOpen(!open);
31597
31710
  },
31598
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDatabaseOutline16, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
31711
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DatabaseIcon, {}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
31599
31712
  className: StatsLineEnhanced_module_default.label,
31600
- children: [
31601
- totalText,
31602
- showSubagents && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31603
- className: StatsLineEnhanced_module_default.sep,
31604
- "aria-hidden": true,
31605
- children: "·"
31606
- }),
31607
- showSubagents && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
31608
- className: StatsLineEnhanced_module_default.subagentMark,
31609
- title: t("stats.dialog.subagents"),
31610
- children: ["+", tree?.tree?.subagentSessions ?? 0]
31611
- }),
31612
- cacheHitText !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31613
- className: StatsLineEnhanced_module_default.sep,
31614
- "aria-hidden": true,
31615
- children: "·"
31616
- }), cacheHitText] })
31617
- ]
31713
+ children: [totalText, cacheHitText !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31714
+ className: StatsLineEnhanced_module_default.sep,
31715
+ "aria-hidden": true,
31716
+ children: "·"
31717
+ }), cacheHitText] })]
31618
31718
  })]
31619
31719
  }), open && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
31620
31720
  ref: panelRef,
@@ -31628,7 +31728,7 @@ window.__ModuleLoader__.load({
31628
31728
  className: StatsLineEnhanced_module_default.dialogTitle,
31629
31729
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
31630
31730
  className: StatsLineEnhanced_module_default.dialogTitleLabel,
31631
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDatabaseOutline16, {}), t("stats.dialog.usageTitle")]
31731
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(DatabaseIcon, {}), t("stats.dialog.usageTitle")]
31632
31732
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31633
31733
  className: StatsLineEnhanced_module_default.dialogTitleValue,
31634
31734
  children: exactCount(total)
@@ -31647,24 +31747,9 @@ window.__ModuleLoader__.load({
31647
31747
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.uncachedInputTokens) }),
31648
31748
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheRead") }),
31649
31749
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.cacheReadTokens) }),
31650
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheWrite") }),
31651
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.cacheWriteTokens) }),
31652
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("stats.cacheMiss") }),
31653
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(tokenBreakdown(billed).cacheMiss) }),
31750
+ billed.cacheWriteTokens !== 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.cacheWrite") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.cacheWriteTokens) })] }),
31654
31751
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("message.turnUsage.output") }),
31655
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.outputTokens) }),
31656
- showSubagents && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
31657
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("stats.dialog.main") }),
31658
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billedTotal(display.self)) }),
31659
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("stats.dialog.subagents") }),
31660
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", { children: [
31661
- exactCount(billedTotal(subagentTotals)),
31662
- " · ",
31663
- t("stats.dialog.subagentCount", { count: tree?.tree?.subagentSessions ?? 0 })
31664
- ] }),
31665
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("stats.dialog.treeTotal") }),
31666
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billedTotal(billed)) })
31667
- ] })
31752
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: exactCount(billed.outputTokens) })
31668
31753
  ]
31669
31754
  })
31670
31755
  ]
@@ -31803,7 +31888,7 @@ window.__ModuleLoader__.load({
31803
31888
  }
31804
31889
  //#endregion
31805
31890
  //#region \0token-total-css:/home/runner/work/dsh-token-total/dsh-token-total/src/client/ContextUsageEntry.module.css?inline
31806
- const css = ".root_1877794043 { display: inline-flex; align-items: center; min-width: 0; font: inherit; font-variant-numeric: tabular-nums; line-height: inherit; }\n.trigger_1877794043 { display: inline-flex; align-items: center; gap: 6px; box-sizing: border-box; max-width: 100%; padding: 1px 8px; border: 0; border-radius: 24px; background: transparent; color: var(--dsw-alias-label-tertiary); font: inherit; line-height: inherit; white-space: nowrap; cursor: pointer; }\n.trigger_1877794043 svg { flex: none; }\n.track_1877794043 { fill: none; stroke: var(--dsw-alias-separator-primary); stroke-width: 1.5; }\n.fill_1877794043 { fill: none; stroke: var(--dsw-alias-label-tertiary); stroke-width: 2; stroke-linecap: round; }\n.trigger_1877794043:hover, .trigger_1877794043[aria-expanded='true'] { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-secondary); }\n.panel_1877794043 { position: fixed; z-index: 1100; box-sizing: border-box; width: max-content; min-width: min(300px, calc(100vw - 24px)); max-width: min(440px, calc(100vw - 24px)); padding: 16px; border-radius: 12px; background: var(--dsw-specific-menu); backdrop-filter: var(--dsw-menu-backdrop-filter); box-shadow: var(--dsw-elevation-prominent); color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; }\n.header_1877794043 { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; color: var(--dsw-alias-label-primary); font-weight: 500; }\n.percent_1877794043 { font-variant-numeric: tabular-nums; }\n.figures_1877794043 { margin-left: auto; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; white-space: nowrap; }\n.bar_1877794043 { display: flex; width: 100%; height: 8px; overflow: hidden; border-radius: 4px; background: var(--dsw-alias-separator-primary); }\n.segment_1877794043 { height: 100%; }\n.systemTokens_1877794043 { background: var(--dsw-alias-label-primary); }\n.toolsTokens_1877794043 { background: var(--dsw-alias-label-secondary); }\n.messageTokens_1877794043 { background: var(--dsw-alias-label-tertiary); }\n.rows_1877794043 { display: grid; gap: 6px; margin: 12px 0 0; color: var(--dsw-alias-label-tertiary); }\n.row_1877794043 { display: flex; align-items: center; justify-content: space-between; gap: 16px; }\n.row_1877794043 dt { display: flex; align-items: center; gap: 8px; }\n.row_1877794043 dd { margin: 0; color: var(--dsw-alias-label-secondary); font-variant-numeric: tabular-nums; }\n.swatch_1877794043 { width: 8px; height: 8px; border-radius: 2px; }\n";
31891
+ const css = "/* Mirror the host ContextMeter, including its theme and font-size tokens. */\n.root_1877794043 {\n flex: none;\n display: inline-flex;\n}\n\n.trigger_1877794043 {\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-tertiary);\n font-family: inherit;\n font-size: var(--dsh-content-font-size-secondary, 13px);\n font-variant-numeric: tabular-nums;\n line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));\n white-space: nowrap;\n cursor: pointer;\n background: transparent;\n border: none;\n flex: none;\n align-items: center;\n gap: 6px;\n padding: 1px 8px;\n display: inline-flex;\n}\n\n.trigger_1877794043:hover,\n.trigger_1877794043[aria-expanded='true'] {\n background: var(--dsw-alias-interactive-bg-hover);\n color: var(--dsw-alias-label-secondary);\n}\n\n.track_1877794043 {\n fill: none;\n stroke: var(--dsw-alias-border-l3);\n stroke-width: 2px;\n}\n\n.fill_1877794043 {\n fill: none;\n stroke: var(--dsw-alias-label-tertiary);\n stroke-width: 2px;\n stroke-linecap: round;\n}\n\n.panel_1877794043 {\n z-index: 1100;\n box-sizing: border-box;\n border-radius: var(--dsw-radius-lg);\n background: var(--dsw-specific-menu);\n width: min(264px, calc(100vw - 24px));\n backdrop-filter: var(--dsw-menu-backdrop-filter);\n --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);\n box-shadow: var(--dsw-elevation-prominent);\n color: var(--dsw-alias-label-secondary);\n cursor: default;\n border: 0;\n padding: 12px;\n font-size: 12px;\n line-height: 20px;\n position: fixed;\n}\n\n.header_1877794043 {\n align-items: center;\n gap: 6px;\n display: flex;\n}\n\n.figures_1877794043 {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-primary);\n margin-left: auto;\n font-weight: 500;\n}\n\n.percent_1877794043 {\n color: var(--dsw-alias-label-primary);\n font-weight: 500;\n}\n\n.headline_1877794043 { color: var(--dsw-alias-label-tertiary); }\n.headline_1877794043:empty { display: none; }\n\n.bar_1877794043 {\n corner-shape: round;\n background: var(--dsw-alias-interactive-bg-hover);\n border-radius: 999px;\n gap: 1px;\n height: 4px;\n margin: 10px 0 12px;\n display: flex;\n overflow: hidden;\n}\n\n.segment_1877794043 {\n background: var(--meter-tint, var(--dsw-alias-label-tertiary));\n border-radius: 1px;\n flex: none;\n min-width: 2px;\n height: 100%;\n}\n\n.swatch_1877794043 {\n background: var(--meter-tint);\n vertical-align: baseline;\n border-radius: 2px;\n width: 8px;\n height: 8px;\n margin-right: 6px;\n display: inline-block;\n}\n\n.colorSystem_1877794043 { --meter-tint: var(--dsw-static-neutral-bluish-400); }\n.colorTools_1877794043 { --meter-tint: #a78bfa; }\n.colorMessages_1877794043 { --meter-tint: var(--dsw-static-blue-450); }\n.rows_1877794043 { margin: 6px 0 0; }\n\n.row_1877794043 {\n justify-content: space-between;\n align-items: center;\n gap: 12px;\n padding: 2px 0;\n display: flex;\n}\n\n.row_1877794043 dt { color: var(--dsw-alias-label-secondary); }\n.row_1877794043 dd {\n font-variant-numeric: tabular-nums;\n color: var(--dsw-alias-label-primary);\n margin: 0;\n}\n";
31807
31892
  const tagId = "dsh-token-total/ContextUsageEntry.module.css";
31808
31893
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"" + tagId + "\"]") === null) {
31809
31894
  const tag = document.createElement("style");
@@ -31819,16 +31904,17 @@ window.__ModuleLoader__.load({
31819
31904
  "fill": "fill_1877794043",
31820
31905
  "panel": "panel_1877794043",
31821
31906
  "header": "header_1877794043",
31822
- "percent": "percent_1877794043",
31823
31907
  "figures": "figures_1877794043",
31908
+ "percent": "percent_1877794043",
31909
+ "headline": "headline_1877794043",
31824
31910
  "bar": "bar_1877794043",
31825
31911
  "segment": "segment_1877794043",
31826
- "systemTokens": "systemTokens_1877794043",
31827
- "toolsTokens": "toolsTokens_1877794043",
31828
- "messageTokens": "messageTokens_1877794043",
31912
+ "swatch": "swatch_1877794043",
31913
+ "colorSystem": "colorSystem_1877794043",
31914
+ "colorTools": "colorTools_1877794043",
31915
+ "colorMessages": "colorMessages_1877794043",
31829
31916
  "rows": "rows_1877794043",
31830
- "row": "row_1877794043",
31831
- "swatch": "swatch_1877794043"
31917
+ "row": "row_1877794043"
31832
31918
  };
31833
31919
  //#endregion
31834
31920
  //#region src/client/ContextUsageEntry.tsx
@@ -31836,13 +31922,13 @@ window.__ModuleLoader__.load({
31836
31922
  const CONTEXT_LOCALES = {
31837
31923
  zh: {
31838
31924
  label: "上下文",
31839
- used: "已用上下文",
31925
+ used: "上下文已用",
31840
31926
  system: "系统提示词",
31841
- tools: "工具",
31842
- messages: "对话",
31843
- aria: "上下文占用 {percent}",
31844
- thousand: "{value}千",
31845
- million: "{value}百万"
31927
+ tools: "工具定义",
31928
+ messages: "对话消息",
31929
+ aria: "上下文已用 {percent}",
31930
+ thousand: "{value}K",
31931
+ million: "{value}M"
31846
31932
  },
31847
31933
  en: {
31848
31934
  label: "Context",
@@ -31900,13 +31986,27 @@ window.__ModuleLoader__.load({
31900
31986
  if (context === null) return null;
31901
31987
  const percent = context.percent;
31902
31988
  const reading = `${percent}%`;
31903
- const total = breakdown === void 0 ? 0 : breakdown.systemTokens + breakdown.toolsTokens + breakdown.messageTokens;
31904
31989
  const segments = contextBreakdownSegments(percent, breakdown);
31905
31990
  const format = (value) => formatContextTokens(value, {
31906
31991
  thousand: t("thousand"),
31907
31992
  million: t("million")
31908
31993
  });
31909
- const label = (part) => t(part);
31994
+ const [headBefore = "", headAfter = ""] = t("aria", { percent: "\0" }).split("\0").map((part) => part.trim());
31995
+ const rowLabels = [
31996
+ "system",
31997
+ "tools",
31998
+ "messages"
31999
+ ];
32000
+ const rowColors = [
32001
+ "colorSystem",
32002
+ "colorTools",
32003
+ "colorMessages"
32004
+ ];
32005
+ const segmentColors = {
32006
+ systemTokens: ContextUsageEntry_module_default.colorSystem,
32007
+ toolsTokens: ContextUsageEntry_module_default.colorTools,
32008
+ messageTokens: ContextUsageEntry_module_default.colorMessages
32009
+ };
31910
32010
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
31911
32011
  ref: rootRef,
31912
32012
  className: ContextUsageEntry_module_default.root,
@@ -31947,11 +32047,18 @@ window.__ModuleLoader__.load({
31947
32047
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
31948
32048
  className: ContextUsageEntry_module_default.header,
31949
32049
  children: [
31950
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("used") }),
32050
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32051
+ className: ContextUsageEntry_module_default.headline,
32052
+ children: headBefore
32053
+ }),
31951
32054
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31952
32055
  className: ContextUsageEntry_module_default.percent,
31953
32056
  children: reading
31954
32057
  }),
32058
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
32059
+ className: ContextUsageEntry_module_default.headline,
32060
+ children: headAfter
32061
+ }),
31955
32062
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31956
32063
  className: ContextUsageEntry_module_default.figures,
31957
32064
  children: `~${format(context.usedTokens)} / ${format(context.contextWindow)}`
@@ -31961,11 +32068,11 @@ window.__ModuleLoader__.load({
31961
32068
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
31962
32069
  className: ContextUsageEntry_module_default.bar,
31963
32070
  children: segments.map((segment) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
31964
- className: `${ContextUsageEntry_module_default.segment} ${ContextUsageEntry_module_default[segment.key] ?? ""}`,
32071
+ className: segmentColors[segment.key] === void 0 ? ContextUsageEntry_module_default.segment : `${ContextUsageEntry_module_default.segment} ${segmentColors[segment.key]}`,
31965
32072
  style: { width: `${segment.width}%` }
31966
32073
  }, segment.key))
31967
32074
  }),
31968
- breakdown !== void 0 && total > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
32075
+ breakdown !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
31969
32076
  className: ContextUsageEntry_module_default.rows,
31970
32077
  children: [
31971
32078
  "systemTokens",
@@ -31973,11 +32080,7 @@ window.__ModuleLoader__.load({
31973
32080
  "messageTokens"
31974
32081
  ].map((key, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
31975
32082
  className: ContextUsageEntry_module_default.row,
31976
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dt", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `${ContextUsageEntry_module_default.swatch} ${ContextUsageEntry_module_default[key]}` }), label([
31977
- "system",
31978
- "tools",
31979
- "messages"
31980
- ][index])] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: `~${format(breakdown[key])}` })]
32083
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dt", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `${ContextUsageEntry_module_default.swatch} ${ContextUsageEntry_module_default[rowColors[index]]}` }), t(rowLabels[index])] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: `~${format(breakdown[key])}` })]
31981
32084
  }, key))
31982
32085
  })
31983
32086
  ]
@@ -0,0 +1,4 @@
1
+ /** Gauge glyph for the activity pill (counts + throughput). */
2
+ export declare function GaugeIcon(): JSX.Element;
3
+ /** Database glyph for the token-usage pill. */
4
+ export declare function DatabaseIcon(): JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-token-total",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "DSH web plugin: token 用量统计看板 + 单会话(主智能体 + 子智能体)用量统计。提供今日/近3天/近7天/近1月/近3月/自定义筛选、六个汇总卡片、活跃热力图、缓存命中率折线与分模型柱状图、按模型/按供应商 Top5 列表。",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",
@@ -1,20 +1,130 @@
1
- .root { display: inline-flex; align-items: center; min-width: 0; font: inherit; font-variant-numeric: tabular-nums; line-height: inherit; }
2
- .trigger { display: inline-flex; align-items: center; gap: 6px; box-sizing: border-box; max-width: 100%; padding: 1px 8px; border: 0; border-radius: 24px; background: transparent; color: var(--dsw-alias-label-tertiary); font: inherit; line-height: inherit; white-space: nowrap; cursor: pointer; }
3
- .trigger svg { flex: none; }
4
- .track { fill: none; stroke: var(--dsw-alias-separator-primary); stroke-width: 1.5; }
5
- .fill { fill: none; stroke: var(--dsw-alias-label-tertiary); stroke-width: 2; stroke-linecap: round; }
6
- .trigger:hover, .trigger[aria-expanded='true'] { background: var(--dsw-alias-interactive-bg-hover); color: var(--dsw-alias-label-secondary); }
7
- .panel { position: fixed; z-index: 1100; box-sizing: border-box; width: max-content; min-width: min(300px, calc(100vw - 24px)); max-width: min(440px, calc(100vw - 24px)); padding: 16px; border-radius: 12px; background: var(--dsw-specific-menu); backdrop-filter: var(--dsw-menu-backdrop-filter); box-shadow: var(--dsw-elevation-prominent); color: var(--dsw-alias-label-secondary); font-size: 12px; line-height: 18px; }
8
- .header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; color: var(--dsw-alias-label-primary); font-weight: 500; }
9
- .percent { font-variant-numeric: tabular-nums; }
10
- .figures { margin-left: auto; color: var(--dsw-alias-label-tertiary); font-variant-numeric: tabular-nums; white-space: nowrap; }
11
- .bar { display: flex; width: 100%; height: 8px; overflow: hidden; border-radius: 4px; background: var(--dsw-alias-separator-primary); }
12
- .segment { height: 100%; }
13
- .systemTokens { background: var(--dsw-alias-label-primary); }
14
- .toolsTokens { background: var(--dsw-alias-label-secondary); }
15
- .messageTokens { background: var(--dsw-alias-label-tertiary); }
16
- .rows { display: grid; gap: 6px; margin: 12px 0 0; color: var(--dsw-alias-label-tertiary); }
17
- .row { display: flex; align-items: center; justify-content: space-between; gap: 16px; }
18
- .row dt { display: flex; align-items: center; gap: 8px; }
19
- .row dd { margin: 0; color: var(--dsw-alias-label-secondary); font-variant-numeric: tabular-nums; }
20
- .swatch { width: 8px; height: 8px; border-radius: 2px; }
1
+ /* Mirror the host ContextMeter, including its theme and font-size tokens. */
2
+ .root {
3
+ flex: none;
4
+ display: inline-flex;
5
+ }
6
+
7
+ .trigger {
8
+ border-radius: var(--dsw-radius-sm);
9
+ color: var(--dsw-alias-label-tertiary);
10
+ font-family: inherit;
11
+ font-size: var(--dsh-content-font-size-secondary, 13px);
12
+ font-variant-numeric: tabular-nums;
13
+ line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
14
+ white-space: nowrap;
15
+ cursor: pointer;
16
+ background: transparent;
17
+ border: none;
18
+ flex: none;
19
+ align-items: center;
20
+ gap: 6px;
21
+ padding: 1px 8px;
22
+ display: inline-flex;
23
+ }
24
+
25
+ .trigger:hover,
26
+ .trigger[aria-expanded='true'] {
27
+ background: var(--dsw-alias-interactive-bg-hover);
28
+ color: var(--dsw-alias-label-secondary);
29
+ }
30
+
31
+ .track {
32
+ fill: none;
33
+ stroke: var(--dsw-alias-border-l3);
34
+ stroke-width: 2px;
35
+ }
36
+
37
+ .fill {
38
+ fill: none;
39
+ stroke: var(--dsw-alias-label-tertiary);
40
+ stroke-width: 2px;
41
+ stroke-linecap: round;
42
+ }
43
+
44
+ .panel {
45
+ z-index: 1100;
46
+ box-sizing: border-box;
47
+ border-radius: var(--dsw-radius-lg);
48
+ background: var(--dsw-specific-menu);
49
+ width: min(264px, calc(100vw - 24px));
50
+ backdrop-filter: var(--dsw-menu-backdrop-filter);
51
+ --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
52
+ box-shadow: var(--dsw-elevation-prominent);
53
+ color: var(--dsw-alias-label-secondary);
54
+ cursor: default;
55
+ border: 0;
56
+ padding: 12px;
57
+ font-size: 12px;
58
+ line-height: 20px;
59
+ position: fixed;
60
+ }
61
+
62
+ .header {
63
+ align-items: center;
64
+ gap: 6px;
65
+ display: flex;
66
+ }
67
+
68
+ .figures {
69
+ font-variant-numeric: tabular-nums;
70
+ color: var(--dsw-alias-label-primary);
71
+ margin-left: auto;
72
+ font-weight: 500;
73
+ }
74
+
75
+ .percent {
76
+ color: var(--dsw-alias-label-primary);
77
+ font-weight: 500;
78
+ }
79
+
80
+ .headline { color: var(--dsw-alias-label-tertiary); }
81
+ .headline:empty { display: none; }
82
+
83
+ .bar {
84
+ corner-shape: round;
85
+ background: var(--dsw-alias-interactive-bg-hover);
86
+ border-radius: 999px;
87
+ gap: 1px;
88
+ height: 4px;
89
+ margin: 10px 0 12px;
90
+ display: flex;
91
+ overflow: hidden;
92
+ }
93
+
94
+ .segment {
95
+ background: var(--meter-tint, var(--dsw-alias-label-tertiary));
96
+ border-radius: 1px;
97
+ flex: none;
98
+ min-width: 2px;
99
+ height: 100%;
100
+ }
101
+
102
+ .swatch {
103
+ background: var(--meter-tint);
104
+ vertical-align: baseline;
105
+ border-radius: 2px;
106
+ width: 8px;
107
+ height: 8px;
108
+ margin-right: 6px;
109
+ display: inline-block;
110
+ }
111
+
112
+ .colorSystem { --meter-tint: var(--dsw-static-neutral-bluish-400); }
113
+ .colorTools { --meter-tint: #a78bfa; }
114
+ .colorMessages { --meter-tint: var(--dsw-static-blue-450); }
115
+ .rows { margin: 6px 0 0; }
116
+
117
+ .row {
118
+ justify-content: space-between;
119
+ align-items: center;
120
+ gap: 12px;
121
+ padding: 2px 0;
122
+ display: flex;
123
+ }
124
+
125
+ .row dt { color: var(--dsw-alias-label-secondary); }
126
+ .row dd {
127
+ font-variant-numeric: tabular-nums;
128
+ color: var(--dsw-alias-label-primary);
129
+ margin: 0;
130
+ }
@@ -10,7 +10,7 @@ export const CONTEXT_LOCALE_NS = 'tokenTotalContext'
10
10
  const keys = ['label', 'used', 'system', 'tools', 'messages', 'aria', 'thousand', 'million'] as const
11
11
  export type ContextLocaleKey = typeof keys[number]
12
12
  export const CONTEXT_LOCALES: Record<string, Record<ContextLocaleKey, string>> = {
13
- zh: { label: '上下文', used: '已用上下文', system: '系统提示词', tools: '工具', messages: '对话', aria: '上下文占用 {percent}', thousand: '{value}千', million: '{value}百万' },
13
+ zh: { label: '上下文', used: '上下文已用', system: '系统提示词', tools: '工具定义', messages: '对话消息', aria: '上下文已用 {percent}', thousand: '{value}K', million: '{value}M' },
14
14
  en: { label: 'Context', used: 'Context used', system: 'System', tools: 'Tools', messages: 'Conversation', aria: 'Context usage {percent}', thousand: '{value}K', million: '{value}M' },
15
15
  'zh-TW': { label: '上下文', used: '已用上下文', system: '系統提示詞', tools: '工具', messages: '對話', aria: '上下文使用量 {percent}', thousand: '{value}千', million: '{value}百萬' },
16
16
  }
@@ -50,12 +50,14 @@ export function ContextUsageEntry({ useProjection, t, installContextTakeover }:
50
50
  if (context === null) return null
51
51
  const percent = context.percent
52
52
  const reading = `${percent}%`
53
- const total = breakdown === undefined ? 0 : breakdown.systemTokens + breakdown.toolsTokens + breakdown.messageTokens
54
53
  const segments = contextBreakdownSegments(percent, breakdown)
55
54
  const format = (value: number): string => formatContextTokens(value, {
56
55
  thousand: t('thousand'), million: t('million'),
57
56
  })
58
- const label = (part: 'system' | 'tools' | 'messages'): string => t(part)
57
+ const [headBefore = '', headAfter = ''] = t('aria', { percent: '\0' }).split('\0').map(part => part.trim())
58
+ const rowLabels = ['system', 'tools', 'messages'] as const
59
+ const rowColors = ['colorSystem', 'colorTools', 'colorMessages'] as const
60
+ const segmentColors: Record<string, string> = { systemTokens: css.colorSystem, toolsTokens: css.colorTools, messageTokens: css.colorMessages }
59
61
  return (
60
62
  <span ref={rootRef} className={css.root} data-dsh-token-total-stat="context">
61
63
  <button type="button" className={css.trigger} aria-label={t('aria', { percent: reading })} aria-haspopup="dialog" aria-expanded={open} onClick={() => setOpen(!open)}>
@@ -68,13 +70,15 @@ export function ContextUsageEntry({ useProjection, t, installContextTakeover }:
68
70
  {open && createPortal(
69
71
  <div ref={panelRef} className={css.panel} style={position ?? MEASURE_STYLE} role="dialog" aria-label={t('used')}>
70
72
  <div className={css.header}>
71
- <span>{t('used')}</span><span className={css.percent}>{reading}</span>
73
+ <span className={css.headline}>{headBefore}</span>
74
+ <span className={css.percent}>{reading}</span>
75
+ <span className={css.headline}>{headAfter}</span>
72
76
  <span className={css.figures}>{`~${format(context.usedTokens)} / ${format(context.contextWindow)}`}</span>
73
77
  </div>
74
- <div className={css.bar}>{segments.map(segment => <div key={segment.key} className={`${css.segment} ${css[segment.key] ?? ''}`} style={{ width: `${segment.width}%` }} />)}</div>
75
- {breakdown !== undefined && total > 0 && <dl className={css.rows}>
78
+ <div className={css.bar}>{segments.map(segment => <div key={segment.key} className={segmentColors[segment.key] === undefined ? css.segment : `${css.segment} ${segmentColors[segment.key]}`} style={{ width: `${segment.width}%` }} />)}</div>
79
+ {breakdown !== undefined && <dl className={css.rows}>
76
80
  {(['systemTokens', 'toolsTokens', 'messageTokens'] as const).map((key, index) => <div className={css.row} key={key}>
77
- <dt><span className={`${css.swatch} ${css[key]}`} />{label((['system', 'tools', 'messages'] as const)[index])}</dt>
81
+ <dt><span className={`${css.swatch} ${css[rowColors[index]]}`} />{t(rowLabels[index])}</dt>
78
82
  <dd>{`~${format(breakdown[key])}`}</dd>
79
83
  </div>)}
80
84
  </dl>}
@@ -53,7 +53,7 @@
53
53
  declarations from `.root` onto `.anchor`. The tier stays off the plain
54
54
  `.anchor` because the pre-split row must keep its own `.root` tier. */
55
55
  .dockAnchor {
56
- font-size: calc(var(--dsh-content-font-size-secondary, 13px) - 1px);
56
+ font-size: var(--dsh-content-font-size-secondary, 13px);
57
57
  line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
58
58
  }
59
59
 
@@ -65,8 +65,8 @@
65
65
  max-width: 100%;
66
66
  padding: 1px 8px;
67
67
  border: none;
68
- border-radius: 24px;
69
- background: transparent;
68
+ border-radius: var(--dsw-radius-sm);
69
+ background: none;
70
70
  color: var(--dsw-alias-label-tertiary);
71
71
  font: inherit;
72
72
  font-variant-numeric: tabular-nums;
@@ -103,22 +103,13 @@ button.pill[aria-expanded='true'] {
103
103
  margin: 0 6px;
104
104
  }
105
105
 
106
- /* Reserved slot for the speed figure: the official pill's figure only changes
107
- when a step settles, but the live reading refreshes on every beat. A width
108
- that tracked the digits would reflow the centred row on each of them and drag
109
- the counts pill beside it along; 10ch covers "1200 tok/s", the widest shape
110
- formatTokensPerSecond can produce. */
106
+ /* The live throughput reading sits inline exactly like the official figure:
107
+ no reserved width, no box of its own. The earlier `min-width: 10ch` slot kept
108
+ the centred row from shifting while the number grew, but it also left a
109
+ permanent gap before the separator — the official pill has no such slot, and
110
+ native parity wins over that jitter fix. */
111
111
  .speed {
112
- display: inline-block;
113
- min-width: 10ch;
114
- }
115
-
116
- /* Marks a usage figure that now includes subagent spend. Deliberately quiet:
117
- it rides inside the official pill's label and must not read as a badge or
118
- compete with the token count beside it. */
119
- .subagentMark {
120
- color: var(--dsw-alias-label-tertiary);
121
- font-variant-numeric: tabular-nums;
112
+ display: inline;
122
113
  }
123
114
 
124
115
  /* Portal dialog surface: fixed in the viewport, left/top supplied inline from
@@ -135,15 +126,15 @@ button.pill[aria-expanded='true'] {
135
126
  width: max-content;
136
127
  min-width: min(300px, calc(100vw - 24px));
137
128
  max-width: min(440px, calc(100vw - 24px));
138
- padding: 16px;
129
+ padding: 12px;
139
130
  border: 0;
140
- border-radius: 12px;
131
+ border-radius: var(--dsw-radius-lg);
141
132
  background: var(--dsw-specific-menu);
142
133
  backdrop-filter: var(--dsw-menu-backdrop-filter);
143
134
  --dsw-elevation-stroke-color: var(--dsw-alias-border-l1);
144
135
  box-shadow: var(--dsw-elevation-prominent);
145
- font-size: 12px;
146
- line-height: 18px;
136
+ font-size: var(--dsh-content-font-size-secondary, 13px);
137
+ line-height: calc(20px + var(--dsh-content-font-delta-secondary, 0px));
147
138
  color: var(--dsw-alias-label-secondary);
148
139
  cursor: default;
149
140
  }
@@ -160,7 +151,7 @@ button.pill[aria-expanded='true'] {
160
151
 
161
152
  /* Rule under the heading, above its rows. */
162
153
  .dialogTitleRule {
163
- margin-bottom: 10px;
154
+ margin-bottom: 8px;
164
155
  border-top: 0.5px solid var(--dsw-alias-border-l2);
165
156
  }
166
157
 
@@ -185,7 +176,7 @@ button.pill[aria-expanded='true'] {
185
176
  .dialogDetails {
186
177
  display: grid;
187
178
  grid-template-columns: minmax(76px, auto) minmax(0, 1fr);
188
- gap: 6px 16px;
179
+ gap: 4px 12px;
189
180
  margin: 0;
190
181
  color: var(--dsw-alias-label-tertiary);
191
182
  }
@@ -23,9 +23,12 @@
23
23
  * Which pair to register is read from the dock's entry ledger in
24
24
  * src/client/index.tsx.
25
25
  *
26
- * Cache-hit precision, the full session token breakdown and streaming
27
- * throughput are always enabled; no stored preferences gate these readouts.
28
- * The token dialog includes the cache-miss row alongside every billing bucket.
26
+ * Streaming throughput is always enabled; no stored preference gates it. The
27
+ * three columns are style-for-style the official ones: same tier, radius,
28
+ * colours, same dialog rows and the same integer cache-hit figure. The one
29
+ * difference is the middle figure's SCOPE — it totals the whole session tree
30
+ * (this agent plus every subagent it spawned), which the native projection
31
+ * cannot see.
29
32
  *
30
33
  * Streaming throughput replaces the gauge pill's speed
31
34
  * figure with a live estimate while a step is still streaming and drops back to
@@ -44,14 +47,14 @@
44
47
  */
45
48
  import { memo, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
46
49
  import { createPortal } from 'react-dom'
47
- import { IconDatabaseOutline16, IconGaugeOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
48
50
  import type { PropsLocale, SnapshotSelectorHook } from '@deepseek-ai/dsh-client-ui-slots'
51
+ import { DatabaseIcon, GaugeIcon } from './stats-icons.tsx'
49
52
  import { LOCALE_NS } from './locales.ts'
50
53
  import { MEASURE_STYLE, useStatDialog, type StatDialogSeat } from './stat-dialog.ts'
51
54
  import {
52
- billedInputTokens, cacheHitPercentPrecise, deriveStats,
55
+ billedInputTokens, cacheHitPercent, deriveStats,
53
56
  estimateOutputTokens, formatDuration, formatTokensCompact, formatTokensPerSecond,
54
- measuredTokenScale, StreamingRateSampler, tokenBreakdown,
57
+ measuredTokenScale, StreamingRateSampler,
55
58
  type ConversationNodeLike, type PartialAssistantLike, type UseProjection, type WindowStats,
56
59
  } from './stats-line-core.ts'
57
60
  import { formatTokens } from './format.ts'
@@ -112,18 +115,15 @@ function anchorClass(dock: boolean): string | undefined {
112
115
  return dock ? `${css.anchor} ${css.dockAnchor}` : css.anchor
113
116
  }
114
117
 
115
- /** The separator + figure shape both speed arms render into the gauge pill.
116
- * The figure sits in a width-reserved slot (css.speed): the live reading
117
- * changes every second, and a slot that tracked its digits would reflow the
118
- * centred row — and with it the counts pill — on every refresh. The slot is
119
- * rendered even with no figure at all (an empty session has no official figure
120
- * to fall back to until its first step settles), so the row's layout never
121
- * depends on whether a number is available. */
118
+ /** The separator + figure shape both speed arms render into the gauge pill:
119
+ * exactly the official pill's inline shape — separator and figure appear only
120
+ * when there is a figure, and the figure occupies no reserved width, so the
121
+ * row reads and reflows like the native one. */
122
122
  function SpeedFigure({ text }: { text: string | null }) {
123
123
  return (
124
124
  <>
125
125
  {text !== null && <span className={css.sep} aria-hidden>·</span>}
126
- <span className={css.speed} data-stats-speed>{text}</span>
126
+ {text !== null && <span className={css.speed} data-stats-speed>{text}</span>}
127
127
  </>
128
128
  )
129
129
  }
@@ -290,7 +290,7 @@ function TimePill({ stats, t, dialog, useChat, scale, dock }: {
290
290
  return (
291
291
  <span className={anchorClass(dock)}>
292
292
  <span className={css.pill}>
293
- <IconGaugeOutline16 />
293
+ <GaugeIcon />
294
294
  {label}
295
295
  </span>
296
296
  </span>
@@ -306,7 +306,7 @@ function TimePill({ stats, t, dialog, useChat, scale, dock }: {
306
306
  aria-label={officialTps === null ? counts : counts + ' · ' + officialTps}
307
307
  onClick={() => { setOpen(!open) }}
308
308
  >
309
- <IconGaugeOutline16 />
309
+ <GaugeIcon />
310
310
  {label}
311
311
  </button>
312
312
  {open && createPortal(
@@ -320,7 +320,7 @@ function TimePill({ stats, t, dialog, useChat, scale, dock }: {
320
320
  >
321
321
  <div className={css.dialogTitle}>
322
322
  <span className={css.dialogTitleLabel}>
323
- <IconGaugeOutline16 />
323
+ <GaugeIcon />
324
324
  {t('stats.dialog.title')}
325
325
  </span>
326
326
  </div>
@@ -372,13 +372,15 @@ function UsagePill({ usage, tree, t, dialog, dock }: {
372
372
  }) {
373
373
  const { open, setOpen, rootRef, panelRef, pos } = useStatDialog(dialog)
374
374
  const display = sessionUsageForDisplay(usage, tree?.tree ?? null)
375
- const subagentTotals = display.subagents
376
- const showSubagents = display.subagentSessions > 0
377
375
  const billed = display.total
378
376
  // Same aggregate as the official pill total: every prompt-side billing bucket plus output.
379
377
  const total = billedTotal(billed)
380
378
  const totalText = t('message.turnUsage.count', { count: formatTokensCompact(total) })
381
- const cacheHit = cacheHitPercentPrecise(billed)
379
+ // Native formatting, one consistent scope: the ratio comes from the same
380
+ // aggregate the headline uses, so "33M tok · 87%" can never contradict the
381
+ // number printed beside it. The integer shape and its >99% guard are the
382
+ // official ones, untouched.
383
+ const cacheHit = cacheHitPercent(billed)
382
384
  const cacheHitText = cacheHit !== null ? t('stats.cacheHit', { percent: cacheHit }) : null
383
385
  const exactCount = (value: number): string =>
384
386
  t('message.turnUsage.count', { count: formatTokens(value) })
@@ -392,19 +394,13 @@ function UsagePill({ usage, tree, t, dialog, dock }: {
392
394
  aria-label={cacheHitText === null ? totalText : totalText + ' · ' + cacheHitText}
393
395
  onClick={() => { setOpen(!open) }}
394
396
  >
395
- <IconDatabaseOutline16 />
397
+ <DatabaseIcon />
396
398
  <span className={css.label}>
397
399
  {totalText}
398
- {showSubagents && (
399
- <span className={css.sep} aria-hidden>·</span>
400
- )}
401
- {showSubagents && (
402
- <span className={css.subagentMark} title={t('stats.dialog.subagents')}>
403
- +{tree?.tree?.subagentSessions ?? 0}
404
- </span>
405
- )}
406
400
  {cacheHitText !== null && (
407
401
  <>
402
+ {/* Separator only when the second figure exists: an empty span
403
+ would still take the label's gap and read as a trailing dot. */}
408
404
  <span className={css.sep} aria-hidden>·</span>
409
405
  {cacheHitText}
410
406
  </>
@@ -422,12 +418,14 @@ function UsagePill({ usage, tree, t, dialog, dock }: {
422
418
  >
423
419
  <div className={css.dialogTitle}>
424
420
  <span className={css.dialogTitleLabel}>
425
- <IconDatabaseOutline16 />
421
+ <DatabaseIcon />
426
422
  {t('stats.dialog.usageTitle')}
427
423
  </span>
428
424
  <span className={css.dialogTitleValue}>{exactCount(total)}</span>
429
425
  </div>
430
426
  <div className={css.dialogTitleRule} aria-hidden />
427
+ {/* Exactly the official dialog's rows, in the official order: the
428
+ takeover adds the tree to the TOTAL, not to the breakdown. */}
431
429
  <dl className={css.dialogDetails} data-session-stats-usage>
432
430
  {cacheHit !== null && (
433
431
  <>
@@ -439,28 +437,14 @@ function UsagePill({ usage, tree, t, dialog, dock }: {
439
437
  <dd>{exactCount(billed.uncachedInputTokens)}</dd>
440
438
  <dt>{t('message.turnUsage.cacheRead')}</dt>
441
439
  <dd>{exactCount(billed.cacheReadTokens)}</dd>
442
- <dt>{t('message.turnUsage.cacheWrite')}</dt>
443
- <dd>{exactCount(billed.cacheWriteTokens)}</dd>
444
- <dt>{t('stats.cacheMiss')}</dt>
445
- <dd>{exactCount(tokenBreakdown(billed).cacheMiss)}</dd>
446
- <dt>{t('message.turnUsage.output')}</dt>
447
- <dd>{exactCount(billed.outputTokens)}</dd>
448
- {/* The split that the native figure cannot show: this session's own
449
- main agent against every subagent it delegated to. */}
450
- {showSubagents && (
440
+ {billed.cacheWriteTokens !== 0 && (
451
441
  <>
452
- <dt>{t('stats.dialog.main')}</dt>
453
- <dd>{exactCount(billedTotal(display.self))}</dd>
454
- <dt>{t('stats.dialog.subagents')}</dt>
455
- <dd>
456
- {exactCount(billedTotal(subagentTotals))}
457
- {' · '}
458
- {t('stats.dialog.subagentCount', { count: tree?.tree?.subagentSessions ?? 0 })}
459
- </dd>
460
- <dt>{t('stats.dialog.treeTotal')}</dt>
461
- <dd>{exactCount(billedTotal(billed))}</dd>
442
+ <dt>{t('message.turnUsage.cacheWrite')}</dt>
443
+ <dd>{exactCount(billed.cacheWriteTokens)}</dd>
462
444
  </>
463
445
  )}
446
+ <dt>{t('message.turnUsage.output')}</dt>
447
+ <dd>{exactCount(billed.outputTokens)}</dd>
464
448
  </dl>
465
449
  </div>,
466
450
  document.body,
@@ -0,0 +1,83 @@
1
+ /**
2
+ * The stat-pill glyphs.
3
+ *
4
+ * The shell freezes its primitives table, and that table's icon names moved
5
+ * with the host line: 0.2.1-rc.2 exports `IconGaugeOutlineRegular` /
6
+ * `IconDatabaseOutlineRegular` (plus the `Medium` weight) and no longer
7
+ * exports the `…Outline16` names this plugin used to import. A missing named
8
+ * export is `undefined` at runtime, and React then throws "Element type is
9
+ * invalid" on the entry's first render — the shell's slot boundary catches it,
10
+ * ABDICATES the entry, and the shadowed official pill silently returns. That
11
+ * failure mode is invisible in types (the package's own declarations still
12
+ * describe the old names) and looks exactly like "the plugin did nothing".
13
+ *
14
+ * So the glyph is resolved from the runtime table by preference order and
15
+ * falls back to an inline drawing of the same shape when no name exists, which
16
+ * keeps one published build rendering on every host line.
17
+ */
18
+ import * as primitives from '@deepseek-ai/dsh-client-ui-primitives'
19
+
20
+ type IconComponent = () => JSX.Element
21
+
22
+ const table = primitives as unknown as Record<string, unknown>
23
+
24
+ /** First exported function component among `names`, or null when the shell
25
+ * exports none of them (an older or newer icon roster). */
26
+ function resolveIcon(...names: readonly string[]): IconComponent | null {
27
+ for (const name of names) {
28
+ const candidate = table[name]
29
+ if (typeof candidate === 'function') return candidate as IconComponent
30
+ }
31
+ return null
32
+ }
33
+
34
+ const resolvedGauge = resolveIcon(
35
+ 'IconGaugeOutlineRegular',
36
+ 'IconGaugeOutlineMedium',
37
+ 'IconGaugeOutline16',
38
+ )
39
+ const resolvedDatabase = resolveIcon(
40
+ 'IconDatabaseOutlineRegular',
41
+ 'IconDatabaseOutlineMedium',
42
+ 'IconDatabaseOutline16',
43
+ )
44
+
45
+ /** Gauge glyph for the activity pill (counts + throughput). */
46
+ export function GaugeIcon(): JSX.Element {
47
+ if (resolvedGauge !== null) {
48
+ const Icon = resolvedGauge
49
+ return <Icon />
50
+ }
51
+ return (
52
+ <svg viewBox="0 0 16 16" aria-hidden="true">
53
+ <path
54
+ d="M8 2.5a5.5 5.5 0 0 1 5.5 5.5M8 8l2.6-2.6M2.5 8A5.5 5.5 0 0 1 8 2.5"
55
+ fill="none"
56
+ stroke="currentColor"
57
+ strokeWidth="1.3"
58
+ strokeLinecap="round"
59
+ />
60
+ <circle cx="8" cy="8" r="1" fill="currentColor" />
61
+ </svg>
62
+ )
63
+ }
64
+
65
+ /** Database glyph for the token-usage pill. */
66
+ export function DatabaseIcon(): JSX.Element {
67
+ if (resolvedDatabase !== null) {
68
+ const Icon = resolvedDatabase
69
+ return <Icon />
70
+ }
71
+ return (
72
+ <svg viewBox="0 0 16 16" aria-hidden="true">
73
+ <ellipse cx="8" cy="3.6" rx="4.6" ry="1.9" fill="none" stroke="currentColor" strokeWidth="1.3" />
74
+ <path
75
+ d="M3.4 3.6v8.8c0 1.05 2.06 1.9 4.6 1.9s4.6-.85 4.6-1.9V3.6M3.4 8c0 1.05 2.06 1.9 4.6 1.9s4.6-.85 4.6-1.9"
76
+ fill="none"
77
+ stroke="currentColor"
78
+ strokeWidth="1.3"
79
+ strokeLinecap="round"
80
+ />
81
+ </svg>
82
+ )
83
+ }