dsh-context 0.10.2 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +13 -8
  2. package/lib/client.js +158 -42
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -5,21 +5,26 @@
5
5
  [![npm version](https://img.shields.io/npm/v/dsh-context)](https://www.npmjs.com/package/dsh-context)
6
6
  [![GitHub stars](https://img.shields.io/github/stars/bowenliang123/dsh-context?style=social)](https://github.com/bowenliang123/dsh-context)
7
7
 
8
- **A DeepSeek Harness plugin for context dashboard and context command, for understanding how the context is made of, and how it evolves.**
8
+ **The best [DeepSeek Harness plugin](https://www.deepseek.com/harness/) for Agent's context insights and management.**
9
9
 
10
- `dsh-context` is a [DeepSeek Harness](https://github.com/deepseek-ai/DeepSeek-Harness) plugin for context insight.
10
+ `dsh-context` provides full context lifecycle management features.
11
+ - **Context tab** — an UI context dashboard for DeepSeek Harness’s context stats, composition, history, events, and messages.
12
+ - **`/context` command** — the slash command shows the context model for current context composition and recent context evolution.
11
13
 
12
- - **The Context tab** — a full insight panel for context composition, per-turn context history, context compactions, and the message surface, etc.
13
- - **The `/context` command** — the same headline and recent trend as a centered dialog straight from the composer (the screenshot above).
14
+ ## Install / Update
14
15
 
15
- ## Install
16
-
17
- One command, from any DeepSeek Harness installation:
16
+ To Install from any DeepSeek Harness installation:
18
17
 
19
18
  ```sh
20
19
  dsh plugin --profile web add dsh-context
21
20
  ```
22
21
 
22
+ Or to update the `dsh-context` plugin:
23
+
24
+ ```sh
25
+ dsh plugin --profile web update dsh-context@latest
26
+ ```
27
+
23
28
  Then start the web UI with `dsh web`. No build step, no restart.
24
29
 
25
30
  ## Use it
@@ -40,7 +45,7 @@ Type `/context` (or pick it from the `/` menu) and press Enter: a centered dialo
40
45
 
41
46
  ### 📊 Context stats — the session at a glance
42
47
 
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.
48
+ Turns, steps, how many injections, compactions, and prunes have happened.
44
49
 
45
50
  ### 🧱 Current composition — what's in the window right now
46
51
 
package/lib/client.js CHANGED
@@ -25,13 +25,13 @@ 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": "The best DSH context plugin \u2B50",
33
+ "plugin.name": "\u63D2\u4EF6",
34
+ "plugin.github": "GitHub",
35
35
  "tools.top": "\u5DE5\u5177\u5B9A\u4E49 Top\uFF1A",
36
36
  "tools.more": "\u7B49 {n} \u4E2A",
37
37
  "trend.title": "\u5386\u53F2\u8D8B\u52BF",
@@ -50,6 +50,10 @@ var DICT_ZH = {
50
50
  "events.at": "\u7B2C {t} \u8F6E \xB7 \u7B2C {s} \u6B65",
51
51
  "events.range": "\u7B2C {t} \u8F6E \xB7 \u7B2C {a}\u2192{b} \u6B65",
52
52
  "events.rangeTo": "\u7B2C {a} \u8F6E \xB7 \u7B2C {as} \u6B65 \u2192 \u7B2C {b} \u8F6E \xB7 \u7B2C {bs} \u6B65",
53
+ "kind.inject": "\u6CE8\u5165",
54
+ "kind.compaction": "\u538B\u7F29",
55
+ "kind.prune": "\u526A\u679D",
56
+ "kind.model": "\u5207\u6362",
53
57
  "nodes.title": "\u6D88\u606F\u6784\u6210",
54
58
  "nodes.hint": "\u5F53\u524D\u6A21\u578B\u53EF\u89C1\u7684\u6D88\u606F\uFF0C\u6700\u65B0\u5728\u524D",
55
59
  "nodes.more": "\u2026 \u66F4\u65E9\u7684 {n} \u6761\u6D88\u606F\u5DF2\u7701\u7565",
@@ -98,13 +102,13 @@ var DICT_EN = {
98
102
  "stats.hint": "Over the retained history window (same as the History chart)",
99
103
  "stats.turns": "Turns",
100
104
  "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",
105
+ "stats.injects": "Injections",
106
+ "stats.compactions": "Compactions",
107
+ "stats.prunes": "Prunes",
108
+ "plugin.title": "Plugin info",
109
+ "plugin.hint": "The best DSH context plugin \u2B50",
110
+ "plugin.name": "Plugin",
111
+ "plugin.github": "GitHub",
108
112
  "tools.top": "Top tool schemas:",
109
113
  "tools.more": "of {n}",
110
114
  "trend.title": "History",
@@ -123,6 +127,10 @@ var DICT_EN = {
123
127
  "events.at": "Turn {t} \xB7 Step {s}",
124
128
  "events.range": "Turn {t} \xB7 Step {a}\u2192{b}",
125
129
  "events.rangeTo": "Turn {a} \xB7 Step {as} \u2192 Turn {b} \xB7 Step {bs}",
130
+ "kind.inject": "Inject",
131
+ "kind.compaction": "Compact",
132
+ "kind.prune": "Prune",
133
+ "kind.model": "Switch",
126
134
  "nodes.title": "Messages",
127
135
  "nodes.hint": "currently model-visible, newest first",
128
136
  "nodes.more": "\u2026 {n} earlier messages omitted",
@@ -656,11 +664,11 @@ var STYLES = [
656
664
  ".lc-card-title { font-weight: 600; margin-bottom: 10px; display: flex; align-items: baseline; gap: 8px; }",
657
665
  ".lc-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: 8px; }",
658
666
  ".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; }",
659
- ".lc-stat-label { color: var(--dsw-alias-label-secondary); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
660
- ".lc-stat-value { color: var(--dsw-alias-label-primary); font-size: 14px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
667
+ ".lc-stat-label { color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
668
+ ".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; }",
661
669
  ".lc-stat-sub { color: var(--dsw-alias-label-secondary); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
662
670
  ".lc-card-sub { font-weight: 400; color: var(--dsw-alias-label-secondary); font-size: 12px; }",
663
- ".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; }",
671
+ ".lc-gran, .lc-kinds { 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; }",
664
672
  ".lc-gran-btn { border: 0; background: transparent; color: var(--dsw-alias-label-secondary); font-size: 11px; line-height: 1; padding: 3px 8px; border-radius: 5px; cursor: pointer; font-family: inherit; }",
665
673
  ".lc-gran-btn:hover { color: var(--dsw-alias-label-primary); }",
666
674
  ".lc-gran-on, .lc-gran-on:hover { background: var(--dsw-alias-button-primary-fill); color: var(--dsw-alias-label-primary-foreground); }",
@@ -746,14 +754,47 @@ var STYLES = [
746
754
  ".lc-detail-pct { width: 34px; text-align: right; color: var(--dsw-alias-label-secondary); }",
747
755
  ".lc-cols { display: flex; gap: 14px; flex-wrap: wrap; }",
748
756
  ".lc-col { flex: 1; min-width: 280px; }",
757
+ // Head row: stats board + plugin info sit side by side under the shared
758
+ // `lc-cols` flex — stats takes ~7/10 of the row, plugin info ~3/10, both
759
+ // wrap onto their own line when the available width falls below each card's
760
+ // min-width (so a narrow viewport keeps both readable). Both children are
761
+ // `.lc-card` themselves (rendered by StatsBoard / PluginInfo); the flex
762
+ // sizing lives on the head row's direct children so the existing card
763
+ // chrome doesn't change.
764
+ ".lc-head > .lc-card { margin-bottom: 0; }",
765
+ ".lc-head > .lc-card:first-child { flex: 7 1 0; min-width: 360px; }",
766
+ ".lc-head > .lc-card:last-child { flex: 3 1 0; min-width: 220px; }",
767
+ // Plugin info: two full-width rows (Plugin / GitHub), each one horizontal
768
+ // line with the label on the left and the value on the right — a compact
769
+ // definition list rather than a multi-cell grid.
770
+ ".lc-pi-grid { display: grid; grid-template-columns: 1fr; gap: 8px; }",
771
+ // The row IS the link: an `<a>` with the label + value on one baseline.
772
+ // Hovering a row underlines its value — the only hover affordance.
773
+ ".lc-pi-row { display: flex; flex-direction: row; justify-content: space-between; align-items: baseline; gap: 12px; min-width: 0; text-decoration: none; color: inherit; }",
774
+ ".lc-pi-row:hover .lc-pi-value { text-decoration: underline; }",
775
+ // Upgrade chip appended to the Plugin value when the npm registry has a
776
+ // newer version than the baked-in one.
777
+ ".lc-pi-update { color: var(--dsw-alias-state-warn-primary); font-size: 11px; margin-left: 6px; white-space: nowrap; }",
778
+ ".lc-pi-label { flex: none; color: var(--dsw-alias-label-secondary); font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }",
779
+ // min-width: 0 lets the value shrink inside the flex row and truncate with
780
+ // an ellipsis; without it a narrow card pushes the value over the label.
781
+ ".lc-pi-value { min-width: 0; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-align: right; }",
749
782
  ".lc-events, .lc-nodes { display: flex; flex-direction: column; gap: 2px; max-height: 320px; overflow-y: auto; }",
750
783
  ".lc-event { display: flex; align-items: center; gap: 8px; padding: 3px 0; }",
751
784
  ".lc-event-icon { width: 18px; text-align: center; color: var(--dsw-alias-state-warn-primary); }",
752
785
  ".lc-event-icon.lc-event-inject { color: #a855f7; }",
753
786
  ".lc-event-icon.lc-event-model { color: var(--dsw-alias-brand-primary); }",
787
+ // Kind chip: the event classification at a glance; the tint matches the
788
+ // impact direction (inject = adds context, compaction/prune = frees it,
789
+ // model switch = neutral), mirroring the token sign colors below.
790
+ ".lc-kind { flex: none; font-size: 10px; font-weight: 600; padding: 1px 6px; border-radius: 4px; white-space: nowrap; }",
791
+ ".lc-kind-inject { background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 15%, transparent); color: var(--dsw-alias-state-success-primary); }",
792
+ ".lc-kind-compaction { background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 15%, transparent); color: var(--dsw-alias-state-error-primary); }",
793
+ ".lc-kind-prune { background: color-mix(in srgb, #8b5cf6 15%, transparent); color: #8b5cf6; }",
794
+ ".lc-kind-model { background: color-mix(in srgb, var(--dsw-alias-brand-primary) 15%, transparent); color: var(--dsw-alias-brand-primary); }",
754
795
  ".lc-event-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }",
755
796
  ".lc-event-at { flex: none; color: var(--dsw-alias-label-secondary); font-size: 11px; white-space: nowrap; }",
756
- ".lc-event-tokens { color: var(--dsw-alias-state-success-primary); }",
797
+ ".lc-event-tokens { color: var(--dsw-alias-state-success-primary); font-weight: 600; white-space: nowrap; }",
757
798
  ".lc-event-tokens.lc-up { color: var(--dsw-alias-state-warn-primary); }",
758
799
  ".lc-event-time { color: var(--dsw-alias-label-secondary); font-size: 12px; }",
759
800
  ".lc-node { display: flex; align-items: center; gap: 8px; padding: 3px 0; }",
@@ -829,12 +870,25 @@ function makeEventList(kit) {
829
870
  if (props.events.length === 0) {
830
871
  return /* @__PURE__ */ React.createElement("div", { className: "lc-empty" }, t("events.empty"));
831
872
  }
873
+ const rootRef = React.useRef(null);
874
+ React.useLayoutEffect(() => {
875
+ const root = rootRef.current;
876
+ if (!root) return;
877
+ const sync = () => {
878
+ for (const el of root.querySelectorAll(".lc-event-label")) {
879
+ el.title = el.scrollWidth > el.clientWidth ? el.textContent || "" : "";
880
+ }
881
+ };
882
+ sync();
883
+ window.addEventListener("resize", sync);
884
+ return () => window.removeEventListener("resize", sync);
885
+ });
832
886
  const sorted = props.events.slice().reverse();
833
- return /* @__PURE__ */ React.createElement("div", { className: "lc-events" }, sorted.map((ev, i) => {
887
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-events", ref: rootRef }, sorted.map((ev, i) => {
834
888
  const label = eventLabel(ev);
835
889
  const at = eventAt(ev);
836
890
  const glyph = ev.kind === "inject" ? /* @__PURE__ */ React.createElement(import_dsh_client_ui_primitives.IconPlusOutline16, null) : ev.kind === "model" ? /* @__PURE__ */ React.createElement(import_dsh_client_ui_primitives.IconBranchOutline16, null) : EVENT_ICONS[ev.kind] || "\u2022";
837
- return /* @__PURE__ */ React.createElement("div", { key: ev.seq + "-" + i, className: "lc-event" }, /* @__PURE__ */ React.createElement("span", { className: "lc-event-icon lc-event-" + ev.kind }, glyph), /* @__PURE__ */ React.createElement("span", { className: "lc-event-label", title: label }, label), at !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-event-at" }, at) : null, ev.tokens ? /* @__PURE__ */ React.createElement("span", { className: "lc-event-tokens" + (ev.kind === "inject" ? " lc-up" : " lc-down") }, (ev.kind === "inject" ? "+" : "\u2212") + fmt3(ev.tokens)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-event-time" }, fmtTime2(ev.time)));
891
+ return /* @__PURE__ */ React.createElement("div", { key: ev.seq + "-" + i, className: "lc-event" }, /* @__PURE__ */ React.createElement("span", { className: "lc-event-icon lc-event-" + ev.kind }, glyph), /* @__PURE__ */ React.createElement("span", { className: "lc-kind lc-kind-" + ev.kind }, t("kind." + ev.kind)), /* @__PURE__ */ React.createElement("span", { className: "lc-event-label" }, label), at !== null ? /* @__PURE__ */ React.createElement("span", { className: "lc-event-at" }, at) : null, ev.tokens ? /* @__PURE__ */ React.createElement("span", { className: "lc-event-tokens" + (ev.kind === "inject" ? " lc-up" : " lc-down") }, (ev.kind === "inject" ? "+" : "\u2212") + fmt3(ev.tokens)) : null, /* @__PURE__ */ React.createElement("span", { className: "lc-event-time" }, fmtTime2(ev.time)));
838
892
  }));
839
893
  };
840
894
  }
@@ -867,41 +921,83 @@ function makeNodeList(kit) {
867
921
  };
868
922
  }
869
923
 
924
+ // src/client/meta.ts
925
+ var PLUGIN_NAME = "dsh-context";
926
+ var PLUGIN_VERSION = true ? "0.11.0" : "0.0.0-dev";
927
+ var PLUGIN_REPO = true ? "https://github.com/bowenliang123/dsh-context" : "https://github.com/bowenliang123/dsh-context";
928
+ var PLUGIN_REPO_SHORT = PLUGIN_REPO.replace(/^https?:\/\/github\.com\//, "");
929
+
930
+ // src/client/latestVersion.ts
931
+ var REGISTRY_URL = "https://registry.npmjs.org/" + PLUGIN_NAME + "/latest";
932
+ var TTL_MS = 60 * 60 * 1e3;
933
+ var cached = null;
934
+ function fetchLatestVersion() {
935
+ if (!cached || Date.now() - cached.at >= TTL_MS) {
936
+ cached = {
937
+ at: Date.now(),
938
+ promise: fetch(REGISTRY_URL).then((res) => res.ok ? res.json() : null).then((body) => body && typeof body.version === "string" ? body.version : null).catch(() => null)
939
+ };
940
+ }
941
+ return cached.promise;
942
+ }
943
+ function isNewerVersion(latest, current) {
944
+ const parse = (v) => v.replace(/^v/, "").split("-", 1)[0].split(".").map((n) => parseInt(n, 10) || 0);
945
+ const a = parse(latest);
946
+ const b = parse(current);
947
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
948
+ const x = a[i] || 0;
949
+ const y = b[i] || 0;
950
+ if (x !== y) return x > y;
951
+ }
952
+ return false;
953
+ }
954
+
955
+ // src/client/components/pluginInfo.tsx
956
+ function makePluginInfo(kit) {
957
+ const { t } = kit;
958
+ const row = (label, value, href) => /* @__PURE__ */ React.createElement("a", { className: "lc-pi-row", href, target: "_blank", rel: "noreferrer" }, /* @__PURE__ */ React.createElement("div", { className: "lc-pi-label" }, label), /* @__PURE__ */ React.createElement("div", { className: "lc-pi-value" }, value));
959
+ return function PluginInfo() {
960
+ const [latest, setLatest] = React.useState(null);
961
+ React.useEffect(() => {
962
+ if (PLUGIN_VERSION.includes("-dev")) return;
963
+ let on = true;
964
+ fetchLatestVersion().then((v) => {
965
+ if (on && v) setLatest(v);
966
+ });
967
+ return () => {
968
+ on = false;
969
+ };
970
+ }, []);
971
+ const update = latest !== null && isNewerVersion(latest, PLUGIN_VERSION) ? latest : null;
972
+ const nameValue = [PLUGIN_NAME + " (v" + PLUGIN_VERSION + ")"];
973
+ if (update) nameValue.push(/* @__PURE__ */ React.createElement("span", { key: "update", className: "lc-pi-update" }, "\u2191 v" + update));
974
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-card" }, /* @__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-pi-grid" }, row(t("plugin.name"), nameValue, PLUGIN_REPO + "/releases"), row(t("plugin.github"), PLUGIN_REPO_SHORT, PLUGIN_REPO)));
975
+ };
976
+ }
977
+
870
978
  // src/client/components/statsBoard.tsx
871
979
  function makeStatsBoard(kit) {
872
- const { t, tr, fmt: fmt3 } = kit;
980
+ const { t, fmt: fmt3 } = kit;
873
981
  return function StatsBoard(props) {
874
982
  const turns = /* @__PURE__ */ new Set();
875
- let steps = 0, compactions = 0, prunes = 0, injects = 0, switches = 0;
876
- let recycled = 0, est = 0, prompt = 0, output = 0;
983
+ let steps = 0, compactions = 0, prunes = 0, injects = 0;
877
984
  for (const req of props.requests) {
878
985
  turns.add(req.turn ?? 0);
879
986
  steps++;
880
- est += req.total;
881
- if (typeof req.prompt === "number") prompt += req.prompt;
882
- if (typeof req.output === "number") output += req.output;
883
987
  }
884
988
  for (const ev of props.events) {
885
- if (ev.kind === "compaction") {
886
- compactions++;
887
- recycled += ev.tokens || 0;
888
- } else if (ev.kind === "prune") {
889
- prunes++;
890
- recycled += ev.tokens || 0;
891
- } else if (ev.kind === "inject") injects++;
892
- else if (ev.kind === "model") switches++;
989
+ if (ev.kind === "compaction") compactions++;
990
+ else if (ev.kind === "prune") prunes++;
991
+ else if (ev.kind === "inject") injects++;
893
992
  }
894
- 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);
895
- 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(
896
- t("stats.recycled"),
897
- recycled > 0 ? "\u2212" + fmt3(recycled) : "0",
898
- tr("stats.recycleSub", { c: compactions, p: prunes })
899
- ), 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))));
993
+ 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));
994
+ 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))));
900
995
  };
901
996
  }
902
997
 
903
998
  // src/client/components/contextView.tsx
904
999
  var viewScroll = /* @__PURE__ */ new Map();
1000
+ var EVENT_KINDS = ["inject", "compaction", "prune", "model"];
905
1001
  function makeContextView(ctx, kit) {
906
1002
  const { t, tr, fmt: fmt3, fmtTime: fmtTime2, catLabel } = kit;
907
1003
  const StackedBar = makeStackedBar(kit);
@@ -911,6 +1007,7 @@ function makeContextView(ctx, kit) {
911
1007
  const EventList = makeEventList(kit);
912
1008
  const NodeList = makeNodeList(kit);
913
1009
  const StatsBoard = makeStatsBoard(kit);
1010
+ const PluginInfo = makePluginInfo(kit);
914
1011
  return function ContextView(props) {
915
1012
  const sessionId = props.sessionId;
916
1013
  const data = typeof props.useProjection === "function" ? timelineOf(props.useProjection("contextTimeline")) : null;
@@ -921,6 +1018,14 @@ function makeContextView(ctx, kit) {
921
1018
  const [tick, setTick] = React.useState(0);
922
1019
  const [granularity, setGranularity] = React.useState("step");
923
1020
  const [hoverCat, setHoverCat] = React.useState(null);
1021
+ const [pickedKinds, setPickedKinds] = React.useState([...EVENT_KINDS]);
1022
+ const toggleKind = (k) => {
1023
+ setPickedKinds((p) => {
1024
+ if (p.length === EVENT_KINDS.length) return [k];
1025
+ if (!p.includes(k)) return [...p, k];
1026
+ return p.length === 1 ? [...EVENT_KINDS] : p.filter((x) => x !== k);
1027
+ });
1028
+ };
924
1029
  const rootRef = React.useRef(null);
925
1030
  const scrollerRef = React.useRef(null);
926
1031
  const restoredRef = React.useRef(null);
@@ -952,6 +1057,7 @@ function makeContextView(ctx, kit) {
952
1057
  }
953
1058
  const requests = data.requests || [];
954
1059
  const events = data.events || [];
1060
+ const shownEvents = pickedKinds.length === EVENT_KINDS.length ? events : events.filter((e) => pickedKinds.includes(e.kind));
955
1061
  const nodes = data.nodes || [];
956
1062
  const displayRequests = granularity === "turn" ? aggregateByTurn(requests) : requests;
957
1063
  const markers = attachMarkers(displayRequests, events);
@@ -975,7 +1081,7 @@ function makeContextView(ctx, kit) {
975
1081
  }
976
1082
  }
977
1083
  const head = headlineOf(data, pressure);
978
- 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) => {
1084
+ return /* @__PURE__ */ React.createElement("div", { className: "lc-root", ref: rootRef }, /* @__PURE__ */ React.createElement("div", { className: "lc-cols lc-head" }, /* @__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) => {
979
1085
  return /* @__PURE__ */ React.createElement("span", { key: tool.name, className: "lc-tool-chip" }, tool.name + " " + fmt3(tool.tokens));
980
1086
  }), 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(
981
1087
  "button",
@@ -1009,7 +1115,17 @@ function makeContextView(ctx, kit) {
1009
1115
  onHover: setHoveredSeq,
1010
1116
  onHoverTurn: setHoverTurn
1011
1117
  }
1012
- ), /* @__PURE__ */ React.createElement(RequestDetail, { request: activeReq, marker: activeReq !== null ? markerOf(activeReq) : void 0 }))), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("events.title")), /* @__PURE__ */ React.createElement(EventList, { events })), /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, 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")));
1118
+ ), /* @__PURE__ */ React.createElement(RequestDetail, { request: activeReq, marker: activeReq !== null ? markerOf(activeReq) : void 0 }))), /* @__PURE__ */ React.createElement("div", { className: "lc-cols" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, t("events.title"), /* @__PURE__ */ React.createElement("div", { className: "lc-kinds" }, EVENT_KINDS.map((k) => /* @__PURE__ */ React.createElement(
1119
+ "button",
1120
+ {
1121
+ key: k,
1122
+ className: "lc-gran-btn" + (pickedKinds.includes(k) ? " lc-gran-on lc-kind-" + k : ""),
1123
+ onClick: () => {
1124
+ toggleKind(k);
1125
+ }
1126
+ },
1127
+ t("kind." + k)
1128
+ )))), /* @__PURE__ */ React.createElement(EventList, { events: shownEvents })), /* @__PURE__ */ React.createElement("div", { className: "lc-card lc-col" }, /* @__PURE__ */ React.createElement("div", { className: "lc-card-title" }, 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")));
1013
1129
  };
1014
1130
  }
1015
1131
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.10.2",
4
- "description": "A DeepSeek Harness plugin for context dashboard and context command, for understanding how the context is made of, and how it evolves.",
3
+ "version": "0.11.0",
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": {
7
7
  "type": "git",