dsh-context 0.10.1 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -40,7 +40,7 @@ Type `/context` (or pick it from the `/` menu) and press Enter: a centered dialo
40
40
 
41
41
  ### 📊 Context stats — the session at a glance
42
42
 
43
- Turns, steps, how much context has been recycled by compactions and prunes, how many injections happened, model switches, and the estimated total tokens sent — next to the provider-reported actuals, so you can see how the estimate holds up.
43
+ Turns, steps, how many injections, compactions, and prunes have happened.
44
44
 
45
45
  ### 🧱 Current composition — what's in the window right now
46
46
 
package/lib/client.js CHANGED
@@ -25,13 +25,15 @@ var DICT_ZH = {
25
25
  "stats.hint": "\u7EDF\u8BA1\u5F53\u524D\u4FDD\u7559\u7684\u5386\u53F2\u7A97\u53E3\uFF08\u4E0E\u8D8B\u52BF\u56FE\u4E00\u81F4\uFF09",
26
26
  "stats.turns": "\u8F6E\u6B21",
27
27
  "stats.steps": "\u6B65\u6570",
28
- "stats.recycled": "\u2702 \u56DE\u6536",
29
- "stats.recycleSub": "\u538B\u7F29 {c} \u6B21 \xB7 \u526A\u679D {p} \u6B21",
30
- "stats.injects": "\uFF0B \u6CE8\u5165",
31
- "stats.switches": "\u21C4 \u6A21\u578B\u5207\u6362",
32
- "stats.est": "\u2248 \u7D2F\u8BA1\u4F30\u7B97",
33
- "stats.actualPrompt": "\u5B9E\u9645 prompt",
34
- "stats.output": "\u8F93\u51FA",
28
+ "stats.injects": "\u6CE8\u5165",
29
+ "stats.compactions": "\u538B\u7F29",
30
+ "stats.prunes": "\u526A\u679D",
31
+ "plugin.title": "\u63D2\u4EF6\u4FE1\u606F",
32
+ "plugin.hint": "\u611F\u8C22\u4F7F\u7528 \xB7 \u6B22\u8FCE Star \u4E0E\u53CD\u9988 \u2B50",
33
+ "plugin.name": "\u63D2\u4EF6",
34
+ "plugin.version": "\u7248\u672C",
35
+ "plugin.updateBadge": "Update!",
36
+ "plugin.updateTip": "\u65B0\u7248\u672C v{v} \u53EF\u7528 \xB7 \u6267\u884C dsh plugin --profile web add dsh-context \u66F4\u65B0\uFF08\u70B9\u51FB\u524D\u5F80 npm\uFF09",
35
37
  "tools.top": "\u5DE5\u5177\u5B9A\u4E49 Top\uFF1A",
36
38
  "tools.more": "\u7B49 {n} \u4E2A",
37
39
  "trend.title": "\u5386\u53F2\u8D8B\u52BF",
@@ -98,13 +100,15 @@ var DICT_EN = {
98
100
  "stats.hint": "Over the retained history window (same as the History chart)",
99
101
  "stats.turns": "Turns",
100
102
  "stats.steps": "Steps",
101
- "stats.recycled": "\u2702 Recycled",
102
- "stats.recycleSub": "{c} compactions \xB7 {p} prunes",
103
- "stats.injects": "\uFF0B Injections",
104
- "stats.switches": "\u21C4 Model switches",
105
- "stats.est": "\u2248 Estimated total",
106
- "stats.actualPrompt": "actual prompt",
107
- "stats.output": "output",
103
+ "stats.injects": "Injections",
104
+ "stats.compactions": "Compactions",
105
+ "stats.prunes": "Prunes",
106
+ "plugin.title": "Plugin info",
107
+ "plugin.hint": "Thanks for using \u2014 stars & feedback welcome \u2B50",
108
+ "plugin.name": "Plugin",
109
+ "plugin.version": "Version",
110
+ "plugin.updateBadge": "Update!",
111
+ "plugin.updateTip": "v{v} available \u2014 run `dsh plugin --profile web add dsh-context` to update (click for npm)",
108
112
  "tools.top": "Top tool schemas:",
109
113
  "tools.more": "of {n}",
110
114
  "trend.title": "History",
@@ -245,15 +249,14 @@ function anchoredParts(parts, target) {
245
249
  }
246
250
 
247
251
  // src/client/headline.ts
248
- function headlineOf(data) {
252
+ function headlineOf(data, pressure = null) {
249
253
  const current = data.current;
250
- const occ = data.occupancy;
251
- const projected = occ !== void 0 && typeof occ.projectedTokens === "number" ? occ.projectedTokens : void 0;
254
+ const projected = pressure !== null && typeof pressure.projectedTokens === "number" ? pressure.projectedTokens : void 0;
252
255
  const requests = data.requests || [];
253
256
  const lastReq = requests.length > 0 ? requests[requests.length - 1] : null;
254
257
  const derived = lastReq !== null && typeof lastReq.prompt === "number" ? lastReq.prompt + (current.total - lastReq.total) : void 0;
255
258
  const occupancyTokens = projected ?? derived ?? null;
256
- const window2 = occ !== void 0 && typeof occ.contextWindow === "number" ? occ.contextWindow : data.contextWindow;
259
+ const window2 = pressure !== null && typeof pressure.contextWindow === "number" ? pressure.contextWindow : data.contextWindow;
257
260
  const tokens = occupancyTokens ?? current.total;
258
261
  const pct = window2 !== void 0 && window2 > 0 ? Math.min(100, Math.round(tokens / window2 * 100)) : null;
259
262
  const parts = anchoredParts(partsOf(current), occupancyTokens !== null && tokens > 0 ? tokens : null);
@@ -265,6 +268,10 @@ function timelineOf(value) {
265
268
  if (value === null || value === void 0 || typeof value !== "object") return null;
266
269
  return value;
267
270
  }
271
+ function contextPressureOf(value) {
272
+ if (value === null || value === void 0 || typeof value !== "object") return null;
273
+ return value;
274
+ }
268
275
 
269
276
  // src/client/react.ts
270
277
  var React = require("react");
@@ -581,6 +588,7 @@ function makeContextModal(ctx, kit) {
581
588
  const sessionId = typeof props.sessionId === "string" ? props.sessionId : "";
582
589
  const open = typeof props.useContextModal === "function" ? props.useContextModal((s) => s) : false;
583
590
  const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
591
+ const pressure = typeof props.useProjection === "function" ? contextPressureOf(props.useProjection("contextPressure")) : null;
584
592
  const [selectedSeq, setSelectedSeq] = React.useState(null);
585
593
  const [hoveredSeq, setHoveredSeq] = React.useState(null);
586
594
  const [hoverCat, setHoverCat] = React.useState(null);
@@ -624,7 +632,7 @@ function makeContextModal(ctx, kit) {
624
632
  const i = turns.indexOf(req);
625
633
  return i >= 0 ? markers[i] : void 0;
626
634
  };
627
- const head = data !== null ? headlineOf(data) : null;
635
+ const head = data !== null ? headlineOf(data, pressure) : null;
628
636
  return /* @__PURE__ */ React.createElement("div", { className: "lc-modal-backdrop", onClick: close }, /* @__PURE__ */ React.createElement("div", { className: "lc-modal-card", onClick: (ev) => {
629
637
  ev.stopPropagation();
630
638
  } }, /* @__PURE__ */ React.createElement("div", { className: "lc-modal-head" }, /* @__PURE__ */ React.createElement("span", { className: "lc-modal-title" }, t("tab")), data !== null && (data.model || data.provider) ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : "")) : null, /* @__PURE__ */ React.createElement("button", { className: "lc-modal-close", "aria-label": t("cmd.close"), onClick: close }, "\xD7")), data === null || head === null ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("loading")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "lc-overview-num" }, /* @__PURE__ */ React.createElement("b", null, fmt3(head.tokens)), /* @__PURE__ */ React.createElement("span", null, head.window ? " / " + fmt3(head.window) + " " + tr("overview.ofWindow", { p: head.pct ?? 0 }) : " " + t("overview.estimate")), !head.estimated ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("overview.splitEst")) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: head.parts, height: 16, max: head.window, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement(Legend, { parts: head.parts, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement("div", { className: "lc-card-title lc-modal-trend" }, t("trend.title")), turns.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("trend.empty")) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(
@@ -652,8 +660,8 @@ var STYLES = [
652
660
  ".lc-card-title { font-weight: 600; margin-bottom: 10px; display: flex; align-items: baseline; gap: 8px; }",
653
661
  ".lc-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: 8px; }",
654
662
  ".lc-stat { background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; padding: 8px 10px; display: flex; flex-direction: column; gap: 2px; min-width: 0; }",
655
- ".lc-stat-label { color: var(--dsw-alias-label-secondary); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
656
- ".lc-stat-value { color: var(--dsw-alias-label-primary); font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
663
+ ".lc-stat-label { color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
664
+ ".lc-stat-value { color: var(--dsw-alias-label-primary); font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: center; }",
657
665
  ".lc-stat-sub { color: var(--dsw-alias-label-secondary); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
658
666
  ".lc-card-sub { font-weight: 400; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
659
667
  ".lc-gran { margin-left: auto; display: flex; gap: 2px; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 6px; padding: 1px; }",
@@ -742,6 +750,19 @@ var STYLES = [
742
750
  ".lc-detail-pct { width: 34px; text-align: right; color: var(--dsw-alias-label-secondary); }",
743
751
  ".lc-cols { display: flex; gap: 14px; flex-wrap: wrap; }",
744
752
  ".lc-col { flex: 1; min-width: 280px; }",
753
+ // Head row: the stats board (five cells) takes the wider share, the plugin
754
+ // card (three cells) the narrower — both wrap onto their own line when tight.
755
+ ".lc-col-stats { flex: 3; min-width: 380px; }",
756
+ ".lc-col-plugin { flex: 2; min-width: 260px; }",
757
+ ".lc-stat-link { color: var(--dsw-alias-brand-primary); text-decoration: none; font-weight: 600; }",
758
+ // Whole-cell link (Plugin info): the anchor carries the stat-cell chrome,
759
+ // so the entire padded box is one hit target; hovering anywhere on it
760
+ // underlines the value and tints the border for affordance.
761
+ ".lc-stat-cell { text-decoration: none; cursor: pointer; transition: border-color 120ms ease; }",
762
+ ".lc-stat-cell:hover { border-color: var(--dsw-alias-brand-primary); }",
763
+ ".lc-stat:hover .lc-stat-link { text-decoration: underline; }",
764
+ // "Update!" pill inside the version cell (update = good news -> brand fill).
765
+ ".lc-update-badge { margin-left: 6px; font-size: 10px; font-weight: 600; background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); border-radius: 4px; padding: 1px 6px; }",
745
766
  ".lc-events, .lc-nodes { display: flex; flex-direction: column; gap: 2px; max-height: 320px; overflow-y: auto; }",
746
767
  ".lc-event { display: flex; align-items: center; gap: 8px; padding: 3px 0; }",
747
768
  ".lc-event-icon { width: 18px; text-align: center; color: var(--dsw-alias-state-warn-primary); }",
@@ -863,36 +884,63 @@ function makeNodeList(kit) {
863
884
  };
864
885
  }
865
886
 
887
+ // src/client/meta.ts
888
+ var PLUGIN_NAME = "dsh-context";
889
+ var PLUGIN_VERSION = true ? "0.10.3" : "0.0.0-dev";
890
+ var PLUGIN_REPO = true ? "https://github.com/bowenliang123/dsh-context" : "https://github.com/bowenliang123/dsh-context";
891
+ var PLUGIN_NPM = "https://www.npmjs.com/package/" + PLUGIN_NAME;
892
+ var PLUGIN_REPO_SHORT = PLUGIN_REPO.replace(/^https?:\/\/github\.com\//, "");
893
+
894
+ // src/client/components/pluginInfo.tsx
895
+ var latestCache;
896
+ function makePluginInfo(kit) {
897
+ const { t, tr } = kit;
898
+ return function PluginInfo() {
899
+ const [latest, setLatest] = React.useState(latestCache);
900
+ React.useEffect(() => {
901
+ if (latestCache !== void 0) return void 0;
902
+ let alive = true;
903
+ fetch("https://registry.npmjs.org/" + PLUGIN_NAME + "/latest").then((res) => res.ok ? res.json() : null).then((body) => {
904
+ if (!alive) return;
905
+ latestCache = body && typeof body.version === "string" ? body.version : null;
906
+ setLatest(latestCache);
907
+ }).catch(() => {
908
+ if (!alive) return;
909
+ latestCache = null;
910
+ setLatest(null);
911
+ });
912
+ return () => {
913
+ alive = false;
914
+ };
915
+ }, []);
916
+ const update = typeof latest === "string" && latest !== PLUGIN_VERSION;
917
+ const cell = (label, value, href, title) => /* @__PURE__ */ React.createElement("a", { className: "lc-stat lc-stat-cell", href, target: "_blank", rel: "noreferrer", title }, /* @__PURE__ */ React.createElement("span", { className: "lc-stat-label" }, label), /* @__PURE__ */ React.createElement("b", { className: "lc-stat-value lc-stat-link" }, value));
918
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col lc-col-plugin" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("plugin.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("plugin.hint"))), /* @__PURE__ */ React.createElement("div", { className: "lc-stats" }, cell(t("plugin.name"), PLUGIN_NAME, PLUGIN_REPO), cell(
919
+ t("plugin.version"),
920
+ /* @__PURE__ */ React.createElement(React.Fragment, null, "v" + PLUGIN_VERSION, update ? /* @__PURE__ */ React.createElement("span", { className: "lc-update-badge" }, t("plugin.updateBadge")) : null),
921
+ PLUGIN_NPM,
922
+ update ? tr("plugin.updateTip", { v: latest ?? "" }) : void 0
923
+ ), cell("GitHub", PLUGIN_REPO_SHORT + " \u2197", PLUGIN_REPO)));
924
+ };
925
+ }
926
+
866
927
  // src/client/components/statsBoard.tsx
867
928
  function makeStatsBoard(kit) {
868
- const { t, tr, fmt: fmt3 } = kit;
929
+ const { t, fmt: fmt3 } = kit;
869
930
  return function StatsBoard(props) {
870
931
  const turns = /* @__PURE__ */ new Set();
871
- let steps = 0, compactions = 0, prunes = 0, injects = 0, switches = 0;
872
- let recycled = 0, est = 0, prompt = 0, output = 0;
932
+ let steps = 0, compactions = 0, prunes = 0, injects = 0;
873
933
  for (const req of props.requests) {
874
934
  turns.add(req.turn ?? 0);
875
935
  steps++;
876
- est += req.total;
877
- if (typeof req.prompt === "number") prompt += req.prompt;
878
- if (typeof req.output === "number") output += req.output;
879
936
  }
880
937
  for (const ev of props.events) {
881
- if (ev.kind === "compaction") {
882
- compactions++;
883
- recycled += ev.tokens || 0;
884
- } else if (ev.kind === "prune") {
885
- prunes++;
886
- recycled += ev.tokens || 0;
887
- } else if (ev.kind === "inject") injects++;
888
- else if (ev.kind === "model") switches++;
938
+ if (ev.kind === "compaction") compactions++;
939
+ else if (ev.kind === "prune") prunes++;
940
+ else if (ev.kind === "inject") injects++;
889
941
  }
890
- const cell = (label, value, sub) => /* @__PURE__ */ React.createElement("div", { className: "lc-stat" }, /* @__PURE__ */ React.createElement("span", { className: "lc-stat-label" }, label), /* @__PURE__ */ React.createElement("b", { className: "lc-stat-value" }, value), sub !== void 0 ? /* @__PURE__ */ React.createElement("span", { className: "lc-stat-sub" }, sub) : null);
891
- return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("stats.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("stats.hint"))), /* @__PURE__ */ React.createElement("div", { className: "lc-stats" }, cell(t("stats.turns"), fmt3(turns.size)), cell(t("stats.steps"), fmt3(steps)), cell(
892
- t("stats.recycled"),
893
- recycled > 0 ? "\u2212" + fmt3(recycled) : "0",
894
- tr("stats.recycleSub", { c: compactions, p: prunes })
895
- ), cell(t("stats.injects"), fmt3(injects)), cell(t("stats.switches"), fmt3(switches)), cell(t("stats.est"), "\u2248 " + fmt3(est)), cell(t("stats.actualPrompt"), fmt3(prompt)), cell(t("stats.output"), fmt3(output))));
942
+ const cell = (label, value) => /* @__PURE__ */ React.createElement("div", { className: "lc-stat" }, /* @__PURE__ */ React.createElement("span", { className: "lc-stat-label" }, label), /* @__PURE__ */ React.createElement("b", { className: "lc-stat-value" }, value));
943
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col lc-col-stats" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("stats.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("stats.hint"))), /* @__PURE__ */ React.createElement("div", { className: "lc-stats" }, cell(t("stats.turns"), fmt3(turns.size)), cell(t("stats.steps"), fmt3(steps)), cell(t("stats.injects"), fmt3(injects)), cell(t("stats.compactions"), fmt3(compactions)), cell(t("stats.prunes"), fmt3(prunes))));
896
944
  };
897
945
  }
898
946
 
@@ -907,9 +955,11 @@ function makeContextView(ctx, kit) {
907
955
  const EventList = makeEventList(kit);
908
956
  const NodeList = makeNodeList(kit);
909
957
  const StatsBoard = makeStatsBoard(kit);
958
+ const PluginInfo = makePluginInfo(kit);
910
959
  return function ContextView(props) {
911
960
  const sessionId = props.sessionId;
912
961
  const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
962
+ const pressure = typeof props.useProjection === "function" ? contextPressureOf(props.useProjection("contextPressure")) : null;
913
963
  const [selectedSeq, setSelectedSeq] = React.useState(null);
914
964
  const [hoveredSeq, setHoveredSeq] = React.useState(null);
915
965
  const [hoverTurn, setHoverTurn] = React.useState(null);
@@ -969,8 +1019,8 @@ function makeContextView(ctx, kit) {
969
1019
  break;
970
1020
  }
971
1021
  }
972
- const head = headlineOf(data);
973
- return /* @__PURE__ */ React.createElement("div", { className: "lc-root", ref: rootRef }, /* @__PURE__ */ React.createElement(StatsBoard, { requests, events }), /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("overview.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : ""))), /* @__PURE__ */ React.createElement("div", { className: "lc-overview-num" }, /* @__PURE__ */ React.createElement("b", null, fmt3(head.tokens)), /* @__PURE__ */ React.createElement("span", null, head.window ? " / " + fmt3(head.window) + " " + tr("overview.ofWindow", { p: head.pct ?? 0 }) : " " + t("overview.estimate")), !head.estimated ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("overview.splitEst")) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: head.parts, height: 16, max: head.window, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement(Legend, { parts: head.parts, hoverKey: hoverCat, onHoverKey: setHoverCat }), data.toolList && data.toolList.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-tools" }, t("tools.top"), data.toolList.slice().sort((a, b) => b.tokens - a.tokens).slice(0, 5).map((tool) => {
1022
+ const head = headlineOf(data, pressure);
1023
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-root", ref: rootRef }, /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement(StatsBoard, { requests, events }), /* @__PURE__ */ React.createElement(PluginInfo, null)), /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("overview.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, (data.model ? data.model : "") + (data.provider ? " \xB7 " + data.provider : ""))), /* @__PURE__ */ React.createElement("div", { className: "lc-overview-num" }, /* @__PURE__ */ React.createElement("b", null, fmt3(head.tokens)), /* @__PURE__ */ React.createElement("span", null, head.window ? " / " + fmt3(head.window) + " " + tr("overview.ofWindow", { p: head.pct ?? 0 }) : " " + t("overview.estimate")), !head.estimated ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("overview.splitEst")) : null), /* @__PURE__ */ React.createElement(StackedBar, { parts: head.parts, height: 16, max: head.window, hoverKey: hoverCat, onHoverKey: setHoverCat }), /* @__PURE__ */ React.createElement(Legend, { parts: head.parts, hoverKey: hoverCat, onHoverKey: setHoverCat }), data.toolList && data.toolList.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: "lc-tools" }, t("tools.top"), data.toolList.slice().sort((a, b) => b.tokens - a.tokens).slice(0, 5).map((tool) => {
974
1024
  return /* @__PURE__ */ React.createElement("span", { key: tool.name, className: "lc-tool-chip" }, tool.name + " " + fmt3(tool.tokens));
975
1025
  }), data.toolList.length > 5 ? /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, " " + tr("tools.more", { n: data.toolList.length })) : null) : null), /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("trend.title"), /* @__PURE__ */ React.createElement("span", { className: "lc-card-sub" }, t("trend.hint")), /* @__PURE__ */ React.createElement("div", { className: "lc-gran" }, /* @__PURE__ */ React.createElement(
976
1026
  "button",
package/lib/index.js CHANGED
@@ -1,5 +1,32 @@
1
- // src/host/timeline.ts
1
+ // src/host/config.ts
2
2
  import { z } from "zod";
3
+ var DEFAULT_BOUNDS = {
4
+ maxRequestSteps: 1500,
5
+ maxKeptTurns: 300,
6
+ maxEvents: 400,
7
+ maxNodes: 200
8
+ };
9
+ var Config = z.preprocess(
10
+ (v) => v ?? {},
11
+ z.object({
12
+ maxRequestSteps: z.number().int().min(1).default(DEFAULT_BOUNDS.maxRequestSteps),
13
+ maxKeptTurns: z.number().int().min(1).default(DEFAULT_BOUNDS.maxKeptTurns),
14
+ maxEvents: z.number().int().min(1).default(DEFAULT_BOUNDS.maxEvents),
15
+ maxNodes: z.number().int().min(1).default(DEFAULT_BOUNDS.maxNodes)
16
+ }).strict()
17
+ );
18
+ function resolveBounds(config) {
19
+ const parsed = Config.parse(config ?? {});
20
+ return {
21
+ maxRequestSteps: parsed.maxRequestSteps,
22
+ maxKeptTurns: parsed.maxKeptTurns,
23
+ maxEvents: parsed.maxEvents,
24
+ maxNodes: parsed.maxNodes
25
+ };
26
+ }
27
+
28
+ // src/host/timeline.ts
29
+ import { z as z2 } from "zod";
3
30
 
4
31
  // src/host/pricing.ts
5
32
  var CHARS_PER_TOKEN = 4;
@@ -65,10 +92,6 @@ function isInjection(source) {
65
92
  }
66
93
 
67
94
  // src/host/fold.ts
68
- var MAX_REQUEST_STEPS = 1500;
69
- var MAX_KEPT_TURNS = 300;
70
- var MAX_EVENTS = 400;
71
- var MAX_NODES = 200;
72
95
  function trimToLastTurns(requests, maxTurns) {
73
96
  let runs = 0;
74
97
  let start = requests.length;
@@ -95,14 +118,14 @@ function countTurnRuns(requests) {
95
118
  }
96
119
  return runs;
97
120
  }
98
- function trimState(st) {
99
- if (countTurnRuns(st.requests) > MAX_KEPT_TURNS) {
100
- st.requests = trimToLastTurns(st.requests, MAX_KEPT_TURNS);
121
+ function trimState(st, bounds) {
122
+ if (countTurnRuns(st.requests) > bounds.maxKeptTurns) {
123
+ st.requests = trimToLastTurns(st.requests, bounds.maxKeptTurns);
101
124
  }
102
- if (st.requests.length > MAX_REQUEST_STEPS) {
103
- st.requests = st.requests.slice(-MAX_REQUEST_STEPS);
125
+ if (st.requests.length > bounds.maxRequestSteps) {
126
+ st.requests = st.requests.slice(-bounds.maxRequestSteps);
104
127
  }
105
- if (st.events.length > MAX_EVENTS) st.events = st.events.slice(-MAX_EVENTS);
128
+ if (st.events.length > bounds.maxEvents) st.events = st.events.slice(-bounds.maxEvents);
106
129
  }
107
130
  function createTimelineState() {
108
131
  return {
@@ -115,9 +138,6 @@ function createTimelineState() {
115
138
  provider: void 0,
116
139
  lastModel: void 0,
117
140
  contextWindow: void 0,
118
- pressureTokens: void 0,
119
- sampledSurfaceTokens: void 0,
120
- occupancyWindow: void 0,
121
141
  requests: [],
122
142
  events: [],
123
143
  callNames: {}
@@ -207,17 +227,7 @@ function applySurface(st, ev, type, data, message) {
207
227
  st.sums[cat] += node.tokens;
208
228
  return node;
209
229
  }
210
- function pressureOf(usage) {
211
- if (usage === void 0 || typeof usage.inputTokens !== "number") return void 0;
212
- return usage.inputTokens + (usage.cacheReadTokens || 0) + (usage.cacheWriteTokens || 0);
213
- }
214
- function sampleUsage(st, usage) {
215
- const pressureTokens = pressureOf(usage);
216
- if (pressureTokens === void 0) return;
217
- st.pressureTokens = pressureTokens;
218
- st.sampledSurfaceTokens = st.sums.user + st.sums.inject + st.sums.assistant + st.sums.tool;
219
- }
220
- function applyTimeline(state, event) {
230
+ function applyTimeline(state, event, bounds) {
221
231
  let st;
222
232
  const ensure = () => st ??= {
223
233
  ...state,
@@ -253,7 +263,6 @@ function applyTimeline(state, event) {
253
263
  if (data && typeof data.contextWindow === "number") s.contextWindow = data.contextWindow;
254
264
  if (data && typeof data.model === "string") s.model = data.model;
255
265
  if (data && typeof data.provider === "string") s.provider = data.provider;
256
- s.occupancyWindow = data && typeof data.contextWindow === "number" ? data.contextWindow : void 0;
257
266
  break;
258
267
  }
259
268
  case "tool/call": {
@@ -292,18 +301,9 @@ function applyTimeline(state, event) {
292
301
  applySurface(s, event, event.type, data, toolMsg);
293
302
  break;
294
303
  }
295
- case "assistant/chunk": {
296
- const chunk = data?.chunk;
297
- if (chunk !== void 0 && chunk.type === "usage") {
298
- const s = ensure();
299
- sampleUsage(s, chunk.usage);
300
- }
301
- break;
302
- }
303
304
  case "assistant/message": {
304
305
  const usage = data?.usage;
305
306
  const s = ensure();
306
- sampleUsage(s, usage);
307
307
  const total = s.systemTokens + s.toolsTokens + s.sums.user + s.sums.inject + s.sums.assistant + s.sums.tool;
308
308
  const record = {
309
309
  turn: data && typeof data.turn === "number" ? data.turn : void 0,
@@ -346,14 +346,13 @@ function applyTimeline(state, event) {
346
346
  return state;
347
347
  }
348
348
  if (st !== void 0) {
349
- trimState(st);
349
+ trimState(st, bounds);
350
350
  return st;
351
351
  }
352
352
  return state;
353
353
  }
354
- function buildTimelineView(state) {
354
+ function buildTimelineView(state, bounds) {
355
355
  const surfaceTotal = state.sums.user + state.sums.inject + state.sums.assistant + state.sums.tool;
356
- const projectedTokens = state.pressureTokens !== void 0 && state.sampledSurfaceTokens !== void 0 ? Math.max(0, state.pressureTokens + surfaceTotal - state.sampledSurfaceTokens) : void 0;
357
356
  const result = {
358
357
  ok: true,
359
358
  model: state.model,
@@ -368,21 +367,14 @@ function buildTimelineView(state) {
368
367
  tool: state.sums.tool,
369
368
  total: surfaceTotal + state.systemTokens + state.toolsTokens
370
369
  },
371
- occupancy: {
372
- ...state.pressureTokens === void 0 ? {} : { pressureTokens: state.pressureTokens },
373
- surfaceTokens: surfaceTotal,
374
- ...state.sampledSurfaceTokens === void 0 ? {} : { sampledSurfaceTokens: state.sampledSurfaceTokens },
375
- ...projectedTokens === void 0 ? {} : { projectedTokens },
376
- ...state.occupancyWindow === void 0 ? {} : { contextWindow: state.occupancyWindow }
377
- },
378
370
  toolList: state.toolList,
379
371
  requests: state.requests.map((r) => ({ ...r })),
380
372
  events: state.events.map((e) => ({ ...e })),
381
373
  nodes: [],
382
374
  droppedNodes: 0
383
375
  };
384
- result.droppedNodes = Math.max(0, state.surface.length - MAX_NODES);
385
- result.nodes = state.surface.slice(-MAX_NODES);
376
+ result.droppedNodes = Math.max(0, state.surface.length - bounds.maxNodes);
377
+ result.nodes = state.surface.slice(-bounds.maxNodes);
386
378
  const requests = result.requests;
387
379
  const events = result.events;
388
380
  let ri = 0;
@@ -403,97 +395,95 @@ function buildTimelineView(state) {
403
395
  }
404
396
 
405
397
  // src/host/timeline.ts
406
- var surfaceNodeSchema = z.object({
407
- seq: z.number().int().nonnegative(),
408
- time: z.number().optional(),
409
- cat: z.enum(["user", "inject", "assistant", "tool"]),
410
- tokens: z.number().int().nonnegative(),
411
- form: z.string().optional(),
412
- text: z.string().optional(),
413
- tool: z.string().optional(),
414
- err: z.boolean().optional(),
415
- skill: z.string().optional(),
416
- calls: z.array(z.string()).optional()
417
- }).strict();
418
- var requestRecordSchema = z.object({
419
- turn: z.number().optional(),
420
- step: z.number().optional(),
421
- time: z.number(),
422
- seq: z.number(),
423
- system: z.number().int().nonnegative(),
424
- tools: z.number().int().nonnegative(),
425
- user: z.number().int().nonnegative(),
426
- inject: z.number().int().nonnegative(),
427
- assistant: z.number().int().nonnegative(),
428
- tool: z.number().int().nonnegative(),
429
- total: z.number().int().nonnegative(),
430
- prompt: z.number().int().nonnegative().optional(),
431
- output: z.number().int().nonnegative().optional(),
432
- stepCount: z.number().int().positive().optional()
398
+ var surfaceNodeSchema = z2.object({
399
+ seq: z2.number().int().nonnegative(),
400
+ time: z2.number().optional(),
401
+ cat: z2.enum(["user", "inject", "assistant", "tool"]),
402
+ tokens: z2.number().int().nonnegative(),
403
+ form: z2.string().optional(),
404
+ text: z2.string().optional(),
405
+ tool: z2.string().optional(),
406
+ err: z2.boolean().optional(),
407
+ skill: z2.string().optional(),
408
+ calls: z2.array(z2.string()).optional()
433
409
  }).strict();
434
- var contextEventSchema = z.object({
435
- seq: z.number(),
436
- time: z.number(),
437
- kind: z.enum(["compaction", "prune", "inject", "model"]),
438
- form: z.string().optional(),
439
- tokens: z.number().optional(),
440
- count: z.number().optional(),
441
- sub: z.string().optional(),
442
- name: z.string().optional(),
443
- from: z.string().optional(),
444
- to: z.string().optional(),
445
- fromTurn: z.number().optional(),
446
- fromStep: z.number().optional(),
447
- turn: z.number().optional(),
448
- step: z.number().optional()
410
+ var requestRecordSchema = z2.object({
411
+ turn: z2.number().optional(),
412
+ step: z2.number().optional(),
413
+ time: z2.number(),
414
+ seq: z2.number(),
415
+ system: z2.number().int().nonnegative(),
416
+ tools: z2.number().int().nonnegative(),
417
+ user: z2.number().int().nonnegative(),
418
+ inject: z2.number().int().nonnegative(),
419
+ assistant: z2.number().int().nonnegative(),
420
+ tool: z2.number().int().nonnegative(),
421
+ total: z2.number().int().nonnegative(),
422
+ prompt: z2.number().int().nonnegative().optional(),
423
+ output: z2.number().int().nonnegative().optional(),
424
+ stepCount: z2.number().int().positive().optional()
449
425
  }).strict();
450
- var currentSchema = z.object({
451
- system: z.number().int().nonnegative(),
452
- tools: z.number().int().nonnegative(),
453
- user: z.number().int().nonnegative(),
454
- inject: z.number().int().nonnegative(),
455
- assistant: z.number().int().nonnegative(),
456
- tool: z.number().int().nonnegative(),
457
- total: z.number().int().nonnegative()
426
+ var contextEventSchema = z2.object({
427
+ seq: z2.number(),
428
+ time: z2.number(),
429
+ kind: z2.enum(["compaction", "prune", "inject", "model"]),
430
+ form: z2.string().optional(),
431
+ tokens: z2.number().optional(),
432
+ count: z2.number().optional(),
433
+ sub: z2.string().optional(),
434
+ name: z2.string().optional(),
435
+ from: z2.string().optional(),
436
+ to: z2.string().optional(),
437
+ fromTurn: z2.number().optional(),
438
+ fromStep: z2.number().optional(),
439
+ turn: z2.number().optional(),
440
+ step: z2.number().optional()
458
441
  }).strict();
459
- var occupancySchema = z.object({
460
- pressureTokens: z.number().int().nonnegative().optional(),
461
- surfaceTokens: z.number().int().nonnegative(),
462
- sampledSurfaceTokens: z.number().int().nonnegative().optional(),
463
- projectedTokens: z.number().int().nonnegative().optional(),
464
- contextWindow: z.number().int().positive().optional()
442
+ var currentSchema = z2.object({
443
+ system: z2.number().int().nonnegative(),
444
+ tools: z2.number().int().nonnegative(),
445
+ user: z2.number().int().nonnegative(),
446
+ inject: z2.number().int().nonnegative(),
447
+ assistant: z2.number().int().nonnegative(),
448
+ tool: z2.number().int().nonnegative(),
449
+ total: z2.number().int().nonnegative()
465
450
  }).strict();
466
- var contextTimelineSchema = z.object({
467
- ok: z.literal(true),
468
- model: z.string().optional(),
469
- provider: z.string().optional(),
470
- contextWindow: z.number().optional(),
451
+ var contextTimelineSchema = z2.object({
452
+ ok: z2.literal(true),
453
+ model: z2.string().optional(),
454
+ provider: z2.string().optional(),
455
+ contextWindow: z2.number().optional(),
471
456
  current: currentSchema,
472
- occupancy: occupancySchema.optional(),
473
- toolList: z.array(z.object({ name: z.string(), tokens: z.number().int().nonnegative() }).strict()),
474
- requests: z.array(requestRecordSchema),
475
- events: z.array(contextEventSchema),
476
- nodes: z.array(surfaceNodeSchema),
477
- droppedNodes: z.number().int().nonnegative()
457
+ toolList: z2.array(z2.object({ name: z2.string(), tokens: z2.number().int().nonnegative() }).strict()),
458
+ requests: z2.array(requestRecordSchema),
459
+ events: z2.array(contextEventSchema),
460
+ nodes: z2.array(surfaceNodeSchema),
461
+ droppedNodes: z2.number().int().nonnegative()
478
462
  }).strict();
479
- var contextTimelineDefinition = {
480
- key: "contextTimeline",
481
- schema: contextTimelineSchema,
482
- init: () => createTimelineState(),
483
- apply: (state, event) => applyTimeline(state, event),
484
- view: (state) => buildTimelineView(state),
485
- stateVersion: 1
486
- };
463
+ function createContextTimelineDefinition(config) {
464
+ const bounds = resolveBounds(config);
465
+ return {
466
+ key: "contextTimeline",
467
+ schema: contextTimelineSchema,
468
+ init: () => createTimelineState(),
469
+ apply: (state, event) => applyTimeline(state, event, bounds),
470
+ view: (state) => buildTimelineView(state, bounds),
471
+ // 2 since 0.11: the occupancy mirror (pressureTokens/sampledSurfaceTokens/
472
+ // occupancyWindow) left the persisted state — the client now reads the
473
+ // official token-meter `contextPressure` projection instead. Old cached
474
+ // rows are discarded and refolded.
475
+ stateVersion: 2
476
+ };
477
+ }
487
478
 
488
479
  // src/host/index.ts
489
480
  var name = "dsh-context";
490
481
  var inject = ["sessionProjections"];
491
- function apply(ctx) {
492
- ctx.inject(["sessionProjections"], (projectionCtx) => {
493
- projectionCtx.sessionProjections.register(contextTimelineDefinition);
494
- });
482
+ function apply(ctx, config) {
483
+ ctx.sessionProjections.register(createContextTimelineDefinition(config));
495
484
  }
496
485
  export {
486
+ Config,
497
487
  apply,
498
488
  inject,
499
489
  name
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "A DeepSeek Harness plugin for context dashboard and context command, for understanding how the context is made of, and how it evolves.",
5
5
  "author": "bowenliang123",
6
6
  "repository": {