dsh-context 0.32.0 → 0.34.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
@@ -39,8 +39,6 @@ window.__ModuleLoader__.load({
39
39
  "plugin.hint": "The best DSH context plugin ⭐",
40
40
  "plugin.name": "插件",
41
41
  "plugin.github": "GitHub",
42
- "tools.top": "工具定义 Top:",
43
- "tools.more": "等 {n} 个",
44
42
  "trend.title": "上下文趋势",
45
43
  "gran.step": "步骤",
46
44
  "gran.turn": "轮次",
@@ -48,6 +46,7 @@ window.__ModuleLoader__.load({
48
46
  "settings.desc": "上下文面板的展示偏好",
49
47
  "settings.gran": "趋势图默认粒度",
50
48
  "settings.mode": "趋势图默认展示",
49
+ "settings.fileSort": "文件活动默认排序",
51
50
  "settings.expand": "展开",
52
51
  "settings.collapse": "收起",
53
52
  "settings.readOnly": "当前环境的设置为只读",
@@ -67,10 +66,11 @@ window.__ModuleLoader__.load({
67
66
  "brief.input": "输入",
68
67
  "brief.reply": "回复",
69
68
  "brief.more": "+{n}",
69
+ "brief.noInputs": "(无新增)",
70
70
  "brief.locate": "在上下文浏览器中查看",
71
- "brief.turnTip": "这一轮开场时用户发送的消息",
72
- "brief.inputTip": "这一步新进入上下文的内容(通常是上一步工具调用的结果)",
73
- "brief.replyTip": "这一步模型返回的内容(文本回复或工具调用)",
71
+ "brief.turnTip": "这一轮开场时用户发送的消息;回合内的每一步都会回显同一条",
72
+ "brief.inputTip": "上一步回复之后、本次请求发出前新进入上下文的内容——通常是上一步所调工具的结果;每轮的第一步无新增",
73
+ "brief.replyTip": "这一步模型返回的内容(文本回复或工具调用);此步发起的调用,其结果会出现在下一柱的「输入」行",
74
74
  "events.title": "上下文事件",
75
75
  "events.empty": "暂无上下文事件(压缩、注入、模型切换会出现在这里)",
76
76
  "events.at": "第 {t} 轮 · 第 {s} 步",
@@ -81,10 +81,43 @@ window.__ModuleLoader__.load({
81
81
  "kind.prune": "剪枝",
82
82
  "kind.model": "切换",
83
83
  "kind.mode": "模式",
84
- "nodes.title": "消息构成",
85
- "nodes.hint": "当前模型可见的消息,最新在前",
86
- "nodes.more": "… 更早的 {n} 条消息已省略",
87
- "nodes.empty": "当前没有模型可见的消息",
84
+ "files.title": "文件活动",
85
+ "files.scopeLatest": "截至最新 · 跟随趋势图的选择",
86
+ "files.kind.all": "全部",
87
+ "files.kind.read": "读取",
88
+ "files.kind.write": "写入",
89
+ "files.kind.search": "搜索",
90
+ "files.kind.image": "图片",
91
+ "files.chipTip": "{files} 个文件 · {ops} 次操作",
92
+ "files.search": "按路径过滤…",
93
+ "files.sort.count": "按次数",
94
+ "files.sort.latest": "按最新",
95
+ "files.sort.path": "按路径",
96
+ "files.sortTip": "文件排序:按操作次数(选中分类时按该分类的次数)、最近操作时间或路径",
97
+ "files.files": "{n} 个文件",
98
+ "files.deltaTip": "按 edit/write 调用参数估算的累计行数变化:+ 新增 / − 删除",
99
+ "files.empty": "该范围内没有文件读取、写入或搜索操作",
100
+ "files.noMatch": "没有匹配当前过滤条件的文件",
101
+ "files.locate": "在上下文浏览器中查看此次操作",
102
+ "files.errs": "{n} 次失败",
103
+ "files.form.image": "图片",
104
+ "files.form.dir": "目录",
105
+ "files.form.text": "文件",
106
+ "agents.title": "Agent 网络",
107
+ "agents.sub": "当前 Agent 与子 Agent 的实时上下文 · 点击节点跳转会话",
108
+ "agents.chip.count": "{n} 个 Agent",
109
+ "agents.chip.running": "{n} 个运行中",
110
+ "agents.chip.tokens": "上下文合计 {n}",
111
+ "agents.more": "另有 {n} 个未显示",
112
+ "agents.solo": "还没有子 Agent — 当 Agent 通过 subagent 工具委派任务时,协作网络会在这里展开",
113
+ "agents.self": "当前",
114
+ "agents.running": "运行中",
115
+ "agents.legend.free": "空闲窗口",
116
+ "agents.oneshot": "一次性",
117
+ "agents.continuable": "多轮",
118
+ "agents.requests": "{n} 次请求",
119
+ "agents.billed": "累计计费 {n}",
120
+ "agents.open": "点击打开会话 →",
88
121
  "loading": "正在读取会话日志…",
89
122
  "error": "上下文数据读取失败:",
90
123
  "error.retry": "重试",
@@ -126,6 +159,8 @@ window.__ModuleLoader__.load({
126
159
  "browser.noEpoch": "该步骤的头部内容(系统提示词 / 工具定义)不在保留范围内",
127
160
  "browser.noContent": "完整内容不在当前加载的消息窗口内(在聊天页加载更早历史后可查看)",
128
161
  "browser.loading": "正在从更早的会话历史加载完整内容…",
162
+ "browser.notInLog": "会话日志中未找到该条目的完整内容(可能已被压缩或清理)",
163
+ "browser.loadFailed": "内容加载失败,点击重试",
129
164
  "browser.preview": "预览",
130
165
  "tool.desc": "描述",
131
166
  "tool.params": "参数",
@@ -194,8 +229,6 @@ window.__ModuleLoader__.load({
194
229
  "plugin.hint": "The best DSH context plugin ⭐",
195
230
  "plugin.name": "Plugin",
196
231
  "plugin.github": "GitHub",
197
- "tools.top": "Top Tool Schemas:",
198
- "tools.more": "of {n}",
199
232
  "trend.title": "Context Trend",
200
233
  "gran.step": "Step",
201
234
  "gran.turn": "Turn",
@@ -203,6 +236,7 @@ window.__ModuleLoader__.load({
203
236
  "settings.desc": "Display preferences for the Context panel",
204
237
  "settings.gran": "Default trend granularity",
205
238
  "settings.mode": "Default trend display",
239
+ "settings.fileSort": "File Activity default sort",
206
240
  "settings.expand": "Expand",
207
241
  "settings.collapse": "Collapse",
208
242
  "settings.readOnly": "Settings are read-only in this environment",
@@ -222,10 +256,11 @@ window.__ModuleLoader__.load({
222
256
  "brief.input": "In",
223
257
  "brief.reply": "Response",
224
258
  "brief.more": "+{n}",
259
+ "brief.noInputs": "(nothing new)",
225
260
  "brief.locate": "Reveal in Context Browser",
226
- "brief.turnTip": "The user message that opened this turn",
227
- "brief.inputTip": "What newly entered the context for this step (usually results of the previous tool calls)",
228
- "brief.replyTip": "What the model returned on this step (a text reply or tool calls)",
261
+ "brief.turnTip": "The user message that opened this turn — every step of the turn recalls it",
262
+ "brief.inputTip": "What entered after the previous reply and before this request went out usually the previous tool-call results; a turn opener has nothing new",
263
+ "brief.replyTip": "What the model returned on this step (a text reply or tool calls) — results of calls made here appear in the In row of the next bar",
229
264
  "events.title": "Context Events",
230
265
  "events.empty": "No context events yet (compaction, injections, model switches appear here)",
231
266
  "events.at": "Turn {t} · Step {s}",
@@ -236,10 +271,43 @@ window.__ModuleLoader__.load({
236
271
  "kind.prune": "Prune",
237
272
  "kind.model": "Switch",
238
273
  "kind.mode": "Mode",
239
- "nodes.title": "Messages",
240
- "nodes.hint": "currently model-visible, newest first",
241
- "nodes.more": "… {n} earlier messages omitted",
242
- "nodes.empty": "No model-visible messages right now",
274
+ "files.title": "File Activity",
275
+ "files.scopeLatest": "Up to latest · follows the trend chart pick",
276
+ "files.kind.all": "All",
277
+ "files.kind.read": "Read",
278
+ "files.kind.write": "Written",
279
+ "files.kind.search": "Searched",
280
+ "files.kind.image": "Images",
281
+ "files.chipTip": "{files} files · {ops} ops",
282
+ "files.search": "Filter by path…",
283
+ "files.sort.count": "Most active",
284
+ "files.sort.latest": "Latest",
285
+ "files.sort.path": "By path",
286
+ "files.sortTip": "Sort files by operation counts (of the selected kind when a kind chip is active), most recent activity, or path",
287
+ "files.files": "{n} files",
288
+ "files.deltaTip": "Cumulative line change estimated from edit/write call arguments: + added / − removed",
289
+ "files.empty": "No file reads, writes, or searches in this range",
290
+ "files.noMatch": "No files match the current filters",
291
+ "files.locate": "Reveal this operation in the Context Browser",
292
+ "files.errs": "{n} failed",
293
+ "files.form.image": "Image",
294
+ "files.form.dir": "Directory",
295
+ "files.form.text": "File",
296
+ "agents.title": "Agent Network",
297
+ "agents.sub": "Live context of this agent and its subagents · click a node to open its session",
298
+ "agents.chip.count": "{n} agents",
299
+ "agents.chip.running": "{n} running",
300
+ "agents.chip.tokens": "{n} tokens in context",
301
+ "agents.more": "{n} more not shown",
302
+ "agents.solo": "No subagents yet — when the agent delegates work through the subagent tool, the network unfolds here",
303
+ "agents.self": "current",
304
+ "agents.running": "running",
305
+ "agents.legend.free": "free window",
306
+ "agents.oneshot": "one-shot",
307
+ "agents.continuable": "continuable",
308
+ "agents.requests": "{n} requests",
309
+ "agents.billed": "{n} billed",
310
+ "agents.open": "click to open →",
243
311
  "loading": "Reading the session log…",
244
312
  "error": "Failed to read context data: ",
245
313
  "error.retry": "Retry",
@@ -281,6 +349,8 @@ window.__ModuleLoader__.load({
281
349
  "browser.noEpoch": "The header content (System Prompt / Tool Schemas) of this step is outside retention",
282
350
  "browser.noContent": "Full content is outside the loaded message window (load older history in Chat to view)",
283
351
  "browser.loading": "Loading full content from older session history…",
352
+ "browser.notInLog": "This item is not in the session log anymore (it may have been compacted away or cleared)",
353
+ "browser.loadFailed": "Load failed — click to retry",
284
354
  "browser.preview": "Preview",
285
355
  "tool.desc": "Description",
286
356
  "tool.params": "Parameters",
@@ -435,8 +505,7 @@ window.__ModuleLoader__.load({
435
505
  "assistant",
436
506
  "tool"
437
507
  ];
438
- /** Category key → chart color, built once from CATS. */
439
- const CAT_COLORS = Object.fromEntries(CATS.map((c) => [c.key, c.color]));
508
+ Object.fromEntries(CATS.map((c) => [c.key, c.color]));
440
509
  function partsOf(breakdown) {
441
510
  return CATS.map((c) => {
442
511
  return {
@@ -478,6 +547,9 @@ window.__ModuleLoader__.load({
478
547
  return CATS.map((c) => ({
479
548
  key: c.key,
480
549
  color: c.color,
550
+ /* v8 ignore next 1 -- `shares` is initialized with system/tools and both
551
+ foldSurface arms assign every MESSAGE_CATS key, so each key is always
552
+ defined; the fallback is defensive. */
481
553
  value: shares[c.key] ?? 0
482
554
  }));
483
555
  }
@@ -534,7 +606,7 @@ window.__ModuleLoader__.load({
534
606
  * every field read below must re-prove itself (the no-white-screen
535
607
  * guarantee), so no field may borrow the wire type before its check.
536
608
  */
537
- function asRecord(value) {
609
+ function asRecord$1(value) {
538
610
  if (value === null || value === void 0 || typeof value !== "object") return null;
539
611
  return value;
540
612
  }
@@ -563,7 +635,7 @@ window.__ModuleLoader__.load({
563
635
  * during render and unmounting the conversation view.
564
636
  */
565
637
  function timelineOf(value) {
566
- const data = asRecord(value);
638
+ const data = asRecord$1(value);
567
639
  if (data === null) return null;
568
640
  const current = data.current;
569
641
  if (current !== null && typeof current === "object" && [
@@ -574,7 +646,7 @@ window.__ModuleLoader__.load({
574
646
  "assistant",
575
647
  "tool",
576
648
  "total"
577
- ].every((k) => typeof current[k] === "number") && Array.isArray(data.requests) && Array.isArray(data.events) && Array.isArray(data.nodes) && Array.isArray(data.archive) && Array.isArray(data.toolList)) return data;
649
+ ].every((k) => typeof current[k] === "number") && Array.isArray(data.requests) && Array.isArray(data.events) && Array.isArray(data.nodes) && Array.isArray(data.archive)) return data;
578
650
  const safeCurrent = current !== null && typeof current === "object" ? current : {};
579
651
  const cost = typeof data.cost === "object" && data.cost !== null && !Array.isArray(data.cost) ? data.cost : void 0;
580
652
  return {
@@ -591,7 +663,6 @@ window.__ModuleLoader__.load({
591
663
  tool: numOf(safeCurrent.tool),
592
664
  total: numOf(safeCurrent.total)
593
665
  },
594
- toolList: objectsOf(data.toolList),
595
666
  requests: objectsOf(data.requests),
596
667
  events: objectsOf(data.events),
597
668
  nodes: objectsOf(data.nodes),
@@ -612,7 +683,7 @@ window.__ModuleLoader__.load({
612
683
  * back to their derived anchor, so the UI degrades gracefully.
613
684
  */
614
685
  function contextPressureOf(value) {
615
- return asRecord(value);
686
+ return asRecord$1(value);
616
687
  }
617
688
  /**
618
689
  * Narrow a delivered projection value to the official token-meter
@@ -622,7 +693,7 @@ window.__ModuleLoader__.load({
622
693
  * own sums instead of mixing sources.
623
694
  */
624
695
  function contextBreakdownOf(value) {
625
- const data = asRecord(value);
696
+ const data = asRecord$1(value);
626
697
  if (data === null) return null;
627
698
  const { systemTokens, toolsTokens, messageTokens } = data;
628
699
  if (typeof systemTokens !== "number" || !Number.isFinite(systemTokens)) return null;
@@ -641,7 +712,7 @@ window.__ModuleLoader__.load({
641
712
  * usage yet) — callers drop the cache-hit cell to a dash.
642
713
  */
643
714
  function tokenUsageOf(value) {
644
- return asRecord(value);
715
+ return asRecord$1(value);
645
716
  }
646
717
  /**
647
718
  * Narrow a delivered projection value to the plugin's `contextHeaders`
@@ -655,7 +726,7 @@ window.__ModuleLoader__.load({
655
726
  * and the card falls back to its tokens-only note.
656
727
  */
657
728
  function headersOf(value) {
658
- const headers = asRecord(value);
729
+ const headers = asRecord$1(value);
659
730
  if (headers === null || !Array.isArray(headers.headers)) return null;
660
731
  for (const h of headers.headers) {
661
732
  if (h === null || typeof h !== "object") return null;
@@ -666,6 +737,157 @@ window.__ModuleLoader__.load({
666
737
  return headers;
667
738
  }
668
739
  //#endregion
740
+ //#region src/client/historyPage.ts
741
+ /** Narrow one served row to a validated durable event envelope, or null. */
742
+ function eventOf(entry) {
743
+ if (entry === null || typeof entry !== "object") return null;
744
+ const ev = entry.event;
745
+ if (ev === null || typeof ev !== "object") return null;
746
+ const e = ev;
747
+ if (typeof e.type !== "string" || typeof e.seq !== "number" || !Number.isFinite(e.seq)) return null;
748
+ const data = e.data !== null && typeof e.data === "object" ? e.data : {};
749
+ return {
750
+ type: e.type,
751
+ seq: e.seq,
752
+ data
753
+ };
754
+ }
755
+ /** All string texts of an event's message-content shape joined (compaction summaries). */
756
+ function textOf(blocks) {
757
+ if (!Array.isArray(blocks)) return null;
758
+ let out = "";
759
+ for (const b of blocks) {
760
+ const text = b !== null && typeof b === "object" ? b.text : void 0;
761
+ if (typeof text === "string") out += text;
762
+ }
763
+ return out.trim() === "" ? null : out;
764
+ }
765
+ /**
766
+ * One assistant content block → the snapshot block vocabulary the browser
767
+ * already renders (`kind`: text/reasoning/image/tool-call); unmappable
768
+ * blocks pass through raw and degrade to the generic JSON section.
769
+ */
770
+ function assistantBlockOf(block) {
771
+ const b = block !== null && typeof block === "object" ? block : null;
772
+ switch (b?.type) {
773
+ case "text":
774
+ case "reasoning": return {
775
+ kind: b.type,
776
+ ...typeof b.text === "string" ? { text: b.text } : {}
777
+ };
778
+ case "image": return {
779
+ kind: "image",
780
+ ...b.attachment !== void 0 ? { attachment: b.attachment } : {}
781
+ };
782
+ case "tool-call": return {
783
+ kind: "tool-call",
784
+ name: typeof b.name === "string" ? b.name : "?",
785
+ argsRaw: b.arguments
786
+ };
787
+ default: return block !== null && typeof block === "object" ? block : { value: block };
788
+ }
789
+ }
790
+ /**
791
+ * Map one history page into joined conversation nodes keyed by their event
792
+ * seq — the display subset of the browser's join: user messages, assistant
793
+ * blocks, tool results paired with their in-page call head, and compaction
794
+ * checkpoints paired with their summary event. Everything else (headers,
795
+ * boundaries, chunks, bare calls) projects to nothing.
796
+ */
797
+ function pageNodesOf(entries) {
798
+ const nodes = /* @__PURE__ */ new Map();
799
+ const calls = /* @__PURE__ */ new Map();
800
+ const summaries = /* @__PURE__ */ new Map();
801
+ for (const entry of entries) {
802
+ const ev = eventOf(entry);
803
+ if (ev === null) continue;
804
+ const { type, seq, data } = ev;
805
+ if (type === "tool/call") {
806
+ if (typeof data.callId === "string") calls.set(data.callId, {
807
+ name: typeof data.name === "string" ? data.name : "?",
808
+ argsRaw: typeof data.arguments === "string" ? data.arguments : ""
809
+ });
810
+ continue;
811
+ }
812
+ if (type === "compaction/summary") {
813
+ if (typeof data.compactionId === "string") {
814
+ const summary = textOf(data.summary);
815
+ if (summary !== null) summaries.set(data.compactionId, summary);
816
+ }
817
+ continue;
818
+ }
819
+ if (type === "user/message") {
820
+ const source = data.source !== null && typeof data.source === "object" ? data.source : null;
821
+ const compactionId = source !== null && source.kind === "plugin" && typeof source.compactionId === "string" ? source.compactionId : null;
822
+ if (compactionId !== null) {
823
+ nodes.set(seq, {
824
+ kind: "compaction",
825
+ seq,
826
+ summary: summaries.get(compactionId) ?? null
827
+ });
828
+ continue;
829
+ }
830
+ nodes.set(seq, {
831
+ kind: "user",
832
+ seq,
833
+ content: Array.isArray(data.content) ? data.content : []
834
+ });
835
+ continue;
836
+ }
837
+ if (type === "assistant/message") {
838
+ const message = data.message !== null && typeof data.message === "object" ? data.message : null;
839
+ const content = message !== null && Array.isArray(message.content) ? message.content : [];
840
+ nodes.set(seq, {
841
+ kind: "assistant",
842
+ seq,
843
+ blocks: content.map(assistantBlockOf)
844
+ });
845
+ continue;
846
+ }
847
+ if (type === "tool/result") {
848
+ const message = data.message !== null && typeof data.message === "object" ? data.message : null;
849
+ const source = message?.source !== null && typeof message?.source === "object" ? message.source : null;
850
+ const first = Array.isArray(message?.content) ? message.content[0] : void 0;
851
+ const block = first !== null && typeof first === "object" ? first : null;
852
+ const callId = typeof source?.callId === "string" ? source.callId : typeof block?.toolCallId === "string" ? block.toolCallId : null;
853
+ const call = callId !== null ? calls.get(callId) ?? null : null;
854
+ nodes.set(seq, {
855
+ kind: "tool-result",
856
+ seq,
857
+ call,
858
+ content: block !== null && Array.isArray(block.content) ? block.content : [],
859
+ isError: block?.isError === true || data.error === true
860
+ });
861
+ continue;
862
+ }
863
+ }
864
+ return nodes;
865
+ }
866
+ /**
867
+ * Build the browser's per-session fetcher over the shared api client.
868
+ * Undefined when the connection face or the history verb is absent (older
869
+ * hosts) — the caller keeps its static preview-plus-hint degradation. Found
870
+ * nodes cache in the closure: one mount re-reading a row never re-fetches.
871
+ */
872
+ function makeContentFetcher(ctx, sessionId) {
873
+ const sessions = ctx.get("connection")?.api?.sessions;
874
+ if (typeof sessions?.history !== "function") return void 0;
875
+ const cache = /* @__PURE__ */ new Map();
876
+ return async (seq) => {
877
+ const hit = cache.get(seq);
878
+ if (hit !== void 0) return hit;
879
+ const result = (await sessions.history({
880
+ sessionId,
881
+ beforeSeq: seq + 1
882
+ })).result;
883
+ if (result === null || typeof result !== "object" || result.ok !== true || result.value === null || typeof result.value !== "object") throw new Error("history rpc failed");
884
+ const events = result.value.events;
885
+ const node = pageNodesOf(Array.isArray(events) ? events : []).get(seq) ?? null;
886
+ if (node !== null) cache.set(seq, node);
887
+ return node;
888
+ };
889
+ }
890
+ //#endregion
669
891
  //#region src/client/assemble.ts
670
892
  /** The header epoch in force at `seq` (last logged before it), or the newest. */
671
893
  function headerAt(headers, seq) {
@@ -767,24 +989,6 @@ window.__ModuleLoader__.load({
767
989
  return t("node.nonText");
768
990
  };
769
991
  }
770
- function makeNodeList(kit) {
771
- const { t, fmt, fmtTime } = kit;
772
- const nodeText = makeNodeText(kit);
773
- return function NodeList(props) {
774
- if (props.nodes.length === 0) return /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("nodes.empty"));
775
- const rows = props.nodes.slice().reverse();
776
- return /* @__PURE__ */ React.createElement("div", { className: "lc-nodes" }, props.dropped > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-nodes-more" }, t("nodes.more", { n: props.dropped })) : null, rows.map((n) => {
777
- const text = nodeText(n);
778
- return /* @__PURE__ */ React.createElement("div", {
779
- key: n.seq,
780
- className: "lc-node"
781
- }, /* @__PURE__ */ React.createElement("i", { style: { background: CAT_COLORS[n.cat] || "#999" } }), /* @__PURE__ */ React.createElement("span", {
782
- className: "lc-node-preview",
783
- title: text
784
- }, text), typeof n.time === "number" ? /* @__PURE__ */ React.createElement("span", { className: "lc-node-time" }, fmtTime(n.time)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-node-tokens" }, fmt(n.tokens)));
785
- }));
786
- };
787
- }
788
992
  //#endregion
789
993
  //#region src/client/format.ts
790
994
  /** `fmt`: the k/M suffix style shared by bars/details/stats; `fmtTime`: local HH:MM:SS. */
@@ -865,6 +1069,8 @@ window.__ModuleLoader__.load({
865
1069
  let bestWidth;
866
1070
  if (gridW < 1) {
867
1071
  let rows = floorDiv(budget - 2, 2);
1072
+ /* v8 ignore else -- budget enters at 381 and the tall solve always fits
1073
+ (never decrements), so rows is always odd here; parity-defensive. */
868
1074
  if (rows % 2 === 1) rows -= 1;
869
1075
  bestWidth = unit;
870
1076
  bestHeight = rows * unit;
@@ -944,6 +1150,8 @@ window.__ModuleLoader__.load({
944
1150
  }
945
1151
  return null;
946
1152
  } catch {
1153
+ /* v8 ignore next -- the only throw site is the clamped-away budget guard
1154
+ above; the catch keeps a hostile dimension from ever breaking the fold. */
947
1155
  return null;
948
1156
  }
949
1157
  }
@@ -1189,18 +1397,22 @@ window.__ModuleLoader__.load({
1189
1397
  */
1190
1398
  function Section(props) {
1191
1399
  const right = props.actions !== void 0 || props.meta !== void 0;
1192
- 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);
1400
+ 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, props.actions) : null, props.count !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-count" }, props.count) : null), props.children);
1401
+ }
1402
+ function lineCountOf(text) {
1403
+ return text.split(/\r\n|\r|\n/).length;
1193
1404
  }
1194
1405
  function TextSection(props) {
1195
1406
  const { rich } = props;
1196
1407
  const [mode, setMode] = rich.useRichMode();
1408
+ const lineCount = React.useMemo(() => lineCountOf(props.text), [props.text]);
1197
1409
  return /* @__PURE__ */ React.createElement(Section, {
1198
1410
  label: props.label,
1199
1411
  actions: /* @__PURE__ */ React.createElement(rich.RichSwitch, {
1200
1412
  mode,
1201
1413
  onPick: setMode
1202
1414
  }),
1203
- meta: props.meta
1415
+ meta: /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-meta" }, props.lines(lineCount))
1204
1416
  }, /* @__PURE__ */ React.createElement(rich.RichText, {
1205
1417
  text: props.text,
1206
1418
  mode
@@ -1238,7 +1450,8 @@ window.__ModuleLoader__.load({
1238
1450
  return /* @__PURE__ */ React.createElement(React.Fragment, null, props.description !== void 0 ? /* @__PURE__ */ React.createElement(TextSection, {
1239
1451
  label: props.labels.desc,
1240
1452
  text: props.description,
1241
- rich
1453
+ rich,
1454
+ lines: props.lines
1242
1455
  }) : null, params !== null && rows.length > 0 ? /* @__PURE__ */ React.createElement(Section, {
1243
1456
  label: props.labels.title,
1244
1457
  count: rows.length
@@ -1257,8 +1470,8 @@ window.__ModuleLoader__.load({
1257
1470
  }
1258
1471
  /**
1259
1472
  * Both block vocabularies normalize here — raw durable blocks (`type`: text/reasoning/tool-call/tool-result/image) and snapshot assistant
1260
- * blocks (`kind`: text/reasoning/tool-call/image, argsRaw). Consecutive images group into one grid; tool-result payloads carry the Raw/MD
1261
- * switch + line count; nested tool-result blocks flatten into the same flow.
1473
+ * blocks (`kind`: text/reasoning/tool-call/image, argsRaw). Consecutive images group into one grid; every
1474
+ * rich text block carries the Raw/MD switch + line count; nested tool-result blocks flatten into the same flow.
1262
1475
  */
1263
1476
  function BlocksBody(props) {
1264
1477
  const { rich, img, labels } = props;
@@ -1294,7 +1507,7 @@ window.__ModuleLoader__.load({
1294
1507
  label,
1295
1508
  text: blk.text,
1296
1509
  rich,
1297
- meta: props.textLabel === labels.result ? /* @__PURE__ */ React.createElement("span", { className: "lc-ts-card-meta" }, labels.lines(blk.text.split("\n").length)) : void 0
1510
+ lines: labels.lines
1298
1511
  }));
1299
1512
  continue;
1300
1513
  }
@@ -1412,6 +1625,8 @@ window.__ModuleLoader__.load({
1412
1625
  }
1413
1626
  function CallArgRow(props) {
1414
1627
  const v = props.value;
1628
+ /* v8 ignore next 2 -- the only caller maps Object.keys of a JSON.parse'd
1629
+ object, which never holds undefined values; defensive. */
1415
1630
  const text = typeof v === "string" ? v : v === void 0 ? "" : JSON.stringify(v);
1416
1631
  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));
1417
1632
  }
@@ -1422,7 +1637,8 @@ window.__ModuleLoader__.load({
1422
1637
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(TextSection, {
1423
1638
  label: labels.content,
1424
1639
  text: node.text,
1425
- rich
1640
+ rich,
1641
+ lines: labels.lines
1426
1642
  }), /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, props.hint));
1427
1643
  }
1428
1644
  if (conv.kind === "assistant" && Array.isArray(conv.blocks)) return /* @__PURE__ */ React.createElement(BlocksBody, {
@@ -1452,7 +1668,8 @@ window.__ModuleLoader__.load({
1452
1668
  if (conv.kind === "compaction") return typeof conv.summary === "string" && conv.summary !== "" ? /* @__PURE__ */ React.createElement(TextSection, {
1453
1669
  label: labels.summary,
1454
1670
  text: conv.summary,
1455
- rich
1671
+ rich,
1672
+ lines: labels.lines
1456
1673
  }) : /* @__PURE__ */ React.createElement(React.Fragment, null);
1457
1674
  if (Array.isArray(conv.content)) return /* @__PURE__ */ React.createElement(BlocksBody, {
1458
1675
  blocks: conv.content,
@@ -1483,52 +1700,51 @@ window.__ModuleLoader__.load({
1483
1700
  const nodeText = makeNodeText(kit);
1484
1701
  const rich = makeRichText(kit);
1485
1702
  const ImageCard = makeImageCard(kit);
1486
- const MAX_AUTO_PAGES = 20;
1703
+ const lineLabel = (n) => t(n === 1 ? "block.line" : "block.lines", { n });
1487
1704
  return function ContextBrowser(props) {
1488
1705
  const { data, headers } = props;
1489
1706
  const [sel, setSel] = React.useState("live");
1490
1707
  const [openCat, setOpenCat] = React.useState(null);
1491
1708
  const [openElem, setOpenElem] = React.useState(null);
1492
1709
  const convNodes = typeof props.useSession === "function" ? props.useSession((s) => s.nodes) : void 0;
1710
+ const [fetched, setFetched] = React.useState(() => /* @__PURE__ */ new Map());
1493
1711
  const bySeq = React.useMemo(() => {
1494
1712
  const m = /* @__PURE__ */ new Map();
1495
1713
  for (const n of convNodes ?? []) m.set(n.seq, n);
1714
+ for (const [seq, n] of fetched) if (!m.has(seq)) m.set(seq, n);
1496
1715
  return m;
1497
- }, [convNodes]);
1498
- const hasMore = typeof props.useSession === "function" ? props.useSession((s) => s.hasMore === true) : false;
1499
- const loadingOlder = typeof props.useSession === "function" ? props.useSession((s) => s.loadingOlder === true) : false;
1716
+ }, [convNodes, fetched]);
1500
1717
  const openSeq = openElem !== null && openElem.startsWith("n") ? Number(openElem.slice(1)) : null;
1501
1718
  const missingSeq = openSeq !== null && !bySeq.has(openSeq) ? openSeq : null;
1502
- const [exhausted, setExhausted] = React.useState(false);
1503
- const pagesRef = React.useRef(0);
1504
- React.useEffect(() => {
1505
- pagesRef.current = 0;
1506
- setExhausted(false);
1507
- }, [openElem]);
1508
- const loadOlderHistory = props.loadOlderHistory;
1509
- React.useEffect(() => {
1510
- if (missingSeq === null || !hasMore || loadingOlder || exhausted) return;
1511
- if (loadOlderHistory === void 0) return;
1512
- if (pagesRef.current >= MAX_AUTO_PAGES) {
1513
- setExhausted(true);
1514
- return;
1515
- }
1516
- pagesRef.current += 1;
1517
- loadOlderHistory();
1518
- }, [
1519
- missingSeq,
1520
- hasMore,
1521
- loadingOlder,
1522
- exhausted,
1523
- bySeq,
1524
- loadOlderHistory
1525
- ]);
1719
+ const fetchContent = props.fetchContent;
1720
+ const [missState, setMissState] = React.useState("idle");
1721
+ const [retry, setRetry] = React.useState(0);
1526
1722
  React.useEffect(() => {
1527
- if (!hasMore && missingSeq !== null && !exhausted) setExhausted(true);
1723
+ if (missingSeq === null || fetchContent === void 0) return;
1724
+ let live = true;
1725
+ setMissState("loading");
1726
+ fetchContent(missingSeq).then((node) => {
1727
+ if (!live) return;
1728
+ if (node === null) {
1729
+ setMissState("absent");
1730
+ return;
1731
+ }
1732
+ setFetched((prev) => {
1733
+ const next = new Map(prev);
1734
+ next.set(missingSeq, node);
1735
+ return next;
1736
+ });
1737
+ setMissState("idle");
1738
+ }, () => {
1739
+ if (live) setMissState("failed");
1740
+ });
1741
+ return () => {
1742
+ live = false;
1743
+ };
1528
1744
  }, [
1529
- hasMore,
1530
1745
  missingSeq,
1531
- exhausted
1746
+ fetchContent,
1747
+ retry
1532
1748
  ]);
1533
1749
  const pinSeq = props.pinSeq;
1534
1750
  React.useEffect(() => {
@@ -1536,14 +1752,6 @@ window.__ModuleLoader__.load({
1536
1752
  setOpenCat(null);
1537
1753
  setOpenElem(null);
1538
1754
  }, [pinSeq]);
1539
- const toolFocus = props.toolFocus;
1540
- React.useEffect(() => {
1541
- if (toolFocus === null || toolFocus === void 0) return;
1542
- setSel("live");
1543
- setOpenCat("tools");
1544
- setOpenElem(toolFocus.tool !== void 0 ? "tool:" + toolFocus.tool : null);
1545
- if (props.onToolFocusHandled !== void 0) props.onToolFocusHandled();
1546
- }, [toolFocus, props.onToolFocusHandled]);
1547
1755
  const rootRef = React.useRef(null);
1548
1756
  const focusScrollRef = React.useRef(false);
1549
1757
  const nodeFocus = props.nodeFocus;
@@ -1560,7 +1768,13 @@ window.__ModuleLoader__.load({
1560
1768
  focusScrollRef.current = false;
1561
1769
  rootRef.current?.querySelector(".lc-br-elem-on")?.scrollIntoView({ block: "nearest" });
1562
1770
  });
1563
- const awaiting = missingSeq !== null && !exhausted && loadOlderHistory !== void 0 && hasMore;
1771
+ const missNote = fetchContent === void 0 ? t("browser.noContent") : missState === "loading" ? t("browser.loading") : missState === "absent" ? t("browser.notInLog") : missState === "failed" ? /* @__PURE__ */ React.createElement("button", {
1772
+ type: "button",
1773
+ className: "lc-br-retry",
1774
+ onClick: () => {
1775
+ setRetry((r) => r + 1);
1776
+ }
1777
+ }, t("browser.loadFailed")) : t("browser.noContent");
1564
1778
  const requests = data.requests;
1565
1779
  const hoverReq = props.previewSeq !== null && props.previewSeq !== void 0 ? requests.find((r) => r.seq === props.previewSeq) ?? null : null;
1566
1780
  const req = hoverReq ?? (sel === "live" ? null : requests.find((r) => r.seq === sel) ?? null);
@@ -1583,8 +1797,13 @@ window.__ModuleLoader__.load({
1583
1797
  const toolCount = (c) => countOf(view, byCat, c);
1584
1798
  const toggleCat = (c) => {
1585
1799
  if (!(toolCount(c) > 0 || (c === "system" || c === "tools") && view.header === null)) return;
1586
- setOpenCat(openCat === c ? null : c);
1587
- setOpenElem(null);
1800
+ if (openCat === c) {
1801
+ setOpenCat(null);
1802
+ setOpenElem(null);
1803
+ return;
1804
+ }
1805
+ setOpenCat(c);
1806
+ setOpenElem(c === "system" && view.header?.system !== void 0 ? "sys" : null);
1588
1807
  };
1589
1808
  const toggleElem = (key) => {
1590
1809
  setOpenElem(openElem === key ? null : key);
@@ -1613,11 +1832,14 @@ window.__ModuleLoader__.load({
1613
1832
  if (c === "system") {
1614
1833
  if (view.header === null) return /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, t(headers === null ? "browser.noHeader" : "browser.noEpoch"));
1615
1834
  const system = view.header.system;
1835
+ /* v8 ignore next 1 -- the category opens only when its count > 0
1836
+ (countOf ⟺ system defined); the undefined arm is defensive. */
1616
1837
  if (system === void 0) return null;
1617
1838
  return elemRow("sys", null, system.replace(/\s+/g, " ").trim().slice(0, 80), breakdown.system, void 0, /* @__PURE__ */ React.createElement(TextSection, {
1618
1839
  label: catLabel("system"),
1619
1840
  text: system,
1620
- rich
1841
+ rich,
1842
+ lines: lineLabel
1621
1843
  }));
1622
1844
  }
1623
1845
  if (c === "tools") {
@@ -1634,6 +1856,7 @@ window.__ModuleLoader__.load({
1634
1856
  description: tool.description,
1635
1857
  schema: tool.schema,
1636
1858
  rich,
1859
+ lines: lineLabel,
1637
1860
  labels
1638
1861
  }));
1639
1862
  });
@@ -1673,10 +1896,10 @@ window.__ModuleLoader__.load({
1673
1896
  summary: t("block.summary"),
1674
1897
  images: t("attach.images"),
1675
1898
  other: t("attach.other"),
1676
- lines: (n) => t(n === 1 ? "block.line" : "block.lines", { n }),
1899
+ lines: lineLabel,
1677
1900
  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"))
1678
1901
  },
1679
- hint: conv === void 0 && awaiting ? t("browser.loading") : t("browser.noContent")
1902
+ hint: conv === void 0 ? missNote : t("browser.noContent")
1680
1903
  }), rowErr);
1681
1904
  });
1682
1905
  };
@@ -1719,12 +1942,15 @@ window.__ModuleLoader__.load({
1719
1942
  }, /* @__PURE__ */ React.createElement("button", {
1720
1943
  type: "button",
1721
1944
  className: "lc-br-cat-row" + (linked && props.hoverKey === c.key ? " lc-br-cat-on" : ""),
1945
+ /* v8 ignore start -- the handlers exist only when linked,
1946
+ and linked already requires onHoverKey defined (above). */
1722
1947
  onMouseEnter: linked ? () => {
1723
1948
  if (props.onHoverKey !== void 0) props.onHoverKey(c.key);
1724
1949
  } : void 0,
1725
1950
  onMouseLeave: linked ? () => {
1726
1951
  if (props.onHoverKey !== void 0) props.onHoverKey(null);
1727
1952
  } : void 0,
1953
+ /* v8 ignore stop */
1728
1954
  onClick: () => {
1729
1955
  toggleCat(c.key);
1730
1956
  }
@@ -1880,26 +2106,7 @@ window.__ModuleLoader__.load({
1880
2106
  parts: head.parts,
1881
2107
  hoverKey: props.hoverKey,
1882
2108
  onHoverKey: props.onHoverKey
1883
- }), props.tools !== void 0 && props.tools.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-tools" }, /* @__PURE__ */ React.createElement("button", {
1884
- type: "button",
1885
- className: "lc-tools-label",
1886
- onClick: () => {
1887
- if (props.onToolFocus !== void 0) props.onToolFocus({});
1888
- }
1889
- }, t("tools.top")), props.tools.slice().sort((a, b) => b.tokens - a.tokens).slice(0, 5).map((tool) => /* @__PURE__ */ React.createElement("button", {
1890
- key: tool.name,
1891
- type: "button",
1892
- className: "lc-tool-chip",
1893
- onClick: () => {
1894
- if (props.onToolFocus !== void 0) props.onToolFocus({ tool: tool.name });
1895
- }
1896
- }, tool.name + " " + fmt(tool.tokens))), props.tools.length > 5 ? /* @__PURE__ */ React.createElement("button", {
1897
- type: "button",
1898
- className: "lc-tools-more",
1899
- onClick: () => {
1900
- if (props.onToolFocus !== void 0) props.onToolFocus({});
1901
- }
1902
- }, t("tools.more", { n: props.tools.length })) : null) : null);
2109
+ }));
1903
2110
  };
1904
2111
  }
1905
2112
  //#endregion
@@ -1943,7 +2150,7 @@ window.__ModuleLoader__.load({
1943
2150
  const breakdown = typeof props.useProjection === "function" ? contextBreakdownOf(props.useProjection("contextBreakdown")) : null;
1944
2151
  const headers = typeof props.useProjection === "function" ? headersOf(props.useProjection("contextHeaders")) : null;
1945
2152
  const [hoverCat, setHoverCat] = React.useState(null);
1946
- const [toolFocus, setToolFocus] = React.useState(null);
2153
+ const fetchContent = React.useMemo(() => sessionId !== "" ? makeContentFetcher(ctx, sessionId) : void 0, [ctx, sessionId]);
1947
2154
  const close = React.useCallback(() => {
1948
2155
  if (sessionId === "") return;
1949
2156
  modalStoreOf(sessionId).set(false);
@@ -1986,20 +2193,14 @@ window.__ModuleLoader__.load({
1986
2193
  head,
1987
2194
  subtitle,
1988
2195
  hoverKey: hoverCat,
1989
- onHoverKey: setHoverCat,
1990
- tools: data.toolList,
1991
- onToolFocus: setToolFocus
2196
+ onHoverKey: setHoverCat
1992
2197
  }), /* @__PURE__ */ React.createElement(ContextBrowser, {
1993
2198
  data,
1994
2199
  headers,
1995
2200
  useSession: props.useSession,
1996
- loadOlderHistory: props.loadOlderHistory,
2201
+ fetchContent,
1997
2202
  hoverKey: hoverCat,
1998
- onHoverKey: setHoverCat,
1999
- toolFocus,
2000
- onToolFocusHandled: () => {
2001
- setToolFocus(null);
2002
- }
2203
+ onHoverKey: setHoverCat
2003
2204
  }))));
2004
2205
  }
2005
2206
  return function ContextModal(props) {
@@ -2082,6 +2283,27 @@ window.__ModuleLoader__.load({
2082
2283
  onPick: (id) => {
2083
2284
  props.set?.("defaultTrendMode", id);
2084
2285
  }
2286
+ }), /* @__PURE__ */ React.createElement(PrefRow, {
2287
+ label: t("settings.fileSort"),
2288
+ value: state.fileSort,
2289
+ disabled,
2290
+ options: [
2291
+ {
2292
+ id: "count",
2293
+ label: t("files.sort.count")
2294
+ },
2295
+ {
2296
+ id: "latest",
2297
+ label: t("files.sort.latest")
2298
+ },
2299
+ {
2300
+ id: "path",
2301
+ label: t("files.sort.path")
2302
+ }
2303
+ ],
2304
+ onPick: (id) => {
2305
+ props.set?.("defaultFileSort", id);
2306
+ }
2085
2307
  })) : null);
2086
2308
  };
2087
2309
  }
@@ -2092,7 +2314,8 @@ window.__ModuleLoader__.load({
2092
2314
  const v = value;
2093
2315
  return {
2094
2316
  ...v.defaultGranularity === "step" || v.defaultGranularity === "turn" ? { granularity: v.defaultGranularity } : {},
2095
- ...v.defaultTrendMode === "total" || v.defaultTrendMode === "delta" ? { mode: v.defaultTrendMode } : {}
2317
+ ...v.defaultTrendMode === "total" || v.defaultTrendMode === "delta" ? { mode: v.defaultTrendMode } : {},
2318
+ ...v.defaultFileSort === "count" || v.defaultFileSort === "latest" || v.defaultFileSort === "path" ? { fileSort: v.defaultFileSort } : {}
2096
2319
  };
2097
2320
  }
2098
2321
  function createContextSettings() {
@@ -2100,12 +2323,13 @@ window.__ModuleLoader__.load({
2100
2323
  status: "loading",
2101
2324
  granularity: "step",
2102
2325
  mode: "total",
2326
+ fileSort: "count",
2103
2327
  writable: false
2104
2328
  };
2105
2329
  let scope;
2106
2330
  const listeners = /* @__PURE__ */ new Set();
2107
2331
  const publish = (next) => {
2108
- if (next.status === state.status && next.granularity === state.granularity && next.mode === state.mode && next.writable === state.writable) return;
2332
+ if (next.status === state.status && next.granularity === state.granularity && next.mode === state.mode && next.fileSort === state.fileSort && next.writable === state.writable) return;
2109
2333
  state = next;
2110
2334
  for (const listener of listeners) listener();
2111
2335
  };
@@ -2121,6 +2345,7 @@ window.__ModuleLoader__.load({
2121
2345
  },
2122
2346
  defaultGranularity: () => state.granularity,
2123
2347
  defaultTrendMode: () => state.mode,
2348
+ defaultFileSort: () => state.fileSort,
2124
2349
  attach(bound) {
2125
2350
  scope = bound;
2126
2351
  const sync = () => {
@@ -2130,6 +2355,7 @@ window.__ModuleLoader__.load({
2130
2355
  status: snap.status === "ready" || snap.status === "unavailable" ? snap.status : "loading",
2131
2356
  granularity: prefs.granularity ?? state.granularity,
2132
2357
  mode: prefs.mode ?? state.mode,
2358
+ fileSort: prefs.fileSort ?? state.fileSort,
2133
2359
  writable: snap.writable
2134
2360
  });
2135
2361
  };
@@ -2213,6 +2439,767 @@ window.__ModuleLoader__.load({
2213
2439
  return m;
2214
2440
  }
2215
2441
  //#endregion
2442
+ //#region src/client/fileActivity.ts
2443
+ const KIND_BY_TOOL = {
2444
+ read: "read",
2445
+ read_image: "read",
2446
+ write: "write",
2447
+ edit: "write",
2448
+ grep: "search",
2449
+ glob: "search"
2450
+ };
2451
+ /** The file purpose of a tool, or null for non-file tools (bash, web_search…). */
2452
+ function kindOfTool(tool) {
2453
+ if (tool === void 0) return null;
2454
+ return KIND_BY_TOOL[tool] ?? null;
2455
+ }
2456
+ /**
2457
+ * The operation's target path: the path-ish argument of read/write tools;
2458
+ * for searches the narrowing `path`, else the pattern itself (a pathless
2459
+ * grep/glob's target IS the pattern — the workspace-wide search text).
2460
+ */
2461
+ function pathOfArgs(tool, args) {
2462
+ if (args === null) return null;
2463
+ if (tool === "grep" || tool === "glob") {
2464
+ const p = args.path;
2465
+ if (typeof p === "string" && p !== "") return p;
2466
+ const pattern = args.pattern;
2467
+ return typeof pattern === "string" && pattern !== "" ? pattern : null;
2468
+ }
2469
+ for (const k of [
2470
+ "file_path",
2471
+ "filePath",
2472
+ "path"
2473
+ ]) {
2474
+ const v = args[k];
2475
+ if (typeof v === "string" && v !== "") return v;
2476
+ }
2477
+ return null;
2478
+ }
2479
+ /** Rendered line count: '' is 0, a trailing newline closes its own line. */
2480
+ function linesOf(s) {
2481
+ if (s === "") return 0;
2482
+ let n = 0;
2483
+ for (let i = 0; i < s.length; i++) if (s[i] === "\n") n++;
2484
+ return s.endsWith("\n") ? n : n + 1;
2485
+ }
2486
+ /**
2487
+ * The signed line footprint of one call: an edit removes its old string and
2488
+ * adds its new one; a write adds its content (the pre-existing body, if any,
2489
+ * is unknowable from the arguments — the estimate stays honest about that).
2490
+ * Callers reach here only with parsed args (a null parse yields no path).
2491
+ */
2492
+ function deltaOf(tool, args) {
2493
+ if (tool === "edit") {
2494
+ const oldS = args.old_string;
2495
+ const newS = args.new_string;
2496
+ return {
2497
+ added: typeof newS === "string" ? linesOf(newS) : 0,
2498
+ removed: typeof oldS === "string" ? linesOf(oldS) : 0
2499
+ };
2500
+ }
2501
+ if (tool === "write") {
2502
+ const content = args.content;
2503
+ return {
2504
+ added: typeof content === "string" ? linesOf(content) : 0,
2505
+ removed: 0
2506
+ };
2507
+ }
2508
+ return {
2509
+ added: 0,
2510
+ removed: 0
2511
+ };
2512
+ }
2513
+ const IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|bmp|ico|avif)$/i;
2514
+ /** The file's form — multimodal reads and image extensions scan apart; a trailing slash marks a directory target. */
2515
+ function formOf(tool, path) {
2516
+ if (tool === "read_image" || IMAGE_EXT.test(path)) return "image";
2517
+ if (path.endsWith("/")) return "dir";
2518
+ return "text";
2519
+ }
2520
+ /** Fold every served tool-result node into per-file activity. */
2521
+ function activityOf(nodes, convOf, before) {
2522
+ const totals = {
2523
+ read: {
2524
+ files: 0,
2525
+ ops: 0
2526
+ },
2527
+ write: {
2528
+ files: 0,
2529
+ ops: 0
2530
+ },
2531
+ search: {
2532
+ files: 0,
2533
+ ops: 0
2534
+ },
2535
+ image: {
2536
+ files: 0,
2537
+ ops: 0
2538
+ },
2539
+ added: 0,
2540
+ removed: 0
2541
+ };
2542
+ const byPath = /* @__PURE__ */ new Map();
2543
+ for (const n of nodes) {
2544
+ if (n.cat !== "tool") continue;
2545
+ if (before !== null && n.seq >= before) continue;
2546
+ const kind = kindOfTool(n.tool);
2547
+ if (kind === null) continue;
2548
+ const tool = n.tool;
2549
+ const conv = convOf(n.seq);
2550
+ const args = parseCallArgs(conv?.call?.argsRaw);
2551
+ const path = pathOfArgs(tool, args);
2552
+ if (path === null || args === null) continue;
2553
+ totals[kind].ops++;
2554
+ const { added, removed } = deltaOf(tool, args);
2555
+ const detail = kind === "search" && typeof args.path === "string" && args.path !== "" && typeof args.pattern === "string" && args.pattern !== "" ? args.pattern : void 0;
2556
+ const op = {
2557
+ seq: n.seq,
2558
+ ...n.gone !== void 0 ? { gone: n.gone } : {},
2559
+ kind,
2560
+ tool,
2561
+ ...n.time !== void 0 ? { time: n.time } : {},
2562
+ err: n.err === true || conv.isError === true,
2563
+ added,
2564
+ removed,
2565
+ ...detail !== void 0 ? { detail } : {}
2566
+ };
2567
+ let entry = byPath.get(path);
2568
+ if (entry === void 0) {
2569
+ entry = {
2570
+ path,
2571
+ form: formOf(tool, path),
2572
+ reads: 0,
2573
+ writes: 0,
2574
+ searches: 0,
2575
+ added: 0,
2576
+ removed: 0,
2577
+ errs: 0,
2578
+ ops: []
2579
+ };
2580
+ byPath.set(path, entry);
2581
+ }
2582
+ if (kind === "read") entry.reads++;
2583
+ else if (kind === "write") entry.writes++;
2584
+ else entry.searches++;
2585
+ entry.added += added;
2586
+ entry.removed += removed;
2587
+ if (op.err) entry.errs++;
2588
+ entry.ops.push(op);
2589
+ }
2590
+ const entries = [...byPath.values()];
2591
+ for (const e of entries) {
2592
+ e.ops.sort((a, b) => b.seq - a.seq);
2593
+ if (e.reads > 0) totals.read.files++;
2594
+ if (e.writes > 0) totals.write.files++;
2595
+ if (e.searches > 0) totals.search.files++;
2596
+ if (e.form === "image") {
2597
+ totals.image.files++;
2598
+ totals.image.ops += e.ops.length;
2599
+ }
2600
+ totals.added += e.added;
2601
+ totals.removed += e.removed;
2602
+ }
2603
+ entries.sort((a, b) => b.ops[0].seq - a.ops[0].seq);
2604
+ return {
2605
+ entries,
2606
+ totals
2607
+ };
2608
+ }
2609
+ /**
2610
+ * The browser step whose assembled surface SHOWS an op's result node: the
2611
+ * first request dispatched after it while it was still alive (that step's
2612
+ * brief is where the result landed). A live node no request has consumed
2613
+ * yet reveals on the live surface; an archived node no retained step still
2614
+ * contains is not viewable anywhere (null → the row stays inert).
2615
+ */
2616
+ function locateStepOf(requests, seq, gone) {
2617
+ for (const r of requests) if (r.seq > seq && (gone === void 0 || gone > r.seq)) return r.seq;
2618
+ return gone === void 0 ? "live" : null;
2619
+ }
2620
+ const SLOT_MAX = 184;
2621
+ const SLOT_MIN = 112;
2622
+ const CAPTION_GUTTER = 8;
2623
+ const LEVEL_H = 154;
2624
+ const CELL_H = 90;
2625
+ const PAD_Y = 56;
2626
+ function asRecord(value) {
2627
+ if (value === null || value === void 0 || typeof value !== "object") return null;
2628
+ return value;
2629
+ }
2630
+ /** Narrow one list-row value; null when it is not a row at all. */
2631
+ function agentRowOf(value) {
2632
+ const rec = asRecord(value);
2633
+ if (rec === null) return null;
2634
+ const projections = asRecord(rec.projectionValues);
2635
+ return {
2636
+ ...typeof rec.displayTitle === "string" ? { displayTitle: rec.displayTitle } : {},
2637
+ ...typeof rec.title === "string" ? { title: rec.title } : {},
2638
+ ...typeof rec.parentId === "string" ? { parentId: rec.parentId } : {},
2639
+ ...typeof rec.origin === "string" ? { origin: rec.origin } : {},
2640
+ running: rec.running === true,
2641
+ completed: rec.completed === true,
2642
+ blank: rec.blank === true,
2643
+ updatedAt: numOf(rec.updatedAt),
2644
+ ...projections !== null ? { projections } : {}
2645
+ };
2646
+ }
2647
+ /** Narrow the subagent identity projection value (null = not a descriptor-backed subagent). */
2648
+ function agentIdentityOf(value) {
2649
+ const rec = asRecord(value);
2650
+ if (rec === null) return null;
2651
+ if (rec.mode !== "one-shot" && rec.mode !== "continuable") return null;
2652
+ return {
2653
+ mode: rec.mode,
2654
+ ...typeof rec.label === "string" && rec.label !== "" ? { label: rec.label } : {}
2655
+ };
2656
+ }
2657
+ /** Narrow the subagent timing projection value into a single duration (null = absent/malformed). */
2658
+ function agentDurationOf(value) {
2659
+ const rec = asRecord(value);
2660
+ if (rec === null) return null;
2661
+ const settled = numOf(rec.settledMs);
2662
+ const active = asRecord(rec.active);
2663
+ const total = settled + (active !== null ? Math.max(0, numOf(active.through) - numOf(active.since)) : 0);
2664
+ return total > 0 ? total : null;
2665
+ }
2666
+ /**
2667
+ * Fold one row's projection values into render-ready stats. The composition
2668
+ * prefers the plugin's own `contextTimeline` (six buckets, provider-anchored
2669
+ * by token-meter's pressure/breakdown — the exact headlineOf derivation the
2670
+ * overview card shows); a pressure-only row (plugin host half absent for that
2671
+ * session) still yields an occupancy ring without slices.
2672
+ */
2673
+ function agentStatsOf(values) {
2674
+ const timeline = timelineOf(values?.contextTimeline);
2675
+ const pressure = contextPressureOf(values?.contextPressure);
2676
+ const breakdown = contextBreakdownOf(values?.contextBreakdown);
2677
+ const usage = tokenUsageOf(values?.tokenUsage);
2678
+ let head = null;
2679
+ if (timeline !== null) head = headlineOf(timeline, pressure, breakdown);
2680
+ else if (pressure !== null) {
2681
+ const tokens = typeof pressure.projectedTokens === "number" ? pressure.projectedTokens : typeof pressure.pressureTokens === "number" ? pressure.pressureTokens : null;
2682
+ if (tokens !== null) {
2683
+ const window = typeof pressure.contextWindow === "number" && pressure.contextWindow > 0 ? pressure.contextWindow : void 0;
2684
+ head = {
2685
+ tokens,
2686
+ window,
2687
+ pct: window !== void 0 ? Math.min(100, Math.round(tokens / window * 100)) : null,
2688
+ parts: []
2689
+ };
2690
+ }
2691
+ }
2692
+ const billed = usage !== null ? numOf(usage.uncachedInputTokens) + numOf(usage.outputTokens) + numOf(usage.cacheReadTokens) + numOf(usage.cacheWriteTokens) : null;
2693
+ return {
2694
+ head,
2695
+ requests: timeline !== null ? timeline.requests.length : 0,
2696
+ billed,
2697
+ durationMs: agentDurationOf(values?.subagentTiming),
2698
+ identity: agentIdentityOf(values?.subagent)
2699
+ };
2700
+ }
2701
+ /**
2702
+ * Build the current session's agent family from a session-list snapshot:
2703
+ * walk up `parentId` to the topmost known ancestor, then DFS its whole
2704
+ * subtree (blank placeholder rows excluded). Null when there is no anchor —
2705
+ * no current session, or a snapshot without a `byId` table (older harness).
2706
+ * The current session synthesizes a row when the list has not delivered it
2707
+ * yet, so the card can still show its live self stats.
2708
+ */
2709
+ function agentForestOf(snapshot, currentId, self) {
2710
+ const byId = asRecord(asRecord(snapshot)?.byId);
2711
+ if (byId === null || currentId === void 0 || currentId === "") return null;
2712
+ const rows = /* @__PURE__ */ new Map();
2713
+ for (const key of Object.keys(byId)) {
2714
+ const row = agentRowOf(byId[key]);
2715
+ if (row !== null && (!row.blank || key === currentId)) rows.set(key, row);
2716
+ }
2717
+ if (!rows.has(currentId)) rows.set(currentId, {
2718
+ running: false,
2719
+ completed: false,
2720
+ blank: false,
2721
+ updatedAt: 0
2722
+ });
2723
+ let root = currentId;
2724
+ const chain = /* @__PURE__ */ new Set([currentId]);
2725
+ for (;;) {
2726
+ const parent = rows.get(root)?.parentId;
2727
+ if (parent === void 0 || !rows.has(parent) || chain.has(parent)) break;
2728
+ chain.add(parent);
2729
+ root = parent;
2730
+ }
2731
+ const rootRow = rows.get(root);
2732
+ /* v8 ignore next 2 -- root is currentId (inserted above) or a parent
2733
+ verified with rows.has, so its row always exists. */
2734
+ if (rootRow === void 0) return null;
2735
+ const childrenOf = /* @__PURE__ */ new Map();
2736
+ for (const [id, row] of rows) {
2737
+ if (row.parentId === void 0 || !rows.has(row.parentId)) continue;
2738
+ const list = childrenOf.get(row.parentId) ?? [];
2739
+ list.push({
2740
+ id,
2741
+ row
2742
+ });
2743
+ childrenOf.set(row.parentId, list);
2744
+ }
2745
+ for (const kids of childrenOf.values()) kids.sort((a, b) => {
2746
+ const runDelta = Number(b.row.running) - Number(a.row.running);
2747
+ if (runDelta !== 0) return runDelta;
2748
+ const timeDelta = b.row.updatedAt - a.row.updatedAt;
2749
+ return timeDelta !== 0 ? timeDelta : a.id < b.id ? -1 : 1;
2750
+ });
2751
+ const measure = (id, seen) => {
2752
+ if (seen.has(id)) return 0;
2753
+ seen.add(id);
2754
+ let total = 1;
2755
+ for (const kid of childrenOf.get(id) ?? []) total += measure(kid.id, seen);
2756
+ return total;
2757
+ };
2758
+ const total = measure(root, /* @__PURE__ */ new Set());
2759
+ const nodes = [];
2760
+ const edges = [];
2761
+ const visit = (id, row, parentId, depth, seen, family) => {
2762
+ if (seen.has(id) || nodes.length >= 25) return;
2763
+ seen.add(id);
2764
+ const stats = agentStatsOf(row.projections);
2765
+ const identity = stats.identity;
2766
+ const node = {
2767
+ ...stats,
2768
+ id,
2769
+ label: identity?.label ?? row.title ?? row.displayTitle ?? id,
2770
+ ...parentId !== void 0 ? { parentId } : {},
2771
+ depth,
2772
+ family,
2773
+ isCurrent: id === currentId,
2774
+ running: row.running,
2775
+ completed: row.completed,
2776
+ subagent: row.origin === "subagent" || identity !== null
2777
+ };
2778
+ if (node.isCurrent && self !== void 0) {
2779
+ node.head = self.head ?? node.head;
2780
+ node.billed = self.billed ?? node.billed;
2781
+ node.requests = self.requests > 0 ? self.requests : node.requests;
2782
+ }
2783
+ nodes.push(node);
2784
+ if (parentId !== void 0) edges.push({
2785
+ from: parentId,
2786
+ to: id
2787
+ });
2788
+ (childrenOf.get(id) ?? []).forEach((kid, ki) => {
2789
+ visit(kid.id, kid.row, id, depth + 1, seen, depth === 0 ? ki : family);
2790
+ });
2791
+ };
2792
+ visit(root, rootRow, void 0, 0, /* @__PURE__ */ new Set(), -1);
2793
+ return {
2794
+ nodes,
2795
+ edges,
2796
+ overflow: Math.max(0, total - nodes.length),
2797
+ solo: nodes.length === 1
2798
+ };
2799
+ }
2800
+ /**
2801
+ * Tidy top-down tree layout: one row per depth level, siblings claim leaf
2802
+ * slots, parents center over their children. Fully responsive to the stage's
2803
+ * visible width: the slot pitch stretches up to SLOT_MAX and compresses down
2804
+ * to SLOT_MIN (captions wrap tighter); a level that still overflows wraps
2805
+ * into bands of at most a per-level node count derived from the stage width —
2806
+ * vertical room is cheaper than horizontal scrolling. Links exit a parent at
2807
+ * its cell bottom (below the caption zone) and enter the child at its top,
2808
+ * so a connector never crosses a label.
2809
+ */
2810
+ function layoutForest(forest, stageWidth = 0) {
2811
+ const childrenOf = /* @__PURE__ */ new Map();
2812
+ for (const n of forest.nodes) {
2813
+ if (n.parentId === void 0) continue;
2814
+ const kids = childrenOf.get(n.parentId) ?? [];
2815
+ kids.push(n);
2816
+ childrenOf.set(n.parentId, kids);
2817
+ }
2818
+ const slotOf = /* @__PURE__ */ new Map();
2819
+ let leafSlots = 0;
2820
+ const place = (node) => {
2821
+ const kids = childrenOf.get(node.id) ?? [];
2822
+ if (kids.length === 0) {
2823
+ const slot = leafSlots;
2824
+ leafSlots++;
2825
+ slotOf.set(node.id, slot);
2826
+ return slot;
2827
+ }
2828
+ let first = 0;
2829
+ let last = 0;
2830
+ kids.forEach((kid, index) => {
2831
+ const slot = place(kid);
2832
+ if (index === 0) first = slot;
2833
+ last = slot;
2834
+ });
2835
+ const slot = (first + last) / 2;
2836
+ slotOf.set(node.id, slot);
2837
+ return slot;
2838
+ };
2839
+ /* v8 ignore next 1 -- a forest always holds at least the (possibly
2840
+ synthesized) current node. */
2841
+ if (forest.nodes.length > 0) place(forest.nodes[0]);
2842
+ const perLevel = stageWidth > 0 ? Math.max(2, Math.floor(stageWidth / SLOT_MIN)) : 0;
2843
+ if (perLevel > 0 && leafSlots > perLevel) {
2844
+ const bandSlot = Math.min(SLOT_MAX, stageWidth / perLevel);
2845
+ const width = perLevel * bandSlot;
2846
+ const points = [];
2847
+ let row = 0;
2848
+ const emitBand = (nodes, depth) => {
2849
+ const inset = (width - nodes.length * bandSlot) / 2;
2850
+ nodes.forEach((node, i) => {
2851
+ points.push({
2852
+ id: node.id,
2853
+ x: inset + (i + .5) * bandSlot,
2854
+ y: PAD_Y + row * LEVEL_H,
2855
+ depth
2856
+ });
2857
+ });
2858
+ row++;
2859
+ let band = [];
2860
+ const flush = () => {
2861
+ if (band.length === 0) return;
2862
+ const packed = band;
2863
+ band = [];
2864
+ emitBand(packed, depth + 1);
2865
+ };
2866
+ for (const node of nodes) {
2867
+ const kids = childrenOf.get(node.id) ?? [];
2868
+ for (let start = 0; start < kids.length; start += perLevel) {
2869
+ const group = kids.slice(start, start + perLevel);
2870
+ if (band.length + group.length > perLevel) flush();
2871
+ band.push(...group);
2872
+ if (band.length === perLevel) flush();
2873
+ }
2874
+ }
2875
+ flush();
2876
+ };
2877
+ /* v8 ignore next 1 -- a forest always holds at least the current node. */
2878
+ if (forest.nodes.length > 0) emitBand([forest.nodes[0]], 0);
2879
+ return {
2880
+ width,
2881
+ height: PAD_Y + (row - 1) * LEVEL_H + CELL_H + 28,
2882
+ captionW: bandSlot - CAPTION_GUTTER,
2883
+ points,
2884
+ links: linksOf(forest, points)
2885
+ };
2886
+ }
2887
+ const slot = stageWidth > 0 && leafSlots > 1 ? Math.min(SLOT_MAX, stageWidth / leafSlots) : SLOT_MAX;
2888
+ const points = forest.nodes.map((node) => ({
2889
+ id: node.id,
2890
+ /* v8 ignore next 1 -- place() visits every node: the forest is exactly
2891
+ the root's subtree by construction. */
2892
+ x: (slotOf.get(node.id) ?? 0) * slot + slot / 2,
2893
+ y: PAD_Y + node.depth * LEVEL_H,
2894
+ depth: node.depth
2895
+ }));
2896
+ const maxDepth = points.reduce((max, p) => Math.max(max, p.depth), 0);
2897
+ return {
2898
+ width: leafSlots * slot,
2899
+ height: PAD_Y + maxDepth * LEVEL_H + CELL_H + 28,
2900
+ captionW: slot - CAPTION_GUTTER,
2901
+ points,
2902
+ links: linksOf(forest, points)
2903
+ };
2904
+ }
2905
+ /**
2906
+ * Family hue by level-1 subtree index: the golden angle keeps consecutive
2907
+ * families maximally separated on the color wheel without a hand-tuned palette.
2908
+ */
2909
+ function familyHue(index) {
2910
+ return `hsl(${Math.round(index * 137.508) % 360} 58% 52%)`;
2911
+ }
2912
+ /** Parent→child links: exit the parent's cell bottom, enter the child's top. */
2913
+ function linksOf(forest, points) {
2914
+ const pointOf = new Map(points.map((p) => [p.id, p]));
2915
+ const nodeOf = new Map(forest.nodes.map((n) => [n.id, n]));
2916
+ const runningIds = new Set(forest.nodes.filter((n) => n.running).map((n) => n.id));
2917
+ const links = [];
2918
+ for (const edge of forest.edges) {
2919
+ const from = pointOf.get(edge.from);
2920
+ const to = pointOf.get(edge.to);
2921
+ /* v8 ignore next 2 -- edges are emitted only for visited parent/child
2922
+ pairs, so both points always exist. */
2923
+ if (from === void 0 || to === void 0) continue;
2924
+ links.push({
2925
+ to: edge.to,
2926
+ running: runningIds.has(edge.to),
2927
+ /* v8 ignore next 1 -- edges only connect visited nodes. */
2928
+ color: familyHue(nodeOf.get(edge.to)?.family ?? 0),
2929
+ x1: from.x,
2930
+ y1: from.y + CELL_H,
2931
+ x2: to.x,
2932
+ y2: to.y - 26 - 10
2933
+ });
2934
+ }
2935
+ return links;
2936
+ }
2937
+ /**
2938
+ * One fused ring per agent — the exact semantics of the chat composer's own
2939
+ * context ring: the composition parts, scaled to the occupancy share of the
2940
+ * window, fill the circle, and a neutral remainder marks the free window.
2941
+ * With no known window the composition fills the whole circle; with no
2942
+ * composition (pressure-only rows) a single threshold-colored arc carries
2943
+ * the occupancy; a known window with zero occupancy draws the free outline.
2944
+ */
2945
+ function ringSegments(parts, pct, radius, fallbackColor) {
2946
+ const circumference = 2 * Math.PI * radius;
2947
+ const occ = pct === null ? 1 : Math.min(100, Math.max(0, pct)) / 100;
2948
+ let total = 0;
2949
+ for (const p of parts) total += p.value > 0 ? p.value : 0;
2950
+ const segs = [];
2951
+ let offset = 0;
2952
+ if (total > 0) for (const p of parts) {
2953
+ if (p.value <= 0) continue;
2954
+ const len = circumference * (p.value / total) * occ;
2955
+ if (len <= 0) continue;
2956
+ segs.push({
2957
+ key: p.key,
2958
+ color: p.color,
2959
+ len,
2960
+ offset,
2961
+ free: false
2962
+ });
2963
+ offset += len;
2964
+ }
2965
+ else if (pct !== null && occ > 0) {
2966
+ segs.push({
2967
+ key: "fill",
2968
+ color: fallbackColor,
2969
+ len: circumference * occ,
2970
+ offset: 0,
2971
+ free: false
2972
+ });
2973
+ offset = circumference * occ;
2974
+ }
2975
+ if (pct !== null && offset < circumference) segs.push({
2976
+ key: "free",
2977
+ color: "",
2978
+ len: circumference - offset,
2979
+ offset,
2980
+ free: true
2981
+ });
2982
+ return segs;
2983
+ }
2984
+ /** Session-switch navigation, fail-soft: a stale row (list rebuilt between snapshot and click) loses its open() race and is ignored. */
2985
+ function openAgentSession(face, id) {
2986
+ if (face === null || typeof face.open !== "function") return;
2987
+ try {
2988
+ face.open(id);
2989
+ } catch {}
2990
+ }
2991
+ /** Narrow `ctx.get('sessions')` to the card's face (null = harness without the outward sessions service). */
2992
+ function sessionsFaceOf(ctx) {
2993
+ const rec = asRecord(ctx.get("sessions"));
2994
+ if (rec === null) return null;
2995
+ const list = asRecord(rec.list);
2996
+ if (list === null || typeof list.getSnapshot !== "function" || typeof list.subscribe !== "function") return null;
2997
+ return rec;
2998
+ }
2999
+ /** Compact duration: `42s`, `3m05s`, `1h07m` (shared by both locales). */
3000
+ function fmtDuration(ms) {
3001
+ if (!Number.isFinite(ms) || ms < 0) return "—";
3002
+ const s = Math.round(ms / 1e3);
3003
+ if (s < 60) return `${s}s`;
3004
+ const m = Math.floor(s / 60);
3005
+ if (m < 60) return `${m}m${String(s % 60).padStart(2, "0")}s`;
3006
+ return `${Math.floor(m / 60)}h${String(m % 60).padStart(2, "0")}m`;
3007
+ }
3008
+ //#endregion
3009
+ //#region src/client/components/agentGraph.tsx
3010
+ /** Caption box height under a node (3 wrapped label lines + the tokens line). */
3011
+ const CAPTION_H = 60;
3012
+ /** Fallback arc color for pressure-only nodes (no composition data), by fill ratio. */
3013
+ function ringColorOf(pct) {
3014
+ if (pct === null) return "var(--dsw-alias-border-l1)";
3015
+ if (pct >= 90) return "#ef4444";
3016
+ if (pct >= 70) return "#f59e0b";
3017
+ return "#22c55e";
3018
+ }
3019
+ function makeAgentGraph(ctx, kit) {
3020
+ const { t, fmt, catLabel } = kit;
3021
+ function AgentGraph(props) {
3022
+ const face = React.useMemo(() => sessionsFaceOf(ctx), []);
3023
+ const subscribe = React.useCallback((fn) => {
3024
+ if (face === null) return () => {};
3025
+ /* v8 ignore next 2 -- sessionsFaceOf returns a face only after proving list.subscribe. */
3026
+ if (face.list === void 0) return () => {};
3027
+ return face.list.subscribe(fn);
3028
+ }, [face]);
3029
+ const getSnapshot = React.useCallback(() => {
3030
+ if (face === null) return null;
3031
+ /* v8 ignore next 2 -- sessionsFaceOf proves list before returning the face. */
3032
+ if (face.list === void 0) return null;
3033
+ return face.list.getSnapshot();
3034
+ }, [face]);
3035
+ const snapshot = React.useSyncExternalStore(subscribe, getSnapshot);
3036
+ const sessionId = props.sessionId;
3037
+ const [hoverId, setHoverId] = React.useState(null);
3038
+ const stageRef = React.useRef(null);
3039
+ const [stageWidth, setStageWidth] = React.useState(0);
3040
+ React.useEffect(() => {
3041
+ /* v8 ignore start -- jsdom has neither ResizeObserver nor layout; tests exercise the natural-pitch fallback (stageWidth 0). */
3042
+ const el = stageRef.current;
3043
+ if (el === null || typeof ResizeObserver !== "function") return;
3044
+ setStageWidth(el.clientWidth);
3045
+ const observer = new ResizeObserver(() => {
3046
+ setStageWidth(el.clientWidth);
3047
+ });
3048
+ observer.observe(el);
3049
+ return () => {
3050
+ observer.disconnect();
3051
+ };
3052
+ /* v8 ignore stop */
3053
+ }, []);
3054
+ React.useEffect(() => {
3055
+ if (face === null || typeof sessionId !== "string" || sessionId === "") return;
3056
+ if (typeof face.refreshSubagents !== "function") return;
3057
+ face.refreshSubagents(sessionId).catch(() => {});
3058
+ }, [face, sessionId]);
3059
+ const built = React.useMemo(() => {
3060
+ const forest = agentForestOf(snapshot, sessionId, props.self);
3061
+ return forest !== null ? {
3062
+ forest,
3063
+ layout: layoutForest(forest, stageWidth)
3064
+ } : null;
3065
+ }, [
3066
+ snapshot,
3067
+ sessionId,
3068
+ props.self,
3069
+ stageWidth
3070
+ ]);
3071
+ if (built === null) return null;
3072
+ const { forest, layout } = built;
3073
+ const byId = new Map(forest.nodes.map((n) => [n.id, n]));
3074
+ /* v8 ignore next 1 -- agentForestOf anchors the forest at the current
3075
+ session, so a current node always exists. */
3076
+ const current = forest.nodes.find((n) => n.isCurrent) ?? forest.nodes[0];
3077
+ const inspected = (hoverId !== null ? byId.get(hoverId) : void 0) ?? current;
3078
+ const runningCount = forest.nodes.filter((n) => n.running).length;
3079
+ let totalTokens = 0;
3080
+ for (const n of forest.nodes) totalTokens += n.head !== null ? n.head.tokens : 0;
3081
+ const open = (id) => {
3082
+ if (id === current.id) return;
3083
+ openAgentSession(face, id);
3084
+ };
3085
+ const keyOpen = (id) => (ev) => {
3086
+ if (ev.key !== "Enter" && ev.key !== " ") return;
3087
+ ev.preventDefault();
3088
+ open(id);
3089
+ };
3090
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-agents" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("agents.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("agents.sub"))), /* @__PURE__ */ React.createElement("div", { className: "lc-agents-chips" }, /* @__PURE__ */ React.createElement("span", { className: "lc-agents-chip" }, t("agents.chip.count", { n: forest.nodes.length + forest.overflow })), /* @__PURE__ */ React.createElement("span", { className: "lc-agents-chip" + (runningCount > 0 ? " lc-agents-chip-on" : "") }, t("agents.chip.running", { n: runningCount })), totalTokens > 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-agents-chip" }, t("agents.chip.tokens", { n: fmt(totalTokens) })) : null, forest.overflow > 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-agents-chip" }, t("agents.more", { n: forest.overflow })) : null), /* @__PURE__ */ React.createElement("div", {
3091
+ className: "lc-agents-stage",
3092
+ ref: stageRef
3093
+ }, /* @__PURE__ */ React.createElement("svg", {
3094
+ className: "lc-agents-svg",
3095
+ width: layout.width,
3096
+ height: layout.height,
3097
+ viewBox: `0 0 ${layout.width} ${layout.height}`
3098
+ }, layout.links.map((link) => {
3099
+ const d = `M ${link.x1} ${link.y1} L ${link.x2} ${link.y2}`;
3100
+ return /* @__PURE__ */ React.createElement("g", { key: link.to }, /* @__PURE__ */ React.createElement("path", {
3101
+ className: "lc-agents-link" + (link.running ? " lc-agents-link-live" : ""),
3102
+ d,
3103
+ stroke: link.color,
3104
+ fill: "none"
3105
+ }), link.running ? /* @__PURE__ */ React.createElement("path", {
3106
+ className: "lc-agents-flow",
3107
+ d,
3108
+ stroke: link.color,
3109
+ fill: "none"
3110
+ }) : null);
3111
+ }), forest.nodes.map((node) => {
3112
+ const point = layout.points.find((p) => p.id === node.id);
3113
+ /* v8 ignore next 2 -- layoutForest positions every forest node,
3114
+ so the lookup never misses. */
3115
+ if (point === void 0) return null;
3116
+ return /* @__PURE__ */ React.createElement(AgentNodeView, {
3117
+ key: node.id,
3118
+ node,
3119
+ x: point.x,
3120
+ y: point.y,
3121
+ captionW: layout.captionW,
3122
+ hovered: hoverId === node.id,
3123
+ onHover: setHoverId,
3124
+ onOpen: open,
3125
+ onKeyOpen: keyOpen(node.id),
3126
+ t,
3127
+ fmt
3128
+ });
3129
+ }))), forest.solo ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty lc-agents-solo" }, t("agents.solo")) : null, /* @__PURE__ */ React.createElement(Inspector, {
3130
+ node: inspected,
3131
+ t,
3132
+ fmt
3133
+ }), /* @__PURE__ */ React.createElement("div", { className: "lc-agents-legend" }, CATS.map((c) => /* @__PURE__ */ React.createElement("span", {
3134
+ key: c.key,
3135
+ className: "lc-agents-legend-item"
3136
+ }, /* @__PURE__ */ React.createElement("i", { style: { background: c.color } }), catLabel(c.key))), /* @__PURE__ */ React.createElement("span", { className: "lc-agents-legend-item" }, /* @__PURE__ */ React.createElement("i", { className: "lc-agents-legend-free" }), t("agents.legend.free")), /* @__PURE__ */ React.createElement("span", { className: "lc-agents-legend-item" }, /* @__PURE__ */ React.createElement("i", { className: "lc-agents-legend-edge" }), t("agents.running"))));
3137
+ }
3138
+ return AgentGraph;
3139
+ }
3140
+ function AgentNodeView(props) {
3141
+ const { node, x, y, captionW } = props;
3142
+ const pct = node.head !== null ? node.head.pct : null;
3143
+ const ring = 2 * Math.PI * 20;
3144
+ const segs = node.head !== null ? ringSegments(node.head.parts, pct, 20, ringColorOf(pct)) : [];
3145
+ const cls = "lc-agent-node" + (node.isCurrent ? " lc-agent-self" : "") + (node.running ? " lc-agent-running" : "") + (node.completed && !node.running ? " lc-agent-done" : "") + (props.hovered ? " lc-agent-hover" : "") + (node.isCurrent ? "" : " lc-agent-clickable");
3146
+ return /* @__PURE__ */ React.createElement("g", {
3147
+ className: cls,
3148
+ transform: `translate(${x}, ${y})`,
3149
+ "data-agent": node.id,
3150
+ role: node.isCurrent ? "img" : "button",
3151
+ tabIndex: node.isCurrent ? void 0 : 0,
3152
+ onClick: () => {
3153
+ props.onOpen(node.id);
3154
+ },
3155
+ onKeyDown: props.onKeyOpen,
3156
+ onMouseEnter: () => {
3157
+ props.onHover(node.id);
3158
+ },
3159
+ onMouseLeave: () => {
3160
+ props.onHover(null);
3161
+ }
3162
+ }, /* @__PURE__ */ React.createElement("circle", {
3163
+ className: "lc-agent-halo",
3164
+ r: 35
3165
+ }), /* @__PURE__ */ React.createElement("circle", {
3166
+ className: "lc-agent-track",
3167
+ r: 26
3168
+ }), segs.map((seg) => /* @__PURE__ */ React.createElement("circle", {
3169
+ key: seg.key,
3170
+ className: "lc-agent-seg" + (seg.free ? " lc-agent-free" : ""),
3171
+ r: 20,
3172
+ stroke: seg.free ? void 0 : seg.color,
3173
+ strokeDasharray: `${seg.len} ${ring - seg.len}`,
3174
+ strokeDashoffset: -seg.offset,
3175
+ transform: "rotate(-90)"
3176
+ })), /* @__PURE__ */ React.createElement("text", {
3177
+ className: "lc-agent-pct",
3178
+ textAnchor: "middle",
3179
+ dy: "0.32em"
3180
+ }, pct !== null ? `${pct}%` : node.head !== null ? props.fmt(node.head.tokens) : "—"), /* @__PURE__ */ React.createElement("foreignObject", {
3181
+ x: -captionW / 2,
3182
+ y: 34,
3183
+ width: captionW,
3184
+ height: CAPTION_H
3185
+ }, /* @__PURE__ */ React.createElement("div", { className: "lc-agent-caption" }, /* @__PURE__ */ React.createElement("div", { className: "lc-agent-label" }, node.label, node.isCurrent ? /* @__PURE__ */ React.createElement("span", { className: "lc-agents-badge lc-agent-self-badge" }, props.t("agents.self")) : null), /* @__PURE__ */ React.createElement("div", { className: "lc-agent-tokens" }, node.head !== null ? props.fmt(node.head.tokens) : "—"))));
3186
+ }
3187
+ /** The detail strip mirroring the hovered (or current) node: identity, occupancy, activity, and the open hint. */
3188
+ function Inspector(props) {
3189
+ const { node, t, fmt } = props;
3190
+ const bits = [];
3191
+ if (node.head !== null) {
3192
+ const head = node.head;
3193
+ const window = head.window !== void 0 ? ` / ${fmt(head.window)}` : "";
3194
+ const pct = head.pct !== null ? ` · ${head.pct}%` : "";
3195
+ bits.push(`${fmt(head.tokens)}${window}${pct}`);
3196
+ }
3197
+ if (node.requests > 0) bits.push(t("agents.requests", { n: node.requests }));
3198
+ if (node.billed !== null && node.billed > 0) bits.push(t("agents.billed", { n: fmt(node.billed) }));
3199
+ if (node.durationMs !== null) bits.push(fmtDuration(node.durationMs));
3200
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-agents-inspector" }, /* @__PURE__ */ React.createElement("b", { className: "lc-agents-inspector-name" }, node.label), node.isCurrent ? /* @__PURE__ */ React.createElement("span", { className: "lc-agents-badge" }, t("agents.self")) : null, node.running ? /* @__PURE__ */ React.createElement("span", { className: "lc-agents-badge lc-agents-badge-on" }, t("agents.running")) : null, node.identity !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-agents-badge" }, t(node.identity.mode === "one-shot" ? "agents.oneshot" : "agents.continuable")) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-agents-inspector-stats" }, bits.join(" · ")), !node.isCurrent ? /* @__PURE__ */ React.createElement("span", { className: "lc-agents-inspector-open" }, t("agents.open")) : null);
3201
+ }
3202
+ //#endregion
2216
3203
  //#region src/client/components/events.tsx
2217
3204
  const EVENT_ICONS = {
2218
3205
  compaction: "✂",
@@ -2274,6 +3261,14 @@ window.__ModuleLoader__.load({
2274
3261
  eventAt
2275
3262
  };
2276
3263
  }
3264
+ /**
3265
+ * Set the native `title` on every label whose text overflows its box (the styled tips cover cards, but a plain
3266
+ * ellipsis row still needs the native fallback); reads (scrollWidth/clientWidth) and writes (title) stay separate
3267
+ * from layout-affecting work, and callers gate WHEN this runs so it never becomes a per-render forced layout.
3268
+ */
3269
+ function syncTitles(root) {
3270
+ for (const el of root.querySelectorAll(".lc-event-label")) el.title = el.scrollWidth > el.clientWidth ? el.textContent || "" : "";
3271
+ }
2277
3272
  function makeEventList(kit) {
2278
3273
  const { t, fmt, fmtTime, eventLabel, eventAt } = kit;
2279
3274
  return function EventList(props) {
@@ -2281,46 +3276,214 @@ window.__ModuleLoader__.load({
2281
3276
  React.useLayoutEffect(() => {
2282
3277
  const root = rootRef.current;
2283
3278
  if (!root) return;
2284
- const sync = () => {
2285
- for (const el of root.querySelectorAll(".lc-event-label")) el.title = el.scrollWidth > el.clientWidth ? el.textContent || "" : "";
3279
+ syncTitles(root);
3280
+ }, [props.events]);
3281
+ React.useEffect(() => {
3282
+ const onResize = () => {
3283
+ const root = rootRef.current;
3284
+ if (root !== null) syncTitles(root);
2286
3285
  };
2287
- sync();
2288
- window.addEventListener("resize", sync);
3286
+ window.addEventListener("resize", onResize);
2289
3287
  return () => {
2290
- window.removeEventListener("resize", sync);
3288
+ window.removeEventListener("resize", onResize);
2291
3289
  };
2292
- });
3290
+ }, []);
2293
3291
  if (props.events.length === 0) return /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("events.empty"));
2294
3292
  const sorted = props.events.slice().reverse();
2295
3293
  return /* @__PURE__ */ React.createElement("div", {
2296
3294
  className: "lc-events",
2297
3295
  ref: rootRef
2298
- }, sorted.map((ev, i) => {
3296
+ }, sorted.map((ev) => {
2299
3297
  const label = eventLabel(ev);
2300
3298
  const at = eventAt(ev);
2301
3299
  const glyph = ev.kind === "inject" ? /* @__PURE__ */ React.createElement(_deepseek_ai_dsh_client_ui_primitives.IconPlusOutline16, null) : ev.kind === "model" ? /* @__PURE__ */ React.createElement(_deepseek_ai_dsh_client_ui_primitives.IconBranchOutline16, null) : EVENT_ICONS[ev.kind] || "•";
2302
3300
  return /* @__PURE__ */ React.createElement("div", {
2303
- key: `${ev.seq}-${i}`,
3301
+ key: ev.seq,
2304
3302
  className: "lc-event"
2305
3303
  }, /* @__PURE__ */ React.createElement("span", { className: "lc-event-icon lc-event-" + ev.kind }, glyph), /* @__PURE__ */ React.createElement("span", { className: "lc-kind lc-kind-" + ev.kind }, t("kind." + ev.kind)), /* @__PURE__ */ React.createElement("span", { className: "lc-event-label" }, label), at !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-event-at" }, at) : null, ev.tokens ? /* @__PURE__ */ React.createElement("span", { className: "lc-event-tokens" + (ev.kind === "inject" ? " lc-up" : " lc-down") }, (ev.kind === "inject" ? "+" : "−") + fmt(ev.tokens)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-event-time" }, fmtTime(ev.time)));
2306
3304
  }));
2307
3305
  };
2308
3306
  }
3307
+ //#endregion
3308
+ //#region src/client/components/fileCard.tsx
3309
+ function makeFileCard(kit, settings) {
3310
+ const { t, fmt, fmtTime } = kit;
3311
+ function formGlyph(form) {
3312
+ return form === "image" ? "🖼" : form === "dir" ? "📁" : "📄";
3313
+ }
3314
+ function matches(e, f) {
3315
+ if (f === "all") return true;
3316
+ if (f === "image") return e.form === "image";
3317
+ if (f === "read") return e.reads > 0;
3318
+ if (f === "write") return e.writes > 0;
3319
+ return e.searches > 0;
3320
+ }
3321
+ /** Signed line pair, harness diff semantics: growth on the success token, shrinkage on the error token. */
3322
+ function DeltaPair(props) {
3323
+ return /* @__PURE__ */ React.createElement("span", { className: "lc-fa-delta" }, props.added > 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-fa-up" }, "+" + fmt(props.added)) : null, props.removed > 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-fa-down" }, "−" + fmt(props.removed)) : null);
3324
+ }
3325
+ return React.memo(function FileCard(props) {
3326
+ const { activity } = props;
3327
+ const [filter, setFilter] = React.useState("all");
3328
+ const [sort, setSort] = React.useState(() => settings.defaultFileSort());
3329
+ const [query, setQuery] = React.useState("");
3330
+ const [openPath, setOpenPath] = React.useState(null);
3331
+ const q = query.trim().toLowerCase();
3332
+ const countOf = (e) => filter === "read" ? e.reads : filter === "write" ? e.writes : filter === "search" ? e.searches : e.ops.length;
3333
+ const shown = activity.entries.filter((e) => matches(e, filter) && (q === "" || e.path.toLowerCase().includes(q))).sort((a, b) => sort === "count" ? countOf(b) - countOf(a) || b.ops[0].seq - a.ops[0].seq : sort === "latest" ? b.ops[0].seq - a.ops[0].seq : a.path < b.path ? -1 : 1);
3334
+ const chips = [
3335
+ {
3336
+ key: "all",
3337
+ files: activity.entries.length,
3338
+ ops: activity.totals.read.ops + activity.totals.write.ops + activity.totals.search.ops
3339
+ },
3340
+ {
3341
+ key: "read",
3342
+ files: activity.totals.read.files,
3343
+ ops: activity.totals.read.ops
3344
+ },
3345
+ {
3346
+ key: "write",
3347
+ files: activity.totals.write.files,
3348
+ ops: activity.totals.write.ops
3349
+ },
3350
+ {
3351
+ key: "search",
3352
+ files: activity.totals.search.files,
3353
+ ops: activity.totals.search.ops
3354
+ },
3355
+ {
3356
+ key: "image",
3357
+ files: activity.totals.image.files,
3358
+ ops: activity.totals.image.ops
3359
+ }
3360
+ ];
3361
+ const opLine = (op) => /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { className: "lc-fa-op-time" }, op.time !== void 0 ? fmtTime(op.time) : "—"), /* @__PURE__ */ React.createElement("span", { className: "lc-fa-op-tool" }, op.tool), op.detail !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-fa-op-detail" }, op.detail) : null, op.added + op.removed > 0 ? /* @__PURE__ */ React.createElement(DeltaPair, {
3362
+ added: op.added,
3363
+ removed: op.removed
3364
+ }) : null, op.err ? /* @__PURE__ */ React.createElement("span", {
3365
+ className: "lc-br-err-dot",
3366
+ title: t("node.failed")
3367
+ }) : null);
3368
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("files.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, props.scope)), activity.entries.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("files.empty")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "lc-fa-ctl" }, /* @__PURE__ */ React.createElement("div", { className: "lc-gran" }, chips.map((c) => /* @__PURE__ */ React.createElement("button", {
3369
+ key: c.key,
3370
+ type: "button",
3371
+ className: "lc-gran-btn" + (filter === c.key ? " lc-gran-on" : "") + (c.key === "read" || c.key === "write" || c.key === "search" ? " lc-fa-chip-" + c.key : ""),
3372
+ title: t("files.chipTip", {
3373
+ files: c.files,
3374
+ ops: c.ops
3375
+ }),
3376
+ onClick: () => {
3377
+ setFilter((cur) => cur === c.key ? "all" : c.key);
3378
+ }
3379
+ }, t("files.kind." + c.key), /* @__PURE__ */ React.createElement("b", { className: "lc-fa-n" }, fmt(c.ops))))), /* @__PURE__ */ React.createElement("input", {
3380
+ className: "lc-fa-search",
3381
+ value: query,
3382
+ placeholder: t("files.search"),
3383
+ onChange: (ev) => {
3384
+ setQuery(ev.target.value);
3385
+ }
3386
+ })), /* @__PURE__ */ React.createElement("div", { className: "lc-fa-meta" }, /* @__PURE__ */ React.createElement("span", null, t("files.files", { n: activity.entries.length })), activity.totals.added + activity.totals.removed > 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-fa-meta-delta" }, /* @__PURE__ */ React.createElement(DeltaPair, {
3387
+ added: activity.totals.added,
3388
+ removed: activity.totals.removed
3389
+ }), /* @__PURE__ */ React.createElement("span", {
3390
+ className: "lc-fa-meta-tip",
3391
+ role: "tooltip"
3392
+ }, t("files.deltaTip"))) : null, /* @__PURE__ */ React.createElement("span", {
3393
+ className: "lc-gran lc-fa-sort",
3394
+ role: "group",
3395
+ title: t("files.sortTip")
3396
+ }, [
3397
+ "count",
3398
+ "latest",
3399
+ "path"
3400
+ ].map((k) => /* @__PURE__ */ React.createElement("button", {
3401
+ key: k,
3402
+ type: "button",
3403
+ className: "lc-gran-btn" + (sort === k ? " lc-gran-on" : ""),
3404
+ onClick: () => {
3405
+ setSort(k);
3406
+ }
3407
+ }, t("files.sort." + k))))), shown.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("files.noMatch")) : /* @__PURE__ */ React.createElement("div", { className: "lc-fa-list" }, shown.map((e) => {
3408
+ const open = openPath === e.path;
3409
+ const trimmed = e.path.endsWith("/") ? e.path.slice(0, -1) : e.path;
3410
+ const slash = trimmed.lastIndexOf("/");
3411
+ const dir = slash >= 0 ? trimmed.slice(0, slash + 1) : "";
3412
+ const base = slash >= 0 ? trimmed.slice(slash + 1) : trimmed;
3413
+ return /* @__PURE__ */ React.createElement("div", {
3414
+ key: e.path,
3415
+ className: "lc-fa-item" + (open ? " lc-fa-item-on" : "")
3416
+ }, /* @__PURE__ */ React.createElement("button", {
3417
+ type: "button",
3418
+ className: "lc-fa-row",
3419
+ title: e.path,
3420
+ onClick: () => {
3421
+ setOpenPath(open ? null : e.path);
3422
+ }
3423
+ }, /* @__PURE__ */ React.createElement("span", { className: "lc-br-chev" + (open ? " lc-br-chev-on" : "") }, "▸"), /* @__PURE__ */ React.createElement("span", {
3424
+ className: "lc-fa-form",
3425
+ title: t("files.form." + e.form)
3426
+ }, formGlyph(e.form)), /* @__PURE__ */ React.createElement("span", { className: "lc-fa-path" }, dir !== "" ? /* @__PURE__ */ React.createElement("em", null, dir) : null, /* @__PURE__ */ React.createElement("b", null, base)), e.reads > 0 ? /* @__PURE__ */ React.createElement("span", {
3427
+ className: "lc-fa-badge lc-fa-b-read",
3428
+ title: t("files.kind.read")
3429
+ }, /* @__PURE__ */ React.createElement("i", null), fmt(e.reads)) : null, e.writes > 0 ? /* @__PURE__ */ React.createElement("span", {
3430
+ className: "lc-fa-badge lc-fa-b-write",
3431
+ title: t("files.kind.write")
3432
+ }, /* @__PURE__ */ React.createElement("i", null), fmt(e.writes)) : null, e.searches > 0 ? /* @__PURE__ */ React.createElement("span", {
3433
+ className: "lc-fa-badge lc-fa-b-search",
3434
+ title: t("files.kind.search")
3435
+ }, /* @__PURE__ */ React.createElement("i", null), fmt(e.searches)) : null, e.added + e.removed > 0 ? /* @__PURE__ */ React.createElement(DeltaPair, {
3436
+ added: e.added,
3437
+ removed: e.removed
3438
+ }) : null, e.errs > 0 ? /* @__PURE__ */ React.createElement("span", {
3439
+ className: "lc-br-err-dot",
3440
+ title: t("files.errs", { n: e.errs })
3441
+ }) : null, e.ops[0].time !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-fa-time" }, fmtTime(e.ops[0].time)) : null), open ? /* @__PURE__ */ React.createElement("div", { className: "lc-fa-ops" }, e.ops.map((op) => {
3442
+ const onLocate = props.onLocate;
3443
+ return onLocate !== void 0 ? /* @__PURE__ */ React.createElement("button", {
3444
+ key: op.seq,
3445
+ type: "button",
3446
+ className: "lc-fa-op lc-fa-op-link",
3447
+ title: t("files.locate"),
3448
+ onClick: () => {
3449
+ onLocate(op);
3450
+ }
3451
+ }, opLine(op)) : /* @__PURE__ */ React.createElement("div", {
3452
+ key: op.seq,
3453
+ className: "lc-fa-op"
3454
+ }, opLine(op));
3455
+ })) : null);
3456
+ }))));
3457
+ });
3458
+ }
2309
3459
  const PLUGIN_REPO = "https://github.com/bowenliang123/dsh-context";
2310
3460
  const PLUGIN_REPO_SHORT = PLUGIN_REPO.replace(/^https?:\/\/github\.com\//, "");
2311
3461
  //#endregion
2312
3462
  //#region src/client/latestVersion.ts
2313
3463
  /**
2314
- * One live check for the Plugin-info card: lazy with 1h TTL; every failure
2315
- * mode narrows to null so the card silently keeps its static version.
3464
+ * One live check for the Plugin-info card: lazy with 1h TTL; registries
3465
+ * are tried strictly in order official npm first, npmmirror only as a
3466
+ * fallback — and one-at-a-time, never in parallel. Each source's failure
3467
+ * (non-ok response, missing/non-string version, network error) advances
3468
+ * to the next one; only an all-source failure resolves to null so the
3469
+ * card silently keeps its static version.
2316
3470
  */
2317
- const REGISTRY_URL = "https://registry.npmjs.org/dsh-context/latest";
3471
+ const REGISTRY_HOSTS = ["https://registry.npmjs.org", "https://registry.npmmirror.com"];
2318
3472
  const TTL_MS = 36e5;
2319
3473
  let cached = null;
3474
+ function readVersion(host) {
3475
+ return fetch(host + "/dsh-context/latest").then((res) => res.ok ? res.json() : null).then((body) => body !== null && typeof body.version === "string" ? body.version : null).catch(() => null);
3476
+ }
2320
3477
  function fetchLatestVersion() {
2321
3478
  if (!cached || Date.now() - cached.at >= TTL_MS) cached = {
2322
3479
  at: Date.now(),
2323
- promise: fetch(REGISTRY_URL).then((res) => res.ok ? res.json() : null).then((body) => body !== null && typeof body.version === "string" ? body.version : null).catch(() => null)
3480
+ promise: (async () => {
3481
+ for (const host of REGISTRY_HOSTS) {
3482
+ const v = await readVersion(host);
3483
+ if (v !== null) return v;
3484
+ }
3485
+ return null;
3486
+ })()
2324
3487
  };
2325
3488
  return cached.promise;
2326
3489
  }
@@ -2349,7 +3512,7 @@ window.__ModuleLoader__.load({
2349
3512
  return function PluginInfo() {
2350
3513
  const [latest, setLatest] = React.useState(null);
2351
3514
  React.useEffect(() => {
2352
- if ("0.32.0".includes("-dev")) return;
3515
+ if ("0.34.0".includes("-dev")) return;
2353
3516
  let on = true;
2354
3517
  fetchLatestVersion().then((v) => {
2355
3518
  if (on && v) setLatest(v);
@@ -2358,8 +3521,8 @@ window.__ModuleLoader__.load({
2358
3521
  on = false;
2359
3522
  };
2360
3523
  }, []);
2361
- const update = latest !== null && isNewerVersion(latest, "0.32.0") ? latest : null;
2362
- const nameValue = ["dsh-context (v0.32.0)"];
3524
+ const update = latest !== null && isNewerVersion(latest, "0.34.0") ? latest : null;
3525
+ const nameValue = ["dsh-context (v0.34.0)"];
2363
3526
  if (update) nameValue.push(/* @__PURE__ */ React.createElement("span", {
2364
3527
  key: "update",
2365
3528
  className: "lc-pi-update"
@@ -2374,26 +3537,29 @@ window.__ModuleLoader__.load({
2374
3537
  /**
2375
3538
  * One brief row: a fixed-width kind tag plus one glanceable line. The tag carries a styled, instant explanation bubble (the
2376
3539
  * `.lc-stat-tip` pattern); the content span keeps the native title (preview + locate hint), so the two never stack.
2377
- * Clickable when the browser linkage is wired.
3540
+ * Clickable when the browser linkage is wired AND the row carries a node (the always-present In row's empty state stays inert).
2378
3541
  */
2379
3542
  function BriefRow(props) {
2380
- const cls = "lc-brief-row" + (props.onLocate !== void 0 ? " lc-brief-row-link" : "");
2381
3543
  const inner = /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { className: "lc-brief-tag" }, props.tag, /* @__PURE__ */ React.createElement("span", {
2382
3544
  className: "lc-brief-tip",
2383
3545
  role: "tooltip"
2384
3546
  }, props.tagTip)), props.children);
2385
- if (props.onLocate === void 0) return /* @__PURE__ */ React.createElement("div", { className: cls }, inner);
3547
+ if (props.node === void 0 || props.onLocate === void 0) return /* @__PURE__ */ React.createElement("div", { className: "lc-brief-row" }, inner);
3548
+ const node = props.node;
3549
+ const onLocate = props.onLocate;
2386
3550
  const locate = () => {
2387
- props.onLocate?.(props.node, props.isResponse);
3551
+ onLocate(node, props.isResponse);
2388
3552
  };
2389
3553
  return /* @__PURE__ */ React.createElement("button", {
2390
3554
  type: "button",
2391
- className: cls,
3555
+ className: "lc-brief-row lc-brief-row-link",
2392
3556
  onClick: locate
2393
3557
  }, inner);
2394
3558
  }
2395
3559
  /** One-line identity of a surface node: text preview, call breadcrumb, tool name, or a localized placeholder. */
2396
3560
  function nodeLine(n, conv) {
3561
+ /* v8 ignore if -- chipParts returns for tool cats upstream; only
3562
+ inject-with-skill and drift nodes ever reach nodeLine. Defensive. */
2397
3563
  if (n.cat === "tool") return callSummaryOf(conv) ?? n.tool ?? t("node.toolResult");
2398
3564
  if (n.text !== void 0 && n.text !== "") return n.text;
2399
3565
  if (n.skill !== void 0) return t("node.skillTag", { name: n.skill });
@@ -2401,13 +3567,71 @@ window.__ModuleLoader__.load({
2401
3567
  const summary = blockSummaryOf(conv);
2402
3568
  return n.calls.join(" › ") + (summary !== null ? " · " + summary : "");
2403
3569
  }
3570
+ /* v8 ignore if -- chipParts returns for assistant cats upstream. Defensive. */
2404
3571
  if (n.cat === "assistant") return t("node.empty");
3572
+ /* v8 ignore if -- inject nodes arrive only WITH a skill (the text/skill
3573
+ arms above return first); inject-without-skill never reaches nodeLine. */
2405
3574
  if (n.cat === "inject") return t("form." + (n.form ?? "context"));
2406
3575
  return t("node.nonText");
2407
3576
  }
3577
+ /**
3578
+ * One line's anatomy — a compact FACT tag plus the preview text, mirroring the Context browser's element rows so a
3579
+ * brief line reads exactly like the browser row its click reveals: tool results tag the tool name (skill results the
3580
+ * skill name), assistant replies tag the call breadcrumb, injections tag the form, user messages tag image attachments.
3581
+ */
3582
+ function chipParts(n, conv) {
3583
+ if (n.cat === "tool") {
3584
+ const summary = callSummaryOf(conv);
3585
+ if (n.skill !== void 0) return {
3586
+ tag: t("node.skillTag", { name: n.skill }),
3587
+ text: summary ?? ""
3588
+ };
3589
+ if (n.tool !== void 0) return {
3590
+ tag: n.tool,
3591
+ text: summary ?? ""
3592
+ };
3593
+ return {
3594
+ tag: null,
3595
+ text: summary ?? t("node.toolResult")
3596
+ };
3597
+ }
3598
+ if (n.cat === "assistant") {
3599
+ const names = n.calls !== void 0 && n.calls.length > 0 ? n.calls : callNamesOf(conv);
3600
+ const own = n.text !== void 0 && n.text !== "" ? n.text : blockSummaryOf(conv) ?? "";
3601
+ return {
3602
+ tag: names.length > 0 ? names.join(" › ") : null,
3603
+ text: own !== "" ? own : names.length > 0 ? "" : t("node.empty")
3604
+ };
3605
+ }
3606
+ if (n.cat === "inject" && n.skill === void 0) {
3607
+ const text = n.text !== void 0 && n.text !== "" ? n.form === "snapshot" ? t("node.snapshot") + n.text : n.text : "";
3608
+ return {
3609
+ tag: t("form." + (n.form ?? "context")),
3610
+ text
3611
+ };
3612
+ }
3613
+ if (n.cat === "user") {
3614
+ const imgs = n.imgs ?? 0;
3615
+ return {
3616
+ tag: imgs > 0 ? t("attach.image") + (imgs > 1 ? " ×" + String(imgs) : "") : null,
3617
+ text: n.text ?? ""
3618
+ };
3619
+ }
3620
+ return {
3621
+ tag: null,
3622
+ text: nodeLine(n, conv)
3623
+ };
3624
+ }
3625
+ /** The native-title line for a fact+text pair: 'tag · text', degrading to whichever half exists. */
3626
+ function factTitle(tag, text) {
3627
+ return tag !== null ? text !== "" ? tag + " · " + text : tag : text;
3628
+ }
3629
+ /**
3630
+ * The brief's container ALWAYS renders — a fixed three-row lane (`.lc-brief`'s min-height) so scrubbing the chart never
3631
+ * changes the panel's height; unknown or empty steps just leave parts of the lane blank instead of collapsing it.
3632
+ */
2408
3633
  function BriefSection(props) {
2409
- const { opener, inputs, response } = props.brief;
2410
- if (opener === void 0 && inputs.length === 0 && response === void 0) return null;
3634
+ const { opener, inputs, response } = props.brief ?? { inputs: [] };
2411
3635
  const convOf = props.convOf ?? (() => void 0);
2412
3636
  const hint = props.onLocate !== void 0 ? " — " + t("brief.locate") : "";
2413
3637
  const locateChip = props.onLocate === void 0 ? void 0 : (n) => (e) => {
@@ -2415,54 +3639,52 @@ window.__ModuleLoader__.load({
2415
3639
  props.onLocate?.(n, false);
2416
3640
  };
2417
3641
  const MAX_CHIPS = 3;
2418
- let replyText = "";
2419
- let replyArrow = false;
2420
- if (response !== void 0) {
2421
- const conv = convOf(response.seq);
2422
- replyText = nodeLine(response, conv);
2423
- if (response.calls !== void 0 && response.calls.length > 0) replyArrow = true;
2424
- else {
2425
- const joined = callNamesOf(conv);
2426
- if (joined.length > 0) replyText += " → " + joined.join(" › ");
2427
- }
2428
- }
2429
- return /* @__PURE__ */ React.createElement("div", { className: "lc-brief" }, opener !== void 0 ? /* @__PURE__ */ React.createElement(BriefRow, {
3642
+ /**
3643
+ * The ONE content unit of the brief — inputs and the reply share the same chip anatomy (error dot, fact tag,
3644
+ * preview text). Input chips are compact and individually clickable (each locates its own node); the reply is a
3645
+ * single chip grown to the row's width, left inert because the row button already locates it.
3646
+ */
3647
+ const nodeChip = (n, onClick, grow = false) => {
3648
+ const { tag, text } = chipParts(n, convOf(n.seq));
3649
+ return /* @__PURE__ */ React.createElement("span", {
3650
+ key: n.seq,
3651
+ className: "lc-brief-chip" + (grow ? " lc-brief-chip-grow" : "") + (onClick !== void 0 ? " lc-brief-chip-link" : ""),
3652
+ title: factTitle(tag, text) + hint,
3653
+ onClick
3654
+ }, n.err === true ? /* @__PURE__ */ React.createElement("span", { className: "lc-br-err-dot" }) : null, tag !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-brief-chip-tag" }, tag) : null, text !== "" ? /* @__PURE__ */ React.createElement("span", { className: "lc-brief-chip-text" }, text) : null);
3655
+ };
3656
+ const openerParts = opener !== void 0 ? chipParts(opener, convOf(opener.seq)) : null;
3657
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-brief" }, opener !== void 0 && openerParts !== null ? /* @__PURE__ */ React.createElement(BriefRow, {
2430
3658
  tag: t("brief.turn"),
2431
3659
  tagTip: t("brief.turnTip"),
2432
3660
  node: opener,
2433
3661
  isResponse: false,
2434
3662
  onLocate: props.onLocate
2435
- }, /* @__PURE__ */ React.createElement("span", {
3663
+ }, openerParts.tag !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-brief-fact" }, openerParts.tag) : null, openerParts.text !== "" ? /* @__PURE__ */ React.createElement("span", {
2436
3664
  className: "lc-brief-text",
2437
- title: nodeLine(opener, convOf(opener.seq)) + hint
2438
- }, nodeLine(opener, convOf(opener.seq)))) : null, inputs.length > 0 ? /* @__PURE__ */ React.createElement(BriefRow, {
3665
+ title: factTitle(openerParts.tag, openerParts.text) + hint
3666
+ }, openerParts.text) : null) : null, /* @__PURE__ */ React.createElement(BriefRow, {
2439
3667
  tag: t("brief.input"),
2440
3668
  tagTip: t("brief.inputTip"),
2441
3669
  node: inputs[0],
2442
3670
  isResponse: false,
2443
3671
  onLocate: props.onLocate
2444
- }, inputs.slice(0, MAX_CHIPS).map((n) => /* @__PURE__ */ React.createElement("span", {
2445
- key: n.seq,
2446
- className: "lc-brief-chip" + (props.onLocate !== void 0 ? " lc-brief-chip-link" : ""),
2447
- title: nodeLine(n, convOf(n.seq)) + hint,
2448
- onClick: locateChip !== void 0 ? locateChip(n) : void 0
2449
- }, n.err === true ? /* @__PURE__ */ React.createElement("span", { className: "lc-br-err-dot" }) : null, nodeLine(n, convOf(n.seq)))), inputs.length > MAX_CHIPS ? /* @__PURE__ */ React.createElement("span", { className: "lc-brief-more" }, t("brief.more", { n: inputs.length - MAX_CHIPS })) : null) : null, response !== void 0 ? /* @__PURE__ */ React.createElement(BriefRow, {
3672
+ }, inputs.length > 0 ? /* @__PURE__ */ React.createElement(React.Fragment, null, inputs.slice(0, MAX_CHIPS).map((n) => nodeChip(n, locateChip?.(n))), inputs.length > MAX_CHIPS ? /* @__PURE__ */ React.createElement("span", { className: "lc-brief-more" }, t("brief.more", { n: inputs.length - MAX_CHIPS })) : null) : /* @__PURE__ */ React.createElement("span", { className: "lc-brief-empty" }, t("brief.noInputs"))), response !== void 0 ? /* @__PURE__ */ React.createElement(BriefRow, {
2450
3673
  tag: t("brief.reply"),
2451
3674
  tagTip: t("brief.replyTip"),
2452
3675
  node: response,
2453
3676
  isResponse: true,
2454
3677
  onLocate: props.onLocate
2455
- }, /* @__PURE__ */ React.createElement("span", {
2456
- className: "lc-brief-text",
2457
- title: replyText + hint
2458
- }, replyArrow ? "→ " : "", replyText)) : null);
3678
+ }, nodeChip(response, void 0, true)) : null);
2459
3679
  }
2460
- return function RequestDetail(props) {
3680
+ return React.memo(function RequestDetail(props) {
2461
3681
  const req = props.request;
2462
3682
  if (!req) return null;
2463
3683
  const isTurn = req.stepCount !== void 0 && req.stepCount > 1;
2464
3684
  const head = isTurn ? t("detail.turn", {
2465
3685
  t: req.turn ?? 0,
3686
+ /* v8 ignore next -- isTurn already guarantees stepCount !== undefined;
3687
+ the fallback is defensive. */
2466
3688
  n: req.stepCount ?? 0
2467
3689
  }) : t("detail.step", {
2468
3690
  t: req.turn ?? 0,
@@ -2487,11 +3709,11 @@ window.__ModuleLoader__.load({
2487
3709
  return /* @__PURE__ */ React.createElement("div", { className: "lc-detail" }, /* @__PURE__ */ React.createElement("div", { className: "lc-detail-head" }, /* @__PURE__ */ React.createElement("b", null, head), marker !== null && markerAt !== null ? /* @__PURE__ */ React.createElement("span", {
2488
3710
  className: "lc-detail-marker",
2489
3711
  title: eventLabel(marker)
2490
- }, "✂ " + markerAt) : null, isTurn ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-tag" }, t("detail.lastStep")) : null, delta ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-tag" }, t("gran.delta")) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-detail-time" }, fmtTime(req.time)), delta ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-metric" + (net > 0 ? " lc-detail-metric-up" : net < 0 ? " lc-detail-metric-down" : "") }, t("tip.delta", { n: (net > 0 ? "+" : "") + fmt(net) })) : null, !delta && req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-metric" }, t("detail.actual", { n: fmt(req.prompt) })) : null, !delta && req.output !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-metric" }, t("detail.output", { n: fmt(req.output) })) : null, !delta && req.prompt !== void 0 && req.cacheRead !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-metric" }, t("detail.cache", { n: cacheHitPercent$1(req.cacheRead, req.prompt) ?? "—" })) : null), props.brief !== null && props.brief !== void 0 ? /* @__PURE__ */ React.createElement(BriefSection, {
3712
+ }, "✂ " + markerAt) : null, isTurn ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-tag" }, t("detail.lastStep")) : null, delta ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-tag" }, t("gran.delta")) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-detail-time" }, fmtTime(req.time)), delta ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-metric" + (net > 0 ? " lc-detail-metric-up" : net < 0 ? " lc-detail-metric-down" : "") }, t("tip.delta", { n: (net > 0 ? "+" : "") + fmt(net) })) : null, !delta && req.prompt !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-metric" }, t("detail.actual", { n: fmt(req.prompt) })) : null, !delta && req.output !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-metric" }, t("detail.output", { n: fmt(req.output) })) : null, !delta && req.prompt !== void 0 && req.cacheRead !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-metric" }, t("detail.cache", { n: cacheHitPercent$1(req.cacheRead, req.prompt) ?? "—" })) : null), /* @__PURE__ */ React.createElement(BriefSection, {
2491
3713
  brief: props.brief,
2492
3714
  convOf: props.convOf,
2493
3715
  onLocate: props.onLocate
2494
- }) : null, /* @__PURE__ */ React.createElement(StackedBar, {
3716
+ }), /* @__PURE__ */ React.createElement(StackedBar, {
2495
3717
  parts,
2496
3718
  height: 10
2497
3719
  }), /* @__PURE__ */ React.createElement("div", { className: "lc-detail-rows" }, CATS.map((c, i) => {
@@ -2514,7 +3736,7 @@ window.__ModuleLoader__.load({
2514
3736
  }
2515
3737
  })), delta ? /* @__PURE__ */ React.createElement("span", { className: "lc-detail-num" + (v > 0 ? " lc-detail-num-up" : v < 0 ? " lc-detail-num-down" : "") }, (v > 0 ? "+" : "") + fmt(v)) : /* @__PURE__ */ React.createElement("span", { className: "lc-detail-num" }, "≈" + fmt(v)), /* @__PURE__ */ React.createElement("span", { className: "lc-detail-pct" }, !delta && req.total > 0 ? `${Math.round(v / req.total * 100)}%` : ""));
2516
3738
  })));
2517
- };
3739
+ });
2518
3740
  }
2519
3741
  //#endregion
2520
3742
  //#region src/client/cost.ts
@@ -2844,25 +4066,8 @@ window.__ModuleLoader__.load({
2844
4066
  const scrollRef = React.useRef(null);
2845
4067
  const scrolledOnce = React.useRef(false);
2846
4068
  const lastGranRef = React.useRef(props.granularity);
2847
- const [edges, setEdges] = React.useState({
2848
- left: false,
2849
- right: false
2850
- });
2851
- const edgesRef = React.useRef(edges);
2852
- const updateEdges = (el) => {
2853
- const left = el.scrollLeft > 4;
2854
- const right = el.scrollLeft + el.clientWidth < el.scrollWidth - 4;
2855
- const prev = edgesRef.current;
2856
- if (prev.left === left && prev.right === right) return;
2857
- edgesRef.current = {
2858
- left,
2859
- right
2860
- };
2861
- setEdges({
2862
- left,
2863
- right
2864
- });
2865
- };
4069
+ const lastSeqRef = React.useRef(0);
4070
+ const prevScrollWidthRef = React.useRef(0);
2866
4071
  /**
2867
4072
  * 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
2868
4073
  * (narrower-than-label blocks stay put); reads (offsetWidth) batch before writes (transform) to avoid layout thrash — out-of-view
@@ -2894,7 +4099,12 @@ window.__ModuleLoader__.load({
2894
4099
  };
2895
4100
  React.useLayoutEffect(() => {
2896
4101
  const el = scrollRef.current;
4102
+ /* v8 ignore next 1 -- the scroll div renders unconditionally and React
4103
+ attaches refs before layout effects run; el is never null here. */
2897
4104
  if (el === null) return;
4105
+ const newestSeq = requests.length === 0 ? 0 : requests[requests.length - 1].seq;
4106
+ const grew = newestSeq !== lastSeqRef.current;
4107
+ const widthBeforeAppend = prevScrollWidthRef.current;
2898
4108
  if (props.granularity !== lastGranRef.current) {
2899
4109
  lastGranRef.current = props.granularity;
2900
4110
  scrolledOnce.current = false;
@@ -2906,14 +4116,19 @@ window.__ModuleLoader__.load({
2906
4116
  el.scrollLeft = Math.max(0, gi * 16 + BAR_W / 2 - el.clientWidth / 2);
2907
4117
  }
2908
4118
  props.onFocusTurnHandled();
2909
- }
2910
- if (!scrolledOnce.current) {
4119
+ } else if (!scrolledOnce.current) {
2911
4120
  scrolledOnce.current = true;
2912
4121
  el.scrollLeft = el.scrollWidth;
2913
- } else if (el.scrollLeft + el.clientWidth >= el.scrollWidth - 24) el.scrollLeft = el.scrollWidth;
2914
- updateEdges(el);
4122
+ } else if (grew && el.scrollLeft + el.clientWidth >= widthBeforeAppend - 24) el.scrollLeft = el.scrollWidth;
4123
+ lastSeqRef.current = newestSeq;
4124
+ prevScrollWidthRef.current = el.scrollWidth;
2915
4125
  updateTurnLabels(el);
2916
- });
4126
+ syncTip(el);
4127
+ }, [
4128
+ props.granularity,
4129
+ props.focusTurn,
4130
+ requests
4131
+ ]);
2917
4132
  const tipOf = (req) => {
2918
4133
  const head = req.stepCount !== void 0 && req.stepCount > 1 ? t("tip.turn", {
2919
4134
  t: req.turn ?? 0,
@@ -2925,6 +4140,8 @@ window.__ModuleLoader__.load({
2925
4140
  const reply = props.replyTips?.get(req.seq);
2926
4141
  const tail = reply !== void 0 ? " · “" + (reply.length > 48 ? reply.slice(0, 48) + "…" : reply) + "”" : "";
2927
4142
  if (delta) {
4143
+ /* v8 ignore next 1 -- delta mode only receives records from
4144
+ deltaOf, which always assigns net; the fallback is defensive. */
2928
4145
  const n = req.net ?? 0;
2929
4146
  return head + " · " + fmtTime(req.time) + " · " + t("tip.delta", { n: (n > 0 ? "+" : "") + fmt(n) }) + tail;
2930
4147
  }
@@ -2932,17 +4149,43 @@ window.__ModuleLoader__.load({
2932
4149
  };
2933
4150
  const hoveredIdx = props.hoveredSeq !== null ? requests.findIndex((r) => r.seq === props.hoveredSeq) : -1;
2934
4151
  const hoveredReq = hoveredIdx >= 0 ? requests[hoveredIdx] : null;
4152
+ const tipColRef = React.useRef(0);
4153
+ /**
4154
+ * Glue the hover tip to its bar's VISIBLE slice. The tip deliberately does NOT live inside the scrolling
4155
+ * content: an absolutely-positioned child of a scroller contributes to its scrollable overflow, so a wide
4156
+ * reply preview on a right-edge bar used to inflate scrollWidth on every hover and flap the horizontal
4157
+ * scrollbar open/closed — jumping the whole card. Reads (offsetWidth/clientWidth) batch before the single
4158
+ * style write; unchanged transforms write nothing.
4159
+ */
4160
+ const syncTip = (el) => {
4161
+ /* v8 ignore next 1 -- the scroll div renders unconditionally while mounted, so its parent exists. */
4162
+ const tip = (el.parentElement ?? document.body).querySelector(".lc-chart-tip");
4163
+ if (tip === null) return;
4164
+ const lw = tip.offsetWidth;
4165
+ const cw = el.clientWidth;
4166
+ const half = Math.min(lw / 2, cw / 2);
4167
+ const cx = Math.min(Math.max(tipColRef.current - el.scrollLeft, half), cw - half);
4168
+ const next = `translate(${Math.round(cx - lw / 2)}px, 0)`;
4169
+ if (tip.style.transform !== next) tip.style.transform = next;
4170
+ };
4171
+ React.useLayoutEffect(() => {
4172
+ /* v8 ignore next 1 -- the scroll div renders unconditionally and React attaches refs before
4173
+ layout effects run; el is never null here. */
4174
+ if (scrollRef.current === null) return;
4175
+ tipColRef.current = hoveredIdx >= 0 ? hoveredIdx * 16 + BAR_W / 2 : 0;
4176
+ syncTip(scrollRef.current);
4177
+ });
2935
4178
  return /* @__PURE__ */ React.createElement("div", { className: "lc-chartrow" }, /* @__PURE__ */ React.createElement("div", { className: "lc-axis" }, delta ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { className: "lc-axis-top" }, (maxUp > 0 ? "+" : "") + fmt(maxUp)), /* @__PURE__ */ React.createElement("span", {
2936
4179
  className: "lc-axis-mid",
2937
4180
  style: { top: `${13 + upPx}px` }
2938
- }, "0"), /* @__PURE__ */ React.createElement("span", { className: "lc-axis-bot" }, (maxDown > 0 ? "-" : "") + fmt(maxDown))) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { className: "lc-axis-top" }, fmt(maxTotal)), /* @__PURE__ */ React.createElement("span", { className: "lc-axis-mid" }, fmt(Math.round(maxTotal / 2))), /* @__PURE__ */ React.createElement("span", { className: "lc-axis-bot" }, "0"))), /* @__PURE__ */ React.createElement("div", {
4181
+ }, "0"), /* @__PURE__ */ React.createElement("span", { className: "lc-axis-bot" }, (maxDown > 0 ? "-" : "") + fmt(maxDown))) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("span", { className: "lc-axis-top" }, fmt(maxTotal)), /* @__PURE__ */ React.createElement("span", { className: "lc-axis-mid" }, fmt(Math.round(maxTotal / 2))), /* @__PURE__ */ React.createElement("span", { className: "lc-axis-bot" }, "0"))), /* @__PURE__ */ React.createElement("div", { className: "lc-chart-wrap" }, /* @__PURE__ */ React.createElement("div", {
2939
4182
  className: "lc-chart-scroll" + (props.activeTurn !== null ? " lc-chart-dim" : ""),
2940
4183
  ref: scrollRef,
2941
4184
  onScroll: (e) => {
2942
- updateEdges(e.currentTarget);
2943
4185
  updateTurnLabels(e.currentTarget);
4186
+ syncTip(e.currentTarget);
2944
4187
  }
2945
- }, edges.left ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-l" }) : null, /* @__PURE__ */ React.createElement("div", {
4188
+ }, /* @__PURE__ */ React.createElement("div", {
2946
4189
  className: "lc-chart",
2947
4190
  onMouseLeave: () => {
2948
4191
  props.onHover(null);
@@ -2963,10 +4206,7 @@ window.__ModuleLoader__.load({
2963
4206
  deltaScale: delta ? deltaScale : void 0,
2964
4207
  onSelect: props.onSelect,
2965
4208
  onHover: props.onHover
2966
- }))), hoveredReq !== null ? /* @__PURE__ */ React.createElement("div", {
2967
- className: "lc-chart-tip",
2968
- style: { left: `${hoveredIdx * 16 + BAR_W / 2}px` }
2969
- }, tipOf(hoveredReq)) : null, /* @__PURE__ */ React.createElement("div", {
4209
+ }))), /* @__PURE__ */ React.createElement("div", {
2970
4210
  className: "lc-turns",
2971
4211
  onMouseLeave: () => {
2972
4212
  props.onHoverTurn(null);
@@ -2988,7 +4228,7 @@ window.__ModuleLoader__.load({
2988
4228
  props.onPickTurn(grp.turn);
2989
4229
  }
2990
4230
  }, /* @__PURE__ */ React.createElement("span", { className: "lc-turn-label" }, `T${grp.turn}`));
2991
- }))), edges.right ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-fade lc-chart-fade-r" }) : null);
4231
+ }))), hoveredReq !== null ? /* @__PURE__ */ React.createElement("div", { className: "lc-chart-tip" }, tipOf(hoveredReq)) : null));
2992
4232
  };
2993
4233
  }
2994
4234
  //#endregion
@@ -3008,10 +4248,11 @@ window.__ModuleLoader__.load({
3008
4248
  const TrendChart = makeTrendChart(kit);
3009
4249
  const RequestDetail = makeRequestDetail(kit, StackedBar);
3010
4250
  const EventList = makeEventList(kit);
3011
- const NodeList = makeNodeList(kit);
4251
+ const FileCard = makeFileCard(kit, settings);
3012
4252
  const StatsBoard = makeStatsBoard(kit);
3013
4253
  const PluginInfo = makePluginInfo(kit);
3014
4254
  const ContextBrowser = makeContextBrowser(kit, StackedBar);
4255
+ const AgentGraph = makeAgentGraph(ctx, kit);
3015
4256
  const ErrorBoundary = makeErrorBoundary(t);
3016
4257
  function ContextViewBody(props) {
3017
4258
  const sessionId = props.sessionId;
@@ -3035,10 +4276,6 @@ window.__ModuleLoader__.load({
3035
4276
  return p.length === 1 ? [...EVENT_KINDS] : p.filter((x) => x !== k);
3036
4277
  });
3037
4278
  };
3038
- const [toolFocus, setToolFocus] = React.useState(null);
3039
- const clearToolFocus = React.useCallback(() => {
3040
- setToolFocus(null);
3041
- }, []);
3042
4279
  const [nodeFocus, setNodeFocus] = React.useState(null);
3043
4280
  const clearNodeFocus = React.useCallback(() => {
3044
4281
  setNodeFocus(null);
@@ -3049,6 +4286,7 @@ window.__ModuleLoader__.load({
3049
4286
  if (conversation === void 0 || typeof conversation.resolveImage !== "function") return void 0;
3050
4287
  return (attachment) => conversation.resolveImage(sessionId, attachment);
3051
4288
  }, [sessionId]);
4289
+ const fetchContent = React.useMemo(() => typeof sessionId === "string" && sessionId !== "" ? makeContentFetcher(ctx, sessionId) : void 0, [ctx, sessionId]);
3052
4290
  const rootRef = React.useRef(null);
3053
4291
  const scrollerRef = React.useRef(null);
3054
4292
  const restoredRef = React.useRef(null);
@@ -3056,6 +4294,8 @@ window.__ModuleLoader__.load({
3056
4294
  if (typeof sessionId !== "string" || sessionId === "" || data === null) return;
3057
4295
  if (restoredRef.current === sessionId) return;
3058
4296
  restoredRef.current = sessionId;
4297
+ /* v8 ignore next 3 -- a layout effect body only runs while mounted and
4298
+ both render paths attach rootRef, so the null arm cannot fire. */
3059
4299
  const scroller = rootRef.current !== null ? rootRef.current.closest("[data-conversation-scroll]") : null;
3060
4300
  if (scroller === null) return;
3061
4301
  scrollerRef.current = scroller;
@@ -3072,7 +4312,6 @@ window.__ModuleLoader__.load({
3072
4312
  const requests = data ? data.requests : [];
3073
4313
  const events = data ? data.events : [];
3074
4314
  const shownEvents = pickedKinds.length === EVENT_KINDS.length ? events : events.filter((e) => pickedKinds.includes(e.kind));
3075
- const nodes = data ? data.nodes : [];
3076
4315
  const displayRequests = React.useMemo(() => granularity === "turn" ? aggregateByTurn(requests) : requests, [requests, granularity]);
3077
4316
  const markers = React.useMemo(() => attachMarkers(displayRequests, events), [displayRequests, events]);
3078
4317
  const briefList = React.useMemo(() => data ? briefNodes(data) : [], [data]);
@@ -3083,14 +4322,6 @@ window.__ModuleLoader__.load({
3083
4322
  for (const n of convNodes ?? []) m.set(n.seq, n);
3084
4323
  return m;
3085
4324
  }, [convNodes]);
3086
- if (!data) return /* @__PURE__ */ React.createElement("div", {
3087
- className: "lc-root",
3088
- ref: rootRef
3089
- }, /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("loading")));
3090
- const markerOf = (req) => {
3091
- const i = displayRequests.indexOf(req);
3092
- return i >= 0 ? markers[i] : void 0;
3093
- };
3094
4325
  let pinnedIdx = -1;
3095
4326
  for (let i = 0; i < displayRequests.length; i++) if (displayRequests[i].seq === selectedSeq) pinnedIdx = i;
3096
4327
  const pinnedReq = pinnedIdx >= 0 ? displayRequests[pinnedIdx] : null;
@@ -3104,9 +4335,35 @@ window.__ModuleLoader__.load({
3104
4335
  if (activeIdx < 0) activeIdx = pinnedIdx;
3105
4336
  if (activeIdx < 0 && displayRequests.length > 0) activeIdx = displayRequests.length - 1;
3106
4337
  const activeReq = activeIdx >= 0 ? displayRequests[activeIdx] : null;
3107
- const brief = activeReq !== null ? briefOf(briefList, displayRequests, activeIdx) : null;
3108
- const convOf = (seq) => bySeq.get(seq);
3109
- const locateNode = (node, isResponse) => {
4338
+ let filesBefore = null;
4339
+ if (activeReq !== null) {
4340
+ const ri = requests.findIndex((r) => r.seq === activeReq.seq);
4341
+ filesBefore = ri + 1 < requests.length ? requests[ri + 1].seq : null;
4342
+ }
4343
+ const brief = React.useMemo(() => activeReq !== null ? briefOf(briefList, displayRequests, activeIdx) : null, [
4344
+ activeReq,
4345
+ briefList,
4346
+ displayRequests,
4347
+ activeIdx
4348
+ ]);
4349
+ const convOf = React.useCallback((seq) => bySeq.get(seq), [bySeq]);
4350
+ const fileActivity = React.useMemo(() => activityOf(briefList, convOf, filesBefore), [
4351
+ briefList,
4352
+ convOf,
4353
+ filesBefore
4354
+ ]);
4355
+ const locateFileOp = React.useCallback((op) => {
4356
+ const step = locateStepOf(requests, op.seq, op.gone);
4357
+ if (step === null) return;
4358
+ setNodeFocus({
4359
+ step,
4360
+ seq: op.seq,
4361
+ cat: "tool"
4362
+ });
4363
+ }, [requests]);
4364
+ const locateNode = React.useCallback((node, isResponse) => {
4365
+ /* v8 ignore next 1 -- locateNode is only wired to brief rows, and
4366
+ brief !== null guarantees activeReq !== null in the same closure. */
3110
4367
  if (activeReq === null) return;
3111
4368
  const next = isResponse && activeIdx + 1 < displayRequests.length ? displayRequests[activeIdx + 1] : null;
3112
4369
  const step = isResponse ? next !== null ? next.seq : "live" : activeReq.seq;
@@ -3115,7 +4372,30 @@ window.__ModuleLoader__.load({
3115
4372
  seq: node.seq,
3116
4373
  cat: node.cat
3117
4374
  });
4375
+ }, [
4376
+ activeReq,
4377
+ activeIdx,
4378
+ displayRequests
4379
+ ]);
4380
+ if (!data) return /* @__PURE__ */ React.createElement("div", {
4381
+ className: "lc-root",
4382
+ ref: rootRef
4383
+ }, /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("loading")));
4384
+ const markerOf = (req) => {
4385
+ const i = displayRequests.indexOf(req);
4386
+ /* v8 ignore next 1 -- the only caller passes displayRequests[activeIdx],
4387
+ an element of the very array indexOf scans. */
4388
+ return i >= 0 ? markers[i] : void 0;
3118
4389
  };
4390
+ const head = headlineOf(data, pressure, breakdown);
4391
+ let fileScope = t("files.scopeLatest");
4392
+ if (activeReq !== null && filesBefore !== null) fileScope = activeReq.stepCount !== void 0 && activeReq.stepCount > 1 ? t("detail.turn", {
4393
+ t: activeReq.turn ?? 0,
4394
+ n: activeReq.stepCount
4395
+ }) : t("detail.step", {
4396
+ t: activeReq.turn ?? 0,
4397
+ s: activeReq.step ?? 0
4398
+ });
3119
4399
  let activeTurn = hoverTurn;
3120
4400
  if (activeTurn === null && hoveredSeq !== null) {
3121
4401
  for (const req of displayRequests) if (req.seq === hoveredSeq) {
@@ -3123,9 +4403,9 @@ window.__ModuleLoader__.load({
3123
4403
  break;
3124
4404
  }
3125
4405
  }
3126
- const head = headlineOf(data, pressure, breakdown);
3127
4406
  const localeSvc = ctx.get("locale");
3128
4407
  const activeLocale = localeSvc !== void 0 && typeof localeSvc.getLocale === "function" ? localeSvc.getLocale().active : "en";
4408
+ const subtitle = (data.model ?? "") + (data.provider ? " · " + data.provider : "");
3129
4409
  return /* @__PURE__ */ React.createElement("div", {
3130
4410
  className: "lc-root",
3131
4411
  ref: rootRef
@@ -3139,11 +4419,9 @@ window.__ModuleLoader__.load({
3139
4419
  locale: activeLocale
3140
4420
  }), /* @__PURE__ */ React.createElement(PluginInfo, null)), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-col" }, /* @__PURE__ */ React.createElement(CurrentComposition, {
3141
4421
  head,
3142
- subtitle: (data.model ? data.model : "") + (data.provider ? " · " + data.provider : ""),
4422
+ subtitle,
3143
4423
  hoverKey: hoverCat,
3144
- onHoverKey: setHoverCat,
3145
- tools: data.toolList,
3146
- onToolFocus: setToolFocus
4424
+ onHoverKey: setHoverCat
3147
4425
  }), /* @__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("trend.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("trend.hint")), /* @__PURE__ */ React.createElement("div", { className: "lc-trend-ctl" }, /* @__PURE__ */ React.createElement("div", { className: "lc-gran" }, /* @__PURE__ */ React.createElement("button", {
3148
4426
  className: "lc-gran-btn" + (granularity === "step" ? " lc-gran-on" : ""),
3149
4427
  onClick: () => {
@@ -3191,6 +4469,9 @@ window.__ModuleLoader__.load({
3191
4469
  }), /* @__PURE__ */ React.createElement(RequestDetail, {
3192
4470
  request: activeReq,
3193
4471
  prev: trendMode === "delta" && activeIdx >= 0 ? activeIdx > 0 ? displayRequests[activeIdx - 1] : null : void 0,
4472
+ /* v8 ignore next 1 -- RequestDetail renders only when
4473
+ displayRequests.length > 0, which forces activeReq
4474
+ non-null via the activeIdx fallback above. */
3194
4475
  marker: activeReq !== null ? markerOf(activeReq) : void 0,
3195
4476
  brief,
3196
4477
  convOf,
@@ -3199,13 +4480,11 @@ window.__ModuleLoader__.load({
3199
4480
  data,
3200
4481
  headers,
3201
4482
  useSession: props.useSession,
3202
- loadOlderHistory: props.loadOlderHistory,
4483
+ fetchContent,
3203
4484
  previewSeq: hoveredSeq,
3204
4485
  pinSeq: pinnedReq !== null ? pinnedReq.seq : null,
3205
4486
  hoverKey: hoverCat,
3206
4487
  onHoverKey: setHoverCat,
3207
- toolFocus,
3208
- onToolFocusHandled: clearToolFocus,
3209
4488
  nodeFocus,
3210
4489
  onNodeFocusHandled: clearNodeFocus,
3211
4490
  loadImage
@@ -3215,10 +4494,18 @@ window.__ModuleLoader__.load({
3215
4494
  onClick: () => {
3216
4495
  toggleKind(k);
3217
4496
  }
3218
- }, t("kind." + k))))), /* @__PURE__ */ React.createElement(EventList, { events: shownEvents })), /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("nodes.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("nodes.hint"))), /* @__PURE__ */ React.createElement(NodeList, {
3219
- nodes,
3220
- dropped: data.droppedNodes || 0
3221
- }))), /* @__PURE__ */ React.createElement("div", { className: "lc-foot" }, t("footer")));
4497
+ }, t("kind." + k))))), /* @__PURE__ */ React.createElement(EventList, { events: shownEvents })), /* @__PURE__ */ React.createElement(FileCard, {
4498
+ activity: fileActivity,
4499
+ scope: fileScope,
4500
+ onLocate: locateFileOp
4501
+ })), /* @__PURE__ */ React.createElement(AgentGraph, {
4502
+ sessionId: typeof sessionId === "string" ? sessionId : void 0,
4503
+ self: {
4504
+ head,
4505
+ billed: usage !== null ? numOf(usage.uncachedInputTokens) + numOf(usage.outputTokens) + numOf(usage.cacheReadTokens) + numOf(usage.cacheWriteTokens) : null,
4506
+ requests: requests.length
4507
+ }
4508
+ }), /* @__PURE__ */ React.createElement("div", { className: "lc-foot" }, t("footer")));
3222
4509
  }
3223
4510
  return function ContextView(props) {
3224
4511
  return h(ErrorBoundary, null, h(ContextViewBody, props));
@@ -3239,7 +4526,7 @@ window.__ModuleLoader__.load({
3239
4526
  }
3240
4527
  //#endregion
3241
4528
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles.css.mjs
3242
- 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);margin-left:auto;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;scrollbar-gutter:stable;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-grid-zero{border-top-style:solid;border-top-color:#80808080}.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-up,.lc-bar-down{width:100%;display:flex;position:absolute;left:0}.lc-bar-up{flex-direction:column-reverse}.lc-bar-down{flex-direction:column}.lc-bar-up>div,.lc-bar-down>div{width:100%}.lc-bar-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;align-items:center;gap:6px 8px;margin-bottom:10px;display:flex}.lc-detail-head b{color:var(--dsw-alias-label-primary);font-size:13px}.lc-detail-time{font-variant-numeric:tabular-nums;white-space:nowrap;margin-left:auto;font-size:12px}.lc-detail-marker{color:var(--dsw-alias-state-warn-primary);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);border-radius:6px;padding:1px 7px;font-size:11px}.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-metric{font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--dsw-alias-label-primary);background:color-mix(in srgb, var(--dsw-alias-label-secondary) 14%, transparent);border-radius:6px;padding:2px 8px;font-size:12px;font-weight:600}.lc-detail-metric-up{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent)}.lc-detail-metric-down{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent)}.lc-detail-rows{grid-template-columns:1fr 1fr;gap:4px 24px;margin-top:10px;display:grid}.lc-brief{flex-direction:column;gap:2px;margin:-4px 0 10px;display:flex}.lc-brief-row{min-width:0;color:var(--dsw-alias-label-primary);text-align:left;background:0 0;border:0;border-radius:6px;align-items:center;gap:8px;margin-left:-6px;margin-right:-6px;padding:3px 6px;font-family:inherit;font-size:12px;line-height:1.5;transition:background-color .12s;display:flex}.lc-brief-row-link:hover{background:var(--dsw-alias-interactive-bg-hover)}button.lc-brief-row-link{cursor:pointer}.lc-brief-tag{box-sizing:border-box;text-align:center;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);min-width:56px;color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:0 6px;font-size:11px;position:relative}.lc-brief-tip{z-index:6;white-space:normal;text-align:left;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;max-width:240px;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;bottom:calc(100% + 6px);left:0;box-shadow:0 2px 8px #0000002e}.lc-brief-tag:hover .lc-brief-tip{opacity:1}.lc-brief-text{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.lc-brief-chip{text-overflow:ellipsis;white-space:nowrap;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);max-width:220px;color:var(--dsw-alias-label-primary);border-radius:5px;align-items:center;gap:5px;padding:0 7px;font-size:12px;transition:background-color .12s;display:inline-flex;overflow:hidden}.lc-brief-chip-link{cursor:pointer}.lc-brief-chip-link:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-brief-more{color:var(--dsw-alias-label-secondary);flex:none;font-size:11px}.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;position:relative;overflow:hidden}.lc-bar-fill{border-radius:3px;height:100%;display:block}.lc-bar-zero{border-left:1px solid #80808073;position:absolute;top:0;bottom:0;left:50%}.lc-bar-fill-up,.lc-bar-fill-down{height:100%;display:block;position:absolute;top:0}.lc-bar-fill-up{border-radius:0 3px 3px 0;left:50%}.lc-bar-fill-down{border-radius:3px 0 0 3px;right:50%}.lc-detail-num{text-align:right;font-variant-numeric:tabular-nums;width:52px}.lc-detail-num-up{color:var(--dsw-alias-state-success-primary)}.lc-detail-num-down{color:var(--dsw-alias-state-error-primary)}.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}";
4529
+ 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{flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-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);margin-left:auto;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{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-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-wrap{flex:1;min-width:0;position:relative}.lc-chart-scroll{scrollbar-gutter:stable;padding-bottom:var(--dsh-scrollbar-width,8px);overflow:auto hidden}.lc-chart{box-sizing:border-box;align-items:flex-end;gap:2px;width:max-content;min-width:100%;height:130px;padding-top:18px;display:flex;position:relative}.lc-grid{border-top:1px dashed var(--dsw-alias-border-l1);pointer-events:none;position:absolute;left:0;right:0}.lc-grid-top{top:18px}.lc-grid-mid{top:74px}.lc-grid-zero{border-top-style:solid;border-top-color:#80808080}.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;left:0;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-up,.lc-bar-down{width:100%;display:flex;position:absolute;left:0}.lc-bar-up{flex-direction:column-reverse}.lc-bar-down{flex-direction:column}.lc-bar-up>div,.lc-bar-down>div{width:100%}.lc-bar-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;align-items:center;gap:6px 8px;margin-bottom:10px;display:flex}.lc-detail-head b{color:var(--dsw-alias-label-primary);font-size:13px}.lc-detail-time{font-variant-numeric:tabular-nums;white-space:nowrap;margin-left:auto;font-size:12px}.lc-detail-marker{color:var(--dsw-alias-state-warn-primary);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);border-radius:6px;padding:1px 7px;font-size:11px}.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-metric{font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--dsw-alias-label-primary);background:color-mix(in srgb, var(--dsw-alias-label-secondary) 14%, transparent);border-radius:6px;padding:2px 8px;font-size:12px;font-weight:600}.lc-detail-metric-up{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent)}.lc-detail-metric-down{color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent)}.lc-detail-rows{grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:4px 24px;margin-top:10px;display:grid}.lc-brief{flex-direction:column;gap:2px;min-height:76px;margin:-4px 0 10px;display:flex}.lc-brief-row{min-width:0;color:var(--dsw-alias-label-primary);text-align:left;background:0 0;border:0;border-radius:6px;align-items:center;gap:8px;margin-left:-6px;margin-right:-6px;padding:3px 6px;font-family:inherit;font-size:12px;line-height:1.5;transition:background-color .12s;display:flex}.lc-brief-row-link:hover{background:var(--dsw-alias-interactive-bg-hover)}button.lc-brief-row-link{cursor:pointer}.lc-brief-tag{box-sizing:border-box;text-align:center;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);min-width:56px;color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:0 6px;font-size:11px;position:relative}.lc-brief-tip{z-index:6;white-space:normal;text-align:left;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;max-width:240px;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;bottom:calc(100% + 6px);left:0;box-shadow:0 2px 8px #0000002e}.lc-brief-tag:hover .lc-brief-tip{opacity:1}.lc-brief-text{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.lc-brief-empty{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-tertiary);flex:1;overflow:hidden}.lc-brief-fact{box-sizing:border-box;text-overflow:ellipsis;white-space:nowrap;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);max-width:160px;color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:0 6px;font-size:11px;overflow:hidden}.lc-brief-chip{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);min-width:0;max-width:260px;color:var(--dsw-alias-label-primary);border-radius:5px;align-items:center;gap:5px;padding:0 7px;font-size:12px;transition:background-color .12s;display:inline-flex;overflow:hidden}.lc-brief-chip-grow{flex:1;max-width:none}.lc-brief-chip-tag{text-overflow:ellipsis;white-space:nowrap;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);max-width:120px;color:var(--dsw-alias-label-secondary);border-radius:4px;flex:none;padding:0 6px;font-size:11px;overflow:hidden}.lc-brief-chip-text{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.lc-brief-chip-link{cursor:pointer}.lc-brief-chip-link:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-brief-more{color:var(--dsw-alias-label-secondary);flex:none;font-size:11px}.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;position:relative;overflow:hidden}.lc-bar-fill{border-radius:3px;height:100%;display:block}.lc-bar-zero{border-left:1px solid #80808073;position:absolute;top:0;bottom:0;left:50%}.lc-bar-fill-up,.lc-bar-fill-down{height:100%;display:block;position:absolute;top:0}.lc-bar-fill-up{border-radius:0 3px 3px 0;left:50%}.lc-bar-fill-down{border-radius:3px 0 0 3px;right:50%}.lc-detail-num{text-align:right;font-variant-numeric:tabular-nums;width:52px}.lc-detail-num-up{color:var(--dsw-alias-state-success-primary)}.lc-detail-num-down{color:var(--dsw-alias-state-error-primary)}.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-fa-list{flex-direction:column;gap:2px;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-fa-ctl{flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:8px;display:flex}.lc-fa-ctl .lc-gran{margin-left:0}.lc-fa-n{opacity:.75;margin-left:4px;font-weight:700}.lc-fa-search{min-width:80px;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;outline:none;flex:1;padding:3px 8px;font-size:12px}.lc-fa-search:focus{border-color:var(--dsw-alias-label-dimmed)}.lc-fa-meta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:6px;font-size:12px;display:flex}.lc-fa-meta .lc-fa-sort{flex:none}.lc-fa-meta-delta{position:relative}.lc-fa-meta-tip{z-index:6;white-space:normal;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;max-width:260px;color:var(--dsw-alias-label-primary);pointer-events:none;opacity:0;border-radius:6px;padding:6px 10px;font-size:12px;line-height:1.5;transition:opacity .12s;position:absolute;top:calc(100% + 6px);left:0;box-shadow:0 2px 8px #0000002e}.lc-fa-meta-delta:hover .lc-fa-meta-tip{opacity:1}.lc-fa-item{border:1px solid #0000;border-radius:8px;transition:background-color .12s,border-color .12s}.lc-fa-item-on{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-border-l1)}.lc-fa-row{width:100%;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;text-align:left;background:0 0;border:0;border-radius:7px;align-items:center;gap:7px;padding:6px 8px;font-size:12px;transition:background-color .12s;display:flex}.lc-fa-row:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-fa-form{text-align:center;opacity:.85;flex:none;width:16px;font-size:11px}.lc-fa-path{word-break:break-all;flex:1;min-width:0}.lc-fa-path em{color:var(--dsw-alias-label-secondary);font-style:normal}.lc-fa-badge{font-variant-numeric:tabular-nums;white-space:nowrap;border-radius:999px;flex:none;align-items:center;gap:4px;padding:1px 7px;font-size:11px;font-weight:600;display:inline-flex}.lc-fa-badge i{background:currentColor;border-radius:50%;width:5px;height:5px;display:inline-block}.lc-fa-b-read{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent)}.lc-fa-b-write{color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent)}.lc-fa-b-search{color:var(--dsw-alias-state-warn-primary);background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent)}.lc-fa-chip-read,.lc-fa-chip-read:hover{color:var(--dsw-alias-brand-primary)}.lc-fa-chip-write,.lc-fa-chip-write:hover{color:var(--dsw-alias-state-success-primary)}.lc-fa-chip-search,.lc-fa-chip-search:hover{color:var(--dsw-alias-state-warn-primary)}.lc-fa-chip-read.lc-gran-on,.lc-fa-chip-read.lc-gran-on:hover{background:color-mix(in srgb, var(--dsw-alias-brand-primary) 14%, transparent);color:var(--dsw-alias-brand-primary)}.lc-fa-chip-write.lc-gran-on,.lc-fa-chip-write.lc-gran-on:hover{background:color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);color:var(--dsw-alias-state-success-primary)}.lc-fa-chip-search.lc-gran-on,.lc-fa-chip-search.lc-gran-on:hover{background:color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);color:var(--dsw-alias-state-warn-primary)}.lc-fa-delta{font-variant-numeric:tabular-nums;white-space:nowrap;flex:none;gap:5px;font-size:11px;font-weight:600;display:inline-flex}.lc-fa-up{color:var(--dsw-alias-state-success-primary)}.lc-fa-down{color:var(--dsw-alias-state-error-primary)}.lc-fa-time{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none;font-size:11px}.lc-fa-ops{border-left:2px solid var(--dsw-alias-border-l1);flex-direction:column;gap:1px;margin:0 8px 8px 15px;padding:2px 0 2px 10px;display:flex}.lc-fa-op{min-width:0;color:var(--dsw-alias-label-primary);font:inherit;text-align:left;background:0 0;border:0;border-radius:5px;align-items:center;gap:8px;padding:3px 6px;font-size:11px;transition:background-color .12s;display:flex}.lc-fa-op-link{cursor:pointer}.lc-fa-op-link:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-fa-op-time{width:58px;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex:none}.lc-fa-op-tool{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:0 6px;font-size:11px}.lc-fa-op-detail{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-secondary);flex:1;overflow:hidden}.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-retry{color:var(--dsw-alias-brand-primary);font:inherit;cursor:pointer;text-underline-offset:2px;background:0 0;border:0;padding:0;font-size:12px;text-decoration:underline}.lc-br-retry:hover{filter:brightness(1.15)}.lc-br-cats{flex-direction:column;gap:4px;margin-top:10px;display:flex}.lc-br-cat{border:1px solid var(--dsw-alias-border-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{min-width:0;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);text-overflow:ellipsis;white-space:nowrap;border-radius:4px;max-width:220px;padding:0 6px;font-size:11px;overflow:hidden}.lc-br-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:minmax(0,140px) minmax(0,90px) 56px minmax(0,1fr);align-items:baseline;gap:2px 10px;padding:6px 10px;font-size:12px;display:grid}.lc-ts-param-row:first-of-type{border-top:0}.lc-ts-param-name{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-weight:600;overflow:hidden}.lc-ts-param-type{color:var(--dsw-alias-label-secondary);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);overflow-wrap:anywhere;grid-column:1/-1;line-height:1.5}.lc-ts-params-empty{color:var(--dsw-alias-label-secondary);padding:8px 10px;font-size:12px}.lc-ts-arg-row{border-top:1px solid var(--dsw-alias-border-l1);grid-template-columns:minmax(0,96px) 1fr;align-items:start;column-gap:12px;padding:6px 10px;font-size:12px;display:grid}.lc-ts-arg-row:first-of-type{border-top:0}.lc-ts-arg-row .lc-ts-param-name{text-overflow:unset;white-space:normal;overflow-wrap:anywhere;overflow:visible}.lc-ts-arg-val{color:var(--dsw-alias-label-primary);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}.lc-agents-chips{flex-wrap:wrap;gap:6px;margin-bottom:10px;display:flex}.lc-agents-chip{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;border-radius:999px;padding:2px 8px;font-size:11px}.lc-agents-chip-on{color:var(--dsw-alias-state-success-primary);border-color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent)}.lc-agents-stage{margin:0 -4px;padding:0 4px;overflow-x:auto}.lc-agents-svg{max-width:none;margin:0 auto;display:block}.lc-agents-link{stroke-width:1.5px;stroke-opacity:.45}.lc-agents-link-live{stroke-opacity:.85}.lc-agents-flow{stroke-width:2px;stroke-linecap:round;stroke-dasharray:2 7;animation:.9s linear infinite lc-agent-flow}@keyframes lc-agent-flow{to{stroke-dashoffset:-9px}}.lc-agent-node{outline:none}.lc-agent-clickable{cursor:pointer}.lc-agent-halo{fill:#0000;transition:fill .12s}.lc-agent-hover .lc-agent-halo,.lc-agent-node:focus-visible .lc-agent-halo{fill:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}.lc-agent-self .lc-agent-halo{fill:var(--dsw-alias-bg-layer-2)}.lc-agent-running .lc-agent-halo{fill:color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, transparent);animation:1.8s ease-in-out infinite lc-agent-glow}.lc-agent-done .lc-agent-halo{fill:color-mix(in srgb, var(--dsw-alias-state-success-primary) 7%, transparent)}@keyframes lc-agent-glow{0%,to{opacity:1}50%{opacity:.4}}.lc-agent-track{fill:var(--dsw-alias-bg-layer-1);stroke:var(--dsw-alias-border-l1);stroke-width:1.5px}.lc-agent-seg{fill:none;stroke-width:9px;transition:stroke-dasharray .3s,stroke-dashoffset .3s}.lc-agent-free{stroke:color-mix(in srgb, var(--dsw-alias-label-secondary) 18%, transparent)}.lc-agent-pct{fill:var(--dsw-alias-label-primary);font-variant-numeric:tabular-nums;pointer-events:none;font-size:11px;font-weight:600}.lc-agent-caption{text-align:center;pointer-events:none;width:100%;line-height:1.25}.lc-agent-label{color:var(--dsw-alias-label-primary);overflow-wrap:break-word;-webkit-line-clamp:3;-webkit-box-orient:vertical;font-size:11px;display:-webkit-box;overflow:hidden}.lc-agent-tokens{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;font-size:10px}.lc-agents-solo{padding:4px 0 10px}.lc-agents-inspector{border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-alias-bg-layer-2);border-radius:8px;flex-wrap:wrap;align-items:baseline;gap:4px 8px;min-width:0;margin-top:10px;padding:7px 10px;font-size:12px;display:flex}.lc-agents-inspector-name{color:var(--dsw-alias-label-primary);overflow-wrap:anywhere}.lc-agents-badge{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l1);color:var(--dsw-alias-label-secondary);border-radius:999px;flex:none;padding:1px 6px;font-size:10px}.lc-agent-self-badge{vertical-align:1px;margin-left:5px}.lc-agents-badge-on{color:var(--dsw-alias-state-success-primary);border-color:color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent)}.lc-agents-inspector-stats{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.lc-agents-inspector-open{color:var(--dsw-alias-brand-primary,#3b82f6);flex:none;margin-left:auto}.lc-agents-legend{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:4px 14px;margin-top:8px;font-size:11px;display:flex}.lc-agents-legend-item{align-items:center;gap:5px;display:inline-flex}.lc-agents-legend-item i{border-radius:2px;width:8px;height:8px}.lc-agents-legend-free{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 18%, transparent)}.lc-agents-legend-edge{border-top:2px dashed var(--dsw-alias-label-secondary);border-radius:0!important;width:14px!important;height:0!important}";
3243
4530
  const tagId = "dsh-context/styles.css";
3244
4531
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
3245
4532
  const tag = document.createElement("style");
@@ -3284,19 +4571,6 @@ window.__ModuleLoader__.load({
3284
4571
  const kit = makeViewKit(t);
3285
4572
  const settings = createContextSettings();
3286
4573
  const ContextView = makeContextView(ctx, kit, settings);
3287
- ctx.effect(() => {
3288
- const noop = () => {};
3289
- const sessions = ctx.get("sessions");
3290
- if (sessions === void 0 || typeof sessions.provide !== "function") return noop;
3291
- try {
3292
- return sessions.provide({
3293
- props: ["loadOlderHistory"],
3294
- resolve: (binding) => ({ props: { loadOlderHistory: () => binding.session.loadOlder() } })
3295
- });
3296
- } catch {
3297
- return noop;
3298
- }
3299
- }, "dsh-context: loadOlderHistory prop");
3300
4574
  ctx.slots.inject("conversation.view", () => {
3301
4575
  return ctx.slots.register({
3302
4576
  name: "conversation.view",
@@ -3314,7 +4588,7 @@ window.__ModuleLoader__.load({
3314
4588
  id: "context-modal",
3315
4589
  order: 10,
3316
4590
  locale: NS,
3317
- inject: (sessionId) => ({ hooks: { contextModal: modalStoreOf(sessionId) } })
4591
+ inject: (sessionId = "") => ({ hooks: { contextModal: modalStoreOf(sessionId) } })
3318
4592
  }, (props) => h(ContextModal, props));
3319
4593
  });
3320
4594
  ctx.inject(["settingsScope"], (raw) => {