dsh-context 0.21.0 → 0.21.1

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 +107 -9
  2. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -67,6 +67,7 @@ var DICT_ZH = {
67
67
  "nodes.empty": "\u5F53\u524D\u6CA1\u6709\u6A21\u578B\u53EF\u89C1\u7684\u6D88\u606F",
68
68
  "loading": "\u6B63\u5728\u8BFB\u53D6\u4F1A\u8BDD\u65E5\u5FD7\u2026",
69
69
  "error": "\u4E0A\u4E0B\u6587\u6570\u636E\u8BFB\u53D6\u5931\u8D25\uFF1A",
70
+ "error.retry": "\u91CD\u8BD5",
70
71
  "footer": "\u4F30\u7B97\u53E3\u5F84\uFF1A\u4E0E dsh \u5185\u7F6E tokenMeter \u76F8\u540C\u7684\u56FA\u5B9A\u5BC6\u5EA6\u542F\u53D1\u5F0F\uFF08\u7EA6 4 \u5B57\u7B26 \u2248 1 token\uFF09\uFF1B\u300C\u5B9E\u9645\u300D\u4E3A\u4F9B\u5E94\u5546\u4E0A\u62A5\u7528\u91CF\u3002",
71
72
  "tip.step": "\u7B2C {t} \u8F6E \xB7 \u7B2C{s}\u6B65",
72
73
  "tip.turn": "\u7B2C {t} \u8F6E \xB7 \u5171 {n} \u6B65",
@@ -177,6 +178,7 @@ var DICT_EN = {
177
178
  "nodes.empty": "No model-visible messages right now",
178
179
  "loading": "Reading the session log\u2026",
179
180
  "error": "Failed to read context data: ",
181
+ "error.retry": "Retry",
180
182
  "footer": "Estimate: same fixed-density heuristic as dsh\u2019s built-in tokenMeter (~4 chars \u2248 1 token); \u201Cactual\u201D is provider-reported usage.",
181
183
  "tip.step": "Turn {t} \xB7 Step {s}",
182
184
  "tip.turn": "Turn {t} \xB7 {n} steps",
@@ -338,12 +340,60 @@ function asRecord(value) {
338
340
  if (value === null || value === void 0 || typeof value !== "object") return null;
339
341
  return value;
340
342
  }
341
- var timelineOf = (value) => asRecord(value);
343
+ function numOf(value) {
344
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
345
+ }
346
+ function objectsOf(value) {
347
+ if (!Array.isArray(value)) return [];
348
+ return value.filter((v) => v !== null && typeof v === "object");
349
+ }
350
+ function timelineOf(value) {
351
+ const data = asRecord(value);
352
+ if (data === null) return null;
353
+ const current = data.current;
354
+ const numericBreakdown = current !== null && typeof current === "object" && ["system", "tools", "user", "inject", "assistant", "tool", "total"].every((k) => typeof current[k] === "number");
355
+ if (numericBreakdown && Array.isArray(data.requests) && Array.isArray(data.events) && Array.isArray(data.nodes) && Array.isArray(data.archive) && Array.isArray(data.toolList)) {
356
+ return data;
357
+ }
358
+ const safeCurrent = current !== null && typeof current === "object" ? current : {};
359
+ const cost = typeof data.cost === "object" && data.cost !== null && !Array.isArray(data.cost) ? data.cost : void 0;
360
+ return {
361
+ ok: true,
362
+ ...typeof data.model === "string" ? { model: data.model } : {},
363
+ ...typeof data.provider === "string" ? { provider: data.provider } : {},
364
+ ...typeof data.contextWindow === "number" ? { contextWindow: data.contextWindow } : {},
365
+ current: {
366
+ system: numOf(safeCurrent.system),
367
+ tools: numOf(safeCurrent.tools),
368
+ user: numOf(safeCurrent.user),
369
+ inject: numOf(safeCurrent.inject),
370
+ assistant: numOf(safeCurrent.assistant),
371
+ tool: numOf(safeCurrent.tool),
372
+ total: numOf(safeCurrent.total)
373
+ },
374
+ toolList: objectsOf(data.toolList),
375
+ requests: objectsOf(data.requests),
376
+ events: objectsOf(data.events),
377
+ nodes: objectsOf(data.nodes),
378
+ droppedNodes: numOf(data.droppedNodes),
379
+ archive: objectsOf(data.archive),
380
+ ...cost !== void 0 ? { cost } : {},
381
+ ...typeof data.surfaceFloor === "number" ? { surfaceFloor: data.surfaceFloor } : {},
382
+ ...typeof data.archiveFloor === "number" ? { archiveFloor: data.archiveFloor } : {}
383
+ };
384
+ }
342
385
  var contextPressureOf = (value) => asRecord(value);
343
386
  var tokenUsageOf = (value) => asRecord(value);
344
387
  function headersOf(value) {
345
388
  const headers = asRecord(value);
346
- return headers !== null && Array.isArray(headers.headers) ? headers : null;
389
+ if (headers === null || !Array.isArray(headers.headers)) return null;
390
+ for (const h2 of headers.headers) {
391
+ if (h2 === null || typeof h2 !== "object") return null;
392
+ const entry = h2;
393
+ if (!Array.isArray(entry.tools)) return null;
394
+ if (entry.system !== void 0 && typeof entry.system !== "string") return null;
395
+ }
396
+ return headers;
347
397
  }
348
398
 
349
399
  // src/client/assemble.ts
@@ -982,6 +1032,34 @@ function makeCurrentComposition(kit, StackedBar, Legend) {
982
1032
  };
983
1033
  }
984
1034
 
1035
+ // src/client/components/errorBoundary.tsx
1036
+ function makeErrorBoundary(t) {
1037
+ return class ErrorBoundary extends React.Component {
1038
+ constructor(props) {
1039
+ super(props);
1040
+ this.state = { error: null };
1041
+ }
1042
+ static getDerivedStateFromError(error) {
1043
+ return { error: error instanceof Error ? error : new Error(String(error)) };
1044
+ }
1045
+ render() {
1046
+ const error = this.state.error;
1047
+ if (error === null) return this.props.children;
1048
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-root" }, /* @__PURE__ */ React.createElement("div", { className: "lc-empty lc-error" }, /* @__PURE__ */ React.createElement("span", null, t("error")), /* @__PURE__ */ React.createElement("code", { className: "lc-error-msg" }, error.message), /* @__PURE__ */ React.createElement(
1049
+ "button",
1050
+ {
1051
+ type: "button",
1052
+ className: "lc-error-retry",
1053
+ onClick: () => {
1054
+ this.setState({ error: null });
1055
+ }
1056
+ },
1057
+ t("error.retry")
1058
+ )));
1059
+ }
1060
+ };
1061
+ }
1062
+
985
1063
  // src/client/components/contextModal.tsx
986
1064
  function makeContextModal(ctx, kit) {
987
1065
  const { t } = kit;
@@ -990,7 +1068,8 @@ function makeContextModal(ctx, kit) {
990
1068
  const Legend = makeLegend(kit);
991
1069
  const CurrentComposition = makeCurrentComposition(kit, StackedBar, Legend);
992
1070
  const ContextBrowser = makeContextBrowser(kit, StackedBar);
993
- return function ContextModal(props) {
1071
+ const ErrorBoundary = makeErrorBoundary(t);
1072
+ function ContextModalBody(props) {
994
1073
  const sessionId = typeof props.sessionId === "string" ? props.sessionId : "";
995
1074
  const open = typeof props.useContextModal === "function" ? props.useContextModal((s) => s) : false;
996
1075
  const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
@@ -1051,6 +1130,9 @@ function makeContextModal(ctx, kit) {
1051
1130
  }
1052
1131
  }
1053
1132
  ))));
1133
+ }
1134
+ return function ContextModal(props) {
1135
+ return h(ErrorBoundary, null, h(ContextModalBody, props));
1054
1136
  };
1055
1137
  }
1056
1138
 
@@ -1273,6 +1355,13 @@ var STYLES = [
1273
1355
  ".lc-node-tokens { color: var(--dsw-alias-label-secondary); }",
1274
1356
  ".lc-nodes-more { color: var(--dsw-alias-label-secondary); padding: 3px 0; }",
1275
1357
  ".lc-empty { color: var(--dsw-alias-label-secondary); padding: 18px 0; text-align: center; }",
1358
+ // Error-boundary fallback card: the tab degrades to this instead of
1359
+ // white-screening; the message runs in mono on a padded chip, and Retry
1360
+ // re-mounts the subtree once a healthy value arrives.
1361
+ ".lc-error { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 40px 16px; }",
1362
+ ".lc-error-msg { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--dsw-alias-state-error-primary); background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 4px 8px; max-width: 100%; overflow-wrap: anywhere; }",
1363
+ ".lc-error-retry { border: 1px solid var(--dsw-alias-border-l1); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; padding: 4px 14px; border-radius: 6px; cursor: pointer; }",
1364
+ ".lc-error-retry:hover { border-color: var(--dsw-alias-label-primary); }",
1276
1365
  ".lc-foot { color: var(--dsw-alias-label-secondary); font-size: 12px; margin-top: 4px; }",
1277
1366
  // ---- /context modal (centered dialog; escapes the composer anchor via fixed positioning) ----
1278
1367
  ".lc-modal-backdrop { position: fixed; inset: 0; z-index: 200; background: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; }",
@@ -1406,7 +1495,9 @@ function fmt(n) {
1406
1495
  return String(Math.round(n));
1407
1496
  }
1408
1497
  function fmtTime(t) {
1409
- return new Date(t).toLocaleTimeString("en-GB", { hour12: false });
1498
+ const d = new Date(t);
1499
+ if (isNaN(d.getTime())) return "\u2014";
1500
+ return d.toLocaleTimeString("en-GB", { hour12: false });
1410
1501
  }
1411
1502
 
1412
1503
  // src/client/components/events.tsx
@@ -1472,7 +1563,7 @@ function makeEventList(kit) {
1472
1563
 
1473
1564
  // src/client/meta.ts
1474
1565
  var PLUGIN_NAME = "dsh-context";
1475
- var PLUGIN_VERSION = true ? "0.21.0" : "0.0.0-dev";
1566
+ var PLUGIN_VERSION = true ? "0.21.1" : "0.0.0-dev";
1476
1567
  var PLUGIN_REPO = true ? "https://github.com/bowenliang123/dsh-context" : "https://github.com/bowenliang123/dsh-context";
1477
1568
  var PLUGIN_REPO_SHORT = PLUGIN_REPO.replace(/^https?:\/\/github\.com\//, "");
1478
1569
 
@@ -1563,7 +1654,7 @@ function estimateSessionCost(usage, currency) {
1563
1654
  const b = fam[period];
1564
1655
  if (b === void 0) continue;
1565
1656
  const p = PRICES[currency][family][period];
1566
- total += (b.cacheRead * p.hit + (b.uncached + b.cacheWrite) * p.miss + b.output * p.out) / 1e6;
1657
+ total += (numOf(b.cacheRead) * p.hit + (numOf(b.uncached) + numOf(b.cacheWrite)) * p.miss + numOf(b.output) * p.out) / 1e6;
1567
1658
  any = true;
1568
1659
  }
1569
1660
  }
@@ -1586,9 +1677,12 @@ function formatPriceRate(amount, currency) {
1586
1677
 
1587
1678
  // src/client/components/statsBoard.tsx
1588
1679
  function cacheHitPercent(usage) {
1589
- const billed = usage.uncachedInputTokens + usage.cacheReadTokens + usage.cacheWriteTokens;
1680
+ const uncached = numOf(usage.uncachedInputTokens);
1681
+ const reads = numOf(usage.cacheReadTokens);
1682
+ const writes = numOf(usage.cacheWriteTokens);
1683
+ const billed = uncached + reads + writes;
1590
1684
  if (billed === 0) return null;
1591
- const hundredths = Math.trunc(usage.cacheReadTokens / billed * 1e4 + 1e-9);
1685
+ const hundredths = Math.trunc(reads / billed * 1e4 + 1e-9);
1592
1686
  return `${Math.floor(hundredths / 100)}.${String(hundredths % 100).padStart(2, "0")}`;
1593
1687
  }
1594
1688
  function makeStatsBoard(kit) {
@@ -1811,7 +1905,8 @@ function makeContextView(ctx, kit) {
1811
1905
  const StatsBoard = makeStatsBoard(kit);
1812
1906
  const PluginInfo = makePluginInfo(kit);
1813
1907
  const ContextBrowser = makeContextBrowser(kit, StackedBar);
1814
- return function ContextView(props) {
1908
+ const ErrorBoundary = makeErrorBoundary(t);
1909
+ function ContextViewBody(props) {
1815
1910
  const sessionId = props.sessionId;
1816
1911
  const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
1817
1912
  const pressure = typeof props.useProjection === "function" ? contextPressureOf(props.useProjection("contextPressure")) : null;
@@ -1959,6 +2054,9 @@ function makeContextView(ctx, kit) {
1959
2054
  },
1960
2055
  t("kind." + k)
1961
2056
  )))), /* @__PURE__ */ React.createElement(EventList, { events: shownEvents })), /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, /* @__PURE__ */ React.createElement("span", { className: "lc-card-title-text" }, t("nodes.title")), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("nodes.hint"))), /* @__PURE__ */ React.createElement(NodeList, { nodes, dropped: data.droppedNodes || 0 }))), /* @__PURE__ */ React.createElement("div", { className: "lc-foot" }, t("footer")));
2057
+ }
2058
+ return function ContextView(props) {
2059
+ return h(ErrorBoundary, null, h(ContextViewBody, props));
1962
2060
  };
1963
2061
  }
1964
2062
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.21.0",
3
+ "version": "0.21.1",
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": {