dsh-context 0.38.4 → 0.39.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.
package/README.md CHANGED
@@ -140,7 +140,7 @@ In **Settings → Plugins → Plugin configuration**, the **Context** card holds
140
140
  ## Good to know
141
141
 
142
142
  - **Estimates vs actuals** — category figures use dsh's own fixed-density heuristic (the same one as its built-in token meter); the pinned trend details and Token/Timing rings show provider-reported actuals next to them.
143
- - **Compatibility** — works on `@deepseek-ai/dsh` **0.1.0-rc7+**, **0.1.1-rc2+**, and **0.1.2-alpha1+**; multimodal rendering requires 0.1.1+.
143
+ - **Compatibility** — works on `@deepseek-ai/dsh` **0.1.1-rc2+** and **0.1.2-alpha2+**.
144
144
  - **I18n** — UI in English and 简体中文.
145
145
 
146
146
  ## Like it?
package/lib/client.js CHANGED
@@ -4833,7 +4833,7 @@ window.__ModuleLoader__.load({
4833
4833
  return function PluginInfo() {
4834
4834
  const [latest, setLatest] = React.useState(null);
4835
4835
  React.useEffect(() => {
4836
- if ("0.38.4".includes("-dev")) return;
4836
+ if ("0.39.0".includes("-dev")) return;
4837
4837
  let on = true;
4838
4838
  fetchLatestVersion().then((v) => {
4839
4839
  if (on && v) setLatest(v);
@@ -4842,8 +4842,8 @@ window.__ModuleLoader__.load({
4842
4842
  on = false;
4843
4843
  };
4844
4844
  }, []);
4845
- const update = latest !== null && isNewerVersion(latest, "0.38.4") ? latest : null;
4846
- const nameText = "dsh-context (v0.38.4)";
4845
+ const update = latest !== null && isNewerVersion(latest, "0.39.0") ? latest : null;
4846
+ const nameText = "dsh-context (v0.39.0)";
4847
4847
  const nameValue = [nameText];
4848
4848
  if (update) nameValue.push(/* @__PURE__ */ React.createElement("span", {
4849
4849
  key: "update",
@@ -6180,7 +6180,7 @@ window.__ModuleLoader__.load({
6180
6180
  }
6181
6181
  //#endregion
6182
6182
  //#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/stats.css.mjs
6183
- const css$12 = ".lc-stats{grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;display:grid}.lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}.lc-stats .lc-stat{padding:6px 3px}.lc-stats .lc-stat-q{margin:0;position:absolute;top:3px;right:4px}.lc-stat-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;font-weight:600;overflow:hidden}.lc-stat-value{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:center;font-size:14px;font-weight:600;overflow:hidden}.lc-stat-sub{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.lc-stat-tipped{position:relative}.lc-stat-q{text-align:center;width:11px;height:11px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:help;border-radius:50%;margin-left:4px;font-size:9px;font-style:normal;font-weight:700;line-height:11px;display:inline-block}.lc-stat-tipped:hover .lc-stat-q{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary)}.lc-stat-tip{max-width:280px;font-weight:400;line-height:1.5;top:calc(100% + 6px);left:0}.lc-stat-tipped:hover .lc-stat-tip{opacity:1}.lc-stat-tip-prices{border-top:1px dashed var(--dsw-alias-border-l1);margin-top:5px;padding-top:5px;display:block}.lc-stat-tip-head{font-weight:600;display:block}.lc-stat-tip-row{margin-top:2px;display:block}.lc-stat-tip-model{color:var(--dsw-alias-label-primary)}.lc-card-sub{color:var(--dsw-alias-label-secondary);margin-left:auto;font-size:12px;font-weight:400}.lc-overview-num{align-items:baseline;gap:6px;margin-bottom:8px;display:flex}.lc-overview-num>b{font-size:20px}.lc-overview-num span{color:var(--dsw-alias-label-secondary)}.lc-overview-pct{margin-left:auto;font-size:11px}.lc-overview-pct b{color:var(--dsw-alias-label-primary);margin-right:4px;font-size:20px}.lc-stat-head{color:var(--dsw-alias-label-secondary);margin:12px 0 8px;font-size:11px;font-weight:600}.lc-donut-row{justify-content:flex-start;align-items:center;gap:12px;min-width:0;display:flex}.lc-col-donut{flex-direction:column;display:flex}.lc-col-donut .lc-donut-row{flex:1}.lc-donut-row .lc-sl{flex:auto}.lc-donut{flex:none;position:relative}.lc-donut svg{display:block}.lc-donut-track{stroke:#8080802e}.lc-donut-center{pointer-events:none;text-align:center;box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:1px;padding:0 7px;display:flex;position:absolute;inset:0}.lc-donut-center b{white-space:nowrap;text-overflow:ellipsis;max-width:100%;line-height:1.15;overflow:hidden}.lc-donut-center span{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-sl{flex-direction:column;gap:5px;min-width:0;display:flex;overflow:hidden}.lc-sl-row{align-items:baseline;gap:6px;min-width:0;font-size:12px;display:flex}.lc-sl-dot{border-radius:2px;flex:none;align-self:flex-start;width:8px;height:8px;margin-top:2px}.lc-sl-label{flex:0 auto;min-width:80px;line-height:1.25;overflow:hidden}.lc-sl-count{text-align:right;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;text-overflow:ellipsis;flex:auto;overflow:hidden}.lc-sl-pct{text-align:right;font-variant-numeric:tabular-nums;flex:none;min-width:30px;font-weight:700}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.lc-pi-row-btn{appearance:none;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;width:100%;padding:0}.lc-pi-row{min-width:0;color:inherit;flex-direction:row;justify-content:space-between;align-items:baseline;gap:12px;text-decoration:none;display:flex}.lc-pi-row:hover .lc-pi-value{text-decoration:underline}.lc-pi-hint{text-decoration:none}.lc-pi-hint:hover{text-decoration:underline}.lc-pi-update{color:var(--dsw-alias-state-warn-primary);white-space:nowrap;margin-left:6px;font-size:11px}.lc-pi-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;flex:none;font-size:11px;font-weight:600;overflow:hidden}.lc-pi-value{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:right;font-size:13px;font-weight:600;overflow:hidden}.lc-pi-row-btn .lc-pi-value{font-weight:400}@container lc-head-card (width<=320px){.lc-donut-row{flex-wrap:wrap}.lc-donut{margin:0 auto}.lc-donut-row .lc-sl{flex-basis:100%}.lc-sl-label{min-width:40px}.lc-stats{grid-template-columns:repeat(2,minmax(0,1fr))}}";
6183
+ const css$12 = ".lc-stats{grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;display:grid}.lc-stat{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;flex-direction:column;gap:2px;min-width:0;padding:6px;display:flex}.lc-stats .lc-stat{padding:6px 3px}.lc-stats .lc-stat-q{margin:0;position:absolute;top:3px;right:4px}.lc-stat-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;font-weight:600;overflow:hidden}.lc-stat-value{color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:center;font-size:14px;font-weight:600;overflow:hidden}.lc-stat-sub{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;font-size:11px;overflow:hidden}.lc-stat-tipped{position:relative}.lc-stat-q{text-align:center;width:11px;height:11px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);cursor:help;border-radius:50%;margin-left:4px;font-size:9px;font-style:normal;font-weight:700;line-height:11px;display:inline-block}.lc-stat-tipped:hover .lc-stat-q{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-label-primary)}.lc-stat-tip{max-width:280px;font-weight:400;line-height:1.5;top:calc(100% + 6px);left:0}.lc-stat-tipped:hover .lc-stat-tip{opacity:1}.lc-stat-tip-prices{border-top:1px dashed var(--dsw-alias-border-l1);margin-top:5px;padding-top:5px;display:block}.lc-stat-tip-head{font-weight:600;display:block}.lc-stat-tip-row{margin-top:2px;display:block}.lc-stat-tip-model{color:var(--dsw-alias-label-primary)}.lc-card-sub{color:var(--dsw-alias-label-secondary);margin-left:auto;font-size:12px;font-weight:400}.lc-overview-num{align-items:baseline;gap:6px;margin-bottom:8px;display:flex}.lc-overview-num>b{font-size:20px}.lc-overview-num span{color:var(--dsw-alias-label-secondary)}.lc-overview-pct{margin-left:auto;font-size:11px}.lc-overview-pct b{color:var(--dsw-alias-label-primary);margin-right:4px;font-size:20px}.lc-stat-head{color:var(--dsw-alias-label-secondary);margin:12px 0 8px;font-size:11px;font-weight:600}.lc-donut-row{justify-content:flex-start;align-items:center;gap:12px;min-width:0;display:flex}.lc-col-donut{flex-direction:column;display:flex}.lc-col-donut .lc-donut-row{flex:1}.lc-donut-row .lc-sl{flex:auto}.lc-donut{flex:none;position:relative}.lc-donut svg{display:block}.lc-donut-track{stroke:#8080802e}.lc-donut-center{pointer-events:none;text-align:center;box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:1px;padding:0 7px;display:flex;position:absolute;inset:0}.lc-donut-center b{white-space:nowrap;text-overflow:ellipsis;max-width:100%;line-height:1.15;overflow:hidden}.lc-donut-center span{color:var(--dsw-alias-label-secondary);white-space:nowrap}.lc-sl{flex-direction:column;gap:5px;min-width:0;display:flex;overflow:hidden}.lc-sl-row{align-items:baseline;gap:6px;min-width:0;font-size:12px;display:flex}.lc-sl-dot{border-radius:2px;flex:none;align-self:flex-start;width:8px;height:8px;margin-top:2px}.lc-sl-label{flex:0 auto;min-width:80px;line-height:1.25;overflow:hidden}.lc-sl-count{text-align:right;color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;text-overflow:ellipsis;flex:auto;overflow:hidden}.lc-sl-pct{text-align:right;font-variant-numeric:tabular-nums;flex:none;min-width:30px;font-weight:700}.lc-pi-grid{grid-template-columns:1fr;gap:8px;display:grid}.lc-pi-row-btn{appearance:none;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;width:100%;padding:0}.lc-pi-row{min-width:0;color:inherit;flex-direction:row;justify-content:space-between;align-items:baseline;gap:12px;text-decoration:none;display:flex}.lc-pi-row:hover .lc-pi-value{text-decoration:underline}.lc-pi-hint{text-decoration:none}.lc-pi-hint:hover{text-decoration:underline}.lc-pi-update{color:var(--dsw-alias-state-warn-primary);white-space:nowrap;margin-left:6px;font-size:11px}.lc-pi-label{color:var(--dsw-alias-label-secondary);white-space:nowrap;text-overflow:ellipsis;flex:none;font-size:11px;font-weight:600;overflow:hidden}.lc-pi-value{min-width:0;color:var(--dsw-alias-label-primary);white-space:nowrap;text-overflow:ellipsis;text-align:right;font-size:13px;font-weight:600;overflow:hidden}.lc-pi-row-btn .lc-pi-value{font-weight:400}@container lc-head-card (width<=320px){.lc-donut-row{gap:8px}.lc-sl-label{min-width:40px}.lc-sl-count{flex:none}.lc-stats{grid-template-columns:repeat(4,minmax(0,1fr))}}@container lc-head-card (width<=240px){.lc-donut-row{flex-wrap:wrap}.lc-donut{margin:0 auto}.lc-donut-row .lc-sl{flex-basis:100%}.lc-sl-count{flex:auto}.lc-stats{grid-template-columns:repeat(2,minmax(0,1fr))}}";
6184
6184
  const tagId$12 = "dsh-context/stats.css";
6185
6185
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$12) + "]") === null) {
6186
6186
  const tag = document.createElement("style");
package/lib/index.js CHANGED
@@ -90,7 +90,10 @@ const FIRST_PARTY_SOURCES = Object.freeze({
90
90
  * wraps that instance's `register` (once — an earlier wrapper of a previous
91
91
  * hook incarnation is peeled back to the original, so a plugin reload
92
92
  * re-wraps without stacking) to capture the reader at registration time
93
- * into a live map.
93
+ * into a live map. Every wrapper is undone when the plugin unloads: the
94
+ * original `register` goes back on the instance, unless a newer hook
95
+ * incarnation re-wrapped it first (that incarnation's own cleanup then
96
+ * owns the restore).
94
97
  * - When the reader slot is missing, root-named, or this plugin's own (e.g.
95
98
  * LOCAL-LINK plugins — dev installs via `dsh plugin add <path>` or
96
99
  * npm/pnpm link — whose anonymous entrypoints make cordis fall back to the
@@ -186,8 +189,8 @@ function callerPackageFrom(stack) {
186
189
  }
187
190
  /**
188
191
  * Install the runtime-attribution hook on a cordis app context. The hook
189
- * rides the calling fiber's lifetime (`ctx.on` / `ctx.get`), so it is
190
- * disposed with the plugin.
192
+ * rides the calling fiber's lifetime (`ctx.on`, and an effect that restores
193
+ * every patched `register`), so it is disposed with the plugin.
191
194
  * @param ctx - the context the dsh-context plugin runs in; its fiber name is
192
195
  * excluded from attributions.
193
196
  */
@@ -196,6 +199,7 @@ function createToolAttribution(ctx) {
196
199
  const wrapped = /* @__PURE__ */ new WeakSet();
197
200
  const self = ctx.fiber.name;
198
201
  let lastReader;
202
+ const patched = [];
199
203
  const wrapInstance = (tools) => {
200
204
  if (!tools || typeof tools !== "object" || wrapped.has(tools)) return;
201
205
  const register = tools.register;
@@ -223,6 +227,9 @@ function createToolAttribution(ctx) {
223
227
  };
224
228
  wrappedRegister.attributedOriginal = original;
225
229
  instance.register = wrappedRegister;
230
+ patched.push(() => {
231
+ if (instance.register === wrappedRegister) instance.register = original;
232
+ });
226
233
  };
227
234
  ctx.on("internal/get", (reader, name, _error, next) => {
228
235
  if (name !== "tools") return next();
@@ -231,6 +238,9 @@ function createToolAttribution(ctx) {
231
238
  wrapInstance(tools);
232
239
  return tools;
233
240
  });
241
+ ctx.effect(() => () => {
242
+ for (const restore of patched.splice(0)) restore();
243
+ }, "tools.register attribution");
234
244
  const toolsService = ctx.get("tools", false);
235
245
  wrapInstance(toolsService);
236
246
  const boot = /* @__PURE__ */ new Set();
@@ -601,8 +611,8 @@ function recordOf(event) {
601
611
  }
602
612
  /**
603
613
  * The context-headers projection unit; registered alongside the timeline unit (host/index.ts); clients read it through
604
- * `useProjection('contextHeaders')` and degrade to tokens-only header sections when the key is absent. Dual-contract definition (see
605
- * compat.ts).
614
+ * `useProjection('contextHeaders')` and degrade to tokens-only header sections when the key is absent. Contract mirror
615
+ * with a REQUIRED `wire` block (see compat.ts).
606
616
  * @param resolve - best-effort tool-to-plugin attribution (see toolSources.ts); fills a missing `plugin` at view time so
607
617
  * epochs folded without attribution still render a tag when the source is known.
608
618
  */
@@ -620,8 +630,6 @@ function createContextHeadersDefinition(resolve) {
620
630
  })) });
621
631
  return {
622
632
  key: "contextHeaders",
623
- schema: contextHeadersSchema,
624
- view,
625
633
  stateSchema: contextHeadersStateSchema,
626
634
  wire: {
627
635
  viewSchema: contextHeadersSchema,
@@ -1422,23 +1430,20 @@ const timelineStateSchema = z.object({
1422
1430
  * or fold semantics change (invalidation of cached rows); config-only
1423
1431
  * changes never require it (bounds tune retention, not state shape).
1424
1432
  *
1425
- * The definition carries BOTH session-projection contracts (see compat.ts):
1426
- * `schema`/`view` for dsh <= 0.1.0-rc.8, `stateSchema`/`wire` for
1427
- * dsh >= 0.1.1-rc.1 each registry reads its own fields off the same unit.
1428
- * (The return type is the mirrored dual contract, not the installed dts
1429
- * `ProjectionDefinition`: the 0.1.1+ registry's wired-register overload
1430
- * demands `wire` PRESENT, which the dts's optional `wire?` fails.)
1431
- * Without the `wire` block the 0.1.1-rc.1+ registry treats the unit as
1432
- * host-only and never delivers `contextTimeline` to the browser (the Context
1433
- * tab would stay on its loading screen forever).
1433
+ * The definition carries the session-projection contract served by every
1434
+ * supported harness (see compat.ts): `stateSchema` + a REQUIRED `wire` block.
1435
+ * (The return type is the mirrored contract, not the installed dts
1436
+ * `ProjectionDefinition`: the registry's wired-register overload demands
1437
+ * `wire` PRESENT, which the dts's optional `wire?` fails.) Without the
1438
+ * `wire` block the registry treats the unit as host-only and never delivers
1439
+ * `contextTimeline` to the browser (the Context tab would stay on its
1440
+ * loading screen forever).
1434
1441
  */
1435
1442
  function createContextTimelineDefinition(config) {
1436
1443
  const bounds = resolveBounds(config);
1437
1444
  const view = (state) => buildTimelineView(state, bounds);
1438
1445
  return {
1439
1446
  key: "contextTimeline",
1440
- schema: contextTimelineSchema,
1441
- view,
1442
1447
  stateSchema: timelineStateSchema,
1443
1448
  wire: {
1444
1449
  viewSchema: contextTimelineSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.38.4",
3
+ "version": "0.39.0",
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": {
@@ -38,7 +38,6 @@
38
38
  "register": "bash scripts/register.sh",
39
39
  "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.tests.json",
40
40
  "test": "pnpm run typecheck && vitest run --coverage",
41
- "test:bundle": "node scripts/bundle-smoke.mjs",
42
41
  "coverage": "vitest run --coverage",
43
42
  "release": "bash scripts/publish.sh",
44
43
  "hooks": "bash scripts/install-hooks.sh",
@@ -73,9 +72,9 @@
73
72
  },
74
73
  "peerDependencies": {
75
74
  "@deepseek-ai/cordis": "^4.0.1",
76
- "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.0-rc.7",
77
- "@deepseek-ai/dsh-session": ">=0.1.0-rc.7",
78
- "@deepseek-ai/dsh-settings": ">=0.1.0-rc.7",
75
+ "@deepseek-ai/dsh-client-ui-primitives": ">=0.1.1-rc.2",
76
+ "@deepseek-ai/dsh-session": ">=0.1.1-rc.2",
77
+ "@deepseek-ai/dsh-settings": ">=0.1.1-rc.2",
79
78
  "@deepseek-ai/schemastery": "^3.18.1",
80
79
  "react": "^18.3.1"
81
80
  },
@@ -89,12 +88,11 @@
89
88
  },
90
89
  "devDependencies": {
91
90
  "@deepseek-ai/cordis": "^4.0.1",
92
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.1-rc.2",
93
- "@deepseek-ai/dsh-session": "^0.1.1-rc.2",
94
- "@deepseek-ai/dsh-session-projection": "^0.1.1-rc.2",
95
- "@deepseek-ai/dsh-settings": "^0.1.1-rc.2",
91
+ "@deepseek-ai/dsh-client-ui-primitives": "0.1.2-alpha.2",
92
+ "@deepseek-ai/dsh-session": "0.1.2-alpha.2",
93
+ "@deepseek-ai/dsh-session-projection": "0.1.2-alpha.2",
94
+ "@deepseek-ai/dsh-settings": "0.1.2-alpha.2",
96
95
  "@deepseek-ai/schemastery": "^3.18.1",
97
- "zod": "^4.4.3",
98
96
  "@stylistic/eslint-plugin": "^5.10.0",
99
97
  "@types/node": "^26.2.0",
100
98
  "@types/react": "^18.3.31",
@@ -110,7 +108,8 @@
110
108
  "react-dom": "^18.3.1",
111
109
  "tsdown": "^0.22.14",
112
110
  "typescript": "^7.0.2",
113
- "vitest": "^4.1.11"
111
+ "vitest": "^4.1.11",
112
+ "zod": "^4.4.3"
114
113
  },
115
114
  "packageManager": "pnpm@11.9.0"
116
115
  }