dsh-context 0.49.2 → 0.49.4

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/lib/client.js +99 -16
  2. package/lib/index.js +105 -29
  3. package/package.json +1 -1
package/lib/client.js CHANGED
@@ -67,6 +67,10 @@ window.__ModuleLoader__.load({
67
67
  "gran.turn": "轮次",
68
68
  "settings.title": "上下文",
69
69
  "settings.desc": "dsh-context 插件中上下文面板的偏好设置",
70
+ "settings.placement": "入口位置",
71
+ "placement.tab": "标签页",
72
+ "placement.sidebar": "侧边栏",
73
+ "placement.all": "全部",
70
74
  "settings.gran": "趋势图 · 默认粒度",
71
75
  "settings.mode": "趋势图 · 默认展示方式",
72
76
  "settings.fileSort": "文件活动 · 默认排序",
@@ -358,6 +362,10 @@ window.__ModuleLoader__.load({
358
362
  "gran.turn": "Turn",
359
363
  "settings.title": "Context",
360
364
  "settings.desc": "Preferences for Context panel in dsh-context plugin",
365
+ "settings.placement": "Entry Options",
366
+ "placement.tab": "Tab",
367
+ "placement.sidebar": "Sidebar",
368
+ "placement.all": "All",
361
369
  "settings.gran": "Trend Chart · Default granularity",
362
370
  "settings.mode": "Trend Chart · Default display",
363
371
  "settings.fileSort": "File Activity · Default sort",
@@ -4426,6 +4434,28 @@ window.__ModuleLoader__.load({
4426
4434
  role: "status",
4427
4435
  children: t("settings.readOnly")
4428
4436
  }) : null,
4437
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
4438
+ label: t("settings.placement"),
4439
+ value: state.placement,
4440
+ disabled,
4441
+ options: [
4442
+ {
4443
+ id: "all",
4444
+ label: t("placement.all")
4445
+ },
4446
+ {
4447
+ id: "tab",
4448
+ label: t("placement.tab")
4449
+ },
4450
+ {
4451
+ id: "sidebar",
4452
+ label: t("placement.sidebar")
4453
+ }
4454
+ ],
4455
+ onPick: (id) => {
4456
+ props.set?.("defaultPlacement", id);
4457
+ }
4458
+ }),
4429
4459
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PrefRow, {
4430
4460
  label: t("settings.gran"),
4431
4461
  value: state.granularity,
@@ -4489,6 +4519,7 @@ window.__ModuleLoader__.load({
4489
4519
  if (value === null || typeof value !== "object") return {};
4490
4520
  const v = value;
4491
4521
  return {
4522
+ ...v.defaultPlacement === "all" || v.defaultPlacement === "tab" || v.defaultPlacement === "sidebar" ? { placement: v.defaultPlacement } : {},
4492
4523
  ...v.defaultGranularity === "step" || v.defaultGranularity === "turn" ? { granularity: v.defaultGranularity } : {},
4493
4524
  ...v.defaultTrendMode === "total" || v.defaultTrendMode === "delta" ? { mode: v.defaultTrendMode } : {},
4494
4525
  ...v.defaultFileSort === "count" || v.defaultFileSort === "latest" || v.defaultFileSort === "path" ? { fileSort: v.defaultFileSort } : {}
@@ -4497,6 +4528,7 @@ window.__ModuleLoader__.load({
4497
4528
  function createContextSettings() {
4498
4529
  let state = {
4499
4530
  status: "loading",
4531
+ placement: "all",
4500
4532
  granularity: "step",
4501
4533
  mode: "total",
4502
4534
  fileSort: "count",
@@ -4505,20 +4537,23 @@ window.__ModuleLoader__.load({
4505
4537
  let scope;
4506
4538
  const listeners = /* @__PURE__ */ new Set();
4507
4539
  const publish = (next) => {
4508
- if (next.status === state.status && next.granularity === state.granularity && next.mode === state.mode && next.fileSort === state.fileSort && next.writable === state.writable) return;
4540
+ if (next.status === state.status && next.placement === state.placement && next.granularity === state.granularity && next.mode === state.mode && next.fileSort === state.fileSort && next.writable === state.writable) return;
4509
4541
  state = next;
4510
4542
  for (const listener of listeners) listener();
4511
4543
  };
4512
4544
  const sync = (bound) => {
4513
4545
  const snap = bound.getSnapshot();
4514
4546
  const prefs = prefsOf(snap.value);
4547
+ const rawPlacement = snap.value !== null && typeof snap.value === "object" ? snap.value.defaultPlacement : void 0;
4515
4548
  publish({
4516
4549
  status: snap.status === "ready" || snap.status === "unavailable" ? snap.status : "loading",
4550
+ placement: prefs.placement ?? (rawPlacement === void 0 ? state.placement : "all"),
4517
4551
  granularity: prefs.granularity ?? state.granularity,
4518
4552
  mode: prefs.mode ?? state.mode,
4519
4553
  fileSort: prefs.fileSort ?? state.fileSort,
4520
4554
  writable: snap.writable
4521
4555
  });
4556
+ return prefs.placement;
4522
4557
  };
4523
4558
  return {
4524
4559
  store: {
@@ -4530,6 +4565,7 @@ window.__ModuleLoader__.load({
4530
4565
  },
4531
4566
  getSnapshot: () => state
4532
4567
  },
4568
+ defaultPlacement: () => state.placement,
4533
4569
  defaultGranularity: () => state.granularity,
4534
4570
  defaultTrendMode: () => state.mode,
4535
4571
  defaultFileSort: () => state.fileSort,
@@ -4548,7 +4584,11 @@ window.__ModuleLoader__.load({
4548
4584
  const bound = scope;
4549
4585
  if (bound === void 0) return;
4550
4586
  bound.set(field, value).catch(() => {
4551
- sync(bound);
4587
+ const truth = sync(bound);
4588
+ if (field === "defaultPlacement" && truth === void 0) publish({
4589
+ ...state,
4590
+ placement: "all"
4591
+ });
4552
4592
  });
4553
4593
  }
4554
4594
  };
@@ -6640,7 +6680,7 @@ window.__ModuleLoader__.load({
6640
6680
  return function PluginInfo() {
6641
6681
  const [latest, setLatest] = (0, react.useState)(null);
6642
6682
  (0, react.useEffect)(() => {
6643
- if ("0.49.2".includes("-dev")) return;
6683
+ if ("0.49.4".includes("-dev")) return;
6644
6684
  let on = true;
6645
6685
  fetchLatestVersion().then((v) => {
6646
6686
  if (on && v) setLatest(v);
@@ -6649,8 +6689,8 @@ window.__ModuleLoader__.load({
6649
6689
  on = false;
6650
6690
  };
6651
6691
  }, []);
6652
- const update = latest !== null && isNewerVersion(latest, "0.49.2") ? latest : null;
6653
- const nameText = "dsh-context (v0.49.2)";
6692
+ const update = latest !== null && isNewerVersion(latest, "0.49.4") ? latest : null;
6693
+ const nameText = "dsh-context (v0.49.4)";
6654
6694
  const nameValue = [nameText];
6655
6695
  if (update) nameValue.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6656
6696
  className: "lc-pi-update",
@@ -8756,6 +8796,42 @@ window.__ModuleLoader__.load({
8756
8796
  };
8757
8797
  }
8758
8798
  //#endregion
8799
+ //#region src/client/placement.ts
8800
+ /**
8801
+ * Mount per the current preference and keep the registrations glued to it
8802
+ * until the returned disposer runs.
8803
+ */
8804
+ function watchPlacement(settings, mounts) {
8805
+ let tab;
8806
+ let sidebar;
8807
+ const own = (result) => typeof result === "function" ? result : void 0;
8808
+ const mount = (placement) => {
8809
+ const wantTab = placement !== "sidebar";
8810
+ const wantSidebar = placement !== "tab";
8811
+ if (wantTab && tab === void 0) tab = own(mounts.tab());
8812
+ if (!wantTab && tab !== void 0) {
8813
+ tab();
8814
+ tab = void 0;
8815
+ }
8816
+ if (wantSidebar && sidebar === void 0) sidebar = own(mounts.sidebar());
8817
+ if (!wantSidebar && sidebar !== void 0) {
8818
+ sidebar();
8819
+ sidebar = void 0;
8820
+ }
8821
+ };
8822
+ mount(settings.defaultPlacement());
8823
+ const unsubscribe = settings.store.subscribe(() => {
8824
+ mount(settings.defaultPlacement());
8825
+ });
8826
+ return () => {
8827
+ unsubscribe();
8828
+ tab?.();
8829
+ sidebar?.();
8830
+ tab = void 0;
8831
+ sidebar = void 0;
8832
+ };
8833
+ }
8834
+ //#endregion
8759
8835
  //#region src/client/icon.tsx
8760
8836
  /** The sheet's strokes, in paint order, as `[path, fill]` pairs. */
8761
8837
  const SHEET_PATHS = [
@@ -8853,9 +8929,11 @@ window.__ModuleLoader__.load({
8853
8929
  * locale at call time, so a language switch relabels the guide entry.
8854
8930
  * @param ns - the plugin's locale namespace, put on the body registration so
8855
8931
  * the framework synthesizes the `t` seat for the panel too.
8932
+ * @returns the deferred inject's disposer (placement toggling calls it to
8933
+ * take the mount down; a no-op on faces that return no handle).
8856
8934
  */
8857
8935
  function watchSidebarContextTab(ctx, view, t, ns) {
8858
- ctx.inject(["sidebarRightTabs"], (raw) => {
8936
+ const handle = ctx.inject(["sidebarRightTabs"], (raw) => {
8859
8937
  const injected = raw;
8860
8938
  const disposers = [];
8861
8939
  const own = (result) => {
@@ -8895,6 +8973,9 @@ window.__ModuleLoader__.load({
8895
8973
  for (const dispose of disposers) dispose();
8896
8974
  };
8897
8975
  });
8976
+ return () => {
8977
+ handle?.dispose?.();
8978
+ };
8898
8979
  }
8899
8980
  //#endregion
8900
8981
  //#region src/client/viewkit.ts
@@ -9103,16 +9184,18 @@ window.__ModuleLoader__.load({
9103
9184
  watchHistoryFaces(ctx);
9104
9185
  const settings = createContextSettings();
9105
9186
  const ContextView = makeContextView(ctx, kit, settings);
9106
- ctx.slots.inject("conversation.view", () => {
9107
- return ctx.slots.register({
9108
- name: "conversation.view",
9109
- id: "context",
9110
- order: 20,
9111
- locale: NS,
9112
- label: () => t("tab")
9113
- }, (props) => (0, react.createElement)(ContextView, props));
9114
- });
9115
- watchSidebarContextTab(ctx, ContextView, t, NS);
9187
+ ctx.effect(() => watchPlacement(settings, {
9188
+ tab: () => ctx.slots.inject("conversation.view", () => {
9189
+ return ctx.slots.register({
9190
+ name: "conversation.view",
9191
+ id: "context",
9192
+ order: 20,
9193
+ locale: NS,
9194
+ label: () => t("tab")
9195
+ }, (props) => (0, react.createElement)(ContextView, props));
9196
+ }),
9197
+ sidebar: () => watchSidebarContextTab(ctx, ContextView, t, NS)
9198
+ }), "dsh-context: placement");
9116
9199
  const ContextJump = makeContextJumpButton(kit);
9117
9200
  ctx.slots.inject("conversation.chat.assistant-actions", () => {
9118
9201
  return ctx.slots.register({
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
- import { dirname, join, normalize } from "node:path";
3
+ import { dirname, join, normalize, relative } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { z } from "zod";
6
6
  import { deriveEventMessage } from "@deepseek-ai/dsh-session";
@@ -2009,7 +2009,7 @@ function watchDetailChannel(ctx, bounds) {
2009
2009
  * host probes and gates; the client displays what the wire record carries),
2010
2010
  * so this module must stay dependency-free.
2011
2011
  *
2012
- * The baseline mirrors the support matrix (AGENTS.md "Compatibility" and the
2012
+ * The baseline mirrors the support matrix (docs/compatibility.md and the
2013
2013
  * package's `dsh.compatibility.dshReleases` declaration): the oldest dsh
2014
2014
  * release this plugin works on. A harness BELOW it gets the fallback units
2015
2015
  * (host/fallback.ts) instead of the real folds.
@@ -2607,6 +2607,11 @@ function createFallbackHeadersDefinition() {
2607
2607
  const SETTINGS_NAMESPACE = "dsh-context";
2608
2608
  /** Section schema: also the wire envelope the browser scope validates against. */
2609
2609
  const SettingsSchema = z$1.object({
2610
+ defaultPlacement: z$1.union([
2611
+ "all",
2612
+ "tab",
2613
+ "sidebar"
2614
+ ]).default("all").loose(),
2610
2615
  defaultGranularity: z$1.union(["step", "turn"]).default("step"),
2611
2616
  defaultTrendMode: z$1.union(["total", "delta"]).default("total").loose(),
2612
2617
  defaultFileSort: z$1.union([
@@ -2707,35 +2712,72 @@ function watchStepIdentity(ctx) {
2707
2712
  * The probe never imports a harness package for its version; it reads
2708
2713
  * manifests through Node resolution from two anchors, in order:
2709
2714
  *
2710
- * 1. The harness home's healed `profiles/node_modules` mirror (located via
2711
- * the app-boot `dshHomePath` service) it symlinks/proxies the running
2712
- * installation's dependency closure, so it names the true release even
2713
- * when this plugin is a `link:`-installed dev checkout.
2714
- * 2. This module itself in a real profile install the plugin sits inside
2715
- * the profile's node_modules and walk-up resolution reaches the same
2716
- * mirror; this anchor also covers harnesses too old to provide the home
2717
- * service.
2715
+ * 1. This module's own ESM resolution of the library packages the Host half
2716
+ * imports at runtime. That is the very pipeline the process resolves those
2717
+ * imports through, so an embedding shell that redirects plugin imports —
2718
+ * the packaged Desktop's ASAR resolver bridge does exactly this — redirects
2719
+ * the probe identically, and the answer names the module instances the
2720
+ * harness is actually running. A witness that lands inside this package's
2721
+ * own tree is the plugin's dependency closure (a `link:`-installed dev
2722
+ * checkout's pinned devDependencies), never the harness, so it is discarded
2723
+ * for the home anchor.
2724
+ * 2. The harness home's healed `profiles/node_modules` mirror (located via
2725
+ * the app-boot `dshHomePath` service). It names the installation that last
2726
+ * healed it: authoritative for a dev checkout, whose own tree holds pinned
2727
+ * devDependencies, but only as fresh as the last CLI profile boot. A
2728
+ * packaged Desktop never heals it, so a stale global-CLI mirror can outlive
2729
+ * the CLI and misname the running harness — that is why it answers only
2730
+ * when the running anchor has nothing it can trust.
2718
2731
  *
2719
2732
  * The `@deepseek-ai/dsh` CLI package is probed ONLY through the home anchor:
2720
- * the plugin never depends on it, so a hit from the module anchor can only
2721
- * be an ambient install above the plugin's tree (e.g. a global copy under
2733
+ * the plugin never imports it, so a hit from the running anchor can only be an
2734
+ * ambient install above the plugin's tree (e.g. a global copy under
2722
2735
  * ~/node_modules) — not necessarily the RUNNING harness.
2723
2736
  *
2724
2737
  * Every step is guarded: any failure (absent service, unresolvable package,
2725
- * unreadable/invalid manifest, non-string version) degrades to `undefined`,
2726
- * and the gate treats an unknown version as SATISFIED — a probe misfire must
2727
- * never blank a working deployment.
2738
+ * non-file URL, unreadable/invalid manifest, non-string version) degrades to
2739
+ * `undefined`, and the gate treats an unknown version as SATISFIED — a probe
2740
+ * misfire must never blank a working deployment.
2728
2741
  */
2729
2742
  /**
2730
- * Library packages whose manifest version IS the harness release version
2731
- * (the dsh monorepo versions every package in lockstep). From the plugin's
2732
- * own tree they resolve to its pinned devDependencies in dev and to the
2733
- * healed profiles mirror in real installs — both correct — so they are safe
2734
- * to probe from either anchor.
2743
+ * Library packages whose manifest version IS the harness release version (the
2744
+ * dsh monorepo versions every package in lockstep). The home anchor probes
2745
+ * them after the CLI package; the running anchor probes the two first- and
2746
+ * second-ordered.
2735
2747
  */
2736
2748
  const LIBRARY_PROBE_PACKAGES = ["@deepseek-ai/dsh-session-projection", "@deepseek-ai/dsh-session"];
2749
+ /**
2750
+ * Running-anchor probe order: the package the Host half genuinely imports at
2751
+ * runtime first (host/fold.ts imports it, so it MUST resolve whenever this
2752
+ * plugin runs), then its co-versioned sibling.
2753
+ */
2754
+ const RUNNING_PROBE_PACKAGES = ["@deepseek-ai/dsh-session", "@deepseek-ai/dsh-session-projection"];
2737
2755
  /** Home-anchor probe order: the user-facing CLI version first, then the libraries. */
2738
2756
  const HOME_PROBE_PACKAGES = ["@deepseek-ai/dsh", ...LIBRARY_PROBE_PACKAGES];
2757
+ /**
2758
+ * This package's root. A running-anchor witness under it belongs to the
2759
+ * plugin's own dependency closure, not to the harness.
2760
+ *
2761
+ * Located through the package's own `./package.json` self-reference: Node
2762
+ * resolves that against the nearest package.json, so the answer is the package
2763
+ * root in the source tree and in the bundled profile install alike — a fixed
2764
+ * number of `..` steps cannot be, since `src/host/` and `lib/` differ by a
2765
+ * level. A loader that cannot self-resolve (an exotic loader, or a fork renamed
2766
+ * without a matching `exports`) falls back to this module's own directory: the
2767
+ * guard then covers less of the package, but it still can never mistake the
2768
+ * plugin's own `node_modules` for the harness.
2769
+ */
2770
+ function ownPackageRoot() {
2771
+ try {
2772
+ return dirname(fileURLToPath(import.meta.resolve("dsh-context/package.json")));
2773
+ } catch {
2774
+ /* v8 ignore next -- a loader that cannot resolve one of its own package's
2775
+ published subpaths is out of the suite's reach; the fallback keeps one
2776
+ probe path (an unanswered probe fails open either way). */
2777
+ return dirname(fileURLToPath(import.meta.url));
2778
+ }
2779
+ }
2780
+ const PLUGIN_ROOT = ownPackageRoot();
2739
2781
  /** One manifest's `version`, or undefined on any read/shape failure. */
2740
2782
  function versionOfManifest(manifestPath, expectedName) {
2741
2783
  try {
@@ -2785,11 +2827,51 @@ function probeAnchor(resolve, packageNames) {
2785
2827
  }
2786
2828
  }
2787
2829
  /**
2830
+ * Whether `candidate` lies inside `root`'s tree. A `..`-prefixed relative path
2831
+ * escapes it; a sibling such as `dsh-context-extra` is reached through `..` too,
2832
+ * so it never counts as a child. A pathological child literally named `..foo`
2833
+ * would degrade to the home anchor, which is the safe direction.
2834
+ */
2835
+ function isInside(root, candidate) {
2836
+ return !relative(root, candidate).startsWith("..");
2837
+ }
2838
+ /**
2839
+ * The running module tree's answer, or undefined when it cannot be trusted.
2840
+ * Each probe package is checked on its own: a witness inside the plugin's own
2841
+ * closure is the plugin's dependency, not the harness, and a trusted witness
2842
+ * whose manifest cannot be read is no answer either — the next package may
2843
+ * still resolve to one.
2844
+ */
2845
+ function probeRunningTree(resolve, pluginRoot) {
2846
+ for (const packageName of RUNNING_PROBE_PACKAGES) {
2847
+ let witness;
2848
+ try {
2849
+ witness = resolve(packageName);
2850
+ } catch {
2851
+ continue;
2852
+ }
2853
+ if (isInside(pluginRoot, witness)) continue;
2854
+ const version = versionViaManifest(resolve, packageName) ?? versionViaEntry(resolve, packageName);
2855
+ if (version !== void 0) return version;
2856
+ }
2857
+ }
2858
+ /** The default running anchor: this module's own ESM resolution. */
2859
+ function resolveRunningModule(specifier) {
2860
+ return import.meta.resolve(specifier);
2861
+ }
2862
+ /**
2788
2863
  * The running harness's version string, or undefined when nothing answers
2789
2864
  * (the gate fails open on it — see the header note).
2790
- * @param selfUrl - the module anchor (injectable for hermetic tests).
2791
- */
2792
- function detectHarnessVersion(ctx, selfUrl = import.meta.url) {
2865
+ * @param resolveUrl - the running anchor's URL resolver (injectable for hermetic tests).
2866
+ * @param pluginRoot - this package's root for the own-closure check (injectable for hermetic tests).
2867
+ */
2868
+ function detectHarnessVersion(ctx, resolveUrl = resolveRunningModule, pluginRoot = PLUGIN_ROOT) {
2869
+ const running = probeRunningTree((specifier) => {
2870
+ const url = resolveUrl(specifier);
2871
+ if (!url.startsWith("file:")) throw new Error(`dsh-context: non-file module URL for ${specifier}`);
2872
+ return fileURLToPath(url);
2873
+ }, pluginRoot);
2874
+ if (running !== void 0) return running;
2793
2875
  try {
2794
2876
  const homePath = ctx.get("dshHomePath");
2795
2877
  if (typeof homePath === "function") {
@@ -2798,12 +2880,6 @@ function detectHarnessVersion(ctx, selfUrl = import.meta.url) {
2798
2880
  if (home !== void 0) return home;
2799
2881
  }
2800
2882
  } catch {}
2801
- try {
2802
- const req = createRequire(selfUrl);
2803
- return probeAnchor((specifier) => req.resolve(specifier), LIBRARY_PROBE_PACKAGES);
2804
- } catch {
2805
- return;
2806
- }
2807
2883
  }
2808
2884
  //#endregion
2809
2885
  //#region src/host/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-context",
3
- "version": "0.49.2",
3
+ "version": "0.49.4",
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": {