dsh-context 0.56.1 → 0.57.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 (3) hide show
  1. package/lib/client.js +854 -675
  2. package/lib/index.js +41 -4
  3. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -356,6 +356,7 @@ window.__ModuleLoader__.load({
356
356
  "ov.kpi.sessionsSub": "含 {n} 个会话",
357
357
  "ov.kpi.toolSub": "工具执行 {dur}",
358
358
  "ov.kpi.wallSub": "模型调用 {n}",
359
+ "ov.stats.empty": "统计范围内暂无 Token 用量",
359
360
  "balance.title": "DeepSeek 余额",
360
361
  "balance.tip.granted": "赠送余额",
361
362
  "balance.tip.toppedUp": "充值余额",
@@ -735,6 +736,7 @@ window.__ModuleLoader__.load({
735
736
  "ov.kpi.sessionsSub": "across {n} sessions",
736
737
  "ov.kpi.toolSub": "tool runs {dur}",
737
738
  "ov.kpi.wallSub": "{n} model calls",
739
+ "ov.stats.empty": "No billed tokens in this range",
738
740
  "balance.title": "DeepSeek balance",
739
741
  "balance.tip.granted": "Granted balance",
740
742
  "balance.tip.toppedUp": "Topped-up balance",
@@ -1751,6 +1753,31 @@ window.__ModuleLoader__.load({
1751
1753
  }
1752
1754
  };
1753
1755
  }
1756
+ /**
1757
+ * Jump to one session — the harness's own session-selection verb (issue #90).
1758
+ * Every supported line selects through the view owner (`uiWorkspace`
1759
+ * .openSession, the sidebar row click's own verb); the sessions service's own
1760
+ * `open` is the retired spelling (still served through the 0.1.5 line, gone
1761
+ * since the 0.1.6 selection refactor) and stays as the degradation path for a
1762
+ * composition without the workspace module. Both faces are re-proved per call
1763
+ * (a service can land or be revoked across an HMR reload) and the verb is
1764
+ * invoked bound (the service instance reads its own state); a generation
1765
+ * serving neither, or a hostile face, swallows — the jump is best-effort by
1766
+ * nature.
1767
+ */
1768
+ function openSessionVia(ctx, id) {
1769
+ try {
1770
+ const workspace = asRecord(ctx.get("uiWorkspace"));
1771
+ const open = workspace?.openSession;
1772
+ if (typeof open === "function") {
1773
+ open.call(workspace, id);
1774
+ return;
1775
+ }
1776
+ const sessions = asRecord(ctx.get("sessions"));
1777
+ const legacy = sessions?.open;
1778
+ if (typeof legacy === "function") legacy.call(sessions, id);
1779
+ } catch {}
1780
+ }
1754
1781
  //#endregion
1755
1782
  //#region src/client/historyPage.ts
1756
1783
  /**
@@ -6181,6 +6208,105 @@ window.__ModuleLoader__.load({
6181
6208
  totals.total = totals.input + totals.cacheRead + totals.cacheWrite + totals.output;
6182
6209
  return totals;
6183
6210
  }
6211
+ /**
6212
+ * The range's summed timing totals — the aggregate Timing Stats card's
6213
+ * source, folded from the rows' host-folded whole-session totals exactly as
6214
+ * the KPI band folds the scalar figures. Null when no row carries timing.
6215
+ * The additive-optional fields (the decode split, the throughput seat) sum
6216
+ * their carriers only: a row cached before a field existed contributes
6217
+ * nothing to it, and a field no row carries stays absent — the card keeps
6218
+ * its un-split shape / no-chip fallback instead of materialized zeros.
6219
+ */
6220
+ function timingSumOf(rows) {
6221
+ let out = null;
6222
+ for (const row of rows) {
6223
+ const t = row.timeline?.timing;
6224
+ if (t === void 0) continue;
6225
+ if (out === null) {
6226
+ out = {
6227
+ ...t,
6228
+ tools: { ...t.tools }
6229
+ };
6230
+ continue;
6231
+ }
6232
+ out.wallMs += t.wallMs;
6233
+ out.ttftMs += t.ttftMs;
6234
+ out.genMs += t.genMs;
6235
+ out.calls += t.calls;
6236
+ out.toolsMs += t.toolsMs;
6237
+ out.toolCalls += t.toolCalls;
6238
+ addOptionalOf(out, t, "reasoningMs");
6239
+ addOptionalOf(out, t, "reasoningBlocks");
6240
+ addOptionalOf(out, t, "textMs");
6241
+ addOptionalOf(out, t, "textBlocks");
6242
+ addOptionalOf(out, t, "toolArgMs");
6243
+ addOptionalOf(out, t, "toolArgBlocks");
6244
+ addOptionalOf(out, t, "speedTokens");
6245
+ addOptionalOf(out, t, "speedMs");
6246
+ for (const name of Object.keys(t.tools)) {
6247
+ const tool = t.tools[name];
6248
+ const prev = out.tools[name];
6249
+ out.tools[name] = prev === void 0 ? { ...tool } : {
6250
+ calls: prev.calls + tool.calls,
6251
+ ms: prev.ms + tool.ms
6252
+ };
6253
+ }
6254
+ }
6255
+ return out;
6256
+ }
6257
+ /** One additive-optional timing field's in-place merge: absent on both sides stays absent (never a materialized undefined). */
6258
+ function addOptionalOf(target, source, key) {
6259
+ const a = target[key];
6260
+ const b = source[key];
6261
+ if (a !== void 0 || b !== void 0) target[key] = (a ?? 0) + (b ?? 0);
6262
+ }
6263
+ /**
6264
+ * The range's billed tokens split by WHAT they are — the Context tab's Token
6265
+ * card categorization, folded across sessions: each session's
6266
+ * `billedParts` estimate (the composition ratios proportioning that
6267
+ * session's provider-reported prompt total, output exact) sums by category,
6268
+ * and every session's parts total its own billed figure, so the aggregate's
6269
+ * total stays the exact merged billed volume while the split inherits the
6270
+ * per-session card's `≈` estimate convention. Null when no session billed
6271
+ * anything. A non-finite part estimate (a hostile fast-path composition)
6272
+ * drops whole instead of poisoning the sums.
6273
+ */
6274
+ function tokenPartsOf(rows) {
6275
+ const sums = /* @__PURE__ */ new Map();
6276
+ let total = 0;
6277
+ let any = false;
6278
+ for (const row of rows) {
6279
+ const timeline = row.timeline;
6280
+ if (timeline === null) continue;
6281
+ const totals = usageTotalsOf(timeline.cost);
6282
+ if (totals === null) continue;
6283
+ any = true;
6284
+ const usage = {
6285
+ uncachedInputTokens: totals.input,
6286
+ cacheReadTokens: totals.cacheRead,
6287
+ cacheWriteTokens: totals.cacheWrite,
6288
+ outputTokens: totals.output
6289
+ };
6290
+ for (const part of billedParts(timeline.current, null, usage)) {
6291
+ if (!Number.isFinite(part.value) || part.value <= 0) continue;
6292
+ total += part.value;
6293
+ const prev = sums.get(part.key);
6294
+ sums.set(part.key, prev === void 0 ? {
6295
+ key: part.key,
6296
+ color: part.color,
6297
+ value: part.value
6298
+ } : {
6299
+ ...prev,
6300
+ value: prev.value + part.value
6301
+ });
6302
+ }
6303
+ }
6304
+ if (!any) return null;
6305
+ return {
6306
+ parts: [...sums.values()],
6307
+ total
6308
+ };
6309
+ }
6184
6310
  function kpisOf(rows, listed, prices, currency) {
6185
6311
  const usage = mergeCostUsage(...rows.map((row) => row.timeline?.cost));
6186
6312
  const totals = usageTotalsOf(usage);
@@ -6214,7 +6340,9 @@ window.__ModuleLoader__.load({
6214
6340
  toolCalls,
6215
6341
  toolsMs,
6216
6342
  calls,
6217
- wallMs
6343
+ wallMs,
6344
+ tokenParts: tokenPartsOf(rows),
6345
+ timing: timingSumOf(rows)
6218
6346
  };
6219
6347
  }
6220
6348
  /**
@@ -6248,18 +6376,6 @@ window.__ModuleLoader__.load({
6248
6376
  return days;
6249
6377
  }
6250
6378
  /**
6251
- * Jump to one session: the harness's own selection verb (`sessions.open`,
6252
- * the sidebar row click's mechanism). The face is re-proved per call and a
6253
- * hostile or absent service swallows silently — the panel still closes, so
6254
- * the gesture never dead-ends on an error.
6255
- */
6256
- function openSession(ctx, id) {
6257
- try {
6258
- const sessions = ctx.get("sessions");
6259
- if (sessions !== void 0 && typeof sessions.open === "function") sessions.open(id);
6260
- } catch {}
6261
- }
6262
- /**
6263
6379
  * Re-pull the session-list baseline so host-side projection backfills
6264
6380
  * (host/backfill.ts) reach a long-connected browser. Fire-and-forget: the
6265
6381
  * verb is re-proved and every failure swallows — the panel renders off the
@@ -6926,206 +7042,512 @@ window.__ModuleLoader__.load({
6926
7042
  };
6927
7043
  }
6928
7044
  //#endregion
6929
- //#region src/client/components/overviewCard.tsx
6930
- function makeOverviewCard(kit) {
6931
- const { t } = kit;
6932
- const Donut = makeDonut(kit);
6933
- return function OverviewCard(props) {
6934
- const { row } = props;
6935
- const timeline = row.timeline;
6936
- const billed = billedOf(timeline);
6937
- const occupancy = timeline !== null && typeof timeline.contextWindow === "number" && timeline.contextWindow > 0 ? fmtShare(timeline.current.total, timeline.contextWindow) : null;
6938
- const turns = turnsOf(timeline);
6939
- const steps = timeline?.counts?.steps;
6940
- const turnsLabel = steps === void 0 ? String(turns) : t("ov.card.turns", {
6941
- n: turns,
6942
- s: steps
6943
- });
6944
- const project = projectOf(row.cwd);
6945
- const group = props.group !== void 0 && props.group !== project ? props.group : void 0;
6946
- const created = createdDayOf(row.activity);
6947
- const lastUser = timeline?.lastUser;
6948
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
6949
- type: "button",
6950
- className: "lc-ov-session" + (row.current ? " lc-ov-session-current" : ""),
6951
- onClick: () => {
6952
- props.onOpen(row.id);
7045
+ //#region src/client/components/sliceList.tsx
7046
+ /**
7047
+ * The stats cards' slice rows: the donut's legend with numbers, two tiers
7048
+ * per slice — the primary line pairs the color dot and the name with the
7049
+ * bold share closing a fixed right column, and the secondary line drops the
7050
+ * absolute quantity (plus a qualifier like the call count) under the name in
7051
+ * muted small print. Hovering a row lights it and its donut segment together
7052
+ * (the shared hover key), and vice versa. No tracks or bars — the donut IS
7053
+ * the proportion chart; the rows are its legend. Preformatted strings in,
7054
+ * dumb markup out.
7055
+ */
7056
+ function makeSliceList(kit) {
7057
+ return function SliceList(props) {
7058
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7059
+ className: "lc-sl flex-auto @max-[240px]/lc-card:basis-full",
7060
+ onMouseLeave: () => {
7061
+ if (props.onHoverKey !== void 0) props.onHoverKey(null);
6953
7062
  },
6954
- children: [
6955
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6956
- className: "lc-ov-session-head",
7063
+ children: props.rows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7064
+ className: "lc-sl-row" + (r.dim ? " lc-sl-row-dim" : "") + (props.hoverKey !== void 0 && props.hoverKey === r.key ? " lc-sl-row-on" : ""),
7065
+ onMouseEnter: () => {
7066
+ if (props.onHoverKey !== void 0) props.onHoverKey(r.key);
7067
+ },
7068
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7069
+ className: "lc-sl-main",
6957
7070
  children: [
6958
- row.running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6959
- className: "lc-ov-running",
6960
- title: t("ov.running")
7071
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
7072
+ className: "lc-sl-dot",
7073
+ style: { background: r.color }
6961
7074
  }),
6962
7075
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6963
- className: "lc-ov-session-title",
6964
- title: row.title,
6965
- children: row.title
6966
- }),
6967
- row.current && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6968
- className: "lc-ov-current",
6969
- children: t("ov.current")
6970
- }),
6971
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6972
- className: "lc-ov-session-times",
6973
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6974
- className: "lc-ov-session-time",
6975
- children: relativeTime(t, row.updatedAt, props.now)
6976
- }), created !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6977
- className: "lc-ov-session-created",
6978
- children: created
6979
- })]
6980
- })
6981
- ]
6982
- }),
6983
- (group !== void 0 || project !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
6984
- className: "lc-ov-session-crumb",
6985
- title: row.cwd,
6986
- children: [
6987
- group !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6988
- className: "lc-ov-crumb-group",
6989
- children: group
6990
- }),
6991
- group !== void 0 && project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6992
- className: "lc-ov-crumb-sep",
6993
- children: "/"
7076
+ className: "lc-sl-label",
7077
+ title: r.label,
7078
+ children: r.label
6994
7079
  }),
6995
- project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6996
- className: "lc-ov-crumb-project",
6997
- children: project
7080
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7081
+ className: "lc-sl-pct",
7082
+ children: r.pct
6998
7083
  })
6999
7084
  ]
7000
- }),
7001
- timeline === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7002
- className: "lc-ov-session-empty",
7003
- children: t("ov.list.noData")
7004
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7005
- className: "lc-ov-session-body",
7006
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
7007
- size: 64,
7008
- segments: partsOf(timeline.current),
7009
- centerTop: fmt(timeline.current.total),
7010
- centerSub: occupancy ?? void 0
7011
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7012
- className: "lc-ov-mini-stats",
7013
- children: [
7014
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7015
- className: "lc-ov-mini-stat",
7016
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7017
- className: "lc-ov-mini-label",
7018
- children: t("stats.turns")
7019
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7020
- className: "lc-ov-mini-value",
7021
- children: turnsLabel
7022
- })]
7023
- }),
7024
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7025
- className: "lc-ov-mini-stat",
7026
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7027
- className: "lc-ov-mini-label",
7028
- children: t("tokens.total")
7029
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7030
- className: "lc-ov-mini-value",
7031
- children: billed === null ? "—" : fmt(billed)
7032
- })]
7033
- }),
7034
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7035
- className: "lc-ov-mini-stat",
7036
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7037
- className: "lc-ov-mini-label",
7038
- children: t("stats.cost")
7039
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7040
- className: "lc-ov-mini-value",
7041
- children: props.costLabel
7042
- })]
7043
- })
7044
- ]
7045
- })]
7046
- }),
7047
- lastUser !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7048
- className: "lc-ov-session-preview",
7049
- title: lastUser,
7050
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7051
- className: "lc-ov-session-preview-label",
7052
- children: t("ov.card.lastUser")
7053
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7054
- className: "lc-ov-session-preview-text",
7055
- children: lastUser
7056
- })]
7057
- })
7058
- ]
7085
+ }), r.count !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7086
+ className: "lc-sl-sub",
7087
+ title: r.count,
7088
+ children: r.count
7089
+ }) : null]
7090
+ }, r.key))
7059
7091
  });
7060
7092
  };
7061
7093
  }
7062
7094
  //#endregion
7063
- //#region src/client/components/overviewPanel.tsx
7095
+ //#region src/client/components/overviewTokens.tsx
7064
7096
  /**
7065
- * The Context Dashboard panel — the cross-session insight surface opened
7066
- * from the sidebar foot (overviewButton.tsx). Rendered from the frame-wide
7067
- * `shell.overlay` slot behind the module store's flag; data rides the
7068
- * root-scope `useSessions` standard kit (every list row's host-cached
7069
- * projection values), so the panel draws every session's insight without
7070
- * opening one log.
7071
- *
7072
- * The body is a 3:7 column pair: the insight column (the KPI 2×3 block over
7073
- * the activity heatmap, then the preferences entry row) beside the session
7074
- * column (search, group chips, and the card grid); the heatmap keeps its own
7075
- * fixed 8-week window and PINs the list to a picked day (the panel's
7076
- * drill-down gesture). A session card click jumps to that session through
7077
- * the harness's own `sessions.open` and closes the panel.
7097
+ * The Context Insights panel's aggregate Token Stats card — the first row's
7098
+ * token half, categorized exactly like the Context tab's Token card
7099
+ * (statsTokens.tsx): the range's billed volume split by WHAT the tokens are —
7100
+ * the composition categories' `≈` estimates proportioning each session's
7101
+ * provider-reported prompt total, plus the exact output — folded by
7102
+ * tokenPartsOf. The center figure stays the exact merged billed total the
7103
+ * KPI band's Tokens Used cell shows.
7078
7104
  */
7079
- const RANGES = [
7080
- "24h",
7081
- "7d",
7082
- "30d",
7083
- "all"
7084
- ];
7085
- const SORTS = [
7086
- "recent",
7087
- "tokens",
7088
- "context"
7089
- ];
7090
- /** The heatmap's depth metrics, in toggle order (steps is the default). */
7091
- const METRICS = ["sessions", "steps"];
7092
- function makeOverviewPanel(ctx, kit) {
7093
- const { t, fmtDuration } = kit;
7094
- const Heatmap = makeHeatmap(kit);
7095
- const OverviewCard = makeOverviewCard(kit);
7096
- const BalanceCapsule = makeBalanceCapsule(ctx, kit);
7097
- const ErrorBoundary = makeErrorBoundary(t);
7098
- /** The display currency follows the active locale (zh → CNY), read per render — the slot outlet re-renders on a locale switch. */
7099
- function activeCurrency() {
7100
- const locale = ctx.locale;
7101
- return (typeof locale.getLocale === "function" ? locale.getLocale().active : "en") === "zh" ? "cny" : "usd";
7102
- }
7103
- function OverviewBody(props) {
7104
- const open = (0, react.useSyncExternalStore)(overviewStore.subscribe, overviewStore.getSnapshot);
7105
- const { prices } = useModelPrices();
7106
- const snapshot = sessionsSnapshotOf(props);
7107
- const wsSnapshot = workspacesSnapshotOf(props);
7108
- const [range, setRange] = (0, react.useState)("30d");
7109
- const [day, setDay] = (0, react.useState)(null);
7110
- const [query, setQuery] = (0, react.useState)("");
7111
- const [group, setGroup] = (0, react.useState)(null);
7112
- const [sort, setSort] = (0, react.useState)("recent");
7113
- const [metric, setMetric] = (0, react.useState)("steps");
7114
- const [page, setPage] = (0, react.useState)(0);
7115
- const close = () => {
7116
- overviewStore.set(false);
7117
- };
7118
- useEscapeClose(open, close);
7119
- const rows = (0, react.useMemo)(() => rowsOfSnapshot(snapshot, wsSnapshot), [snapshot, wsSnapshot]);
7120
- const groups = (0, react.useMemo)(() => sessionGroupsOf(wsSnapshot), [wsSnapshot]);
7121
- (0, react.useEffect)(() => {
7122
- if (open) {
7123
- requestActivityBackfill();
7124
- refreshSessions(ctx);
7125
- }
7126
- }, [open]);
7127
- (0, react.useEffect)(() => {
7128
- setPage(0);
7105
+ function makeOverviewTokens(kit, Donut) {
7106
+ const { t, fmt, fmtShare, catLabel } = kit;
7107
+ const SliceList = makeSliceList(kit);
7108
+ return function OverviewTokens(props) {
7109
+ const [hoverKey, setHoverKey] = (0, react.useState)(null);
7110
+ const tokens = props.tokens;
7111
+ const total = tokens?.total ?? 0;
7112
+ const shown = tokens !== null && total > 0 ? tokens.parts.filter((p) => p.value > 0) : [];
7113
+ const rows = shown.map((p) => ({
7114
+ key: p.key,
7115
+ color: p.color,
7116
+ label: p.key === "output" ? t("tokens.output") : catLabel(p.key),
7117
+ pct: fmtShare(p.value, total),
7118
+ count: p.key === "output" ? `${fmt(p.value)} · ${t("tokens.outputNote")}` : "≈" + fmt(p.value)
7119
+ }));
7120
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7121
+ className: "lc-card lc-col-stats lc-col-donut flex-1 min-w-[min(360px,100%)]",
7122
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7123
+ className: "lc-card-title",
7124
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7125
+ className: "lc-card-title-text",
7126
+ children: t("tokens.title")
7127
+ })
7128
+ }), shown.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7129
+ className: "lc-empty",
7130
+ children: t("ov.stats.empty")
7131
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7132
+ className: "lc-donut-row flex items-center justify-start gap-3 min-w-0 @max-[320px]/lc-card:gap-2 @max-[240px]/lc-card:flex-wrap",
7133
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
7134
+ segments: shown.map((p) => ({
7135
+ key: p.key,
7136
+ color: p.color,
7137
+ value: p.value
7138
+ })),
7139
+ size: 96,
7140
+ centerTop: fmt(total),
7141
+ centerSub: t("tokens.total"),
7142
+ hoverKey,
7143
+ onHoverKey: setHoverKey
7144
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliceList, {
7145
+ rows,
7146
+ hoverKey,
7147
+ onHoverKey: setHoverKey
7148
+ })]
7149
+ })]
7150
+ });
7151
+ };
7152
+ }
7153
+ //#endregion
7154
+ //#region src/client/components/statsTiming.tsx
7155
+ /**
7156
+ * The Timing card: where the session's ACTIVE time went. The donut splits
7157
+ * whole-step wall time into the model-call slices — TTFT (step start → first
7158
+ * token, the wait) and LLM generation (first token → assistant message) —
7159
+ * and the tool-execution slice, with the residue as overhead; a call whose
7160
+ * stream recorded no token delta (legacy or aborted) stays unattributed and
7161
+ * lands in the residue. When the host folded the generation split (see
7162
+ * TimingTotals), the generation slice expands into what was being decoded:
7163
+ * thinking, answer text, and tool-call arguments. The slice rows lead with
7164
+ * the true duration and qualify it with a tally on the secondary line — the
7165
+ * model calls (TTFT), the tool runs, and the decode slices' own BLOCK counts
7166
+ * (zero to many per call, so a call count would be a false tally there). A
7167
+ * slice that never happened (zero time) is omitted entirely — the ring already skips
7168
+ * its arc, and a zero row would otherwise borrow the session's call count and
7169
+ * read as "no time, many calls". The donut and the rows sit side by side so
7170
+ * the head row stays half-height. Parallel tool calls each count, so the tools
7171
+ * figure can overlap — the ring clamps it into the post-model window while the
7172
+ * row numbers stay true.
7173
+ */
7174
+ const COLOR = {
7175
+ ttft: "var(--color-blue-500)",
7176
+ reasoning: "var(--color-violet-500)",
7177
+ text: "var(--color-pink-500)",
7178
+ toolarg: "var(--color-amber-500)",
7179
+ tools: "var(--color-teal-500)",
7180
+ other: "var(--color-slate-400)"
7181
+ };
7182
+ /**
7183
+ * The decode-throughput figure, formatted exactly as the harness's own
7184
+ * session-stats seat formats it: whole tokens from ten up, one decimal below.
7185
+ */
7186
+ function formatTokensPerSecond(tps) {
7187
+ const clamped = Math.max(0, tps);
7188
+ return clamped >= 10 ? String(Math.round(clamped)) : String(Math.round(clamped * 10) / 10);
7189
+ }
7190
+ function makeStatsTiming(kit, Donut) {
7191
+ const { t, fmt, fmtDuration, fmtShare } = kit;
7192
+ const SliceList = makeSliceList(kit);
7193
+ return function StatsTiming(props) {
7194
+ const [hoverKey, setHoverKey] = (0, react.useState)(null);
7195
+ const timing = props.timing;
7196
+ const wall = timing !== null && Number.isFinite(timing.wallMs) && timing.wallMs > 0 ? timing.wallMs : 0;
7197
+ const tps = timing !== null && typeof timing.speedMs === "number" && Number.isFinite(timing.speedMs) && timing.speedMs > 0 && typeof timing.speedTokens === "number" && Number.isFinite(timing.speedTokens) && timing.speedTokens >= 0 ? formatTokensPerSecond(timing.speedTokens / (timing.speedMs / 1e3)) : null;
7198
+ let segments = [];
7199
+ let rows = [];
7200
+ if (timing !== null && (wall > 0 || timing.calls > 0 || timing.toolCalls > 0)) {
7201
+ const callTimes = t("timing.callTimes", { n: fmt(timing.calls) });
7202
+ const modelSlices = [{
7203
+ key: "ttft",
7204
+ color: COLOR.ttft,
7205
+ label: t("timing.ttft"),
7206
+ ms: timing.ttftMs,
7207
+ times: callTimes
7208
+ }];
7209
+ const blockTimes = (blocks) => blocks !== void 0 && blocks > 0 ? t("timing.blockTimes", { n: fmt(blocks) }) : void 0;
7210
+ const buckets = [
7211
+ [
7212
+ "reasoning",
7213
+ COLOR.reasoning,
7214
+ t("timing.reasoning"),
7215
+ timing.reasoningMs ?? 0,
7216
+ blockTimes(timing.reasoningBlocks)
7217
+ ],
7218
+ [
7219
+ "text",
7220
+ COLOR.text,
7221
+ t("timing.text"),
7222
+ timing.textMs ?? 0,
7223
+ blockTimes(timing.textBlocks)
7224
+ ],
7225
+ [
7226
+ "toolarg",
7227
+ COLOR.toolarg,
7228
+ t("timing.toolArgs"),
7229
+ timing.toolArgMs ?? 0,
7230
+ blockTimes(timing.toolArgBlocks)
7231
+ ]
7232
+ ];
7233
+ if (buckets.some(([, , , ms]) => ms > 0)) for (const [key, color, label, ms, times] of buckets) modelSlices.push({
7234
+ key,
7235
+ color,
7236
+ label,
7237
+ ms,
7238
+ ...times !== void 0 ? { times } : {}
7239
+ });
7240
+ else modelSlices.push({
7241
+ key: "gen",
7242
+ color: COLOR.reasoning,
7243
+ label: t("timing.gen"),
7244
+ ms: timing.genMs
7245
+ });
7246
+ const ttftRing = Math.min(timing.ttftMs, wall);
7247
+ const genRing = Math.max(0, Math.min(timing.genMs, wall - ttftRing));
7248
+ const modelRings = [ttftRing];
7249
+ let used = ttftRing;
7250
+ const genEnd = ttftRing + genRing;
7251
+ for (const slice of modelSlices.slice(1)) {
7252
+ const take = Math.max(0, Math.min(slice.ms, genEnd - used));
7253
+ modelRings.push(take);
7254
+ used += take;
7255
+ }
7256
+ const toolRing = Math.max(0, Math.min(timing.toolsMs, wall - ttftRing - genRing));
7257
+ const other = Math.max(0, wall - ttftRing - genRing - toolRing);
7258
+ const share = (ms) => wall > 0 ? ms / wall : 0;
7259
+ const countOf = (ms, times) => {
7260
+ const dur = fmtDuration(ms);
7261
+ if (times === void 0) return dur;
7262
+ return ms > 0 ? `${dur} · ${times}` : times;
7263
+ };
7264
+ const modelSegments = modelSlices.map((slice, index) => ({
7265
+ key: slice.key,
7266
+ color: slice.color,
7267
+ value: share(modelRings[index])
7268
+ }));
7269
+ const toolSlice = {
7270
+ key: "tools",
7271
+ color: COLOR.tools,
7272
+ label: t("timing.tools"),
7273
+ ms: timing.toolsMs,
7274
+ times: t("timing.toolTimes", { n: fmt(timing.toolCalls) })
7275
+ };
7276
+ const otherSlice = {
7277
+ key: "other",
7278
+ color: COLOR.other,
7279
+ label: t("timing.other"),
7280
+ ms: other
7281
+ };
7282
+ segments = [
7283
+ ...modelSegments,
7284
+ {
7285
+ key: "tools",
7286
+ color: COLOR.tools,
7287
+ value: share(toolRing)
7288
+ },
7289
+ {
7290
+ key: "other",
7291
+ color: COLOR.other,
7292
+ value: share(other)
7293
+ }
7294
+ ];
7295
+ const toRow = (slice) => ({
7296
+ key: slice.key,
7297
+ color: slice.color,
7298
+ label: slice.label,
7299
+ dim: slice.ms === 0,
7300
+ pct: fmtShare(slice.ms, wall),
7301
+ count: countOf(slice.ms, slice.times)
7302
+ });
7303
+ rows = [
7304
+ ...modelSlices.map(toRow),
7305
+ toRow(toolSlice),
7306
+ toRow(otherSlice)
7307
+ ].filter((row) => !row.dim);
7308
+ }
7309
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7310
+ className: "lc-card lc-col-stats lc-col-donut flex-1 min-w-[min(360px,100%)]",
7311
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7312
+ className: "lc-card-title",
7313
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7314
+ className: "lc-card-title-text",
7315
+ children: t("timing.title")
7316
+ }), tps !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7317
+ className: "lc-timing-tps",
7318
+ title: t("timing.tpsTip"),
7319
+ children: t("timing.tps", { tps })
7320
+ })]
7321
+ }), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7322
+ className: "lc-empty",
7323
+ children: t("timing.empty")
7324
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7325
+ className: "lc-donut-row flex items-center justify-start gap-3 min-w-0 @max-[320px]/lc-card:gap-2 @max-[240px]/lc-card:flex-wrap",
7326
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
7327
+ segments,
7328
+ size: 96,
7329
+ centerTop: wall > 0 ? fmtDuration(wall) : "—",
7330
+ centerSub: t("timing.total"),
7331
+ hoverKey,
7332
+ onHoverKey: setHoverKey
7333
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliceList, {
7334
+ rows,
7335
+ hoverKey,
7336
+ onHoverKey: setHoverKey
7337
+ })]
7338
+ })]
7339
+ });
7340
+ };
7341
+ }
7342
+ //#endregion
7343
+ //#region src/client/components/overviewCard.tsx
7344
+ function makeOverviewCard(kit) {
7345
+ const { t } = kit;
7346
+ const Donut = makeDonut(kit);
7347
+ return function OverviewCard(props) {
7348
+ const { row } = props;
7349
+ const timeline = row.timeline;
7350
+ const billed = billedOf(timeline);
7351
+ const occupancy = timeline !== null && typeof timeline.contextWindow === "number" && timeline.contextWindow > 0 ? fmtShare(timeline.current.total, timeline.contextWindow) : null;
7352
+ const turns = turnsOf(timeline);
7353
+ const steps = timeline?.counts?.steps;
7354
+ const turnsLabel = steps === void 0 ? String(turns) : t("ov.card.turns", {
7355
+ n: turns,
7356
+ s: steps
7357
+ });
7358
+ const project = projectOf(row.cwd);
7359
+ const group = props.group !== void 0 && props.group !== project ? props.group : void 0;
7360
+ const created = createdDayOf(row.activity);
7361
+ const lastUser = timeline?.lastUser;
7362
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7363
+ type: "button",
7364
+ className: "lc-ov-session" + (row.current ? " lc-ov-session-current" : ""),
7365
+ onClick: () => {
7366
+ props.onOpen(row.id);
7367
+ },
7368
+ children: [
7369
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7370
+ className: "lc-ov-session-head",
7371
+ children: [
7372
+ row.running && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7373
+ className: "lc-ov-running",
7374
+ title: t("ov.running")
7375
+ }),
7376
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7377
+ className: "lc-ov-session-title",
7378
+ title: row.title,
7379
+ children: row.title
7380
+ }),
7381
+ row.current && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7382
+ className: "lc-ov-current",
7383
+ children: t("ov.current")
7384
+ }),
7385
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7386
+ className: "lc-ov-session-times",
7387
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7388
+ className: "lc-ov-session-time",
7389
+ children: relativeTime(t, row.updatedAt, props.now)
7390
+ }), created !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7391
+ className: "lc-ov-session-created",
7392
+ children: created
7393
+ })]
7394
+ })
7395
+ ]
7396
+ }),
7397
+ (group !== void 0 || project !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7398
+ className: "lc-ov-session-crumb",
7399
+ title: row.cwd,
7400
+ children: [
7401
+ group !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7402
+ className: "lc-ov-crumb-group",
7403
+ children: group
7404
+ }),
7405
+ group !== void 0 && project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7406
+ className: "lc-ov-crumb-sep",
7407
+ children: "/"
7408
+ }),
7409
+ project !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7410
+ className: "lc-ov-crumb-project",
7411
+ children: project
7412
+ })
7413
+ ]
7414
+ }),
7415
+ timeline === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7416
+ className: "lc-ov-session-empty",
7417
+ children: t("ov.list.noData")
7418
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7419
+ className: "lc-ov-session-body",
7420
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
7421
+ size: 64,
7422
+ segments: partsOf(timeline.current),
7423
+ centerTop: fmt(timeline.current.total),
7424
+ centerSub: occupancy ?? void 0
7425
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7426
+ className: "lc-ov-mini-stats",
7427
+ children: [
7428
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7429
+ className: "lc-ov-mini-stat",
7430
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7431
+ className: "lc-ov-mini-label",
7432
+ children: t("stats.turns")
7433
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7434
+ className: "lc-ov-mini-value",
7435
+ children: turnsLabel
7436
+ })]
7437
+ }),
7438
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7439
+ className: "lc-ov-mini-stat",
7440
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7441
+ className: "lc-ov-mini-label",
7442
+ children: t("tokens.total")
7443
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7444
+ className: "lc-ov-mini-value",
7445
+ children: billed === null ? "—" : fmt(billed)
7446
+ })]
7447
+ }),
7448
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7449
+ className: "lc-ov-mini-stat",
7450
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7451
+ className: "lc-ov-mini-label",
7452
+ children: t("stats.cost")
7453
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7454
+ className: "lc-ov-mini-value",
7455
+ children: props.costLabel
7456
+ })]
7457
+ })
7458
+ ]
7459
+ })]
7460
+ }),
7461
+ lastUser !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7462
+ className: "lc-ov-session-preview",
7463
+ title: lastUser,
7464
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7465
+ className: "lc-ov-session-preview-label",
7466
+ children: t("ov.card.lastUser")
7467
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7468
+ className: "lc-ov-session-preview-text",
7469
+ children: lastUser
7470
+ })]
7471
+ })
7472
+ ]
7473
+ });
7474
+ };
7475
+ }
7476
+ //#endregion
7477
+ //#region src/client/components/overviewPanel.tsx
7478
+ /**
7479
+ * The Context Dashboard panel — the cross-session insight surface opened
7480
+ * from the sidebar foot (overviewButton.tsx). Rendered from the frame-wide
7481
+ * `shell.overlay` slot behind the module store's flag; data rides the
7482
+ * root-scope `useSessions` standard kit (every list row's host-cached
7483
+ * projection values), so the panel draws every session's insight without
7484
+ * opening one log.
7485
+ *
7486
+ * The panel's first row is the KPI metrics band: the range's six figures
7487
+ * (sessions, billed tokens, cost, cache hit, tool calls, active time) in one
7488
+ * full-width line. Below it, the aggregate stats row folds the same range's
7489
+ * sessions into the two donut cards the per-session Context tab opens with —
7490
+ * Token Stats (the composition-split billed volume) and Timing Stats (the
7491
+ * summed totals). The body is then a 3:7 column pair: the insight column (the
7492
+ * activity heatmap, then the preferences entry row) beside the session column
7493
+ * (search, group chips, and the card grid); the heatmap keeps its own fixed
7494
+ * 8-week window and PINs the list to a picked day (the panel's drill-down
7495
+ * gesture). A session card click jumps to that session through the harness's
7496
+ * own selection verb (openSessionVia) and closes the panel.
7497
+ */
7498
+ const RANGES = [
7499
+ "24h",
7500
+ "7d",
7501
+ "30d",
7502
+ "all"
7503
+ ];
7504
+ const SORTS = [
7505
+ "recent",
7506
+ "tokens",
7507
+ "context"
7508
+ ];
7509
+ /** The heatmap's depth metrics, in toggle order (steps is the default). */
7510
+ const METRICS = ["sessions", "steps"];
7511
+ function makeOverviewPanel(ctx, kit) {
7512
+ const { t, fmtDuration } = kit;
7513
+ const Heatmap = makeHeatmap(kit);
7514
+ const OverviewCard = makeOverviewCard(kit);
7515
+ const BalanceCapsule = makeBalanceCapsule(ctx, kit);
7516
+ const Donut = makeDonut(kit);
7517
+ const OverviewTokens = makeOverviewTokens(kit, Donut);
7518
+ const StatsTiming = makeStatsTiming(kit, Donut);
7519
+ const ErrorBoundary = makeErrorBoundary(t);
7520
+ /** The display currency follows the active locale (zh → CNY), read per render — the slot outlet re-renders on a locale switch. */
7521
+ function activeCurrency() {
7522
+ const locale = ctx.locale;
7523
+ return (typeof locale.getLocale === "function" ? locale.getLocale().active : "en") === "zh" ? "cny" : "usd";
7524
+ }
7525
+ function OverviewBody(props) {
7526
+ const open = (0, react.useSyncExternalStore)(overviewStore.subscribe, overviewStore.getSnapshot);
7527
+ const { prices } = useModelPrices();
7528
+ const snapshot = sessionsSnapshotOf(props);
7529
+ const wsSnapshot = workspacesSnapshotOf(props);
7530
+ const [range, setRange] = (0, react.useState)("30d");
7531
+ const [day, setDay] = (0, react.useState)(null);
7532
+ const [query, setQuery] = (0, react.useState)("");
7533
+ const [group, setGroup] = (0, react.useState)(null);
7534
+ const [sort, setSort] = (0, react.useState)("recent");
7535
+ const [metric, setMetric] = (0, react.useState)("steps");
7536
+ const [page, setPage] = (0, react.useState)(0);
7537
+ const close = () => {
7538
+ overviewStore.set(false);
7539
+ };
7540
+ useEscapeClose(open, close);
7541
+ const rows = (0, react.useMemo)(() => rowsOfSnapshot(snapshot, wsSnapshot), [snapshot, wsSnapshot]);
7542
+ const groups = (0, react.useMemo)(() => sessionGroupsOf(wsSnapshot), [wsSnapshot]);
7543
+ (0, react.useEffect)(() => {
7544
+ if (open) {
7545
+ requestActivityBackfill();
7546
+ refreshSessions(ctx);
7547
+ }
7548
+ }, [open]);
7549
+ (0, react.useEffect)(() => {
7550
+ setPage(0);
7129
7551
  }, [
7130
7552
  range,
7131
7553
  day,
@@ -7157,7 +7579,7 @@ window.__ModuleLoader__.load({
7157
7579
  const kpi = kpisOf(ranged, allRows.length, prices, currency);
7158
7580
  const days = aggregateDays(allRows);
7159
7581
  const openOne = (id) => {
7160
- openSession(ctx, id);
7582
+ openSessionVia(ctx, id);
7161
7583
  overviewStore.set(false);
7162
7584
  };
7163
7585
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -7204,123 +7626,127 @@ window.__ModuleLoader__.load({
7204
7626
  }), rows === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7205
7627
  className: "lc-empty",
7206
7628
  children: t("ov.unavailable")
7207
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7208
- className: "lc-ov-body",
7209
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7210
- className: "lc-ov-left",
7629
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7630
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7631
+ className: "lc-ov-kpis",
7211
7632
  children: [
7212
7633
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7213
- className: "lc-ov-kpis",
7634
+ className: "lc-stat lc-ov-kpi",
7214
7635
  children: [
7215
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7216
- className: "lc-stat lc-ov-kpi",
7217
- children: [
7218
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7219
- className: "lc-stat-label",
7220
- children: t("ov.kpi.sessions")
7221
- }),
7222
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7223
- className: "lc-stat-value",
7224
- children: kpi.sessions
7225
- }),
7226
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7227
- className: "lc-stat-sub",
7228
- children: t("ov.kpi.ofTotal", { n: kpi.listed })
7229
- })
7230
- ]
7636
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7637
+ className: "lc-stat-label",
7638
+ children: t("ov.kpi.sessions")
7231
7639
  }),
7232
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7233
- className: "lc-stat lc-ov-kpi",
7234
- children: [
7235
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7236
- className: "lc-stat-label",
7237
- children: t("ov.kpi.tokens")
7238
- }),
7239
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7240
- className: "lc-stat-value",
7241
- children: fmt(kpi.tokens)
7242
- }),
7243
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7244
- className: "lc-stat-sub",
7245
- children: [
7246
- t("stats.turns"),
7247
- " ",
7248
- fmt(kpi.turns)
7249
- ]
7250
- })
7251
- ]
7640
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7641
+ className: "lc-stat-value",
7642
+ children: kpi.sessions
7252
7643
  }),
7253
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7254
- className: "lc-stat lc-ov-kpi",
7255
- children: [
7256
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7257
- className: "lc-stat-label",
7258
- children: t("stats.cost")
7259
- }),
7260
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7261
- className: "lc-stat-value",
7262
- children: kpi.cost === null ? "—" : formatCost(kpi.cost, currency)
7263
- }),
7264
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7265
- className: "lc-stat-sub",
7266
- children: t("ov.kpi.sessionsSub", { n: kpi.costSessions })
7267
- })
7268
- ]
7644
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7645
+ className: "lc-stat-sub",
7646
+ children: t("ov.kpi.ofTotal", { n: kpi.listed })
7647
+ })
7648
+ ]
7649
+ }),
7650
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7651
+ className: "lc-stat lc-ov-kpi",
7652
+ children: [
7653
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7654
+ className: "lc-stat-label",
7655
+ children: t("ov.kpi.tokens")
7269
7656
  }),
7270
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7271
- className: "lc-stat lc-ov-kpi",
7272
- children: [
7273
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7274
- className: "lc-stat-label",
7275
- children: t("stats.cacheHit")
7276
- }),
7277
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7278
- className: "lc-stat-value",
7279
- children: kpi.cacheHit === null ? "—" : kpi.cacheHit + "%"
7280
- }),
7281
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7282
- className: "lc-stat-sub",
7283
- children: t("ov.kpi.sessionsSub", { n: kpi.usageSessions })
7284
- })
7285
- ]
7657
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7658
+ className: "lc-stat-value",
7659
+ children: fmt(kpi.tokens)
7286
7660
  }),
7287
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7288
- className: "lc-stat lc-ov-kpi",
7661
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7662
+ className: "lc-stat-sub",
7289
7663
  children: [
7290
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7291
- className: "lc-stat-label",
7292
- children: t("stats.toolCalls")
7293
- }),
7294
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7295
- className: "lc-stat-value",
7296
- children: fmt(kpi.toolCalls)
7297
- }),
7298
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7299
- className: "lc-stat-sub",
7300
- children: t("ov.kpi.toolSub", { dur: fmtDuration(kpi.toolsMs) })
7301
- })
7664
+ t("stats.turns"),
7665
+ " ",
7666
+ fmt(kpi.turns)
7302
7667
  ]
7668
+ })
7669
+ ]
7670
+ }),
7671
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7672
+ className: "lc-stat lc-ov-kpi",
7673
+ children: [
7674
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7675
+ className: "lc-stat-label",
7676
+ children: t("stats.cost")
7303
7677
  }),
7304
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7305
- className: "lc-stat lc-ov-kpi",
7306
- children: [
7307
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7308
- className: "lc-stat-label",
7309
- children: t("timing.total")
7310
- }),
7311
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7312
- className: "lc-stat-value",
7313
- children: fmtDuration(kpi.wallMs)
7314
- }),
7315
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7316
- className: "lc-stat-sub",
7317
- children: t("ov.kpi.wallSub", { n: fmt(kpi.calls) })
7318
- })
7319
- ]
7678
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7679
+ className: "lc-stat-value",
7680
+ children: kpi.cost === null ? "—" : formatCost(kpi.cost, currency)
7681
+ }),
7682
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7683
+ className: "lc-stat-sub",
7684
+ children: t("ov.kpi.sessionsSub", { n: kpi.costSessions })
7685
+ })
7686
+ ]
7687
+ }),
7688
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7689
+ className: "lc-stat lc-ov-kpi",
7690
+ children: [
7691
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7692
+ className: "lc-stat-label",
7693
+ children: t("stats.cacheHit")
7694
+ }),
7695
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7696
+ className: "lc-stat-value",
7697
+ children: kpi.cacheHit === null ? "—" : kpi.cacheHit + "%"
7698
+ }),
7699
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7700
+ className: "lc-stat-sub",
7701
+ children: t("ov.kpi.sessionsSub", { n: kpi.usageSessions })
7702
+ })
7703
+ ]
7704
+ }),
7705
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7706
+ className: "lc-stat lc-ov-kpi",
7707
+ children: [
7708
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7709
+ className: "lc-stat-label",
7710
+ children: t("stats.toolCalls")
7711
+ }),
7712
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7713
+ className: "lc-stat-value",
7714
+ children: fmt(kpi.toolCalls)
7715
+ }),
7716
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7717
+ className: "lc-stat-sub",
7718
+ children: t("ov.kpi.toolSub", { dur: fmtDuration(kpi.toolsMs) })
7320
7719
  })
7321
7720
  ]
7322
7721
  }),
7323
7722
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7723
+ className: "lc-stat lc-ov-kpi",
7724
+ children: [
7725
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7726
+ className: "lc-stat-label",
7727
+ children: t("timing.total")
7728
+ }),
7729
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7730
+ className: "lc-stat-value",
7731
+ children: fmtDuration(kpi.wallMs)
7732
+ }),
7733
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7734
+ className: "lc-stat-sub",
7735
+ children: t("ov.kpi.wallSub", { n: fmt(kpi.calls) })
7736
+ })
7737
+ ]
7738
+ })
7739
+ ]
7740
+ }),
7741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7742
+ className: "lc-ov-stats",
7743
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewTokens, { tokens: kpi.tokenParts }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsTiming, { timing: kpi.timing })]
7744
+ }),
7745
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7746
+ className: "lc-ov-body",
7747
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7748
+ className: "lc-ov-left",
7749
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7324
7750
  className: "lc-card lc-ov-heat-card",
7325
7751
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7326
7752
  className: "lc-card-title",
@@ -7353,8 +7779,7 @@ window.__ModuleLoader__.load({
7353
7779
  onSelect: setDay,
7354
7780
  today: todayKey()
7355
7781
  })]
7356
- }),
7357
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7782
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7358
7783
  type: "button",
7359
7784
  className: "lc-ov-settings",
7360
7785
  onClick: () => {
@@ -7368,131 +7793,131 @@ window.__ModuleLoader__.load({
7368
7793
  className: "lc-ov-settings-hint",
7369
7794
  children: t("plugin.settingsOpen")
7370
7795
  })]
7371
- })
7372
- ]
7373
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7374
- className: "lc-ov-right",
7375
- children: [
7376
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7377
- className: "lc-ov-list-head",
7378
- children: [
7379
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7380
- className: "lc-ov-list-title",
7381
- children: t("ov.list.title")
7382
- }),
7383
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7384
- className: "lc-ov-list-count",
7385
- children: visible.length
7386
- }),
7387
- day !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7388
- type: "button",
7389
- className: "lc-ov-day-chip",
7390
- title: t("ov.list.dayClear"),
7391
- onClick: () => {
7392
- setDay(null);
7393
- },
7394
- children: [t("ov.list.dayFilter", { day }), " ×"]
7395
- }),
7396
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7397
- className: "lc-ov-search",
7398
- type: "search",
7399
- value: query,
7400
- placeholder: t("ov.list.search"),
7401
- "aria-label": t("ov.list.search"),
7402
- onChange: (ev) => {
7403
- setQuery(ev.target.value);
7404
- }
7405
- }),
7406
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7407
- className: "lc-gran",
7408
- role: "group",
7409
- "aria-label": t("ov.list.sortLabel"),
7410
- children: SORTS.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7796
+ })]
7797
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7798
+ className: "lc-ov-right",
7799
+ children: [
7800
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7801
+ className: "lc-ov-list-head",
7802
+ children: [
7803
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7804
+ className: "lc-ov-list-title",
7805
+ children: t("ov.list.title")
7806
+ }),
7807
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7808
+ className: "lc-ov-list-count",
7809
+ children: visible.length
7810
+ }),
7811
+ day !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7411
7812
  type: "button",
7412
- className: "lc-gran-btn" + (sort === s ? " lc-gran-on" : ""),
7813
+ className: "lc-ov-day-chip",
7814
+ title: t("ov.list.dayClear"),
7413
7815
  onClick: () => {
7414
- setSort(s);
7816
+ setDay(null);
7415
7817
  },
7416
- children: t("ov.list.sort." + s)
7417
- }, s))
7418
- })
7419
- ]
7420
- }),
7421
- chips.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7422
- className: "lc-ov-groups",
7423
- role: "group",
7424
- "aria-label": t("ov.group.label"),
7425
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7426
- type: "button",
7427
- className: "lc-ov-chip" + (group === null ? " lc-ov-chip-on" : ""),
7428
- onClick: () => {
7429
- setGroup(null);
7430
- },
7431
- children: [t("ov.range.all"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7432
- className: "lc-ov-chip-n",
7433
- children: scoped.length
7434
- })]
7435
- }), chips.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7436
- type: "button",
7437
- className: "lc-ov-chip" + (group === c.key ? " lc-ov-chip-on" : ""),
7438
- onClick: () => {
7439
- setGroup(group === c.key ? null : c.key);
7440
- },
7441
- children: [c.key === "__ungrouped__" ? t("ov.group.ungrouped") : c.key, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7442
- className: "lc-ov-chip-n",
7443
- children: c.count
7444
- })]
7445
- }, c.key))]
7446
- }),
7447
- visible.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7448
- className: "lc-empty",
7449
- children: t(allRows.length === 0 ? "ov.list.empty" : "ov.list.noMatch")
7450
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7451
- className: "lc-ov-grid",
7452
- children: paged.items.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewCard, {
7453
- row,
7454
- ...groups?.[row.id] !== void 0 ? { group: groups[row.id] } : {},
7455
- costLabel: cardCostOf(row, prices, currency),
7456
- now,
7457
- onOpen: openOne
7458
- }, row.id))
7459
- }), paged.count > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7460
- className: "lc-ov-pager",
7461
- role: "navigation",
7462
- "aria-label": t("ov.list.pager"),
7463
- children: [
7464
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7818
+ children: [t("ov.list.dayFilter", { day }), " ×"]
7819
+ }),
7820
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7821
+ className: "lc-ov-search",
7822
+ type: "search",
7823
+ value: query,
7824
+ placeholder: t("ov.list.search"),
7825
+ "aria-label": t("ov.list.search"),
7826
+ onChange: (ev) => {
7827
+ setQuery(ev.target.value);
7828
+ }
7829
+ }),
7830
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7831
+ className: "lc-gran",
7832
+ role: "group",
7833
+ "aria-label": t("ov.list.sortLabel"),
7834
+ children: SORTS.map((s) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7835
+ type: "button",
7836
+ className: "lc-gran-btn" + (sort === s ? " lc-gran-on" : ""),
7837
+ onClick: () => {
7838
+ setSort(s);
7839
+ },
7840
+ children: t("ov.list.sort." + s)
7841
+ }, s))
7842
+ })
7843
+ ]
7844
+ }),
7845
+ chips.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7846
+ className: "lc-ov-groups",
7847
+ role: "group",
7848
+ "aria-label": t("ov.group.label"),
7849
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7465
7850
  type: "button",
7466
- className: "lc-ov-pager-btn",
7467
- disabled: paged.index === 0,
7468
- "aria-label": t("ov.list.prev"),
7851
+ className: "lc-ov-chip" + (group === null ? " lc-ov-chip-on" : ""),
7469
7852
  onClick: () => {
7470
- setPage(paged.index - 1);
7853
+ setGroup(null);
7471
7854
  },
7472
- children: "‹"
7473
- }),
7474
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7475
- className: "lc-ov-pager-n",
7476
- children: t("ov.list.page", {
7477
- n: paged.index + 1,
7478
- total: paged.count
7855
+ children: [t("ov.range.all"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7856
+ className: "lc-ov-chip-n",
7857
+ children: scoped.length
7858
+ })]
7859
+ }), chips.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7860
+ type: "button",
7861
+ className: "lc-ov-chip" + (group === c.key ? " lc-ov-chip-on" : ""),
7862
+ onClick: () => {
7863
+ setGroup(group === c.key ? null : c.key);
7864
+ },
7865
+ children: [c.key === "__ungrouped__" ? t("ov.group.ungrouped") : c.key, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7866
+ className: "lc-ov-chip-n",
7867
+ children: c.count
7868
+ })]
7869
+ }, c.key))]
7870
+ }),
7871
+ visible.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7872
+ className: "lc-empty",
7873
+ children: t(allRows.length === 0 ? "ov.list.empty" : "ov.list.noMatch")
7874
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7875
+ className: "lc-ov-grid",
7876
+ children: paged.items.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewCard, {
7877
+ row,
7878
+ ...groups?.[row.id] !== void 0 ? { group: groups[row.id] } : {},
7879
+ costLabel: cardCostOf(row, prices, currency),
7880
+ now,
7881
+ onOpen: openOne
7882
+ }, row.id))
7883
+ }), paged.count > 1 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7884
+ className: "lc-ov-pager",
7885
+ role: "navigation",
7886
+ "aria-label": t("ov.list.pager"),
7887
+ children: [
7888
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7889
+ type: "button",
7890
+ className: "lc-ov-pager-btn",
7891
+ disabled: paged.index === 0,
7892
+ "aria-label": t("ov.list.prev"),
7893
+ onClick: () => {
7894
+ setPage(paged.index - 1);
7895
+ },
7896
+ children: "‹"
7897
+ }),
7898
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7899
+ className: "lc-ov-pager-n",
7900
+ children: t("ov.list.page", {
7901
+ n: paged.index + 1,
7902
+ total: paged.count
7903
+ })
7904
+ }),
7905
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7906
+ type: "button",
7907
+ className: "lc-ov-pager-btn",
7908
+ disabled: paged.index === paged.count - 1,
7909
+ "aria-label": t("ov.list.next"),
7910
+ onClick: () => {
7911
+ setPage(paged.index + 1);
7912
+ },
7913
+ children: "›"
7479
7914
  })
7480
- }),
7481
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7482
- type: "button",
7483
- className: "lc-ov-pager-btn",
7484
- disabled: paged.index === paged.count - 1,
7485
- "aria-label": t("ov.list.next"),
7486
- onClick: () => {
7487
- setPage(paged.index + 1);
7488
- },
7489
- children: "›"
7490
- })
7491
- ]
7492
- })] })
7493
- ]
7494
- })]
7495
- })]
7915
+ ]
7916
+ })] })
7917
+ ]
7918
+ })]
7919
+ })
7920
+ ] })]
7496
7921
  })
7497
7922
  });
7498
7923
  }
@@ -9077,13 +9502,6 @@ window.__ModuleLoader__.load({
9077
9502
  });
9078
9503
  return segs;
9079
9504
  }
9080
- /** Session-switch navigation, fail-soft: a stale row (list rebuilt between snapshot and click) loses its open() race and is ignored. */
9081
- function openAgentSession(face, id) {
9082
- if (face === null || typeof face.open !== "function") return;
9083
- try {
9084
- face.open(id);
9085
- } catch {}
9086
- }
9087
9505
  /** Narrow `ctx.get('sessions')` to the card's face (null = harness without the outward sessions service). */
9088
9506
  function sessionsFaceOf(ctx) {
9089
9507
  const rec = asRecord(ctx.get("sessions"));
@@ -9207,7 +9625,7 @@ window.__ModuleLoader__.load({
9207
9625
  for (const n of forest.nodes) totalTokens += n.head !== null ? n.head.tokens : 0;
9208
9626
  const open = (id) => {
9209
9627
  if (id === current.id) return;
9210
- openAgentSession(face, id);
9628
+ openSessionVia(ctx, id);
9211
9629
  };
9212
9630
  const keyOpen = (id) => (ev) => {
9213
9631
  if (ev.key !== "Enter" && ev.key !== " ") return;
@@ -9986,7 +10404,7 @@ window.__ModuleLoader__.load({
9986
10404
  return function PluginInfo() {
9987
10405
  const [latest, setLatest] = (0, react.useState)(null);
9988
10406
  (0, react.useEffect)(() => {
9989
- if ("0.56.1".includes("-dev")) return;
10407
+ if ("0.57.0".includes("-dev")) return;
9990
10408
  let on = true;
9991
10409
  fetchLatestVersion().then((v) => {
9992
10410
  if (on && v) setLatest(v);
@@ -9995,8 +10413,8 @@ window.__ModuleLoader__.load({
9995
10413
  on = false;
9996
10414
  };
9997
10415
  }, []);
9998
- const update = latest !== null && isNewerVersion(latest, "0.56.1") ? latest : null;
9999
- const nameText = "dsh-context (v0.56.1)";
10416
+ const update = latest !== null && isNewerVersion(latest, "0.57.0") ? latest : null;
10417
+ const nameText = "dsh-context (v0.57.0)";
10000
10418
  const nameValue = [nameText];
10001
10419
  if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10002
10420
  className: "lc-pi-update",
@@ -10652,245 +11070,6 @@ window.__ModuleLoader__.load({
10652
11070
  };
10653
11071
  }
10654
11072
  //#endregion
10655
- //#region src/client/components/sliceList.tsx
10656
- /**
10657
- * The stats cards' slice rows: the donut's legend with numbers, two tiers
10658
- * per slice — the primary line pairs the color dot and the name with the
10659
- * bold share closing a fixed right column, and the secondary line drops the
10660
- * absolute quantity (plus a qualifier like the call count) under the name in
10661
- * muted small print. Hovering a row lights it and its donut segment together
10662
- * (the shared hover key), and vice versa. No tracks or bars — the donut IS
10663
- * the proportion chart; the rows are its legend. Preformatted strings in,
10664
- * dumb markup out.
10665
- */
10666
- function makeSliceList(kit) {
10667
- return function SliceList(props) {
10668
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10669
- className: "lc-sl flex-auto @max-[240px]/lc-card:basis-full",
10670
- onMouseLeave: () => {
10671
- if (props.onHoverKey !== void 0) props.onHoverKey(null);
10672
- },
10673
- children: props.rows.map((r) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10674
- className: "lc-sl-row" + (r.dim ? " lc-sl-row-dim" : "") + (props.hoverKey !== void 0 && props.hoverKey === r.key ? " lc-sl-row-on" : ""),
10675
- onMouseEnter: () => {
10676
- if (props.onHoverKey !== void 0) props.onHoverKey(r.key);
10677
- },
10678
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10679
- className: "lc-sl-main",
10680
- children: [
10681
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
10682
- className: "lc-sl-dot",
10683
- style: { background: r.color }
10684
- }),
10685
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10686
- className: "lc-sl-label",
10687
- title: r.label,
10688
- children: r.label
10689
- }),
10690
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10691
- className: "lc-sl-pct",
10692
- children: r.pct
10693
- })
10694
- ]
10695
- }), r.count !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10696
- className: "lc-sl-sub",
10697
- title: r.count,
10698
- children: r.count
10699
- }) : null]
10700
- }, r.key))
10701
- });
10702
- };
10703
- }
10704
- //#endregion
10705
- //#region src/client/components/statsTiming.tsx
10706
- /**
10707
- * The Timing card: where the session's ACTIVE time went. The donut splits
10708
- * whole-step wall time into the model-call slices — TTFT (step start → first
10709
- * token, the wait) and LLM generation (first token → assistant message) —
10710
- * and the tool-execution slice, with the residue as overhead; a call whose
10711
- * stream recorded no token delta (legacy or aborted) stays unattributed and
10712
- * lands in the residue. When the host folded the generation split (see
10713
- * TimingTotals), the generation slice expands into what was being decoded:
10714
- * thinking, answer text, and tool-call arguments. The slice rows lead with
10715
- * the true duration and qualify it with a tally on the secondary line — the
10716
- * model calls (TTFT), the tool runs, and the decode slices' own BLOCK counts
10717
- * (zero to many per call, so a call count would be a false tally there). A
10718
- * slice that never happened (zero time) is omitted entirely — the ring already skips
10719
- * its arc, and a zero row would otherwise borrow the session's call count and
10720
- * read as "no time, many calls". The donut and the rows sit side by side so
10721
- * the head row stays half-height. Parallel tool calls each count, so the tools
10722
- * figure can overlap — the ring clamps it into the post-model window while the
10723
- * row numbers stay true.
10724
- */
10725
- const COLOR = {
10726
- ttft: "var(--color-blue-500)",
10727
- reasoning: "var(--color-violet-500)",
10728
- text: "var(--color-pink-500)",
10729
- toolarg: "var(--color-amber-500)",
10730
- tools: "var(--color-teal-500)",
10731
- other: "var(--color-slate-400)"
10732
- };
10733
- /**
10734
- * The decode-throughput figure, formatted exactly as the harness's own
10735
- * session-stats seat formats it: whole tokens from ten up, one decimal below.
10736
- */
10737
- function formatTokensPerSecond(tps) {
10738
- const clamped = Math.max(0, tps);
10739
- return clamped >= 10 ? String(Math.round(clamped)) : String(Math.round(clamped * 10) / 10);
10740
- }
10741
- function makeStatsTiming(kit, Donut) {
10742
- const { t, fmt, fmtDuration, fmtShare } = kit;
10743
- const SliceList = makeSliceList(kit);
10744
- return function StatsTiming(props) {
10745
- const [hoverKey, setHoverKey] = (0, react.useState)(null);
10746
- const timing = props.timing;
10747
- const wall = timing !== null && Number.isFinite(timing.wallMs) && timing.wallMs > 0 ? timing.wallMs : 0;
10748
- const tps = timing !== null && typeof timing.speedMs === "number" && Number.isFinite(timing.speedMs) && timing.speedMs > 0 && typeof timing.speedTokens === "number" && Number.isFinite(timing.speedTokens) && timing.speedTokens >= 0 ? formatTokensPerSecond(timing.speedTokens / (timing.speedMs / 1e3)) : null;
10749
- let segments = [];
10750
- let rows = [];
10751
- if (timing !== null && (wall > 0 || timing.calls > 0 || timing.toolCalls > 0)) {
10752
- const callTimes = t("timing.callTimes", { n: fmt(timing.calls) });
10753
- const modelSlices = [{
10754
- key: "ttft",
10755
- color: COLOR.ttft,
10756
- label: t("timing.ttft"),
10757
- ms: timing.ttftMs,
10758
- times: callTimes
10759
- }];
10760
- const blockTimes = (blocks) => blocks !== void 0 && blocks > 0 ? t("timing.blockTimes", { n: fmt(blocks) }) : void 0;
10761
- const buckets = [
10762
- [
10763
- "reasoning",
10764
- COLOR.reasoning,
10765
- t("timing.reasoning"),
10766
- timing.reasoningMs ?? 0,
10767
- blockTimes(timing.reasoningBlocks)
10768
- ],
10769
- [
10770
- "text",
10771
- COLOR.text,
10772
- t("timing.text"),
10773
- timing.textMs ?? 0,
10774
- blockTimes(timing.textBlocks)
10775
- ],
10776
- [
10777
- "toolarg",
10778
- COLOR.toolarg,
10779
- t("timing.toolArgs"),
10780
- timing.toolArgMs ?? 0,
10781
- blockTimes(timing.toolArgBlocks)
10782
- ]
10783
- ];
10784
- if (buckets.some(([, , , ms]) => ms > 0)) for (const [key, color, label, ms, times] of buckets) modelSlices.push({
10785
- key,
10786
- color,
10787
- label,
10788
- ms,
10789
- ...times !== void 0 ? { times } : {}
10790
- });
10791
- else modelSlices.push({
10792
- key: "gen",
10793
- color: COLOR.reasoning,
10794
- label: t("timing.gen"),
10795
- ms: timing.genMs
10796
- });
10797
- const ttftRing = Math.min(timing.ttftMs, wall);
10798
- const genRing = Math.max(0, Math.min(timing.genMs, wall - ttftRing));
10799
- const modelRings = [ttftRing];
10800
- let used = ttftRing;
10801
- const genEnd = ttftRing + genRing;
10802
- for (const slice of modelSlices.slice(1)) {
10803
- const take = Math.max(0, Math.min(slice.ms, genEnd - used));
10804
- modelRings.push(take);
10805
- used += take;
10806
- }
10807
- const toolRing = Math.max(0, Math.min(timing.toolsMs, wall - ttftRing - genRing));
10808
- const other = Math.max(0, wall - ttftRing - genRing - toolRing);
10809
- const share = (ms) => wall > 0 ? ms / wall : 0;
10810
- const countOf = (ms, times) => {
10811
- const dur = fmtDuration(ms);
10812
- if (times === void 0) return dur;
10813
- return ms > 0 ? `${dur} · ${times}` : times;
10814
- };
10815
- const modelSegments = modelSlices.map((slice, index) => ({
10816
- key: slice.key,
10817
- color: slice.color,
10818
- value: share(modelRings[index])
10819
- }));
10820
- const toolSlice = {
10821
- key: "tools",
10822
- color: COLOR.tools,
10823
- label: t("timing.tools"),
10824
- ms: timing.toolsMs,
10825
- times: t("timing.toolTimes", { n: fmt(timing.toolCalls) })
10826
- };
10827
- const otherSlice = {
10828
- key: "other",
10829
- color: COLOR.other,
10830
- label: t("timing.other"),
10831
- ms: other
10832
- };
10833
- segments = [
10834
- ...modelSegments,
10835
- {
10836
- key: "tools",
10837
- color: COLOR.tools,
10838
- value: share(toolRing)
10839
- },
10840
- {
10841
- key: "other",
10842
- color: COLOR.other,
10843
- value: share(other)
10844
- }
10845
- ];
10846
- const toRow = (slice) => ({
10847
- key: slice.key,
10848
- color: slice.color,
10849
- label: slice.label,
10850
- dim: slice.ms === 0,
10851
- pct: fmtShare(slice.ms, wall),
10852
- count: countOf(slice.ms, slice.times)
10853
- });
10854
- rows = [
10855
- ...modelSlices.map(toRow),
10856
- toRow(toolSlice),
10857
- toRow(otherSlice)
10858
- ].filter((row) => !row.dim);
10859
- }
10860
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10861
- className: "lc-card lc-col-stats lc-col-donut flex-1 min-w-[min(360px,100%)]",
10862
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10863
- className: "lc-card-title",
10864
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10865
- className: "lc-card-title-text",
10866
- children: t("timing.title")
10867
- }), tps !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10868
- className: "lc-timing-tps",
10869
- title: t("timing.tpsTip"),
10870
- children: t("timing.tps", { tps })
10871
- })]
10872
- }), rows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10873
- className: "lc-empty",
10874
- children: t("timing.empty")
10875
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10876
- className: "lc-donut-row flex items-center justify-start gap-3 min-w-0 @max-[320px]/lc-card:gap-2 @max-[240px]/lc-card:flex-wrap",
10877
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Donut, {
10878
- segments,
10879
- size: 96,
10880
- centerTop: wall > 0 ? fmtDuration(wall) : "—",
10881
- centerSub: t("timing.total"),
10882
- hoverKey,
10883
- onHoverKey: setHoverKey
10884
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SliceList, {
10885
- rows,
10886
- hoverKey,
10887
- onHoverKey: setHoverKey
10888
- })]
10889
- })]
10890
- });
10891
- };
10892
- }
10893
- //#endregion
10894
11073
  //#region src/client/components/statsTokens.tsx
10895
11074
  /**
10896
11075
  * The Token card: the session's whole billed token usage — the SAME total
@@ -11941,7 +12120,7 @@ window.__ModuleLoader__.load({
11941
12120
  }
11942
12121
  //#endregion
11943
12122
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/overview.css.mjs
11944
- const css = ".lc-ov-entry{box-sizing:border-box;width:calc(100% + 4px);height:42px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:8px;margin:0 -2px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.lc-ov-entry:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-ov-entry-rail{border-radius:50%;flex:none;justify-content:center;gap:0;width:36px;height:36px;margin:0;padding:0}.lc-ov-entry-icon{flex:none}.lc-ov-entry-label{text-align:left;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-ov-backdrop{z-index:200;background:var(--dsw-alias-bg-mask-1,#00000073);justify-content:center;align-items:center;display:flex;position:fixed;inset:0;container:lc-ov/inline-size}.lc-ov-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);width:min(1120px,100vw - 32px);height:calc(100% - 80px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;flex-direction:column;padding:16px 18px 18px;font-size:13px;display:flex;overflow:hidden}.lc-ov-head{flex-wrap:wrap;flex:none;align-items:center;gap:10px;margin-bottom:14px;display:flex}.lc-ov-head-icon{flex:none}.lc-ov-title{white-space:nowrap;font-size:15px;font-weight:600}.lc-ov-balance{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);max-width:100%;color:inherit;border-radius:999px;flex:none;align-items:center;gap:6px;padding:2px 10px;font-size:12px;line-height:18px;text-decoration:none;display:inline-flex}.lc-ov-balance:hover .lc-ov-balance-value{text-decoration:underline}.lc-ov-balance-label{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-ov-balance-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-head .lc-modal-close{flex:none;margin-left:0}.lc-ov-body{flex:auto;grid-template-columns:minmax(0,3fr) minmax(0,7fr);gap:10px;min-height:0;display:grid}.lc-ov-left{scrollbar-gutter:stable;flex-direction:column;gap:10px;min-height:0;padding-right:8px;display:flex;overflow-y:auto}.lc-ov-right{scrollbar-gutter:stable;min-height:0;padding-right:8px;overflow-y:auto}@container lc-ov (width<=932px){.lc-ov-card{overflow-y:auto}.lc-ov-body{display:block}.lc-ov-left{margin-bottom:12px}.lc-ov-left,.lc-ov-right{min-height:auto;padding-right:0;overflow:visible}.lc-ov-heat-card{flex:none}}.lc-ov-left>.lc-card{box-sizing:border-box;min-width:0;margin-bottom:0}.lc-ov-kpis{flex:none;grid-template-rows:repeat(3,1fr);grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;display:grid}.lc-ov-kpi{align-items:center;padding:10px 8px}.lc-ov-kpi .lc-stat-value{font-size:20px}.lc-ov-heat-card{flex-direction:column;flex:1 0 auto;display:flex}.lc-ov-settings{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex:none;align-items:center;gap:8px;padding:9px 12px;font-size:12px;display:flex}.lc-ov-settings:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.lc-ov-settings-label{white-space:nowrap;flex:none;align-items:center;gap:6px;display:inline-flex}.lc-ov-settings-hint{text-align:right;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-heat-wrap{flex-direction:column;flex:1;display:flex}.lc-heat{flex:1;justify-content:center;align-items:center;gap:8px;display:flex}.lc-heat-wds,.lc-heat-col{padding-top:18px}.lc-heat-wds{flex-direction:column;gap:3px;display:flex}.lc-heat-wd{height:20px;color:var(--dsw-alias-label-secondary);text-align:center;white-space:nowrap;flex:none;justify-content:center;align-items:center;font-size:9px;display:flex}.lc-heat-cols{gap:3px;display:flex}.lc-heat-col{flex-direction:column;gap:3px;display:flex;position:relative}.lc-heat-mon{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:9px;line-height:18px;position:absolute;top:0;left:0}.lc-heat-cell{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);cursor:default;border:0;border-radius:20%;width:20px;height:20px;padding:0}button.lc-heat-cell{cursor:pointer}button.lc-heat-cell:hover{box-shadow:0 0 0 1.5px color-mix(in srgb, var(--color-blue-500) 70%, transparent)}.lc-heat-future{background:0 0}.lc-heat-0{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent)}.lc-heat-1{background:color-mix(in srgb, var(--color-blue-500) 28%, transparent)}.lc-heat-2{background:color-mix(in srgb, var(--color-blue-500) 50%, transparent)}.lc-heat-3{background:color-mix(in srgb, var(--color-blue-500) 72%, transparent)}.lc-heat-4{background:color-mix(in srgb, var(--color-blue-500) 92%, transparent)}.lc-heat-on{box-shadow:0 0 0 1.5px var(--color-blue-500)}.lc-heat-legend{justify-content:flex-end;align-items:center;gap:3px;padding-top:6px;display:flex}.lc-heat-key{color:var(--dsw-alias-label-secondary);font-size:9px}.lc-ov-list-head{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:10px;display:flex}.lc-ov-list-title{white-space:nowrap;font-weight:600}.lc-ov-list-count{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;background:var(--dsw-alias-bg-layer-2);border-radius:8px;padding:1px 7px;font-size:11px}.lc-ov-day-chip{border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);color:var(--color-blue-500);background:color-mix(in srgb, var(--color-blue-500) 10%, transparent);cursor:pointer;white-space:nowrap;border-radius:999px;padding:1px 10px;font-family:inherit;font-size:11px;line-height:1.4}.lc-ov-search{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:110px;font:inherit;color:var(--dsw-alias-label-primary);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;outline:none;flex:150px;padding:4px 10px;font-size:12px}.lc-ov-search:focus{border-color:var(--dsw-alias-label-secondary)}.lc-ov-search::placeholder{color:var(--dsw-alias-label-secondary)}.lc-ov-groups{flex-wrap:wrap;gap:6px;margin:-2px 0 10px;display:flex}.lc-ov-chip{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;text-overflow:ellipsis;max-width:220px;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:999px;align-items:baseline;gap:5px;padding:2px 10px;font-size:11px;line-height:1.5;display:inline-flex;overflow:hidden}.lc-ov-chip:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary)}.lc-ov-chip-on,.lc-ov-chip-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.lc-ov-chip-n{font-variant-numeric:tabular-nums;opacity:.65;font-size:10px}.lc-ov-chip-on .lc-ov-chip-n{opacity:.85}.lc-ov-grid{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;display:grid}.lc-ov-session{text-align:left;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;font:inherit;color:inherit;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}.lc-ov-session:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.lc-ov-session-current{border-color:color-mix(in srgb, var(--color-blue-500) 65%, transparent)}.lc-ov-session-head{align-items:flex-start;gap:6px;min-width:0;display:flex}.lc-ov-running{background:var(--color-green-500);border-radius:50%;flex:none;width:7px;height:7px}.lc-ov-session-title{-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;flex:auto;min-width:0;font-weight:600;display:-webkit-box;overflow:hidden}.lc-ov-current{color:var(--color-blue-500);border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);border-radius:4px;flex:none;padding:0 4px;font-size:10px;line-height:1.5}.lc-ov-session-times{flex-direction:column;flex:none;align-items:flex-end;gap:1px;display:flex}.lc-ov-session-time{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:11px}.lc-ov-session-created{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:10px;line-height:1.4}.lc-ov-session-crumb{min-width:0;color:var(--dsw-alias-label-secondary);white-space:nowrap;align-items:center;gap:5px;font-size:11px;display:flex;overflow:hidden}.lc-ov-crumb-group{text-overflow:ellipsis;flex:none;max-width:55%;overflow:hidden}.lc-ov-crumb-sep{opacity:.55;flex:none}.lc-ov-crumb-project{text-overflow:ellipsis;min-width:0;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-label-primary);background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent);border-radius:999px;flex:0 auto;padding:0 8px;font-size:10px;line-height:1.6;overflow:hidden}.lc-ov-session-body{align-items:center;gap:14px;min-width:0;display:flex}.lc-ov-mini-stats{flex-direction:column;flex:auto;gap:3px;min-width:0;display:flex}.lc-ov-mini-stat{justify-content:space-between;align-items:baseline;gap:10px;font-size:11px;display:flex}.lc-ov-mini-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none}.lc-ov-mini-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-session-empty{color:var(--dsw-alias-label-secondary);padding:6px 0;font-size:11px;line-height:1.5}.lc-ov-session-preview{border-top:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 60%, transparent);align-items:baseline;gap:6px;min-width:0;margin-top:auto;padding-top:6px;display:flex}.lc-ov-session-preview-label{color:var(--dsw-alias-label-secondary);flex:none;font-size:10px}.lc-ov-session-preview-text{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);flex:0 auto;font-size:11px;line-height:1.5;overflow:hidden}.lc-ov-pager{justify-content:center;align-items:center;gap:10px;margin-top:12px;display:flex}.lc-ov-pager-btn{border:1px solid var(--dsw-alias-border-l2);width:26px;height:22px;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);background:0 0;border-radius:6px;flex:none;justify-content:center;align-items:center;line-height:1;display:flex}.lc-ov-pager-btn:hover:enabled{border-color:var(--dsw-alias-label-secondary)}.lc-ov-pager-btn:disabled{opacity:.4;cursor:default}.lc-ov-pager-n{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:11px}";
12123
+ const css = ".lc-ov-entry{box-sizing:border-box;width:calc(100% + 4px);height:42px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:8px;margin:0 -2px;padding:0 10px 0 8px;font-family:inherit;font-size:14px;line-height:22px;display:flex;overflow:hidden}.lc-ov-entry:hover{background:var(--dsw-alias-interactive-bg-hover)}.lc-ov-entry-rail{border-radius:50%;flex:none;justify-content:center;gap:0;width:36px;height:36px;margin:0;padding:0}.lc-ov-entry-icon{flex:none}.lc-ov-entry-label{text-align:left;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-ov-backdrop{z-index:200;background:var(--dsw-alias-bg-mask-1,#00000073);justify-content:center;align-items:center;display:flex;position:fixed;inset:0;container:lc-ov/inline-size}.lc-ov-card{box-sizing:border-box;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);width:min(1120px,100vw - 32px);height:calc(100% - 80px);box-shadow:var(--dsw-shadow-lv3,0 12px 32px #0006);color:var(--dsw-alias-label-primary);border-radius:12px;flex-direction:column;padding:16px 18px 18px;font-size:13px;display:flex;overflow:hidden}.lc-ov-head{flex-wrap:wrap;flex:none;align-items:center;gap:10px;margin-bottom:14px;display:flex}.lc-ov-head-icon{flex:none}.lc-ov-title{white-space:nowrap;font-size:15px;font-weight:600}.lc-ov-balance{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);max-width:100%;color:inherit;border-radius:999px;flex:none;align-items:center;gap:6px;padding:2px 10px;font-size:12px;line-height:18px;text-decoration:none;display:inline-flex}.lc-ov-balance:hover .lc-ov-balance-value{text-decoration:underline}.lc-ov-balance-label{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-ov-balance-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-head .lc-modal-close{flex:none;margin-left:0}.lc-ov-kpis{flex:none;grid-template-columns:repeat(6,minmax(0,1fr));gap:10px;margin-bottom:10px;display:grid}.lc-ov-kpi{align-items:center;padding:10px 8px}.lc-ov-kpi .lc-stat-value{font-size:20px}@container lc-ov (width<=1000px){.lc-ov-kpis{grid-template-columns:repeat(3,minmax(0,1fr))}}@container lc-ov (width<=640px){.lc-ov-kpis{grid-template-columns:repeat(2,minmax(0,1fr))}}.lc-ov-stats{flex:none;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-bottom:10px;display:grid}.lc-ov-stats>.lc-card{box-sizing:border-box;min-width:0;margin-bottom:0}@container lc-ov (width<=700px){.lc-ov-stats{grid-template-columns:minmax(0,1fr)}}.lc-ov-body{flex:auto;grid-template-columns:minmax(0,3fr) minmax(0,7fr);gap:10px;min-height:0;display:grid}.lc-ov-left{scrollbar-gutter:stable;flex-direction:column;gap:10px;min-height:0;padding-right:8px;display:flex;overflow-y:auto}.lc-ov-right{scrollbar-gutter:stable;min-height:0;padding-right:8px;overflow-y:auto}@container lc-ov (width<=932px){.lc-ov-card{overflow-y:auto}.lc-ov-body{display:block}.lc-ov-left{margin-bottom:12px}.lc-ov-left,.lc-ov-right{min-height:auto;padding-right:0;overflow:visible}.lc-ov-heat-card{flex:none}}.lc-ov-left>.lc-card{box-sizing:border-box;min-width:0;margin-bottom:0}.lc-ov-heat-card{flex-direction:column;flex:1 0 auto;display:flex}.lc-ov-settings{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);width:100%;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex:none;align-items:center;gap:8px;padding:9px 12px;font-size:12px;display:flex}.lc-ov-settings:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary)}.lc-ov-settings-label{white-space:nowrap;flex:none;align-items:center;gap:6px;display:inline-flex}.lc-ov-settings-hint{text-align:right;white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.lc-heat-wrap{flex-direction:column;flex:1;display:flex}.lc-heat{flex:1;justify-content:center;align-items:center;gap:8px;display:flex}.lc-heat-wds,.lc-heat-col{padding-top:18px}.lc-heat-wds{flex-direction:column;gap:3px;display:flex}.lc-heat-wd{height:20px;color:var(--dsw-alias-label-secondary);text-align:center;white-space:nowrap;flex:none;justify-content:center;align-items:center;font-size:9px;display:flex}.lc-heat-cols{gap:3px;display:flex}.lc-heat-col{flex-direction:column;gap:3px;display:flex;position:relative}.lc-heat-mon{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:9px;line-height:18px;position:absolute;top:0;left:0}.lc-heat-cell{box-sizing:border-box;background:var(--dsw-alias-bg-layer-2);cursor:default;border:0;border-radius:20%;width:20px;height:20px;padding:0}button.lc-heat-cell{cursor:pointer}button.lc-heat-cell:hover{box-shadow:0 0 0 1.5px color-mix(in srgb, var(--color-blue-500) 70%, transparent)}.lc-heat-future{background:0 0}.lc-heat-0{background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent)}.lc-heat-1{background:color-mix(in srgb, var(--color-blue-500) 28%, transparent)}.lc-heat-2{background:color-mix(in srgb, var(--color-blue-500) 50%, transparent)}.lc-heat-3{background:color-mix(in srgb, var(--color-blue-500) 72%, transparent)}.lc-heat-4{background:color-mix(in srgb, var(--color-blue-500) 92%, transparent)}.lc-heat-on{box-shadow:0 0 0 1.5px var(--color-blue-500)}.lc-heat-legend{justify-content:flex-end;align-items:center;gap:3px;padding-top:6px;display:flex}.lc-heat-key{color:var(--dsw-alias-label-secondary);font-size:9px}.lc-ov-list-head{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:10px;display:flex}.lc-ov-list-title{white-space:nowrap;font-weight:600}.lc-ov-list-count{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;background:var(--dsw-alias-bg-layer-2);border-radius:8px;padding:1px 7px;font-size:11px}.lc-ov-day-chip{border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);color:var(--color-blue-500);background:color-mix(in srgb, var(--color-blue-500) 10%, transparent);cursor:pointer;white-space:nowrap;border-radius:999px;padding:1px 10px;font-family:inherit;font-size:11px;line-height:1.4}.lc-ov-search{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);min-width:110px;font:inherit;color:var(--dsw-alias-label-primary);transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;outline:none;flex:150px;padding:4px 10px;font-size:12px}.lc-ov-search:focus{border-color:var(--dsw-alias-label-secondary)}.lc-ov-search::placeholder{color:var(--dsw-alias-label-secondary)}.lc-ov-groups{flex-wrap:wrap;gap:6px;margin:-2px 0 10px;display:flex}.lc-ov-chip{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);font:inherit;cursor:pointer;white-space:nowrap;text-overflow:ellipsis;max-width:220px;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border-radius:999px;align-items:baseline;gap:5px;padding:2px 10px;font-size:11px;line-height:1.5;display:inline-flex;overflow:hidden}.lc-ov-chip:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-secondary)}.lc-ov-chip-on,.lc-ov-chip-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);border-color:#0000}.lc-ov-chip-n{font-variant-numeric:tabular-nums;opacity:.65;font-size:10px}.lc-ov-chip-on .lc-ov-chip-n{opacity:.85}.lc-ov-grid{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px;display:grid}.lc-ov-session{text-align:left;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;font:inherit;color:inherit;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out), background-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:10px;flex-direction:column;gap:6px;padding:10px 12px;display:flex}.lc-ov-session:hover{border-color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2)}.lc-ov-session-current{border-color:color-mix(in srgb, var(--color-blue-500) 65%, transparent)}.lc-ov-session-head{align-items:flex-start;gap:6px;min-width:0;display:flex}.lc-ov-running{background:var(--color-green-500);border-radius:50%;flex:none;width:7px;height:7px}.lc-ov-session-title{-webkit-line-clamp:2;overflow-wrap:anywhere;-webkit-box-orient:vertical;flex:auto;min-width:0;font-weight:600;display:-webkit-box;overflow:hidden}.lc-ov-current{color:var(--color-blue-500);border:1px solid color-mix(in srgb, var(--color-blue-500) 45%, transparent);border-radius:4px;flex:none;padding:0 4px;font-size:10px;line-height:1.5}.lc-ov-session-times{flex-direction:column;flex:none;align-items:flex-end;gap:1px;display:flex}.lc-ov-session-time{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:11px}.lc-ov-session-created{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:10px;line-height:1.4}.lc-ov-session-crumb{min-width:0;color:var(--dsw-alias-label-secondary);white-space:nowrap;align-items:center;gap:5px;font-size:11px;display:flex;overflow:hidden}.lc-ov-crumb-group{text-overflow:ellipsis;flex:none;max-width:55%;overflow:hidden}.lc-ov-crumb-sep{opacity:.55;flex:none}.lc-ov-crumb-project{text-overflow:ellipsis;min-width:0;font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-label-primary);background:color-mix(in srgb, var(--dsw-alias-label-secondary) 16%, transparent);border-radius:999px;flex:0 auto;padding:0 8px;font-size:10px;line-height:1.6;overflow:hidden}.lc-ov-session-body{align-items:center;gap:14px;min-width:0;display:flex}.lc-ov-mini-stats{flex-direction:column;flex:auto;gap:3px;min-width:0;display:flex}.lc-ov-mini-stat{justify-content:space-between;align-items:baseline;gap:10px;font-size:11px;display:flex}.lc-ov-mini-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;flex:none}.lc-ov-mini-value{font-variant-numeric:tabular-nums;white-space:nowrap;font-weight:600}.lc-ov-session-empty{color:var(--dsw-alias-label-secondary);padding:6px 0;font-size:11px;line-height:1.5}.lc-ov-session-preview{border-top:1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 60%, transparent);align-items:baseline;gap:6px;min-width:0;margin-top:auto;padding-top:6px;display:flex}.lc-ov-session-preview-label{color:var(--dsw-alias-label-secondary);flex:none;font-size:10px}.lc-ov-session-preview-text{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-alias-label-primary);flex:0 auto;font-size:11px;line-height:1.5;overflow:hidden}.lc-ov-pager{justify-content:center;align-items:center;gap:10px;margin-top:12px;display:flex}.lc-ov-pager-btn{border:1px solid var(--dsw-alias-border-l2);width:26px;height:22px;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);background:0 0;border-radius:6px;flex:none;justify-content:center;align-items:center;line-height:1;display:flex}.lc-ov-pager-btn:hover:enabled{border-color:var(--dsw-alias-label-secondary)}.lc-ov-pager-btn:disabled{opacity:.4;cursor:default}.lc-ov-pager-n{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:11px}";
11945
12124
  const tagId = "dsh-context/overview.css";
11946
12125
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
11947
12126
  const tag = document.createElement("style");