dsh-context 0.47.0 → 0.49.0

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/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  [`dsh-context`](https://www.npmjs.com/package/dsh-context) provides full context lifecycle management features.
12
12
  - **Context tab** — an UI context dashboard for DeepSeek Harness's context stats, composition, trend, events, and messages.
13
- - **Context panel** — the same dashboard as a right-sidebar tab (dsh 0.1.5+): pick **Context** on the sidebar's guide page and the panel opens beside the chat.
13
+ - **Context panel** — the same dashboard as a right-sidebar tab (dsh 0.1.5-rc.1+): pick **Context** on the sidebar's guide page and the panel opens beside the chat.
14
14
  - **`/context` command** — the slash command shows the context model for current context composition and recent context evolution.
15
15
 
16
16
  ## Install / Update
package/lib/client.js CHANGED
@@ -32,15 +32,18 @@ window.__ModuleLoader__.load({
32
32
  "stats.toolCalls": "工具调用",
33
33
  "stats.images": "图片",
34
34
  "stats.cost": "预估费用",
35
- "stats.costTip": "按 DeepSeek 官方刊例价估算整个会话的累计费用:输入区分缓存命中/未命中,输出含思考;按请求时间区分高峰(北京时间工作日 9:00–12:00、14:00–18:00)与空闲时段(半价,周末全天均为空闲时段)。适用于 deepseek-v4-flash / deepseek-v4-pro(不限 provider),价格写死在代码中,仅供参考。",
35
+ "stats.costTip": "按 DeepSeek 官方刊例价估算整个会话的累计费用:输入区分缓存命中/未命中,输出含思考;按请求时间区分高峰(北京时间工作日 9:00–12:00、14:00–18:00)与空闲时段(半价,周末全天均为空闲时段)。适用于 deepseek-v4.1-flash / deepseek-flash / deepseek-v4-pro(不限 provider;V4.1 Pro 上线前,V4 Pro 请求将被路由并按 V4.1 Flash 单价计费),价格写死在代码中,仅供参考。",
36
36
  "stats.costPriceHead": "每百万 tokens 价格(高峰 | 空闲半价):",
37
37
  "stats.costHit": "命中",
38
38
  "stats.costMiss": "未命中",
39
39
  "stats.costOut": "输出",
40
40
  "timing.title": "耗时统计",
41
- "timing.total": "总活跃时长",
41
+ "timing.total": "活跃时长",
42
42
  "timing.ttft": "模型等待",
43
43
  "timing.gen": "模型生成",
44
+ "timing.reasoning": "模型思考",
45
+ "timing.text": "正文输出",
46
+ "timing.toolArgs": "工具参数",
44
47
  "timing.tools": "工具执行",
45
48
  "timing.other": "其他开销",
46
49
  "timing.callTimes": "{n}次",
@@ -268,6 +271,8 @@ window.__ModuleLoader__.load({
268
271
  "rich.md": "Markdown",
269
272
  "rich.toMd": "按 Markdown 渲染查看",
270
273
  "rich.toRaw": "查看原始文本",
274
+ "rich.copy": "复制原文",
275
+ "rich.copied": "已复制",
271
276
  "rich.md.copy": "复制",
272
277
  "rich.md.copied": "已复制",
273
278
  "rich.md.footnotes": "脚注",
@@ -319,7 +324,7 @@ window.__ModuleLoader__.load({
319
324
  "stats.toolCalls": "Tool Calls",
320
325
  "stats.images": "Images",
321
326
  "stats.cost": "Cost",
322
- "stats.costTip": "Rough cumulative cost of the whole session at DeepSeek’s list prices: input split by cache hit/miss, output includes reasoning; each request is priced at the peak (Beijing Time weekdays 09:00–12:00, 14:00–18:00) or half-price off-peak rate by its time — weekends bill at off-peak all day. Applies to deepseek-v4-flash / deepseek-v4-pro on any provider; prices are hardcoded, for reference only.",
327
+ "stats.costTip": "Rough cumulative cost of the whole session at DeepSeek’s list prices: input split by cache hit/miss, output includes reasoning; each request is priced at the peak (Beijing Time weekdays 09:00–12:00, 14:00–18:00) or half-price off-peak rate by its time — weekends bill at off-peak all day. Applies to deepseek-v4.1-flash / deepseek-flash / deepseek-v4-pro on any provider (until V4.1 Pro ships, Pro requests are routed to and billed at V4.1 Flash rates); prices are hardcoded, for reference only.",
323
328
  "stats.costPriceHead": "Per-1M-token rates (peak | off-peak at half price):",
324
329
  "stats.costHit": "hit",
325
330
  "stats.costMiss": "miss",
@@ -328,6 +333,9 @@ window.__ModuleLoader__.load({
328
333
  "timing.total": "Active Time",
329
334
  "timing.ttft": "TTFT",
330
335
  "timing.gen": "LLM Gen",
336
+ "timing.reasoning": "Thinking",
337
+ "timing.text": "Answer",
338
+ "timing.toolArgs": "Tool args",
331
339
  "timing.tools": "Tool runs",
332
340
  "timing.other": "Overhead",
333
341
  "timing.callTimes": "{n} calls",
@@ -555,6 +563,8 @@ window.__ModuleLoader__.load({
555
563
  "rich.md": "Markdown",
556
564
  "rich.toMd": "View as Markdown",
557
565
  "rich.toRaw": "View Raw Text",
566
+ "rich.copy": "Copy Raw Text",
567
+ "rich.copied": "Copied",
558
568
  "rich.md.copy": "Copy",
559
569
  "rich.md.copied": "Copied",
560
570
  "rich.md.footnotes": "Footnotes",
@@ -1136,6 +1146,14 @@ window.__ModuleLoader__.load({
1136
1146
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
1137
1147
  }
1138
1148
  /**
1149
+ * The OPTIONAL timing scalars (the generation split): a real non-negative
1150
+ * number passes, anything else — including absence — reads as undefined so the
1151
+ * field stays absent on the narrowed value (see `timingOf`).
1152
+ */
1153
+ function optMsNumOf(value) {
1154
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1155
+ }
1156
+ /**
1139
1157
  * Cheap whole-value check for the pass-through path of `timelineOf`: absent
1140
1158
  * timing passes; present timing must already be well-formed (every scalar
1141
1159
  * numeric, every per-name row shaped) — anything else sends the payload down
@@ -1153,6 +1171,14 @@ window.__ModuleLoader__.load({
1153
1171
  "toolsMs",
1154
1172
  "toolCalls"
1155
1173
  ]) if (typeof t[k] !== "number") return false;
1174
+ for (const k of [
1175
+ "reasoningMs",
1176
+ "textMs",
1177
+ "toolArgMs"
1178
+ ]) {
1179
+ const v = t[k];
1180
+ if (v !== void 0 && (typeof v !== "number" || !Number.isFinite(v) || v < 0)) return false;
1181
+ }
1156
1182
  const tools = t.tools;
1157
1183
  if (tools === null || typeof tools !== "object" || Array.isArray(tools)) return false;
1158
1184
  for (const k in tools) {
@@ -1187,7 +1213,7 @@ window.__ModuleLoader__.load({
1187
1213
  ms
1188
1214
  };
1189
1215
  }
1190
- return {
1216
+ const totals = {
1191
1217
  wallMs: msNumOf(data.wallMs),
1192
1218
  ttftMs: msNumOf(data.ttftMs),
1193
1219
  genMs: msNumOf(data.genMs),
@@ -1196,6 +1222,13 @@ window.__ModuleLoader__.load({
1196
1222
  toolCalls: msNumOf(data.toolCalls),
1197
1223
  tools
1198
1224
  };
1225
+ const reasoning = optMsNumOf(data.reasoningMs);
1226
+ if (reasoning !== void 0) totals.reasoningMs = reasoning;
1227
+ const textMs = optMsNumOf(data.textMs);
1228
+ if (textMs !== void 0) totals.textMs = textMs;
1229
+ const toolArgMs = optMsNumOf(data.toolArgMs);
1230
+ if (toolArgMs !== void 0) totals.toolArgMs = toolArgMs;
1231
+ return totals;
1199
1232
  }
1200
1233
  /**
1201
1234
  * Narrow a delivered projection value to the plugin's `contextHeaders`
@@ -2453,22 +2486,20 @@ window.__ModuleLoader__.load({
2453
2486
  return `${pct.toFixed(1)}%`;
2454
2487
  }
2455
2488
  /**
2456
- * Whole-session durations for the timing card, in the locale's units: raw ms
2457
- * under a second, one-decimal seconds under a minute, then m/s and h/m.
2489
+ * Whole-session durations for the timing card, locale-free compact units: raw
2490
+ * ms under a second, one-decimal seconds under a minute, then m/s and h/m.
2458
2491
  * Non-finite or non-positive input shows the dash (callers render their empty
2459
2492
  * state anyway).
2460
2493
  */
2461
- function fmtDuration(ms, lang) {
2494
+ function fmtDuration(ms) {
2462
2495
  if (!Number.isFinite(ms) || ms <= 0) return "—";
2463
2496
  if (ms < 1e3) return `${Math.round(ms)}ms`;
2464
- const zh = lang === "zh";
2465
- if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}${zh ? "秒" : "s"}`;
2497
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
2466
2498
  const totalSec = Math.floor(ms / 1e3);
2467
2499
  const m = Math.floor(totalSec / 60);
2468
2500
  const s = totalSec % 60;
2469
- if (ms < 36e5) return zh ? `${m}分${s}秒` : `${m}m ${s}s`;
2470
- const h = Math.floor(m / 60);
2471
- return zh ? `${h}时${m % 60}分` : `${h}h ${m % 60}m`;
2501
+ if (ms < 36e5) return `${m}m${s}s`;
2502
+ return `${Math.floor(m / 60)}h${m % 60}m`;
2472
2503
  }
2473
2504
  //#endregion
2474
2505
  //#region src/shared/imageTokens.ts
@@ -2789,7 +2820,7 @@ window.__ModuleLoader__.load({
2789
2820
  /**
2790
2821
  * RichText — the raw/markdown body for the Context browser's detail sections. Markdown renders via the harness's shared MarkdownText (GFM,
2791
2822
  * sanitized, resolved from the platform module table — zero plugin-side markdown dependency); raw is a line-numbered `<pre>`. The Raw/MD
2792
- * switch sits at a section head's right edge (RichSwitch; per-card mode via useRichMode).
2823
+ * switch sits at a section head's right edge (RichSwitch; per-card mode via useRichMode), with the copy-raw control (RichCopy) beside it.
2793
2824
  */
2794
2825
  const Markdown = _deepseek_ai_dsh_client_ui_primitives.MarkdownText;
2795
2826
  function makeRichText(kit) {
@@ -2826,6 +2857,28 @@ window.__ModuleLoader__.load({
2826
2857
  }, index))
2827
2858
  });
2828
2859
  }
2860
+ function RichCopy(props) {
2861
+ const [copied, setCopied] = (0, react.useState)(false);
2862
+ const onCopy = (0, react.useCallback)(() => {
2863
+ if (copied) return;
2864
+ (0, _deepseek_ai_dsh_client_ui_primitives.writeClipboard)(props.text).then((ok) => {
2865
+ if (!ok) return;
2866
+ setCopied(true);
2867
+ window.setTimeout(() => {
2868
+ setCopied(false);
2869
+ }, 1200);
2870
+ });
2871
+ }, [copied, props.text]);
2872
+ const label = copied ? t("rich.copied") : t("rich.copy");
2873
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2874
+ type: "button",
2875
+ className: "lc-rich-copy" + (copied ? " lc-rich-copy-on" : ""),
2876
+ title: label,
2877
+ "aria-label": label,
2878
+ onClick: onCopy,
2879
+ children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline16, { size: 13 }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCopyOutline16, { size: 13 })
2880
+ });
2881
+ }
2829
2882
  function RichText(props) {
2830
2883
  const mdLabels = (0, react.useMemo)(() => ({
2831
2884
  code: {
@@ -2846,6 +2899,7 @@ window.__ModuleLoader__.load({
2846
2899
  return {
2847
2900
  RichText,
2848
2901
  RichSwitch,
2902
+ RichCopy,
2849
2903
  useRichMode
2850
2904
  };
2851
2905
  }
@@ -2927,7 +2981,7 @@ window.__ModuleLoader__.load({
2927
2981
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2928
2982
  className: "lc-ts-card",
2929
2983
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2930
- className: "lc-ts-card-head",
2984
+ className: "lc-ts-card-head" + (props.foldHead === true ? " lc-ts-card-head-wrap" : ""),
2931
2985
  children: [
2932
2986
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
2933
2987
  className: props.labelClass,
@@ -2955,10 +3009,11 @@ window.__ModuleLoader__.load({
2955
3009
  const lineCount = (0, react.useMemo)(() => lineCountOf(props.text), [props.text]);
2956
3010
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Section, {
2957
3011
  label: props.label,
2958
- actions: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(rich.RichSwitch, {
3012
+ foldHead: true,
3013
+ actions: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(rich.RichSwitch, {
2959
3014
  mode,
2960
3015
  onPick: setMode
2961
- }),
3016
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(rich.RichCopy, { text: props.text })] }),
2962
3017
  meta: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2963
3018
  className: "lc-ts-card-meta",
2964
3019
  children: props.lines(lineCount)
@@ -3924,7 +3979,7 @@ window.__ModuleLoader__.load({
3924
3979
  className: "lc-stacked-seg" + (on ? " lc-stacked-seg-on" : "") + (pickKey !== void 0 ? " lc-stacked-seg-pick" : ""),
3925
3980
  style: {
3926
3981
  width: `${widths[i]}%`,
3927
- background: p.color
3982
+ backgroundColor: p.color
3928
3983
  },
3929
3984
  onMouseEnter: () => {
3930
3985
  if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
@@ -5662,9 +5717,9 @@ window.__ModuleLoader__.load({
5662
5717
  }
5663
5718
  /**
5664
5719
  * Compact duration: `42s`, `3m05s`, `1h07m` (shared by both locales).
5665
- * Deliberately distinct from format.ts's locale-aware `fmtDuration` (the
5666
- * timing card's `12.3s` / `4分0秒`): the inspector's caption column needs
5667
- * whole-second, fixed-width, locale-free text.
5720
+ * Deliberately distinct from format.ts's `fmtDuration` (the timing card's
5721
+ * `12.3s` / `3m25s`): the inspector's caption column needs whole-second,
5722
+ * fixed-width text.
5668
5723
  */
5669
5724
  function fmtDurationCompact(ms) {
5670
5725
  if (!Number.isFinite(ms) || ms < 0) return "—";
@@ -6579,7 +6634,7 @@ window.__ModuleLoader__.load({
6579
6634
  return function PluginInfo() {
6580
6635
  const [latest, setLatest] = (0, react.useState)(null);
6581
6636
  (0, react.useEffect)(() => {
6582
- if ("0.47.0".includes("-dev")) return;
6637
+ if ("0.49.0".includes("-dev")) return;
6583
6638
  let on = true;
6584
6639
  fetchLatestVersion().then((v) => {
6585
6640
  if (on && v) setLatest(v);
@@ -6588,8 +6643,8 @@ window.__ModuleLoader__.load({
6588
6643
  on = false;
6589
6644
  };
6590
6645
  }, []);
6591
- const update = latest !== null && isNewerVersion(latest, "0.47.0") ? latest : null;
6592
- const nameText = "dsh-context (v0.47.0)";
6646
+ const update = latest !== null && isNewerVersion(latest, "0.49.0") ? latest : null;
6647
+ const nameText = "dsh-context (v0.49.0)";
6593
6648
  const nameValue = [nameText];
6594
6649
  if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6595
6650
  className: "lc-pi-update",
@@ -7046,61 +7101,49 @@ window.__ModuleLoader__.load({
7046
7101
  }
7047
7102
  //#endregion
7048
7103
  //#region src/client/cost.ts
7049
- const PRICES = {
7104
+ /** The Flash-series rates (peak = 2× off-peak), per currency. */
7105
+ const FLASH_RATES = {
7050
7106
  usd: {
7051
- flash: {
7052
- peak: {
7053
- hit: .014,
7054
- miss: .44,
7055
- out: 1.32
7056
- },
7057
- off: {
7058
- hit: .007,
7059
- miss: .22,
7060
- out: .66
7061
- }
7107
+ peak: {
7108
+ hit: .006,
7109
+ miss: .3,
7110
+ out: 1.2
7062
7111
  },
7063
- pro: {
7064
- peak: {
7065
- hit: .044,
7066
- miss: 1.32,
7067
- out: 3.96
7068
- },
7069
- off: {
7070
- hit: .022,
7071
- miss: .66,
7072
- out: 1.98
7073
- }
7112
+ off: {
7113
+ hit: .003,
7114
+ miss: .15,
7115
+ out: .6
7074
7116
  }
7075
7117
  },
7076
7118
  cny: {
7077
- flash: {
7078
- peak: {
7079
- hit: .1,
7080
- miss: 3,
7081
- out: 9
7082
- },
7083
- off: {
7084
- hit: .05,
7085
- miss: 1.5,
7086
- out: 4.5
7087
- }
7119
+ peak: {
7120
+ hit: .04,
7121
+ miss: 2,
7122
+ out: 8
7088
7123
  },
7089
- pro: {
7090
- peak: {
7091
- hit: .3,
7092
- miss: 9,
7093
- out: 27
7094
- },
7095
- off: {
7096
- hit: .15,
7097
- miss: 4.5,
7098
- out: 13.5
7099
- }
7124
+ off: {
7125
+ hit: .02,
7126
+ miss: 1,
7127
+ out: 4
7100
7128
  }
7101
7129
  }
7102
7130
  };
7103
7131
  /**
7132
+ * Pro rides the Flash triple: between the V4.1 Flash launch and the V4.1 Pro
7133
+ * release, DeepSeek routes every Pro request to V4.1 Flash and bills it at
7134
+ * the Flash rates — point `pro` at the new table once V4.1 Pro is priced.
7135
+ */
7136
+ const PRICES = {
7137
+ usd: {
7138
+ flash: FLASH_RATES.usd,
7139
+ pro: FLASH_RATES.usd
7140
+ },
7141
+ cny: {
7142
+ flash: FLASH_RATES.cny,
7143
+ pro: FLASH_RATES.cny
7144
+ }
7145
+ };
7146
+ /**
7104
7147
  * Price the session's cumulative billed-token totals. Cache reads bill at
7105
7148
  * the hit rate; uncached input AND cache writes bill at the miss rate;
7106
7149
  * output (reasoning included) bills at the out rate. Null when nothing was
@@ -7135,7 +7178,7 @@ window.__ModuleLoader__.load({
7135
7178
  */
7136
7179
  function sessionPrices(currency) {
7137
7180
  return ["flash", "pro"].map((id) => ({
7138
- family: id === "flash" ? "deepseek-v4-flash" : "deepseek-v4-pro",
7181
+ family: id === "flash" ? "deepseek-v4.1-flash / deepseek-flash" : "deepseek-v4-pro",
7139
7182
  peak: PRICES[currency][id].peak,
7140
7183
  off: PRICES[currency][id].off
7141
7184
  }));
@@ -7330,90 +7373,139 @@ window.__ModuleLoader__.load({
7330
7373
  * token, the wait) and LLM generation (first token → assistant message) —
7331
7374
  * and the tool-execution slice, with the residue as overhead; a call whose
7332
7375
  * stream recorded no token delta (legacy or aborted) stays unattributed and
7333
- * lands in the residue. The slice rows lead with the true duration and
7334
- * qualify it with the call count on the secondary line. The donut and the
7335
- * rows sit side by side so the head row stays half-height. Parallel tool
7336
- * calls each count, so the tools figure can overlap the ring clamps it
7337
- * into the post-model window while the row numbers stay true.
7338
- */
7376
+ * lands in the residue. When the host folded the generation split (see
7377
+ * TimingTotals), the generation slice expands into what was being decoded:
7378
+ * thinking, answer text, and tool-call arguments. The slice rows lead with
7379
+ * the true duration and qualify it with the call count on the secondary line
7380
+ * except the decode slices, which count BLOCKS (zero to many per call, so a
7381
+ * call count would be a false tally) and therefore carry no qualifier. A slice
7382
+ * that never happened (zero time) is omitted entirely — the ring already skips
7383
+ * its arc, and a zero row would otherwise borrow the session's call count and
7384
+ * read as "no time, many calls". The donut and the rows sit side by side so
7385
+ * the head row stays half-height. Parallel tool calls each count, so the tools
7386
+ * figure can overlap — the ring clamps it into the post-model window while the
7387
+ * row numbers stay true.
7388
+ */
7389
+ const COLOR = {
7390
+ ttft: "#3b82f6",
7391
+ reasoning: "#8b5cf6",
7392
+ text: "#ec4899",
7393
+ toolarg: "#f59e0b",
7394
+ tools: "#14b8a6",
7395
+ other: "#94a3b8"
7396
+ };
7339
7397
  function makeStatsTiming(kit, Donut) {
7340
7398
  const { t, fmt, fmtDuration, fmtShare } = kit;
7341
7399
  const SliceList = makeSliceList(kit);
7342
7400
  return function StatsTiming(props) {
7343
- const lang = props.locale === "zh" ? "zh" : "en";
7344
7401
  const [hoverKey, setHoverKey] = (0, react.useState)(null);
7345
7402
  const timing = props.timing;
7346
7403
  const wall = timing !== null && Number.isFinite(timing.wallMs) && timing.wallMs > 0 ? timing.wallMs : 0;
7347
7404
  let segments = [];
7348
7405
  let rows = [];
7349
7406
  if (timing !== null && (wall > 0 || timing.calls > 0 || timing.toolCalls > 0)) {
7350
- const ttft = Math.min(timing.ttftMs, wall);
7351
- const gen = Math.max(0, Math.min(timing.genMs, wall - ttft));
7352
- const toolRing = Math.max(0, Math.min(timing.toolsMs, wall - ttft - gen));
7353
- const other = Math.max(0, wall - ttft - gen - toolRing);
7407
+ const callTimes = t("timing.callTimes", { n: fmt(timing.calls) });
7408
+ const modelSlices = [{
7409
+ key: "ttft",
7410
+ color: COLOR.ttft,
7411
+ label: t("timing.ttft"),
7412
+ ms: timing.ttftMs,
7413
+ times: callTimes
7414
+ }];
7415
+ const buckets = [
7416
+ [
7417
+ "reasoning",
7418
+ COLOR.reasoning,
7419
+ t("timing.reasoning"),
7420
+ timing.reasoningMs ?? 0
7421
+ ],
7422
+ [
7423
+ "text",
7424
+ COLOR.text,
7425
+ t("timing.text"),
7426
+ timing.textMs ?? 0
7427
+ ],
7428
+ [
7429
+ "toolarg",
7430
+ COLOR.toolarg,
7431
+ t("timing.toolArgs"),
7432
+ timing.toolArgMs ?? 0
7433
+ ]
7434
+ ];
7435
+ if (buckets.some(([, , , ms]) => ms > 0)) for (const [key, color, label, ms] of buckets) modelSlices.push({
7436
+ key,
7437
+ color,
7438
+ label,
7439
+ ms
7440
+ });
7441
+ else modelSlices.push({
7442
+ key: "gen",
7443
+ color: COLOR.reasoning,
7444
+ label: t("timing.gen"),
7445
+ ms: timing.genMs
7446
+ });
7447
+ const ttftRing = Math.min(timing.ttftMs, wall);
7448
+ const genRing = Math.max(0, Math.min(timing.genMs, wall - ttftRing));
7449
+ const modelRings = [ttftRing];
7450
+ let used = ttftRing;
7451
+ const genEnd = ttftRing + genRing;
7452
+ for (const slice of modelSlices.slice(1)) {
7453
+ const take = Math.max(0, Math.min(slice.ms, genEnd - used));
7454
+ modelRings.push(take);
7455
+ used += take;
7456
+ }
7457
+ const toolRing = Math.max(0, Math.min(timing.toolsMs, wall - ttftRing - genRing));
7458
+ const other = Math.max(0, wall - ttftRing - genRing - toolRing);
7354
7459
  const share = (ms) => wall > 0 ? ms / wall : 0;
7355
7460
  const countOf = (ms, times) => {
7356
- const dur = fmtDuration(ms, lang);
7461
+ const dur = fmtDuration(ms);
7357
7462
  if (times === void 0) return dur;
7358
7463
  return ms > 0 ? `${dur} · ${times}` : times;
7359
7464
  };
7360
- const callTimes = t("timing.callTimes", { n: fmt(timing.calls) });
7465
+ const modelSegments = modelSlices.map((slice, index) => ({
7466
+ key: slice.key,
7467
+ color: slice.color,
7468
+ value: share(modelRings[index])
7469
+ }));
7470
+ const toolSlice = {
7471
+ key: "tools",
7472
+ color: COLOR.tools,
7473
+ label: t("timing.tools"),
7474
+ ms: timing.toolsMs,
7475
+ times: t("timing.toolTimes", { n: fmt(timing.toolCalls) })
7476
+ };
7477
+ const otherSlice = {
7478
+ key: "other",
7479
+ color: COLOR.other,
7480
+ label: t("timing.other"),
7481
+ ms: other
7482
+ };
7361
7483
  segments = [
7362
- {
7363
- key: "ttft",
7364
- color: "#3b82f6",
7365
- value: share(ttft)
7366
- },
7367
- {
7368
- key: "gen",
7369
- color: "#8b5cf6",
7370
- value: share(gen)
7371
- },
7484
+ ...modelSegments,
7372
7485
  {
7373
7486
  key: "tools",
7374
- color: "#14b8a6",
7487
+ color: COLOR.tools,
7375
7488
  value: share(toolRing)
7376
7489
  },
7377
7490
  {
7378
7491
  key: "other",
7379
- color: "#94a3b8",
7492
+ color: COLOR.other,
7380
7493
  value: share(other)
7381
7494
  }
7382
7495
  ];
7496
+ const toRow = (slice) => ({
7497
+ key: slice.key,
7498
+ color: slice.color,
7499
+ label: slice.label,
7500
+ dim: slice.ms === 0,
7501
+ pct: fmtShare(slice.ms, wall),
7502
+ count: countOf(slice.ms, slice.times)
7503
+ });
7383
7504
  rows = [
7384
- {
7385
- key: "ttft",
7386
- color: "#3b82f6",
7387
- label: t("timing.ttft"),
7388
- dim: timing.ttftMs === 0,
7389
- pct: fmtShare(timing.ttftMs, wall),
7390
- count: countOf(timing.ttftMs, callTimes)
7391
- },
7392
- {
7393
- key: "gen",
7394
- color: "#8b5cf6",
7395
- label: t("timing.gen"),
7396
- dim: timing.genMs === 0,
7397
- pct: fmtShare(timing.genMs, wall),
7398
- count: countOf(timing.genMs, callTimes)
7399
- },
7400
- {
7401
- key: "tools",
7402
- color: "#14b8a6",
7403
- label: t("timing.tools"),
7404
- dim: timing.toolsMs === 0,
7405
- pct: fmtShare(timing.toolsMs, wall),
7406
- count: countOf(timing.toolsMs, t("timing.toolTimes", { n: fmt(timing.toolCalls) }))
7407
- },
7408
- {
7409
- key: "other",
7410
- color: "#94a3b8",
7411
- label: t("timing.other"),
7412
- dim: other === 0,
7413
- pct: fmtShare(other, wall),
7414
- count: countOf(other)
7415
- }
7416
- ];
7505
+ ...modelSlices.map(toRow),
7506
+ toRow(toolSlice),
7507
+ toRow(otherSlice)
7508
+ ].filter((row) => !row.dim);
7417
7509
  }
7418
7510
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7419
7511
  className: "lc-card lc-col-stats lc-col-donut",
@@ -7431,7 +7523,7 @@ window.__ModuleLoader__.load({
7431
7523
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
7432
7524
  segments,
7433
7525
  size: 96,
7434
- centerTop: wall > 0 ? fmtDuration(wall, lang) : "—",
7526
+ centerTop: wall > 0 ? fmtDuration(wall) : "—",
7435
7527
  centerSub: t("timing.total"),
7436
7528
  hoverKey,
7437
7529
  onHoverKey: setHoverKey
@@ -8500,35 +8592,19 @@ window.__ModuleLoader__.load({
8500
8592
  locale: activeLocale
8501
8593
  }),
8502
8594
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTokens, { usage }),
8503
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTiming, {
8504
- timing: data.timing ?? null,
8505
- locale: activeLocale
8506
- }),
8595
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTiming, { timing: data.timing ?? null }),
8507
8596
  inSidebar ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginInfo, {})
8508
8597
  ]
8509
8598
  }),
8510
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8599
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8511
8600
  className: "lc-cols lc-cols-main",
8512
- children: inSidebar ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8513
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8514
- className: "lc-col",
8515
- children: compositionCard
8516
- }),
8517
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8518
- className: "lc-col lc-col-browser",
8519
- children: browserCard
8520
- }),
8521
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8522
- className: "lc-col",
8523
- children: trendCard
8524
- })
8525
- ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8601
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8526
8602
  className: "lc-col",
8527
8603
  children: [compositionCard, trendCard]
8528
8604
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8529
8605
  className: "lc-col lc-col-browser",
8530
8606
  children: browserCard
8531
- })] })
8607
+ })]
8532
8608
  }),
8533
8609
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8534
8610
  className: "lc-cols",
@@ -8676,7 +8752,7 @@ window.__ModuleLoader__.load({
8676
8752
  //#endregion
8677
8753
  //#region src/client/sidebar.ts
8678
8754
  /**
8679
- * The right Sidebar's Context tab (dsh 0.1.5-alpha.1+).
8755
+ * The right Sidebar's Context tab (dsh 0.1.5-rc.1+).
8680
8756
  *
8681
8757
  * The tab reuses the Context conversation-view component VERBATIM: the
8682
8758
  * `sidebar.right.pane.tab` seat is session-scoped and delivers the same
@@ -8684,14 +8760,15 @@ window.__ModuleLoader__.load({
8684
8760
  * `t` seat) the `conversation.view` seat does, so the panel and the tab are
8685
8761
  * one component with one data path. The tab type contributes a guide entry, so
8686
8762
  * the sidebar's guide page offers "Context" and picking it opens the panel —
8687
- * the product's own path, exactly as the shipped Files type does.
8763
+ * the product's own path, exactly as the shipped Files type does: a capsule of
8764
+ * glyph, title, and description line.
8688
8765
  *
8689
- * OPTIONAL BY CONTRACT. `ctx.sidebarRightTabs` and the seat exist only from
8690
- * dsh 0.1.5-alpha.1; the registration therefore rides a DEFERRED inject (the
8691
- * plugin's hard injects stay `slots` + `locale`), so on every older supported
8692
- * line the callback never fires, the plugin fiber never pends, and nothing is
8693
- * registered. The registry is re-proved structurally and the whole
8694
- * registration is guarded: a foreign or hostile registry (a throwing
8766
+ * OPTIONAL BY CONTRACT. `ctx.sidebarRightTabs` and the seat ship only on the
8767
+ * 0.1.5 line (0.1.5-rc.1+ supported); the registration therefore rides a
8768
+ * DEFERRED inject (the plugin's hard injects stay `slots` + `locale`), so on
8769
+ * every older supported line the callback never fires, the plugin fiber never
8770
+ * pends, and nothing is registered. The registry is re-proved structurally and
8771
+ * the whole registration is guarded: a foreign or hostile registry (a throwing
8695
8772
  * `register`, a taken id/kind) leaves the sidebar without the tab instead of
8696
8773
  * taking the browser down.
8697
8774
  *
@@ -8705,13 +8782,13 @@ window.__ModuleLoader__.load({
8705
8782
  * non-coexisting band, and a foreign plugin may well own `context`.
8706
8783
  */
8707
8784
  const SIDEBAR_CONTEXT_KIND = "dsh-context";
8708
- /** The guide box's position: after the shipped Files entry (order 10). */
8785
+ /** The guide capsule's position: after the shipped Files entry (order 10). */
8709
8786
  const GUIDE_ORDER = 20;
8710
8787
  /**
8711
- * The guide box's glyph, read defensively: the icon is OPTIONAL in the entry,
8712
- * and a primitives module that does not serve it — or whose namespace THROWS
8713
- * on the read (an interop/mock shape does exactly that) — must cost the glyph,
8714
- * never the whole tab.
8788
+ * The guide capsule's glyph, read defensively: the icon is OPTIONAL in the
8789
+ * entry, and a primitives module that does not serve it — or whose namespace
8790
+ * THROWS on the read (an interop/mock shape does exactly that) — must cost the
8791
+ * glyph, never the whole tab.
8715
8792
  * @returns the icon component, or undefined to register the tab without one.
8716
8793
  */
8717
8794
  function guideIcon() {
@@ -8783,7 +8860,7 @@ window.__ModuleLoader__.load({
8783
8860
  }
8784
8861
  //#endregion
8785
8862
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/base.css.mjs
8786
- const css$13 = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}[data-sidebar-right-open] .lc-root,[data-sidebar-right-float-host] .lc-root{height:auto;padding:0;overflow:visible}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-root .lc-card{margin-bottom:7px;padding:7px 8px;container:lc-card/inline-size}.lc-root .lc-cols{gap:7px;margin-bottom:7px}.lc-card-title{flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran{margin-left:0}.lc-trend-adaptive{flex:none;margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3,0 2px 8px #0000002e);pointer-events:none;opacity:0;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:6px 10px;font-size:12px;position:absolute}.lc-events,.lc-fa-list{flex-direction:column;gap:2px;height:320px;display:flex;overflow-y:auto}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:min(360px,100%)}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head{container:lc-head-row/inline-size}.lc-head>.lc-card{flex:1 1 0;min-width:0}@container lc-head-row (width<=1045px){.lc-head>.lc-card{flex-basis:40%}}@container lc-head-row (width<=480px){.lc-head>.lc-card{flex-basis:100%}}.lc-head>.lc-card:first-child{flex-direction:column;display:flex}.lc-head>.lc-card:first-child .lc-stats{flex:1;align-content:stretch}.lc-head>.lc-card:first-child .lc-stat{justify-content:center}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:4px 14px;font-size:12px}.lc-error-retry:hover{border-color:var(--dsw-alias-label-primary)}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])),[data-conversation-scroll]:has(.lc-root,.lc-modal-backdrop)~[data-width-handle]{display:none}";
8863
+ const css$13 = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}[data-sidebar-right-open] .lc-root,[data-sidebar-right-float-host] .lc-root{height:auto;padding:12px;overflow:visible}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-root .lc-card{margin-bottom:7px;padding:7px 8px;container:lc-card/inline-size}.lc-root .lc-cols{gap:7px;margin-bottom:7px}.lc-card-title{flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran{margin-left:0}.lc-trend-adaptive{flex:none;margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3,0 2px 8px #0000002e);pointer-events:none;opacity:0;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:6px 10px;font-size:12px;position:absolute}.lc-events,.lc-fa-list{flex-direction:column;gap:2px;height:320px;display:flex;overflow-y:auto}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:min(360px,100%)}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head{container:lc-head-row/inline-size}.lc-head>.lc-card{flex:1 1 0;min-width:0}@container lc-head-row (width<=1045px){.lc-head>.lc-card{flex-basis:40%}}@container lc-head-row (width<=480px){.lc-head>.lc-card{flex-basis:100%}}.lc-head>.lc-card:first-child{flex-direction:column;display:flex}.lc-head>.lc-card:first-child .lc-stats{flex:1;align-content:stretch}.lc-head>.lc-card:first-child .lc-stat{justify-content:center}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:4px 14px;font-size:12px}.lc-error-retry:hover{border-color:var(--dsw-alias-label-primary)}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])),[data-conversation-scroll]:has(.lc-root,.lc-modal-backdrop)~[data-width-handle]{display:none}";
8787
8864
  const tagId$13 = "dsh-context/base.css";
8788
8865
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$13) + "]") === null) {
8789
8866
  const tag = document.createElement("style");
@@ -8838,7 +8915,7 @@ window.__ModuleLoader__.load({
8838
8915
  }
8839
8916
  //#endregion
8840
8917
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/trendChart.css.mjs
8841
- const css$8 = ".lc-chartrow{align-items:stretch;gap:6px;display:flex}.lc-axis{box-sizing:border-box;width:40px;height:150px;color:var(--dsw-alias-label-secondary);padding-top:18px;font-size:11px;position:relative}.lc-axis span{line-height:1;position:absolute;right:0}.lc-axis-top{top:13px}.lc-axis-q3{top:41px}.lc-axis-mid{top:69px}.lc-axis-q1{top:97px}.lc-axis-bot{top:125px}.lc-chart-wrap{flex:1;min-width:0;position:relative}.lc-chart-scroll{overscroll-behavior-x:contain;scrollbar-gutter:stable;padding-bottom:var(--dsh-scrollbar-width,8px);overflow:auto hidden}.lc-chart{box-sizing:border-box;align-items:flex-end;gap:2px;width:max-content;min-width:100%;height:130px;padding-top:18px;display:flex;position:relative}.lc-grid{border-top:1px dashed var(--dsw-alias-border-l2);pointer-events:none;position:absolute;left:0;right:0}.lc-grid-top{top:18px}.lc-grid-q3{top:46px}.lc-grid-mid{top:74px}.lc-grid-q1{top:102px}.lc-grid-zero{border-top-style:solid;border-top-color:#80808080;top:130px}.lc-bar{cursor:pointer;outline-offset:1px;width:14px;height:100%;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), outline-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:2px;outline:1px dashed #0000;flex:none;align-items:flex-end;display:flex;position:relative}.lc-chart-dim .lc-bar{opacity:.35}.lc-chart-dim .lc-bar-in-turn{opacity:1}.lc-chart-dim .lc-turn{opacity:.35}.lc-chart-dim .lc-turn-on{opacity:1}.lc-chart-tip{z-index:5;box-sizing:border-box;overflow-wrap:break-word;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);max-width:100%;color:var(--dsw-alias-label-primary);pointer-events:none;border-radius:6px;flex-direction:column;gap:2px;padding:3px 8px;font-size:12px;display:flex;position:absolute;bottom:calc(100% + 4px);left:0;box-shadow:0 2px 8px #0000002e}.lc-bar:hover{background:var(--dsw-alias-bg-layer-2)}.lc-bar-hovered{outline-color:var(--dsw-alias-brand-primary)}.lc-bar-selected{outline:2px solid var(--dsw-alias-label-primary);outline-offset:1px}.lc-bar-in-turn{background:#80808024}.lc-bar-stack{flex-direction:column-reverse;width:100%;display:flex}.lc-bar-stack>div{width:100%}.lc-bar-up,.lc-bar-down{width:100%;display:flex;position:absolute;left:0}.lc-bar-up{flex-direction:column-reverse}.lc-bar-down{flex-direction:column}.lc-bar-up>div,.lc-bar-down>div{width:100%}.lc-cat-seg{transition:filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out)}.lc-chart[data-catdim] .lc-cat-seg{opacity:.35}.lc-chart[data-catdim=system] .lc-cat-seg[data-cat=system],.lc-chart[data-catdim=tools] .lc-cat-seg[data-cat=tools],.lc-chart[data-catdim=user] .lc-cat-seg[data-cat=user],.lc-chart[data-catdim=inject] .lc-cat-seg[data-cat=inject],.lc-chart[data-catdim=assistant] .lc-cat-seg[data-cat=assistant],.lc-chart[data-catdim=tool] .lc-cat-seg[data-cat=tool]{opacity:1;filter:brightness(1.18)}.lc-bar-marker{color:var(--dsw-alias-state-warn-primary);font-size:11px;position:absolute;top:-16px;left:50%;transform:translate(-50%)}.lc-turns{gap:2px;width:max-content;min-width:100%;margin-top:4px;display:flex;overflow:hidden}.lc-turn{box-sizing:border-box;text-align:center;color:var(--dsw-alias-label-secondary);white-space:nowrap;cursor:pointer;height:14px;transition:filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:3px;flex:none;font-size:10px;font-weight:600;line-height:14px}.lc-turn-on{filter:brightness(1.35);color:var(--dsw-alias-label-primary)}.lc-turn-label{display:inline-block}";
8918
+ const css$8 = ".lc-chartrow{align-items:stretch;gap:6px;display:flex}.lc-axis{box-sizing:border-box;width:40px;height:150px;color:var(--dsw-alias-label-secondary);padding-top:18px;font-size:11px;position:relative}.lc-axis span{line-height:1;position:absolute;right:0}.lc-axis-top{top:13px}.lc-axis-q3{top:41px}.lc-axis-mid{top:69px}.lc-axis-q1{top:97px}.lc-axis-bot{top:125px}.lc-chart-wrap{flex:1;min-width:0;position:relative}.lc-chart-scroll{overscroll-behavior-x:contain;scrollbar-gutter:stable;padding-bottom:var(--dsh-scrollbar-width,8px);overflow:auto hidden}.lc-chart{box-sizing:border-box;align-items:flex-end;gap:2px;width:max-content;min-width:100%;height:130px;padding-top:18px;display:flex;position:relative}.lc-grid{border-top:1px dashed var(--dsw-alias-border-l2);pointer-events:none;position:absolute;left:0;right:0}.lc-grid-top{top:18px}.lc-grid-q3{top:46px}.lc-grid-mid{top:74px}.lc-grid-q1{top:102px}.lc-grid-zero{border-top-style:solid;border-top-color:#80808080;top:130px}.lc-bar{cursor:pointer;outline-offset:1px;width:14px;height:100%;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), outline-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:2px;outline:1px dashed #0000;flex:none;align-items:flex-end;display:flex;position:relative}.lc-chart-dim .lc-bar{opacity:.35}.lc-chart-dim .lc-bar-in-turn{opacity:1}.lc-chart-dim .lc-turn{opacity:.35}.lc-chart-dim .lc-turn-on{opacity:1}.lc-chart-tip{z-index:5;box-sizing:border-box;overflow-wrap:break-word;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);max-width:100%;color:var(--dsw-alias-label-primary);pointer-events:none;border-radius:6px;flex-direction:column;gap:2px;padding:3px 8px;font-size:12px;display:flex;position:absolute;bottom:calc(100% + 4px);left:0;box-shadow:0 2px 8px #0000002e}.lc-bar:hover{background:var(--dsw-alias-bg-layer-2)}.lc-bar-hovered{outline-color:var(--dsw-alias-brand-primary)}.lc-bar-selected{outline:2px solid var(--dsw-alias-label-primary);outline-offset:1px}.lc-bar-in-turn{background:#80808024}.lc-bar-stack{flex-direction:column-reverse;width:100%;display:flex}.lc-bar-stack>div{width:100%}.lc-bar-stack>div:first-child{border-radius:0 0 2px 2px}.lc-bar-stack>div:last-child{border-radius:2px 2px 0 0}.lc-bar-stack>div:only-child{border-radius:2px}.lc-bar-up,.lc-bar-down{width:100%;display:flex;position:absolute;left:0}.lc-bar-up{flex-direction:column-reverse}.lc-bar-down{flex-direction:column}.lc-bar-up>div,.lc-bar-down>div{width:100%}.lc-bar-up>div:last-child{border-radius:2px 2px 0 0}.lc-bar-down>div:last-child{border-radius:0 0 2px 2px}.lc-cat-seg{transition:filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out)}.lc-chart[data-catdim] .lc-cat-seg{opacity:.35}.lc-chart[data-catdim=system] .lc-cat-seg[data-cat=system],.lc-chart[data-catdim=tools] .lc-cat-seg[data-cat=tools],.lc-chart[data-catdim=user] .lc-cat-seg[data-cat=user],.lc-chart[data-catdim=inject] .lc-cat-seg[data-cat=inject],.lc-chart[data-catdim=assistant] .lc-cat-seg[data-cat=assistant],.lc-chart[data-catdim=tool] .lc-cat-seg[data-cat=tool]{opacity:1;filter:brightness(1.18)}.lc-bar-marker{color:var(--dsw-alias-state-warn-primary);font-size:11px;position:absolute;top:-16px;left:50%;transform:translate(-50%)}.lc-turns{gap:2px;width:max-content;min-width:100%;margin-top:4px;display:flex;overflow:hidden}.lc-turn{box-sizing:border-box;text-align:center;color:var(--dsw-alias-label-secondary);white-space:nowrap;cursor:pointer;height:14px;transition:filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:3px;flex:none;font-size:10px;font-weight:600;line-height:14px}.lc-turn-on{filter:brightness(1.35);color:var(--dsw-alias-label-primary)}.lc-turn-label{display:inline-block}";
8842
8919
  const tagId$8 = "dsh-context/trendChart.css";
8843
8920
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$8) + "]") === null) {
8844
8921
  const tag = document.createElement("style");
@@ -8893,7 +8970,7 @@ window.__ModuleLoader__.load({
8893
8970
  }
8894
8971
  //#endregion
8895
8972
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/browser.css.mjs
8896
- const css$3 = ".lc-br-dna-ctl{flex:none;margin-left:0}.lc-br-bar-dna .lc-stacked-seg{box-shadow:inset -1px 0 0 var(--dsw-alias-bg-layer-1)}.lc-br-hint{color:var(--dsw-alias-label-secondary);white-space:nowrap;margin-left:auto;font-size:11px;font-weight:400}.lc-br-pick{font:inherit;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;max-width:240px;padding:3px 6px;font-size:12px}.lc-br-meta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:6px 16px;margin-bottom:8px;display:flex}.lc-br-meta b{color:var(--dsw-alias-label-primary)}.lc-br-meta .lc-actual{color:var(--dsw-alias-state-success-primary)}.lc-br-note{color:var(--dsw-alias-label-secondary);margin-top:8px;font-size:12px}.lc-br-retry{color:var(--dsw-alias-brand-primary);font:inherit;cursor:pointer;text-underline-offset:2px;transition:filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;padding:0;font-size:12px;text-decoration:underline}.lc-br-retry:hover{filter:brightness(1.15)}.lc-br-cats{flex-direction:column;gap:4px;margin-top:10px;display:flex}.lc-br-cat{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:8px;overflow:hidden}.lc-br-cat-empty{opacity:.55}.lc-br-cat-row{width:100%;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;text-align:left;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;align-items:center;gap:8px;padding:7px 10px;display:flex}.lc-br-cat-row:hover,.lc-br-cat-on{background:var(--dsw-alias-interactive-bg-hover)}.lc-br-cat-on i{box-shadow:0 0 0 1px var(--dsw-alias-brand-primary)}.lc-br-cat-row i{width:8px;height:8px;transition:box-shadow var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:2px;flex:none;display:inline-block}.lc-br-cat-label{font-weight:600}.lc-br-cat-count{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px}.lc-br-count-grp{flex:1;align-items:center;gap:4px;min-width:0;display:inline-flex}.lc-br-chev{width:12px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:flex}.lc-br-chev:before{content:\"\";width:5px;height:5px;transition:transform var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-bottom:1px solid;border-right:1px solid;transform:rotate(-45deg)}.lc-br-chev-on:before{transform:rotate(45deg)}.lc-br-tokens{color:var(--dsw-alias-label-secondary);flex:none;font-size:12px}.lc-br-delta,.lc-br-tdelta{white-space:nowrap;border-radius:4px;flex:none;padding:0 5px;font-size:11px;font-weight:600}.lc-br-delta-up,.lc-br-tdelta-up{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent)}.lc-br-delta-down,.lc-br-tdelta-down{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent)}.lc-br-tokens-grp{flex:none;align-items:center;gap:4px;min-width:0;display:inline-flex}.lc-br-pct{text-align:right;width:36px;color:var(--dsw-alias-label-secondary);flex:none;font-size:12px}.lc-br-body{border-top:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:2px;padding:4px 6px;display:flex}.lc-br-elem{border-radius:6px}.lc-br-elem-on{background:var(--dsw-alias-interactive-bg-active)}.lc-br-elem-row{width:100%;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;text-align:left;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:6px;align-items:center;gap:8px;padding:5px 6px;font-size:12px;display:flex}.lc-br-elem-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-br-err-dot{background:var(--dsw-alias-state-error-primary);width:6px;height:6px;box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);border-radius:50%;flex:none}.lc-br-tag{min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);text-overflow:ellipsis;white-space:nowrap;border-radius:4px;max-width:220px;padding:0 6px;font-size:11px;overflow:hidden}.lc-br-tool-plugin{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent);border-color:#0000;border-radius:6px;font-size:12px}.lc-br-preview{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.lc-br-time{color:var(--dsw-alias-label-secondary);flex:none;font-size:11px}.lc-br-content{flex-direction:column;gap:6px;padding:2px 6px 8px 26px;display:flex}.lc-br-dim{color:var(--dsw-alias-label-secondary)}";
8973
+ const css$3 = ".lc-br-dna-ctl{flex:none;margin-left:0}.lc-br-bar-dna .lc-stacked-seg{background-image:linear-gradient(to right, transparent calc(100% - min(.5px, 12.5%)), var(--dsw-alias-bg-layer-1) 0)}.lc-br-hint{color:var(--dsw-alias-label-secondary);white-space:nowrap;margin-left:auto;font-size:11px;font-weight:400}.lc-br-pick{font:inherit;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;max-width:240px;padding:3px 6px;font-size:12px}.lc-br-meta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:6px 16px;margin-bottom:8px;display:flex}.lc-br-meta b{color:var(--dsw-alias-label-primary)}.lc-br-meta .lc-actual{color:var(--dsw-alias-state-success-primary)}.lc-br-note{color:var(--dsw-alias-label-secondary);margin-top:8px;font-size:12px}.lc-br-retry{color:var(--dsw-alias-brand-primary);font:inherit;cursor:pointer;text-underline-offset:2px;transition:filter var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;padding:0;font-size:12px;text-decoration:underline}.lc-br-retry:hover{filter:brightness(1.15)}.lc-br-cats{flex-direction:column;gap:4px;margin-top:10px;display:flex}.lc-br-cat{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);border-radius:8px;overflow:hidden}.lc-br-cat-empty{opacity:.55}.lc-br-cat-row{width:100%;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;text-align:left;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;align-items:center;gap:8px;padding:7px 10px;display:flex}.lc-br-cat-row:hover,.lc-br-cat-on{background:var(--dsw-alias-interactive-bg-hover)}.lc-br-cat-on i{box-shadow:0 0 0 1px var(--dsw-alias-brand-primary)}.lc-br-cat-row i{width:8px;height:8px;transition:box-shadow var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:2px;flex:none;display:inline-block}.lc-br-cat-label{font-weight:600}.lc-br-cat-count{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px}.lc-br-count-grp{flex:1;align-items:center;gap:4px;min-width:0;display:inline-flex}.lc-br-chev{width:12px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;display:flex}.lc-br-chev:before{content:\"\";width:5px;height:5px;transition:transform var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-bottom:1px solid;border-right:1px solid;transform:rotate(-45deg)}.lc-br-chev-on:before{transform:rotate(45deg)}.lc-br-tokens{color:var(--dsw-alias-label-secondary);flex:none;font-size:12px}.lc-br-delta,.lc-br-tdelta{white-space:nowrap;border-radius:4px;flex:none;padding:0 5px;font-size:11px;font-weight:600}.lc-br-delta-up,.lc-br-tdelta-up{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent)}.lc-br-delta-down,.lc-br-tdelta-down{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent)}.lc-br-tokens-grp{flex:none;align-items:center;gap:4px;min-width:0;display:inline-flex}.lc-br-pct{text-align:right;width:36px;color:var(--dsw-alias-label-secondary);flex:none;font-size:12px}.lc-br-body{border-top:1px solid var(--dsw-alias-border-l1);flex-direction:column;gap:2px;padding:4px 6px;display:flex}.lc-br-elem{border-radius:6px}.lc-br-elem-on{background:var(--dsw-alias-interactive-bg-active)}.lc-br-elem-row{width:100%;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;text-align:left;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:6px;align-items:center;gap:8px;padding:5px 6px;font-size:12px;display:flex}.lc-br-elem-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-br-err-dot{background:var(--dsw-alias-state-error-primary);width:6px;height:6px;box-shadow:0 0 0 2px color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);border-radius:50%;flex:none}.lc-br-tag{min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);text-overflow:ellipsis;white-space:nowrap;border-radius:4px;max-width:220px;padding:0 6px;font-size:11px;overflow:hidden}.lc-br-tool-plugin{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent);border-color:#0000;border-radius:6px;font-size:12px}.lc-br-preview{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.lc-br-time{color:var(--dsw-alias-label-secondary);flex:none;font-size:11px}.lc-br-content{flex-direction:column;gap:6px;padding:2px 6px 8px 26px;display:flex}.lc-br-dim{color:var(--dsw-alias-label-secondary)}";
8897
8974
  const tagId$3 = "dsh-context/browser.css";
8898
8975
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
8899
8976
  const tag = document.createElement("style");
@@ -8904,7 +8981,7 @@ window.__ModuleLoader__.load({
8904
8981
  }
8905
8982
  //#endregion
8906
8983
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/detailSections.css.mjs
8907
- const css$2 = ".lc-ts-card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;min-width:0;overflow:hidden}.lc-ts-card-head{color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:8px;padding:6px 10px;font-size:11px;font-weight:600;display:flex}.lc-ts-card-head b{color:var(--dsw-alias-label-primary)}.lc-ts-call-name{font-size:13px;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.lc-ts-card-count{margin-left:auto}.lc-ts-card-right{align-items:center;gap:8px;margin-left:auto;display:inline-flex}.lc-ts-card-meta{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:11px;font-weight:400}.lc-ts-call-state{white-space:nowrap;font-size:11px;font-weight:600;line-height:1;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);border-radius:999px;flex:none;align-items:center;gap:4px;padding:2px 7px;display:inline-flex}.lc-ts-call-state i{background:currentColor;border-radius:50%;flex:none;width:6px;height:6px}.lc-ts-call-ok{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent)}.lc-ts-call-err{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)}.lc-ts-desc-body{color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;scrollbar-width:thin;max-height:320px;margin:0;padding:8px 10px;font-size:12px;line-height:1.55;overflow-y:auto}.lc-ts-lines{counter-reset:lc-line}.lc-ts-line{padding-left:26px;display:block;position:relative}.lc-ts-line:before{content:counter(lc-line);counter-increment:lc-line;text-align:right;width:20px;color:var(--dsw-alias-label-tertiary);user-select:none;padding-right:6px;font-size:11px;position:absolute;left:0}.lc-ts-line:empty:after{content:\"​\"}.lc-ts-param-row{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:minmax(0,140px) minmax(0,90px) 56px minmax(0,1fr);align-items:baseline;gap:2px 10px;padding:6px 10px;font-size:12px;display:grid}.lc-ts-param-row:first-of-type{border-top:0}.lc-ts-param-name{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.lc-ts-param-type{color:var(--dsw-alias-label-secondary);font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.lc-ts-param-req{color:var(--dsw-alias-state-warn-primary);font-size:11px;font-weight:600}.lc-ts-param-req-off{color:var(--dsw-alias-label-secondary);font-size:11px}.lc-ts-param-desc{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;grid-column:1/-1;line-height:1.5}.lc-ts-params-empty{color:var(--dsw-alias-label-secondary);padding:8px 10px;font-size:12px}.lc-ts-arg-row{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:minmax(0,96px) 1fr;align-items:start;column-gap:12px;padding:6px 10px;font-size:12px;display:grid}.lc-ts-arg-row:first-of-type{border-top:0}.lc-ts-arg-row .lc-ts-param-name{text-overflow:unset;white-space:normal;overflow-wrap:anywhere;overflow:visible}.lc-ts-arg-val{color:var(--dsw-alias-label-primary);font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);white-space:pre-wrap;word-break:break-word;scrollbar-width:thin;max-height:200px;font-size:12px;line-height:1.55;overflow-y:auto}.lc-ts-json{flex-direction:column;gap:4px;display:flex}.lc-ts-json-toggle{color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;align-self:flex-start;padding:0;font-size:11px}.lc-ts-json-toggle:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}.lc-rich-seg{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-rich-seg-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-rich-seg-btn:hover{color:var(--dsw-alias-label-primary)}.lc-rich-seg-on,.lc-rich-seg-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-ts-desc-md{color:var(--dsw-alias-label-primary);word-break:break-word;overflow-wrap:anywhere;scrollbar-width:thin;min-width:0;max-height:320px;padding:4px 10px;font-size:12px;line-height:1.55;overflow:hidden auto}.lc-ts-desc-md>div{font-size:13px;line-height:1.55}.lc-ts-desc-md div :is(h1,h2,h3,h4,h5,h6){margin:10px 0 4px;font-size:13px;font-weight:600}.lc-ts-desc-md div p{white-space:pre-line;margin:6px 0}.lc-ts-desc-md div p br{display:none}.lc-ts-desc-md div pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;margin:6px 0}.lc-ts-desc-md div :not(pre)>code{box-decoration-break:clone;padding:1px 5px;line-height:1.3;font-size:1em!important}.lc-ts-desc-md div table code{font-size:inherit}";
8984
+ const css$2 = ".lc-ts-card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;min-width:0;overflow:hidden}.lc-ts-card-head{color:var(--dsw-alias-label-secondary);border-bottom:1px solid var(--dsw-alias-border-l1);align-items:center;gap:8px;padding:6px 10px;font-size:11px;font-weight:600;display:flex}.lc-ts-card-head-wrap{flex-wrap:wrap}.lc-ts-card-head b{color:var(--dsw-alias-label-primary)}.lc-ts-call-name{font-size:13px;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.lc-ts-card-count{margin-left:auto}.lc-ts-card-right{align-items:center;gap:8px;margin-left:auto;display:inline-flex}.lc-ts-card-meta{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:11px;font-weight:400}.lc-ts-call-state{white-space:nowrap;font-size:11px;font-weight:600;line-height:1;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);border-radius:999px;flex:none;align-items:center;gap:4px;padding:2px 7px;display:inline-flex}.lc-ts-call-state i{background:currentColor;border-radius:50%;flex:none;width:6px;height:6px}.lc-ts-call-ok{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent)}.lc-ts-call-err{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent)}.lc-ts-desc-body{color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;scrollbar-width:thin;max-height:320px;margin:0;padding:8px 10px;font-size:12px;line-height:1.55;overflow-y:auto}.lc-ts-lines{counter-reset:lc-line}.lc-ts-line{padding-left:26px;display:block;position:relative}.lc-ts-line:before{content:counter(lc-line);counter-increment:lc-line;text-align:right;width:20px;color:var(--dsw-alias-label-tertiary);user-select:none;padding-right:6px;font-size:11px;position:absolute;left:0}.lc-ts-line:empty:after{content:\"​\"}.lc-ts-param-row{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:minmax(0,140px) minmax(0,90px) 56px minmax(0,1fr);align-items:baseline;gap:2px 10px;padding:6px 10px;font-size:12px;display:grid}.lc-ts-param-row:first-of-type{border-top:0}.lc-ts-param-name{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.lc-ts-param-type{color:var(--dsw-alias-label-secondary);font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.lc-ts-param-req{color:var(--dsw-alias-state-warn-primary);font-size:11px;font-weight:600}.lc-ts-param-req-off{color:var(--dsw-alias-label-secondary);font-size:11px}.lc-ts-param-desc{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;grid-column:1/-1;line-height:1.5}.lc-ts-params-empty{color:var(--dsw-alias-label-secondary);padding:8px 10px;font-size:12px}.lc-ts-arg-row{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:minmax(0,96px) 1fr;align-items:start;column-gap:12px;padding:6px 10px;font-size:12px;display:grid}.lc-ts-arg-row:first-of-type{border-top:0}.lc-ts-arg-row .lc-ts-param-name{text-overflow:unset;white-space:normal;overflow-wrap:anywhere;overflow:visible}.lc-ts-arg-val{color:var(--dsw-alias-label-primary);font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);white-space:pre-wrap;word-break:break-word;scrollbar-width:thin;max-height:200px;font-size:12px;line-height:1.55;overflow-y:auto}.lc-ts-json{flex-direction:column;gap:4px;display:flex}.lc-ts-json-toggle{color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;align-self:flex-start;padding:0;font-size:11px}.lc-ts-json-toggle:hover{color:var(--dsw-alias-label-primary);text-decoration:underline}.lc-rich-seg{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-rich-seg-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-rich-seg-btn:hover{color:var(--dsw-alias-label-primary)}.lc-rich-seg-on,.lc-rich-seg-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-rich-copy{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;align-items:center;padding:2px;display:inline-flex}.lc-rich-copy:hover{color:var(--dsw-alias-label-primary)}.lc-rich-copy-on,.lc-rich-copy-on:hover{color:var(--dsw-alias-state-success-primary)}.lc-ts-desc-md{color:var(--dsw-alias-label-primary);word-break:break-word;overflow-wrap:anywhere;scrollbar-width:thin;min-width:0;max-height:320px;padding:4px 10px;font-size:12px;line-height:1.55;overflow:hidden auto}.lc-ts-desc-md>div{font-size:13px;line-height:1.55}.lc-ts-desc-md div :is(h1,h2,h3,h4,h5,h6){margin:10px 0 4px;font-size:13px;font-weight:600}.lc-ts-desc-md div p{white-space:pre-line;margin:6px 0}.lc-ts-desc-md div p br{display:none}.lc-ts-desc-md div pre{white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;margin:6px 0}.lc-ts-desc-md div :not(pre)>code{box-decoration-break:clone;padding:1px 5px;line-height:1.3;font-size:1em!important}.lc-ts-desc-md div table code{font-size:inherit}";
8908
8985
  const tagId$2 = "dsh-context/detailSections.css";
8909
8986
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
8910
8987
  const tag = document.createElement("style");
package/lib/index.d.ts CHANGED
@@ -56,6 +56,15 @@ interface HeadersState {
56
56
  headers: StoredHeaderRecord[];
57
57
  }
58
58
  //#endregion
59
+ //#region src/host/logShapes.d.ts
60
+ /**
61
+ * The decode bucket a stream's `block-start.blockType` names: the model's
62
+ * thinking, the answer text, or the tool-call arguments. Undefined for an
63
+ * unknown/hostile marker, whose interval then stays unattributed rather than
64
+ * poisoning a bucket.
65
+ */
66
+ type DecodeKind = 'reasoning' | 'text' | 'toolarg';
67
+ //#endregion
59
68
  //#region src/host/fold.d.ts
60
69
  /**
61
70
  * History retention bounds (configurable since 0.11 — see config.ts; these
@@ -120,10 +129,10 @@ interface TimelineState {
120
129
  archived: SurfaceNode[];
121
130
  /**
122
131
  * Session-cost raw material: cumulative billed-token totals per DeepSeek
123
- * V4 model family and pricing period (see SessionCostUsage). Running
132
+ * model family and pricing period (see SessionCostUsage). Running
124
133
  * totals — never trimmed, so the estimate always covers the COMPLETE
125
134
  * session log even after the request/event retention bounds cut in.
126
- * Absent until a v4-flash / v4-pro request reports usage.
135
+ * Absent until a DeepSeek flash/pro request reports usage.
127
136
  */
128
137
  cost?: SessionCostUsage;
129
138
  archiveFloor?: number;
@@ -153,10 +162,21 @@ interface TimelineState {
153
162
  * one those events close — a hostile interleaved log degrades to skipped
154
163
  * durations, never to unbounded state. Same arm/remove lifecycle as
155
164
  * `pendingShadowedSeqs`.
165
+ *
166
+ * `decode` and `block` carry the generation split (reasoning / answer text /
167
+ * tool arguments — see TimingTotals): a V0 log's `assistant/chunk`
168
+ * `block-start` markers open `block` and close the previous one into
169
+ * `decode`; a V2+ log carries no such events, so `decode` stays absent and
170
+ * `assistant/message` reads the spans off its embedded stream instead.
156
171
  */
157
172
  stepStart?: {
158
173
  time: number;
159
174
  firstToken?: number;
175
+ decode?: Record<DecodeKind, number>;
176
+ block?: {
177
+ kind: DecodeKind;
178
+ since: number;
179
+ };
160
180
  };
161
181
  /**
162
182
  * Tool callId → the call's name, start instant, and raw arguments, armed by
@@ -503,6 +523,18 @@ interface ToolTimingTotals {
503
523
  * both only over calls whose stream carried a token delta, `toolsMs` the sum
504
524
  * of per-call tool durations (parallel calls each count, so it can overlap).
505
525
  * Absent until the first step lifecycle completes in the log.
526
+ *
527
+ * The generation window itself splits by WHAT was being decoded, off the
528
+ * stream's `block-start` framing (`blockType`): `reasoningMs` (the model's
529
+ * thinking), `textMs` (the answer text), and `toolArgMs` (the tool-call
530
+ * arguments). Each marker owns the interval up to the next one (the last one
531
+ * up to the assistant message), so the three tile the marker span and together
532
+ * account for essentially all of `genMs` — the span opens at the first marker,
533
+ * which can sit marginally before the first token, so it is not an exact
534
+ * partition. They are ADDITIVE-OPTIONAL: cached projection rows written before
535
+ * the split carry `genMs` without them, so the card falls back to the
536
+ * un-split shape instead of the cache row being discarded (the
537
+ * stateVersion-15 rationale in host/timeline.ts).
506
538
  */
507
539
  interface TimingTotals {
508
540
  /** Summed wall time of completed steps (the session's active time). */
@@ -511,6 +543,12 @@ interface TimingTotals {
511
543
  ttftMs: number;
512
544
  /** Summed first-token → assistant-message time (the generation). */
513
545
  genMs: number;
546
+ /** Reasoning-decode slice of `genMs` (the model's thinking). */
547
+ reasoningMs?: number;
548
+ /** Answer-text decode slice of `genMs`. */
549
+ textMs?: number;
550
+ /** Tool-call-argument decode slice of `genMs`. */
551
+ toolArgMs?: number;
514
552
  /** Completed model calls (assistant messages folded). */
515
553
  calls: number;
516
554
  /** Summed per-call durations of completed tool calls. */
@@ -527,10 +565,11 @@ interface CostFamilyUsage {
527
565
  }
528
566
  /**
529
567
  * The session-cost estimate's raw material: cumulative provider-reported
530
- * token totals per DeepSeek V4 model family (matched on the model NAME,
568
+ * token totals per DeepSeek model family (matched on the model NAME,
531
569
  * provider-agnostic) and pricing period. The Client prices these with its
532
570
  * hardcoded list-price table in the locale's currency. Absent until a
533
- * deepseek-v4-flash / deepseek-v4-pro request reports usage.
571
+ * deepseek-flash / deepseek-v4.1-flash / deepseek-v4-pro request reports
572
+ * usage.
534
573
  */
535
574
  interface SessionCostUsage {
536
575
  flash?: CostFamilyUsage;
package/lib/index.js CHANGED
@@ -596,7 +596,7 @@ function isInjection(source) {
596
596
  *
597
597
  * - V0 (dsh 0.1.2-rc.1): `request/header.header.system`, `assistant/chunk`
598
598
  * stream events, `SurfaceOp { start, end }`, `tool/code-dispatch`.
599
- * - V3 (dsh 0.1.5-alpha.1+): `system/message` surface nodes,
599
+ * - V3 (dsh 0.1.5-alpha.x+): `system/message` surface nodes,
600
600
  * `assistant/message.data.stream` / `assistant/attempt.data.stream`,
601
601
  * `SurfaceOp { startSeq, endSeq }`, `tool/ptc-dispatch`.
602
602
  *
@@ -627,6 +627,45 @@ function isTokenChunk(chunk) {
627
627
  default: return false;
628
628
  }
629
629
  }
630
+ /** Map one `blockType` to its timing bucket (see {@link DecodeKind}). */
631
+ function decodeKindOfBlock(blockType) {
632
+ if (blockType === "reasoning") return "reasoning";
633
+ if (blockType === "text") return "text";
634
+ if (blockType === "tool-call") return "toolarg";
635
+ }
636
+ /**
637
+ * Per-kind decode spans inside one embedded assistant stream, tiling
638
+ * [first block-start, endTime]: each `block-start` record owns the interval up
639
+ * to the next one, the last one up to `endTime`. This is the V2+ shape, whose
640
+ * timed stream rides the settlement (`assistant/message.data.stream`) instead
641
+ * of separate `assistant/chunk` events. Total over untrusted input — a
642
+ * malformed record is skipped, a non-finite boundary yields zero, and a stream
643
+ * with no marker (or not an array) yields all zeros.
644
+ */
645
+ function decodeSpansOfStream(stream, endTime) {
646
+ const spans = {
647
+ reasoning: 0,
648
+ text: 0,
649
+ toolarg: 0
650
+ };
651
+ if (!Array.isArray(stream) || !Number.isFinite(endTime)) return spans;
652
+ let kind;
653
+ let since = 0;
654
+ for (const record of stream) {
655
+ if (record === null || typeof record !== "object") continue;
656
+ const r = record;
657
+ if (r.type !== "chunk" || r.chunk === null || typeof r.chunk !== "object") continue;
658
+ const chunk = r.chunk;
659
+ if (chunk.type !== "block-start") continue;
660
+ const time = r.time;
661
+ if (typeof time !== "number" || !Number.isFinite(time)) continue;
662
+ if (kind !== void 0) spans[kind] += Math.max(0, time - since);
663
+ kind = decodeKindOfBlock(chunk.blockType);
664
+ since = time;
665
+ }
666
+ if (kind !== void 0) spans[kind] += Math.max(0, endTime - since);
667
+ return spans;
668
+ }
630
669
  /**
631
670
  * The first token's instant inside one PACKED delta run (`text-chunks` /
632
671
  * `reasoning-chunks` / `tool-call-chunks`): the run's base time plus the
@@ -1235,15 +1274,16 @@ function tokenCountOf(value) {
1235
1274
  return null;
1236
1275
  }
1237
1276
  /**
1238
- * The DeepSeek V4 model family a model name prices as — matched on the NAME
1277
+ * The DeepSeek model family a model name prices as — matched on the NAME
1239
1278
  * alone (provider-agnostic: official API, proxies, OpenRouter spellings like
1240
- * `deepseek/deepseek-v4-flash` all land here). Null for any other model:
1241
- * non-V4 usage is simply not priced.
1279
+ * `deepseek/deepseek-v4.1-flash` and `deepseek/deepseek-flash` all land
1280
+ * here). The name must carry a DeepSeek marker (`v4` or `deepseek`) so a
1281
+ * foreign flash/pro-named model (gemini-2.0-flash) is never priced.
1242
1282
  */
1243
1283
  function costFamilyOf(model) {
1244
1284
  if (model === void 0) return null;
1245
1285
  const m = model.toLowerCase();
1246
- if (!m.includes("v4")) return null;
1286
+ if (!m.includes("v4") && !m.includes("deepseek")) return null;
1247
1287
  if (m.includes("flash")) return "flash";
1248
1288
  if (m.includes("pro")) return "pro";
1249
1289
  return null;
@@ -1300,6 +1340,12 @@ function accumulateCost(st, time, usage) {
1300
1340
  */
1301
1341
  /** The timing card's per-tool ranking cap: the busiest 16 names are kept. */
1302
1342
  const TOOL_TIMING_CAP = 16;
1343
+ /** The decode buckets of the generation split, in card order (see TimingTotals). */
1344
+ const DECODE_KINDS = [
1345
+ "reasoning",
1346
+ "text",
1347
+ "toolarg"
1348
+ ];
1303
1349
  /** Non-negative, NaN-proof duration between two instants (hostile times degrade to 0). */
1304
1350
  function durOf(from, to) {
1305
1351
  if (!Number.isFinite(from) || !Number.isFinite(to)) return 0;
@@ -1323,6 +1369,18 @@ function ensureTiming(st) {
1323
1369
  return st.timing;
1324
1370
  }
1325
1371
  /**
1372
+ * Fold one block's decode span into the totals' generation split (see
1373
+ * TimingTotals). A zero span stays ABSENT — the field then carries the
1374
+ * "no time was decoded in this bucket" fact without adding dead properties to
1375
+ * every pre-split-shaped state, and the card reads absence as 0.
1376
+ */
1377
+ function addDecode(timing, kind, ms) {
1378
+ if (!(ms > 0)) return;
1379
+ if (kind === "reasoning") timing.reasoningMs = (timing.reasoningMs ?? 0) + ms;
1380
+ else if (kind === "text") timing.textMs = (timing.textMs ?? 0) + ms;
1381
+ else timing.toolArgMs = (timing.toolArgMs ?? 0) + ms;
1382
+ }
1383
+ /**
1326
1384
  * Tally one completed tool call into the per-name ranking, bounded to
1327
1385
  * TOOL_TIMING_CAP names: repeated names update in place, a new name beyond
1328
1386
  * the cap evicts the smallest tally first (the ranking's tail), so state
@@ -1462,11 +1520,34 @@ function applyTimeline(state, event, bounds) {
1462
1520
  }
1463
1521
  case "assistant/chunk": {
1464
1522
  const start = state.stepStart;
1465
- if (start === void 0 || start.firstToken !== void 0) return state;
1523
+ if (start === void 0) return state;
1524
+ const chunk = data?.chunk;
1525
+ if (chunk !== null && typeof chunk === "object" && chunk.type === "block-start") {
1526
+ const kind = decodeKindOfBlock(chunk.blockType);
1527
+ if (start.block === void 0 && kind === void 0) return state;
1528
+ const s = ensure();
1529
+ const decode = { ...start.decode ?? {
1530
+ reasoning: 0,
1531
+ text: 0,
1532
+ toolarg: 0
1533
+ } };
1534
+ if (start.block !== void 0) decode[start.block.kind] += durOf(start.block.since, event.time);
1535
+ s.stepStart = {
1536
+ time: start.time,
1537
+ ...start.firstToken !== void 0 ? { firstToken: start.firstToken } : {},
1538
+ decode,
1539
+ ...kind !== void 0 ? { block: {
1540
+ kind,
1541
+ since: event.time
1542
+ } } : {}
1543
+ };
1544
+ break;
1545
+ }
1546
+ if (start.firstToken !== void 0) return state;
1466
1547
  if (!isTokenChunk(data?.chunk)) return state;
1467
1548
  const s = ensure();
1468
1549
  s.stepStart = {
1469
- time: start.time,
1550
+ ...start,
1470
1551
  firstToken: event.time
1471
1552
  };
1472
1553
  break;
@@ -1613,6 +1694,14 @@ function applyTimeline(state, event, bounds) {
1613
1694
  if (firstToken !== void 0) {
1614
1695
  timing.ttftMs += durOf(stepStart.time, firstToken);
1615
1696
  timing.genMs += durOf(firstToken, event.time);
1697
+ if (stepStart.decode !== void 0) {
1698
+ const decode = { ...stepStart.decode };
1699
+ if (stepStart.block !== void 0) decode[stepStart.block.kind] += durOf(stepStart.block.since, event.time);
1700
+ for (const kind of DECODE_KINDS) addDecode(timing, kind, decode[kind]);
1701
+ } else {
1702
+ const spans = decodeSpansOfStream(data?.stream, event.time);
1703
+ for (const kind of DECODE_KINDS) addDecode(timing, kind, spans[kind]);
1704
+ }
1616
1705
  }
1617
1706
  }
1618
1707
  const asstMsg = deriveEventMessage(event);
@@ -2254,6 +2343,9 @@ const timingTotalsSchema = z.object({
2254
2343
  wallMs: z.number().nonnegative(),
2255
2344
  ttftMs: z.number().nonnegative(),
2256
2345
  genMs: z.number().nonnegative(),
2346
+ reasoningMs: z.number().nonnegative().optional(),
2347
+ textMs: z.number().nonnegative().optional(),
2348
+ toolArgMs: z.number().nonnegative().optional(),
2257
2349
  calls: z.number().int().nonnegative(),
2258
2350
  toolsMs: z.number().nonnegative(),
2259
2351
  toolCalls: z.number().int().nonnegative(),
@@ -2347,7 +2439,20 @@ const timelineStateSchema = z.object({
2347
2439
  timing: timingTotalsSchema.optional(),
2348
2440
  stepStart: z.object({
2349
2441
  time: z.number(),
2350
- firstToken: z.number().optional()
2442
+ firstToken: z.number().optional(),
2443
+ decode: z.object({
2444
+ reasoning: z.number(),
2445
+ text: z.number(),
2446
+ toolarg: z.number()
2447
+ }).strict().optional(),
2448
+ block: z.object({
2449
+ kind: z.enum([
2450
+ "reasoning",
2451
+ "text",
2452
+ "toolarg"
2453
+ ]),
2454
+ since: z.number()
2455
+ }).strict().optional()
2351
2456
  }).strict().optional(),
2352
2457
  callNames: z.record(z.string(), z.object({
2353
2458
  name: z.string(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.47.0",
3
+ "version": "0.49.0",
4
4
  "description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
5
5
  "author": "bowenliang123",
6
6
  "repository": {
@@ -66,7 +66,7 @@
66
66
  "dshReleases": {
67
67
  "0.1.2-rc.1": "compatible",
68
68
  "0.1.3-alpha.2": "compatible",
69
- "0.1.5-alpha.1": "compatible"
69
+ "0.1.5-rc.1": "compatible"
70
70
  }
71
71
  }
72
72
  },