dsh-context 0.53.1 → 0.53.2

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 (2) hide show
  1. package/lib/client.js +325 -342
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -3400,11 +3400,12 @@ window.__ModuleLoader__.load({
3400
3400
  }
3401
3401
  //#endregion
3402
3402
  //#region src/client/format.ts
3403
- /** `fmt`: the k/M suffix style shared by bars/details/stats; `fmtTime`: local HH:MM:SS. */
3403
+ /** `fmt`: the k/M/B suffix style shared by bars/details/stats; `fmtTime`: local HH:MM:SS. */
3404
3404
  function fmt(n) {
3405
3405
  if (n === void 0 || n === null || isNaN(n)) return "—";
3406
3406
  const sign = n < 0 ? "-" : "";
3407
3407
  const a = Math.abs(n);
3408
+ if (a >= 1e9) return sign + (a / 1e9).toFixed(1) + "B";
3408
3409
  if (a >= 1e6) return sign + (a / 1e6).toFixed(1) + "M";
3409
3410
  if (a >= 1e3) return sign + (a / 1e3).toFixed(1) + "k";
3410
3411
  return sign + String(Math.round(a));
@@ -5293,77 +5294,67 @@ window.__ModuleLoader__.load({
5293
5294
  };
5294
5295
  }
5295
5296
  //#endregion
5296
- //#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
5297
- /**
5298
- * The `dsh-resource://file/…` address grammar: how a file is named across the
5299
- * Sidebar and the resource model, built and parsed without touching a
5300
- * filesystem.
5301
- * @module
5302
- */
5303
- /** The scheme and type every file address opens with. */
5304
- const FILE_ADDRESS_PREFIX = "dsh-resource://file/";
5305
- /** Component-encode one id or path segment, keeping `:` literal for drive letters. */
5306
- function encodeSegment(segment) {
5307
- return encodeURIComponent(segment).replace(/%3A/gi, ":");
5308
- }
5309
- /** Encode a `/`-separated path segment by segment. */
5310
- function encodePath(path) {
5311
- return path.split("/").map(encodeSegment).join("/");
5312
- }
5313
- /**
5314
- * Build the address of a file read through one Session.
5315
- * @param sessionId - the Session whose Host workspace resolves the path.
5316
- * @param path - absolute or workspace-relative path; backslashes are normalized to `/`, and leading `./` prefixes are dropped.
5317
- * @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
5318
- */
5319
- function sessionFileAddress(sessionId, path) {
5320
- const normalized = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
5321
- return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(normalized)}`;
5322
- }
5323
- /**
5324
- * Browser-safe Workspace path and display helpers.
5325
- * @module @deepseek-ai/dsh-util-workspace-path
5326
- */
5327
- /** Whether a path uses a Windows drive or UNC prefix. */
5328
- function isWindowsStylePath(value) {
5329
- return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
5330
- }
5331
- /**
5332
- * Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`) or Windows drive or UNC.
5333
- * @param path - the path to classify.
5334
- * @returns `true` for an absolute path; `false` for a Workspace-relative one.
5335
- */
5336
- function isAbsoluteWorkspacePath(path) {
5337
- return path.startsWith("/") || isWindowsStylePath(path);
5338
- }
5339
- /**
5340
- * Read the final non-empty segment of a Workspace path for display.
5341
- * Workspace-label surfaces use this helper instead of deriving another basename.
5342
- * @param path - Workspace directory path using POSIX or Windows separators.
5343
- * @returns the final segment, or an empty string for a separator-only path.
5344
- */
5345
- function workspaceTitleOf(path) {
5346
- const trimmed = path.replace(/[/\\]+$/, "");
5347
- const separator = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
5348
- return trimmed.slice(separator + 1);
5297
+ //#region src/client/overviewStore.ts
5298
+ function createStore() {
5299
+ let open = false;
5300
+ const listeners = /* @__PURE__ */ new Set();
5301
+ return {
5302
+ subscribe: (listener) => {
5303
+ listeners.add(listener);
5304
+ return () => {
5305
+ listeners.delete(listener);
5306
+ };
5307
+ },
5308
+ getSnapshot: () => open,
5309
+ set: (next) => {
5310
+ if (next === open) return;
5311
+ open = next;
5312
+ for (const listener of listeners) listener();
5313
+ }
5314
+ };
5349
5315
  }
5316
+ const overviewStore = createStore();
5317
+ //#endregion
5318
+ //#region src/client/components/overviewButton.tsx
5350
5319
  /**
5351
- * The address for a path as a caller holds it: a relative path, or an absolute
5352
- * path inside the Session's workspace, becomes a `session`-scoped address; an
5353
- * absolute path outside it, or one whose workspace root is unknown, keeps its
5354
- * absolute path in that Session's address.
5355
- * @param sessionId - the Session the path is read in.
5356
- * @param cwd - that Session's workspace root, when known.
5357
- * @param path - absolute or workspace-relative path, in either separator spelling.
5358
- * @returns the `dsh-resource://file/…` address.
5320
+ * The Context Dashboard's sidebar entry: a footer action stacked directly
5321
+ * above Settings (the harness's own foot layout: footer actions, then the
5322
+ * settings row), mirroring the Settings trigger's geometry in both column
5323
+ * widths. The button carries the plugin emblem and, on the wide
5324
+ * column, its label. Clicking opens the overview overlay through the shared
5325
+ * module store (overviewStore.ts).
5326
+ *
5327
+ * The per-user `insightsEntry` preference takes the entry down (renders
5328
+ * null) without unregistering the seat. The subscription fails open: an
5329
+ * absent settings store, an unserved namespace, or a value the plugin
5330
+ * cannot understand all leave the entry visible.
5359
5331
  */
5360
- function fileAddressFor(sessionId, cwd, path) {
5361
- const normalized = path.replace(/\\/g, "/");
5362
- if (!isAbsoluteWorkspacePath(normalized)) return sessionFileAddress(sessionId, normalized);
5363
- const root = cwd === void 0 ? "" : cwd.replace(/\\/g, "/").replace(/\/+$/, "");
5364
- if (root !== "" && normalized === root) return sessionFileAddress(sessionId, "");
5365
- if (root !== "" && normalized.startsWith(`${root}/`)) return sessionFileAddress(sessionId, normalized.slice(root.length + 1));
5366
- return sessionFileAddress(sessionId, normalized);
5332
+ /** Stable subscription faces for the settings-less degrade (no re-subscribes). */
5333
+ const subscribeNever = () => () => {};
5334
+ const entryShown = () => "show";
5335
+ function makeOverviewButton(kit, settings) {
5336
+ const { t } = kit;
5337
+ const subscribeEntry = settings === void 0 ? subscribeNever : (listener) => settings.store.subscribe(listener);
5338
+ const getEntry = settings === void 0 ? entryShown : () => settings.insightsEntry();
5339
+ return function OverviewButton(props) {
5340
+ if ((0, react.useSyncExternalStore)(subscribeEntry, getEntry) === "hide") return null;
5341
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5342
+ type: "button",
5343
+ className: props.wide === true ? "lc-ov-entry" : "lc-ov-entry lc-ov-entry-rail",
5344
+ title: t("ov.entry"),
5345
+ "aria-label": t("ov.entry"),
5346
+ onClick: () => {
5347
+ overviewStore.set(true);
5348
+ },
5349
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextIcon, {
5350
+ size: props.wide === true ? 16 : 18,
5351
+ className: "lc-ov-entry-icon"
5352
+ }), props.wide === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5353
+ className: "lc-ov-entry-label",
5354
+ children: t("ov.entry")
5355
+ })]
5356
+ });
5357
+ };
5367
5358
  }
5368
5359
  //#endregion
5369
5360
  //#region src/shared/providers.ts
@@ -5543,6 +5534,258 @@ window.__ModuleLoader__.load({
5543
5534
  return formatCost(amount, currency).replace(/0+$/, "").replace(/\.$/, "");
5544
5535
  }
5545
5536
  //#endregion
5537
+ //#region node_modules/.pnpm/@opencode-ai+models@0.0.72/node_modules/@opencode-ai/models/dist/error.js
5538
+ /**
5539
+ * The only error thrown by the models.dev client.
5540
+ *
5541
+ * - `Transport` — the fetch itself failed (network, DNS, abort). `cause` is the underlying error.
5542
+ * - `UnexpectedStatus` — non-2xx response. `cause` is `{ status: number }`.
5543
+ * - `MalformedResponse` — the body was empty or not valid JSON. `cause` is the parse error, if any.
5544
+ */
5545
+ var ModelsDevError = class extends Error {
5546
+ reason;
5547
+ name = "ModelsDevError";
5548
+ constructor(reason, options) {
5549
+ super(reason, options);
5550
+ this.reason = reason;
5551
+ }
5552
+ };
5553
+ //#endregion
5554
+ //#region node_modules/.pnpm/@opencode-ai+models@0.0.72/node_modules/@opencode-ai/models/dist/client.js
5555
+ /**
5556
+ * Creates a stateless models.dev client. Every method performs exactly one
5557
+ * `GET` and nothing is ever cached — callers who want caching should wrap
5558
+ * calls with their own policy. For a no-network alternative, see the
5559
+ * `@opencode-ai/models/snapshot` entrypoint.
5560
+ */
5561
+ function make(options = {}) {
5562
+ const baseUrl = options.baseUrl ?? "https://models.dev";
5563
+ const base = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
5564
+ const request = async (path, requestOptions) => {
5565
+ const fetch = options.fetch ?? globalThis.fetch;
5566
+ const headers = new Headers();
5567
+ for (const [key, value] of new Headers(options.headers)) headers.set(key, value);
5568
+ for (const [key, value] of new Headers(requestOptions?.headers)) headers.set(key, value);
5569
+ let response;
5570
+ try {
5571
+ response = await fetch(new URL(path, base), {
5572
+ method: "GET",
5573
+ headers,
5574
+ signal: requestOptions?.signal
5575
+ });
5576
+ } catch (cause) {
5577
+ throw new ModelsDevError("Transport", { cause });
5578
+ }
5579
+ if (!response.ok) {
5580
+ try {
5581
+ await response.body?.cancel();
5582
+ } catch {}
5583
+ throw new ModelsDevError("UnexpectedStatus", { cause: { status: response.status } });
5584
+ }
5585
+ let text;
5586
+ try {
5587
+ text = await response.text();
5588
+ } catch (cause) {
5589
+ throw new ModelsDevError("Transport", { cause });
5590
+ }
5591
+ if (text === "") throw new ModelsDevError("MalformedResponse");
5592
+ try {
5593
+ return JSON.parse(text);
5594
+ } catch (cause) {
5595
+ throw new ModelsDevError("MalformedResponse", { cause });
5596
+ }
5597
+ };
5598
+ return {
5599
+ /** All providers with their models, pricing, and limits (`/api.json`). */
5600
+ providers: (requestOptions) => request("api.json", requestOptions),
5601
+ /** Provider-agnostic model metadata (`/models.json`). */
5602
+ models: (requestOptions) => request("models.json", requestOptions),
5603
+ /** Providers and model metadata in a single request (`/catalog.json`). */
5604
+ catalog: (requestOptions) => request("catalog.json", requestOptions)
5605
+ };
5606
+ }
5607
+ //#endregion
5608
+ //#region src/client/modelPrices.ts
5609
+ /**
5610
+ * The client's model-price book (client/cost.ts prices from it): the
5611
+ * models.dev registry, fetched through the official @opencode-ai/models
5612
+ * SDK. The registry payload is untrusted wire input, so `pricesBookOf`
5613
+ * re-proves every field at the boundary — a non-conforming provider/model/
5614
+ * cost entry drops whole. The book keeps EVERY provider that prices (keyed
5615
+ * by the registry's own provider id), so a dsh provider id outside the
5616
+ * rename table still prices by direct passthrough. One fetch per page load,
5617
+ * kicked on first subscribe; a failure degrades to a visible state the cost
5618
+ * cell notes, with a backed-off automatic retry — never a spinner, and
5619
+ * never an unhandled rejection.
5620
+ */
5621
+ /** One finite non-negative registry figure, or null. */
5622
+ function rateOf(value) {
5623
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
5624
+ }
5625
+ /**
5626
+ * Extract the whole registry's per-model USD rates from a delivered
5627
+ * providers payload (`/api.json`), keyed by the registry's provider ids.
5628
+ * Any shape failure skips just that entry; a payload that is not a record
5629
+ * at all returns null (the store treats it as a failed fetch and retries).
5630
+ */
5631
+ function pricesBookOf(value) {
5632
+ const data = asRecord(value);
5633
+ if (data === null || Array.isArray(data)) return null;
5634
+ const book = {};
5635
+ for (const providerId of Object.keys(data)) {
5636
+ const provider = asRecord(data[providerId]);
5637
+ const modelList = provider !== null ? asRecord(provider.models) : null;
5638
+ if (modelList === null) continue;
5639
+ const models = {};
5640
+ for (const id of Object.keys(modelList)) {
5641
+ const model = asRecord(modelList[id]);
5642
+ const cost = model !== null ? asRecord(model.cost) : null;
5643
+ if (cost === null) continue;
5644
+ const miss = rateOf(cost.input);
5645
+ const out = rateOf(cost.output);
5646
+ if (miss === null || out === null) continue;
5647
+ models[id] = {
5648
+ hit: rateOf(cost.cache_read) ?? miss,
5649
+ miss,
5650
+ write: rateOf(cost.cache_write) ?? miss,
5651
+ out
5652
+ };
5653
+ }
5654
+ if (Object.keys(models).length > 0) book[providerId] = models;
5655
+ }
5656
+ return book;
5657
+ }
5658
+ const defaultLoader = () => make().providers();
5659
+ /** The retry backoff base; each consecutive failure doubles the wait, capped at 3 doublings. */
5660
+ const RETRY_BASE_MS = 3e4;
5661
+ let loader = defaultLoader;
5662
+ let snap = {
5663
+ prices: null,
5664
+ failed: false
5665
+ };
5666
+ let inFlight = false;
5667
+ let failures = 0;
5668
+ let timer = null;
5669
+ const listeners = /* @__PURE__ */ new Set();
5670
+ function fail() {
5671
+ snap = {
5672
+ ...snap,
5673
+ failed: true
5674
+ };
5675
+ failures++;
5676
+ timer = setTimeout(() => {
5677
+ timer = null;
5678
+ fire();
5679
+ }, RETRY_BASE_MS * 2 ** Math.min(failures - 1, 3));
5680
+ }
5681
+ async function fire() {
5682
+ inFlight = true;
5683
+ try {
5684
+ const book = pricesBookOf(await loader());
5685
+ if (book !== null) {
5686
+ snap = {
5687
+ prices: book,
5688
+ failed: false
5689
+ };
5690
+ failures = 0;
5691
+ } else fail();
5692
+ } catch {
5693
+ fail();
5694
+ }
5695
+ inFlight = false;
5696
+ for (const fn of listeners) fn();
5697
+ }
5698
+ function kick() {
5699
+ if (snap.prices !== null || inFlight || timer !== null) return;
5700
+ fire();
5701
+ }
5702
+ /** The useSyncExternalStore seam: subscribing also kicks the first fetch. */
5703
+ const subscribeModelPrices = (fn) => {
5704
+ listeners.add(fn);
5705
+ kick();
5706
+ return () => {
5707
+ listeners.delete(fn);
5708
+ };
5709
+ };
5710
+ const getModelPricesSnap = () => snap;
5711
+ /** The stats board's read of the price book (null until the fetch lands). */
5712
+ function useModelPrices() {
5713
+ return (0, react.useSyncExternalStore)(subscribeModelPrices, getModelPricesSnap);
5714
+ }
5715
+ //#endregion
5716
+ //#region node_modules/.pnpm/@deepseek-ai+dsh-util-workspace-path@0.1.5-rc.1_@deepseek-ai+cordis@4.0.2/node_modules/@deepseek-ai/dsh-util-workspace-path/lib/index.js
5717
+ /**
5718
+ * The `dsh-resource://file/…` address grammar: how a file is named across the
5719
+ * Sidebar and the resource model, built and parsed without touching a
5720
+ * filesystem.
5721
+ * @module
5722
+ */
5723
+ /** The scheme and type every file address opens with. */
5724
+ const FILE_ADDRESS_PREFIX = "dsh-resource://file/";
5725
+ /** Component-encode one id or path segment, keeping `:` literal for drive letters. */
5726
+ function encodeSegment(segment) {
5727
+ return encodeURIComponent(segment).replace(/%3A/gi, ":");
5728
+ }
5729
+ /** Encode a `/`-separated path segment by segment. */
5730
+ function encodePath(path) {
5731
+ return path.split("/").map(encodeSegment).join("/");
5732
+ }
5733
+ /**
5734
+ * Build the address of a file read through one Session.
5735
+ * @param sessionId - the Session whose Host workspace resolves the path.
5736
+ * @param path - absolute or workspace-relative path; backslashes are normalized to `/`, and leading `./` prefixes are dropped.
5737
+ * @returns the `dsh-resource://file/session/<sessionId>/<path>` address.
5738
+ */
5739
+ function sessionFileAddress(sessionId, path) {
5740
+ const normalized = path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
5741
+ return `${FILE_ADDRESS_PREFIX}session/${encodeSegment(sessionId)}/${encodePath(normalized)}`;
5742
+ }
5743
+ /**
5744
+ * Browser-safe Workspace path and display helpers.
5745
+ * @module @deepseek-ai/dsh-util-workspace-path
5746
+ */
5747
+ /** Whether a path uses a Windows drive or UNC prefix. */
5748
+ function isWindowsStylePath(value) {
5749
+ return /^[A-Za-z]:[/\\]/.test(value) || value.startsWith("\\\\");
5750
+ }
5751
+ /**
5752
+ * Whether a path is absolute in either spelling the Host accepts: POSIX (`/a/b`) or Windows drive or UNC.
5753
+ * @param path - the path to classify.
5754
+ * @returns `true` for an absolute path; `false` for a Workspace-relative one.
5755
+ */
5756
+ function isAbsoluteWorkspacePath(path) {
5757
+ return path.startsWith("/") || isWindowsStylePath(path);
5758
+ }
5759
+ /**
5760
+ * Read the final non-empty segment of a Workspace path for display.
5761
+ * Workspace-label surfaces use this helper instead of deriving another basename.
5762
+ * @param path - Workspace directory path using POSIX or Windows separators.
5763
+ * @returns the final segment, or an empty string for a separator-only path.
5764
+ */
5765
+ function workspaceTitleOf(path) {
5766
+ const trimmed = path.replace(/[/\\]+$/, "");
5767
+ const separator = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
5768
+ return trimmed.slice(separator + 1);
5769
+ }
5770
+ /**
5771
+ * The address for a path as a caller holds it: a relative path, or an absolute
5772
+ * path inside the Session's workspace, becomes a `session`-scoped address; an
5773
+ * absolute path outside it, or one whose workspace root is unknown, keeps its
5774
+ * absolute path in that Session's address.
5775
+ * @param sessionId - the Session the path is read in.
5776
+ * @param cwd - that Session's workspace root, when known.
5777
+ * @param path - absolute or workspace-relative path, in either separator spelling.
5778
+ * @returns the `dsh-resource://file/…` address.
5779
+ */
5780
+ function fileAddressFor(sessionId, cwd, path) {
5781
+ const normalized = path.replace(/\\/g, "/");
5782
+ if (!isAbsoluteWorkspacePath(normalized)) return sessionFileAddress(sessionId, normalized);
5783
+ const root = cwd === void 0 ? "" : cwd.replace(/\\/g, "/").replace(/\/+$/, "");
5784
+ if (root !== "" && normalized === root) return sessionFileAddress(sessionId, "");
5785
+ if (root !== "" && normalized.startsWith(`${root}/`)) return sessionFileAddress(sessionId, normalized.slice(root.length + 1));
5786
+ return sessionFileAddress(sessionId, normalized);
5787
+ }
5788
+ //#endregion
5546
5789
  //#region src/client/overview.ts
5547
5790
  /**
5548
5791
  * The Context Dashboard's data layer: everything the panel renders is derived
@@ -5576,21 +5819,6 @@ window.__ModuleLoader__.load({
5576
5819
  return null;
5577
5820
  }
5578
5821
  }
5579
- /** The running-session tally for the footer entry's badge (0 on any shape failure). */
5580
- function runningCountOf(snapshot) {
5581
- const state = asRecord(snapshot);
5582
- if (state === null) return 0;
5583
- const byId = asRecord(state.byId);
5584
- if (byId === null) return 0;
5585
- let count = 0;
5586
- try {
5587
- for (const id of Object.keys(byId)) {
5588
- const row = asRecord(byId[id]);
5589
- if (row !== null && row.running === true) count++;
5590
- }
5591
- } catch {}
5592
- return count;
5593
- }
5594
5822
  /**
5595
5823
  * The `useWorkspaces` standard prop read — same guarded-hook contract as
5596
5824
  * {@link sessionsSnapshotOf}. The overview joins its session → workspace
@@ -5857,7 +6085,12 @@ window.__ModuleLoader__.load({
5857
6085
  let toolsMs = 0;
5858
6086
  let calls = 0;
5859
6087
  let wallMs = 0;
6088
+ let costSessions = 0;
6089
+ let usageSessions = 0;
5860
6090
  for (const row of rows) {
6091
+ const cost = row.timeline?.cost;
6092
+ if (estimateSessionCost(cost, prices, currency) !== null) costSessions++;
6093
+ if (usageTotalsOf(cost) !== null) usageSessions++;
5861
6094
  turns += turnsOf(row.timeline);
5862
6095
  const timing = row.timeline?.timing;
5863
6096
  toolCalls += timing?.toolCalls ?? 0;
@@ -5871,7 +6104,9 @@ window.__ModuleLoader__.load({
5871
6104
  tokens: totals?.total ?? 0,
5872
6105
  turns,
5873
6106
  cost: estimateSessionCost(usage, prices, currency),
6107
+ costSessions,
5874
6108
  cacheHit: totals === null ? null : cacheHitPercent(totals.cacheRead, totals.input + totals.cacheRead + totals.cacheWrite),
6109
+ usageSessions,
5875
6110
  toolCalls,
5876
6111
  toolsMs,
5877
6112
  calls,
@@ -5948,258 +6183,6 @@ window.__ModuleLoader__.load({
5948
6183
  return t("ov.time.d", { n: Math.floor(hours / 24) });
5949
6184
  }
5950
6185
  //#endregion
5951
- //#region src/client/overviewStore.ts
5952
- function createStore() {
5953
- let open = false;
5954
- const listeners = /* @__PURE__ */ new Set();
5955
- return {
5956
- subscribe: (listener) => {
5957
- listeners.add(listener);
5958
- return () => {
5959
- listeners.delete(listener);
5960
- };
5961
- },
5962
- getSnapshot: () => open,
5963
- set: (next) => {
5964
- if (next === open) return;
5965
- open = next;
5966
- for (const listener of listeners) listener();
5967
- }
5968
- };
5969
- }
5970
- const overviewStore = createStore();
5971
- //#endregion
5972
- //#region src/client/components/overviewButton.tsx
5973
- /**
5974
- * The Context Dashboard's sidebar entry: a footer action stacked directly
5975
- * above Settings (the harness's own foot layout: footer actions, then the
5976
- * settings row), mirroring the Settings trigger's geometry in both column
5977
- * widths. The button carries the plugin emblem and, on the wide
5978
- * column, its label; a badge counts the sessions currently running (the one
5979
- * glanceable live fact a footer can carry). Clicking opens the overview
5980
- * overlay through the shared module store (overviewStore.ts).
5981
- *
5982
- * The per-user `insightsEntry` preference takes the entry down (renders
5983
- * null) without unregistering the seat. The subscription fails open: an
5984
- * absent settings store, an unserved namespace, or a value the plugin
5985
- * cannot understand all leave the entry visible.
5986
- */
5987
- /** Stable subscription faces for the settings-less degrade (no re-subscribes). */
5988
- const subscribeNever = () => () => {};
5989
- const entryShown = () => "show";
5990
- function makeOverviewButton(kit, settings) {
5991
- const { t } = kit;
5992
- const subscribeEntry = settings === void 0 ? subscribeNever : (listener) => settings.store.subscribe(listener);
5993
- const getEntry = settings === void 0 ? entryShown : () => settings.insightsEntry();
5994
- return function OverviewButton(props) {
5995
- const running = runningCountOf(sessionsSnapshotOf(props));
5996
- if ((0, react.useSyncExternalStore)(subscribeEntry, getEntry) === "hide") return null;
5997
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
5998
- type: "button",
5999
- className: props.wide === true ? "lc-ov-entry" : "lc-ov-entry lc-ov-entry-rail",
6000
- title: t("ov.entry"),
6001
- "aria-label": t("ov.entry"),
6002
- onClick: () => {
6003
- overviewStore.set(true);
6004
- },
6005
- children: [
6006
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ContextIcon, {
6007
- size: props.wide === true ? 16 : 18,
6008
- className: "lc-ov-entry-icon"
6009
- }),
6010
- props.wide === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6011
- className: "lc-ov-entry-label",
6012
- children: t("ov.entry")
6013
- }),
6014
- running > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6015
- className: "lc-ov-badge",
6016
- "aria-hidden": "true",
6017
- children: running
6018
- })
6019
- ]
6020
- });
6021
- };
6022
- }
6023
- //#endregion
6024
- //#region node_modules/.pnpm/@opencode-ai+models@0.0.72/node_modules/@opencode-ai/models/dist/error.js
6025
- /**
6026
- * The only error thrown by the models.dev client.
6027
- *
6028
- * - `Transport` — the fetch itself failed (network, DNS, abort). `cause` is the underlying error.
6029
- * - `UnexpectedStatus` — non-2xx response. `cause` is `{ status: number }`.
6030
- * - `MalformedResponse` — the body was empty or not valid JSON. `cause` is the parse error, if any.
6031
- */
6032
- var ModelsDevError = class extends Error {
6033
- reason;
6034
- name = "ModelsDevError";
6035
- constructor(reason, options) {
6036
- super(reason, options);
6037
- this.reason = reason;
6038
- }
6039
- };
6040
- //#endregion
6041
- //#region node_modules/.pnpm/@opencode-ai+models@0.0.72/node_modules/@opencode-ai/models/dist/client.js
6042
- /**
6043
- * Creates a stateless models.dev client. Every method performs exactly one
6044
- * `GET` and nothing is ever cached — callers who want caching should wrap
6045
- * calls with their own policy. For a no-network alternative, see the
6046
- * `@opencode-ai/models/snapshot` entrypoint.
6047
- */
6048
- function make(options = {}) {
6049
- const baseUrl = options.baseUrl ?? "https://models.dev";
6050
- const base = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
6051
- const request = async (path, requestOptions) => {
6052
- const fetch = options.fetch ?? globalThis.fetch;
6053
- const headers = new Headers();
6054
- for (const [key, value] of new Headers(options.headers)) headers.set(key, value);
6055
- for (const [key, value] of new Headers(requestOptions?.headers)) headers.set(key, value);
6056
- let response;
6057
- try {
6058
- response = await fetch(new URL(path, base), {
6059
- method: "GET",
6060
- headers,
6061
- signal: requestOptions?.signal
6062
- });
6063
- } catch (cause) {
6064
- throw new ModelsDevError("Transport", { cause });
6065
- }
6066
- if (!response.ok) {
6067
- try {
6068
- await response.body?.cancel();
6069
- } catch {}
6070
- throw new ModelsDevError("UnexpectedStatus", { cause: { status: response.status } });
6071
- }
6072
- let text;
6073
- try {
6074
- text = await response.text();
6075
- } catch (cause) {
6076
- throw new ModelsDevError("Transport", { cause });
6077
- }
6078
- if (text === "") throw new ModelsDevError("MalformedResponse");
6079
- try {
6080
- return JSON.parse(text);
6081
- } catch (cause) {
6082
- throw new ModelsDevError("MalformedResponse", { cause });
6083
- }
6084
- };
6085
- return {
6086
- /** All providers with their models, pricing, and limits (`/api.json`). */
6087
- providers: (requestOptions) => request("api.json", requestOptions),
6088
- /** Provider-agnostic model metadata (`/models.json`). */
6089
- models: (requestOptions) => request("models.json", requestOptions),
6090
- /** Providers and model metadata in a single request (`/catalog.json`). */
6091
- catalog: (requestOptions) => request("catalog.json", requestOptions)
6092
- };
6093
- }
6094
- //#endregion
6095
- //#region src/client/modelPrices.ts
6096
- /**
6097
- * The client's model-price book (client/cost.ts prices from it): the
6098
- * models.dev registry, fetched through the official @opencode-ai/models
6099
- * SDK. The registry payload is untrusted wire input, so `pricesBookOf`
6100
- * re-proves every field at the boundary — a non-conforming provider/model/
6101
- * cost entry drops whole. The book keeps EVERY provider that prices (keyed
6102
- * by the registry's own provider id), so a dsh provider id outside the
6103
- * rename table still prices by direct passthrough. One fetch per page load,
6104
- * kicked on first subscribe; a failure degrades to a visible state the cost
6105
- * cell notes, with a backed-off automatic retry — never a spinner, and
6106
- * never an unhandled rejection.
6107
- */
6108
- /** One finite non-negative registry figure, or null. */
6109
- function rateOf(value) {
6110
- return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
6111
- }
6112
- /**
6113
- * Extract the whole registry's per-model USD rates from a delivered
6114
- * providers payload (`/api.json`), keyed by the registry's provider ids.
6115
- * Any shape failure skips just that entry; a payload that is not a record
6116
- * at all returns null (the store treats it as a failed fetch and retries).
6117
- */
6118
- function pricesBookOf(value) {
6119
- const data = asRecord(value);
6120
- if (data === null || Array.isArray(data)) return null;
6121
- const book = {};
6122
- for (const providerId of Object.keys(data)) {
6123
- const provider = asRecord(data[providerId]);
6124
- const modelList = provider !== null ? asRecord(provider.models) : null;
6125
- if (modelList === null) continue;
6126
- const models = {};
6127
- for (const id of Object.keys(modelList)) {
6128
- const model = asRecord(modelList[id]);
6129
- const cost = model !== null ? asRecord(model.cost) : null;
6130
- if (cost === null) continue;
6131
- const miss = rateOf(cost.input);
6132
- const out = rateOf(cost.output);
6133
- if (miss === null || out === null) continue;
6134
- models[id] = {
6135
- hit: rateOf(cost.cache_read) ?? miss,
6136
- miss,
6137
- write: rateOf(cost.cache_write) ?? miss,
6138
- out
6139
- };
6140
- }
6141
- if (Object.keys(models).length > 0) book[providerId] = models;
6142
- }
6143
- return book;
6144
- }
6145
- const defaultLoader = () => make().providers();
6146
- /** The retry backoff base; each consecutive failure doubles the wait, capped at 3 doublings. */
6147
- const RETRY_BASE_MS = 3e4;
6148
- let loader = defaultLoader;
6149
- let snap = {
6150
- prices: null,
6151
- failed: false
6152
- };
6153
- let inFlight = false;
6154
- let failures = 0;
6155
- let timer = null;
6156
- const listeners = /* @__PURE__ */ new Set();
6157
- function fail() {
6158
- snap = {
6159
- ...snap,
6160
- failed: true
6161
- };
6162
- failures++;
6163
- timer = setTimeout(() => {
6164
- timer = null;
6165
- fire();
6166
- }, RETRY_BASE_MS * 2 ** Math.min(failures - 1, 3));
6167
- }
6168
- async function fire() {
6169
- inFlight = true;
6170
- try {
6171
- const book = pricesBookOf(await loader());
6172
- if (book !== null) {
6173
- snap = {
6174
- prices: book,
6175
- failed: false
6176
- };
6177
- failures = 0;
6178
- } else fail();
6179
- } catch {
6180
- fail();
6181
- }
6182
- inFlight = false;
6183
- for (const fn of listeners) fn();
6184
- }
6185
- function kick() {
6186
- if (snap.prices !== null || inFlight || timer !== null) return;
6187
- fire();
6188
- }
6189
- /** The useSyncExternalStore seam: subscribing also kicks the first fetch. */
6190
- const subscribeModelPrices = (fn) => {
6191
- listeners.add(fn);
6192
- kick();
6193
- return () => {
6194
- listeners.delete(fn);
6195
- };
6196
- };
6197
- const getModelPricesSnap = () => snap;
6198
- /** The stats board's read of the price book (null until the fetch lands). */
6199
- function useModelPrices() {
6200
- return (0, react.useSyncExternalStore)(subscribeModelPrices, getModelPricesSnap);
6201
- }
6202
- //#endregion
6203
6186
  //#region src/shared/days.ts
6204
6187
  /**
6205
6188
  * The local-day key (`YYYY-MM-DD`) shared by the activity fold (host) and the
@@ -6824,7 +6807,7 @@ window.__ModuleLoader__.load({
6824
6807
  }),
6825
6808
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6826
6809
  className: "lc-stat-sub",
6827
- children: t("ov.kpi.sessionsSub", { n: kpi.sessions })
6810
+ children: t("ov.kpi.sessionsSub", { n: kpi.costSessions })
6828
6811
  })
6829
6812
  ]
6830
6813
  }),
@@ -6841,7 +6824,7 @@ window.__ModuleLoader__.load({
6841
6824
  }),
6842
6825
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6843
6826
  className: "lc-stat-sub",
6844
- children: t("ov.kpi.sessionsSub", { n: kpi.sessions })
6827
+ children: t("ov.kpi.sessionsSub", { n: kpi.usageSessions })
6845
6828
  })
6846
6829
  ]
6847
6830
  }),
@@ -9492,7 +9475,7 @@ window.__ModuleLoader__.load({
9492
9475
  return function PluginInfo() {
9493
9476
  const [latest, setLatest] = (0, react.useState)(null);
9494
9477
  (0, react.useEffect)(() => {
9495
- if ("0.53.1".includes("-dev")) return;
9478
+ if ("0.53.2".includes("-dev")) return;
9496
9479
  let on = true;
9497
9480
  fetchLatestVersion().then((v) => {
9498
9481
  if (on && v) setLatest(v);
@@ -9501,8 +9484,8 @@ window.__ModuleLoader__.load({
9501
9484
  on = false;
9502
9485
  };
9503
9486
  }, []);
9504
- const update = latest !== null && isNewerVersion(latest, "0.53.1") ? latest : null;
9505
- const nameText = "dsh-context (v0.53.1)";
9487
+ const update = latest !== null && isNewerVersion(latest, "0.53.2") ? latest : null;
9488
+ const nameText = "dsh-context (v0.53.2)";
9506
9489
  const nameValue = [nameText];
9507
9490
  if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9508
9491
  className: "lc-pi-update",
@@ -11348,7 +11331,7 @@ window.__ModuleLoader__.load({
11348
11331
  }
11349
11332
  //#endregion
11350
11333
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/overview.css.mjs
11351
- 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;position:relative;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-badge{box-sizing:border-box;background:var(--color-green-500);color:#fff;pointer-events:none;border-radius:999px;justify-content:center;align-items:center;min-width:15px;height:15px;padding:0 4px;font-size:9px;font-weight:700;line-height:1;display:flex;position:absolute;top:1px;right:4px}.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);max-height:min(86vh,900px);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-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-heat{flex:1;justify-content:center;align-items:center;gap:4px;display:flex}.lc-heat-wds,.lc-heat-col{padding-top:13px}.lc-heat-wds{flex-direction:column;gap:3px;display:flex}.lc-heat-wd{width:10px;height:20px;color:var(--dsw-alias-label-secondary);text-align:center;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:13px;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-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}";
11334
+ 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);max-height:min(86vh,900px);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-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-heat{flex:1;justify-content:center;align-items:center;gap:4px;display:flex}.lc-heat-wds,.lc-heat-col{padding-top:13px}.lc-heat-wds{flex-direction:column;gap:3px;display:flex}.lc-heat-wd{width:10px;height:20px;color:var(--dsw-alias-label-secondary);text-align:center;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:13px;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-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}";
11352
11335
  const tagId = "dsh-context/overview.css";
11353
11336
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
11354
11337
  const tag = document.createElement("style");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.53.1",
3
+ "version": "0.53.2",
4
4
  "description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
5
5
  "author": "bowenliang123",
6
6
  "repository": {