dsh-context 0.28.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/lib/client.js CHANGED
@@ -44,6 +44,13 @@ window.__ModuleLoader__.load({
44
44
  "trend.title": "历史趋势",
45
45
  "gran.step": "步骤",
46
46
  "gran.turn": "轮次",
47
+ "settings.title": "上下文",
48
+ "settings.desc": "上下文面板的展示偏好",
49
+ "settings.gran": "趋势图默认粒度",
50
+ "settings.mode": "趋势图默认展示",
51
+ "settings.expand": "展开",
52
+ "settings.collapse": "收起",
53
+ "settings.readOnly": "当前环境的设置为只读",
47
54
  "gran.total": "全量",
48
55
  "gran.diff": "增量",
49
56
  "gran.modeHint": "全量:累计构成;增量:相对上一条请求的变化量",
@@ -125,6 +132,12 @@ window.__ModuleLoader__.load({
125
132
  "block.content": "内容",
126
133
  "block.result": "结果",
127
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": "工具执行失败",
128
141
  "attach.images": "图片附件",
129
142
  "attach.other": "其他内容",
130
143
  "attach.image": "图片",
@@ -177,6 +190,13 @@ window.__ModuleLoader__.load({
177
190
  "trend.title": "Context History",
178
191
  "gran.step": "Step",
179
192
  "gran.turn": "Turn",
193
+ "settings.title": "Context",
194
+ "settings.desc": "Display preferences for the Context panel",
195
+ "settings.gran": "Default trend granularity",
196
+ "settings.mode": "Default trend display",
197
+ "settings.expand": "Expand",
198
+ "settings.collapse": "Collapse",
199
+ "settings.readOnly": "Settings are read-only in this environment",
180
200
  "gran.total": "Total",
181
201
  "gran.diff": "Diff",
182
202
  "gran.modeHint": "Total: cumulative makeup; Diff: change vs the previous request",
@@ -258,6 +278,12 @@ window.__ModuleLoader__.load({
258
278
  "block.content": "Content",
259
279
  "block.result": "Result",
260
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",
261
287
  "attach.images": "Images",
262
288
  "attach.other": "Other content",
263
289
  "attach.image": "Image",
@@ -300,7 +326,6 @@ window.__ModuleLoader__.load({
300
326
  function setPendingConsume(sessionId, guard) {
301
327
  pendingConsume.set(sessionId, guard);
302
328
  }
303
- /** Take (and clear) the guard recorded by the open path, if any. */
304
329
  function takePendingConsume(sessionId) {
305
330
  const guard = pendingConsume.get(sessionId);
306
331
  if (guard !== void 0) pendingConsume.delete(sessionId);
@@ -500,7 +525,6 @@ window.__ModuleLoader__.load({
500
525
  function numOf(value) {
501
526
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
502
527
  }
503
- /** The usable (non-null object) entries of a delivered list; [] when the field is not a list. */
504
528
  function objectsOf(value) {
505
529
  if (!Array.isArray(value)) return [];
506
530
  return value.filter((v) => v !== null && typeof v === "object");
@@ -630,10 +654,6 @@ window.__ModuleLoader__.load({
630
654
  for (let i = headers.headers.length - 1; i >= 0; i--) if (headers.headers[i].seq < seq) return headers.headers[i];
631
655
  return null;
632
656
  }
633
- /**
634
- * Reconstruct the assembled surface for one step (`seq` = the request
635
- * record's seq) or the live surface (`seq` = null).
636
- */
637
657
  function assemble(data, headers, seq) {
638
658
  const live = seq === null;
639
659
  let nodes;
@@ -665,7 +685,6 @@ window.__ModuleLoader__.load({
665
685
  const ReactDOM = require("react-dom");
666
686
  //#endregion
667
687
  //#region src/client/components/nodes.tsx
668
- /** One-line preview of a surface node (shared by the node list and the Context browser's element rows). */
669
688
  function makeNodeText(kit) {
670
689
  const { t } = kit;
671
690
  return function nodeText(n) {
@@ -700,10 +719,7 @@ window.__ModuleLoader__.load({
700
719
  }
701
720
  //#endregion
702
721
  //#region src/client/format.ts
703
- /**
704
- * Number/time formatting for the UI. `fmt` uses the same k/M suffix style
705
- * everywhere (bars, details, stats); `fmtTime` renders a local HH:MM:SS.
706
- */
722
+ /** `fmt`: the k/M suffix style shared by bars/details/stats; `fmtTime`: local HH:MM:SS. */
707
723
  function fmt(n) {
708
724
  if (n === void 0 || n === null || isNaN(n)) return "—";
709
725
  const sign = n < 0 ? "-" : "";
@@ -753,7 +769,6 @@ window.__ModuleLoader__.load({
753
769
  const MIN_PIXELS = 147456;
754
770
  const floorDiv = (a, b) => Math.floor(a / b);
755
771
  const ceilDiv = (a, b) => Math.floor((a + b - 1) / b);
756
- /** Token count of one patch grid (rows×cols) under the v4 layout rule. */
757
772
  function gridTokens(rows, cols) {
758
773
  let n = rows * (cols + 1) + 2;
759
774
  if (rows % 2 === 1) n += cols + 1;
@@ -835,10 +850,8 @@ window.__ModuleLoader__.load({
835
850
  return safeResize(h, w, paddedHeight, paddedWidth);
836
851
  }
837
852
  /**
838
- * Estimate the tokens one image consumes in a DeepSeek vision request, from
839
- * its pixel dimensions. Returns null for non-positive/non-finite dimensions
840
- * or when the official iteration fails to converge — callers fall back to
841
- * 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.
842
855
  */
843
856
  function estimateImageTokens(width, height) {
844
857
  if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return null;
@@ -857,11 +870,9 @@ window.__ModuleLoader__.load({
857
870
  //#endregion
858
871
  //#region src/client/components/images.tsx
859
872
  /**
860
- * Narrow an unknown content block to a durable image reference. Accepts both
861
- * raw message blocks (`{ type: 'image', attachment }`) and the conversation
862
- * snapshot's assistant blocks (`{ kind: 'image', attachment }`); everything
863
- * else returns null. Lenient on the ref's optional facts (name/bytes/dims) —
864
- * `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.
865
876
  */
866
877
  function imageRefOf(block) {
867
878
  if (block === null || typeof block !== "object") return null;
@@ -887,12 +898,9 @@ window.__ModuleLoader__.load({
887
898
  };
888
899
  }
889
900
  /**
890
- * Document-level original-image preview — the chat history's ImageLightbox
891
- * recipe (dsh ui-attachment, which the browser module table does not seed)
892
- * ported onto the plugin's lc-* classes: body portal so a transformed or
893
- * filtered ancestor cannot trap the fixed backdrop, blurred mask layer,
894
- * contain-fit image, circular close control, Escape/mask close, and focus
895
- * 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.
896
904
  */
897
905
  function AttachmentLightbox(props) {
898
906
  const { src, alt, labels, onClose } = props;
@@ -932,13 +940,9 @@ window.__ModuleLoader__.load({
932
940
  }, /* @__PURE__ */ React.createElement(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, { size: 16 }))), document.body);
933
941
  }
934
942
  /**
935
- * One attachment card: the WHOLE card is the click target — a 64px cover
936
- * tile beside a metadata column (display name, then one labeled row per
937
- * known fact: Raw, the pre-normalization raster dsh 0.1.1 records when
938
- * normalization reduced the image; Sent, the normalized raster the model
939
- * receives, with its stored byte size; and the estimated provider-billed
940
- * tokens). Clicking opens the chat-style lightbox preview (load failures
941
- * 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.
942
946
  */
943
947
  function makeImageCard(kit) {
944
948
  const { t, fmt } = kit;
@@ -1052,7 +1056,6 @@ window.__ModuleLoader__.load({
1052
1056
  }
1053
1057
  //#endregion
1054
1058
  //#region src/client/components/browser.tsx
1055
- /** Flatten the `anyOf` / `oneOf` alternation into one displayable string. */
1056
1059
  function unionTypesOf(p) {
1057
1060
  const branches = [];
1058
1061
  if (Array.isArray(p.anyOf)) branches.push(...p.anyOf);
@@ -1062,12 +1065,6 @@ window.__ModuleLoader__.load({
1062
1065
  for (const b of branches) if (b !== null && typeof b === "object") parts.push(typeOf(b));
1063
1066
  return parts.length > 0 ? parts.join(" | ") : null;
1064
1067
  }
1065
- /**
1066
- * Derive a short, human-readable type label from a JSON Schema fragment.
1067
- * Arrays show their element type (`array<number>`); unions fold into
1068
- * `a | b`; enums collapse into `(enum)`. Returns `unknown` when the schema
1069
- * carries no usable signal — the row falls back to its description then.
1070
- */
1071
1068
  function typeOf(p) {
1072
1069
  const u = unionTypesOf(p);
1073
1070
  if (u !== null) return u;
@@ -1089,9 +1086,8 @@ window.__ModuleLoader__.load({
1089
1086
  return "unknown";
1090
1087
  }
1091
1088
  /**
1092
- * Locate the parameter-bearing object inside a raw tool schema. Producers
1093
- * may nest it under `parameters`, `input_schema`, `inputSchema`, or hand
1094
- * 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.
1095
1091
  */
1096
1092
  function paramsOf(schema) {
1097
1093
  if (schema === null || typeof schema !== "object") return null;
@@ -1102,33 +1098,19 @@ window.__ModuleLoader__.load({
1102
1098
  if (s.type === "object" && s.properties !== void 0 && typeof s.properties === "object") return s;
1103
1099
  return null;
1104
1100
  }
1105
- /**
1106
- * One row of the parsed parameter table — name (mono-styled, with a
1107
- * required chip), short type label, and the description on its own line
1108
- * (so a long blurb never breaks the name/type rhythm).
1109
- */
1110
1101
  function ParamRow(props) {
1111
1102
  const typeLabel = typeOf(props.schema);
1112
1103
  const desc = props.schema.description;
1113
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);
1114
1105
  }
1115
1106
  /**
1116
- * Section — the ONE detail chrome of the Context browser: a labeled card
1117
- * (`lc-ts-card`) whose head carries the section title on the left and the
1118
- * extras on the right (a count badge and/or the Raw/MD switch), with the
1119
- * body below. Every expanded element renders as a stack of these — prose,
1120
- * tool calls, parameter rows, image grids, and raw JSON all share the same
1121
- * frame, so the reader scans one repeating anatomy instead of a different
1122
- * 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.
1123
1109
  */
1124
1110
  function Section(props) {
1125
- 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);
1126
1113
  }
1127
- /**
1128
- * TextSection — a prose Section (system prompt, message/injection/summary
1129
- * body, thinking, answer, tool description): the head's right edge carries
1130
- * the Raw/MD switch and the body follows the per-card mode.
1131
- */
1132
1114
  function TextSection(props) {
1133
1115
  const { rich } = props;
1134
1116
  const [mode, setMode] = rich.useRichMode();
@@ -1137,21 +1119,19 @@ window.__ModuleLoader__.load({
1137
1119
  actions: /* @__PURE__ */ React.createElement(rich.RichSwitch, {
1138
1120
  mode,
1139
1121
  onPick: setMode
1140
- })
1122
+ }),
1123
+ meta: props.meta
1141
1124
  }, /* @__PURE__ */ React.createElement(rich.RichText, {
1142
1125
  text: props.text,
1143
1126
  mode
1144
1127
  }));
1145
1128
  }
1146
- /** RawSection — a dimmed text Section without a view switch (structured payloads: tool results, raw JSON). */
1147
1129
  function RawSection(props) {
1148
1130
  return /* @__PURE__ */ React.createElement(Section, { label: props.label }, /* @__PURE__ */ React.createElement("pre", { className: "lc-ts-desc-body lc-br-dim" }, props.text));
1149
1131
  }
1150
1132
  /**
1151
- * The full body of one expanded tool row: description, a parsed parameter
1152
- * table (when the schema carries one), and the raw JSON behind a toggle.
1153
- * Owns its own open/closed state for the JSON so two expanded tools stay
1154
- * 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.
1155
1135
  */
1156
1136
  function ToolSchema(props) {
1157
1137
  const { rich } = props;
@@ -1196,14 +1176,9 @@ window.__ModuleLoader__.load({
1196
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);
1197
1177
  }
1198
1178
  /**
1199
- * Render one message's content blocks as a uniform Section stack. Both
1200
- * block vocabularies normalize here raw durable blocks (`type`: text /
1201
- * reasoning / tool-call / tool-result / image, arguments) and the
1202
- * conversation snapshot's assistant blocks (`kind`: text / reasoning /
1203
- * tool-call / image, argsRaw). Consecutive images group into one grid
1204
- * section; `richable` bodies (messages, injections) carry the Raw/MD
1205
- * switch while tool-result payloads stay raw (structured data, not prose)
1206
- * 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.
1207
1182
  */
1208
1183
  function BlocksBody(props) {
1209
1184
  const { rich, img, labels } = props;
@@ -1234,15 +1209,12 @@ window.__ModuleLoader__.load({
1234
1209
  const blockKind = blk !== null ? typeof blk.type === "string" ? blk.type : typeof blk.kind === "string" ? blk.kind : "" : "";
1235
1210
  if ((blockKind === "text" || blockKind === "reasoning") && typeof blk?.text === "string") {
1236
1211
  const label = blockKind === "reasoning" ? labels.thinking : props.textLabel;
1237
- out.push(props.richable ? /* @__PURE__ */ React.createElement(TextSection, {
1212
+ out.push(/* @__PURE__ */ React.createElement(TextSection, {
1238
1213
  key: out.length,
1239
1214
  label,
1240
1215
  text: blk.text,
1241
- rich
1242
- }) : /* @__PURE__ */ React.createElement(RawSection, {
1243
- key: out.length,
1244
- label,
1245
- 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
1246
1218
  }));
1247
1219
  continue;
1248
1220
  }
@@ -1275,11 +1247,6 @@ window.__ModuleLoader__.load({
1275
1247
  flushImages();
1276
1248
  return /* @__PURE__ */ React.createElement(React.Fragment, null, out);
1277
1249
  }
1278
- /**
1279
- * Parse a call's raw argument payload into an object; null when the payload
1280
- * is missing, malformed, or not a JSON object (callers fall back to the raw
1281
- * text).
1282
- */
1283
1250
  function parseArgs(raw) {
1284
1251
  if (typeof raw !== "string" || raw === "") return null;
1285
1252
  try {
@@ -1290,11 +1257,8 @@ window.__ModuleLoader__.load({
1290
1257
  }
1291
1258
  }
1292
1259
  /**
1293
- * The one-line summary a call's arguments carry about the call: a
1294
- * bash-style `description` says what it does; path-taking tools (edit /
1295
- * read / write) name their target via `file_path`/`path`/`filePath`. Null
1296
- * when the arguments hold neither — the row then falls back to the generic
1297
- * 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.
1298
1262
  */
1299
1263
  function summaryIn(args) {
1300
1264
  if (args === null) return null;
@@ -1309,18 +1273,24 @@ window.__ModuleLoader__.load({
1309
1273
  }
1310
1274
  return null;
1311
1275
  }
1312
- /**
1313
- * The summary of a tool result's own call — the collapsed row's preview.
1314
- * Null when the call summarizes to nothing.
1315
- */
1316
1276
  function callSummaryOf(conv) {
1317
1277
  return summaryIn(parseArgs(conv?.call?.argsRaw));
1318
1278
  }
1319
1279
  /**
1320
- * The first call summary inside an assistant message's tool-call blocks —
1321
- * the collapsed-row preview for a text-less assistant turn. Null when no
1322
- * 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.
1323
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
+ }
1324
1294
  function blockSummaryOf(conv) {
1325
1295
  if (conv === void 0 || !Array.isArray(conv.blocks)) return null;
1326
1296
  for (const b of conv.blocks) {
@@ -1331,37 +1301,23 @@ window.__ModuleLoader__.load({
1331
1301
  }
1332
1302
  return null;
1333
1303
  }
1334
- /**
1335
- * One tool call as a Section, mirroring the tool-definition parameter
1336
- * card: the head names the call target (mono, with the parsed argument
1337
- * count), the body lists the arguments as name/value rows. Arguments that
1338
- * are not a parseable JSON object fall back to the raw payload inside the
1339
- * same card. Shared by assistant tool-call blocks (`→`) and the call half
1340
- * of a tool result (`←`).
1341
- */
1342
1304
  function ToolCallCard(props) {
1343
1305
  const args = React.useMemo(() => parseArgs(props.argsRaw), [props.argsRaw]);
1344
1306
  return /* @__PURE__ */ React.createElement(Section, {
1345
1307
  label: (props.arrow ?? "→") + " " + props.name,
1346
1308
  labelClass: "lc-ts-call-name",
1347
- count: args !== null ? Object.keys(args).length : void 0
1309
+ meta: props.status
1348
1310
  }, args !== null ? Object.keys(args).map((k) => /* @__PURE__ */ React.createElement(CallArgRow, {
1349
1311
  key: k,
1350
1312
  name: k,
1351
1313
  value: args[k]
1352
1314
  })) : typeof props.argsRaw === "string" && props.argsRaw !== "" ? /* @__PURE__ */ React.createElement("pre", { className: "lc-ts-desc-body lc-br-dim" }, props.argsRaw) : null);
1353
1315
  }
1354
- /** One parsed call argument: the name on the left, the value on the right. */
1355
1316
  function CallArgRow(props) {
1356
1317
  const v = props.value;
1357
1318
  const text = typeof v === "string" ? v : v === void 0 ? "" : JSON.stringify(v);
1358
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));
1359
1320
  }
1360
- /**
1361
- * The actual content of one surface element, joined from the conversation
1362
- * snapshot — rendered as a uniform Section stack (see BlocksBody); the
1363
- * element row's open body wraps the stack in `lc-br-content`.
1364
- */
1365
1321
  function NodeContent(props) {
1366
1322
  const { node, conv, rich, img, labels } = props;
1367
1323
  if (conv === void 0) {
@@ -1380,18 +1336,22 @@ window.__ModuleLoader__.load({
1380
1336
  img,
1381
1337
  labels
1382
1338
  });
1383
- if (conv.kind === "tool-result") return /* @__PURE__ */ React.createElement(React.Fragment, null, conv.call != null ? /* @__PURE__ */ React.createElement(ToolCallCard, {
1384
- arrow: "←",
1385
- name: conv.call.name,
1386
- argsRaw: conv.call.argsRaw
1387
- }) : null, Array.isArray(conv.content) ? /* @__PURE__ */ React.createElement(BlocksBody, {
1388
- blocks: conv.content,
1389
- richable: false,
1390
- textLabel: labels.result,
1391
- rich,
1392
- img,
1393
- labels
1394
- }) : 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
+ }
1395
1355
  if (conv.kind === "compaction") return typeof conv.summary === "string" && conv.summary !== "" ? /* @__PURE__ */ React.createElement(TextSection, {
1396
1356
  label: labels.summary,
1397
1357
  text: conv.summary,
@@ -1407,19 +1367,16 @@ window.__ModuleLoader__.load({
1407
1367
  });
1408
1368
  return /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, props.hint);
1409
1369
  }
1410
- /** Group an assembled surface's nodes by category (for per-category counts). */
1411
1370
  function byCatOf(asm) {
1412
1371
  const m = {};
1413
1372
  for (const n of asm.nodes) (m[n.cat] ??= []).push(n);
1414
1373
  return m;
1415
1374
  }
1416
- /** Count one category's elements in an assembled surface (header cats included). */
1417
1375
  function countOf(asm, c) {
1418
1376
  if (c === "system") return asm.header !== null && asm.header.system !== void 0 ? 1 : 0;
1419
1377
  if (c === "tools") return asm.header !== null ? asm.header.tools.length : 0;
1420
1378
  return byCatOf(asm)[c]?.length ?? 0;
1421
1379
  }
1422
- /** The last request of `turn` in a seq-ordered timeline, or null. */
1423
1380
  function lastOfTurn(requests, turn) {
1424
1381
  for (let i = requests.length - 1; i >= 0; i--) if ((requests[i].turn ?? 0) === turn) return requests[i];
1425
1382
  return null;
@@ -1520,9 +1477,11 @@ window.__ModuleLoader__.load({
1520
1477
  const toggleElem = (key) => {
1521
1478
  setOpenElem(openElem === key ? null : key);
1522
1479
  };
1523
- /** One expandable element row (preview line; the open body is the
1524
- * uniform indented section stack `lc-br-content`). */
1525
- 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) => {
1526
1485
  const open = openElem === key;
1527
1486
  return /* @__PURE__ */ React.createElement("div", {
1528
1487
  key,
@@ -1533,7 +1492,10 @@ window.__ModuleLoader__.load({
1533
1492
  onClick: () => {
1534
1493
  toggleElem(key);
1535
1494
  }
1536
- }, /* @__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);
1537
1499
  };
1538
1500
  const catBody = (c) => {
1539
1501
  if (c === "system") {
@@ -1565,17 +1527,18 @@ window.__ModuleLoader__.load({
1565
1527
  });
1566
1528
  }
1567
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);
1568
1532
  let tag = null;
1569
1533
  let preview = nodeText(n);
1570
1534
  if (n.cat === "tool") {
1571
- tag = n.skill ? t("node.skillTag", { name: n.skill }) : (n.tool ?? "?") + (n.err ? " ⚠" : "");
1572
- 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");
1573
1537
  } else if (n.cat === "assistant" && Array.isArray(n.calls) && n.calls.length > 0) {
1574
1538
  tag = n.calls.join(" › ");
1575
- preview = (n.text !== void 0 && n.text !== "" ? n.text : null) ?? blockSummaryOf(bySeq.get(n.seq)) ?? t("node.empty");
1576
- } 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;
1577
1541
  else if (n.cat === "user") {
1578
- const conv = bySeq.get(n.seq);
1579
1542
  const imgCount = conv !== void 0 && Array.isArray(conv.content) ? conv.content.filter((b) => imageRefOf(b) !== null).length : 0;
1580
1543
  if (imgCount > 0 && openElem !== `n${n.seq}`) tag = t("attach.image") + (imgCount > 1 ? " ×" + String(imgCount) : "");
1581
1544
  } else if (n.cat === "inject" && !n.skill) {
@@ -1584,7 +1547,7 @@ window.__ModuleLoader__.load({
1584
1547
  }
1585
1548
  return elemRow(`n${n.seq}`, tag, preview, n.tokens, n.time, /* @__PURE__ */ React.createElement(NodeContent, {
1586
1549
  node: n,
1587
- conv: bySeq.get(n.seq),
1550
+ conv,
1588
1551
  rich,
1589
1552
  img: {
1590
1553
  Card: ImageCard,
@@ -1597,10 +1560,12 @@ window.__ModuleLoader__.load({
1597
1560
  result: t("block.result"),
1598
1561
  summary: t("block.summary"),
1599
1562
  images: t("attach.images"),
1600
- 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"))
1601
1566
  },
1602
- hint: bySeq.get(n.seq) === void 0 && awaiting ? t("browser.loading") : t("browser.noContent")
1603
- }));
1567
+ hint: conv === void 0 && awaiting ? t("browser.loading") : t("browser.noContent")
1568
+ }), rowErr);
1604
1569
  });
1605
1570
  };
1606
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", {
@@ -1655,12 +1620,9 @@ window.__ModuleLoader__.load({
1655
1620
  //#endregion
1656
1621
  //#region src/client/components/stackedBar.tsx
1657
1622
  /**
1658
- * DSH's automatic compaction trigger as a fraction of the routed context
1659
- * window the default `thresholdRatio` of `@deepseek-ai/dsh-compaction-basic`
1660
- * (it compacts at step boundaries once `floor(contextWindow × ratio)` is
1661
- * reached). DSH does not publish the configured ratio to plugins or clients,
1662
- * so the UI mirrors the default here; deployments that tune `thresholdRatio`
1663
- * / `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.
1664
1626
  */
1665
1627
  const AUTO_COMPACT_RATIO = .8;
1666
1628
  function makeStackedBar(kit) {
@@ -1930,6 +1892,145 @@ window.__ModuleLoader__.load({
1930
1892
  };
1931
1893
  }
1932
1894
  //#endregion
1895
+ //#region src/client/components/settingsCard.tsx
1896
+ function PrefRow(props) {
1897
+ const [open, setOpen] = React.useState(false);
1898
+ const active = props.options.find((o) => o.id === props.value)?.label ?? props.value;
1899
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-settings-row" }, /* @__PURE__ */ React.createElement("span", { className: "lc-settings-label" }, props.label), /* @__PURE__ */ React.createElement(_deepseek_ai_dsh_client_ui_primitives.Menu, {
1900
+ open,
1901
+ onClose: () => {
1902
+ setOpen(false);
1903
+ },
1904
+ items: props.options,
1905
+ selectedId: props.value,
1906
+ onSelect: (id) => {
1907
+ setOpen(false);
1908
+ props.onPick(id);
1909
+ },
1910
+ align: "end",
1911
+ portal: true,
1912
+ anchor: /* @__PURE__ */ React.createElement("button", {
1913
+ type: "button",
1914
+ className: "lc-settings-select",
1915
+ disabled: props.disabled,
1916
+ "aria-haspopup": "menu",
1917
+ "aria-expanded": open,
1918
+ onClick: () => {
1919
+ setOpen((v) => !v);
1920
+ }
1921
+ }, active, /* @__PURE__ */ React.createElement(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, null))
1922
+ }));
1923
+ }
1924
+ function makeSettingsCard(kit) {
1925
+ const { t } = kit;
1926
+ return function SettingsCard(props) {
1927
+ const [open, setOpen] = React.useState(false);
1928
+ const state = typeof props.useContextSettings === "function" ? props.useContextSettings((s) => s) : void 0;
1929
+ if (state === void 0 || state.status === "unavailable") return null;
1930
+ const disabled = state.status !== "ready" || !state.writable;
1931
+ return /* @__PURE__ */ React.createElement("li", { className: "lc-settings-card" + (open ? " lc-settings-open" : "") }, /* @__PURE__ */ React.createElement("button", {
1932
+ type: "button",
1933
+ className: "lc-settings-head",
1934
+ "aria-expanded": open,
1935
+ "aria-label": `${t(open ? "settings.collapse" : "settings.expand")}: ${t("settings.title")}`,
1936
+ onClick: () => {
1937
+ setOpen(!open);
1938
+ }
1939
+ }, /* @__PURE__ */ React.createElement("span", { className: "lc-settings-headtext" }, /* @__PURE__ */ React.createElement("span", { className: "lc-settings-name" }, t("settings.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-settings-desc" }, t("settings.desc"))), /* @__PURE__ */ React.createElement(_deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { className: "lc-settings-chevron" })), open ? /* @__PURE__ */ React.createElement("div", { className: "lc-settings-body" }, !state.writable && state.status === "ready" ? /* @__PURE__ */ React.createElement("p", {
1940
+ className: "lc-settings-note",
1941
+ role: "status"
1942
+ }, t("settings.readOnly")) : null, /* @__PURE__ */ React.createElement(PrefRow, {
1943
+ label: t("settings.gran"),
1944
+ value: state.granularity,
1945
+ disabled,
1946
+ options: [{
1947
+ id: "step",
1948
+ label: t("gran.step")
1949
+ }, {
1950
+ id: "turn",
1951
+ label: t("gran.turn")
1952
+ }],
1953
+ onPick: (id) => {
1954
+ props.set?.("defaultGranularity", id);
1955
+ }
1956
+ }), /* @__PURE__ */ React.createElement(PrefRow, {
1957
+ label: t("settings.mode"),
1958
+ value: state.mode,
1959
+ disabled,
1960
+ options: [{
1961
+ id: "total",
1962
+ label: t("gran.total")
1963
+ }, {
1964
+ id: "diff",
1965
+ label: t("gran.diff")
1966
+ }],
1967
+ onPick: (id) => {
1968
+ props.set?.("defaultTrendMode", id);
1969
+ }
1970
+ })) : null);
1971
+ };
1972
+ }
1973
+ //#endregion
1974
+ //#region src/client/settings.ts
1975
+ function prefsOf(value) {
1976
+ if (value === null || typeof value !== "object") return {};
1977
+ const v = value;
1978
+ return {
1979
+ ...v.defaultGranularity === "step" || v.defaultGranularity === "turn" ? { granularity: v.defaultGranularity } : {},
1980
+ ...v.defaultTrendMode === "total" || v.defaultTrendMode === "diff" ? { mode: v.defaultTrendMode } : {}
1981
+ };
1982
+ }
1983
+ function createContextSettings() {
1984
+ let state = {
1985
+ status: "loading",
1986
+ granularity: "step",
1987
+ mode: "total",
1988
+ writable: false
1989
+ };
1990
+ let scope;
1991
+ const listeners = /* @__PURE__ */ new Set();
1992
+ const publish = (next) => {
1993
+ if (next.status === state.status && next.granularity === state.granularity && next.mode === state.mode && next.writable === state.writable) return;
1994
+ state = next;
1995
+ for (const listener of listeners) listener();
1996
+ };
1997
+ return {
1998
+ store: {
1999
+ subscribe(listener) {
2000
+ listeners.add(listener);
2001
+ return () => {
2002
+ listeners.delete(listener);
2003
+ };
2004
+ },
2005
+ getSnapshot: () => state
2006
+ },
2007
+ defaultGranularity: () => state.granularity,
2008
+ defaultTrendMode: () => state.mode,
2009
+ attach(bound) {
2010
+ scope = bound;
2011
+ const sync = () => {
2012
+ const snap = bound.getSnapshot();
2013
+ const prefs = prefsOf(snap.value);
2014
+ publish({
2015
+ status: snap.status === "ready" || snap.status === "unavailable" ? snap.status : "loading",
2016
+ granularity: prefs.granularity ?? state.granularity,
2017
+ mode: prefs.mode ?? state.mode,
2018
+ writable: snap.writable
2019
+ });
2020
+ };
2021
+ sync();
2022
+ return bound.subscribe(sync);
2023
+ },
2024
+ set(field, value) {
2025
+ publish({
2026
+ ...state,
2027
+ ...prefsOf({ [field]: value })
2028
+ });
2029
+ scope?.set(field, value);
2030
+ }
2031
+ };
2032
+ }
2033
+ //#endregion
1933
2034
  //#region src/client/components/events.tsx
1934
2035
  const EVENT_ICONS = {
1935
2036
  compaction: "✂",
@@ -1938,7 +2039,6 @@ window.__ModuleLoader__.load({
1938
2039
  model: "⇄",
1939
2040
  mode: "⇄"
1940
2041
  };
1941
- /** Build the event text helpers bound to the translate function. */
1942
2042
  function makeEventText(t) {
1943
2043
  function eventLabel(ev) {
1944
2044
  if (ev.kind === "compaction") return t("ev.compaction", { n: ev.count || 0 });
@@ -1955,11 +2055,8 @@ window.__ModuleLoader__.load({
1955
2055
  return label;
1956
2056
  }
1957
2057
  /**
1958
- * Where this event sits in the request timeline, as a label or null.
1959
- * Boundary events (compaction/prune) show the GAP they sit in: same-turn
1960
- * "Step 2→3", cross-turn "Turn 50 · Step 8 → Turn 51 · Step 1". Injections
1961
- * and model switches belong to one request and keep the single point.
1962
- * 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.
1963
2060
  */
1964
2061
  function eventAt(ev) {
1965
2062
  if (ev.kind === "compaction" || ev.kind === "prune") {
@@ -2028,20 +2125,14 @@ window.__ModuleLoader__.load({
2028
2125
  };
2029
2126
  }
2030
2127
  const PLUGIN_REPO = "https://github.com/bowenliang123/dsh-context";
2031
- /** Short `owner/repo` form of the GitHub URL, for display only. */
2032
2128
  const PLUGIN_REPO_SHORT = PLUGIN_REPO.replace(/^https?:\/\/github\.com\//, "");
2033
2129
  //#endregion
2034
2130
  //#region src/client/latestVersion.ts
2035
2131
  /**
2036
- * Latest published version of this plugin on the npm registry the one
2037
- * live check the Plugin info card performs. Fetched lazily on the first
2038
- * card mount and cached with a 1-hour TTL, so the registry is hit at most
2039
- * once per hour per page. Every failure mode (offline, CSP-blocked,
2040
- * non-200, malformed body) narrows to null = no upgrade hint, so the card
2041
- * 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.
2042
2134
  */
2043
2135
  const REGISTRY_URL = "https://registry.npmjs.org/dsh-context/latest";
2044
- /** How long a registry result stays fresh before the next check refetches. */
2045
2136
  const TTL_MS = 36e5;
2046
2137
  let cached = null;
2047
2138
  function fetchLatestVersion() {
@@ -2076,7 +2167,7 @@ window.__ModuleLoader__.load({
2076
2167
  return function PluginInfo() {
2077
2168
  const [latest, setLatest] = React.useState(null);
2078
2169
  React.useEffect(() => {
2079
- if ("0.28.0".includes("-dev")) return;
2170
+ if ("0.30.0".includes("-dev")) return;
2080
2171
  let on = true;
2081
2172
  fetchLatestVersion().then((v) => {
2082
2173
  if (on && v) setLatest(v);
@@ -2085,8 +2176,8 @@ window.__ModuleLoader__.load({
2085
2176
  on = false;
2086
2177
  };
2087
2178
  }, []);
2088
- const update = latest !== null && isNewerVersion(latest, "0.28.0") ? latest : null;
2089
- const nameValue = ["dsh-context (v0.28.0)"];
2179
+ const update = latest !== null && isNewerVersion(latest, "0.30.0") ? latest : null;
2180
+ const nameValue = ["dsh-context (v0.30.0)"];
2090
2181
  if (update) nameValue.push(/* @__PURE__ */ React.createElement("span", {
2091
2182
  key: "update",
2092
2183
  className: "lc-pi-update"
@@ -2211,7 +2302,6 @@ window.__ModuleLoader__.load({
2211
2302
  }
2212
2303
  return any ? total : null;
2213
2304
  }
2214
- /** Short money format: two decimals at and above one unit, two significant digits below. */
2215
2305
  function formatCost(amount, currency) {
2216
2306
  return (currency === "cny" ? "¥" : "$") + (amount >= 1 ? amount.toFixed(2) : amount.toPrecision(2));
2217
2307
  }
@@ -2236,15 +2326,10 @@ window.__ModuleLoader__.load({
2236
2326
  //#endregion
2237
2327
  //#region src/client/components/statsBoard.tsx
2238
2328
  /**
2239
- * Cache-hit share of billed prompt-side input — same buckets as the harness's
2240
- * chat stats line below the input box (`cacheReadTokens` over the three
2241
- * disjoint billed buckets: uncached + reads + writes). Unlike that line's
2242
- * whole-percent rounding, this one TRUNCATES to two decimal places (cut, not
2243
- * round). Null when no input was billed. A tiny epsilon keeps a double stored
2244
- * a hair below a cent boundary (e.g. 80.00 as 79.9999999999…) from losing its
2245
- * last digit — with integer token counts no genuine value ever sits that close
2246
- * to a boundary, so the epsilon can only absorb float noise. Bucket reads go
2247
- * 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.
2248
2333
  */
2249
2334
  function cacheHitPercent(usage) {
2250
2335
  const uncached = numOf(usage.uncachedInputTokens);
@@ -2291,12 +2376,8 @@ window.__ModuleLoader__.load({
2291
2376
  //#endregion
2292
2377
  //#region src/client/components/trendChart.tsx
2293
2378
  /**
2294
- * Collapse a per-step request timeline into one bar per turn: each turn is
2295
- * represented by its LAST step's record (the context state when the turn
2296
- * finished), tagged with the number of steps the turn spans so the bar can
2297
- * keep the turn's column width and the detail can label it. Requests of one
2298
- * turn are consecutive in the log, so a run of equal turns is replaced by
2299
- * 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.
2300
2381
  */
2301
2382
  function aggregateByTurn(requests) {
2302
2383
  const out = [];
@@ -2320,10 +2401,8 @@ window.__ModuleLoader__.load({
2320
2401
  return out;
2321
2402
  }
2322
2403
  /**
2323
- * Attach each boundary event (compaction/prune) to the first request
2324
- * logged after it one entry per request index, for the marker above
2325
- * the bar and the range chip in the detail header. Shared by TrendChart
2326
- * 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.
2327
2406
  */
2328
2407
  function attachMarkers(requests, events) {
2329
2408
  const markers = new Array(requests.length);
@@ -2345,14 +2424,8 @@ window.__ModuleLoader__.load({
2345
2424
  const anchorOf = (req) => typeof req.prompt === "number" && req.prompt > 0 && req.total > 0 ? req.prompt / req.total : 1;
2346
2425
  const barTotalOf = (req) => typeof req.prompt === "number" && req.prompt > 0 ? req.prompt : req.total;
2347
2426
  /**
2348
- * Transform one request into its per-request incremental diff for the
2349
- * diff mode. Each category becomes |current previous| (the magnitude of
2350
- * change, stacked like the cumulative bars), `total` becomes the summed
2351
- * magnitude, and `net` keeps the signed change for the tooltip. The first
2352
- * request (no previous) diff from empty is zero, so the diff mode's scale
2353
- * is driven purely by inter-request changes rather than the initial
2354
- * cumulative baseline. Provider prompt/output are dropped: they are
2355
- * 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).
2356
2429
  */
2357
2430
  const diffOf = (req, prev) => {
2358
2431
  const out = { ...req };
@@ -2422,6 +2495,17 @@ window.__ModuleLoader__.load({
2422
2495
  grp.count++;
2423
2496
  grp.span += req.stepCount ?? 1;
2424
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
+ }
2425
2509
  const scrollRef = React.useRef(null);
2426
2510
  const scrolledOnce = React.useRef(false);
2427
2511
  const lastGranRef = React.useRef(props.granularity);
@@ -2444,6 +2528,35 @@ window.__ModuleLoader__.load({
2444
2528
  right
2445
2529
  });
2446
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
+ };
2447
2560
  React.useLayoutEffect(() => {
2448
2561
  const el = scrollRef.current;
2449
2562
  if (el === null) return;
@@ -2464,6 +2577,7 @@ window.__ModuleLoader__.load({
2464
2577
  el.scrollLeft = el.scrollWidth;
2465
2578
  } else if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) el.scrollLeft = el.scrollWidth;
2466
2579
  updateEdges(el);
2580
+ updateTurnLabels(el);
2467
2581
  });
2468
2582
  const tipOf = (req) => {
2469
2583
  const head = req.stepCount !== void 0 && req.stepCount > 1 ? t("tip.turn", {
@@ -2486,6 +2600,7 @@ window.__ModuleLoader__.load({
2486
2600
  ref: scrollRef,
2487
2601
  onScroll: (e) => {
2488
2602
  updateEdges(e.currentTarget);
2603
+ updateTurnLabels(e.currentTarget);
2489
2604
  }
2490
2605
  }, edges.left ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-l" }) : null, /* @__PURE__ */ React.createElement("div", {
2491
2606
  className: "lc-chart",
@@ -2512,12 +2627,11 @@ window.__ModuleLoader__.load({
2512
2627
  }
2513
2628
  }, groups.map((grp, gi) => {
2514
2629
  const on = props.activeTurn === grp.turn;
2515
- const blockW = grp.agg ? BAR_W : grp.span * 16 - BAR_GAP;
2516
2630
  return /* @__PURE__ */ React.createElement("span", {
2517
2631
  key: `turn-${gi}`,
2518
2632
  className: "lc-turn" + (on ? " lc-turn-on" : ""),
2519
2633
  style: {
2520
- width: `${blockW}px`,
2634
+ width: `${turnWidths[gi]}px`,
2521
2635
  background: TURN_FILLS[gi % TURN_FILLS.length]
2522
2636
  },
2523
2637
  title: `T${grp.turn}`,
@@ -2527,7 +2641,7 @@ window.__ModuleLoader__.load({
2527
2641
  onClick: () => {
2528
2642
  props.onPickTurn(grp.turn);
2529
2643
  }
2530
- }, `T${grp.turn}`);
2644
+ }, /* @__PURE__ */ React.createElement("span", { className: "lc-turn-label" }, `T${grp.turn}`));
2531
2645
  }))), edges.right ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-r" }) : null);
2532
2646
  };
2533
2647
  }
@@ -2541,7 +2655,7 @@ window.__ModuleLoader__.load({
2541
2655
  "model",
2542
2656
  "mode"
2543
2657
  ];
2544
- function makeContextView(ctx, kit) {
2658
+ function makeContextView(ctx, kit, settings) {
2545
2659
  const { t } = kit;
2546
2660
  const StackedBar = makeStackedBar(kit);
2547
2661
  const CurrentComposition = makeCurrentComposition(kit, StackedBar, makeLegend(kit));
@@ -2564,8 +2678,8 @@ window.__ModuleLoader__.load({
2564
2678
  const [hoveredSeq, setHoveredSeq] = React.useState(null);
2565
2679
  const [hoverTurn, setHoverTurn] = React.useState(null);
2566
2680
  const [tick, setTick] = React.useState(0);
2567
- const [granularity, setGranularity] = React.useState("step");
2568
- const [trendMode, setTrendMode] = React.useState("total");
2681
+ const [granularity, setGranularity] = React.useState(() => settings.defaultGranularity());
2682
+ const [trendMode, setTrendMode] = React.useState(() => settings.defaultTrendMode());
2569
2683
  const [focusTurn, setFocusTurn] = React.useState(null);
2570
2684
  const [hoverCat, setHoverCat] = React.useState(null);
2571
2685
  const [pickedKinds, setPickedKinds] = React.useState([...EVENT_KINDS]);
@@ -2751,7 +2865,7 @@ window.__ModuleLoader__.load({
2751
2865
  }
2752
2866
  //#endregion
2753
2867
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles.css.mjs
2754
- 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-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}";
2755
2869
  const tagId = "dsh-context/styles.css";
2756
2870
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2757
2871
  const tag = document.createElement("style");
@@ -2794,7 +2908,8 @@ window.__ModuleLoader__.load({
2794
2908
  }, "dsh-context: dictionaries");
2795
2909
  const t = ctx.locale.bind(NS);
2796
2910
  const kit = makeViewKit(t);
2797
- const ContextView = makeContextView(ctx, kit);
2911
+ const settings = createContextSettings();
2912
+ const ContextView = makeContextView(ctx, kit, settings);
2798
2913
  ctx.effect(() => {
2799
2914
  const noop = () => {};
2800
2915
  const sessions = ctx.get("sessions");
@@ -2828,6 +2943,26 @@ window.__ModuleLoader__.load({
2828
2943
  inject: (sessionId) => ({ hooks: { contextModal: modalStoreOf(sessionId) } })
2829
2944
  }, (props) => h(ContextModal, props));
2830
2945
  });
2946
+ ctx.inject(["settingsScope"], (raw) => {
2947
+ const c = raw;
2948
+ const binder = c.settingsScope;
2949
+ if (binder === void 0) return;
2950
+ c.effect(() => settings.attach(binder.bind({ namespace: NS })), "dsh-context: settings scope");
2951
+ const SettingsCard = makeSettingsCard(kit);
2952
+ c.slots.inject("settings.plugin.item", () => {
2953
+ return c.slots.register({
2954
+ name: "settings.plugin.item",
2955
+ key: NS,
2956
+ locale: NS,
2957
+ inject: () => ({
2958
+ hooks: { contextSettings: settings.store },
2959
+ set: (field, value) => {
2960
+ settings.set(field, value);
2961
+ }
2962
+ })
2963
+ }, (props) => h(SettingsCard, props));
2964
+ });
2965
+ });
2831
2966
  }
2832
2967
  module.exports = {
2833
2968
  name: "dsh-context",