dsh-context 0.29.0 → 0.30.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
@@ -4,6 +4,7 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/dsh-context)](https://www.npmjs.com/package/dsh-context)
6
6
  [![GitHub stars](https://img.shields.io/github/stars/bowenliang123/dsh-context?style=social)](https://github.com/bowenliang123/dsh-context)
7
+ [![dshfind](https://dshfind.com/api/badge/bowenliang123/dsh-context)](https://dshfind.com/en/plugins/bowenliang123/dsh-context?ref=badge)
7
8
 
8
9
  **The best [DeepSeek Harness plugin](https://www.deepseek.com/harness/) for Agent's context insights and management.**
9
10
 
package/cordis.patch.yml CHANGED
@@ -1,10 +1,6 @@
1
- # dsh-context bundle patch layer.
2
- #
3
- # Installing this package into a profile (`dsh plugin --profile <name> add dsh-context`)
4
- # appends it to `dsh.profile.bundles`; this patch is then applied as that
5
- # bundle's layer. The row below loads the package's main entry as the host
6
- # half (a plain Cordis plugin) and, because the package declares `dsh.client`,
7
- # the web app also loads its `./client` bundle as the browser half.
1
+ # Bundle patch layer: the row loads the package's main entry as the host half
2
+ # (a plain Cordis plugin); the `dsh.client` declaration makes the web app load
3
+ # its ./client bundle as the browser half.
8
4
  - insert:
9
5
  - id: dsh-context
10
6
  name: dsh-context
package/lib/client.js CHANGED
@@ -132,6 +132,12 @@ window.__ModuleLoader__.load({
132
132
  "block.content": "内容",
133
133
  "block.result": "结果",
134
134
  "block.summary": "摘要",
135
+ "block.line": "{n} 行",
136
+ "block.lines": "{n} 行",
137
+ "call.ok": "正常",
138
+ "call.fail": "失败",
139
+ "call.exit": "exit {n}",
140
+ "node.failed": "工具执行失败",
135
141
  "attach.images": "图片附件",
136
142
  "attach.other": "其他内容",
137
143
  "attach.image": "图片",
@@ -272,6 +278,12 @@ window.__ModuleLoader__.load({
272
278
  "block.content": "Content",
273
279
  "block.result": "Result",
274
280
  "block.summary": "Summary",
281
+ "block.line": "1 line",
282
+ "block.lines": "{n} lines",
283
+ "call.ok": "OK",
284
+ "call.fail": "Failed",
285
+ "call.exit": "exit {n}",
286
+ "node.failed": "Tool execution failed",
275
287
  "attach.images": "Images",
276
288
  "attach.other": "Other content",
277
289
  "attach.image": "Image",
@@ -314,7 +326,6 @@ window.__ModuleLoader__.load({
314
326
  function setPendingConsume(sessionId, guard) {
315
327
  pendingConsume.set(sessionId, guard);
316
328
  }
317
- /** Take (and clear) the guard recorded by the open path, if any. */
318
329
  function takePendingConsume(sessionId) {
319
330
  const guard = pendingConsume.get(sessionId);
320
331
  if (guard !== void 0) pendingConsume.delete(sessionId);
@@ -514,7 +525,6 @@ window.__ModuleLoader__.load({
514
525
  function numOf(value) {
515
526
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
516
527
  }
517
- /** The usable (non-null object) entries of a delivered list; [] when the field is not a list. */
518
528
  function objectsOf(value) {
519
529
  if (!Array.isArray(value)) return [];
520
530
  return value.filter((v) => v !== null && typeof v === "object");
@@ -644,10 +654,6 @@ window.__ModuleLoader__.load({
644
654
  for (let i = headers.headers.length - 1; i >= 0; i--) if (headers.headers[i].seq < seq) return headers.headers[i];
645
655
  return null;
646
656
  }
647
- /**
648
- * Reconstruct the assembled surface for one step (`seq` = the request
649
- * record's seq) or the live surface (`seq` = null).
650
- */
651
657
  function assemble(data, headers, seq) {
652
658
  const live = seq === null;
653
659
  let nodes;
@@ -679,7 +685,6 @@ window.__ModuleLoader__.load({
679
685
  const ReactDOM = require("react-dom");
680
686
  //#endregion
681
687
  //#region src/client/components/nodes.tsx
682
- /** One-line preview of a surface node (shared by the node list and the Context browser's element rows). */
683
688
  function makeNodeText(kit) {
684
689
  const { t } = kit;
685
690
  return function nodeText(n) {
@@ -714,10 +719,7 @@ window.__ModuleLoader__.load({
714
719
  }
715
720
  //#endregion
716
721
  //#region src/client/format.ts
717
- /**
718
- * Number/time formatting for the UI. `fmt` uses the same k/M suffix style
719
- * everywhere (bars, details, stats); `fmtTime` renders a local HH:MM:SS.
720
- */
722
+ /** `fmt`: the k/M suffix style shared by bars/details/stats; `fmtTime`: local HH:MM:SS. */
721
723
  function fmt(n) {
722
724
  if (n === void 0 || n === null || isNaN(n)) return "—";
723
725
  const sign = n < 0 ? "-" : "";
@@ -767,7 +769,6 @@ window.__ModuleLoader__.load({
767
769
  const MIN_PIXELS = 147456;
768
770
  const floorDiv = (a, b) => Math.floor(a / b);
769
771
  const ceilDiv = (a, b) => Math.floor((a + b - 1) / b);
770
- /** Token count of one patch grid (rows×cols) under the v4 layout rule. */
771
772
  function gridTokens(rows, cols) {
772
773
  let n = rows * (cols + 1) + 2;
773
774
  if (rows % 2 === 1) n += cols + 1;
@@ -849,10 +850,8 @@ window.__ModuleLoader__.load({
849
850
  return safeResize(h, w, paddedHeight, paddedWidth);
850
851
  }
851
852
  /**
852
- * Estimate the tokens one image consumes in a DeepSeek vision request, from
853
- * its pixel dimensions. Returns null for non-positive/non-finite dimensions
854
- * or when the official iteration fails to converge — callers fall back to
855
- * the generic structural price.
853
+ * Estimate the tokens one image consumes in a DeepSeek vision request from its pixel dimensions. Returns null for non-positive/non-finite
854
+ * dimensions or when the official iteration fails to converge — callers fall back to the generic structural price.
856
855
  */
857
856
  function estimateImageTokens(width, height) {
858
857
  if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
@@ -871,11 +870,9 @@ window.__ModuleLoader__.load({
871
870
  //#endregion
872
871
  //#region src/client/components/images.tsx
873
872
  /**
874
- * Narrow an unknown content block to a durable image reference. Accepts both
875
- * raw message blocks (`{ type: 'image', attachment }`) and the conversation
876
- * snapshot's assistant blocks (`{ kind: 'image', attachment }`); everything
877
- * else returns null. Lenient on the ref's optional facts (name/bytes/dims) —
878
- * `resolveImage` reads only `attachmentId`.
873
+ * Narrow an unknown content block to a durable image ref: accepts both raw message blocks (`{ type: 'image', attachment }`) and the
874
+ * snapshot's assistant blocks (`{ kind: 'image', attachment }`); everything else null. Lenient on the optional facts — resolveImage reads
875
+ * only attachmentId.
879
876
  */
880
877
  function imageRefOf(block) {
881
878
  if (block === null || typeof block !== "object") return null;
@@ -901,12 +898,9 @@ window.__ModuleLoader__.load({
901
898
  };
902
899
  }
903
900
  /**
904
- * Document-level original-image preview — the chat history's ImageLightbox
905
- * recipe (dsh ui-attachment, which the browser module table does not seed)
906
- * ported onto the plugin's lc-* classes: body portal so a transformed or
907
- * filtered ancestor cannot trap the fixed backdrop, blurred mask layer,
908
- * contain-fit image, circular close control, Escape/mask close, and focus
909
- * restored to the opener on unmount.
901
+ * Document-level original-image preview — the chat history's ImageLightbox recipe (dsh ui-attachment, which the browser module table does
902
+ * not seed) ported onto the plugin's lc-* classes: body portal (a transformed/filtered ancestor cannot trap the fixed backdrop), blurred
903
+ * mask, contain-fit image, circular close, Escape/mask close, focus restored to the opener.
910
904
  */
911
905
  function AttachmentLightbox(props) {
912
906
  const { src, alt, labels, onClose } = props;
@@ -946,13 +940,9 @@ window.__ModuleLoader__.load({
946
940
  }, /* @__PURE__ */ React.createElement(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 16 }))), document.body);
947
941
  }
948
942
  /**
949
- * One attachment card: the WHOLE card is the click target — a 64px cover
950
- * tile beside a metadata column (display name, then one labeled row per
951
- * known fact: Raw, the pre-normalization raster dsh 0.1.1 records when
952
- * normalization reduced the image; Sent, the normalized raster the model
953
- * receives, with its stored byte size; and the estimated provider-billed
954
- * tokens). Clicking opens the chat-style lightbox preview (load failures
955
- * retry on click instead). Unknown facts leave no row behind.
943
+ * One attachment card, the WHOLE card the click target: 64px cover tile + metadata column Raw (the pre-normalization raster dsh records
944
+ * when normalization reduced the image), Sent (the normalized raster the model receives, with byte size), estimated provider-billed tokens.
945
+ * Click opens the chat-style lightbox; load failures retry on click; unknown facts leave no row.
956
946
  */
957
947
  function makeImageCard(kit) {
958
948
  const { t, fmt } = kit;
@@ -1066,7 +1056,6 @@ window.__ModuleLoader__.load({
1066
1056
  }
1067
1057
  //#endregion
1068
1058
  //#region src/client/components/browser.tsx
1069
- /** Flatten the `anyOf` / `oneOf` alternation into one displayable string. */
1070
1059
  function unionTypesOf(p) {
1071
1060
  const branches = [];
1072
1061
  if (Array.isArray(p.anyOf)) branches.push(...p.anyOf);
@@ -1076,12 +1065,6 @@ window.__ModuleLoader__.load({
1076
1065
  for (const b of branches) if (b !== null && typeof b === "object") parts.push(typeOf(b));
1077
1066
  return parts.length > 0 ? parts.join(" | ") : null;
1078
1067
  }
1079
- /**
1080
- * Derive a short, human-readable type label from a JSON Schema fragment.
1081
- * Arrays show their element type (`array<number>`); unions fold into
1082
- * `a | b`; enums collapse into `(enum)`. Returns `unknown` when the schema
1083
- * carries no usable signal — the row falls back to its description then.
1084
- */
1085
1068
  function typeOf(p) {
1086
1069
  const u = unionTypesOf(p);
1087
1070
  if (u !== null) return u;
@@ -1103,9 +1086,8 @@ window.__ModuleLoader__.load({
1103
1086
  return "unknown";
1104
1087
  }
1105
1088
  /**
1106
- * Locate the parameter-bearing object inside a raw tool schema. Producers
1107
- * may nest it under `parameters`, `input_schema`, `inputSchema`, or hand
1108
- * the schema as the bare JSON Schema (when `type === 'object'`).
1089
+ * Tool schemas nest parameters under `parameters`, `input_schema`, or `inputSchema` (producer-dependent), or bare when `type === 'object'`
1090
+ * `{type:'object', properties}` at the root is itself the parameter object.
1109
1091
  */
1110
1092
  function paramsOf(schema) {
1111
1093
  if (schema === null || typeof schema !== "object") return null;
@@ -1116,33 +1098,19 @@ window.__ModuleLoader__.load({
1116
1098
  if (s.type === "object" && s.properties !== void 0 && typeof s.properties === "object") return s;
1117
1099
  return null;
1118
1100
  }
1119
- /**
1120
- * One row of the parsed parameter table — name (mono-styled, with a
1121
- * required chip), short type label, and the description on its own line
1122
- * (so a long blurb never breaks the name/type rhythm).
1123
- */
1124
1101
  function ParamRow(props) {
1125
1102
  const typeLabel = typeOf(props.schema);
1126
1103
  const desc = props.schema.description;
1127
1104
  return /* @__PURE__ */ React.createElement("div", { className: "lc-ts-param-row" }, /* @__PURE__ */ React.createElement("span", { className: "lc-ts-param-name" }, props.name), /* @__PURE__ */ React.createElement("span", { className: "lc-ts-param-type" }, typeLabel), /* @__PURE__ */ React.createElement("span", { className: props.required ? "lc-ts-param-req" : "lc-ts-param-req-off" }, props.required ? "✓" : "·"), typeof desc === "string" && desc !== "" ? /* @__PURE__ */ React.createElement("span", { className: "lc-ts-param-desc" }, desc) : null);
1128
1105
  }
1129
1106
  /**
1130
- * Section — the ONE detail chrome of the Context browser: a labeled card
1131
- * (`lc-ts-card`) whose head carries the section title on the left and the
1132
- * extras on the right (a count badge and/or the Raw/MD switch), with the
1133
- * body below. Every expanded element renders as a stack of these — prose,
1134
- * tool calls, parameter rows, image grids, and raw JSON all share the same
1135
- * frame, so the reader scans one repeating anatomy instead of a different
1136
- * layout per content kind.
1107
+ * Section — the ONE detail chrome of the browser: every expanded element is a stack of these (labeled head + body), so the reader scans one
1108
+ * repeating anatomy per content kind.
1137
1109
  */
1138
1110
  function Section(props) {
1139
- return /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card-head" }, /* @__PURE__ */ React.createElement("b", { className: props.labelClass }, props.label), props.actions ?? null, props.count !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-count" }, props.count) : null), props.children);
1111
+ const right = props.actions !== void 0 || props.meta !== void 0;
1112
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-ts-card-head" }, /* @__PURE__ */ React.createElement("b", { className: props.labelClass }, props.label), right ? /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-right" }, props.meta ?? null, props.actions ?? null) : null, props.count !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-count" }, props.count) : null), props.children);
1140
1113
  }
1141
- /**
1142
- * TextSection — a prose Section (system prompt, message/injection/summary
1143
- * body, thinking, answer, tool description): the head's right edge carries
1144
- * the Raw/MD switch and the body follows the per-card mode.
1145
- */
1146
1114
  function TextSection(props) {
1147
1115
  const { rich } = props;
1148
1116
  const [mode, setMode] = rich.useRichMode();
@@ -1151,21 +1119,19 @@ window.__ModuleLoader__.load({
1151
1119
  actions: /* @__PURE__ */ React.createElement(rich.RichSwitch, {
1152
1120
  mode,
1153
1121
  onPick: setMode
1154
- })
1122
+ }),
1123
+ meta: props.meta
1155
1124
  }, /* @__PURE__ */ React.createElement(rich.RichText, {
1156
1125
  text: props.text,
1157
1126
  mode
1158
1127
  }));
1159
1128
  }
1160
- /** RawSection — a dimmed text Section without a view switch (structured payloads: tool results, raw JSON). */
1161
1129
  function RawSection(props) {
1162
1130
  return /* @__PURE__ */ React.createElement(Section, { label: props.label }, /* @__PURE__ */ React.createElement("pre", { className: "lc-ts-desc-body lc-br-dim" }, props.text));
1163
1131
  }
1164
1132
  /**
1165
- * The full body of one expanded tool row: description, a parsed parameter
1166
- * table (when the schema carries one), and the raw JSON behind a toggle.
1167
- * Owns its own open/closed state for the JSON so two expanded tools stay
1168
- * independent.
1133
+ * Full tool-row body: description, parsed parameter table (when the schema carries one), raw JSON behind a per-row toggle — the JSON open
1134
+ * state is per-row so two expanded tools stay independent.
1169
1135
  */
1170
1136
  function ToolSchema(props) {
1171
1137
  const { rich } = props;
@@ -1210,14 +1176,9 @@ window.__ModuleLoader__.load({
1210
1176
  }, (jsonOpen ? "▾ " : "▸ ") + (jsonOpen ? props.labels.hide : props.labels.show)), jsonOpen ? /* @__PURE__ */ React.createElement("pre", { className: "lc-ts-desc-body lc-br-dim" }, schemaJson) : null) : null);
1211
1177
  }
1212
1178
  /**
1213
- * Render one message's content blocks as a uniform Section stack. Both
1214
- * block vocabularies normalize here raw durable blocks (`type`: text /
1215
- * reasoning / tool-call / tool-result / image, arguments) and the
1216
- * conversation snapshot's assistant blocks (`kind`: text / reasoning /
1217
- * tool-call / image, argsRaw). Consecutive images group into one grid
1218
- * section; `richable` bodies (messages, injections) carry the Raw/MD
1219
- * switch while tool-result payloads stay raw (structured data, not prose)
1220
- * and dim; nested tool-result blocks flatten into the same flow.
1179
+ * Both block vocabularies normalize here — raw durable blocks (`type`: text/reasoning/tool-call/tool-result/image) and snapshot assistant
1180
+ * blocks (`kind`: text/reasoning/tool-call/image, argsRaw). Consecutive images group into one grid; tool-result payloads carry the Raw/MD
1181
+ * switch + line count; nested tool-result blocks flatten into the same flow.
1221
1182
  */
1222
1183
  function BlocksBody(props) {
1223
1184
  const { rich, img, labels } = props;
@@ -1248,15 +1209,12 @@ window.__ModuleLoader__.load({
1248
1209
  const blockKind = blk !== null ? typeof blk.type === "string" ? blk.type : typeof blk.kind === "string" ? blk.kind : "" : "";
1249
1210
  if ((blockKind === "text" || blockKind === "reasoning") && typeof blk?.text === "string") {
1250
1211
  const label = blockKind === "reasoning" ? labels.thinking : props.textLabel;
1251
- out.push(props.richable ? /* @__PURE__ */ React.createElement(TextSection, {
1212
+ out.push(/* @__PURE__ */ React.createElement(TextSection, {
1252
1213
  key: out.length,
1253
1214
  label,
1254
1215
  text: blk.text,
1255
- rich
1256
- }) : /* @__PURE__ */ React.createElement(RawSection, {
1257
- key: out.length,
1258
- label,
1259
- text: blk.text
1216
+ rich,
1217
+ meta: props.textLabel === labels.result ? /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-meta" }, labels.lines(blk.text.split("\n").length)) : void 0
1260
1218
  }));
1261
1219
  continue;
1262
1220
  }
@@ -1289,11 +1247,6 @@ window.__ModuleLoader__.load({
1289
1247
  flushImages();
1290
1248
  return /* @__PURE__ */ React.createElement(React.Fragment, null, out);
1291
1249
  }
1292
- /**
1293
- * Parse a call's raw argument payload into an object; null when the payload
1294
- * is missing, malformed, or not a JSON object (callers fall back to the raw
1295
- * text).
1296
- */
1297
1250
  function parseArgs(raw) {
1298
1251
  if (typeof raw !== "string" || raw === "") return null;
1299
1252
  try {
@@ -1304,11 +1257,8 @@ window.__ModuleLoader__.load({
1304
1257
  }
1305
1258
  }
1306
1259
  /**
1307
- * The one-line summary a call's arguments carry about the call: a
1308
- * bash-style `description` says what it does; path-taking tools (edit /
1309
- * read / write) name their target via `file_path`/`path`/`filePath`. Null
1310
- * when the arguments hold neither — the row then falls back to the generic
1311
- * label.
1260
+ * A call's preview: bash-style `description`, or the target path (`file_path`/`path`/`filePath`) for edit/read/write tools; null when the
1261
+ * arguments hold neither.
1312
1262
  */
1313
1263
  function summaryIn(args) {
1314
1264
  if (args === null) return null;
@@ -1323,18 +1273,24 @@ window.__ModuleLoader__.load({
1323
1273
  }
1324
1274
  return null;
1325
1275
  }
1326
- /**
1327
- * The summary of a tool result's own call — the collapsed row's preview.
1328
- * Null when the call summarizes to nothing.
1329
- */
1330
1276
  function callSummaryOf(conv) {
1331
1277
  return summaryIn(parseArgs(conv?.call?.argsRaw));
1332
1278
  }
1333
1279
  /**
1334
- * The first call summary inside an assistant message's tool-call blocks —
1335
- * the collapsed-row preview for a text-less assistant turn. Null when no
1336
- * block summarizes.
1280
+ * The dsh `[exit code: N]` marker across the payload's direct text blocks — surfaced only on the FAILED run-state pill, the one place the
1281
+ * number matters.
1337
1282
  */
1283
+ function exitCodeOf(conv) {
1284
+ if (conv === void 0 || !Array.isArray(conv.content)) return null;
1285
+ for (const b of conv.content) {
1286
+ if (b === null || typeof b !== "object") continue;
1287
+ const blk = b;
1288
+ if (typeof blk.text !== "string") continue;
1289
+ const m = blk.text.match(/\[exit code:\s*(\d+)\]/);
1290
+ if (m !== null) return Number(m[1]);
1291
+ }
1292
+ return null;
1293
+ }
1338
1294
  function blockSummaryOf(conv) {
1339
1295
  if (conv === void 0 || !Array.isArray(conv.blocks)) return null;
1340
1296
  for (const b of conv.blocks) {
@@ -1345,37 +1301,23 @@ window.__ModuleLoader__.load({
1345
1301
  }
1346
1302
  return null;
1347
1303
  }
1348
- /**
1349
- * One tool call as a Section, mirroring the tool-definition parameter
1350
- * card: the head names the call target (mono, with the parsed argument
1351
- * count), the body lists the arguments as name/value rows. Arguments that
1352
- * are not a parseable JSON object fall back to the raw payload inside the
1353
- * same card. Shared by assistant tool-call blocks (`→`) and the call half
1354
- * of a tool result (`←`).
1355
- */
1356
1304
  function ToolCallCard(props) {
1357
1305
  const args = React.useMemo(() => parseArgs(props.argsRaw), [props.argsRaw]);
1358
1306
  return /* @__PURE__ */ React.createElement(Section, {
1359
1307
  label: (props.arrow ?? "→") + " " + props.name,
1360
1308
  labelClass: "lc-ts-call-name",
1361
- count: args !== null ? Object.keys(args).length : void 0
1309
+ meta: props.status
1362
1310
  }, args !== null ? Object.keys(args).map((k) => /* @__PURE__ */ React.createElement(CallArgRow, {
1363
1311
  key: k,
1364
1312
  name: k,
1365
1313
  value: args[k]
1366
1314
  })) : typeof props.argsRaw === "string" && props.argsRaw !== "" ? /* @__PURE__ */ React.createElement("pre", { className: "lc-ts-desc-body lc-br-dim" }, props.argsRaw) : null);
1367
1315
  }
1368
- /** One parsed call argument: the name on the left, the value on the right. */
1369
1316
  function CallArgRow(props) {
1370
1317
  const v = props.value;
1371
1318
  const text = typeof v === "string" ? v : v === void 0 ? "" : JSON.stringify(v);
1372
1319
  return /* @__PURE__ */ React.createElement("div", { className: "lc-ts-arg-row" }, /* @__PURE__ */ React.createElement("span", { className: "lc-ts-param-name" }, props.name), /* @__PURE__ */ React.createElement("span", { className: "lc-ts-arg-val" }, text));
1373
1320
  }
1374
- /**
1375
- * The actual content of one surface element, joined from the conversation
1376
- * snapshot — rendered as a uniform Section stack (see BlocksBody); the
1377
- * element row's open body wraps the stack in `lc-br-content`.
1378
- */
1379
1321
  function NodeContent(props) {
1380
1322
  const { node, conv, rich, img, labels } = props;
1381
1323
  if (conv === void 0) {
@@ -1394,18 +1336,22 @@ window.__ModuleLoader__.load({
1394
1336
  img,
1395
1337
  labels
1396
1338
  });
1397
- if (conv.kind === "tool-result") return /* @__PURE__ */ React.createElement(React.Fragment, null, conv.call != null ? /* @__PURE__ */ React.createElement(ToolCallCard, {
1398
- arrow: "←",
1399
- name: conv.call.name,
1400
- argsRaw: conv.call.argsRaw
1401
- }) : null, Array.isArray(conv.content) ? /* @__PURE__ */ React.createElement(BlocksBody, {
1402
- blocks: conv.content,
1403
- richable: false,
1404
- textLabel: labels.result,
1405
- rich,
1406
- img,
1407
- labels
1408
- }) : null);
1339
+ if (conv.kind === "tool-result") {
1340
+ const err = node.err === true || conv.isError === true;
1341
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, conv.call != null ? /* @__PURE__ */ React.createElement(ToolCallCard, {
1342
+ arrow: "←",
1343
+ name: conv.call.name,
1344
+ argsRaw: conv.call.argsRaw,
1345
+ status: labels.callState(err, err ? exitCodeOf(conv) : null)
1346
+ }) : null, Array.isArray(conv.content) ? /* @__PURE__ */ React.createElement(BlocksBody, {
1347
+ blocks: conv.content,
1348
+ richable: false,
1349
+ textLabel: labels.result,
1350
+ rich,
1351
+ img,
1352
+ labels
1353
+ }) : null);
1354
+ }
1409
1355
  if (conv.kind === "compaction") return typeof conv.summary === "string" && conv.summary !== "" ? /* @__PURE__ */ React.createElement(TextSection, {
1410
1356
  label: labels.summary,
1411
1357
  text: conv.summary,
@@ -1421,19 +1367,16 @@ window.__ModuleLoader__.load({
1421
1367
  });
1422
1368
  return /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, props.hint);
1423
1369
  }
1424
- /** Group an assembled surface's nodes by category (for per-category counts). */
1425
1370
  function byCatOf(asm) {
1426
1371
  const m = {};
1427
1372
  for (const n of asm.nodes) (m[n.cat] ??= []).push(n);
1428
1373
  return m;
1429
1374
  }
1430
- /** Count one category's elements in an assembled surface (header cats included). */
1431
1375
  function countOf(asm, c) {
1432
1376
  if (c === "system") return asm.header !== null && asm.header.system !== void 0 ? 1 : 0;
1433
1377
  if (c === "tools") return asm.header !== null ? asm.header.tools.length : 0;
1434
1378
  return byCatOf(asm)[c]?.length ?? 0;
1435
1379
  }
1436
- /** The last request of `turn` in a seq-ordered timeline, or null. */
1437
1380
  function lastOfTurn(requests, turn) {
1438
1381
  for (let i = requests.length - 1; i >= 0; i--) if ((requests[i].turn ?? 0) === turn) return requests[i];
1439
1382
  return null;
@@ -1534,9 +1477,11 @@ window.__ModuleLoader__.load({
1534
1477
  const toggleElem = (key) => {
1535
1478
  setOpenElem(openElem === key ? null : key);
1536
1479
  };
1537
- /** One expandable element row (preview line; the open body is the
1538
- * uniform indented section stack `lc-br-content`). */
1539
- const elemRow = (key, tag, preview, tokens, time, body) => {
1480
+ /**
1481
+ * Expandable element row; `err` rows carry the red run-state dot right after the chevron (the chat's failed-tool marker) so a failed
1482
+ * result scans while collapsed.
1483
+ */
1484
+ const elemRow = (key, tag, preview, tokens, time, body, err = false) => {
1540
1485
  const open = openElem === key;
1541
1486
  return /* @__PURE__ */ React.createElement("div", {
1542
1487
  key,
@@ -1547,7 +1492,10 @@ window.__ModuleLoader__.load({
1547
1492
  onClick: () => {
1548
1493
  toggleElem(key);
1549
1494
  }
1550
- }, /* @__PURE__ */ React.createElement("span", { className: "lc-br-chev" + (open ? " lc-br-chev-on" : "") }, "▸"), tag !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-br-tag" }, tag) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-br-preview" }, preview), time !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-br-time" }, fmtTime(time)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-br-tokens" }, "≈" + fmt(tokens))), open ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, body) : null);
1495
+ }, /* @__PURE__ */ React.createElement("span", { className: "lc-br-chev" + (open ? " lc-br-chev-on" : "") }, "▸"), err ? /* @__PURE__ */ React.createElement("span", {
1496
+ className: "lc-br-err-dot",
1497
+ title: t("node.failed")
1498
+ }) : null, tag !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-br-tag" }, tag) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-br-preview" }, preview), time !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-br-time" }, fmtTime(time)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-br-tokens" }, "≈" + fmt(tokens))), open ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-content" }, body) : null);
1551
1499
  };
1552
1500
  const catBody = (c) => {
1553
1501
  if (c === "system") {
@@ -1579,17 +1527,18 @@ window.__ModuleLoader__.load({
1579
1527
  });
1580
1528
  }
1581
1529
  return (byCat[c] ?? []).slice().reverse().map((n) => {
1530
+ const conv = bySeq.get(n.seq);
1531
+ const rowErr = n.cat === "tool" && (n.err === true || conv !== void 0 && conv.isError === true);
1582
1532
  let tag = null;
1583
1533
  let preview = nodeText(n);
1584
1534
  if (n.cat === "tool") {
1585
- tag = n.skill ? t("node.skillTag", { name: n.skill }) : (n.tool ?? "?") + (n.err ? " ⚠" : "");
1586
- preview = callSummaryOf(bySeq.get(n.seq)) ?? t("node.toolResult") + (n.err ? " ⚠" : "");
1535
+ tag = n.skill ? t("node.skillTag", { name: n.skill }) : n.tool ?? "?";
1536
+ preview = callSummaryOf(conv) ?? t("node.toolResult");
1587
1537
  } else if (n.cat === "assistant" && Array.isArray(n.calls) && n.calls.length > 0) {
1588
1538
  tag = n.calls.join(" › ");
1589
- preview = (n.text !== void 0 && n.text !== "" ? n.text : null) ?? blockSummaryOf(bySeq.get(n.seq)) ?? t("node.empty");
1590
- } else if (n.cat === "assistant" && (n.text === void 0 || n.text === "")) preview = blockSummaryOf(bySeq.get(n.seq)) ?? preview;
1539
+ preview = (n.text !== void 0 && n.text !== "" ? n.text : null) ?? blockSummaryOf(conv) ?? t("node.empty");
1540
+ } else if (n.cat === "assistant" && (n.text === void 0 || n.text === "")) preview = blockSummaryOf(conv) ?? preview;
1591
1541
  else if (n.cat === "user") {
1592
- const conv = bySeq.get(n.seq);
1593
1542
  const imgCount = conv !== void 0 && Array.isArray(conv.content) ? conv.content.filter((b) => imageRefOf(b) !== null).length : 0;
1594
1543
  if (imgCount > 0 && openElem !== `n${n.seq}`) tag = t("attach.image") + (imgCount > 1 ? " ×" + String(imgCount) : "");
1595
1544
  } else if (n.cat === "inject" && !n.skill) {
@@ -1598,7 +1547,7 @@ window.__ModuleLoader__.load({
1598
1547
  }
1599
1548
  return elemRow(`n${n.seq}`, tag, preview, n.tokens, n.time, /* @__PURE__ */ React.createElement(NodeContent, {
1600
1549
  node: n,
1601
- conv: bySeq.get(n.seq),
1550
+ conv,
1602
1551
  rich,
1603
1552
  img: {
1604
1553
  Card: ImageCard,
@@ -1611,10 +1560,12 @@ window.__ModuleLoader__.load({
1611
1560
  result: t("block.result"),
1612
1561
  summary: t("block.summary"),
1613
1562
  images: t("attach.images"),
1614
- other: t("attach.other")
1563
+ other: t("attach.other"),
1564
+ lines: (n) => t(n === 1 ? "block.line" : "block.lines", { n }),
1565
+ callState: (err, exit) => /* @__PURE__ */ React.createElement("span", { className: "lc-ts-call-state " + (err ? "lc-ts-call-err" : "lc-ts-call-ok") }, /* @__PURE__ */ React.createElement("i", null), err ? t("call.fail") + (exit !== null ? " · " + t("call.exit", { n: exit }) : "") : t("call.ok"))
1615
1566
  },
1616
- hint: bySeq.get(n.seq) === void 0 && awaiting ? t("browser.loading") : t("browser.noContent")
1617
- }));
1567
+ hint: conv === void 0 && awaiting ? t("browser.loading") : t("browser.noContent")
1568
+ }), rowErr);
1618
1569
  });
1619
1570
  };
1620
1571
  return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("browser.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-br-hint" }, t("browser.deltaHint")), /* @__PURE__ */ React.createElement("select", {
@@ -1669,12 +1620,9 @@ window.__ModuleLoader__.load({
1669
1620
  //#endregion
1670
1621
  //#region src/client/components/stackedBar.tsx
1671
1622
  /**
1672
- * DSH's automatic compaction trigger as a fraction of the routed context
1673
- * window the default `thresholdRatio` of `@deepseek-ai/dsh-compaction-basic`
1674
- * (it compacts at step boundaries once `floor(contextWindow × ratio)` is
1675
- * reached). DSH does not publish the configured ratio to plugins or clients,
1676
- * so the UI mirrors the default here; deployments that tune `thresholdRatio`
1677
- * / `modelPolicies` can adjust it if they want the reserve band to match.
1623
+ * Mirror of dsh-compaction-basic's default `thresholdRatio` (0.8): it compacts at step boundaries once `floor(contextWindow × ratio)` is
1624
+ * reached; DSH does not publish the configured ratio to plugins/clients, so the reserve band mirrors the default deployments tuning
1625
+ * `thresholdRatio`/`modelPolicies` should adjust it to match.
1678
1626
  */
1679
1627
  const AUTO_COMPACT_RATIO = .8;
1680
1628
  function makeStackedBar(kit) {
@@ -1945,7 +1893,6 @@ window.__ModuleLoader__.load({
1945
1893
  }
1946
1894
  //#endregion
1947
1895
  //#region src/client/components/settingsCard.tsx
1948
- /** One preference row: label + selector pill opening the framework Menu. */
1949
1896
  function PrefRow(props) {
1950
1897
  const [open, setOpen] = React.useState(false);
1951
1898
  const active = props.options.find((o) => o.id === props.value)?.label ?? props.value;
@@ -2025,7 +1972,6 @@ window.__ModuleLoader__.load({
2025
1972
  }
2026
1973
  //#endregion
2027
1974
  //#region src/client/settings.ts
2028
- /** Read the known preferences out of a raw section; unknown/absent fields drop out. */
2029
1975
  function prefsOf(value) {
2030
1976
  if (value === null || typeof value !== "object") return {};
2031
1977
  const v = value;
@@ -2093,7 +2039,6 @@ window.__ModuleLoader__.load({
2093
2039
  model: "⇄",
2094
2040
  mode: "⇄"
2095
2041
  };
2096
- /** Build the event text helpers bound to the translate function. */
2097
2042
  function makeEventText(t) {
2098
2043
  function eventLabel(ev) {
2099
2044
  if (ev.kind === "compaction") return t("ev.compaction", { n: ev.count || 0 });
@@ -2110,11 +2055,8 @@ window.__ModuleLoader__.load({
2110
2055
  return label;
2111
2056
  }
2112
2057
  /**
2113
- * Where this event sits in the request timeline, as a label or null.
2114
- * Boundary events (compaction/prune) show the GAP they sit in: same-turn
2115
- * "Step 2→3", cross-turn "Turn 50 · Step 8 → Turn 51 · Step 1". Injections
2116
- * and model switches belong to one request and keep the single point.
2117
- * Events with no following request (in flight) stay unlabeled.
2058
+ * Where this event sits in the timeline: boundary events (compaction/prune) label the GAP they sit in — same-turn 'Turn 2 · Step 3→4',
2059
+ * cross-turn 'Turn 50 · Step 8 Turn 51 · Step 1'; other kinds keep their single point; no turn/step (in flight) → null.
2118
2060
  */
2119
2061
  function eventAt(ev) {
2120
2062
  if (ev.kind === "compaction" || ev.kind === "prune") {
@@ -2183,20 +2125,14 @@ window.__ModuleLoader__.load({
2183
2125
  };
2184
2126
  }
2185
2127
  const PLUGIN_REPO = "https://github.com/bowenliang123/dsh-context";
2186
- /** Short `owner/repo` form of the GitHub URL, for display only. */
2187
2128
  const PLUGIN_REPO_SHORT = PLUGIN_REPO.replace(/^https?:\/\/github\.com\//, "");
2188
2129
  //#endregion
2189
2130
  //#region src/client/latestVersion.ts
2190
2131
  /**
2191
- * Latest published version of this plugin on the npm registry the one
2192
- * live check the Plugin info card performs. Fetched lazily on the first
2193
- * card mount and cached with a 1-hour TTL, so the registry is hit at most
2194
- * once per hour per page. Every failure mode (offline, CSP-blocked,
2195
- * non-200, malformed body) narrows to null = no upgrade hint, so the card
2196
- * silently keeps its static version.
2132
+ * One live check for the Plugin-info card: lazy with 1h TTL; every failure
2133
+ * mode narrows to null so the card silently keeps its static version.
2197
2134
  */
2198
2135
  const REGISTRY_URL = "https://registry.npmjs.org/dsh-context/latest";
2199
- /** How long a registry result stays fresh before the next check refetches. */
2200
2136
  const TTL_MS = 36e5;
2201
2137
  let cached = null;
2202
2138
  function fetchLatestVersion() {
@@ -2231,7 +2167,7 @@ window.__ModuleLoader__.load({
2231
2167
  return function PluginInfo() {
2232
2168
  const [latest, setLatest] = React.useState(null);
2233
2169
  React.useEffect(() => {
2234
- if ("0.29.0".includes("-dev")) return;
2170
+ if ("0.30.0".includes("-dev")) return;
2235
2171
  let on = true;
2236
2172
  fetchLatestVersion().then((v) => {
2237
2173
  if (on && v) setLatest(v);
@@ -2240,8 +2176,8 @@ window.__ModuleLoader__.load({
2240
2176
  on = false;
2241
2177
  };
2242
2178
  }, []);
2243
- const update = latest !== null && isNewerVersion(latest, "0.29.0") ? latest : null;
2244
- const nameValue = ["dsh-context (v0.29.0)"];
2179
+ const update = latest !== null && isNewerVersion(latest, "0.30.0") ? latest : null;
2180
+ const nameValue = ["dsh-context (v0.30.0)"];
2245
2181
  if (update) nameValue.push(/* @__PURE__ */ React.createElement("span", {
2246
2182
  key: "update",
2247
2183
  className: "lc-pi-update"
@@ -2366,7 +2302,6 @@ window.__ModuleLoader__.load({
2366
2302
  }
2367
2303
  return any ? total : null;
2368
2304
  }
2369
- /** Short money format: two decimals at and above one unit, two significant digits below. */
2370
2305
  function formatCost(amount, currency) {
2371
2306
  return (currency === "cny" ? "¥" : "$") + (amount >= 1 ? amount.toFixed(2) : amount.toPrecision(2));
2372
2307
  }
@@ -2391,15 +2326,10 @@ window.__ModuleLoader__.load({
2391
2326
  //#endregion
2392
2327
  //#region src/client/components/statsBoard.tsx
2393
2328
  /**
2394
- * Cache-hit share of billed prompt-side input — same buckets as the harness's
2395
- * chat stats line below the input box (`cacheReadTokens` over the three
2396
- * disjoint billed buckets: uncached + reads + writes). Unlike that line's
2397
- * whole-percent rounding, this one TRUNCATES to two decimal places (cut, not
2398
- * round). Null when no input was billed. A tiny epsilon keeps a double stored
2399
- * a hair below a cent boundary (e.g. 80.00 as 79.9999999999…) from losing its
2400
- * last digit — with integer token counts no genuine value ever sits that close
2401
- * to a boundary, so the epsilon can only absorb float noise. Bucket reads go
2402
- * through numOf so a malformed payload degrades to a dash instead of NaN text.
2329
+ * Cache-hit share of billed prompt-side input — same three disjoint buckets as the harness chat line (`cacheReadTokens` over uncached +
2330
+ * reads + writes), but deliberately TRUNCATES to two decimals (cut, not round), unlike that line's integer rounding; null when nothing was
2331
+ * billed. The 1e-9 epsilon absorbs only float noise (integer token counts never sit that close to a boundary); `numOf` keeps malformed
2332
+ * payloads at a dash.
2403
2333
  */
2404
2334
  function cacheHitPercent(usage) {
2405
2335
  const uncached = numOf(usage.uncachedInputTokens);
@@ -2446,12 +2376,8 @@ window.__ModuleLoader__.load({
2446
2376
  //#endregion
2447
2377
  //#region src/client/components/trendChart.tsx
2448
2378
  /**
2449
- * Collapse a per-step request timeline into one bar per turn: each turn is
2450
- * represented by its LAST step's record (the context state when the turn
2451
- * finished), tagged with the number of steps the turn spans so the bar can
2452
- * keep the turn's column width and the detail can label it. Requests of one
2453
- * turn are consecutive in the log, so a run of equal turns is replaced by
2454
- * its final record.
2379
+ * 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
2380
+ * column width; the log keeps one turn's requests consecutive, so a run of equal turns collapses to its final record.
2455
2381
  */
2456
2382
  function aggregateByTurn(requests) {
2457
2383
  const out = [];
@@ -2475,10 +2401,8 @@ window.__ModuleLoader__.load({
2475
2401
  return out;
2476
2402
  }
2477
2403
  /**
2478
- * Attach each boundary event (compaction/prune) to the first request
2479
- * logged after it one entry per request index, for the marker above
2480
- * the bar and the range chip in the detail header. Shared by TrendChart
2481
- * and the detail panel so both show the SAME event for a request.
2404
+ * Attach each boundary event (compaction/prune) to the first request logged after it — one entry per index, for the ✂ marker and the detail
2405
+ * chip; shared with the detail panel so both show the SAME event.
2482
2406
  */
2483
2407
  function attachMarkers(requests, events) {
2484
2408
  const markers = new Array(requests.length);
@@ -2500,14 +2424,8 @@ window.__ModuleLoader__.load({
2500
2424
  const anchorOf = (req) => typeof req.prompt === "number" && req.prompt > 0 && req.total > 0 ? req.prompt / req.total : 1;
2501
2425
  const barTotalOf = (req) => typeof req.prompt === "number" && req.prompt > 0 ? req.prompt : req.total;
2502
2426
  /**
2503
- * Transform one request into its per-request incremental diff for the
2504
- * diff mode. Each category becomes |current previous| (the magnitude of
2505
- * change, stacked like the cumulative bars), `total` becomes the summed
2506
- * magnitude, and `net` keeps the signed change for the tooltip. The first
2507
- * request (no previous) diff from empty is zero, so the diff mode's scale
2508
- * is driven purely by inter-request changes rather than the initial
2509
- * cumulative baseline. Provider prompt/output are dropped: they are
2510
- * per-request values, not diffs.
2427
+ * Diff mode: each category becomes |current previous|, `total` the summed magnitude, `net` the signed change for the tooltip; the first
2428
+ * request diffs from zero so the scale is change-driven, and per-request provider prompt/output are dropped (they are not diffs).
2511
2429
  */
2512
2430
  const diffOf = (req, prev) => {
2513
2431
  const out = { ...req };
@@ -2577,6 +2495,17 @@ window.__ModuleLoader__.load({
2577
2495
  grp.count++;
2578
2496
  grp.span += req.stepCount ?? 1;
2579
2497
  }
2498
+ const turnOffsets = [];
2499
+ const turnWidths = [];
2500
+ {
2501
+ let x = 0;
2502
+ for (const grp of groups) {
2503
+ const w = grp.agg ? BAR_W : grp.span * 16 - BAR_GAP;
2504
+ turnOffsets.push(x);
2505
+ turnWidths.push(w);
2506
+ x += w + BAR_GAP;
2507
+ }
2508
+ }
2580
2509
  const scrollRef = React.useRef(null);
2581
2510
  const scrolledOnce = React.useRef(false);
2582
2511
  const lastGranRef = React.useRef(props.granularity);
@@ -2599,6 +2528,35 @@ window.__ModuleLoader__.load({
2599
2528
  right
2600
2529
  });
2601
2530
  };
2531
+ /**
2532
+ * Keep each turn label centered within its block's VISIBLE slice so it never scrolls out while any part of the block is on screen
2533
+ * (narrower-than-label blocks stay put); reads (offsetWidth) batch before writes (transform) to avoid layout thrash — out-of-view
2534
+ * blocks need no measurement.
2535
+ */
2536
+ const updateTurnLabels = (el) => {
2537
+ const labels = el.querySelectorAll(".lc-turn-label");
2538
+ const n = Math.min(labels.length, turnOffsets.length);
2539
+ const sl = el.scrollLeft;
2540
+ const vr = sl + el.clientWidth;
2541
+ const writes = [];
2542
+ for (let i = 0; i < n; i++) {
2543
+ const off = turnOffsets[i];
2544
+ const w = turnWidths[i];
2545
+ const visL = Math.max(off, sl);
2546
+ const visR = Math.min(off + w, vr);
2547
+ let dx = 0;
2548
+ if (visR > visL) {
2549
+ const lw = labels[i].offsetWidth;
2550
+ if (lw < w) {
2551
+ const center = (visL + visR) / 2 - off;
2552
+ dx = Math.min(Math.max(center, lw / 2), w - lw / 2) - w / 2;
2553
+ }
2554
+ }
2555
+ const next = dx !== 0 ? `translateX(${dx}px)` : "";
2556
+ if (labels[i].style.transform !== next) writes.push([labels[i], next]);
2557
+ }
2558
+ for (const [label, next] of writes) label.style.transform = next;
2559
+ };
2602
2560
  React.useLayoutEffect(() => {
2603
2561
  const el = scrollRef.current;
2604
2562
  if (el === null) return;
@@ -2619,6 +2577,7 @@ window.__ModuleLoader__.load({
2619
2577
  el.scrollLeft = el.scrollWidth;
2620
2578
  } else if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) el.scrollLeft = el.scrollWidth;
2621
2579
  updateEdges(el);
2580
+ updateTurnLabels(el);
2622
2581
  });
2623
2582
  const tipOf = (req) => {
2624
2583
  const head = req.stepCount !== void 0 && req.stepCount > 1 ? t("tip.turn", {
@@ -2641,6 +2600,7 @@ window.__ModuleLoader__.load({
2641
2600
  ref: scrollRef,
2642
2601
  onScroll: (e) => {
2643
2602
  updateEdges(e.currentTarget);
2603
+ updateTurnLabels(e.currentTarget);
2644
2604
  }
2645
2605
  }, edges.left ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-l" }) : null, /* @__PURE__ */ React.createElement("div", {
2646
2606
  className: "lc-chart",
@@ -2667,12 +2627,11 @@ window.__ModuleLoader__.load({
2667
2627
  }
2668
2628
  }, groups.map((grp, gi) => {
2669
2629
  const on = props.activeTurn === grp.turn;
2670
- const blockW = grp.agg ? BAR_W : grp.span * 16 - BAR_GAP;
2671
2630
  return /* @__PURE__ */ React.createElement("span", {
2672
2631
  key: `turn-${gi}`,
2673
2632
  className: "lc-turn" + (on ? " lc-turn-on" : ""),
2674
2633
  style: {
2675
- width: `${blockW}px`,
2634
+ width: `${turnWidths[gi]}px`,
2676
2635
  background: TURN_FILLS[gi % TURN_FILLS.length]
2677
2636
  },
2678
2637
  title: `T${grp.turn}`,
@@ -2682,7 +2641,7 @@ window.__ModuleLoader__.load({
2682
2641
  onClick: () => {
2683
2642
  props.onPickTurn(grp.turn);
2684
2643
  }
2685
- }, `T${grp.turn}`);
2644
+ }, /* @__PURE__ */ React.createElement("span", { className: "lc-turn-label" }, `T${grp.turn}`));
2686
2645
  }))), edges.right ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-r" }) : null);
2687
2646
  };
2688
2647
  }
@@ -2906,7 +2865,7 @@ window.__ModuleLoader__.load({
2906
2865
  }
2907
2866
  //#endregion
2908
2867
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles.css.mjs
2909
- const css = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-card-title{align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-stats{grid-template-columns:repeat(auto-fit,minmax(62px,1fr));gap:6px;display:grid}.lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}.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-l1);cursor:help;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{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;max-width:280px;color:var(--dsw-alias-label-primary);pointer-events:none;opacity:0;border-radius:6px;padding:6px 10px;font-size:12px;font-weight:400;line-height:1.5;transition:opacity .12s;position:absolute;top:calc(100% + 6px);left:0;box-shadow:0 2px 8px #0000002e}.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);font-size:12px;font-weight:400}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);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-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;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-settings-card{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;transition:border-color .16s,background .16s}.lc-settings-card:hover{border-color:var(--dsw-alias-label-dimmed)}.lc-settings-open,.lc-settings-open:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.lc-settings-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.lc-settings-headtext{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.lc-settings-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.lc-settings-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.lc-settings-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.lc-settings-open .lc-settings-chevron{transform:rotate(180deg)}.lc-settings-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 12px}.lc-settings-row{align-items:center;gap:8px;padding:8px 0;display:flex}.lc-settings-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:14px}.lc-settings-select{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.lc-settings-select:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.lc-settings-select:disabled{opacity:.5;cursor:default}.lc-settings-note{color:var(--dsw-alias-label-tertiary);margin:12px 0 4px;font-size:12px;line-height:1.5}.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-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);border-radius:5px;transition:opacity .12s;position:absolute;top:0;bottom:0;left:0}.lc-occupied-box-on{opacity:1}.lc-bar-tip{z-index:5;white-space:nowrap;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);pointer-events:none;opacity:0;border-radius:6px;padding:3px 8px;font-size:12px;transition:opacity .12s;position:absolute;bottom:calc(100% + 6px);transform:translate(-50%);box-shadow:0 2px 8px #0000002e}.lc-bar-tip-on{opacity:1}.lc-stacked>div{height:100%}.lc-stacked-seg{transition:filter .12s,opacity .12s}.lc-stacked-free{box-sizing:border-box;transition:box-shadow .12s,opacity .12s}.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;border-radius:6px;align-items:center;gap:5px;padding:1px 6px;transition:background-color .12s;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,.lc-node i{border-radius:2px;width:8px;height:8px;display:inline-block}.lc-chip i{transition:box-shadow .12s}.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)}.lc-tools{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;align-items:center;gap:6px;margin-top:10px;display:flex}.lc-tools-label,.lc-tools-more{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;padding:0;font-family:inherit;font-size:12px}.lc-tools-label:hover,.lc-tools-more:hover{text-decoration:underline}.lc-tool-chip{background:var(--dsw-alias-bg-layer-2);font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:0;border-radius:4px;padding:1px 7px;font-size:12px}.lc-tool-chip:hover{text-decoration:underline}.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-mid{top:69px}.lc-axis-bot{top:125px}.lc-chart-scroll{scrollbar-width:thin;flex:1;min-width:0;padding-bottom:8px;position:relative;overflow:auto hidden}.lc-chart-fade{pointer-events:none;z-index:2;width:26px;position:absolute;top:0;bottom:0}.lc-chart-fade-l{background:linear-gradient(to right, var(--dsw-alias-bg-layer-1), transparent);left:0}.lc-chart-fade-r{background:linear-gradient(to left, var(--dsw-alias-bg-layer-1), transparent);right:0}.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-l1);pointer-events:none;position:absolute;left:0;right:0}.lc-grid-top{top:18px}.lc-grid-mid{top:74px}.lc-bar{cursor:pointer;border-radius:2px;flex:none;align-items:flex-end;width:14px;height:100%;transition:opacity .12s,background-color .12s;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;white-space:nowrap;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);pointer-events:none;border-radius:6px;padding:3px 8px;font-size:12px;position:absolute;top:0;transform:translate(-50%);box-shadow:0 2px 8px #0000002e}.lc-bar:hover{background:var(--dsw-alias-bg-layer-2)}.lc-bar-hovered{outline:1px dashed var(--dsw-alias-brand-primary);outline-offset:1px}.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-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}.lc-turn{box-sizing:border-box;text-align:center;color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;cursor:pointer;border-radius:3px;flex:none;height:14px;font-size:10px;font-weight:600;line-height:14px;transition:filter .12s,opacity .12s;overflow:hidden}.lc-turn-on{filter:brightness(1.35);color:var(--dsw-alias-label-primary)}.lc-detail{border-top:1px solid var(--dsw-alias-border-l1);margin-top:12px;padding-top:12px}.lc-detail-head{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:6px 16px;margin-bottom:8px;display:flex}.lc-detail-head b{color:var(--dsw-alias-label-primary)}.lc-detail-marker{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-bg-layer-2);border-radius:6px;padding:1px 7px;font-size:11px}.lc-detail-head .lc-actual{color:var(--dsw-alias-state-success-primary)}.lc-detail-tag{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary);border-radius:4px;padding:0 6px;font-size:11px}.lc-detail-rows{grid-template-columns:1fr 1fr;gap:4px 24px;margin-top:10px;display:grid}.lc-detail-row{align-items:center;gap:8px;display:flex}.lc-detail-label{white-space:nowrap;min-width:70px;color:var(--dsw-alias-label-secondary)}.lc-bar-track{background:#8080802e;border-radius:3px;flex:1;height:5px;display:block;overflow:hidden}.lc-bar-fill{border-radius:3px;height:100%;display:block}.lc-detail-num{text-align:right;width:44px}.lc-detail-pct{text-align:right;width:34px;color:var(--dsw-alias-label-secondary)}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:280px}.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>.lc-card:first-child{flex:7 1 0;min-width:360px}.lc-head>.lc-card:last-child{flex:3 1 0;min-width:220px}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.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-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-events,.lc-nodes{flex-direction:column;gap:2px;max-height:320px;display:flex;overflow-y:auto}.lc-event{align-items:center;gap:8px;padding:3px 0;display:flex}.lc-event-icon{text-align:center;width:18px;color:var(--dsw-alias-state-warn-primary)}.lc-event-icon.lc-event-inject{color:#a855f7}.lc-event-icon.lc-event-model{color:var(--dsw-alias-brand-primary)}.lc-event-icon.lc-event-mode{color:var(--dsw-alias-label-secondary)}.lc-kind{white-space:nowrap;border-radius:4px;flex:none;padding:1px 6px;font-size:10px;font-weight:600}.lc-kind-inject{background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent);color:var(--dsw-alias-state-success-primary)}.lc-kind-compaction{background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent);color:var(--dsw-alias-state-error-primary)}.lc-kind-prune{color:#8b5cf6;background:#8b5cf626}.lc-kind-model{background:color-mix(in srgb, var(--dsw-alias-brand-primary) 15%, transparent);color:var(--dsw-alias-brand-primary)}.lc-kind-mode{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 15%, transparent);color:var(--dsw-alias-label-secondary)}.lc-event-label{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.lc-event-at{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none;font-size:11px}.lc-event-tokens{color:var(--dsw-alias-state-success-primary);white-space:nowrap;font-weight:600}.lc-event-tokens.lc-up{color:var(--dsw-alias-state-warn-primary)}.lc-event-time{color:var(--dsw-alias-label-secondary);font-size:12px}.lc-node{align-items:center;gap:8px;padding:3px 0;display:flex}.lc-node-preview{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);flex:1;overflow:hidden}.lc-node-time{color:var(--dsw-alias-label-secondary);text-align:right;min-width:54px;font-size:12px}.lc-node-tokens{color:var(--dsw-alias-label-secondary)}.lc-nodes-more{color:var(--dsw-alias-label-secondary);padding:3px 0}.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{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-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;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}.lc-modal-backdrop{z-index:200;background:#00000073;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.lc-modal-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);width:min(720px,100vw - 48px);max-height:min(82vh,760px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;padding:16px 18px 18px;font-size:13px;overflow-y:auto}.lc-modal-head{align-items:baseline;gap:8px;margin-bottom:12px;display:flex}.lc-modal-title{font-size:14px;font-weight:600}.lc-modal-close{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;margin-left:auto;padding:2px 6px;font-family:inherit;font-size:18px;line-height:1}.lc-modal-close:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.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-l1);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-cats{flex-direction:column;gap:4px;margin-top:10px;display:flex}.lc-br-cat{border:1px solid var(--dsw-alias-border-l1);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;background:0 0;border:0;align-items:center;gap:8px;padding:7px 10px;transition:background-color .12s;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{border-radius:2px;flex:none;width:8px;height:8px;transition:box-shadow .12s;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;color:var(--dsw-alias-label-secondary);flex:none;transition:transform .12s}.lc-br-chev-on{transform:rotate(90deg)}.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;background:0 0;border:0;border-radius:6px;align-items:center;gap:8px;padding:5px 6px;font-size:12px;transition:background-color .12s;display:flex}.lc-br-elem-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-br-tag{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;flex:none;max-width:220px;padding:0 6px;font-size:11px;overflow:hidden}.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)}.lc-ts-card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;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-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px}.lc-ts-card-count{margin-left:auto}.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-param-row{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:140px 90px 56px 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);text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;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);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);white-space:pre-wrap;word-break:break-word;scrollbar-width:thin;max-height:200px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;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;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-l1);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-rich-seg-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;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;scrollbar-width:thin;max-height:320px;padding:4px 10px;font-size:12px;line-height:1.55;overflow-y: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,.lc-ts-desc-md div pre{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}.lc-att-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;padding:8px 10px;display:grid}.lc-att-item{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);width:100%;min-width:0;font:inherit;text-align:left;cursor:pointer;background:0 0;border-radius:8px;align-items:center;gap:8px;padding:6px;display:flex}.lc-att-item:hover{border-color:var(--dsw-alias-border-l2,var(--dsw-alias-border-l1));background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}.lc-att-thumb{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:6px;flex:none;justify-content:center;align-items:center;width:64px;height:64px;padding:0;display:flex;overflow:hidden}.lc-att-thumb img{object-fit:cover;width:100%;height:100%;display:block}.lc-att-ph,.lc-att-err{color:var(--dsw-alias-label-secondary);text-align:center;padding:2px;font-size:10px}.lc-att-meta{flex-direction:column;gap:2px;min-width:0;display:flex}.lc-att-name{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;word-break:break-word;font-size:12px;line-height:1.3}.lc-att-row{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;font-size:11px}.lc-att-row-label{min-width:3.2em;font-weight:400;display:inline-block}.lc-att-lightbox{z-index:1000;place-items:center;padding:40px;display:grid;position:fixed;inset:0}.lc-att-lightbox-mask{background:var(--dsw-alias-bg-mask-1,#00000073);backdrop-filter:var(--dsw-mask-blur,blur(2px));position:absolute;inset:0}.lc-att-lightbox-img{object-fit:contain;background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-1));max-width:min(100%,1600px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);border-radius:12px;position:relative}.lc-att-lightbox-close{z-index:1;border:1px solid var(--dsw-alias-border-l2-darkmode-thin,var(--dsw-alias-border-l1));background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-1));width:36px;height:36px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:999px;place-items:center;padding:0;display:grid;position:fixed;top:20px;right:20px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])){display:none}";
2868
+ const css = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-card-title{align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-stats{grid-template-columns:repeat(auto-fit,minmax(62px,1fr));gap:6px;display:grid}.lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}.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-l1);cursor:help;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{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;max-width:280px;color:var(--dsw-alias-label-primary);pointer-events:none;opacity:0;border-radius:6px;padding:6px 10px;font-size:12px;font-weight:400;line-height:1.5;transition:opacity .12s;position:absolute;top:calc(100% + 6px);left:0;box-shadow:0 2px 8px #0000002e}.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);font-size:12px;font-weight:400}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);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-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;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-settings-card{background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);border-radius:12px;transition:border-color .16s,background .16s}.lc-settings-card:hover{border-color:var(--dsw-alias-label-dimmed)}.lc-settings-open,.lc-settings-open:hover{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}.lc-settings-head{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}.lc-settings-headtext{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.lc-settings-name{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}.lc-settings-desc{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}.lc-settings-chevron{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}.lc-settings-open .lc-settings-chevron{transform:rotate(180deg)}.lc-settings-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 16px;padding:4px 0 12px}.lc-settings-row{align-items:center;gap:8px;padding:8px 0;display:flex}.lc-settings-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:14px}.lc-settings-select{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.lc-settings-select:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.lc-settings-select:disabled{opacity:.5;cursor:default}.lc-settings-note{color:var(--dsw-alias-label-tertiary);margin:12px 0 4px;font-size:12px;line-height:1.5}.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-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);border-radius:5px;transition:opacity .12s;position:absolute;top:0;bottom:0;left:0}.lc-occupied-box-on{opacity:1}.lc-bar-tip{z-index:5;white-space:nowrap;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);pointer-events:none;opacity:0;border-radius:6px;padding:3px 8px;font-size:12px;transition:opacity .12s;position:absolute;bottom:calc(100% + 6px);transform:translate(-50%);box-shadow:0 2px 8px #0000002e}.lc-bar-tip-on{opacity:1}.lc-stacked>div{height:100%}.lc-stacked-seg{transition:filter .12s,opacity .12s}.lc-stacked-free{box-sizing:border-box;transition:box-shadow .12s,opacity .12s}.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;border-radius:6px;align-items:center;gap:5px;padding:1px 6px;transition:background-color .12s;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,.lc-node i{border-radius:2px;width:8px;height:8px;display:inline-block}.lc-chip i{transition:box-shadow .12s}.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)}.lc-tools{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;align-items:center;gap:6px;margin-top:10px;display:flex}.lc-tools-label,.lc-tools-more{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;padding:0;font-family:inherit;font-size:12px}.lc-tools-label:hover,.lc-tools-more:hover{text-decoration:underline}.lc-tool-chip{background:var(--dsw-alias-bg-layer-2);font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:0;border-radius:4px;padding:1px 7px;font-size:12px}.lc-tool-chip:hover{text-decoration:underline}.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-mid{top:69px}.lc-axis-bot{top:125px}.lc-chart-scroll{scrollbar-width:thin;flex:1;min-width:0;padding-bottom:8px;position:relative;overflow:auto hidden}.lc-chart-fade{pointer-events:none;z-index:2;width:26px;position:absolute;top:0;bottom:0}.lc-chart-fade-l{background:linear-gradient(to right, var(--dsw-alias-bg-layer-1), transparent);left:0}.lc-chart-fade-r{background:linear-gradient(to left, var(--dsw-alias-bg-layer-1), transparent);right:0}.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-l1);pointer-events:none;position:absolute;left:0;right:0}.lc-grid-top{top:18px}.lc-grid-mid{top:74px}.lc-bar{cursor:pointer;border-radius:2px;flex:none;align-items:flex-end;width:14px;height:100%;transition:opacity .12s,background-color .12s;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;white-space:nowrap;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-primary);pointer-events:none;border-radius:6px;padding:3px 8px;font-size:12px;position:absolute;top:0;transform:translate(-50%);box-shadow:0 2px 8px #0000002e}.lc-bar:hover{background:var(--dsw-alias-bg-layer-2)}.lc-bar-hovered{outline:1px dashed var(--dsw-alias-brand-primary);outline-offset:1px}.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-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}.lc-turn{box-sizing:border-box;text-align:center;color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;cursor:pointer;border-radius:3px;flex:none;height:14px;font-size:10px;font-weight:600;line-height:14px;transition:filter .12s,opacity .12s;overflow:hidden}.lc-turn-on{filter:brightness(1.35);color:var(--dsw-alias-label-primary)}.lc-turn-label{text-overflow:ellipsis;max-width:100%;display:inline-block;overflow:hidden}.lc-detail{border-top:1px solid var(--dsw-alias-border-l1);margin-top:12px;padding-top:12px}.lc-detail-head{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:6px 16px;margin-bottom:8px;display:flex}.lc-detail-head b{color:var(--dsw-alias-label-primary)}.lc-detail-marker{color:var(--dsw-alias-state-warn-primary);background:var(--dsw-alias-bg-layer-2);border-radius:6px;padding:1px 7px;font-size:11px}.lc-detail-head .lc-actual{color:var(--dsw-alias-state-success-primary)}.lc-detail-tag{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary);border-radius:4px;padding:0 6px;font-size:11px}.lc-detail-rows{grid-template-columns:1fr 1fr;gap:4px 24px;margin-top:10px;display:grid}.lc-detail-row{align-items:center;gap:8px;display:flex}.lc-detail-label{white-space:nowrap;min-width:70px;color:var(--dsw-alias-label-secondary)}.lc-bar-track{background:#8080802e;border-radius:3px;flex:1;height:5px;display:block;overflow:hidden}.lc-bar-fill{border-radius:3px;height:100%;display:block}.lc-detail-num{text-align:right;width:44px}.lc-detail-pct{text-align:right;width:34px;color:var(--dsw-alias-label-secondary)}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:280px}.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>.lc-card:first-child{flex:7 1 0;min-width:360px}.lc-head>.lc-card:last-child{flex:3 1 0;min-width:220px}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.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-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-events,.lc-nodes{flex-direction:column;gap:2px;max-height:320px;display:flex;overflow-y:auto}.lc-event{align-items:center;gap:8px;padding:3px 0;display:flex}.lc-event-icon{text-align:center;width:18px;color:var(--dsw-alias-state-warn-primary)}.lc-event-icon.lc-event-inject{color:#a855f7}.lc-event-icon.lc-event-model{color:var(--dsw-alias-brand-primary)}.lc-event-icon.lc-event-mode{color:var(--dsw-alias-label-secondary)}.lc-kind{white-space:nowrap;border-radius:4px;flex:none;padding:1px 6px;font-size:10px;font-weight:600}.lc-kind-inject{background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent);color:var(--dsw-alias-state-success-primary)}.lc-kind-compaction{background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent);color:var(--dsw-alias-state-error-primary)}.lc-kind-prune{color:#8b5cf6;background:#8b5cf626}.lc-kind-model{background:color-mix(in srgb, var(--dsw-alias-brand-primary) 15%, transparent);color:var(--dsw-alias-brand-primary)}.lc-kind-mode{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 15%, transparent);color:var(--dsw-alias-label-secondary)}.lc-event-label{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.lc-event-at{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none;font-size:11px}.lc-event-tokens{color:var(--dsw-alias-state-success-primary);white-space:nowrap;font-weight:600}.lc-event-tokens.lc-up{color:var(--dsw-alias-state-warn-primary)}.lc-event-time{color:var(--dsw-alias-label-secondary);font-size:12px}.lc-node{align-items:center;gap:8px;padding:3px 0;display:flex}.lc-node-preview{text-overflow:ellipsis;white-space:nowrap;color:var(--dsw-alias-label-primary);flex:1;overflow:hidden}.lc-node-time{color:var(--dsw-alias-label-secondary);text-align:right;min-width:54px;font-size:12px}.lc-node-tokens{color:var(--dsw-alias-label-secondary)}.lc-nodes-more{color:var(--dsw-alias-label-secondary);padding:3px 0}.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{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-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;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}.lc-modal-backdrop{z-index:200;background:#00000073;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.lc-modal-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);width:min(720px,100vw - 48px);max-height:min(82vh,760px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;padding:16px 18px 18px;font-size:13px;overflow-y:auto}.lc-modal-head{align-items:baseline;gap:8px;margin-bottom:12px;display:flex}.lc-modal-title{font-size:14px;font-weight:600}.lc-modal-close{color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:0;border-radius:6px;margin-left:auto;padding:2px 6px;font-family:inherit;font-size:18px;line-height:1}.lc-modal-close:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.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-l1);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-cats{flex-direction:column;gap:4px;margin-top:10px;display:flex}.lc-br-cat{border:1px solid var(--dsw-alias-border-l1);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;background:0 0;border:0;align-items:center;gap:8px;padding:7px 10px;transition:background-color .12s;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{border-radius:2px;flex:none;width:8px;height:8px;transition:box-shadow .12s;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;color:var(--dsw-alias-label-secondary);flex:none;transition:transform .12s}.lc-br-chev-on{transform:rotate(90deg)}.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;background:0 0;border:0;border-radius:6px;align-items:center;gap:8px;padding:5px 6px;font-size:12px;transition:background-color .12s;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{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;flex:none;max-width:220px;padding:0 6px;font-size:11px;overflow:hidden}.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)}.lc-ts-card{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);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-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px}.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;border-radius:999px;flex:none;align-items:center;gap:4px;padding:2px 7px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;font-weight:600;line-height:1;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-param-row{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:140px 90px 56px 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);text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;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);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);white-space:pre-wrap;word-break:break-word;scrollbar-width:thin;max-height:200px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;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;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-l1);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-rich-seg-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;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;white-space:pre-line;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{margin:6px 0}.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}.lc-att-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;padding:8px 10px;display:grid}.lc-att-item{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1);width:100%;min-width:0;font:inherit;text-align:left;cursor:pointer;background:0 0;border-radius:8px;align-items:center;gap:8px;padding:6px;display:flex}.lc-att-item:hover{border-color:var(--dsw-alias-border-l2,var(--dsw-alias-border-l1));background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}.lc-att-thumb{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-1);border-radius:6px;flex:none;justify-content:center;align-items:center;width:64px;height:64px;padding:0;display:flex;overflow:hidden}.lc-att-thumb img{object-fit:cover;width:100%;height:100%;display:block}.lc-att-ph,.lc-att-err{color:var(--dsw-alias-label-secondary);text-align:center;padding:2px;font-size:10px}.lc-att-meta{flex-direction:column;gap:2px;min-width:0;display:flex}.lc-att-name{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere;word-break:break-word;font-size:12px;line-height:1.3}.lc-att-row{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;font-size:11px}.lc-att-row-label{min-width:3.2em;font-weight:400;display:inline-block}.lc-att-lightbox{z-index:1000;place-items:center;padding:40px;display:grid;position:fixed;inset:0}.lc-att-lightbox-mask{background:var(--dsw-alias-bg-mask-1,#00000073);backdrop-filter:var(--dsw-mask-blur,blur(2px));position:absolute;inset:0}.lc-att-lightbox-img{object-fit:contain;background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-1));max-width:min(100%,1600px);max-height:calc(100vh - 80px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);border-radius:12px;position:relative}.lc-att-lightbox-close{z-index:1;border:1px solid var(--dsw-alias-border-l2-darkmode-thin,var(--dsw-alias-border-l1));background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-1));width:36px;height:36px;color:var(--dsw-alias-label-primary);cursor:pointer;border-radius:999px;place-items:center;padding:0;display:grid;position:fixed;top:20px;right:20px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])){display:none}";
2910
2869
  const tagId = "dsh-context/styles.css";
2911
2870
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2912
2871
  const tag = document.createElement("style");
package/lib/index.d.ts CHANGED
@@ -2,29 +2,24 @@ import { ZodType, z } from "zod";
2
2
  import { Session, SessionEvent } from "@deepseek-ai/dsh-session";
3
3
  import { Context, Service } from "@deepseek-ai/cordis";
4
4
  //#region src/host/config.d.ts
5
- /** dsh-context host config. All fields optional; defaults below. */
6
5
  interface Config {
7
6
  /** Cap on kept per-step request records (the hard step backstop). */
8
7
  maxRequestSteps?: number;
9
8
  /** Newest whole-turn window kept; trimming crosses whole turns, never mid-turn. */
10
9
  maxKeptTurns?: number;
11
- /** Newest context-event records kept. */
12
10
  maxEvents?: number;
13
11
  /**
14
- * Surface nodes served to the browser (newest carry the signal; live
15
- * inject nodes are always served they are few and land first). The
16
- * default is deliberately generous: auto-compaction keeps the live
17
- * surface far below it in healthy sessions, so the browser effectively
18
- * lists EVERY live node; the bound stays as a backstop for pathological
19
- * sessions (every projection push ships the whole value, ~150B per node).
12
+ * Served surface nodes (newest carry the signal; live inject nodes are pinned — they land first and are few). Deliberately generous:
13
+ * auto-compaction keeps healthy surfaces far below it, so the browser effectively lists every live node; the bound is a
14
+ * pathological-session backstop (each push ships the whole value, ~150B/node).
20
15
  */
21
16
  maxNodes?: number;
22
17
  /** Removed (shadowed) surface nodes kept for per-step reconstruction. */
23
18
  maxArchiveNodes?: number;
24
19
  }
25
20
  /**
26
- * The cordis `Config` validator: strict on keys, defaults on the schema fields.
27
- * Tolerates `undefined` (a patch row without a `config:` block — defaults win).
21
+ * The cordis `Config` validator: strict on keys, defaults on the schema fields; tolerates `undefined` (a patch row without a `config:`
22
+ * block — defaults win).
28
23
  */
29
24
  declare const Config: z.ZodPreprocess<z.ZodObject<{
30
25
  maxRequestSteps: z.ZodDefault<z.ZodNumber>;
@@ -36,19 +31,10 @@ declare const Config: z.ZodPreprocess<z.ZodObject<{
36
31
  //#endregion
37
32
  //#region src/shared/types.d.ts
38
33
  /**
39
- * Shared wire contract — the snapshot model exchanged between the Host and
40
- * Client halves.
41
- *
42
- * The Host half no longer serves this over a custom RPC channel: it is the
43
- * `view()` payload of the `contextTimeline` session projection, registered on
44
- * the harness's `ctx.sessionProjections` registry. The registry drives
45
- * `apply(state, event)` over every committed session event, persists the state
46
- * through `ctx.sessionProjectionCache`, and pushes the finished value to the
47
- * browser as a `session/projection` frame (with a tail-page baseline), where
48
- * the Client reads it through the framework-standard `useProjection` seat.
49
- *
50
- * TYPE-ONLY host-side module: both halves import these as `import type`, so
51
- * nothing from here ever reaches the runtime bundles.
34
+ * Shared wire contract — the snapshot model exchanged between the Host and Client halves. Delivered as the `view()` payload of the
35
+ * `contextTimeline`/`contextHeaders` session projections (registered on `ctx.sessionProjections`; the registry pushes finished views as
36
+ * `session/projection` frames — see host/timeline.ts). TYPE-ONLY host-side module: both halves import these as `import type`, so nothing
37
+ * from here ever reaches the runtime bundles.
52
38
  */
53
39
  declare module '@deepseek-ai/dsh-session-projection/types' {
54
40
  interface SessionProjectionMap {
@@ -70,7 +56,6 @@ declare module '@deepseek-ai/dsh-session-projection/types' {
70
56
  contextHeaders: ContextHeaders;
71
57
  }
72
58
  }
73
- /** The five priced context categories (plus system/tools handled separately). */
74
59
  type Category = 'user' | 'inject' | 'assistant' | 'tool';
75
60
  interface Snapshot {
76
61
  ok: boolean;
@@ -93,15 +78,10 @@ interface Snapshot {
93
78
  * instead. Kept optional for wire compatibility with older clients.
94
79
  */
95
80
  occupancy?: {
96
- /** Provider-reported prompt size of the most recent request (input + cache). */
97
81
  pressureTokens?: number;
98
- /** Heuristic total over the current model-visible surface. */
99
82
  surfaceTokens: number;
100
- /** `surfaceTokens` at the newest usage sample. */
101
83
  sampledSurfaceTokens?: number;
102
- /** pressureTokens + surface movement since the sample (clamped ≥ 0). */
103
84
  projectedTokens?: number;
104
- /** Newest recorded route capacity (last-wins). */
105
85
  contextWindow?: number;
106
86
  };
107
87
  toolList: {
@@ -131,10 +111,9 @@ interface Snapshot {
131
111
  */
132
112
  cost?: SessionCostUsage;
133
113
  /**
134
- * The served live surface: the newest `maxNodes` tail PLUS every live
135
- * inject node older than the tail (injections land first and are few, so
136
- * they are pinned otherwise a long session would price them while the
137
- * browser could list none). Seq-ordered, oldest first.
114
+ * The served live surface: the newest `maxNodes` tail PLUS every live inject node older than the tail (injections land first and are
115
+ * few,
116
+ * so they are pinned). Seq-ordered, oldest first.
138
117
  */
139
118
  nodes: SurfaceNode[];
140
119
  /** Live nodes not served (the overflow beyond `maxNodes`, minus pinned injects — see `nodes`). */
@@ -159,11 +138,8 @@ interface Snapshot {
159
138
  archiveFloor?: number;
160
139
  }
161
140
  /**
162
- * The `contextTimeline` projection's whole value — the same snapshot the
163
- * Client has always rendered, now delivered through the session-projection
164
- * pipeline. `ok` is always `true` here (a delivered projection is by
165
- * definition available); it is kept for wire compatibility with the
166
- * snapshot shape.
141
+ * The `contextTimeline` projection's whole value — the same snapshot the Client has always rendered. `ok` is always `true` here (a
142
+ * delivered projection is by definition available); kept for wire compatibility with the snapshot shape.
167
143
  */
168
144
  type ContextTimeline = Snapshot;
169
145
  /**
@@ -172,20 +148,14 @@ type ContextTimeline = Snapshot;
172
148
  * session log, immune to the request/event retention bounds).
173
149
  */
174
150
  interface CostBucketTotals {
175
- /** Billed prompt tokens that missed the provider cache. */
176
151
  uncached: number;
177
- /** Billed prompt tokens served from the provider cache. */
178
152
  cacheRead: number;
179
- /** Billed prompt tokens written into the provider cache. */
180
153
  cacheWrite: number;
181
- /** Billed output tokens (reasoning included). */
182
154
  output: number;
183
155
  }
184
156
  /** One model family's totals split by DeepSeek's pricing period (Beijing Time). */
185
157
  interface CostFamilyUsage {
186
- /** Peak windows: 09:00-12:00 and 14:00-18:00 Beijing Time, weekdays only. */
187
158
  peak?: CostBucketTotals;
188
- /** All other hours plus all of Saturday/Sunday (half the peak rate). */
189
159
  off?: CostBucketTotals;
190
160
  }
191
161
  /**
@@ -202,7 +172,6 @@ interface SessionCostUsage {
202
172
  /** One model-visible message on the surface, with its heuristic token price. */
203
173
  interface SurfaceNode {
204
174
  seq: number;
205
- /** Event timestamp (ms epoch); the Client shows it when present. */
206
175
  time?: number;
207
176
  cat: Category;
208
177
  tokens: number;
@@ -280,10 +249,7 @@ interface HeaderTool {
280
249
  schema?: unknown;
281
250
  }
282
251
  /**
283
- * One request-header epoch: the full system prompt and tool schemas in force
284
- * from this event's seq until the next epoch. Headers change rarely (the loop
285
- * only logs `request/header` on change), so this unit's pushes are rare and
286
- * carrying full content is cheap.
252
+ * One request-header epoch: the full system prompt and tool schemas in force from this event's seq until the next epoch.
287
253
  */
288
254
  interface HeaderRecord {
289
255
  seq: number;
@@ -307,11 +273,9 @@ interface ContextHeaders {
307
273
  * (not only when the raw step bound is), so the bounded state stays at the
308
274
  * newest ~`maxKeptTurns` turns deterministically as a live log grows.
309
275
  */
310
- /** The projection unit's persisted state (plain JSON, bounded see above). */
311
276
  interface TimelineState {
312
277
  /** Model-visible surface, newest last. */
313
278
  surface: SurfaceNode[];
314
- /** Live per-category token sums over the surface. */
315
279
  sums: Record<Category, number>;
316
280
  systemTokens: number;
317
281
  toolsTokens: number;
@@ -351,7 +315,6 @@ interface TimelineState {
351
315
  * Absent until a v4-flash / v4-pro request reports usage.
352
316
  */
353
317
  cost?: SessionCostUsage;
354
- /** Newest `gone` among archive entries dropped by the retention bounds. */
355
318
  archiveFloor?: number;
356
319
  /**
357
320
  * Tool callId → name, armed by `tool/call` and DELETED when its
@@ -608,14 +571,12 @@ declare class SessionProjectionRegistry extends Service {
608
571
  }
609
572
  //#endregion
610
573
  //#region src/host/headers.d.ts
611
- /** The unit's persisted state (plain JSON, bounded). */
612
574
  interface HeadersState {
613
575
  headers: HeaderRecord[];
614
576
  }
615
577
  //#endregion
616
578
  //#region src/host/index.d.ts
617
579
  declare const name = "dsh-context";
618
- /** Required services: the session-projection registry that drives the unit. */
619
580
  declare const inject: string[];
620
581
  declare function apply(ctx: Context, config: Config): void;
621
582
  //#endregion
package/lib/index.js CHANGED
@@ -17,7 +17,6 @@ import { deriveEventMessage } from "@deepseek-ai/dsh-session";
17
17
  * only tune the fold's retention / presentation slice, so changing them never
18
18
  * requires a projection `stateVersion` bump.
19
19
  */
20
- /** Defaults — the exact bounds the fold used before they became configurable. */
21
20
  const DEFAULT_BOUNDS = {
22
21
  maxRequestSteps: 1500,
23
22
  maxKeptTurns: 300,
@@ -26,8 +25,8 @@ const DEFAULT_BOUNDS = {
26
25
  maxArchiveNodes: 400
27
26
  };
28
27
  /**
29
- * The cordis `Config` validator: strict on keys, defaults on the schema fields.
30
- * Tolerates `undefined` (a patch row without a `config:` block — defaults win).
28
+ * The cordis `Config` validator: strict on keys, defaults on the schema fields; tolerates `undefined` (a patch row without a `config:`
29
+ * block — defaults win).
31
30
  */
32
31
  const Config = z.preprocess((v) => v ?? {}, z.object({
33
32
  maxRequestSteps: z.number().int().min(1).default(DEFAULT_BOUNDS.maxRequestSteps),
@@ -36,7 +35,6 @@ const Config = z.preprocess((v) => v ?? {}, z.object({
36
35
  maxNodes: z.number().int().min(1).default(DEFAULT_BOUNDS.maxNodes),
37
36
  maxArchiveNodes: z.number().int().min(1).default(DEFAULT_BOUNDS.maxArchiveNodes)
38
37
  }).strict());
39
- /** Validate (and default) the entry config into concrete fold bounds. */
40
38
  function resolveBounds(config) {
41
39
  return Config.parse(config ?? {});
42
40
  }
@@ -69,7 +67,6 @@ const MAX_WH_RATIO = 8;
69
67
  const MIN_PIXELS = 147456;
70
68
  const floorDiv = (a, b) => Math.floor(a / b);
71
69
  const ceilDiv = (a, b) => Math.floor((a + b - 1) / b);
72
- /** Token count of one patch grid (rows×cols) under the v4 layout rule. */
73
70
  function gridTokens(rows, cols) {
74
71
  let n = rows * (cols + 1) + 2;
75
72
  if (rows % 2 === 1) n += cols + 1;
@@ -151,10 +148,8 @@ function calcResizeInner(width, height) {
151
148
  return safeResize(h, w, paddedHeight, paddedWidth);
152
149
  }
153
150
  /**
154
- * Estimate the tokens one image consumes in a DeepSeek vision request, from
155
- * its pixel dimensions. Returns null for non-positive/non-finite dimensions
156
- * or when the official iteration fails to converge — callers fall back to
157
- * the generic structural price.
151
+ * Estimate the tokens one image consumes in a DeepSeek vision request from its pixel dimensions. Returns null for non-positive/non-finite
152
+ * dimensions or when the official iteration fails to converge — callers fall back to the generic structural price.
158
153
  */
159
154
  function estimateImageTokens(width, height) {
160
155
  if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
@@ -188,7 +183,6 @@ function estimateImageTokens(width, height) {
188
183
  const CHARS_PER_TOKEN = 4;
189
184
  const BLOCK_OVERHEAD = 4;
190
185
  const ROLE_OVERHEAD = 4;
191
- /** Whole-array tool-schema price (the header's tools total). */
192
186
  function estimateToolsTotal(tools) {
193
187
  return tools.length > 0 ? Math.ceil(JSON.stringify(tools).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD : 0;
194
188
  }
@@ -234,9 +228,8 @@ function estimateToolSchema(tool) {
234
228
  return Math.ceil(JSON.stringify(tool).length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD;
235
229
  }
236
230
  /**
237
- * Count image blocks in a message payload, recursing into nested content
238
- * (tool-result blocks carry their inner blocks) feeds the stats board's
239
- * whole-session image-file cell.
231
+ * Count image blocks in a message payload, recursing into nested content (tool-result blocks carry their inner blocks) — seeds each node's
232
+ * `imgs`, which the stats board's image cell sums over the LIVE surface (compacted/pruned messages stop counting).
240
233
  */
241
234
  function imageCountOf(blocks) {
242
235
  let count = 0;
@@ -312,12 +305,10 @@ const contextHeadersSchema = z.object({ headers: z.array(z.object({
312
305
  tools: z.array(headerToolSchema)
313
306
  }).strict()) }).strict();
314
307
  /**
315
- * The persisted state and the wire view are the same shape here (the view
316
- * only shallow-copies each record), so one schema validates both under the
317
- * dsh 0.1.1-rc.1+ `stateSchema`/`wire` contract.
308
+ * State and wire are the same shape (the view only shallow-copies each record), so one schema validates both under the dsh 0.1.1-rc.1+
309
+ * `stateSchema`/`wire` contract.
318
310
  */
319
311
  const contextHeadersStateSchema = contextHeadersSchema;
320
- /** Fold one `request/header` payload into an epoch record (display-priced). */
321
312
  function recordOf(event) {
322
313
  if (event.type !== "request/header") return null;
323
314
  const rawHeader = event.data.header;
@@ -342,14 +333,9 @@ function recordOf(event) {
342
333
  return record;
343
334
  }
344
335
  /**
345
- * The context-headers projection unit. Registered alongside the timeline
346
- * unit (host/index.ts); clients read it through `useProjection('contextHeaders')`
347
- * and degrade to tokens-only header sections when the key is absent.
348
- *
349
- * Dual-contract definition (see compat.ts): `schema`/`view` for
350
- * dsh <= 0.1.0-rc.8, `stateSchema`/`wire` for dsh >= 0.1.1-rc.1. Without
351
- * `wire`, the 0.1.1-rc.1+ registry treats the unit as host-only and the
352
- * Context browser's system/tools sections would degrade.
336
+ * The context-headers projection unit; registered alongside the timeline unit (host/index.ts); clients read it through
337
+ * `useProjection('contextHeaders')` and degrade to tokens-only header sections when the key is absent. Dual-contract definition (see
338
+ * compat.ts).
353
339
  */
354
340
  function createContextHeadersDefinition() {
355
341
  const view = (state) => ({ headers: state.headers.map((h) => ({
@@ -394,7 +380,6 @@ function installSettings(ctx) {
394
380
  }
395
381
  //#endregion
396
382
  //#region src/host/fold.ts
397
- /** Keep only the trailing `maxTurns` turn-runs of a request timeline. */
398
383
  function trimToLastTurns(requests, maxTurns) {
399
384
  let runs = 0;
400
385
  let start = requests.length;
@@ -410,7 +395,6 @@ function trimToLastTurns(requests, maxTurns) {
410
395
  }
411
396
  return requests.slice(start);
412
397
  }
413
- /** Distinct turn runs in a request timeline (consecutive equal-turn runs). */
414
398
  function countTurnRuns(requests) {
415
399
  let runs = 0;
416
400
  let prevTurn;
@@ -420,7 +404,6 @@ function countTurnRuns(requests) {
420
404
  }
421
405
  return runs;
422
406
  }
423
- /** Retain the newest tail of the two unbounded lists (bounded persisted state). */
424
407
  function trimState(st, bounds) {
425
408
  if (countTurnRuns(st.requests) > bounds.maxKeptTurns) st.requests = trimToLastTurns(st.requests, bounds.maxKeptTurns);
426
409
  if (st.requests.length > bounds.maxRequestSteps) st.requests = st.requests.slice(-bounds.maxRequestSteps);
@@ -808,10 +791,9 @@ function applyTimeline(state, event, bounds) {
808
791
  return state;
809
792
  }
810
793
  /**
811
- * Build the wire snapshot served to the browser the projection's `view()`.
812
- * Bounds the surface nodes (newest carry the signal), and attributes each
813
- * event to the request around it by stamping COPIES (the persisted state
814
- * objects are never mutated).
794
+ * Serve the projection's wire view: bound the surface nodes to the newest tail and attach each event to the request around it; stamp
795
+ * COPIES
796
+ * the persisted state objects are never mutated.
815
797
  */
816
798
  function buildTimelineView(state, bounds) {
817
799
  const surfaceTotal = state.sums.user + state.sums.inject + state.sums.assistant + state.sums.tool;
@@ -1078,7 +1060,6 @@ function createContextTimelineDefinition(config) {
1078
1060
  //#endregion
1079
1061
  //#region src/host/index.ts
1080
1062
  const name = "dsh-context";
1081
- /** Required services: the session-projection registry that drives the unit. */
1082
1063
  const inject = ["sessionProjections"];
1083
1064
  function apply(ctx, config) {
1084
1065
  ctx.sessionProjections.register(createContextTimelineDefinition(config));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.29.0",
3
+ "version": "0.30.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": {