dsh-context 0.49.4 → 0.50.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
@@ -47,7 +47,7 @@ Open any session and click the **Context / 上下文** tab:
47
47
 
48
48
  | Card | The question it answers |
49
49
  | --- | --- |
50
- | **Context Stats** | Turns, steps, live tool calls & images, context-event tallies — plus a list-price cost estimate (hover the `?` for per-1M rates). |
50
+ | **Context Stats** | Turns, steps, live tool calls, the session's cache-hit rate — plus a list-price cost estimate (hover the `?` for per-1M rates). |
51
51
  | **Token Stats** | Where the billed tokens went: cache read/write, uncached input, output — around the cache-hit ring. |
52
52
  | **Timing Stats** | How active time split across model calls, tool runs, and overhead. |
53
53
  | **Current Context** | What's in the window *right now*. |
@@ -102,7 +102,7 @@ Pick **Live (next request)** or any retained step, and browse what that request
102
102
 
103
103
  ![Context Events with a compaction](https://raw.githubusercontent.com/bowenliang123/dsh-context/main/docs/context-events.png)
104
104
 
105
- Every injection, compaction, prune, model switch, and plan-mode toggle — labeled with its producer (instruction file, plugin id, skill name), its net token delta (compactions show what they reclaimed), turn/step, and time. The **Inject / Compact / Prune / Switch / Mode** chips filter the log by kind.
105
+ Every injection, compaction, prune, model switch, and plan-mode toggle — labeled with its producer (instruction file, plugin id, skill name), its net token delta (compactions show what they reclaimed), turn/step, and time. The **Inject / Compact / Prune / Switch / Mode** chips filter the log by kind, each carrying its whole-session event tally.
106
106
 
107
107
  ### 📁 File Activity — what the agent did to your files
108
108
 
@@ -115,6 +115,7 @@ One row per touched file — read, written, or searched — aggregated up to whi
115
115
  - **Every mode counts** — native tools, the Minimal preset's `str_replace_editor`, and the nested calls inside PTC `run_code` programs are all folded into per-tool rows.
116
116
  - **Searches land on real files** — matched files get their own ops rows with hit counts.
117
117
  - **Click a row** to expand its full operation log — every op jumps straight to the exact tool result in the Context browser.
118
+ - **Click a file name** to open its preview in the right Sidebar (dsh 0.1.5-rc.1+), exactly as the built-in Files sidebar does — the same viewer, the same tab-per-file behavior. On a harness without that column the name opens on your system as before.
118
119
 
119
120
  ### 🕸 Agent Network — the family portrait
120
121
 
package/lib/client.js CHANGED
@@ -26,13 +26,12 @@ window.__ModuleLoader__.load({
26
26
  "stats.title": "上下文统计",
27
27
  "stats.turns": "轮次",
28
28
  "stats.steps": "步数",
29
- "stats.injects": "注入",
30
- "stats.compactions": "压缩",
31
- "stats.prunes": "剪枝",
29
+ "stats.humanInputs": "用户输入",
30
+ "stats.humanInputsTip": "当前会话中的用户消息与问题回答次数。",
32
31
  "stats.toolCalls": "工具调用",
33
- "stats.images": "图片",
32
+ "stats.cacheHit": "缓存命中",
34
33
  "stats.cost": "预估费用",
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 单价计费),价格写死在代码中,仅供参考。",
34
+ "stats.costTip": "按 DeepSeek 官方刊例价估算整个会话的累计费用:输入区分缓存命中/未命中,输出含思考;按请求时间区分高峰(北京时间工作日 9:00–12:00、14:00–18:00)与空闲时段(半价,周末全天均为空闲时段)。适用于 deepseek-flash / deepseek-v4-pro(旧 ID deepseek-v4-flash 等按 Flash 价格计费;按模型名匹配,不限 provider),价格仅供参考。",
36
35
  "stats.costPriceHead": "每百万 tokens 价格(高峰 | 空闲半价):",
37
36
  "stats.costHit": "命中",
38
37
  "stats.costMiss": "未命中",
@@ -42,7 +41,7 @@ window.__ModuleLoader__.load({
42
41
  "timing.ttft": "模型等待",
43
42
  "timing.gen": "模型生成",
44
43
  "timing.reasoning": "模型思考",
45
- "timing.text": "正文输出",
44
+ "timing.text": "模型输出",
46
45
  "timing.toolArgs": "工具参数",
47
46
  "timing.tools": "工具执行",
48
47
  "timing.other": "其他开销",
@@ -50,10 +49,7 @@ window.__ModuleLoader__.load({
50
49
  "timing.toolTimes": "{n}次",
51
50
  "timing.empty": "暂无耗时数据 · 对话开始后这里会显示时长分布",
52
51
  "tokens.title": "Token 统计",
53
- "tokens.cacheHit": "缓存命中",
54
- "tokens.cacheRead": "缓存输入",
55
- "tokens.cacheWrite": "缓存写入",
56
- "tokens.uncached": "未缓存输入",
52
+ "tokens.total": "总用量",
57
53
  "tokens.output": "输出",
58
54
  "tokens.outputNote": "含思考",
59
55
  "plugin.title": "插件信息",
@@ -132,6 +128,7 @@ window.__ModuleLoader__.load({
132
128
  "files.hits": "{n} 处命中",
133
129
  "files.readTip": "读取第 {a}–{b} 行",
134
130
  "files.open": "在系统中打开",
131
+ "files.preview": "在侧边栏预览",
135
132
  "files.readEst": "按调用参数 limit 估算的读取行数",
136
133
  "files.errs": "{n} 次失败",
137
134
  "files.form.image": "图片",
@@ -321,13 +318,12 @@ window.__ModuleLoader__.load({
321
318
  "stats.title": "Context Stats",
322
319
  "stats.turns": "Turns",
323
320
  "stats.steps": "Steps",
324
- "stats.injects": "Injections",
325
- "stats.compactions": "Compactions",
326
- "stats.prunes": "Prunes",
321
+ "stats.humanInputs": "Human Inputs",
322
+ "stats.humanInputsTip": "User messages and question answerings in current session.",
327
323
  "stats.toolCalls": "Tool Calls",
328
- "stats.images": "Images",
324
+ "stats.cacheHit": "Cache Hit",
329
325
  "stats.cost": "Cost",
330
- "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.",
326
+ "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-flash / deepseek-v4-pro (legacy IDs such as deepseek-v4-flash bill at the Flash rates; matched by model name, any provider); prices are for reference only.",
331
327
  "stats.costPriceHead": "Per-1M-token rates (peak | off-peak at half price):",
332
328
  "stats.costHit": "hit",
333
329
  "stats.costMiss": "miss",
@@ -345,10 +341,7 @@ window.__ModuleLoader__.load({
345
341
  "timing.toolTimes": "{n} runs",
346
342
  "timing.empty": "No timing data yet — durations appear as the conversation runs",
347
343
  "tokens.title": "Token Stats",
348
- "tokens.cacheHit": "Cache Hit",
349
- "tokens.cacheRead": "Cached Input",
350
- "tokens.cacheWrite": "Cache Write",
351
- "tokens.uncached": "Uncached Input",
344
+ "tokens.total": "Total",
352
345
  "tokens.output": "Output",
353
346
  "tokens.outputNote": "incl. reasoning",
354
347
  "plugin.title": "Plugin Info",
@@ -427,6 +420,7 @@ window.__ModuleLoader__.load({
427
420
  "files.hits": "{n} hits",
428
421
  "files.readTip": "Read lines {a}–{b}",
429
422
  "files.open": "Open on your system",
423
+ "files.preview": "Preview in the sidebar",
430
424
  "files.readEst": "Lines read, estimated from the limit argument",
431
425
  "files.errs": "{n} failed",
432
426
  "files.form.image": "Image",
@@ -819,6 +813,30 @@ window.__ModuleLoader__.load({
819
813
  value: Math.round(p.raw * scale)
820
814
  }));
821
815
  }
816
+ /**
817
+ * The Token card's billed split, by WHAT the tokens are rather than by how
818
+ * the provider cached them: the six composition categories share the
819
+ * provider-reported prompt-side total (uncached + cache read + cache write —
820
+ * the chat stats line's billed input) by the composition card's own
821
+ * estimated ratios, and the provider's exact output count closes the ring as
822
+ * the seventh part. Only the per-category split is estimated — every
823
+ * category's sum and the output figure are provider-reported, so the parts
824
+ * total equals the chat line's whole-session token count by construction. A
825
+ * zero/negative prompt total (or a hostile negative output) never invents a
826
+ * split: the prompt parts zero out / the output clamps at 0.
827
+ */
828
+ function billedParts(current, breakdown, usage) {
829
+ const input = usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
830
+ const estimated = officialParts(current, breakdown);
831
+ return [...input > 0 ? anchoredParts(estimated, input) : estimated.map((p) => ({
832
+ ...p,
833
+ value: 0
834
+ })), {
835
+ key: "output",
836
+ color: "#ec4899",
837
+ value: Math.max(0, usage.outputTokens)
838
+ }];
839
+ }
822
840
  //#endregion
823
841
  //#region src/client/headline.ts
824
842
  function headlineOf(data, pressure = null, breakdown = null) {
@@ -1000,6 +1018,7 @@ window.__ModuleLoader__.load({
1000
1018
  droppedNodes: numOf(data.droppedNodes),
1001
1019
  ...typeof data.images === "number" ? { images: data.images } : {},
1002
1020
  ...typeof data.toolCalls === "number" ? { toolCalls: data.toolCalls } : {},
1021
+ ...typeof data.humanInputs === "number" ? { humanInputs: data.humanInputs } : {},
1003
1022
  archive: objectsOf(data.archive),
1004
1023
  ...counts !== void 0 ? { counts } : {},
1005
1024
  ...last !== void 0 ? { last } : {},
@@ -1355,6 +1374,37 @@ window.__ModuleLoader__.load({
1355
1374
  } catch {}
1356
1375
  };
1357
1376
  }
1377
+ /**
1378
+ * The right Sidebar's resource opener over `ctx.sidebarRight`, or undefined
1379
+ * when this harness serves no such column (every line older than 0.1.5-rc.1) or
1380
+ * the face is hostile — the caller then keeps its system-open degradation.
1381
+ * Synchronous, and it reports whether the column took the address: `openResource`
1382
+ * throws for a no-type-claims address or with no session surface mounted, and an
1383
+ * unwired preview must fall back rather than become an inert click. The face is
1384
+ * re-proved at call time (the service can land or be revoked across an HMR
1385
+ * reload), so the returned closure reads it per open.
1386
+ */
1387
+ function openResourceVia(ctx) {
1388
+ const faceOf = () => {
1389
+ try {
1390
+ const face = asRecord(ctx.get("sidebarRight"));
1391
+ return face !== null && typeof face.openResource === "function" ? face : void 0;
1392
+ } catch {
1393
+ return;
1394
+ }
1395
+ };
1396
+ if (faceOf() === void 0) return void 0;
1397
+ return (address) => {
1398
+ const face = faceOf();
1399
+ if (face === void 0) return false;
1400
+ try {
1401
+ face.openResource(address);
1402
+ return true;
1403
+ } catch {
1404
+ return false;
1405
+ }
1406
+ };
1407
+ }
1358
1408
  //#endregion
1359
1409
  //#region src/client/historyPage.ts
1360
1410
  /**
@@ -2463,15 +2513,17 @@ window.__ModuleLoader__.load({
2463
2513
  }
2464
2514
  /**
2465
2515
  * Cache-hit share of billed prompt-side input (`reads` over `billed`),
2466
- * TRUNCATED to two decimals (cut, not round) — same formula as the harness
2467
- * chat stats line's '缓存命中' figure and the stats board's cell. Null when
2468
- * nothing was billed. The 1e-9 epsilon absorbs only float noise (integer
2469
- * token counts never sit that close to a boundary).
2516
+ * TRUNCATED to `decimals` places (cut, not round) — same formula as the
2517
+ * harness chat stats line's '缓存命中' figure and the stats board's cell
2518
+ * (which shows one decimal). Null when nothing was billed. The 1e-9 epsilon
2519
+ * absorbs only float noise (integer token counts never sit that close to a
2520
+ * boundary).
2470
2521
  */
2471
- function cacheHitPercent(reads, billed) {
2522
+ function cacheHitPercent(reads, billed, decimals = 2) {
2472
2523
  if (!(billed > 0)) return null;
2473
- const hundredths = Math.trunc(reads / billed * 1e4 + 1e-9);
2474
- return `${Math.floor(hundredths / 100)}.${String(hundredths % 100).padStart(2, "0")}`;
2524
+ const factor = 10 ** decimals;
2525
+ const scaled = Math.trunc(reads / billed * 100 * factor + 1e-9);
2526
+ return `${Math.floor(scaled / factor)}.${String(scaled % factor).padStart(decimals, "0")}`;
2475
2527
  }
2476
2528
  function fmtTime(t) {
2477
2529
  const d = new Date(t);
@@ -3892,7 +3944,9 @@ window.__ModuleLoader__.load({
3892
3944
  //#region src/client/components/stackedBar.tsx
3893
3945
  /**
3894
3946
  * Composition bar + legend; the shared hover-link tooltip is bespoke — no shared primitive reproduces the cross-segment/legend linkage —
3895
- * styled through the shared `--dsw-alias-*` tokens.
3947
+ * styled through the shared `--dsw-alias-*` tokens. On mount the segments and the free track grow open
3948
+ * left to right (stackedBar.css, staggered by the capped per-piece `--lc-i` slots below, so a long DNA
3949
+ * band list settles fast); the reserve band stays unanimated.
3896
3950
  */
3897
3951
  /**
3898
3952
  * Mirror of dsh-compaction-basic's default `thresholdRatio` (0.8): it compacts at step boundaries once `floor(contextWindow × ratio)` is
@@ -3900,6 +3954,12 @@ window.__ModuleLoader__.load({
3900
3954
  * `thresholdRatio`/`modelPolicies` should adjust it to match.
3901
3955
  */
3902
3956
  const AUTO_COMPACT_RATIO = .8;
3957
+ /**
3958
+ * Entrance stagger cap: the browser's DNA bands can list dozens of items, so the grow-in cascade stops
3959
+ * widening after this many slots and late bands simply join within the cap (stackedBar.css delays by
3960
+ * `--lc-i`); the composition bar's few category segments never reach it.
3961
+ */
3962
+ const STAGGER_CAP = 8;
3903
3963
  function makeStackedBar(kit) {
3904
3964
  const { t, fmt, catLabel } = kit;
3905
3965
  return function StackedBar(props) {
@@ -3981,7 +4041,8 @@ window.__ModuleLoader__.load({
3981
4041
  className: "lc-stacked-seg" + (on ? " lc-stacked-seg-on" : "") + (pickKey !== void 0 ? " lc-stacked-seg-pick" : ""),
3982
4042
  style: {
3983
4043
  width: `${widths[i]}%`,
3984
- backgroundColor: p.color
4044
+ backgroundColor: p.color,
4045
+ "--lc-i": Math.min(i, STAGGER_CAP)
3985
4046
  },
3986
4047
  onMouseEnter: () => {
3987
4048
  if (props.onHoverKey !== void 0) props.onHoverKey(p.key);
@@ -3993,7 +4054,10 @@ window.__ModuleLoader__.load({
3993
4054
  }),
3994
4055
  free > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3995
4056
  className: "lc-stacked-free" + (props.hoverKey === "free" ? " lc-stacked-free-on" : ""),
3996
- style: { width: `${free / scale * 100}%` },
4057
+ style: {
4058
+ width: `${free / scale * 100}%`,
4059
+ "--lc-i": Math.min(visible.length, 9)
4060
+ },
3997
4061
  onMouseEnter: () => {
3998
4062
  if (props.onHoverKey !== void 0) props.onHoverKey("free");
3999
4063
  }
@@ -4647,6 +4711,68 @@ window.__ModuleLoader__.load({
4647
4711
  };
4648
4712
  }
4649
4713
  //#endregion
4714
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
4715
+ /**
4716
+ * The `dsh-resource://file/…` address grammar: how a file is named across the
4717
+ * Sidebar and the resource model, built and parsed without touching a
4718
+ * filesystem.
4719
+ * @module
4720
+ */
4721
+ /** The scheme and type every file address opens with. */
4722
+ const FILE_ADDRESS_PREFIX = "dsh-resource://file/";
4723
+ /** Component-encode one id or path segment, keeping `:` literal for drive letters. */
4724
+ function encodeSegment(segment) {
4725
+ return encodeURIComponent(segment).replace(/%3A/gi, ":");
4726
+ }
4727
+ /** Encode a `/`-separated path segment by segment. */
4728
+ function encodePath(path) {
4729
+ return path.split("/").map(encodeSegment).join("/");
4730
+ }
4731
+ /**
4732
+ * Build the address of a file read through one Session.
4733
+ * @param sessionId - the Session whose Host workspace resolves the path.
4734
+ * @param path - absolute or workspace-relative path; backslashes are normalized to `/`, and leading `./` prefixes are dropped.
4735
+ * @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
4736
+ */
4737
+ function sessionFileAddress(sessionId, path) {
4738
+ const normalized = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
4739
+ return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(normalized)}`;
4740
+ }
4741
+ /**
4742
+ * Browser-safe Workspace path and display helpers.
4743
+ * @module @deepseek-ai/dsh-util-workspace-path
4744
+ */
4745
+ /** Whether a path uses a Windows drive or UNC prefix. */
4746
+ function isWindowsStylePath(value) {
4747
+ return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
4748
+ }
4749
+ /**
4750
+ * Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`) or Windows drive or UNC.
4751
+ * @param path - the path to classify.
4752
+ * @returns `true` for an absolute path; `false` for a Workspace-relative one.
4753
+ */
4754
+ function isAbsoluteWorkspacePath(path) {
4755
+ return path.startsWith("/") || isWindowsStylePath(path);
4756
+ }
4757
+ /**
4758
+ * The address for a path as a caller holds it: a relative path, or an absolute
4759
+ * path inside the Session's workspace, becomes a `session`-scoped address; an
4760
+ * absolute path outside it, or one whose workspace root is unknown, keeps its
4761
+ * absolute path in that Session's address.
4762
+ * @param sessionId - the Session the path is read in.
4763
+ * @param cwd - that Session's workspace root, when known.
4764
+ * @param path - absolute or workspace-relative path, in either separator spelling.
4765
+ * @returns the `dsh-resource://file/…` address.
4766
+ */
4767
+ function fileAddressFor(sessionId, cwd, path) {
4768
+ const normalized = path.replace(/\\/g, "/");
4769
+ if (!isAbsoluteWorkspacePath(normalized)) return sessionFileAddress(sessionId, normalized);
4770
+ const root = cwd === void 0 ? "" : cwd.replace(/\\/g, "/").replace(/\/+$/, "");
4771
+ if (root !== "" && normalized === root) return sessionFileAddress(sessionId, "");
4772
+ if (root !== "" && normalized.startsWith(`${root}/`)) return sessionFileAddress(sessionId, normalized.slice(root.length + 1));
4773
+ return sessionFileAddress(sessionId, normalized);
4774
+ }
4775
+ //#endregion
4650
4776
  //#region src/client/fileActivity.ts
4651
4777
  /** The file's form — multimodal reads and image extensions scan apart; a trailing slash marks a directory target. */
4652
4778
  function formOf(tool, path) {
@@ -5138,6 +5264,23 @@ window.__ModuleLoader__.load({
5138
5264
  return workspace.replace(/\/+$/, "") + "/" + path.replace(/^\/+/, "");
5139
5265
  }
5140
5266
  /**
5267
+ * The right-Sidebar preview address of a real file — the session-scoped
5268
+ * `dsh-resource://file/…` address the shipped preview type claims, built by
5269
+ * the harness's own `fileAddressFor` (the files sidebar's idiom: a path under
5270
+ * the workspace collapses to its session-relative spelling, so every route to
5271
+ * one file settles on one tab). Undefined for a pathless search's PATTERN, a
5272
+ * directory target, a missing session, and any path the encoder rejects — the
5273
+ * caller falls back to the system opener or renders the name inert.
5274
+ */
5275
+ function previewAddressOf(path, form, pattern, sessionId, workspace) {
5276
+ if (pattern === true || form === "dir" || sessionId === void 0 || sessionId === "") return void 0;
5277
+ try {
5278
+ return fileAddressFor(sessionId, workspace, path);
5279
+ } catch {
5280
+ return;
5281
+ }
5282
+ }
5283
+ /**
5141
5284
  * The row's display form of a path: inside the workspace (or already
5142
5285
  * workspace-relative) it shortens to a './'-prefixed relative — an absolute
5143
5286
  * path outside the workspace, a Windows drive path, and an already-'.'
@@ -6090,7 +6233,9 @@ window.__ModuleLoader__.load({
6090
6233
  * with no value are skipped; an all-zero ring renders as one neutral track so
6091
6234
  * the card never draws a misleading "100% of nothing" pie. The center type
6092
6235
  * scales with the ring's size, so the label always fits inside the hole the
6093
- * thin stroke leaves.
6236
+ * thin stroke leaves. On mount the slices sweep in one after another, growing
6237
+ * their dasharcs clockwise from 12 o'clock (stats.css, staggered by the
6238
+ * per-slice `--lc-i` slot below).
6094
6239
  */
6095
6240
  /**
6096
6241
  * The hairline divider between two slices, in dasharray units (1 unit ≈ 1% of
@@ -6143,7 +6288,7 @@ window.__ModuleLoader__.load({
6143
6288
  r: "15.9155",
6144
6289
  fill: "none",
6145
6290
  strokeWidth: "4"
6146
- }) : arcs.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
6291
+ }) : arcs.map((a, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
6147
6292
  className: "lc-donut-seg" + (props.hoverKey === a.key ? " lc-donut-seg-on" : ""),
6148
6293
  cx: "21",
6149
6294
  cy: "21",
@@ -6153,6 +6298,7 @@ window.__ModuleLoader__.load({
6153
6298
  strokeWidth: "4",
6154
6299
  strokeDasharray: `${a.len} ${100 - a.len}`,
6155
6300
  strokeDashoffset: a.offset,
6301
+ style: { "--lc-i": i },
6156
6302
  onMouseEnter: () => {
6157
6303
  if (props.onHoverKey !== void 0) props.onHoverKey(a.key);
6158
6304
  }
@@ -6329,6 +6475,9 @@ window.__ModuleLoader__.load({
6329
6475
  * Interaction mirrors the Context browser's element rows: a row click
6330
6476
  * expands the file's own operation log; each operation is itself the click
6331
6477
  * target that jumps to (and reveals) the exact tool result in the browser.
6478
+ * The file name opens the file's right-Sidebar preview where that column
6479
+ * exists (dsh 0.1.5-rc.1+, the shipped files-sidebar idiom), and the system
6480
+ * opener where it does not.
6332
6481
  */
6333
6482
  function makeFileCard(kit, settings) {
6334
6483
  const { t, fmt, fmtTime } = kit;
@@ -6523,6 +6672,8 @@ window.__ModuleLoader__.load({
6523
6672
  const base = slash >= 0 ? trimmed.slice(slash + 1) : trimmed;
6524
6673
  const glyph = glyphOf(e.path, e.form);
6525
6674
  const abs = e.pattern === true ? void 0 : absPathOf(e.path, props.workspace);
6675
+ const previewable = props.onPreview !== void 0 && e.pattern !== true && e.form !== "dir";
6676
+ const openable = previewable || abs !== void 0 && props.onOpen !== void 0;
6526
6677
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6527
6678
  className: "lc-fa-item" + (open ? " lc-fa-item-on" : ""),
6528
6679
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
@@ -6548,12 +6699,13 @@ window.__ModuleLoader__.load({
6548
6699
  }),
6549
6700
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6550
6701
  className: "lc-fa-path",
6551
- children: [dir !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("em", { children: dir }) : null, abs !== void 0 && props.onOpen !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
6702
+ children: [dir !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("em", { children: dir }) : null, openable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", {
6552
6703
  className: "lc-fa-file",
6553
- title: t("files.open"),
6704
+ title: t(previewable ? "files.preview" : "files.open"),
6554
6705
  onClick: (ev) => {
6555
6706
  ev.stopPropagation();
6556
- props.onOpen?.(abs);
6707
+ if (previewable && props.onPreview?.(e) === true) return;
6708
+ if (abs !== void 0) props.onOpen?.(abs);
6557
6709
  },
6558
6710
  children: base
6559
6711
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: base })]
@@ -6680,7 +6832,7 @@ window.__ModuleLoader__.load({
6680
6832
  return function PluginInfo() {
6681
6833
  const [latest, setLatest] = (0, react.useState)(null);
6682
6834
  (0, react.useEffect)(() => {
6683
- if ("0.49.4".includes("-dev")) return;
6835
+ if ("0.50.0".includes("-dev")) return;
6684
6836
  let on = true;
6685
6837
  fetchLatestVersion().then((v) => {
6686
6838
  if (on && v) setLatest(v);
@@ -6689,8 +6841,8 @@ window.__ModuleLoader__.load({
6689
6841
  on = false;
6690
6842
  };
6691
6843
  }, []);
6692
- const update = latest !== null && isNewerVersion(latest, "0.49.4") ? latest : null;
6693
- const nameText = "dsh-context (v0.49.4)";
6844
+ const update = latest !== null && isNewerVersion(latest, "0.50.0") ? latest : null;
6845
+ const nameText = "dsh-context (v0.50.0)";
6694
6846
  const nameValue = [nameText];
6695
6847
  if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6696
6848
  className: "lc-pi-update",
@@ -7174,19 +7326,41 @@ window.__ModuleLoader__.load({
7174
7326
  }
7175
7327
  }
7176
7328
  };
7177
- /**
7178
- * Pro rides the Flash triple: between the V4.1 Flash launch and the V4.1 Pro
7179
- * release, DeepSeek routes every Pro request to V4.1 Flash and bills it at
7180
- * the Flash rates — point `pro` at the new table once V4.1 Pro is priced.
7181
- */
7329
+ /** The Pro-series rates (deepseek-v4-pro; peak = 2× off-peak), per currency. */
7330
+ const PRO_RATES = {
7331
+ usd: {
7332
+ peak: {
7333
+ hit: .044,
7334
+ miss: 1.32,
7335
+ out: 3.96
7336
+ },
7337
+ off: {
7338
+ hit: .022,
7339
+ miss: .66,
7340
+ out: 1.98
7341
+ }
7342
+ },
7343
+ cny: {
7344
+ peak: {
7345
+ hit: .3,
7346
+ miss: 9,
7347
+ out: 27
7348
+ },
7349
+ off: {
7350
+ hit: .15,
7351
+ miss: 4.5,
7352
+ out: 13.5
7353
+ }
7354
+ }
7355
+ };
7182
7356
  const PRICES = {
7183
7357
  usd: {
7184
7358
  flash: FLASH_RATES.usd,
7185
- pro: FLASH_RATES.usd
7359
+ pro: PRO_RATES.usd
7186
7360
  },
7187
7361
  cny: {
7188
7362
  flash: FLASH_RATES.cny,
7189
- pro: FLASH_RATES.cny
7363
+ pro: PRO_RATES.cny
7190
7364
  }
7191
7365
  };
7192
7366
  /**
@@ -7224,7 +7398,7 @@ window.__ModuleLoader__.load({
7224
7398
  */
7225
7399
  function sessionPrices(currency) {
7226
7400
  return ["flash", "pro"].map((id) => ({
7227
- family: id === "flash" ? "deepseek-v4.1-flash / deepseek-flash" : "deepseek-v4-pro",
7401
+ family: id === "flash" ? "deepseek-flash / deepseek-v4-flash" : "deepseek-v4-pro",
7228
7402
  peak: PRICES[currency][id].peak,
7229
7403
  off: PRICES[currency][id].off
7230
7404
  }));
@@ -7236,17 +7410,21 @@ window.__ModuleLoader__.load({
7236
7410
  //#endregion
7237
7411
  //#region src/client/components/statsContext.tsx
7238
7412
  /**
7239
- * The Context card: what the session's context IS and how it evolved — an
7240
- * eight-cell 2×4 grid pairing the session's shape (turns / steps / live tool
7241
- * calls / images) with the context-event tally (injections / compactions /
7242
- * prunes) and the whole-session cost estimate. Count figures only: nothing
7243
- * here is part of a spendable whole, so no pie — proportions live in the
7244
- * composition card. The cost cell prices the host-folded cumulative billed
7245
- * totals (complete session log, never trimmed) at the hardcoded DeepSeek V4
7246
- * list prices (cost.ts) in the locale's currency; its hover bubble (a '?'
7247
- * marker + styled DOM tip) explains the whole-session estimate and lists the
7248
- * per-1M-token table straight from cost.ts, so printed rates can never drift
7249
- * from the math.
7413
+ * The Context card: what the session's context IS and how it evolved — a
7414
+ * six-cell grid of the session's shape (turns / steps / human inputs / live
7415
+ * tool calls), the whole-session cache-hit rate, and the whole-session cost
7416
+ * estimate.
7417
+ * Count figures only: nothing here is part of a spendable whole, so no pie —
7418
+ * proportions live in the composition card, and the context-event tallies
7419
+ * live on the events card's kind filters (contextView.tsx). The cache-hit
7420
+ * cell reads the official `tokenUsage` projection the same source and
7421
+ * formula as the harness chat stats line under the composer, shown with one
7422
+ * decimal and dashes until a provider reports usage. The cost cell prices
7423
+ * the host-folded cumulative billed totals (complete session log, never
7424
+ * trimmed) at the hardcoded DeepSeek V4 list prices (cost.ts) in the locale's
7425
+ * currency; its hover bubble (a '?' marker + styled DOM tip) explains the
7426
+ * whole-session estimate and lists the per-1M-token table straight from
7427
+ * cost.ts, so printed rates can never drift from the math.
7250
7428
  *
7251
7429
  * The counts arrive precomputed: the split-generation wire head carries them
7252
7430
  * (shared/types.ts `TimelineCounts` — computed over the retained records),
@@ -7315,6 +7493,7 @@ window.__ModuleLoader__.load({
7315
7493
  ]
7316
7494
  }, r.family))]
7317
7495
  }, "prices")];
7496
+ const hit = props.usage === null ? null : cacheHitPercent(numOf(props.usage.cacheReadTokens), numOf(props.usage.uncachedInputTokens) + numOf(props.usage.cacheReadTokens) + numOf(props.usage.cacheWriteTokens), 1);
7318
7497
  const cell = (label, value, tip) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7319
7498
  className: "lc-stat" + (tip === void 0 ? "" : " lc-stat-tipped"),
7320
7499
  children: [
@@ -7350,12 +7529,10 @@ window.__ModuleLoader__.load({
7350
7529
  children: [
7351
7530
  cell(t("stats.turns"), props.counts.turns),
7352
7531
  cell(t("stats.steps"), props.counts.steps),
7532
+ cell(t("stats.humanInputs"), props.humanInputs ?? 0, t("stats.humanInputsTip")),
7353
7533
  cell(t("stats.toolCalls"), props.toolCalls ?? 0),
7354
- cell(t("stats.images"), props.images ?? 0),
7355
- cell(t("stats.cost"), cost === null ? "—" : formatCost(cost, currency), costTip),
7356
- cell(t("stats.injects"), props.counts.injects),
7357
- cell(t("stats.compactions"), props.counts.compactions),
7358
- cell(t("stats.prunes"), props.counts.prunes)
7534
+ cell(t("stats.cacheHit"), hit === null ? "—" : `${hit}%`),
7535
+ cell(t("stats.cost"), cost === null ? "—" : formatCost(cost, currency), costTip)
7359
7536
  ]
7360
7537
  })]
7361
7538
  });
@@ -7585,59 +7762,39 @@ window.__ModuleLoader__.load({
7585
7762
  //#endregion
7586
7763
  //#region src/client/components/statsTokens.tsx
7587
7764
  /**
7588
- * The Token card: the provider-reported cumulative billing buckets (the
7589
- * official `tokenUsage` projection, whole session). The donut's center is
7590
- * the cache-hit share the same figure the harness chat stats line shows —
7591
- * and the slice rows carry each bucket's token count: cache reads/writes,
7592
- * uncached input, and output (reasoning included). Zero buckets stay hidden
7593
- * once any usage is reported (DeepSeek sessions never report cache writes);
7594
- * the empty state keeps all four rows.
7595
- */
7765
+ * The Token card: the session's whole billed token usage — the SAME total
7766
+ * the harness chat stats line shows under the composer (uncached input +
7767
+ * cache read + cache write + output off the official `tokenUsage`
7768
+ * projection) split by WHAT the tokens are, not by how the provider
7769
+ * cached them. The six composition categories share the provider-reported
7770
+ * prompt-side total by the composition card's own estimated ratios
7771
+ * (billedParts), and the provider's exact output count is its own slice, so
7772
+ * the ring and the center figure always equal the chat line's figure by
7773
+ * construction. The estimated category counts carry the ≈ marker (the
7774
+ * composition card's convention); the center total and the output count are
7775
+ * exact. Zero parts stay hidden once any usage is reported; the empty state
7776
+ * (no provider report yet) keeps all seven rows behind a dash center.
7777
+ */
7778
+ const NO_USAGE = {
7779
+ uncachedInputTokens: 0,
7780
+ outputTokens: 0,
7781
+ cacheReadTokens: 0,
7782
+ cacheWriteTokens: 0
7783
+ };
7596
7784
  function makeStatsTokens(kit, Donut) {
7597
- const { t, fmt, fmtShare } = kit;
7785
+ const { t, fmt, fmtShare, catLabel } = kit;
7598
7786
  const SliceList = makeSliceList(kit);
7599
7787
  return function StatsTokens(props) {
7600
7788
  const [hoverKey, setHoverKey] = (0, react.useState)(null);
7601
- const reads = props.usage !== null ? numOf(props.usage.cacheReadTokens) : 0;
7602
- const writes = props.usage !== null ? numOf(props.usage.cacheWriteTokens) : 0;
7603
- const uncached = props.usage !== null ? numOf(props.usage.uncachedInputTokens) : 0;
7604
- const output = props.usage !== null ? numOf(props.usage.outputTokens) : 0;
7605
- const billed = reads + writes + uncached + output;
7606
- const hit = props.usage !== null ? cacheHitPercent(reads, uncached + reads + writes) : null;
7607
- const buckets = [
7608
- {
7609
- key: "read",
7610
- color: "#22c55e",
7611
- label: t("tokens.cacheRead"),
7612
- value: reads
7613
- },
7614
- {
7615
- key: "write",
7616
- color: "#f59e0b",
7617
- label: t("tokens.cacheWrite"),
7618
- value: writes
7619
- },
7620
- {
7621
- key: "uncached",
7622
- color: "#a855f7",
7623
- label: t("tokens.uncached"),
7624
- value: uncached
7625
- },
7626
- {
7627
- key: "output",
7628
- color: "#3b82f6",
7629
- label: t("tokens.output"),
7630
- note: t("tokens.outputNote"),
7631
- value: output
7632
- }
7633
- ];
7634
- const shown = billed > 0 ? buckets.filter((b) => b.value > 0) : buckets;
7635
- const rows = shown.map((b) => ({
7636
- key: b.key,
7637
- color: b.color,
7638
- label: b.label,
7639
- pct: fmtShare(b.value, billed),
7640
- count: b.note === void 0 ? fmt(b.value) : `${fmt(b.value)} · ${b.note}`
7789
+ const total = (props.usage !== null ? numOf(props.usage.uncachedInputTokens) + numOf(props.usage.cacheReadTokens) + numOf(props.usage.cacheWriteTokens) : 0) + (props.usage !== null ? numOf(props.usage.outputTokens) : 0);
7790
+ const parts = billedParts(props.current, props.breakdown, props.usage ?? NO_USAGE);
7791
+ const shown = total > 0 ? parts.filter((p) => p.value > 0) : parts;
7792
+ const rows = shown.map((p) => ({
7793
+ key: p.key,
7794
+ color: p.color,
7795
+ label: p.key === "output" ? t("tokens.output") : catLabel(p.key),
7796
+ pct: fmtShare(p.value, total),
7797
+ count: p.key === "output" ? `${fmt(p.value)} · ${t("tokens.outputNote")}` : "" + fmt(p.value)
7641
7798
  }));
7642
7799
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7643
7800
  className: "lc-card lc-col-stats lc-col-donut",
@@ -7650,14 +7807,10 @@ window.__ModuleLoader__.load({
7650
7807
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7651
7808
  className: "lc-donut-row",
7652
7809
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
7653
- segments: shown.map((b) => ({
7654
- key: b.key,
7655
- color: b.color,
7656
- value: b.value
7657
- })),
7810
+ segments: shown,
7658
7811
  size: 96,
7659
- centerTop: hit === null ? "—" : `${hit}%`,
7660
- centerSub: t("tokens.cacheHit"),
7812
+ centerTop: props.usage === null ? "—" : fmt(total),
7813
+ centerSub: t("tokens.total"),
7661
7814
  hoverKey,
7662
7815
  onHoverKey: setHoverKey
7663
7816
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliceList, {
@@ -7673,7 +7826,8 @@ window.__ModuleLoader__.load({
7673
7826
  //#region src/client/components/trendChart.tsx
7674
7827
  /**
7675
7828
  * Bespoke per-request history chart — no shared data-viz primitive — styled through the shared `--dsw-alias-*` tokens; helpers
7676
- * aggregateByTurn/attachMarkers are shared with ContextView.
7829
+ * aggregateByTurn/attachMarkers are shared with ContextView. On mount each bar rises from its baseline,
7830
+ * staggered left to right with the cascade capped for long logs (trendChart.css, `--lc-i` slots below).
7677
7831
  */
7678
7832
  /**
7679
7833
  * Collapse per-step requests into one bar per turn — each turn is represented by its LAST step's record, tagged `stepCount` for the bar's
@@ -7730,6 +7884,7 @@ window.__ModuleLoader__.load({
7730
7884
  const fmtSigned = (v) => (v > 0 ? "+" : "") + fmt(v);
7731
7885
  const BAR_W = 14;
7732
7886
  const BAR_GAP = 2;
7887
+ const STAGGER_CAP = 20;
7733
7888
  const TURN_FILLS = ["rgba(128,128,128,0.12)", "rgba(128,128,128,0.26)"];
7734
7889
  const LABEL_OVERHANG = 48;
7735
7890
  const LABEL_GAP = 2;
@@ -7775,6 +7930,7 @@ window.__ModuleLoader__.load({
7775
7930
  const { req, marker } = props;
7776
7931
  const markerAt = marker !== void 0 ? eventAt(marker) : null;
7777
7932
  const diverge = props.upPx !== void 0 && props.downPx !== void 0 && props.deltaScale !== void 0;
7933
+ const enterStyle = { "--lc-i": Math.min(props.enterIndex, STAGGER_CAP) };
7778
7934
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7779
7935
  className: "lc-bar" + (props.selected ? " lc-bar-selected" : "") + (props.hovered ? " lc-bar-hovered" : "") + (props.inTurn ? " lc-bar-in-turn" : ""),
7780
7936
  "data-seq": req.seq,
@@ -7791,7 +7947,10 @@ window.__ModuleLoader__.load({
7791
7947
  children: "✂"
7792
7948
  }) : null, diverge ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7793
7949
  className: "lc-bar-up",
7794
- style: { bottom: `${props.downPx}px` },
7950
+ style: {
7951
+ bottom: `${props.downPx}px`,
7952
+ ...enterStyle
7953
+ },
7795
7954
  children: CATS.map((c) => {
7796
7955
  const d = req[c.key] || 0;
7797
7956
  if (d <= 0) return null;
@@ -7806,7 +7965,10 @@ window.__ModuleLoader__.load({
7806
7965
  })
7807
7966
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7808
7967
  className: "lc-bar-down",
7809
- style: { top: `${props.upPx}px` },
7968
+ style: {
7969
+ top: `${props.upPx}px`,
7970
+ ...enterStyle
7971
+ },
7810
7972
  children: CATS.map((c) => {
7811
7973
  const d = req[c.key] || 0;
7812
7974
  if (d >= 0) return null;
@@ -7821,6 +7983,7 @@ window.__ModuleLoader__.load({
7821
7983
  })
7822
7984
  })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7823
7985
  className: "lc-bar-stack",
7986
+ style: enterStyle,
7824
7987
  children: CATS.map((c) => {
7825
7988
  const v = req[c.key] || 0;
7826
7989
  if (!v) return null;
@@ -8189,9 +8352,10 @@ window.__ModuleLoader__.load({
8189
8352
  upPx: delta ? upPx : void 0,
8190
8353
  downPx: delta ? downPx : void 0,
8191
8354
  deltaScale: delta ? deltaScale : void 0,
8355
+ enterIndex: i,
8192
8356
  onSelect: props.onSelect,
8193
8357
  onHover: props.onHover
8194
- }, req.seq))
8358
+ }, `${req.seq}:${props.granularity}`))
8195
8359
  ]
8196
8360
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8197
8361
  className: "lc-turns",
@@ -8359,6 +8523,12 @@ window.__ModuleLoader__.load({
8359
8523
  }, [sessionId]);
8360
8524
  const requests = data ? data.requests : [];
8361
8525
  const events = data ? data.events : [];
8526
+ const counts = data?.counts ?? countsOfRecords(requests, events);
8527
+ const kindCounts = {
8528
+ inject: counts.injects,
8529
+ compaction: counts.compactions,
8530
+ prune: counts.prunes
8531
+ };
8362
8532
  const shownEvents = pickedKinds.length === EVENT_KINDS.length ? events : events.filter((e) => pickedKinds.includes(e.kind));
8363
8533
  const displayRequests = (0, react.useMemo)(() => granularity === "turn" ? aggregateByTurn(requests) : requests, [requests, granularity]);
8364
8534
  const markers = (0, react.useMemo)(() => attachMarkers(displayRequests, events), [displayRequests, events]);
@@ -8436,6 +8606,16 @@ window.__ModuleLoader__.load({
8436
8606
  };
8437
8607
  }, [ctx]);
8438
8608
  const fileOpener = (0, react.useMemo)(() => canOpenPaths ? openPathVia(ctx) : void 0, [canOpenPaths, ctx]);
8609
+ const previewOpener = (0, react.useMemo)(() => openResourceVia(ctx), [ctx]);
8610
+ const sessionKey = typeof sessionId === "string" ? sessionId : void 0;
8611
+ const previewFile = (0, react.useMemo)(() => previewOpener === void 0 ? void 0 : (entry) => {
8612
+ const address = previewAddressOf(entry.path, entry.form, entry.pattern, sessionKey, workspace);
8613
+ return address !== void 0 && previewOpener(address);
8614
+ }, [
8615
+ previewOpener,
8616
+ sessionKey,
8617
+ workspace
8618
+ ]);
8439
8619
  const locateFileOp = (0, react.useCallback)((op) => {
8440
8620
  const seq = op.parent ?? op.seq;
8441
8621
  const step = locateStepOf(requests, seq, op.gone);
@@ -8627,20 +8807,24 @@ window.__ModuleLoader__.load({
8627
8807
  className: "lc-root",
8628
8808
  ref: rootRef,
8629
8809
  children: [
8810
+ inSidebar ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8811
+ className: "lc-cols lc-head",
8812
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsContext, {
8813
+ counts,
8814
+ humanInputs: data.humanInputs,
8815
+ toolCalls: data.toolCalls,
8816
+ usage,
8817
+ cost: data.cost,
8818
+ locale: activeLocale
8819
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginInfo, {})]
8820
+ }),
8630
8821
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8631
8822
  className: "lc-cols lc-head",
8632
- children: [
8633
- inSidebar ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsContext, {
8634
- counts: data.counts ?? countsOfRecords(requests, events),
8635
- toolCalls: data.toolCalls,
8636
- images: data.images,
8637
- cost: data.cost,
8638
- locale: activeLocale
8639
- }),
8640
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTokens, { usage }),
8641
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTiming, { timing: data.timing ?? null }),
8642
- inSidebar ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PluginInfo, {})
8643
- ]
8823
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTokens, {
8824
+ usage,
8825
+ current: data.current,
8826
+ breakdown
8827
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTiming, { timing: data.timing ?? null })]
8644
8828
  }),
8645
8829
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8646
8830
  className: "lc-cols lc-cols-main",
@@ -8663,13 +8847,20 @@ window.__ModuleLoader__.load({
8663
8847
  children: t("events.title")
8664
8848
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8665
8849
  className: "lc-kinds",
8666
- children: EVENT_KINDS.map((k) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8667
- className: "lc-gran-btn" + (pickedKinds.includes(k) ? " lc-gran-on lc-kind-" + k : ""),
8668
- onClick: () => {
8669
- toggleKind(k);
8670
- },
8671
- children: t("kind." + k)
8672
- }, k))
8850
+ children: EVENT_KINDS.map((k) => {
8851
+ const n = kindCounts[k];
8852
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8853
+ "data-kind": k,
8854
+ className: "lc-gran-btn" + (pickedKinds.includes(k) ? " lc-gran-on lc-kind-" + k : ""),
8855
+ onClick: () => {
8856
+ toggleKind(k);
8857
+ },
8858
+ children: [t("kind." + k), n !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8859
+ className: "lc-kind-n",
8860
+ children: kit.fmt(n)
8861
+ }) : null]
8862
+ }, k);
8863
+ })
8673
8864
  })]
8674
8865
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EventList, {
8675
8866
  events: shownEvents,
@@ -8680,6 +8871,7 @@ window.__ModuleLoader__.load({
8680
8871
  activity: fileActivity,
8681
8872
  scope: fileScope,
8682
8873
  workspace,
8874
+ onPreview: previewFile,
8683
8875
  onOpen: fileOpener,
8684
8876
  onLocate: locateFileOp,
8685
8877
  state: source.detailState,
@@ -8994,7 +9186,7 @@ window.__ModuleLoader__.load({
8994
9186
  }
8995
9187
  //#endregion
8996
9188
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/base.css.mjs
8997
- 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-title-icon{flex:none}.lc-title-label{padding-right:30px}.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<=940px){.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}";
9189
+ 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-title-icon{flex:none}.lc-title-label{padding-right:30px}.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-kind-n{font-variant-numeric:tabular-nums;opacity:.65;margin-left:4px}.lc-gran-on .lc-kind-n{opacity:.85}.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<=720px){.lc-head>.lc-card{flex-basis:100%}}.lc-head>.lc-card:has(.lc-stats){flex-direction:column;display:flex}.lc-head .lc-stats{flex:1;align-content:stretch}.lc-head .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}";
8998
9190
  const tagId$13 = "dsh-context/base.css";
8999
9191
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$13) + "]") === null) {
9000
9192
  const tag = document.createElement("style");
@@ -9005,7 +9197,7 @@ window.__ModuleLoader__.load({
9005
9197
  }
9006
9198
  //#endregion
9007
9199
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/stats.css.mjs
9008
- const css$12 = ".lc-stats{grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;display:grid}.lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}.lc-stats .lc-stat{padding:6px 3px}.lc-stats .lc-stat-q{margin:0;position:absolute;top:3px;right:4px}.lc-stat-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;font-weight:600;overflow:hidden}.lc-stat-value{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:center;font-size:14px;font-weight:600;overflow:hidden}.lc-stat-sub{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.lc-stat-tipped{position:relative}.lc-stat-q{text-align:center;width:11px;height:11px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:help;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:50%;margin-left:4px;font-size:9px;font-style:normal;font-weight:700;line-height:11px;display:inline-block}.lc-stat-tipped:hover .lc-stat-q{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary)}.lc-stat-tip{max-width:280px;font-weight:400;line-height:1.5;top:calc(100% + 6px);left:0}.lc-stat-tipped:hover .lc-stat-tip{opacity:1}.lc-stat-tip-prices{border-top:1px dashed var(--dsw-alias-border-l1);margin-top:5px;padding-top:5px;display:block}.lc-stat-tip-head{font-weight:600;display:block}.lc-stat-tip-row{margin-top:2px;display:block}.lc-stat-tip-model{color:var(--dsw-alias-label-primary)}.lc-card-sub{color:var(--dsw-alias-label-secondary);margin-left:auto;font-size:12px;font-weight:400}.lc-overview-num{align-items:baseline;gap:6px;margin-bottom:8px;display:flex}.lc-overview-num>b{font-size:20px}.lc-overview-num span{color:var(--dsw-alias-label-secondary)}.lc-overview-pct{margin-left:auto;font-size:11px}.lc-overview-pct b{color:var(--dsw-alias-label-primary);margin-right:4px;font-size:20px}.lc-stat-head{color:var(--dsw-alias-label-secondary);margin:12px 0 8px;font-size:11px;font-weight:600}.lc-donut-row{justify-content:flex-start;align-items:center;gap:12px;min-width:0;display:flex}.lc-col-donut{flex-direction:column;display:flex}.lc-col-donut .lc-donut-row{flex:1}.lc-donut-row .lc-sl{flex:auto}.lc-donut{flex:none;position:relative}.lc-donut svg{display:block}.lc-donut-track{stroke:#8080802e}.lc-donut-seg{transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out)}.lc-donut-dim .lc-donut-seg{opacity:.35}.lc-donut-dim .lc-donut-seg-on{opacity:1}.lc-donut-center{pointer-events:none;text-align:center;box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:1px;padding:0 7px;display:flex;position:absolute;inset:0}.lc-donut-center b{white-space:nowrap;text-overflow:ellipsis;max-width:100%;line-height:1.15;overflow:hidden}.lc-donut-center span{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-sl{flex-direction:column;gap:4px;min-width:0;display:flex;overflow:hidden}.lc-sl-row{min-width:0;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;flex-direction:column;gap:1px;margin:0 -6px;padding:1px 6px;font-size:12px;display:flex}.lc-sl-row-on{background:var(--dsw-alias-interactive-bg-hover)}.lc-sl-row-dim{opacity:.55}.lc-sl-main{align-items:center;gap:6px;min-width:0;display:flex}.lc-sl-dot{border-radius:2px;flex:none;width:8px;height:8px}.lc-sl-label{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;font-weight:600;line-height:1.25;overflow:hidden}.lc-sl-pct{text-align:right;font-variant-numeric:tabular-nums;flex:none;min-width:34px;font-weight:700}.lc-sl-sub{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;text-overflow:ellipsis;margin-left:14px;font-size:11px;line-height:1.3;overflow:hidden}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.lc-pi-row-btn{appearance:none;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;width:100%;padding:0}.lc-pi-row{min-width:0;color:inherit;flex-direction:row;justify-content:space-between;align-items:baseline;gap:12px;text-decoration:none;display:flex}.lc-pi-row:hover .lc-pi-value{text-decoration:underline}.lc-pi-hint{text-decoration:none}.lc-pi-hint:hover{text-decoration:underline}.lc-pi-update{color:var(--dsw-alias-state-warn-primary);white-space:nowrap;margin-left:6px;font-size:11px}.lc-pi-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;flex:none;font-size:11px;font-weight:600;overflow:hidden}.lc-pi-value{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:right;font-size:13px;font-weight:600;overflow:hidden}.lc-pi-row-btn .lc-pi-value{font-weight:400}@container lc-card (width<=320px){.lc-donut-row{gap:8px}.lc-stats{grid-template-columns:repeat(2,minmax(0,1fr))}}@container lc-card (width<=240px){.lc-donut-row{flex-wrap:wrap}.lc-donut{margin:0 auto}.lc-donut-row .lc-sl{flex-basis:100%}}";
9200
+ const css$12 = ".lc-stats{grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;display:grid}.lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}.lc-stats .lc-stat{padding:6px 3px}.lc-stats .lc-stat-q{margin:0;position:absolute;top:3px;right:4px}.lc-stat-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;font-weight:600;overflow:hidden}.lc-stat-value{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:center;font-size:14px;font-weight:600;overflow:hidden}.lc-stat-sub{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.lc-stat-tipped{position:relative}.lc-stat-q{text-align:center;width:11px;height:11px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:help;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:50%;margin-left:4px;font-size:9px;font-style:normal;font-weight:700;line-height:11px;display:inline-block}.lc-stat-tipped:hover .lc-stat-q{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary)}.lc-stat-tip{max-width:280px;font-weight:400;line-height:1.5;top:calc(100% + 6px);left:0}.lc-stat-tipped:hover .lc-stat-tip{opacity:1}.lc-stat-tip-prices{border-top:1px dashed var(--dsw-alias-border-l1);margin-top:5px;padding-top:5px;display:block}.lc-stat-tip-head{font-weight:600;display:block}.lc-stat-tip-row{margin-top:2px;display:block}.lc-stat-tip-model{color:var(--dsw-alias-label-primary)}.lc-card-sub{color:var(--dsw-alias-label-secondary);margin-left:auto;font-size:12px;font-weight:400}.lc-overview-num{align-items:baseline;gap:6px;margin-bottom:8px;display:flex}.lc-overview-num>b{font-size:20px}.lc-overview-num span{color:var(--dsw-alias-label-secondary)}.lc-overview-pct{margin-left:auto;font-size:11px}.lc-overview-pct b{color:var(--dsw-alias-label-primary);margin-right:4px;font-size:20px}.lc-stat-head{color:var(--dsw-alias-label-secondary);margin:12px 0 8px;font-size:11px;font-weight:600}.lc-donut-row{justify-content:flex-start;align-items:center;gap:12px;min-width:0;display:flex}.lc-col-donut{flex-direction:column;display:flex}.lc-col-donut .lc-donut-row{flex:1}.lc-donut-row .lc-sl{flex:auto}.lc-donut{flex:none;position:relative}.lc-donut svg{display:block}.lc-donut-track{stroke:#8080802e}.lc-donut-seg{transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);animation:.45s cubic-bezier(.4,0,.2,1) backwards lc-donut-in;animation-delay:calc(var(--lc-i,0) * 50ms)}@keyframes lc-donut-in{0%{stroke-dasharray:0 100}}.lc-donut-dim .lc-donut-seg{opacity:.35}.lc-donut-dim .lc-donut-seg-on{opacity:1}.lc-donut-center{pointer-events:none;text-align:center;box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:1px;padding:0 7px;display:flex;position:absolute;inset:0}.lc-donut-center b{white-space:nowrap;text-overflow:ellipsis;max-width:100%;line-height:1.15;overflow:hidden}.lc-donut-center span{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-sl{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:4px 20px;min-width:0;display:grid;overflow:hidden}.lc-sl-row{min-width:0;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;flex-direction:column;gap:1px;margin:0 -6px;padding:1px 6px;font-size:12px;display:flex}.lc-sl-row-on{background:var(--dsw-alias-interactive-bg-hover)}.lc-sl-row-dim{opacity:.55}.lc-sl-main{align-items:center;gap:6px;min-width:0;display:flex}.lc-sl-dot{border-radius:2px;flex:none;width:8px;height:8px}.lc-sl-label{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;font-weight:600;line-height:1.25;overflow:hidden}.lc-sl-pct{text-align:right;font-variant-numeric:tabular-nums;flex:none;min-width:34px;font-weight:700}.lc-sl-sub{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;text-overflow:ellipsis;margin-left:14px;font-size:11px;line-height:1.3;overflow:hidden}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.lc-pi-row-btn{appearance:none;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;width:100%;padding:0}.lc-pi-row{min-width:0;color:inherit;flex-direction:row;justify-content:space-between;align-items:baseline;gap:12px;text-decoration:none;display:flex}.lc-pi-row:hover .lc-pi-value{text-decoration:underline}.lc-pi-hint{text-decoration:none}.lc-pi-hint:hover{text-decoration:underline}.lc-pi-update{color:var(--dsw-alias-state-warn-primary);white-space:nowrap;margin-left:6px;font-size:11px}.lc-pi-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;flex:none;font-size:11px;font-weight:600;overflow:hidden}.lc-pi-value{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:right;font-size:13px;font-weight:600;overflow:hidden}.lc-pi-row-btn .lc-pi-value{font-weight:400}@container lc-card (width>=700px){.lc-stats{grid-template-columns:repeat(6,minmax(0,1fr))}}@container lc-card (width<=320px){.lc-donut-row{gap:8px}.lc-stats{grid-template-columns:repeat(2,minmax(0,1fr))}}@container lc-card (width<=240px){.lc-donut-row{flex-wrap:wrap}.lc-donut{margin:0 auto}.lc-donut-row .lc-sl{flex-basis:100%}}@media (prefers-reduced-motion:reduce){.lc-donut-seg{animation:none}}";
9009
9201
  const tagId$12 = "dsh-context/stats.css";
9010
9202
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$12) + "]") === null) {
9011
9203
  const tag = document.createElement("style");
@@ -9038,7 +9230,7 @@ window.__ModuleLoader__.load({
9038
9230
  }
9039
9231
  //#endregion
9040
9232
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/stackedBar.css.mjs
9041
- const css$9 = ".lc-stacked-wrap{width:100%;position:relative}.lc-stacked{background:#8080802e;border-radius:5px;width:100%;display:flex;position:relative;overflow:hidden}.lc-occupied-box{border:2px solid var(--dsw-alias-label-tertiary);box-sizing:border-box;pointer-events:none;opacity:0;box-shadow:0 0 0 1px var(--dsw-alias-bg-layer-2);transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:5px;position:absolute;top:0;bottom:0;left:0}.lc-occupied-box-on{opacity:1}.lc-bar-tip{z-index:5;white-space:nowrap;padding:3px 8px;bottom:calc(100% + 6px);transform:translate(-50%)}.lc-bar-tip-on{opacity:1}.lc-stacked>div{height:100%}.lc-stacked-seg-pick{cursor:pointer}.lc-stacked-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-stacked-free{box-sizing:border-box;transition:box-shadow 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-stacked-seg-on{filter:brightness(1.18)}.lc-reserve{z-index:1;pointer-events:auto;cursor:help;background:repeating-linear-gradient(45deg, color-mix(in srgb, var(--dsw-alias-state-warn-primary) 24%, transparent) 0 5px, transparent 5px 10px);position:absolute;top:0;bottom:0}.lc-stacked-free-on{border:2px dashed var(--dsw-alias-label-secondary);border-radius:3px}.lc-stacked-dim .lc-stacked-seg{opacity:.35}.lc-stacked-dim .lc-stacked-seg-on{opacity:1}.lc-stacked-dim .lc-stacked-free{opacity:.35}.lc-stacked-dim .lc-stacked-free-on{opacity:1}.lc-legend{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:6px 14px;margin-top:10px;display:grid}.lc-chip{min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;align-items:center;gap:5px;padding:1px 6px;display:flex}.lc-chip-label{white-space:nowrap;text-overflow:ellipsis;min-width:0;font-weight:600;overflow:hidden}.lc-chip-nums{white-space:nowrap;flex:none;align-items:baseline;gap:6px;margin-left:auto;display:inline-flex}.lc-chip i,.lc-detail-row i{border-radius:2px;width:8px;height:8px;display:inline-block}.lc-chip i{transition:box-shadow var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out)}.lc-chip em{color:var(--dsw-alias-label-secondary);font-style:normal}.lc-chip-on{background:var(--dsw-alias-interactive-bg-hover);font-weight:600}.lc-chip-on i{box-shadow:0 0 0 1px var(--dsw-alias-brand-primary)}";
9233
+ const css$9 = ".lc-stacked-wrap{width:100%;position:relative}.lc-stacked{background:#8080802e;border-radius:5px;width:100%;display:flex;position:relative;overflow:hidden}.lc-occupied-box{border:2px solid var(--dsw-alias-label-tertiary);box-sizing:border-box;pointer-events:none;opacity:0;box-shadow:0 0 0 1px var(--dsw-alias-bg-layer-2);transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:5px;position:absolute;top:0;bottom:0;left:0}.lc-occupied-box-on{opacity:1}.lc-bar-tip{z-index:5;white-space:nowrap;padding:3px 8px;bottom:calc(100% + 6px);transform:translate(-50%)}.lc-bar-tip-on{opacity:1}.lc-stacked>div{height:100%}.lc-stacked-seg-pick{cursor:pointer}.lc-stacked-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-stacked-free{box-sizing:border-box;transition:box-shadow 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-stacked-seg,.lc-stacked-free{transform-origin:0;animation:.4s cubic-bezier(.4,0,.2,1) backwards lc-stacked-in;animation-delay:calc(var(--lc-i,0) * 40ms)}@keyframes lc-stacked-in{0%{transform:scaleX(0)}}.lc-stacked-seg-on{filter:brightness(1.18)}.lc-reserve{z-index:1;pointer-events:auto;cursor:help;background:repeating-linear-gradient(45deg, color-mix(in srgb, var(--dsw-alias-state-warn-primary) 24%, transparent) 0 5px, transparent 5px 10px);position:absolute;top:0;bottom:0}.lc-stacked-free-on{border:2px dashed var(--dsw-alias-label-secondary);border-radius:3px}.lc-stacked-dim .lc-stacked-seg{opacity:.35}.lc-stacked-dim .lc-stacked-seg-on{opacity:1}.lc-stacked-dim .lc-stacked-free{opacity:.35}.lc-stacked-dim .lc-stacked-free-on{opacity:1}.lc-legend{grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:6px 14px;margin-top:10px;display:grid}.lc-chip{min-width:0;color:var(--dsw-alias-label-primary);cursor:pointer;transition:background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;align-items:center;gap:5px;padding:1px 6px;display:flex}.lc-chip-label{white-space:nowrap;text-overflow:ellipsis;min-width:0;font-weight:600;overflow:hidden}.lc-chip-nums{white-space:nowrap;flex:none;align-items:baseline;gap:6px;margin-left:auto;display:inline-flex}.lc-chip i,.lc-detail-row i{border-radius:2px;width:8px;height:8px;display:inline-block}.lc-chip i{transition:box-shadow var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out)}.lc-chip em{color:var(--dsw-alias-label-secondary);font-style:normal}.lc-chip-on{background:var(--dsw-alias-interactive-bg-hover);font-weight:600}.lc-chip-on i{box-shadow:0 0 0 1px var(--dsw-alias-brand-primary)}@media (prefers-reduced-motion:reduce){.lc-stacked-seg,.lc-stacked-free{animation:none}}";
9042
9234
  const tagId$9 = "dsh-context/stackedBar.css";
9043
9235
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$9) + "]") === null) {
9044
9236
  const tag = document.createElement("style");
@@ -9049,7 +9241,7 @@ window.__ModuleLoader__.load({
9049
9241
  }
9050
9242
  //#endregion
9051
9243
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/trendChart.css.mjs
9052
- 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-chart-tip span+span{color:var(--dsw-alias-label-secondary)}.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}";
9244
+ 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-chart-tip span+span{color:var(--dsw-alias-label-secondary)}.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,.lc-bar-up,.lc-bar-down{transform-origin:50% 100%;animation:.35s cubic-bezier(.4,0,.2,1) backwards lc-bar-in;animation-delay:calc(var(--lc-i,0) * 15ms)}.lc-bar-down{transform-origin:50% 0}@keyframes lc-bar-in{0%{transform:scaleY(0)}}.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}@media (prefers-reduced-motion:reduce){.lc-bar-stack,.lc-bar-up,.lc-bar-down{animation:none}}";
9053
9245
  const tagId$8 = "dsh-context/trendChart.css";
9054
9246
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$8) + "]") === null) {
9055
9247
  const tag = document.createElement("style");
package/lib/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import "@deepseek-ai/dsh-session";
3
3
  import { Context } from "@deepseek-ai/cordis";
4
4
  //#region src/host/config.d.ts
5
- interface Config {
5
+ export interface Config {
6
6
  /** Cap on kept per-step request records (the hard step backstop). */
7
7
  maxRequestSteps?: number;
8
8
  /** Newest whole-turn window kept; trimming crosses whole turns, never mid-turn. */
@@ -23,7 +23,7 @@ interface Config {
23
23
  * The cordis `Config` validator: strict on keys, defaults on the schema fields; tolerates `undefined` (a patch row without a `config:`
24
24
  * block — defaults win).
25
25
  */
26
- declare const Config: z.ZodPreprocess<z.ZodObject<{
26
+ export declare const Config: z.ZodPreprocess<z.ZodObject<{
27
27
  maxRequestSteps: z.ZodDefault<z.ZodNumber>;
28
28
  maxKeptTurns: z.ZodDefault<z.ZodNumber>;
29
29
  maxEvents: z.ZodDefault<z.ZodNumber>;
@@ -135,6 +135,13 @@ interface TimelineState {
135
135
  * Absent until a DeepSeek flash/pro request reports usage.
136
136
  */
137
137
  cost?: SessionCostUsage;
138
+ /**
139
+ * Whole-session human-input tally (see Snapshot.humanInputs): every
140
+ * non-injection `user/message` plus every answered `ask_user_question`
141
+ * result. Running total — never trimmed, like `cost`/`timing`. Absent until
142
+ * the first human input folds.
143
+ */
144
+ humanInputs?: number;
138
145
  archiveFloor?: number;
139
146
  /**
140
147
  * The detail collections' revision marker (see ContextTimelineDetail):
@@ -339,6 +346,14 @@ interface Snapshot {
339
346
  * hosts; clients treat absence as zero.
340
347
  */
341
348
  toolCalls?: number;
349
+ /**
350
+ * Whole-session human-input tally: every non-injection `user/message`
351
+ * (the user's own messages) plus every answered `ask_user_question`
352
+ * result (one per answer submission). A running total over the COMPLETE
353
+ * log — turns the retained window no longer holds still count. Absent
354
+ * from older hosts; clients treat absence as zero.
355
+ */
356
+ humanInputs?: number;
342
357
  /**
343
358
  * Split-generation head fields — present exactly when the host serves the
344
359
  * SLIM head (the heavy collections moved to the on-demand detail channel,
@@ -568,7 +583,7 @@ interface CostFamilyUsage {
568
583
  * token totals per DeepSeek model family (matched on the model NAME,
569
584
  * provider-agnostic) and pricing period. The Client prices these with its
570
585
  * hardcoded list-price table in the locale's currency. Absent until a
571
- * deepseek-flash / deepseek-v4.1-flash / deepseek-v4-pro request reports
586
+ * deepseek-flash / deepseek-v4-flash / deepseek-v4-pro request reports
572
587
  * usage.
573
588
  */
574
589
  interface SessionCostUsage {
@@ -688,8 +703,8 @@ interface ContextHeaders {
688
703
  }
689
704
  //#endregion
690
705
  //#region src/host/index.d.ts
691
- declare const name = "dsh-context";
692
- declare const inject: string[];
693
- declare function apply(ctx: Context, config: Config): void;
706
+ export declare const name = "dsh-context";
707
+ export declare const inject: string[];
708
+ export declare function apply(ctx: Context, config: Config): void;
694
709
  //#endregion
695
- export { type Category, Config, type ContextEventRecord, type ContextHeaders, type ContextTimeline, type ContextTimelineDetail, type HeaderRecord, type HeaderTool, type HeadersState, type RequestRecord, type Snapshot, type SurfaceNode, type TimelineCounts, type TimelineLast, type TimelineState, apply, inject, name };
710
+ export type { Category, ContextEventRecord, ContextHeaders, ContextTimeline, ContextTimelineDetail, HeaderRecord, HeaderTool, HeadersState, RequestRecord, Snapshot, SurfaceNode, TimelineCounts, TimelineLast, TimelineState };
package/lib/index.js CHANGED
@@ -1340,6 +1340,13 @@ function accumulateCost(st, time, usage) {
1340
1340
  */
1341
1341
  /** The timing card's per-tool ranking cap: the busiest 16 names are kept. */
1342
1342
  const TOOL_TIMING_CAP = 16;
1343
+ /**
1344
+ * The interactive Q&A tool (`dsh-tool-ask-user`): its settled result IS the
1345
+ * user's answer, so it folds into the human-input tally alongside the user's
1346
+ * own messages. One result = one answer submission, however many questions
1347
+ * the prompt carried.
1348
+ */
1349
+ const ASK_USER_TOOL = "ask_user_question";
1343
1350
  /** The decode buckets of the generation split, in card order (see TimingTotals). */
1344
1351
  const DECODE_KINDS = [
1345
1352
  "reasoning",
@@ -1600,7 +1607,7 @@ function applyTimeline(state, event, bounds) {
1600
1607
  if (source.form === "notice" && typeof source.summary === "string" && source.summary !== "") rec.detail = source.summary;
1601
1608
  }
1602
1609
  s.events.push(rec);
1603
- }
1610
+ } else s.humanInputs = (s.humanInputs ?? 0) + 1;
1604
1611
  break;
1605
1612
  }
1606
1613
  case "tool/result": {
@@ -1613,6 +1620,7 @@ function applyTimeline(state, event, bounds) {
1613
1620
  const s = ensure();
1614
1621
  bumpDetailRev(s);
1615
1622
  const node = applySurface(s, event, event.type, data, toolMsg);
1623
+ if (node.tool === ASK_USER_TOOL) s.humanInputs = (s.humanInputs ?? 0) + 1;
1616
1624
  if (pendingEntry !== void 0) pushFileOps(s, opsOfCall({
1617
1625
  seq: event.seq,
1618
1626
  time: event.time,
@@ -1775,6 +1783,7 @@ function headFieldsOf(state) {
1775
1783
  },
1776
1784
  images: state.surface.reduce((n, node) => n + (node.imgs ?? 0), 0),
1777
1785
  toolCalls: state.surface.reduce((n, node) => node.cat === "tool" ? n + 1 : n, 0),
1786
+ humanInputs: state.humanInputs ?? 0,
1778
1787
  requests: [],
1779
1788
  events: [],
1780
1789
  nodes: [],
@@ -2387,6 +2396,7 @@ const contextTimelineSchema = z.object({
2387
2396
  current: currentSchema,
2388
2397
  images: z.number().int().nonnegative().optional(),
2389
2398
  toolCalls: z.number().int().nonnegative().optional(),
2399
+ humanInputs: z.number().int().nonnegative().optional(),
2390
2400
  counts: countsSchema.optional(),
2391
2401
  last: lastSchema.optional(),
2392
2402
  detailRev: z.number().int().nonnegative().optional(),
@@ -2437,6 +2447,7 @@ const timelineStateSchema = z.object({
2437
2447
  }).strict().optional(),
2438
2448
  archiveFloor: z.number().optional(),
2439
2449
  timing: timingTotalsSchema.optional(),
2450
+ humanInputs: z.number().int().nonnegative().optional(),
2440
2451
  stepStart: z.object({
2441
2452
  time: z.number(),
2442
2453
  firstToken: z.number().optional(),
@@ -2511,7 +2522,7 @@ function createContextTimelineDefinition(config, slim) {
2511
2522
  },
2512
2523
  init: () => createTimelineState(),
2513
2524
  apply: (state, event) => applyTimeline(state, event, bounds),
2514
- stateVersion: 15
2525
+ stateVersion: 16
2515
2526
  };
2516
2527
  }
2517
2528
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.49.4",
3
+ "version": "0.50.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": {
@@ -107,6 +107,7 @@
107
107
  "@deepseek-ai/dsh-session": "0.1.2-rc.1",
108
108
  "@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
109
109
  "@deepseek-ai/dsh-settings": "0.1.2-rc.1",
110
+ "@deepseek-ai/dsh-util-workspace-path": "0.1.5-rc.1",
110
111
  "@deepseek-ai/schemastery": "^3.18.2",
111
112
  "@stylistic/eslint-plugin": "^5.10.0",
112
113
  "@types/node": "^26.2.0",
@@ -117,12 +118,12 @@
117
118
  "husky": "^9.1.7",
118
119
  "jsdom": "^30.0.1",
119
120
  "lightningcss": "^1.33.0",
120
- "oxlint": "1.76.0",
121
+ "oxlint": "1.82.0",
121
122
  "oxlint-tsgolint": "7.0.2001",
122
123
  "playwright-core": "^1.62.1",
123
124
  "react": "^18.3.1",
124
125
  "react-dom": "^18.3.1",
125
- "tsdown": "^0.22.14",
126
+ "tsdown": "^0.23.0",
126
127
  "typescript": "^7.0.2",
127
128
  "vitest": "^4.1.11",
128
129
  "zod": "^4.4.3"