dsh-m 0.2.13 → 0.2.14

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/lib/client.js CHANGED
@@ -604,6 +604,8 @@ var ZH = {
604
604
  "warn.unlink": "\u5378\u8F7D\u53EA\u79FB\u9664 profile \u5BF9\u672C\u5730\u76EE\u5F55\u7684\u5F15\u7528\uFF08{path}\uFF09\uFF0C\u4E0D\u4F1A\u5220\u9664\u76EE\u5F55\u672C\u8EAB\u3002",
605
605
  "warn.core": "\u8FD9\u662F file: \u5B89\u88C5\u7684\u6838\u5FC3/\u5F52\u6863\u5305\uFF0C\u5378\u8F7D\u53EF\u80FD\u5F71\u54CD DSH \u529F\u80FD\uFF0C\u4E14\u9700\u8981\u624B\u52A8\u6062\u590D\u3002",
606
606
  "profile.hint": "web profile\uFF1A{path}",
607
+ "dsh.chip.copyhint": "\u70B9\u51FB\u590D\u5236\u7248\u672C\u53F7",
608
+ "dsh.chip.copied": "\u5DF2\u590D\u5236 \u2713",
607
609
  "title.panel": "\u63D2\u4EF6\u5E02\u573A",
608
610
  "title.full": "DeepSeek Harness \u63D2\u4EF6\u5E02\u573A"
609
611
  };
@@ -770,6 +772,8 @@ var EN = {
770
772
  "warn.unlink": "Uninstalling only removes the profile's reference to the local directory ({path}); the directory itself is kept.",
771
773
  "warn.core": "This is a core/archive package installed via file:. Uninstalling may affect DSH features and requires manual restore.",
772
774
  "profile.hint": "web profile: {path}",
775
+ "dsh.chip.copyhint": "Click to copy version",
776
+ "dsh.chip.copied": "Copied \u2713",
773
777
  "title.panel": "Plugin Marketplace",
774
778
  "title.full": "DeepSeek Harness Plugin Marketplace"
775
779
  };
@@ -885,6 +889,14 @@ var CSS = `
885
889
  [data-slot="sidebar.footer.action"]{display:flex!important;flex-direction:column;width:100%;min-width:0}
886
890
  [data-slot="sidebar.footer.action"]>*{flex:none;min-width:0}
887
891
  .dshm-empty{text-align:center;color:var(--dsw-alias-label-caption,#6b7280);font-size:13px;padding:32px 0}
892
+ /* \u5934\u90E8 DSH \u7248\u672C chip\uFF1A\u7B49\u5BBD\u5C0F\u5B57\u5706\u89D2\uFF0Chover \u5C55\u5F00\u8BE6\u60C5\uFF0C\u70B9\u51FB\u590D\u5236\uFF082026-09-14 \u5B9A\u7A3F\uFF09 */
893
+ .dshm-dshchip{position:relative;display:inline-flex;align-items:center;gap:4px;border:1px solid var(--dsw-alias-border-l2,#e5e7eb);background:var(--dsw-alias-bg-layer-1,#f5f6f8);color:var(--dsw-alias-label-secondary,#4b5563);border-radius:999px;padding:4px 10px;font:11px/1 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;cursor:pointer;white-space:nowrap;flex:none}
894
+ .dshm-dshchip:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06));color:var(--dsw-alias-label-primary,inherit)}
895
+ .dshm-dshchip-v{font-weight:600;color:var(--dsw-alias-label-primary,inherit)}
896
+ .dshm-dshchip-tip{position:absolute;top:calc(100% + 7px);right:0;visibility:hidden;opacity:0;transition:opacity .12s;z-index:60;background:var(--dsw-alias-bg-overlay,#fff);border:1px solid var(--dsw-alias-border-l2,#e5e7eb);border-radius:9px;box-shadow:var(--dsw-shadow-lv1,0 2px 8px rgb(20 24 32 / 8%));padding:7px 10px;text-align:left;pointer-events:none}
897
+ .dshm-dshchip:hover .dshm-dshchip-tip,.dshm-dshchip:focus-visible .dshm-dshchip-tip{visibility:visible;opacity:1}
898
+ .dshm-dshchip-tiprow{font:12px/16px ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-weight:600;color:var(--dsw-alias-label-primary,inherit)}
899
+ .dshm-dshchip-tipsub{display:block;margin-top:2px;font-size:10px;line-height:14px;color:var(--dsw-alias-label-caption,#9ca3af)}
888
900
  `;
889
901
  function ensureCss() {
890
902
  if (typeof document === "undefined" || document.getElementById("dshm-css")) return;
@@ -1646,10 +1658,44 @@ var TABS = [
1646
1658
  ["installed", "tab.installed", "installed"],
1647
1659
  ["settings", "tab.settings", null]
1648
1660
  ];
1661
+ function DshVersionChip({ version }) {
1662
+ const [copied, setCopied] = useState(false);
1663
+ const copy = async () => {
1664
+ try {
1665
+ await navigator.clipboard.writeText(version);
1666
+ setCopied(true);
1667
+ setTimeout(() => setCopied(false), 1600);
1668
+ } catch {
1669
+ }
1670
+ };
1671
+ return h(
1672
+ "button",
1673
+ { type: "button", className: "dshm-dshchip", onClick: copy, "aria-label": `DSH ${version}` },
1674
+ "DSH ",
1675
+ h("span", { className: "dshm-dshchip-v" }, version),
1676
+ h(
1677
+ "span",
1678
+ { className: "dshm-dshchip-tip", role: "tooltip" },
1679
+ h("span", { className: "dshm-dshchip-tiprow" }, `DSH ${version}`),
1680
+ h("span", { className: "dshm-dshchip-tipsub" }, lookup(copied ? "dsh.chip.copied" : "dsh.chip.copyhint"))
1681
+ )
1682
+ );
1683
+ }
1649
1684
  function MarketPanel({ onClose }) {
1650
1685
  const [tab, setTab] = useState("market");
1651
1686
  const market = useMarketData();
1652
1687
  const installed = useAsync(() => api("installed"), []);
1688
+ const [dshVersion, setDshVersion] = useState(null);
1689
+ useEffect(() => {
1690
+ let live = true;
1691
+ api("ping").then((r) => {
1692
+ if (live) setDshVersion(typeof r?.dshVersion === "string" && r.dshVersion ? r.dshVersion : null);
1693
+ }).catch(() => {
1694
+ });
1695
+ return () => {
1696
+ live = false;
1697
+ };
1698
+ }, []);
1653
1699
  const refreshViews = useCallback(
1654
1700
  () => refreshAfterMutation2({ marketReload: market.reload, installedReload: installed.reload }),
1655
1701
  [market.reload, installed.reload]
@@ -1707,6 +1753,7 @@ function MarketPanel({ onClose }) {
1707
1753
  )
1708
1754
  ),
1709
1755
  h("span", { className: "dshm-spacer" }),
1756
+ dshVersion ? h(DshVersionChip, { version: dshVersion }) : null,
1710
1757
  h("button", { className: "dshm-btn", onClick: onClose }, lookup("common.close"))
1711
1758
  ),
1712
1759
  h(
@@ -0,0 +1,103 @@
1
+ /**
2
+ * DSH 运行版本解析(市场面板头部 chip 数据源,2026-09-14 设计定稿)。
3
+ *
4
+ * 通路(查证结论,三条免费路径已证伪):生产 host 内 dshArgv() 以 process.argv[1]
5
+ * 定位 launcher entry(…/@deepseek-ai/dsh/lib/bin.js,restart.ts 拉起重启即复用同一
6
+ * 定位),从 entry 向上至多三级找 package.json 读 version——官方 bin.js 的 readVersion
7
+ * 同款做法。CLI/PATH 模式或读取失败回退 spawn `dsh --version`(短超时)。
8
+ *
9
+ * 结果进程内缓存(版本随进程恒定);两级皆失败返回 null,客户端据此隐藏 chip。
10
+ * 注意:`window.__DSH_BOOT__` 只有内容哈希 rev 无 semver;settings.yaml 无版本键;
11
+ * .pnpm 目录名多版本残留无判据——均不走。
12
+ */
13
+ import { dirname, join } from 'node:path';
14
+ import { readFileSync } from 'node:fs';
15
+ import { spawn } from 'node:child_process';
16
+ import { dshArgv } from './dsh-cli.js';
17
+ /** 从 `dsh --version` 输出提取 semver:容忍 v 前缀与前后散文,取首个命中。 */
18
+ export function parseDshVersionOutput(text) {
19
+ const m = /\bv?([0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)/.exec(text ?? '');
20
+ return m ? m[1] : null;
21
+ }
22
+ /**
23
+ * 生产通路:launcher entry 定位 + 就近向上找 package.json。
24
+ * 纯同步、无 spawn;任何一步失败返回 null(不抛)。
25
+ */
26
+ export function readLauncherPackageVersion(input = {}) {
27
+ try {
28
+ const argv = dshArgv(input);
29
+ const entry = argv.args.length > 0 ? argv.args[argv.args.length - 1] : undefined;
30
+ // 与 dshArgv 同一判定:entry 必须长得像 launcher bin,否则是 CLI/PATH 模式
31
+ if (!entry || !/[\\/](?:bin\.(?:js|ts)|dsh)$/.test(entry))
32
+ return null;
33
+ let dir = dirname(entry);
34
+ for (let up = 0; up < 3; up++) {
35
+ try {
36
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
37
+ if (typeof pkg?.version === 'string' && pkg.version)
38
+ return pkg.version;
39
+ }
40
+ catch {
41
+ /* 该级无 package.json / 不可读,继续向上一级 */
42
+ }
43
+ dir = dirname(dir);
44
+ }
45
+ return null;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ function spawnDshVersion(timeoutMs) {
52
+ return new Promise((resolveP) => {
53
+ let settled = false;
54
+ const done = (v) => {
55
+ if (!settled) {
56
+ settled = true;
57
+ resolveP(v);
58
+ }
59
+ };
60
+ let child;
61
+ try {
62
+ child = spawn('dsh', ['--version'], { stdio: ['ignore', 'pipe', 'ignore'], shell: process.platform === 'win32' });
63
+ }
64
+ catch {
65
+ done(null);
66
+ return;
67
+ }
68
+ let out = '';
69
+ const timer = setTimeout(() => {
70
+ try {
71
+ child.kill('SIGKILL');
72
+ }
73
+ catch {
74
+ /* already dead */
75
+ }
76
+ done(null);
77
+ }, timeoutMs);
78
+ child.stdout?.on('data', (d) => {
79
+ out += String(d);
80
+ });
81
+ child.on('error', () => {
82
+ clearTimeout(timer);
83
+ done(null);
84
+ });
85
+ child.on('close', () => {
86
+ clearTimeout(timer);
87
+ done(parseDshVersionOutput(out));
88
+ });
89
+ });
90
+ }
91
+ let cache = null;
92
+ /** 进程内缓存的一次性解析;null = 两级皆失败(调用方按「未知」处理)。 */
93
+ export function resolveDshVersion(opts = {}) {
94
+ if (!cache) {
95
+ const direct = readLauncherPackageVersion();
96
+ cache = direct != null ? Promise.resolve(direct) : spawnDshVersion(opts.fallbackTimeoutMs ?? 3_000);
97
+ }
98
+ return cache;
99
+ }
100
+ /** 仅测试:清空进程内缓存。 */
101
+ export function resetDshVersionCacheForTest() {
102
+ cache = null;
103
+ }
@@ -7,6 +7,7 @@
7
7
  * trustedRestartRequest host-equivalence guard → typed method/业务错误映射 → 其他 500。
8
8
  */
9
9
  import { BOOT_ID, publicInstallStatus } from './dsh-cli.js';
10
+ import { resolveDshVersion } from './dsh-version.js';
10
11
  import { listInstalledWithMeta, listMarket, installFromRegistry, uninstallPlugin, upgradePlugin, } from './market.js';
11
12
  import { runProfileTransaction, TransactionError, makeNpmWarmPackument } from './profile-transaction.js';
12
13
  import { readInstalledPluginReadme } from './installed.js';
@@ -162,8 +163,13 @@ export function createApiDispatcher(ctx) {
162
163
  npmLatest: ctx.deps?.npmLatest ?? npmLatest,
163
164
  runTransaction: ctx.deps?.runTransaction ?? runProfileTransaction,
164
165
  scheduleRestart: ctx.deps?.scheduleRestart ?? scheduleRestart,
166
+ resolveDshVersion: ctx.deps?.resolveDshVersion ?? resolveDshVersion,
165
167
  };
166
168
  const cfg = () => ctx.controller.config;
169
+ // DSH 版本一次性解析,dispatcher 创建即预热(首个 ping 不吃 spawn 回退的延迟)
170
+ const dshVersionP = Promise.resolve()
171
+ .then(() => d.resolveDshVersion())
172
+ .catch(() => null);
167
173
  return async function handleApi(req, res) {
168
174
  const abort = new AbortController();
169
175
  res.once('close', () => abort.abort());
@@ -173,7 +179,14 @@ export function createApiDispatcher(ctx) {
173
179
  let payload;
174
180
  switch (method) {
175
181
  case 'ping':
176
- payload = { plugin: ctx.pkg.name, version: ctx.pkg.version, node: process.version, boot: BOOT_ID };
182
+ payload = {
183
+ plugin: ctx.pkg.name,
184
+ version: ctx.pkg.version,
185
+ node: process.version,
186
+ boot: BOOT_ID,
187
+ // DSH 运行版本(头部 chip 数据源);解析失败 → undefined → JSON 序列化时字段缺席
188
+ dshVersion: (await dshVersionP) ?? undefined,
189
+ };
177
190
  break;
178
191
  case 'self-check': {
179
192
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",