dsh-context 0.43.0 → 0.44.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.
Files changed (4) hide show
  1. package/lib/client.js +1044 -562
  2. package/lib/index.d.ts +171 -24
  3. package/lib/index.js +830 -251
  4. package/package.json +6 -4
package/lib/client.js CHANGED
@@ -20,7 +20,6 @@ window.__ModuleLoader__.load({
20
20
  "overview.ofUsed": "占已用上下文",
21
21
  "overview.compactReserve": "自动压缩预留:占用达 {pct}% 窗口时触发压缩,此区域一般不实际占用",
22
22
  "stats.title": "上下文统计",
23
- "stats.hint": "轮次、步骤与上下文事件",
24
23
  "stats.turns": "轮次",
25
24
  "stats.steps": "步数",
26
25
  "stats.injects": "注入",
@@ -35,7 +34,6 @@ window.__ModuleLoader__.load({
35
34
  "stats.costMiss": "未命中",
36
35
  "stats.costOut": "输出",
37
36
  "timing.title": "耗时统计",
38
- "timing.hint": "整个会话的活跃时长分布",
39
37
  "timing.total": "总活跃时长",
40
38
  "timing.ttft": "模型等待",
41
39
  "timing.gen": "模型生成",
@@ -45,7 +43,6 @@ window.__ModuleLoader__.load({
45
43
  "timing.toolTimes": "{n}次",
46
44
  "timing.empty": "暂无耗时数据 · 对话开始后这里会显示时长分布",
47
45
  "tokens.title": "Token 统计",
48
- "tokens.hint": "供应商上报的累计计费用量",
49
46
  "tokens.cacheHit": "缓存命中",
50
47
  "tokens.cacheRead": "缓存输入",
51
48
  "tokens.cacheWrite": "缓存写入",
@@ -188,6 +185,8 @@ window.__ModuleLoader__.load({
188
185
  "agents.billed": "累计计费 {n}",
189
186
  "agents.open": "点击打开会话 →",
190
187
  "loading": "正在读取会话日志…",
188
+ "detail.loading": "正在加载历史数据…",
189
+ "detail.loadFailed": "历史数据加载失败,点击重试",
191
190
  "error": "上下文数据读取失败:",
192
191
  "error.retry": "重试",
193
192
  "gate.title": "请更新 DeepSeek Harness 版本",
@@ -303,7 +302,6 @@ window.__ModuleLoader__.load({
303
302
  "overview.ofUsed": "of used context",
304
303
  "overview.compactReserve": "Auto-compaction reserve: auto-compaction triggers at {pct}% of the window — this area is normally kept as headroom, not actually used",
305
304
  "stats.title": "Context Stats",
306
- "stats.hint": "Turns, steps, and context events",
307
305
  "stats.turns": "Turns",
308
306
  "stats.steps": "Steps",
309
307
  "stats.injects": "Injections",
@@ -318,7 +316,6 @@ window.__ModuleLoader__.load({
318
316
  "stats.costMiss": "miss",
319
317
  "stats.costOut": "output",
320
318
  "timing.title": "Timing Stats",
321
- "timing.hint": "Whole-session active-time split",
322
319
  "timing.total": "Active Time",
323
320
  "timing.ttft": "TTFT",
324
321
  "timing.gen": "LLM Gen",
@@ -328,7 +325,6 @@ window.__ModuleLoader__.load({
328
325
  "timing.toolTimes": "{n} runs",
329
326
  "timing.empty": "No timing data yet — durations appear as the conversation runs",
330
327
  "tokens.title": "Token Stats",
331
- "tokens.hint": "Cumulative provider-reported usage",
332
328
  "tokens.cacheHit": "Cache Hit",
333
329
  "tokens.cacheRead": "Cached Input",
334
330
  "tokens.cacheWrite": "Cache Write",
@@ -471,6 +467,8 @@ window.__ModuleLoader__.load({
471
467
  "agents.billed": "{n} billed",
472
468
  "agents.open": "click to open →",
473
469
  "loading": "Reading the session log…",
470
+ "detail.loading": "Loading history…",
471
+ "detail.loadFailed": "History failed to load — click to retry",
474
472
  "error": "Failed to read context data: ",
475
473
  "error.retry": "Retry",
476
474
  "gate.title": "Update DeepSeek Harness",
@@ -573,9 +571,9 @@ window.__ModuleLoader__.load({
573
571
  };
574
572
  //#endregion
575
573
  //#region src/client/modalStore.ts
576
- const stores = /* @__PURE__ */ new Map();
574
+ const stores$1 = /* @__PURE__ */ new Map();
577
575
  function modalStoreOf(sessionId) {
578
- const existing = stores.get(sessionId);
576
+ const existing = stores$1.get(sessionId);
579
577
  if (existing !== void 0) return existing;
580
578
  let open = false;
581
579
  const listeners = /* @__PURE__ */ new Set();
@@ -593,7 +591,7 @@ window.__ModuleLoader__.load({
593
591
  for (const listener of listeners) listener();
594
592
  }
595
593
  };
596
- stores.set(sessionId, store);
594
+ stores$1.set(sessionId, store);
597
595
  return store;
598
596
  }
599
597
  const pendingConsume = /* @__PURE__ */ new Map();
@@ -716,7 +714,6 @@ window.__ModuleLoader__.load({
716
714
  "assistant",
717
715
  "tool"
718
716
  ];
719
- Object.fromEntries(CATS.map((c) => [c.key, c.color]));
720
717
  function partsOf(breakdown) {
721
718
  return CATS.map((c) => {
722
719
  return {
@@ -796,9 +793,11 @@ window.__ModuleLoader__.load({
796
793
  function headlineOf(data, pressure = null, breakdown = null) {
797
794
  const current = data.current;
798
795
  const projected = pressure !== null && typeof pressure.projectedTokens === "number" ? pressure.projectedTokens : void 0;
796
+ const last = data.last;
799
797
  const requests = data.requests;
800
798
  const lastReq = requests.length > 0 ? requests[requests.length - 1] : null;
801
- const derived = lastReq !== null && typeof lastReq.prompt === "number" ? lastReq.prompt + (current.total - lastReq.total) : void 0;
799
+ const anchor = last !== void 0 ? last : lastReq;
800
+ const derived = anchor !== null && typeof anchor.prompt === "number" ? anchor.prompt + (current.total - anchor.total) : void 0;
802
801
  const occupancyTokens = projected ?? derived ?? null;
803
802
  const window = pressure !== null && typeof pressure.contextWindow === "number" ? pressure.contextWindow : data.contextWindow;
804
803
  const tokens = occupancyTokens ?? current.total;
@@ -860,12 +859,25 @@ window.__ModuleLoader__.load({
860
859
  } catch {}
861
860
  }
862
861
  /**
862
+ * Read one projection key through the standard seat, narrowed at the
863
+ * boundary: null when the seat is absent (a harness without the projection
864
+ * pipeline) or the delivered value fails the narrow. The seat is a real
865
+ * hook — call this unconditionally at the top of the component, one call
866
+ * per key, in a stable order.
867
+ */
868
+ function projectionOf(props, key, narrow) {
869
+ if (typeof props.useProjection !== "function") return null;
870
+ return narrow(props.useProjection(key));
871
+ }
872
+ /**
863
873
  * Narrow an unknown projection value to a string-keyed record, or null when
864
874
  * it is not one. The boundary type is Record<string, unknown> on purpose:
865
875
  * every field read below must re-prove itself (the no-white-screen
866
876
  * guarantee), so no field may borrow the wire type before its check.
877
+ * Shared by every sanitizer here and by the agent-tree derivation
878
+ * (agentTree.ts) — the ONE record guard for the whole client half.
867
879
  */
868
- function asRecord$1(value) {
880
+ function asRecord(value) {
869
881
  if (value === null || value === void 0 || typeof value !== "object") return null;
870
882
  return value;
871
883
  }
@@ -876,17 +888,27 @@ window.__ModuleLoader__.load({
876
888
  function numOf(value) {
877
889
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
878
890
  }
891
+ /** Shared per-item collection guard: drop non-object entries, keep the rest. */
879
892
  function objectsOf(value) {
880
893
  if (!Array.isArray(value)) return [];
881
894
  return value.filter((v) => v !== null && typeof v === "object");
882
895
  }
883
896
  /**
897
+ * The fast path's collection check: a real array whose entries are ALL
898
+ * records. A null/primitive entry would pass a bare Array.isArray yet throw
899
+ * on the first property read downstream (`req.seq` on null), so it sends the
900
+ * value down the sanitizing slow path, where `objectsOf` drops it.
901
+ */
902
+ function recordsOnly(value) {
903
+ return Array.isArray(value) && value.every((e) => e !== null && typeof e === "object");
904
+ }
905
+ /**
884
906
  * Narrow a delivered `unsupported` gate record (the host's baseline gate —
885
907
  * see host/fallback.ts): both version strings re-proved, anything else
886
908
  * degrades to null (no gate shown) instead of rendering garbage.
887
909
  */
888
910
  function unsupportedOf(value) {
889
- const data = asRecord$1(value);
911
+ const data = asRecord(value);
890
912
  if (data === null) return null;
891
913
  if (typeof data.current !== "string" || typeof data.minimum !== "string") return null;
892
914
  return {
@@ -908,7 +930,7 @@ window.__ModuleLoader__.load({
908
930
  * during render and unmounting the conversation view.
909
931
  */
910
932
  function timelineOf(value) {
911
- const data = asRecord$1(value);
933
+ const data = asRecord(value);
912
934
  if (data === null) return null;
913
935
  const current = data.current;
914
936
  if (current !== null && typeof current === "object" && [
@@ -919,11 +941,13 @@ window.__ModuleLoader__.load({
919
941
  "assistant",
920
942
  "tool",
921
943
  "total"
922
- ].every((k) => typeof current[k] === "number") && Array.isArray(data.requests) && Array.isArray(data.events) && Array.isArray(data.nodes) && Array.isArray(data.archive) && timingFastOk(data.timing)) return data;
944
+ ].every((k) => typeof current[k] === "number") && recordsOnly(data.requests) && recordsOnly(data.events) && recordsOnly(data.nodes) && recordsOnly(data.archive) && timingFastOk(data.timing)) return data;
923
945
  const safeCurrent = current !== null && typeof current === "object" ? current : {};
924
946
  const cost = typeof data.cost === "object" && data.cost !== null && !Array.isArray(data.cost) ? data.cost : void 0;
925
947
  const timing = timingOf(data.timing);
926
948
  const unsupported = unsupportedOf(data.unsupported);
949
+ const counts = countsOf(data.counts);
950
+ const last = lastOf(data.last);
927
951
  return {
928
952
  ok: true,
929
953
  ...unsupported !== null ? { unsupported } : {},
@@ -946,10 +970,44 @@ window.__ModuleLoader__.load({
946
970
  ...typeof data.images === "number" ? { images: data.images } : {},
947
971
  ...typeof data.toolCalls === "number" ? { toolCalls: data.toolCalls } : {},
948
972
  archive: objectsOf(data.archive),
973
+ ...counts !== void 0 ? { counts } : {},
974
+ ...last !== void 0 ? { last } : {},
975
+ ...typeof data.detailRev === "number" && Number.isFinite(data.detailRev) ? { detailRev: data.detailRev } : {},
949
976
  ...cost !== void 0 ? { cost } : {},
950
977
  ...timing !== null ? { timing } : {},
951
978
  ...typeof data.surfaceFloor === "number" ? { surfaceFloor: data.surfaceFloor } : {},
952
- ...typeof data.archiveFloor === "number" ? { archiveFloor: data.archiveFloor } : {}
979
+ ...typeof data.archiveFloor === "number" ? { archiveFloor: data.archiveFloor } : {},
980
+ ...data.fileOps !== void 0 ? { fileOps: objectsOf(data.fileOps) } : {},
981
+ ...typeof data.fileOpsFloor === "number" ? { fileOpsFloor: data.fileOpsFloor } : {}
982
+ };
983
+ }
984
+ /**
985
+ * The split head's count figures, re-proved field by field: a present-but-
986
+ * partial record zeroes its unreadable fields (the stats board's no-NaN
987
+ * guarantee), an absent or non-record value stays absent (legacy generation
988
+ * — callers derive the counts from the collections instead).
989
+ */
990
+ function countsOf(value) {
991
+ const data = asRecord(value);
992
+ if (data === null) return void 0;
993
+ return {
994
+ turns: numOf(data.turns),
995
+ steps: numOf(data.steps),
996
+ injects: numOf(data.injects),
997
+ compactions: numOf(data.compactions),
998
+ prunes: numOf(data.prunes)
999
+ };
1000
+ }
1001
+ /** The split head's newest-request summary; absent or shapeless stays absent. */
1002
+ function lastOf(value) {
1003
+ const data = asRecord(value);
1004
+ if (data === null) return void 0;
1005
+ if (typeof data.seq !== "number" || !Number.isFinite(data.seq)) return void 0;
1006
+ if (typeof data.total !== "number" || !Number.isFinite(data.total)) return void 0;
1007
+ return {
1008
+ seq: data.seq,
1009
+ total: data.total,
1010
+ ...typeof data.prompt === "number" && Number.isFinite(data.prompt) ? { prompt: data.prompt } : {}
953
1011
  };
954
1012
  }
955
1013
  /**
@@ -957,10 +1015,19 @@ window.__ModuleLoader__.load({
957
1015
  * `contextPressure` projection (provider-anchored occupancy of the next
958
1016
  * request). Absent key or value = the meter's projection is not composed
959
1017
  * (e.g. a harness without the session-projection registry) — callers fall
960
- * back to their derived anchor, so the UI degrades gracefully.
1018
+ * back to their derived anchor, so the UI degrades gracefully. The three
1019
+ * fields are independent last-wins records on the wire (dsh's strict wire
1020
+ * schema), so each is re-proved on its own: a wrong-typed field drops out,
1021
+ * the readable ones survive.
961
1022
  */
962
1023
  function contextPressureOf(value) {
963
- return asRecord$1(value);
1024
+ const data = asRecord(value);
1025
+ if (data === null) return null;
1026
+ const out = {};
1027
+ if (typeof data.pressureTokens === "number" && Number.isFinite(data.pressureTokens)) out.pressureTokens = data.pressureTokens;
1028
+ if (typeof data.projectedTokens === "number" && Number.isFinite(data.projectedTokens)) out.projectedTokens = data.projectedTokens;
1029
+ if (typeof data.contextWindow === "number" && Number.isFinite(data.contextWindow)) out.contextWindow = data.contextWindow;
1030
+ return out;
964
1031
  }
965
1032
  /**
966
1033
  * Narrow a delivered projection value to the official token-meter
@@ -970,7 +1037,7 @@ window.__ModuleLoader__.load({
970
1037
  * own sums instead of mixing sources.
971
1038
  */
972
1039
  function contextBreakdownOf(value) {
973
- const data = asRecord$1(value);
1040
+ const data = asRecord(value);
974
1041
  if (data === null) return null;
975
1042
  const { systemTokens, toolsTokens, messageTokens } = data;
976
1043
  if (typeof systemTokens !== "number" || !Number.isFinite(systemTokens)) return null;
@@ -986,10 +1053,25 @@ window.__ModuleLoader__.load({
986
1053
  * Narrow a delivered projection value to the official token-meter
987
1054
  * `tokenUsage` projection (durable cumulative provider usage). Absent key or
988
1055
  * value = the meter's projection is not composed (or no request has reported
989
- * usage yet) — callers drop the cache-hit cell to a dash.
1056
+ * usage yet) — callers drop the cache-hit cell to a dash. The wire schema is
1057
+ * strict with all four buckets REQUIRED (dsh token-meter's projectionSchema),
1058
+ * so a partial/corrupt value degrades the whole value to null instead of
1059
+ * undercounting the billed total.
990
1060
  */
991
1061
  function tokenUsageOf(value) {
992
- return asRecord$1(value);
1062
+ const data = asRecord(value);
1063
+ if (data === null) return null;
1064
+ const { uncachedInputTokens, outputTokens, cacheReadTokens, cacheWriteTokens } = data;
1065
+ if (typeof uncachedInputTokens !== "number" || !Number.isFinite(uncachedInputTokens)) return null;
1066
+ if (typeof outputTokens !== "number" || !Number.isFinite(outputTokens)) return null;
1067
+ if (typeof cacheReadTokens !== "number" || !Number.isFinite(cacheReadTokens)) return null;
1068
+ if (typeof cacheWriteTokens !== "number" || !Number.isFinite(cacheWriteTokens)) return null;
1069
+ return {
1070
+ uncachedInputTokens,
1071
+ outputTokens,
1072
+ cacheReadTokens,
1073
+ cacheWriteTokens
1074
+ };
993
1075
  }
994
1076
  /**
995
1077
  * A non-negative finite number (the timing totals' every field): NaN or a
@@ -1034,7 +1116,7 @@ window.__ModuleLoader__.load({
1034
1116
  * hostile row never blanks the ranking.
1035
1117
  */
1036
1118
  function timingOf(value) {
1037
- const data = asRecord$1(value);
1119
+ const data = asRecord(value);
1038
1120
  if (data === null) return null;
1039
1121
  const tools = {};
1040
1122
  const rawTools = data.tools;
@@ -1067,9 +1149,11 @@ window.__ModuleLoader__.load({
1067
1149
  * browser degrades its system/tools sections to a metadata-only note.
1068
1150
  *
1069
1151
  * Entry-level shape is checked too: a malformed epoch (corrupt payload with
1070
- * a missing tools list or wrong-typed systemTokens) would crash the
1071
- * browser's tools/sections reads, so the WHOLE projection degrades to null
1072
- * and the card falls back to its metadata-only note. The epoch CONTENT is
1152
+ * a missing tools list, a wrong-typed systemTokens, or a tool row whose
1153
+ * name/tokens the browser reads blindly `tool.name.toLowerCase()` and
1154
+ * `b.tokens - a.tokens` throw on junk) would crash the browser's
1155
+ * tools/sections reads, so the WHOLE projection degrades to null and the
1156
+ * card falls back to its metadata-only note. The epoch CONTENT is
1073
1157
  * not part of this value — the browser fetches it per epoch on demand.
1074
1158
  *
1075
1159
  * The pre-#37 wire generation carries the system TEXT instead of its token
@@ -1080,13 +1164,20 @@ window.__ModuleLoader__.load({
1080
1164
  * new-shape values pass through untouched.
1081
1165
  */
1082
1166
  function headersOf(value) {
1083
- const headers = asRecord$1(value);
1167
+ const headers = asRecord(value);
1084
1168
  if (headers === null || !Array.isArray(headers.headers)) return null;
1085
1169
  for (const h of headers.headers) {
1086
1170
  if (h === null || typeof h !== "object") return null;
1087
1171
  const entry = h;
1088
1172
  if (!Array.isArray(entry.tools)) return null;
1089
1173
  if (entry.systemTokens !== void 0 && (typeof entry.systemTokens !== "number" || !Number.isFinite(entry.systemTokens))) return null;
1174
+ for (const t of entry.tools) {
1175
+ if (t === null || typeof t !== "object") return null;
1176
+ const tool = t;
1177
+ if (typeof tool.name !== "string") return null;
1178
+ if (typeof tool.tokens !== "number" || !Number.isFinite(tool.tokens)) return null;
1179
+ if (tool.plugin !== void 0 && typeof tool.plugin !== "string") return null;
1180
+ }
1090
1181
  }
1091
1182
  let legacy = false;
1092
1183
  for (const entry of headers.headers) if (entry.systemTokens === void 0 && typeof entry.system === "string" && entry.system !== "") {
@@ -1102,6 +1193,21 @@ window.__ModuleLoader__.load({
1102
1193
  };
1103
1194
  }) };
1104
1195
  }
1196
+ /** The session-namespace workspace-opener remotes, ridden through the generic '/api' channel. */
1197
+ const OPEN_CHANNEL = "/api";
1198
+ const CAN_OPEN_ENDPOINT = "session/canOpenWorkspacePath";
1199
+ const OPEN_ENDPOINT = "session/openWorkspacePath";
1200
+ /**
1201
+ * The connection's bound generic-RPC caller, or undefined when the service
1202
+ * is absent or hostile — every read is guarded, so this can never throw.
1203
+ */
1204
+ function rpcCallOf(ctx) {
1205
+ try {
1206
+ const rpc = asRecord(ctx.get("connection")?.rpc);
1207
+ const fn = rpc?.call;
1208
+ if (rpc !== null && typeof fn === "function") return fn.bind(rpc);
1209
+ } catch {}
1210
+ }
1105
1211
  /**
1106
1212
  * The SESSION's workspace root — the `cwd` its session-list row carries (the
1107
1213
  * host session canon, not the host process's own launch directory) — or
@@ -1121,29 +1227,37 @@ window.__ModuleLoader__.load({
1121
1227
  return;
1122
1228
  }
1123
1229
  }
1124
- /** Whether this deployment can hand a path to the user's native desktop (the
1125
- * host description's `canOpenPath`); false when unknown. */
1126
- function canOpenPathsOf(ctx) {
1230
+ /**
1231
+ * Whether this deployment can hand a path to the user's native desktop: the
1232
+ * page must reach the Host on the operator's own machine (`isLoopback`, the
1233
+ * harness's own gate) AND the session controller's opener capability remote
1234
+ * must answer true. The capability is an RPC round-trip now (the synchronous
1235
+ * host-description fact is gone), so the answer is asynchronous; every
1236
+ * absence, hostility, or transport failure resolves false — never a rejection.
1237
+ */
1238
+ async function canOpenPathsOf(ctx) {
1239
+ const call = rpcCallOf(ctx);
1240
+ if (call === void 0) return false;
1127
1241
  try {
1128
- const source = ctx.get("connection")?.hostDescription;
1129
- const snapshot = typeof source?.getSnapshot === "function" ? source.getSnapshot() : void 0;
1130
- return (snapshot !== null && typeof snapshot === "object" ? snapshot.canOpenPath : void 0) === true;
1242
+ if (ctx.get("connection")?.isLoopback !== true) return false;
1243
+ const r = asRecord(await call(OPEN_CHANNEL, CAN_OPEN_ENDPOINT, { args: {} }));
1244
+ return r !== null && r.ok === true && r.value === true;
1131
1245
  } catch {
1132
1246
  return false;
1133
1247
  }
1134
1248
  }
1135
1249
  /**
1136
- * The system path opener, or undefined when the deployment lacks the RPC.
1137
- * Fire-and-forget: rejections (unknown path, no desktop) swallow — the
1138
- * affordance is best-effort by nature.
1250
+ * The system path opener over the session controller's open remote, or
1251
+ * undefined when the connection carries no RPC caller. Fire-and-forget:
1252
+ * rejections (unknown path, no desktop, offline) swallow — the affordance
1253
+ * is best-effort by nature.
1139
1254
  */
1140
1255
  function openPathVia(ctx) {
1141
- const host = ctx.get("connection")?.api?.host;
1142
- if (host === void 0 || typeof host.openPath !== "function") return void 0;
1143
- const openPath = host.openPath.bind(host);
1256
+ const call = rpcCallOf(ctx);
1257
+ if (call === void 0) return void 0;
1144
1258
  return (path) => {
1145
1259
  try {
1146
- openPath({ path }).catch(() => {});
1260
+ call(OPEN_CHANNEL, OPEN_ENDPOINT, { args: { request: { path } } }).catch(() => {});
1147
1261
  } catch {}
1148
1262
  };
1149
1263
  }
@@ -1428,6 +1542,244 @@ window.__ModuleLoader__.load({
1428
1542
  };
1429
1543
  }
1430
1544
  //#endregion
1545
+ //#region src/client/react.ts
1546
+ const React = require("react");
1547
+ const h = React.createElement;
1548
+ const ReactDOM = require("react-dom");
1549
+ //#endregion
1550
+ //#region src/client/timelineSource.ts
1551
+ const DETAIL_CHANNEL = "/dsh-context";
1552
+ const DETAIL_ENDPOINT = "detail";
1553
+ /**
1554
+ * Narrow the detail endpoint's payload to a render-safe value (the same
1555
+ * boundary rigor as `timelineOf`): collections re-proved per item, scalars
1556
+ * zeroed, a missing/NaN revision rejects the whole payload (the caller then
1557
+ * shows the retryable failure note instead of half-merged data).
1558
+ */
1559
+ function detailOf(value) {
1560
+ const data = asRecord(value);
1561
+ if (data === null) return null;
1562
+ if (typeof data.rev !== "number" || !Number.isFinite(data.rev) || data.rev < 0) return null;
1563
+ return {
1564
+ rev: data.rev,
1565
+ requests: objectsOf(data.requests),
1566
+ events: objectsOf(data.events),
1567
+ nodes: objectsOf(data.nodes),
1568
+ droppedNodes: numOf(data.droppedNodes),
1569
+ archive: objectsOf(data.archive),
1570
+ ...typeof data.surfaceFloor === "number" ? { surfaceFloor: data.surfaceFloor } : {},
1571
+ ...typeof data.archiveFloor === "number" ? { archiveFloor: data.archiveFloor } : {},
1572
+ ...data.fileOps !== void 0 ? { fileOps: objectsOf(data.fileOps) } : {},
1573
+ ...typeof data.fileOpsFloor === "number" ? { fileOpsFloor: data.fileOpsFloor } : {}
1574
+ };
1575
+ }
1576
+ /**
1577
+ * The detail reader over the harness's generic Connection RPC
1578
+ * (`ctx.connection.rpc.call`) — resolved through the shared `rpcCallOf`
1579
+ * reflect read (the same seam openPathVia/canOpenPathsOf use), so a hostile
1580
+ * or absent connection service degrades to `undefined` instead of throwing.
1581
+ * The returned thunk resolves the session's current detail, `null` when the
1582
+ * session is not live anymore, and rejects on transport failure or a
1583
+ * malformed payload (the store turns the two into the retryable state).
1584
+ */
1585
+ function makeDetailFetcher(ctx, sessionId) {
1586
+ if (sessionId === "") return void 0;
1587
+ const call = rpcCallOf(ctx);
1588
+ if (call === void 0) return void 0;
1589
+ return async () => {
1590
+ const r = asRecord(await call(DETAIL_CHANNEL, DETAIL_ENDPOINT, { sessionId }));
1591
+ if (r === null || r.ok !== true) throw new Error("dsh-context: detail rpc failed");
1592
+ if (r.value === null) return null;
1593
+ const detail = detailOf(r.value);
1594
+ if (detail === null) throw new Error("dsh-context: detail rpc malformed");
1595
+ return detail;
1596
+ };
1597
+ }
1598
+ const EMPTY_SNAP = {
1599
+ detail: null,
1600
+ failed: false,
1601
+ pending: false
1602
+ };
1603
+ /** The fetch debounce base; each consecutive failure doubles the wait, capped at 3 doublings. */
1604
+ const DETAIL_DEBOUNCE_MS = 300;
1605
+ /**
1606
+ * One session's detail ledger. Exported for tests (a zero debounce makes the
1607
+ * machine synchronous-ish); the app reaches it through `detailStoreOf`.
1608
+ */
1609
+ var DetailStore = class {
1610
+ fetcher;
1611
+ baseDelay;
1612
+ detail = null;
1613
+ /** The revision of `detail` (latest-wins cursor); -1 before the first landing. */
1614
+ acceptedRev = -1;
1615
+ /** The newest revision the head has asked for. */
1616
+ wantedRev = -1;
1617
+ /** The head rev the IN-FLIGHT (or scheduled) read targets — the refold exception's acceptance key. */
1618
+ targetRev = -1;
1619
+ /** The head rev seen last — a DECREASE means the host refolded (revisions restart). */
1620
+ lastHeadRev = -1;
1621
+ failed = false;
1622
+ inFlight = false;
1623
+ timer = null;
1624
+ failures = 0;
1625
+ listeners = /* @__PURE__ */ new Set();
1626
+ snap = EMPTY_SNAP;
1627
+ constructor(fetcher, baseDelay = DETAIL_DEBOUNCE_MS) {
1628
+ this.fetcher = fetcher;
1629
+ this.baseDelay = baseDelay;
1630
+ }
1631
+ subscribe = (fn) => {
1632
+ this.listeners.add(fn);
1633
+ return () => this.listeners.delete(fn);
1634
+ };
1635
+ getSnapshot = () => this.snap;
1636
+ /**
1637
+ * The head's current revision arrived: schedule the trailing-edge read
1638
+ * when it outruns the served detail. A rev DECREASE means the host
1639
+ * refolded the session (a discarded checkpoint, a restart): revisions are
1640
+ * no longer comparable, so the ledger resets and refetches.
1641
+ */
1642
+ request(rev) {
1643
+ if (rev < this.lastHeadRev) {
1644
+ this.acceptedRev = -1;
1645
+ this.wantedRev = -1;
1646
+ this.detail = null;
1647
+ this.failed = false;
1648
+ }
1649
+ this.lastHeadRev = rev;
1650
+ if (rev <= this.acceptedRev || rev <= this.wantedRev) return;
1651
+ this.wantedRev = rev;
1652
+ this.schedule();
1653
+ }
1654
+ /** Re-arm after a failure (the cards' retry note): immediate, backoff reset. */
1655
+ retry = () => {
1656
+ this.failures = 0;
1657
+ if (this.detail !== null) return;
1658
+ if (this.timer !== null) {
1659
+ clearTimeout(this.timer);
1660
+ this.timer = null;
1661
+ }
1662
+ if (!this.inFlight) this.fire();
1663
+ };
1664
+ schedule() {
1665
+ if (this.timer !== null) return;
1666
+ this.timer = setTimeout(() => {
1667
+ this.timer = null;
1668
+ this.fire();
1669
+ }, this.baseDelay * 2 ** Math.min(this.failures, 3));
1670
+ this.emit();
1671
+ }
1672
+ async fire() {
1673
+ if (this.inFlight) return;
1674
+ if (this.fetcher === void 0) {
1675
+ this.failed = true;
1676
+ this.emit();
1677
+ return;
1678
+ }
1679
+ this.inFlight = true;
1680
+ this.targetRev = this.wantedRev;
1681
+ this.emit();
1682
+ try {
1683
+ const d = await this.fetcher();
1684
+ if (d !== null) {
1685
+ if (d.rev >= this.acceptedRev || d.rev === this.targetRev) {
1686
+ this.detail = d;
1687
+ this.acceptedRev = d.rev;
1688
+ }
1689
+ this.failures = 0;
1690
+ this.failed = false;
1691
+ } else {
1692
+ this.wantedRev = this.acceptedRev;
1693
+ this.failed = this.detail === null;
1694
+ this.failures++;
1695
+ }
1696
+ } catch {
1697
+ this.failed = this.detail === null;
1698
+ this.failures++;
1699
+ }
1700
+ this.inFlight = false;
1701
+ if (this.wantedRev > this.acceptedRev) this.schedule();
1702
+ this.emit();
1703
+ }
1704
+ emit() {
1705
+ const pending = this.timer !== null || this.inFlight;
1706
+ const next = {
1707
+ detail: this.detail,
1708
+ failed: this.failed,
1709
+ pending
1710
+ };
1711
+ if (next.detail === this.snap.detail && next.failed === this.snap.failed && next.pending === this.snap.pending) return;
1712
+ this.snap = next;
1713
+ for (const fn of this.listeners) fn();
1714
+ }
1715
+ };
1716
+ /** Page-lifetime per-session stores (the tab and the modal share one). */
1717
+ const stores = /* @__PURE__ */ new Map();
1718
+ function detailStoreOf(ctx, sessionId) {
1719
+ let store = stores.get(sessionId);
1720
+ if (store === void 0) {
1721
+ store = new DetailStore(makeDetailFetcher(ctx, sessionId));
1722
+ stores.set(sessionId, store);
1723
+ }
1724
+ return store;
1725
+ }
1726
+ const noopSubscribe = () => () => {};
1727
+ const noopRetry = () => {};
1728
+ /**
1729
+ * The view's one read of the timeline (see the module header for the
1730
+ * generation rules). Hook-order safe: every hook runs unconditionally, the
1731
+ * branches below only shape the returned record.
1732
+ */
1733
+ function useTimelineSource(ctx, props) {
1734
+ const head = projectionOf(props, "contextTimeline", timelineOf);
1735
+ const sessionId = typeof props.sessionId === "string" ? props.sessionId : "";
1736
+ const headRev = head !== null && typeof head.detailRev === "number" ? head.detailRev : null;
1737
+ const slim = headRev !== null;
1738
+ const store = React.useMemo(() => slim ? detailStoreOf(ctx, sessionId) : null, [
1739
+ ctx,
1740
+ sessionId,
1741
+ slim
1742
+ ]);
1743
+ const snap = React.useSyncExternalStore(store !== null ? store.subscribe : noopSubscribe, store !== null ? store.getSnapshot : () => EMPTY_SNAP);
1744
+ React.useEffect(() => {
1745
+ if (store !== null && headRev !== null) store.request(headRev);
1746
+ }, [store, headRev]);
1747
+ return React.useMemo(() => {
1748
+ if (head === null) return {
1749
+ data: null,
1750
+ detailState: "loading",
1751
+ retryDetail: noopRetry
1752
+ };
1753
+ if (!slim || store === null) return {
1754
+ data: head,
1755
+ detailState: "legacy",
1756
+ retryDetail: noopRetry
1757
+ };
1758
+ const detail = snap.detail;
1759
+ return {
1760
+ data: detail === null ? head : {
1761
+ ...head,
1762
+ requests: detail.requests,
1763
+ events: detail.events,
1764
+ nodes: detail.nodes,
1765
+ droppedNodes: detail.droppedNodes,
1766
+ archive: detail.archive,
1767
+ ...detail.surfaceFloor !== void 0 ? { surfaceFloor: detail.surfaceFloor } : {},
1768
+ ...detail.archiveFloor !== void 0 ? { archiveFloor: detail.archiveFloor } : {},
1769
+ ...detail.fileOps !== void 0 ? { fileOps: detail.fileOps } : {},
1770
+ ...detail.fileOpsFloor !== void 0 ? { fileOpsFloor: detail.fileOpsFloor } : {}
1771
+ },
1772
+ detailState: detail !== null ? "ready" : snap.failed ? "failed" : "loading",
1773
+ retryDetail: store.retry
1774
+ };
1775
+ }, [
1776
+ head,
1777
+ slim,
1778
+ store,
1779
+ snap
1780
+ ]);
1781
+ }
1782
+ //#endregion
1431
1783
  //#region src/client/assemble.ts
1432
1784
  /** The header epoch in force at `seq` (last logged before it), or the newest. */
1433
1785
  function headerAt(headers, seq) {
@@ -1461,12 +1813,8 @@ window.__ModuleLoader__.load({
1461
1813
  };
1462
1814
  }
1463
1815
  //#endregion
1464
- //#region src/client/react.ts
1465
- const React = require("react");
1466
- const h = React.createElement;
1467
- const ReactDOM = require("react-dom");
1468
- //#endregion
1469
- //#region src/client/callSummary.ts
1816
+ //#region src/shared/fileOps.ts
1817
+ /** Parse a call's raw JSON arguments; non-string/malformed/non-record inputs yield null. */
1470
1818
  function parseCallArgs(raw) {
1471
1819
  if (typeof raw !== "string" || raw === "") return null;
1472
1820
  try {
@@ -1476,6 +1824,223 @@ window.__ModuleLoader__.load({
1476
1824
  return null;
1477
1825
  }
1478
1826
  }
1827
+ const KIND_BY_TOOL = {
1828
+ read: "read",
1829
+ read_image: "read",
1830
+ write: "write",
1831
+ edit: "write",
1832
+ grep: "search",
1833
+ glob: "search"
1834
+ };
1835
+ /** The file purpose of a tool, or null for non-file tools (bash, web_search…). */
1836
+ function kindOfTool(tool) {
1837
+ if (tool === void 0) return null;
1838
+ return KIND_BY_TOOL[tool] ?? null;
1839
+ }
1840
+ /**
1841
+ * The file purpose of one executed call. Like {@link kindOfTool} except for
1842
+ * the one file tool whose purpose follows its arguments: `str_replace_editor`
1843
+ * reads on `view` and writes on every other command (create / str_replace /
1844
+ * insert — an unknown command writes too; the call failed and the row keeps
1845
+ * its error flag).
1846
+ */
1847
+ function kindOfCall(tool, args) {
1848
+ if (tool === "str_replace_editor") return args !== null && args.command === "view" ? "read" : "write";
1849
+ return kindOfTool(tool);
1850
+ }
1851
+ /**
1852
+ * The operation's target path: the path-ish argument of read/write tools;
1853
+ * for searches the narrowing `path`, else the pattern itself (a pathless
1854
+ * grep/glob's target IS the pattern — the workspace-wide search text).
1855
+ */
1856
+ function pathOfArgs(tool, args) {
1857
+ if (args === null) return null;
1858
+ if (tool === "grep" || tool === "glob") {
1859
+ const p = args.path;
1860
+ if (typeof p === "string" && p !== "") return p;
1861
+ const pattern = args.pattern;
1862
+ return typeof pattern === "string" && pattern !== "" ? pattern : null;
1863
+ }
1864
+ for (const k of [
1865
+ "file_path",
1866
+ "filePath",
1867
+ "path"
1868
+ ]) {
1869
+ const v = args[k];
1870
+ if (typeof v === "string" && v !== "") return v;
1871
+ }
1872
+ return null;
1873
+ }
1874
+ /** Rendered line count: '' is 0, a trailing newline closes its own line. */
1875
+ function linesOf(s) {
1876
+ if (s === "") return 0;
1877
+ let n = 0;
1878
+ for (let i = 0; i < s.length; i++) if (s[i] === "\n") n++;
1879
+ return s.endsWith("\n") ? n : n + 1;
1880
+ }
1881
+ /** The added/removed pair of one content-bearing argument set, or zeros. */
1882
+ function pairOf(added, removed) {
1883
+ return {
1884
+ added: typeof added === "string" ? linesOf(added) : 0,
1885
+ removed: typeof removed === "string" ? linesOf(removed) : 0
1886
+ };
1887
+ }
1888
+ /**
1889
+ * The signed line footprint of one call: an edit removes its old string and
1890
+ * adds its new one; a write adds its content (the pre-existing body, if any,
1891
+ * is unknowable from the arguments — the estimate stays honest about that);
1892
+ * `str_replace_editor` splits the same shapes across its commands. Callers
1893
+ * reach here only with parsed args (a null parse yields no path).
1894
+ */
1895
+ function deltaOf(tool, args) {
1896
+ if (tool === "edit") return pairOf(args.new_string, args.old_string);
1897
+ if (tool === "write") return pairOf(args.content, void 0);
1898
+ if (tool === "str_replace_editor") {
1899
+ if (args.command === "str_replace") return pairOf(args.new_str, args.old_str);
1900
+ if (args.command === "insert") return pairOf(args.new_str, void 0);
1901
+ if (args.command === "create") return pairOf(args.file_text, void 0);
1902
+ }
1903
+ return {
1904
+ added: 0,
1905
+ removed: 0
1906
+ };
1907
+ }
1908
+ /** The exact window a read's result meta reports: `offset` plus the retained
1909
+ * `lines` array (the same bounded payload the read card renders from). Null
1910
+ * for a foreign or malformed meta. */
1911
+ function readWindowOf(meta) {
1912
+ if (meta === null || typeof meta !== "object") return null;
1913
+ const m = meta;
1914
+ if (typeof m.path !== "string" || m.path === "") return null;
1915
+ if (typeof m.offset !== "number" || !Number.isFinite(m.offset) || m.offset < 1) return null;
1916
+ if (!Array.isArray(m.lines) || m.lines.length === 0) return null;
1917
+ return {
1918
+ start: m.offset,
1919
+ count: m.lines.length
1920
+ };
1921
+ }
1922
+ /** The limit estimate: the tool reads up to `limit` lines from `offset`;
1923
+ * absent when the call reads unbounded. */
1924
+ function readEstimateOf(args) {
1925
+ const limit = args.limit;
1926
+ return typeof limit === "number" && Number.isFinite(limit) && limit > 0 ? {
1927
+ count: Math.floor(limit),
1928
+ est: true
1929
+ } : void 0;
1930
+ }
1931
+ /** What a read op shows: the exact window off the result meta, else the
1932
+ * limit estimate, else nothing (an unbounded read names no footprint). */
1933
+ function readOf(meta, args) {
1934
+ const win = readWindowOf(meta);
1935
+ if (win !== null) return {
1936
+ start: win.start,
1937
+ count: win.count
1938
+ };
1939
+ return readEstimateOf(args);
1940
+ }
1941
+ /**
1942
+ * A search op's detail: the pattern, with the include filter appended when
1943
+ * one narrowed the call. A patternless (malformed) search has no detail.
1944
+ */
1945
+ function searchDetailOf(args) {
1946
+ const pattern = args?.pattern;
1947
+ if (typeof pattern !== "string" || pattern === "") return void 0;
1948
+ const include = args?.include;
1949
+ return typeof include === "string" && include !== "" ? `${pattern} (${include})` : pattern;
1950
+ }
1951
+ /**
1952
+ * The files a search demonstrably reached, read off the result's bounded
1953
+ * presentation meta (grep groups matched lines by file; glob lists paths).
1954
+ * Only the COMPLETE list attributes: a capped search (`truncated`) names a
1955
+ * partial file set, and a malformed meta names none — both fall back to the
1956
+ * call's own target. Each entry carries the reported match count.
1957
+ */
1958
+ function searchFilesOf(meta) {
1959
+ if (meta === null || typeof meta !== "object") return null;
1960
+ const m = meta;
1961
+ if (m.truncated !== false) return null;
1962
+ const files = [];
1963
+ if (m.shape === "matches" && Array.isArray(m.files)) for (const f of m.files) {
1964
+ if (f === null || typeof f !== "object") continue;
1965
+ const group = f;
1966
+ if (typeof group.path === "string" && group.path !== "" && Array.isArray(group.matches)) files.push({
1967
+ path: group.path,
1968
+ hits: group.matches.length
1969
+ });
1970
+ }
1971
+ else if (m.shape === "paths" && Array.isArray(m.paths)) {
1972
+ for (const p of m.paths) if (typeof p === "string" && p !== "") files.push({
1973
+ path: p,
1974
+ hits: 0
1975
+ });
1976
+ }
1977
+ return files.length > 0 ? files : null;
1978
+ }
1979
+ /**
1980
+ * The one-shot per-call op assembly, uniform across every producer: the
1981
+ * host's call/result pairing (args off the armed call, meta off the
1982
+ * result), the nested Code-Mode settle (no meta exists on a dispatch — the
1983
+ * read window and per-file search attribution degrade to the argument-only
1984
+ * forms), and the client's inline-generation join fallback. Returns zero to
1985
+ * N records: a search with the complete matched-file meta rows per file;
1986
+ * any other file call rows once; a non-file tool (or a call whose arguments
1987
+ * resolve no target) rows nothing.
1988
+ */
1989
+ function opsOfCall(input) {
1990
+ const args = parseCallArgs(input.argsRaw);
1991
+ const kind = kindOfCall(input.tool, args);
1992
+ if (kind === null) return [];
1993
+ const stamp = {
1994
+ seq: input.seq,
1995
+ kind,
1996
+ tool: input.tool,
1997
+ err: input.err === true,
1998
+ added: 0,
1999
+ removed: 0,
2000
+ path: "",
2001
+ ...input.time !== void 0 ? { time: input.time } : {},
2002
+ ...input.gone !== void 0 ? { gone: input.gone } : {},
2003
+ ...input.parent !== void 0 ? { parent: input.parent } : {},
2004
+ ...input.program !== void 0 ? { program: input.program } : {}
2005
+ };
2006
+ if (kind === "search") {
2007
+ const files = searchFilesOf(input.meta);
2008
+ if (files !== null) {
2009
+ const detail = searchDetailOf(args);
2010
+ const target = args !== null ? pathOfArgs(input.tool, args) : null;
2011
+ const narrowed = args !== null && typeof args.path === "string" && args.path !== "";
2012
+ return [...target !== null && !files.some((f) => f.path === target) ? [{
2013
+ ...stamp,
2014
+ path: target,
2015
+ ...narrowed && detail !== void 0 ? { detail } : {},
2016
+ ...narrowed ? {} : { pattern: true }
2017
+ }] : [], ...files.map((f) => ({
2018
+ ...stamp,
2019
+ path: f.path,
2020
+ ...detail !== void 0 ? { detail } : {},
2021
+ ...f.hits > 0 ? { hits: f.hits } : {}
2022
+ }))];
2023
+ }
2024
+ }
2025
+ if (args === null) return [];
2026
+ const path = pathOfArgs(input.tool, args);
2027
+ if (path === null) return [];
2028
+ const { added, removed } = deltaOf(input.tool, args);
2029
+ const narrowed = typeof args.path === "string" && args.path !== "";
2030
+ const detail = kind === "search" && narrowed ? searchDetailOf(args) : void 0;
2031
+ const read = kind === "read" ? readOf(input.meta, args) : void 0;
2032
+ return [{
2033
+ ...stamp,
2034
+ path,
2035
+ added,
2036
+ removed,
2037
+ ...detail !== void 0 ? { detail } : {},
2038
+ ...read !== void 0 ? { read } : {},
2039
+ ...kind === "search" && !narrowed ? { pattern: true } : {}
2040
+ }];
2041
+ }
2042
+ //#endregion
2043
+ //#region src/client/callSummary.ts
1479
2044
  function summaryInArgs(args) {
1480
2045
  if (args === null) return null;
1481
2046
  for (const k of [
@@ -1516,6 +2081,20 @@ window.__ModuleLoader__.load({
1516
2081
  return names;
1517
2082
  }
1518
2083
  //#endregion
2084
+ //#region src/client/components/detailNote.tsx
2085
+ function makeDetailNote(kit) {
2086
+ const { t } = kit;
2087
+ return function DetailNote(props) {
2088
+ const cls = props.className ?? "lc-empty";
2089
+ if (props.state === "loading") return /* @__PURE__ */ React.createElement("div", { className: cls }, t("detail.loading"));
2090
+ return /* @__PURE__ */ React.createElement("div", { className: cls }, props.onRetry !== void 0 ? /* @__PURE__ */ React.createElement("button", {
2091
+ type: "button",
2092
+ className: "lc-br-retry",
2093
+ onClick: props.onRetry
2094
+ }, t("detail.loadFailed")) : t("detail.loadFailed"));
2095
+ };
2096
+ }
2097
+ //#endregion
1519
2098
  //#region src/client/components/nodes.tsx
1520
2099
  function makeNodeText(kit) {
1521
2100
  const { t } = kit;
@@ -1530,6 +2109,71 @@ window.__ModuleLoader__.load({
1530
2109
  };
1531
2110
  }
1532
2111
  //#endregion
2112
+ //#region src/client/components/fetchOnMiss.tsx
2113
+ /**
2114
+ * Run one fetch for `key` when it is set and not yet cached. `key` null (no
2115
+ * target open) or `fetcher` undefined (older host without the history face)
2116
+ * leaves the machine idle; the caller renders the matching note either way.
2117
+ */
2118
+ function useFetchOnMiss(key, fetcher, warn) {
2119
+ const [values, setValues] = React.useState(() => /* @__PURE__ */ new Map());
2120
+ const [state, setState] = React.useState("idle");
2121
+ const [attempt, setAttempt] = React.useState(0);
2122
+ React.useEffect(() => {
2123
+ if (key === null || fetcher === void 0 || values.has(key)) return;
2124
+ let live = true;
2125
+ setState("loading");
2126
+ fetcher(key).then((value) => {
2127
+ if (!live) return;
2128
+ if (value === null) {
2129
+ setState("absent");
2130
+ return;
2131
+ }
2132
+ setValues((prev) => {
2133
+ const next = new Map(prev);
2134
+ next.set(key, value);
2135
+ return next;
2136
+ });
2137
+ setState("idle");
2138
+ }, (error) => {
2139
+ console.warn(warn, error);
2140
+ if (live) setState("failed");
2141
+ });
2142
+ return () => {
2143
+ live = false;
2144
+ };
2145
+ }, [
2146
+ key,
2147
+ fetcher,
2148
+ attempt,
2149
+ values
2150
+ ]);
2151
+ return {
2152
+ values,
2153
+ state,
2154
+ retry: () => {
2155
+ setAttempt((a) => a + 1);
2156
+ }
2157
+ };
2158
+ }
2159
+ /**
2160
+ * The note a not-yet-loaded fetch target shows — one expression for both
2161
+ * on-demand reads: the legacy static hint (`emptyKey`) when no fetcher is
2162
+ * composed, `loading` while the read is in flight (and on the first frame
2163
+ * before the effect fires), `notInLog` when the durable log does not hold
2164
+ * the key, and a retry button after a failed read.
2165
+ */
2166
+ function fetchMissNote(t, fetcher, state, onRetry, emptyKey) {
2167
+ if (fetcher === void 0) return t(emptyKey);
2168
+ if (state === "absent") return t("browser.notInLog");
2169
+ if (state === "failed") return /* @__PURE__ */ React.createElement("button", {
2170
+ type: "button",
2171
+ className: "lc-br-retry",
2172
+ onClick: onRetry
2173
+ }, t("browser.loadFailed"));
2174
+ return t("browser.loading");
2175
+ }
2176
+ //#endregion
1533
2177
  //#region src/client/format.ts
1534
2178
  /** `fmt`: the k/M suffix style shared by bars/details/stats; `fmtTime`: local HH:MM:SS. */
1535
2179
  function fmt(n) {
@@ -1583,7 +2227,7 @@ window.__ModuleLoader__.load({
1583
2227
  * Non-finite or non-positive input shows the dash (callers render their empty
1584
2228
  * state anyway).
1585
2229
  */
1586
- function fmtDuration$1(ms, lang) {
2230
+ function fmtDuration(ms, lang) {
1587
2231
  if (!Number.isFinite(ms) || ms <= 0) return "—";
1588
2232
  if (ms < 1e3) return `${Math.round(ms)}ms`;
1589
2233
  const zh = lang === "zh";
@@ -2323,6 +2967,7 @@ window.__ModuleLoader__.load({
2323
2967
  }
2324
2968
  function makeContextBrowser(kit, StackedBar) {
2325
2969
  const { t, fmt, fmtTime, catLabel } = kit;
2970
+ const DetailNote = makeDetailNote(kit);
2326
2971
  const nodeText = makeNodeText(kit);
2327
2972
  const rich = makeRichText(kit);
2328
2973
  const ImageCard = makeImageCard(kit);
@@ -2335,46 +2980,20 @@ window.__ModuleLoader__.load({
2335
2980
  const [rowQuery, setRowQuery] = React.useState("");
2336
2981
  const [toolSort, setToolSort] = React.useState("size");
2337
2982
  const convNodes = props.convNodes;
2338
- const [fetched, setFetched] = React.useState(() => /* @__PURE__ */ new Map());
2339
- const bySeq = React.useMemo(() => {
2983
+ const convBySeq = React.useMemo(() => {
2340
2984
  const m = /* @__PURE__ */ new Map();
2341
2985
  for (const n of convNodes ?? []) m.set(n.seq, n);
2342
- for (const [seq, n] of fetched) if (!m.has(seq)) m.set(seq, n);
2343
2986
  return m;
2344
- }, [convNodes, fetched]);
2987
+ }, [convNodes]);
2345
2988
  const openSeq = openElem !== null && openElem.startsWith("n") ? Number(openElem.slice(1)) : null;
2346
- const missingSeq = openSeq !== null && !bySeq.has(openSeq) ? openSeq : null;
2347
2989
  const fetchContent = props.fetchContent;
2348
- const [missState, setMissState] = React.useState("idle");
2349
- const [retry, setRetry] = React.useState(0);
2350
- React.useEffect(() => {
2351
- if (missingSeq === null || fetchContent === void 0) return;
2352
- let live = true;
2353
- setMissState("loading");
2354
- fetchContent(missingSeq).then((node) => {
2355
- if (!live) return;
2356
- if (node === null) {
2357
- setMissState("absent");
2358
- return;
2359
- }
2360
- setFetched((prev) => {
2361
- const next = new Map(prev);
2362
- next.set(missingSeq, node);
2363
- return next;
2364
- });
2365
- setMissState("idle");
2366
- }, (error) => {
2367
- console.warn("dsh-context: targeted history read failed", error);
2368
- if (live) setMissState("failed");
2369
- });
2370
- return () => {
2371
- live = false;
2372
- };
2373
- }, [
2374
- missingSeq,
2375
- fetchContent,
2376
- retry
2377
- ]);
2990
+ const miss = useFetchOnMiss(openSeq !== null && !convBySeq.has(openSeq) ? openSeq : null, fetchContent, "dsh-context: targeted history read failed");
2991
+ const bySeq = React.useMemo(() => {
2992
+ if (miss.values.size === 0) return convBySeq;
2993
+ const m = new Map(convBySeq);
2994
+ for (const [seq, n] of miss.values) if (!m.has(seq)) m.set(seq, n);
2995
+ return m;
2996
+ }, [convBySeq, miss.values]);
2378
2997
  const pinSeq = props.pinSeq;
2379
2998
  React.useEffect(() => {
2380
2999
  setSel(pinSeq === null || pinSeq === void 0 ? "live" : pinSeq);
@@ -2397,13 +3016,7 @@ window.__ModuleLoader__.load({
2397
3016
  focusScrollRef.current = false;
2398
3017
  rootRef.current?.querySelector(".lc-br-elem-on")?.scrollIntoView({ block: "nearest" });
2399
3018
  });
2400
- const missNote = fetchContent === void 0 ? t("browser.noContent") : missState === "loading" ? t("browser.loading") : missState === "absent" ? t("browser.notInLog") : missState === "failed" ? /* @__PURE__ */ React.createElement("button", {
2401
- type: "button",
2402
- className: "lc-br-retry",
2403
- onClick: () => {
2404
- setRetry((r) => r + 1);
2405
- }
2406
- }, t("browser.loadFailed")) : t("browser.noContent");
3019
+ const missNote = fetchMissNote(t, fetchContent, miss.state, miss.retry, "browser.noContent");
2407
3020
  const requests = data.requests;
2408
3021
  const hoverReq = props.previewSeq !== null && props.previewSeq !== void 0 ? requests.find((r) => r.seq === props.previewSeq) ?? null : null;
2409
3022
  const req = hoverReq ?? (sel === "live" ? null : requests.find((r) => r.seq === sel) ?? null);
@@ -2411,48 +3024,11 @@ window.__ModuleLoader__.load({
2411
3024
  const linked = req === null && props.onHoverKey !== void 0;
2412
3025
  const linkKey = linked && props.hoverKey !== null && props.hoverKey !== "free" ? props.hoverKey : null;
2413
3026
  const view = assemble(data, headers, seq);
2414
- const [headerContent, setHeaderContent] = React.useState(() => /* @__PURE__ */ new Map());
2415
- const [headerState, setHeaderState] = React.useState("idle");
2416
- const [headerRetry, setHeaderRetry] = React.useState(0);
2417
3027
  const fetchHeader = props.fetchHeader;
2418
3028
  const headerSeq = view.header !== null ? view.header.seq : null;
2419
- const headerNeeded = view.header !== null && headerSeq !== null && !headerContent.has(headerSeq) && (openCat === "system" || openCat === "tools");
2420
- React.useEffect(() => {
2421
- if (!headerNeeded || fetchHeader === void 0) return;
2422
- let live = true;
2423
- setHeaderState("loading");
2424
- fetchHeader(headerSeq).then((content) => {
2425
- if (!live) return;
2426
- if (content === null) {
2427
- setHeaderState("absent");
2428
- return;
2429
- }
2430
- setHeaderContent((prev) => {
2431
- const next = new Map(prev);
2432
- next.set(headerSeq, content);
2433
- return next;
2434
- });
2435
- setHeaderState("idle");
2436
- }, (error) => {
2437
- console.warn("dsh-context: header content fetch failed", error);
2438
- if (live) setHeaderState("failed");
2439
- });
2440
- return () => {
2441
- live = false;
2442
- };
2443
- }, [
2444
- headerNeeded,
2445
- headerSeq,
2446
- fetchHeader,
2447
- headerRetry
2448
- ]);
2449
- const headerNote = fetchHeader === void 0 ? t("browser.headerMetaOnly") : headerState === "loading" || headerState === "idle" ? t("browser.loading") : headerState === "absent" ? t("browser.notInLog") : /* @__PURE__ */ React.createElement("button", {
2450
- type: "button",
2451
- className: "lc-br-retry",
2452
- onClick: () => {
2453
- setHeaderRetry((r) => r + 1);
2454
- }
2455
- }, t("browser.loadFailed"));
3029
+ const epoch = useFetchOnMiss(headerSeq !== null && (openCat === "system" || openCat === "tools") ? headerSeq : null, fetchHeader, "dsh-context: header content fetch failed");
3030
+ const headerContent = epoch.values;
3031
+ const headerNote = fetchMissNote(t, fetchHeader, epoch.state, epoch.retry, "browser.headerMetaOnly");
2456
3032
  const breakdown = req !== null ? req : data.current;
2457
3033
  const parts = partsOf(breakdown);
2458
3034
  const total = breakdown.total;
@@ -2658,7 +3234,14 @@ window.__ModuleLoader__.load({
2658
3234
  hoverKey: linked ? linkKey : void 0,
2659
3235
  onHoverKey: linked ? props.onHoverKey : void 0,
2660
3236
  tip: false
2661
- })), view.missingLive > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, t("browser.missingLive", { n: view.missingLive })) : null, view.approximate ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, t("browser.approx")) : null, /* @__PURE__ */ React.createElement("div", { className: "lc-br-cats" }, CATS.map((c) => {
3237
+ })), view.missingLive > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, t("browser.missingLive", { n: view.missingLive })) : null, view.approximate ? /* @__PURE__ */ React.createElement("div", { className: "lc-br-note" }, t("browser.approx")) : null, props.detailState === "loading" ? /* @__PURE__ */ React.createElement(DetailNote, {
3238
+ state: "loading",
3239
+ className: "lc-br-note"
3240
+ }) : null, props.detailState === "failed" && props.onDetailRetry !== void 0 ? /* @__PURE__ */ React.createElement(DetailNote, {
3241
+ state: "failed",
3242
+ onRetry: props.onDetailRetry,
3243
+ className: "lc-br-note"
3244
+ }) : null, /* @__PURE__ */ React.createElement("div", { className: "lc-br-cats" }, CATS.map((c) => {
2662
3245
  const count = toolCount(c.key);
2663
3246
  const v = breakdown[c.key] || 0;
2664
3247
  const prevCount = prevView !== null && prevByCat !== null ? countOf(prevView, prevByCat, c.key) : null;
@@ -2865,6 +3448,33 @@ window.__ModuleLoader__.load({
2865
3448
  };
2866
3449
  }
2867
3450
  //#endregion
3451
+ //#region src/client/components/escapeClose.ts
3452
+ /**
3453
+ * The shared Escape-to-close contract of the plugin's overlays (the /context
3454
+ * modal and the baseline-gate dialog): capture-phase keydown on window, so
3455
+ * the composer's own key handling never swallows Escape first; on close, focus
3456
+ * returns to the element that held it when the overlay opened (skipped when
3457
+ * that element left the document).
3458
+ */
3459
+ /** Close on Escape while `active`; restores the pre-open focus on cleanup. */
3460
+ function useEscapeClose(active, onClose) {
3461
+ React.useEffect(() => {
3462
+ if (!active) return void 0;
3463
+ const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
3464
+ const onKey = (ev) => {
3465
+ if (ev.key !== "Escape") return;
3466
+ ev.preventDefault();
3467
+ ev.stopPropagation();
3468
+ onClose();
3469
+ };
3470
+ window.addEventListener("keydown", onKey, true);
3471
+ return () => {
3472
+ window.removeEventListener("keydown", onKey, true);
3473
+ if (previous !== null && document.contains(previous)) previous.focus();
3474
+ };
3475
+ }, [active, onClose]);
3476
+ }
3477
+ //#endregion
2868
3478
  //#region src/client/components/contextModal.tsx
2869
3479
  function makeContextModal(ctx, kit) {
2870
3480
  const { t } = kit;
@@ -2875,10 +3485,11 @@ window.__ModuleLoader__.load({
2875
3485
  function ContextModalBody(props) {
2876
3486
  const sessionId = typeof props.sessionId === "string" ? props.sessionId : "";
2877
3487
  const open = typeof props.useContextModal === "function" ? props.useContextModal((s) => s) : false;
2878
- const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
2879
- const pressure = typeof props.useProjection === "function" ? contextPressureOf(props.useProjection("contextPressure")) : null;
2880
- const breakdown = typeof props.useProjection === "function" ? contextBreakdownOf(props.useProjection("contextBreakdown")) : null;
2881
- const headers = typeof props.useProjection === "function" ? headersOf(props.useProjection("contextHeaders")) : null;
3488
+ const source = useTimelineSource(ctx, props);
3489
+ const data = source.data;
3490
+ const pressure = projectionOf(props, "contextPressure", contextPressureOf);
3491
+ const breakdown = projectionOf(props, "contextBreakdown", contextBreakdownOf);
3492
+ const headers = projectionOf(props, "contextHeaders", headersOf);
2882
3493
  const convNodes = conversationNodesOf(props);
2883
3494
  const [hoverCat, setHoverCat] = React.useState(null);
2884
3495
  const [dockLeft, setDockLeft] = React.useState(0);
@@ -2894,21 +3505,7 @@ window.__ModuleLoader__.load({
2894
3505
  const scope = sessions.scope(sessionId);
2895
3506
  if (scope !== void 0) scope.bail(scope, "slash/input-consume-token", { guard });
2896
3507
  }, [ctx, sessionId]);
2897
- React.useEffect(() => {
2898
- if (!open) return void 0;
2899
- const previous = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2900
- const onKey = (ev) => {
2901
- if (ev.key !== "Escape") return;
2902
- ev.preventDefault();
2903
- ev.stopPropagation();
2904
- close();
2905
- };
2906
- window.addEventListener("keydown", onKey, true);
2907
- return () => {
2908
- window.removeEventListener("keydown", onKey, true);
2909
- if (previous !== null && document.contains(previous)) previous.focus();
2910
- };
2911
- }, [open, close]);
3508
+ useEscapeClose(open, close);
2912
3509
  React.useLayoutEffect(() => {
2913
3510
  if (!open) return void 0;
2914
3511
  const dock = measureDock(backdropRef.current);
@@ -2954,7 +3551,9 @@ window.__ModuleLoader__.load({
2954
3551
  fetchContent,
2955
3552
  fetchHeader,
2956
3553
  hoverKey: hoverCat,
2957
- onHoverKey: setHoverCat
3554
+ onHoverKey: setHoverCat,
3555
+ detailState: source.detailState,
3556
+ onDetailRetry: source.retryDetail
2958
3557
  }))));
2959
3558
  }
2960
3559
  return function ContextModal(props) {
@@ -3154,6 +3753,17 @@ window.__ModuleLoader__.load({
3154
3753
  state = next;
3155
3754
  for (const listener of listeners) listener();
3156
3755
  };
3756
+ const sync = (bound) => {
3757
+ const snap = bound.getSnapshot();
3758
+ const prefs = prefsOf(snap.value);
3759
+ publish({
3760
+ status: snap.status === "ready" || snap.status === "unavailable" ? snap.status : "loading",
3761
+ granularity: prefs.granularity ?? state.granularity,
3762
+ mode: prefs.mode ?? state.mode,
3763
+ fileSort: prefs.fileSort ?? state.fileSort,
3764
+ writable: snap.writable
3765
+ });
3766
+ };
3157
3767
  return {
3158
3768
  store: {
3159
3769
  subscribe(listener) {
@@ -3169,205 +3779,86 @@ window.__ModuleLoader__.load({
3169
3779
  defaultFileSort: () => state.fileSort,
3170
3780
  attach(bound) {
3171
3781
  scope = bound;
3172
- const sync = () => {
3173
- const snap = bound.getSnapshot();
3174
- const prefs = prefsOf(snap.value);
3175
- publish({
3176
- status: snap.status === "ready" || snap.status === "unavailable" ? snap.status : "loading",
3177
- granularity: prefs.granularity ?? state.granularity,
3178
- mode: prefs.mode ?? state.mode,
3179
- fileSort: prefs.fileSort ?? state.fileSort,
3180
- writable: snap.writable
3181
- });
3182
- };
3183
- sync();
3184
- return bound.subscribe(sync);
3782
+ sync(bound);
3783
+ return bound.subscribe(() => {
3784
+ sync(bound);
3785
+ });
3185
3786
  },
3186
3787
  set(field, value) {
3187
3788
  publish({
3188
3789
  ...state,
3189
3790
  ...prefsOf({ [field]: value })
3190
3791
  });
3191
- scope?.set(field, value);
3192
- }
3193
- };
3194
- }
3195
- //#endregion
3196
- //#region src/client/brief.ts
3197
- /** Every served node (live tail + removed archive copies), seq-sorted. */
3198
- function briefNodes(data) {
3199
- return [...data.nodes, ...data.archive].sort((a, b) => a.seq - b.seq);
3200
- }
3201
- /** First index whose node seq is >= `seq` (lower bound over the sorted list). */
3202
- function lowerBound(nodes, seq) {
3203
- let lo = 0;
3204
- let hi = nodes.length;
3205
- while (lo < hi) {
3206
- const mid = lo + hi >> 1;
3207
- if (nodes[mid].seq < seq) lo = mid + 1;
3208
- else hi = mid;
3209
- }
3210
- return lo;
3211
- }
3212
- /**
3213
- * Derive the brief for `requests[idx]` (the DISPLAY list — step records or
3214
- * turn aggregates; a turn aggregate is always a turn start, so its inputs row
3215
- * stays hidden and the opener/response carry the narrative).
3216
- */
3217
- function briefOf(nodes, requests, idx) {
3218
- if (idx < 0 || idx >= requests.length) return null;
3219
- const req = requests[idx];
3220
- const ri = lowerBound(nodes, req.seq);
3221
- const hit = ri < nodes.length && nodes[ri].seq === req.seq ? nodes[ri] : void 0;
3222
- const response = hit !== void 0 && hit.cat === "assistant" ? hit : void 0;
3223
- const turnStart = idx === 0 || (requests[idx - 1].turn ?? 0) !== (req.turn ?? 0);
3224
- let firstIdx = idx;
3225
- while (firstIdx > 0 && (requests[firstIdx - 1].turn ?? 0) === (req.turn ?? 0)) firstIdx--;
3226
- const upper = requests[firstIdx].seq;
3227
- const lower = firstIdx > 0 ? requests[firstIdx - 1].seq : -1;
3228
- let opener;
3229
- for (let i = lowerBound(nodes, upper) - 1; i >= 0; i--) {
3230
- const n = nodes[i];
3231
- if (n.seq <= lower) break;
3232
- if (n.cat === "user") {
3233
- opener = n;
3234
- break;
3792
+ const bound = scope;
3793
+ if (bound === void 0) return;
3794
+ bound.set(field, value).catch(() => {
3795
+ sync(bound);
3796
+ });
3235
3797
  }
3236
- }
3237
- const inputs = [];
3238
- if (!turnStart) {
3239
- const prevSeq = requests[idx - 1].seq;
3240
- for (let i = lowerBound(nodes, prevSeq + 1); i < nodes.length && nodes[i].seq < req.seq; i++) inputs.push(nodes[i]);
3241
- }
3242
- return {
3243
- opener,
3244
- inputs,
3245
- response
3246
3798
  };
3247
3799
  }
3248
3800
  //#endregion
3249
- //#region src/client/fileActivity.ts
3250
- const KIND_BY_TOOL = {
3251
- read: "read",
3252
- read_image: "read",
3253
- write: "write",
3254
- edit: "write",
3255
- grep: "search",
3256
- glob: "search"
3257
- };
3258
- /** The file purpose of a tool, or null for non-file tools (bash, web_search…). */
3259
- function kindOfTool(tool) {
3260
- if (tool === void 0) return null;
3261
- return KIND_BY_TOOL[tool] ?? null;
3262
- }
3263
- /**
3264
- * The file purpose of one executed call. Like {@link kindOfTool} except for
3265
- * the one file tool whose purpose follows its arguments: `str_replace_editor`
3266
- * reads on `view` and writes on every other command (create / str_replace /
3267
- * insert — an unknown command writes too; the call failed and the row keeps
3268
- * its error flag).
3269
- */
3270
- function kindOfCall(tool, args) {
3271
- if (tool === "str_replace_editor") return args !== null && args.command === "view" ? "read" : "write";
3272
- return kindOfTool(tool);
3273
- }
3274
- /**
3275
- * The operation's target path: the path-ish argument of read/write tools;
3276
- * for searches the narrowing `path`, else the pattern itself (a pathless
3277
- * grep/glob's target IS the pattern — the workspace-wide search text).
3278
- */
3279
- function pathOfArgs(tool, args) {
3280
- if (args === null) return null;
3281
- if (tool === "grep" || tool === "glob") {
3282
- const p = args.path;
3283
- if (typeof p === "string" && p !== "") return p;
3284
- const pattern = args.pattern;
3285
- return typeof pattern === "string" && pattern !== "" ? pattern : null;
3286
- }
3287
- for (const k of [
3288
- "file_path",
3289
- "filePath",
3290
- "path"
3291
- ]) {
3292
- const v = args[k];
3293
- if (typeof v === "string" && v !== "") return v;
3294
- }
3295
- return null;
3296
- }
3297
- /** Rendered line count: '' is 0, a trailing newline closes its own line. */
3298
- function linesOf(s) {
3299
- if (s === "") return 0;
3300
- let n = 0;
3301
- for (let i = 0; i < s.length; i++) if (s[i] === "\n") n++;
3302
- return s.endsWith("\n") ? n : n + 1;
3303
- }
3304
- /** The added/removed pair of one content-bearing argument set, or zeros. */
3305
- function pairOf(added, removed) {
3306
- return {
3307
- added: typeof added === "string" ? linesOf(added) : 0,
3308
- removed: typeof removed === "string" ? linesOf(removed) : 0
3309
- };
3310
- }
3311
- /**
3312
- * The signed line footprint of one call: an edit removes its old string and
3313
- * adds its new one; a write adds its content (the pre-existing body, if any,
3314
- * is unknowable from the arguments — the estimate stays honest about that);
3315
- * `str_replace_editor` splits the same shapes across its commands. Callers
3316
- * reach here only with parsed args (a null parse yields no path).
3317
- */
3318
- function deltaOf(tool, args) {
3319
- if (tool === "edit") return pairOf(args.new_string, args.old_string);
3320
- if (tool === "write") return pairOf(args.content, void 0);
3321
- if (tool === "str_replace_editor") {
3322
- if (args.command === "str_replace") return pairOf(args.new_str, args.old_str);
3323
- if (args.command === "insert") return pairOf(args.new_str, void 0);
3324
- if (args.command === "create") return pairOf(args.file_text, void 0);
3325
- }
3326
- return {
3327
- added: 0,
3328
- removed: 0
3329
- };
3330
- }
3331
- /** The exact window a read's result meta reports: `offset` plus the retained
3332
- * `lines` array (the same bounded payload the read card renders from). Null
3333
- * for a foreign or malformed meta. */
3334
- function readWindowOf(meta) {
3335
- if (meta === null || typeof meta !== "object") return null;
3336
- const m = meta;
3337
- if (typeof m.path !== "string" || m.path === "") return null;
3338
- if (typeof m.offset !== "number" || !Number.isFinite(m.offset) || m.offset < 1) return null;
3339
- if (!Array.isArray(m.lines) || m.lines.length === 0) return null;
3340
- return {
3341
- start: m.offset,
3342
- count: m.lines.length
3343
- };
3801
+ //#region src/client/brief.ts
3802
+ /** Every served node (live tail + removed archive copies), seq-sorted. */
3803
+ function briefNodes(data) {
3804
+ return [...data.nodes, ...data.archive].sort((a, b) => a.seq - b.seq);
3344
3805
  }
3345
- /** The limit estimate: the tool reads up to `limit` lines from `offset`;
3346
- * absent when the call reads unbounded. */
3347
- function readEstimateOf(args) {
3348
- const limit = args.limit;
3349
- return typeof limit === "number" && Number.isFinite(limit) && limit > 0 ? {
3350
- count: Math.floor(limit),
3351
- est: true
3352
- } : void 0;
3806
+ /** First index whose node seq is >= `seq` (lower bound over the sorted list). */
3807
+ function lowerBound(nodes, seq) {
3808
+ let lo = 0;
3809
+ let hi = nodes.length;
3810
+ while (lo < hi) {
3811
+ const mid = lo + hi >> 1;
3812
+ if (nodes[mid].seq < seq) lo = mid + 1;
3813
+ else hi = mid;
3814
+ }
3815
+ return lo;
3353
3816
  }
3354
- /** What a read op shows: the exact window off the result meta, else the
3355
- * limit estimate, else nothing (an unbounded read names no footprint). */
3356
- function readOf(meta, args) {
3357
- const win = readWindowOf(meta);
3358
- if (win !== null) return {
3359
- start: win.start,
3360
- count: win.count
3817
+ /**
3818
+ * Derive the brief for `requests[idx]` (the DISPLAY list step records or
3819
+ * turn aggregates; a turn aggregate is always a turn start, so its inputs row
3820
+ * stays hidden and the opener/response carry the narrative).
3821
+ */
3822
+ function briefOf(nodes, requests, idx) {
3823
+ if (idx < 0 || idx >= requests.length) return null;
3824
+ const req = requests[idx];
3825
+ const ri = lowerBound(nodes, req.seq);
3826
+ const hit = ri < nodes.length && nodes[ri].seq === req.seq ? nodes[ri] : void 0;
3827
+ const response = hit !== void 0 && hit.cat === "assistant" ? hit : void 0;
3828
+ const turnStart = idx === 0 || (requests[idx - 1].turn ?? 0) !== (req.turn ?? 0);
3829
+ let firstIdx = idx;
3830
+ while (firstIdx > 0 && (requests[firstIdx - 1].turn ?? 0) === (req.turn ?? 0)) firstIdx--;
3831
+ const upper = requests[firstIdx].seq;
3832
+ const lower = firstIdx > 0 ? requests[firstIdx - 1].seq : -1;
3833
+ let opener;
3834
+ for (let i = lowerBound(nodes, upper) - 1; i >= 0; i--) {
3835
+ const n = nodes[i];
3836
+ if (n.seq <= lower) break;
3837
+ if (n.cat === "user") {
3838
+ opener = n;
3839
+ break;
3840
+ }
3841
+ }
3842
+ const inputs = [];
3843
+ if (!turnStart) {
3844
+ const prevSeq = requests[idx - 1].seq;
3845
+ for (let i = lowerBound(nodes, prevSeq + 1); i < nodes.length && nodes[i].seq < req.seq; i++) inputs.push(nodes[i]);
3846
+ }
3847
+ return {
3848
+ opener,
3849
+ inputs,
3850
+ response
3361
3851
  };
3362
- return readEstimateOf(args);
3363
3852
  }
3364
- const IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|bmp|ico|avif)$/i;
3853
+ //#endregion
3854
+ //#region src/client/fileActivity.ts
3365
3855
  /** The file's form — multimodal reads and image extensions scan apart; a trailing slash marks a directory target. */
3366
3856
  function formOf(tool, path) {
3367
3857
  if (tool === "read_image" || IMAGE_EXT.test(path)) return "image";
3368
3858
  if (path.endsWith("/")) return "dir";
3369
3859
  return "text";
3370
3860
  }
3861
+ const IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|bmp|ico|avif)$/i;
3371
3862
  /** Directory buckets, by last path segment (lowercased, explicit plurals). Checked in order, then hidden dirs, then the plain folder. */
3372
3863
  const DIR_BUCKETS = [
3373
3864
  [
@@ -3866,118 +4357,11 @@ window.__ModuleLoader__.load({
3866
4357
  return "./" + path;
3867
4358
  }
3868
4359
  /**
3869
- * A search op's detail: the pattern, with the include filter appended when
3870
- * one narrowed the call. A patternless (malformed) search has no detail.
3871
- */
3872
- function searchDetailOf(args) {
3873
- const pattern = args?.pattern;
3874
- if (typeof pattern !== "string" || pattern === "") return void 0;
3875
- const include = args?.include;
3876
- return typeof include === "string" && include !== "" ? `${pattern} (${include})` : pattern;
3877
- }
3878
- /**
3879
- * The files a search demonstrably reached, read off the result's bounded
3880
- * presentation meta (grep groups matched lines by file; glob lists paths).
3881
- * Only the COMPLETE list attributes: a capped search (`truncated`) names a
3882
- * partial file set, and a malformed meta names none — both fall back to the
3883
- * call's own target. Each entry carries the reported match count.
3884
- */
3885
- function searchFilesOf(meta) {
3886
- if (meta === null || typeof meta !== "object") return null;
3887
- const m = meta;
3888
- if (m.truncated !== false) return null;
3889
- const files = [];
3890
- if (m.shape === "matches" && Array.isArray(m.files)) for (const f of m.files) {
3891
- if (f === null || typeof f !== "object") continue;
3892
- const group = f;
3893
- if (typeof group.path === "string" && group.path !== "" && Array.isArray(group.matches)) files.push({
3894
- path: group.path,
3895
- hits: group.matches.length
3896
- });
3897
- }
3898
- else if (m.shape === "paths" && Array.isArray(m.paths)) {
3899
- for (const p of m.paths) if (typeof p === "string" && p !== "") files.push({
3900
- path: p,
3901
- hits: 0
3902
- });
3903
- }
3904
- return files.length > 0 ? files : null;
3905
- }
3906
- /**
3907
- * Narrow one block of a conversation node's `subCalls` tree to a settled
3908
- * nested call, or null. The join is defensive — a running call has no result
3909
- * kind yet, and any malformed block is dropped, never thrown. (Null and
3910
- * non-object blocks never reach here: the folding loop pre-filters them.)
3911
- */
3912
- function subCallOf(block) {
3913
- const b = block;
3914
- if (b.kind !== "tool-result") return null;
3915
- if (b.call === null || typeof b.call !== "object") return null;
3916
- const call = b.call;
3917
- if (typeof call.name !== "string" || typeof call.argsRaw !== "string") return null;
3918
- if (typeof b.seq !== "number" || !Number.isFinite(b.seq)) return null;
3919
- return {
3920
- name: call.name,
3921
- argsRaw: call.argsRaw,
3922
- seq: b.seq,
3923
- ...typeof b.time === "number" ? { time: b.time } : {},
3924
- ...b.isError === true ? { err: true } : { err: false },
3925
- ...Array.isArray(b.subCalls) ? { subCalls: b.subCalls } : {}
3926
- };
3927
- }
3928
- /** The run_code call's model-authored description — the nested ops' "why". */
3929
- function programOf(conv) {
3930
- const description = parseCallArgs(conv?.call?.argsRaw)?.description;
3931
- return typeof description === "string" && description !== "" ? description : void 0;
3932
- }
3933
- /**
3934
- * Depth guard for nested Code-Mode trees. The SDK bindings exclude `run_code`
3935
- * itself, so a real tree is one level deep; the cap only bounds defensive
3936
- * re-entry over a malformed join.
3937
- */
3938
- const SUBCALL_MAX_DEPTH = 8;
3939
- /**
3940
- * Fold one nested Code-Mode tree: every settled sub-dispatch whose arguments
3941
- * resolve to a file target books one op, attributed to the nested tool and
3942
- * located on the parent run_code result. `seen` holds the already-visited
3943
- * blocks, so a malformed (cyclic) join cannot loop.
4360
+ * Fold op records into per-file activity: scope-filtered, aggregated per
4361
+ * path, ops newest first. The presentation half of the card — the parsing
4362
+ * is the shared parser's (shared/fileOps.ts), however the records arrived.
3944
4363
  */
3945
- function foldSubCalls(blocks, parent, program, add, seen, depth) {
3946
- if (depth > SUBCALL_MAX_DEPTH) return;
3947
- for (const block of blocks) {
3948
- if (block === null || typeof block !== "object" || seen.has(block)) continue;
3949
- seen.add(block);
3950
- const sub = subCallOf(block);
3951
- if (sub !== null) {
3952
- const args = parseCallArgs(sub.argsRaw);
3953
- const kind = args !== null ? kindOfCall(sub.name, args) : null;
3954
- const path = kind !== null ? pathOfArgs(sub.name, args) : null;
3955
- if (args !== null && kind !== null && path !== null) {
3956
- const { added, removed } = deltaOf(sub.name, args);
3957
- const narrowed = typeof args.path === "string" && args.path !== "";
3958
- const detail = kind === "search" && narrowed ? searchDetailOf(args) : void 0;
3959
- const read = kind === "read" ? readOf(void 0, args) : void 0;
3960
- add({
3961
- seq: sub.seq,
3962
- ...parent.gone !== void 0 ? { gone: parent.gone } : {},
3963
- kind,
3964
- tool: sub.name,
3965
- ...sub.time !== void 0 ? { time: sub.time } : {},
3966
- err: sub.err,
3967
- added,
3968
- removed,
3969
- ...detail !== void 0 ? { detail } : {},
3970
- ...read !== void 0 ? { read } : {},
3971
- parent: parent.seq,
3972
- ...program !== void 0 ? { program } : {}
3973
- }, path, kind === "search" && !narrowed);
3974
- }
3975
- if (sub.subCalls !== void 0) foldSubCalls(sub.subCalls, parent, program, add, seen, depth + 1);
3976
- }
3977
- }
3978
- }
3979
- /** Fold every served tool-result node into per-file activity. */
3980
- function activityOf(nodes, convOf, before) {
4364
+ function aggregateOps(ops, before) {
3981
4365
  const totals = {
3982
4366
  read: {
3983
4367
  files: 0,
@@ -3999,15 +4383,15 @@ window.__ModuleLoader__.load({
3999
4383
  removed: 0
4000
4384
  };
4001
4385
  const byPath = /* @__PURE__ */ new Map();
4002
- /** Book one executed op under its file; the totals and the entry move together.
4003
- * `pattern` marks a pathless search's pattern-as-target row (a display-only flag). */
4004
- const add = (op, path, pattern = false) => {
4386
+ for (const op of ops) {
4387
+ const key = op.parent ?? op.seq;
4388
+ if (before !== null && key >= before) continue;
4005
4389
  totals[op.kind].ops++;
4006
- let entry = byPath.get(path);
4390
+ let entry = byPath.get(op.path);
4007
4391
  if (entry === void 0) {
4008
4392
  entry = {
4009
- path,
4010
- form: formOf(op.tool, path),
4393
+ path: op.path,
4394
+ form: formOf(op.tool, op.path),
4011
4395
  reads: 0,
4012
4396
  writes: 0,
4013
4397
  searches: 0,
@@ -4015,9 +4399,9 @@ window.__ModuleLoader__.load({
4015
4399
  removed: 0,
4016
4400
  errs: 0,
4017
4401
  ops: [],
4018
- ...pattern ? { pattern: true } : {}
4402
+ ...op.pattern === true ? { pattern: true } : {}
4019
4403
  };
4020
- byPath.set(path, entry);
4404
+ byPath.set(op.path, entry);
4021
4405
  }
4022
4406
  if (op.kind === "read") entry.reads++;
4023
4407
  else if (op.kind === "write") entry.writes++;
@@ -4026,59 +4410,6 @@ window.__ModuleLoader__.load({
4026
4410
  entry.removed += op.removed;
4027
4411
  if (op.err) entry.errs++;
4028
4412
  entry.ops.push(op);
4029
- };
4030
- for (const n of nodes) {
4031
- if (n.cat !== "tool") continue;
4032
- if (before !== null && n.seq >= before) continue;
4033
- const tool = n.tool;
4034
- if (tool === void 0) continue;
4035
- try {
4036
- const conv = convOf(n.seq);
4037
- const staticKind = kindOfTool(tool);
4038
- const args = staticKind !== null || tool === "str_replace_editor" ? parseCallArgs(conv?.call?.argsRaw) : null;
4039
- const kind = staticKind ?? kindOfCall(tool, args);
4040
- if (kind !== null) {
4041
- const err = n.err === true || conv?.isError === true;
4042
- const files = kind === "search" ? searchFilesOf(conv?.meta) : null;
4043
- if (files !== null) {
4044
- const detail = searchDetailOf(args);
4045
- for (const f of files) add({
4046
- seq: n.seq,
4047
- ...n.gone !== void 0 ? { gone: n.gone } : {},
4048
- kind,
4049
- tool,
4050
- ...n.time !== void 0 ? { time: n.time } : {},
4051
- err,
4052
- added: 0,
4053
- removed: 0,
4054
- ...detail !== void 0 ? { detail } : {},
4055
- ...f.hits > 0 ? { hits: f.hits } : {}
4056
- }, f.path);
4057
- } else if (args !== null) {
4058
- const path = pathOfArgs(tool, args);
4059
- if (path !== null) {
4060
- const { added, removed } = deltaOf(tool, args);
4061
- const narrowed = typeof args.path === "string" && args.path !== "";
4062
- const detail = kind === "search" && narrowed ? searchDetailOf(args) : void 0;
4063
- const read = kind === "read" ? readOf(conv?.meta, args) : void 0;
4064
- add({
4065
- seq: n.seq,
4066
- ...n.gone !== void 0 ? { gone: n.gone } : {},
4067
- kind,
4068
- tool,
4069
- ...n.time !== void 0 ? { time: n.time } : {},
4070
- err,
4071
- added,
4072
- removed,
4073
- ...detail !== void 0 ? { detail } : {},
4074
- ...read !== void 0 ? { read } : {}
4075
- }, path, kind === "search" && !narrowed);
4076
- }
4077
- }
4078
- }
4079
- const subCalls = conv?.subCalls;
4080
- if (subCalls !== void 0 && subCalls.length > 0) foldSubCalls(subCalls, n, programOf(conv), add, /* @__PURE__ */ new Set(), 1);
4081
- } catch {}
4082
4413
  }
4083
4414
  const entries = [...byPath.values()];
4084
4415
  for (const e of entries) {
@@ -4100,6 +4431,114 @@ window.__ModuleLoader__.load({
4100
4431
  };
4101
4432
  }
4102
4433
  /**
4434
+ * The op-log generation's read of the card: the fold-derived records (the
4435
+ * detail payload's `fileOps`), with `gone` joined from the detail's archive
4436
+ * at render time (the op's result node leaving the live surface bounds where
4437
+ * its content stays viewable — the locate bridge reads it).
4438
+ */
4439
+ function activityOfOps(ops, archive, before) {
4440
+ const goneBySeq = /* @__PURE__ */ new Map();
4441
+ for (const n of archive) if (n.gone !== void 0) goneBySeq.set(n.seq, n.gone);
4442
+ return aggregateOps(ops.map((op) => {
4443
+ const gone = goneBySeq.get(op.parent ?? op.seq);
4444
+ return gone !== void 0 && op.gone === void 0 ? {
4445
+ ...op,
4446
+ gone
4447
+ } : op;
4448
+ }), before);
4449
+ }
4450
+ /**
4451
+ * Narrow one block of a conversation node's `subCalls` tree to a settled
4452
+ * nested call, or null. The join is defensive — a running call has no result
4453
+ * kind yet, and any malformed block is dropped, never thrown. (Null and
4454
+ * non-object blocks never reach here: the folding loop pre-filters them.)
4455
+ */
4456
+ function subCallOf(block) {
4457
+ const b = block;
4458
+ if (b.kind !== "tool-result") return null;
4459
+ if (b.call === null || typeof b.call !== "object") return null;
4460
+ const call = b.call;
4461
+ if (typeof call.name !== "string" || typeof call.argsRaw !== "string") return null;
4462
+ if (typeof b.seq !== "number" || !Number.isFinite(b.seq)) return null;
4463
+ return {
4464
+ name: call.name,
4465
+ argsRaw: call.argsRaw,
4466
+ seq: b.seq,
4467
+ ...typeof b.time === "number" ? { time: b.time } : {},
4468
+ ...b.isError === true ? { err: true } : { err: false },
4469
+ ...Array.isArray(b.subCalls) ? { subCalls: b.subCalls } : {}
4470
+ };
4471
+ }
4472
+ /** The run_code call's model-authored description — the nested ops' "why". */
4473
+ function programOf(conv) {
4474
+ const description = parseCallArgs(conv?.call?.argsRaw)?.description;
4475
+ return typeof description === "string" && description !== "" ? description : void 0;
4476
+ }
4477
+ /**
4478
+ * Depth guard for nested Code-Mode trees. The SDK bindings exclude `run_code`
4479
+ * itself, so a real tree is one level deep; the cap only bounds defensive
4480
+ * re-entry over a malformed join.
4481
+ */
4482
+ const SUBCALL_MAX_DEPTH = 8;
4483
+ /**
4484
+ * The legacy nested walk: every settled sub-dispatch whose arguments resolve
4485
+ * to a file target books one op, attributed to the nested tool and located
4486
+ * on the parent run_code result. `seen` holds the already-visited blocks, so
4487
+ * a malformed (cyclic) join cannot loop.
4488
+ */
4489
+ function foldSubCalls(blocks, parent, program, ops, seen, depth) {
4490
+ if (depth > SUBCALL_MAX_DEPTH) return;
4491
+ for (const block of blocks) {
4492
+ if (block === null || typeof block !== "object" || seen.has(block)) continue;
4493
+ seen.add(block);
4494
+ const sub = subCallOf(block);
4495
+ if (sub !== null) {
4496
+ ops.push(...opsOfCall({
4497
+ seq: sub.seq,
4498
+ tool: sub.name,
4499
+ argsRaw: sub.argsRaw,
4500
+ err: sub.err,
4501
+ ...sub.time !== void 0 ? { time: sub.time } : {},
4502
+ ...parent.gone !== void 0 ? { gone: parent.gone } : {},
4503
+ parent: parent.seq,
4504
+ ...program !== void 0 ? { program } : {}
4505
+ }));
4506
+ if (sub.subCalls !== void 0) foldSubCalls(sub.subCalls, parent, program, ops, seen, depth + 1);
4507
+ }
4508
+ }
4509
+ }
4510
+ /**
4511
+ * The INLINE generation's derivation: ops from the served tool-result nodes
4512
+ * joined with the conversation window (arguments/meta live on the join —
4513
+ * window-bound; the op-log generation covers the full session instead).
4514
+ * One node's join data can never take the card down: anything that throws
4515
+ * while folding it drops that node and the walk carries on.
4516
+ */
4517
+ function activityOf(nodes, convOf, before) {
4518
+ const ops = [];
4519
+ for (const n of nodes) {
4520
+ if (n.cat !== "tool") continue;
4521
+ if (before !== null && n.seq >= before) continue;
4522
+ const tool = n.tool;
4523
+ if (tool === void 0) continue;
4524
+ try {
4525
+ const conv = convOf(n.seq);
4526
+ ops.push(...opsOfCall({
4527
+ seq: n.seq,
4528
+ tool,
4529
+ argsRaw: conv?.call?.argsRaw,
4530
+ meta: conv?.meta,
4531
+ err: n.err === true || conv?.isError === true,
4532
+ ...n.time !== void 0 ? { time: n.time } : {},
4533
+ ...n.gone !== void 0 ? { gone: n.gone } : {}
4534
+ }));
4535
+ const subCalls = conv?.subCalls;
4536
+ if (subCalls !== void 0 && subCalls.length > 0) foldSubCalls(subCalls, n, programOf(conv), ops, /* @__PURE__ */ new Set(), 1);
4537
+ } catch {}
4538
+ }
4539
+ return aggregateOps(ops, null);
4540
+ }
4541
+ /**
4103
4542
  * The browser step whose assembled surface SHOWS an op's result node: the
4104
4543
  * first request dispatched after it while it was still alive (that step's
4105
4544
  * brief is where the result landed). A live node no request has consumed
@@ -4116,10 +4555,6 @@ window.__ModuleLoader__.load({
4116
4555
  const LEVEL_H = 154;
4117
4556
  const CELL_H = 90;
4118
4557
  const PAD_Y = 56;
4119
- function asRecord(value) {
4120
- if (value === null || value === void 0 || typeof value !== "object") return null;
4121
- return value;
4122
- }
4123
4558
  /** Narrow one list-row value; null when it is not a row at all. */
4124
4559
  function agentRowOf(value) {
4125
4560
  const rec = asRecord(value);
@@ -4185,7 +4620,7 @@ window.__ModuleLoader__.load({
4185
4620
  const billed = usage !== null ? numOf(usage.uncachedInputTokens) + numOf(usage.outputTokens) + numOf(usage.cacheReadTokens) + numOf(usage.cacheWriteTokens) : null;
4186
4621
  return {
4187
4622
  head,
4188
- requests: timeline !== null ? timeline.requests.length : 0,
4623
+ requests: timeline !== null ? timeline.counts?.steps ?? timeline.requests.length : 0,
4189
4624
  billed,
4190
4625
  durationMs: agentDurationOf(values?.subagentTiming),
4191
4626
  identity: agentIdentityOf(values?.subagent)
@@ -4489,8 +4924,13 @@ window.__ModuleLoader__.load({
4489
4924
  if (list === null || typeof list.getSnapshot !== "function" || typeof list.subscribe !== "function") return null;
4490
4925
  return rec;
4491
4926
  }
4492
- /** Compact duration: `42s`, `3m05s`, `1h07m` (shared by both locales). */
4493
- function fmtDuration(ms) {
4927
+ /**
4928
+ * Compact duration: `42s`, `3m05s`, `1h07m` (shared by both locales).
4929
+ * Deliberately distinct from format.ts's locale-aware `fmtDuration` (the
4930
+ * timing card's `12.3s` / `4分0秒`): the inspector's caption column needs
4931
+ * whole-second, fixed-width, locale-free text.
4932
+ */
4933
+ function fmtDurationCompact(ms) {
4494
4934
  if (!Number.isFinite(ms) || ms < 0) return "—";
4495
4935
  const s = Math.round(ms / 1e3);
4496
4936
  if (s < 60) return `${s}s`;
@@ -4689,7 +5129,7 @@ window.__ModuleLoader__.load({
4689
5129
  }
4690
5130
  if (node.requests > 0) bits.push(t("agents.requests", { n: node.requests }));
4691
5131
  if (node.billed !== null && node.billed > 0) bits.push(t("agents.billed", { n: fmt(node.billed) }));
4692
- if (node.durationMs !== null) bits.push(fmtDuration(node.durationMs));
5132
+ if (node.durationMs !== null) bits.push(fmtDurationCompact(node.durationMs));
4693
5133
  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);
4694
5134
  }
4695
5135
  //#endregion
@@ -4824,6 +5264,7 @@ window.__ModuleLoader__.load({
4824
5264
  }
4825
5265
  function makeEventList(kit) {
4826
5266
  const { t, fmt, fmtTime, eventLabel, eventAt } = kit;
5267
+ const DetailNote = makeDetailNote(kit);
4827
5268
  return function EventList(props) {
4828
5269
  const rootRef = React.useRef(null);
4829
5270
  React.useLayoutEffect(() => {
@@ -4841,7 +5282,14 @@ window.__ModuleLoader__.load({
4841
5282
  window.removeEventListener("resize", onResize);
4842
5283
  };
4843
5284
  }, []);
4844
- if (props.events.length === 0) return /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("events.empty"));
5285
+ if (props.events.length === 0) {
5286
+ if (props.state === "loading") return /* @__PURE__ */ React.createElement(DetailNote, { state: "loading" });
5287
+ if (props.state === "failed" && props.onRetry !== void 0) return /* @__PURE__ */ React.createElement(DetailNote, {
5288
+ state: "failed",
5289
+ onRetry: props.onRetry
5290
+ });
5291
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("events.empty"));
5292
+ }
4845
5293
  const sorted = props.events.slice().reverse();
4846
5294
  return /* @__PURE__ */ React.createElement("div", {
4847
5295
  className: "lc-events",
@@ -4861,6 +5309,7 @@ window.__ModuleLoader__.load({
4861
5309
  //#region src/client/components/fileCard.tsx
4862
5310
  function makeFileCard(kit, settings) {
4863
5311
  const { t, fmt, fmtTime } = kit;
5312
+ const DetailNote = makeDetailNote(kit);
4864
5313
  function matches(e, f) {
4865
5314
  if (f === "all") return true;
4866
5315
  if (f === "image") return e.form === "image";
@@ -4925,7 +5374,10 @@ window.__ModuleLoader__.load({
4925
5374
  className: "lc-br-err-dot",
4926
5375
  title: t("node.failed")
4927
5376
  }) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-fa-op-time" }, op.time !== void 0 ? fmtTime(op.time) : "—"));
4928
- 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", {
5377
+ 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 && props.state === "loading" ? /* @__PURE__ */ React.createElement(DetailNote, { state: "loading" }) : activity.entries.length === 0 && props.state === "failed" && props.onRetry !== void 0 ? /* @__PURE__ */ React.createElement(DetailNote, {
5378
+ state: "failed",
5379
+ onRetry: props.onRetry
5380
+ }) : 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", {
4929
5381
  key: c.key,
4930
5382
  type: "button",
4931
5383
  className: "lc-gran-btn" + (filter === c.key ? " lc-gran-on" : "") + (c.key === "read" || c.key === "write" || c.key === "search" ? " lc-fa-chip-" + c.key : ""),
@@ -5092,7 +5544,7 @@ window.__ModuleLoader__.load({
5092
5544
  return function PluginInfo() {
5093
5545
  const [latest, setLatest] = React.useState(null);
5094
5546
  React.useEffect(() => {
5095
- if ("0.43.0".includes("-dev")) return;
5547
+ if ("0.44.0".includes("-dev")) return;
5096
5548
  let on = true;
5097
5549
  fetchLatestVersion().then((v) => {
5098
5550
  if (on && v) setLatest(v);
@@ -5101,8 +5553,8 @@ window.__ModuleLoader__.load({
5101
5553
  on = false;
5102
5554
  };
5103
5555
  }, []);
5104
- const update = latest !== null && isNewerVersion(latest, "0.43.0") ? latest : null;
5105
- const nameText = "dsh-context (v0.43.0)";
5556
+ const update = latest !== null && isNewerVersion(latest, "0.44.0") ? latest : null;
5557
+ const nameText = "dsh-context (v0.44.0)";
5106
5558
  const nameValue = [nameText];
5107
5559
  if (update) nameValue.push(/* @__PURE__ */ React.createElement("span", {
5108
5560
  key: "update",
@@ -5139,19 +5591,7 @@ window.__ModuleLoader__.load({
5139
5591
  dismissed.add(sessionId);
5140
5592
  setClosedFor(sessionId);
5141
5593
  }, [sessionId]);
5142
- React.useEffect(() => {
5143
- if (closed) return void 0;
5144
- const onKey = (ev) => {
5145
- if (ev.key !== "Escape") return;
5146
- ev.preventDefault();
5147
- ev.stopPropagation();
5148
- close();
5149
- };
5150
- window.addEventListener("keydown", onKey, true);
5151
- return () => {
5152
- window.removeEventListener("keydown", onKey, true);
5153
- };
5154
- }, [closed, close]);
5594
+ useEscapeClose(!closed, close);
5155
5595
  if (closed) return null;
5156
5596
  return /* @__PURE__ */ React.createElement("div", {
5157
5597
  className: "lc-modal-backdrop",
@@ -5484,19 +5924,32 @@ window.__ModuleLoader__.load({
5484
5924
  }
5485
5925
  //#endregion
5486
5926
  //#region src/client/components/statsContext.tsx
5927
+ /**
5928
+ * The inline generation's counter derivation — the exact tally the card ran
5929
+ * over the served collections before the split (distinct turn values, record
5930
+ * count, per-kind event tallies). The host's split-generation counts match
5931
+ * it by construction (fold.ts buildTimelineHead).
5932
+ */
5933
+ function countsOfRecords(requests, events) {
5934
+ const turns = /* @__PURE__ */ new Set();
5935
+ for (const req of requests) turns.add(req.turn ?? 0);
5936
+ let injects = 0;
5937
+ let compactions = 0;
5938
+ let prunes = 0;
5939
+ for (const ev of events) if (ev.kind === "inject") injects++;
5940
+ else if (ev.kind === "compaction") compactions++;
5941
+ else if (ev.kind === "prune") prunes++;
5942
+ return {
5943
+ turns: turns.size,
5944
+ steps: requests.length,
5945
+ injects,
5946
+ compactions,
5947
+ prunes
5948
+ };
5949
+ }
5487
5950
  function makeStatsContext(kit) {
5488
5951
  const { t, fmt } = kit;
5489
5952
  return function StatsContext(props) {
5490
- const turns = /* @__PURE__ */ new Set();
5491
- let steps = 0;
5492
- for (const req of props.requests) {
5493
- turns.add(req.turn ?? 0);
5494
- steps++;
5495
- }
5496
- let injects = 0, compactions = 0, prunes = 0;
5497
- for (const ev of props.events) if (ev.kind === "inject") injects++;
5498
- else if (ev.kind === "compaction") compactions++;
5499
- else if (ev.kind === "prune") prunes++;
5500
5953
  const currency = props.locale === "zh" ? "cny" : "usd";
5501
5954
  const cost = estimateSessionCost(props.cost, currency);
5502
5955
  const fmtRate = (n) => formatPriceRate(n, currency);
@@ -5514,7 +5967,7 @@ window.__ModuleLoader__.load({
5514
5967
  className: "lc-tip lc-stat-tip",
5515
5968
  role: "tooltip"
5516
5969
  }, tip));
5517
- return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col-stats" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("stats.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("stats.hint"))), /* @__PURE__ */ React.createElement("div", { className: "lc-stats" }, cell(t("stats.turns"), turns.size), cell(t("stats.steps"), steps), cell(t("stats.toolCalls"), props.toolCalls ?? 0), cell(t("stats.images"), props.images ?? 0), cell(t("stats.cost"), cost === null ? "—" : formatCost(cost, currency), costTip), cell(t("stats.injects"), injects), cell(t("stats.compactions"), compactions), cell(t("stats.prunes"), prunes)));
5970
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col-stats" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("stats.title"))), /* @__PURE__ */ React.createElement("div", { className: "lc-stats" }, cell(t("stats.turns"), props.counts.turns), cell(t("stats.steps"), props.counts.steps), cell(t("stats.toolCalls"), props.toolCalls ?? 0), cell(t("stats.images"), props.images ?? 0), cell(t("stats.cost"), cost === null ? "—" : formatCost(cost, currency), costTip), cell(t("stats.injects"), props.counts.injects), cell(t("stats.compactions"), props.counts.compactions), cell(t("stats.prunes"), props.counts.prunes)));
5518
5971
  };
5519
5972
  }
5520
5973
  //#endregion
@@ -5625,7 +6078,7 @@ window.__ModuleLoader__.load({
5625
6078
  }
5626
6079
  ];
5627
6080
  }
5628
- return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col-stats lc-col-donut" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("timing.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("timing.hint"))), rows.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("timing.empty")) : /* @__PURE__ */ React.createElement("div", { className: "lc-donut-row" }, /* @__PURE__ */ React.createElement(Donut, {
6081
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col-stats lc-col-donut" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("timing.title"))), rows.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("timing.empty")) : /* @__PURE__ */ React.createElement("div", { className: "lc-donut-row" }, /* @__PURE__ */ React.createElement(Donut, {
5629
6082
  segments,
5630
6083
  size: 96,
5631
6084
  centerTop: wall > 0 ? fmtDuration(wall, lang) : "—",
@@ -5687,7 +6140,7 @@ window.__ModuleLoader__.load({
5687
6140
  pct: fmtShare(b.value, billed),
5688
6141
  count: b.note === void 0 ? fmt(b.value) : `${fmt(b.value)} · ${b.note}`
5689
6142
  }));
5690
- return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col-stats lc-col-donut" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("tokens.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("tokens.hint"))), /* @__PURE__ */ React.createElement("div", { className: "lc-donut-row" }, /* @__PURE__ */ React.createElement(Donut, {
6143
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col-stats lc-col-donut" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("tokens.title"))), /* @__PURE__ */ React.createElement("div", { className: "lc-donut-row" }, /* @__PURE__ */ React.createElement(Donut, {
5691
6144
  segments: shown.map((b) => ({
5692
6145
  key: b.key,
5693
6146
  color: b.color,
@@ -6146,16 +6599,18 @@ window.__ModuleLoader__.load({
6146
6599
  const StatsTokens = makeStatsTokens(kit, Donut);
6147
6600
  const PluginInfo = makePluginInfo(kit);
6148
6601
  const UpgradeGate = makeUpgradeGate(kit);
6602
+ const DetailNote = makeDetailNote(kit);
6149
6603
  const ContextBrowser = makeContextBrowser(kit, StackedBar);
6150
6604
  const AgentGraph = makeAgentGraph(ctx, kit);
6151
6605
  const ErrorBoundary = makeErrorBoundary(t);
6152
6606
  function ContextViewBody(props) {
6153
6607
  const sessionId = props.sessionId;
6154
- const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
6155
- const pressure = typeof props.useProjection === "function" ? contextPressureOf(props.useProjection("contextPressure")) : null;
6156
- const usage = typeof props.useProjection === "function" ? tokenUsageOf(props.useProjection("tokenUsage")) : null;
6157
- const breakdown = typeof props.useProjection === "function" ? contextBreakdownOf(props.useProjection("contextBreakdown")) : null;
6158
- const headers = typeof props.useProjection === "function" ? headersOf(props.useProjection("contextHeaders")) : null;
6608
+ const source = useTimelineSource(ctx, props);
6609
+ const data = source.data;
6610
+ const pressure = projectionOf(props, "contextPressure", contextPressureOf);
6611
+ const usage = projectionOf(props, "tokenUsage", tokenUsageOf);
6612
+ const breakdown = projectionOf(props, "contextBreakdown", contextBreakdownOf);
6613
+ const headers = projectionOf(props, "contextHeaders", headersOf);
6159
6614
  const [selectedSeq, setSelectedSeq] = React.useState(null);
6160
6615
  const [hoveredSeq, setHoveredSeq] = React.useState(null);
6161
6616
  const [hoverTurn, setHoverTurn] = React.useState(null);
@@ -6211,8 +6666,9 @@ window.__ModuleLoader__.load({
6211
6666
  const seq = takeContextFocus(sessionId);
6212
6667
  if (seq !== null) setJumpSeq(seq);
6213
6668
  }, [sessionId]);
6669
+ const detailReady = source.detailState === "ready" || source.detailState === "legacy";
6214
6670
  React.useEffect(() => {
6215
- if (jumpSeq === null || data === null) return;
6671
+ if (jumpSeq === null || data === null || !detailReady) return;
6216
6672
  setJumpSeq(null);
6217
6673
  const target = jumpTargetOf(aggregateByTurn(requests), jumpSeq);
6218
6674
  if (target === null) return;
@@ -6223,7 +6679,8 @@ window.__ModuleLoader__.load({
6223
6679
  }, [
6224
6680
  jumpSeq,
6225
6681
  data,
6226
- requests
6682
+ requests,
6683
+ detailReady
6227
6684
  ]);
6228
6685
  const briefList = React.useMemo(() => data ? briefNodes(data) : [], [data]);
6229
6686
  const convNodes = conversationNodesOf(props);
@@ -6257,13 +6714,27 @@ window.__ModuleLoader__.load({
6257
6714
  activeIdx
6258
6715
  ]);
6259
6716
  const convOf = React.useCallback((seq) => bySeq.get(seq), [bySeq]);
6260
- const fileActivity = React.useMemo(() => activityOf(briefList, convOf, filesBefore), [
6717
+ const fileActivity = React.useMemo(() => {
6718
+ if (data !== null && data.fileOps !== void 0) return activityOfOps(data.fileOps, data.archive, filesBefore);
6719
+ return activityOf(briefList, convOf, filesBefore);
6720
+ }, [
6721
+ data,
6261
6722
  briefList,
6262
6723
  convOf,
6263
6724
  filesBefore
6264
6725
  ]);
6265
6726
  const workspace = typeof ctx.get === "function" ? workspaceOf(ctx, typeof sessionId === "string" ? sessionId : void 0) : void 0;
6266
- const fileOpener = React.useMemo(() => typeof ctx.get === "function" && canOpenPathsOf(ctx) ? openPathVia(ctx) : void 0, [ctx]);
6727
+ const [canOpenPaths, setCanOpenPaths] = React.useState(false);
6728
+ React.useEffect(() => {
6729
+ let live = true;
6730
+ canOpenPathsOf(ctx).then((can) => {
6731
+ if (live) setCanOpenPaths(can);
6732
+ });
6733
+ return () => {
6734
+ live = false;
6735
+ };
6736
+ }, [ctx]);
6737
+ const fileOpener = React.useMemo(() => canOpenPaths ? openPathVia(ctx) : void 0, [canOpenPaths, ctx]);
6267
6738
  const locateFileOp = React.useCallback((op) => {
6268
6739
  const seq = op.parent ?? op.seq;
6269
6740
  const step = locateStepOf(requests, seq, op.gone);
@@ -6325,8 +6796,7 @@ window.__ModuleLoader__.load({
6325
6796
  className: "lc-root",
6326
6797
  ref: rootRef
6327
6798
  }, /* @__PURE__ */ React.createElement("div", { className: "lc-cols lc-head" }, /* @__PURE__ */ React.createElement(StatsContext, {
6328
- requests,
6329
- events,
6799
+ counts: data.counts ?? countsOfRecords(requests, events),
6330
6800
  toolCalls: data.toolCalls,
6331
6801
  images: data.images,
6332
6802
  cost: data.cost,
@@ -6362,7 +6832,10 @@ window.__ModuleLoader__.load({
6362
6832
  onClick: () => {
6363
6833
  setTrendMode("delta");
6364
6834
  }
6365
- }, t("gran.delta"))))), displayRequests.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("trend.empty")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(TrendChart, {
6835
+ }, t("gran.delta"))))), displayRequests.length === 0 ? detailReady ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("trend.empty")) : /* @__PURE__ */ React.createElement(DetailNote, {
6836
+ state: source.detailState === "failed" ? "failed" : "loading",
6837
+ onRetry: source.retryDetail
6838
+ }) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(TrendChart, {
6366
6839
  key: sessionId,
6367
6840
  requests: displayRequests,
6368
6841
  markers,
@@ -6406,19 +6879,27 @@ window.__ModuleLoader__.load({
6406
6879
  onHoverKey: setHoverCat,
6407
6880
  nodeFocus,
6408
6881
  onNodeFocusHandled: clearNodeFocus,
6409
- loadImage
6882
+ loadImage,
6883
+ detailState: source.detailState,
6884
+ onDetailRetry: source.retryDetail
6410
6885
  }))), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__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("events.title")), /* @__PURE__ */ React.createElement("div", { className: "lc-kinds" }, EVENT_KINDS.map((k) => /* @__PURE__ */ React.createElement("button", {
6411
6886
  key: k,
6412
6887
  className: "lc-gran-btn" + (pickedKinds.includes(k) ? " lc-gran-on lc-kind-" + k : ""),
6413
6888
  onClick: () => {
6414
6889
  toggleKind(k);
6415
6890
  }
6416
- }, t("kind." + k))))), /* @__PURE__ */ React.createElement(EventList, { events: shownEvents })), /* @__PURE__ */ React.createElement(FileCard, {
6891
+ }, t("kind." + k))))), /* @__PURE__ */ React.createElement(EventList, {
6892
+ events: shownEvents,
6893
+ state: source.detailState,
6894
+ onRetry: source.retryDetail
6895
+ })), /* @__PURE__ */ React.createElement(FileCard, {
6417
6896
  activity: fileActivity,
6418
6897
  scope: fileScope,
6419
6898
  workspace,
6420
6899
  onOpen: fileOpener,
6421
- onLocate: locateFileOp
6900
+ onLocate: locateFileOp,
6901
+ state: source.detailState,
6902
+ onRetry: source.retryDetail
6422
6903
  })), /* @__PURE__ */ React.createElement(AgentGraph, {
6423
6904
  sessionId: typeof sessionId === "string" ? sessionId : void 0,
6424
6905
  self: {
@@ -6623,7 +7104,7 @@ window.__ModuleLoader__.load({
6623
7104
  t,
6624
7105
  fmt,
6625
7106
  fmtTime,
6626
- fmtDuration: fmtDuration$1,
7107
+ fmtDuration,
6627
7108
  fmtShare,
6628
7109
  catLabel: (key) => t("cat." + key),
6629
7110
  eventLabel,
@@ -6632,7 +7113,7 @@ window.__ModuleLoader__.load({
6632
7113
  }
6633
7114
  //#endregion
6634
7115
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/base.css.mjs
6635
- const css$13 = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-root .lc-card{margin-bottom:7px;padding:7px 8px;container:lc-card/inline-size}.lc-root .lc-cols{gap:7px;margin-bottom:7px}.lc-card-title{flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran{margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3,0 2px 8px #0000002e);pointer-events:none;opacity:0;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:6px 10px;font-size:12px;position:absolute}.lc-events,.lc-fa-list{flex-direction:column;gap:2px;height:320px;display:flex;overflow-y:auto}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:min(280px,100%)}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head{container:lc-head-row/inline-size}.lc-head>.lc-card{flex:1 1 0;min-width:0}@container lc-head-row (width<=1045px){.lc-head>.lc-card{flex-basis:40%}}@container lc-head-row (width<=480px){.lc-head>.lc-card{flex-basis:100%}}.lc-head>.lc-card:first-child{flex-direction:column;display:flex}.lc-head>.lc-card:first-child .lc-stats{flex:1;align-content:stretch}.lc-head>.lc-card:first-child .lc-stat{justify-content:center}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:4px 14px;font-size:12px}.lc-error-retry:hover{border-color:var(--dsw-alias-label-primary)}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])){display:none}";
7116
+ const css$13 = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-root .lc-card{margin-bottom:7px;padding:7px 8px;container:lc-card/inline-size}.lc-root .lc-cols{gap:7px;margin-bottom:7px}.lc-card-title{flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran{margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3,0 2px 8px #0000002e);pointer-events:none;opacity:0;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:6px 10px;font-size:12px;position:absolute}.lc-events,.lc-fa-list{flex-direction:column;gap:2px;height:320px;display:flex;overflow-y:auto}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:min(280px,100%)}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head{container:lc-head-row/inline-size}.lc-head>.lc-card{flex:1 1 0;min-width:0}@container lc-head-row (width<=1045px){.lc-head>.lc-card{flex-basis:40%}}@container lc-head-row (width<=480px){.lc-head>.lc-card{flex-basis:100%}}.lc-head>.lc-card:first-child{flex-direction:column;display:flex}.lc-head>.lc-card:first-child .lc-stats{flex:1;align-content:stretch}.lc-head>.lc-card:first-child .lc-stat{justify-content:center}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:4px 14px;font-size:12px}.lc-error-retry:hover{border-color:var(--dsw-alias-label-primary)}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])),[data-conversation-scroll]:has(.lc-root,.lc-modal-backdrop)~[data-width-handle]{display:none}";
6636
7117
  const tagId$13 = "dsh-context/base.css";
6637
7118
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$13) + "]") === null) {
6638
7119
  const tag = document.createElement("style");
@@ -6794,12 +7275,13 @@ window.__ModuleLoader__.load({
6794
7275
  * context-composition timeline: current makeup, per-request stacked-bar
6795
7276
  * history, context events, and the live message list.
6796
7277
  *
6797
- * Since v0.9 the tab needs no custom data plane: the Host half pushes its
6798
- * fold through the harness's session-projection pipeline
6799
- * (`contextTimeline` projection key), and this half reads the finished value
6800
- * from the framework standard kit (`useProjection('contextTimeline')`, a
6801
- * standard prop on every session-scope slot component). No polling, no RPC,
6802
- * no client-side cache.
7278
+ * Since v0.9 the tab rides the harness's session-projection pipeline
7279
+ * (`contextTimeline` projection key), read from the framework standard kit
7280
+ * (`useProjection('contextTimeline')`, a standard prop on every session-scope
7281
+ * slot component). The wire value is the split generation's slim head; the
7282
+ * heavy collections arrive on demand from the host's detail endpoint, one
7283
+ * read per viewing client (timelineSource.ts). No polling, no stale-while-
7284
+ * revalidate cache.
6803
7285
  *
6804
7286
  * This module is the body of the package's `./client` bundle: tsdown
6805
7287
  * (tsdown.config.ts) bundles it (external `react` — the browser module table