@xrxs-ai/dsh-xrxs-plugins-store 0.2.4 → 0.2.6

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
@@ -462,6 +462,13 @@ function formatDate(text) {
462
462
  if (d === null) return text;
463
463
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
464
464
  }
465
+ function formatShortDate(text) {
466
+ const d = parseTime(text);
467
+ if (d === null) return text;
468
+ const now = /* @__PURE__ */ new Date();
469
+ const yearPrefix = d.getFullYear() === now.getFullYear() ? "" : `${String(d.getFullYear() % 100).padStart(2, "0")}-`;
470
+ return `${yearPrefix}${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
471
+ }
465
472
  function formatRelative(text) {
466
473
  const parsed = parseTime(text);
467
474
  if (parsed === null) return text;
@@ -1042,30 +1049,8 @@ var MARKET_CSS = `
1042
1049
  font-size: 13px; line-height: 20px; color: var(--dsw-alias-label-primary, #1f2329);
1043
1050
  text-align: right; word-break: break-all;
1044
1051
  }
1045
- .xps-mk-version {
1046
- display: flex; justify-content: space-between; align-items: center; gap: 8px;
1047
- width: 100%; box-sizing: border-box; padding: 4px 8px;
1048
- border: none; border-radius: 8px; background: transparent; cursor: pointer;
1049
- color: var(--dsw-alias-label-primary, #1f2329);
1050
- font-family: var(--dsw-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1051
- font-size: 12px; line-height: 18px; text-align: left;
1052
- transition: background-color 0.15s ease;
1053
- }
1054
- .xps-mk-version:hover { background: var(--dsw-alias-interactive-bg-hover, rgba(31, 35, 41, 0.06)); }
1055
- .xps-mk-version:active { background: var(--dsw-alias-interactive-bg-active, rgba(38, 49, 72, 0.1)); }
1056
- .xps-mk-version:focus-visible {
1057
- outline: 2px solid var(--dsw-alias-brand-primary, #3370ff);
1058
- outline-offset: -2px;
1059
- }
1060
- .xps-mk-version-active, .xps-mk-version-active:hover {
1061
- background: var(--dsw-alias-interactive-bg-active, rgba(38, 49, 72, 0.1));
1062
- color: var(--dsw-alias-label-primary, #1f2329);
1063
- }
1064
- .xps-mk-version-date {
1065
- flex: none; font-size: 11px; line-height: 17px;
1066
- font-family: inherit;
1067
- color: var(--dsw-alias-label-tertiary, #8f959e);
1068
- }
1052
+ /* \u8BE6\u60C5\u9875\u7248\u672C\u5386\u53F2\u4E0B\u62C9:\u5360\u6EE1\u5361\u7247\u5BBD(.xps-mk-select \u9ED8\u8BA4 flex:none \u53EA\u9002\u914D\u5DE5\u5177\u680F) */
1053
+ .xps-mk-version-select { display: block; width: 100%; margin-top: 8px; }
1069
1054
 
1070
1055
  /* README markdown \u767D\u540D\u5355\u6E32\u67D3 */
1071
1056
  .xps-md {
@@ -1125,8 +1110,7 @@ var MARKET_CSS = `
1125
1110
  .xps-mk-chip,
1126
1111
  .xps-mk-row,
1127
1112
  .xps-mk-page,
1128
- .xps-mk-select,
1129
- .xps-mk-version {
1113
+ .xps-mk-select {
1130
1114
  transition: none;
1131
1115
  }
1132
1116
  .xps-mk-page:active:not(:disabled) {
@@ -2267,6 +2251,12 @@ function ConfirmDialog3(props) {
2267
2251
  ] })
2268
2252
  ] }) });
2269
2253
  }
2254
+ function byInstalledAtDesc(a, b) {
2255
+ const ta = a.installedAt ?? "";
2256
+ const tb = b.installedAt ?? "";
2257
+ if (ta !== tb) return ta < tb ? 1 : -1;
2258
+ return a.name.localeCompare(b.name);
2259
+ }
2270
2260
  function InstalledPage() {
2271
2261
  ensureMarketStyles();
2272
2262
  const { items, push, dismiss } = useToasts();
@@ -2280,7 +2270,7 @@ function InstalledPage() {
2280
2270
  const load = (0, import_react5.useCallback)(() => {
2281
2271
  setList(null);
2282
2272
  setError(false);
2283
- void getInstalled().then((installed) => setList(installed)).catch(() => {
2273
+ void getInstalled().then((installed) => setList([...installed].sort(byInstalledAtDesc))).catch(() => {
2284
2274
  setList([]);
2285
2275
  setError(true);
2286
2276
  });
@@ -2549,6 +2539,24 @@ function satisfies(version, range) {
2549
2539
 
2550
2540
  // src/client/pages/market/InstallButton.tsx
2551
2541
  var import_react6 = require("react");
2542
+
2543
+ // src/client/pages/market/version.ts
2544
+ function compareVersions(a, b) {
2545
+ const parse = (text) => text.trim().replace(/^v/, "").split(".").map((part) => {
2546
+ const numeric = /^\d+/.exec(part);
2547
+ return numeric === null ? 0 : Number(numeric[0]);
2548
+ });
2549
+ const left = parse(a);
2550
+ const right = parse(b);
2551
+ const length = Math.max(left.length, right.length);
2552
+ for (let index = 0; index < length; index += 1) {
2553
+ const diff = (left[index] ?? 0) - (right[index] ?? 0);
2554
+ if (diff !== 0) return diff > 0 ? 1 : -1;
2555
+ }
2556
+ return 0;
2557
+ }
2558
+
2559
+ // src/client/pages/market/InstallButton.tsx
2552
2560
  var import_jsx_runtime15 = require("react/jsx-runtime");
2553
2561
  function InstallButton(props) {
2554
2562
  const [phase, setPhase] = (0, import_react6.useState)("idle");
@@ -2556,10 +2564,11 @@ function InstallButton(props) {
2556
2564
  const [buildConfirm, setBuildConfirm] = (0, import_react6.useState)(null);
2557
2565
  const [approving, setApproving] = (0, import_react6.useState)(false);
2558
2566
  const compat = checkCompat(props.compatRange, props.dshVersion);
2559
- const verb = props.installed?.updateAvailable === true ? "\u66F4\u65B0" : "\u5B89\u88C5";
2560
- const variant = verb === "\u66F4\u65B0" ? "update" : "primary";
2561
- if (props.installed !== void 0 && !props.installed.updateAvailable) {
2562
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "xps-mk-install", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { size: props.size ?? "medium", variant: "primary", disabled: true, title: "\u5DF2\u5B89\u88C5\u6700\u65B0\u7248\u672C,\u91CD\u88C5\u540C\u7248\u672C\u4E0D\u4F1A\u5237\u65B0", children: "\u5DF2\u5B89\u88C5" }) });
2567
+ const sameAsInstalled = props.installed !== void 0 && props.installed.version === props.version;
2568
+ const verb = props.installed === void 0 || sameAsInstalled ? "\u5B89\u88C5" : compareVersions(props.version, props.installed.version) >= 0 ? "\u66F4\u65B0" : "\u964D\u7EA7";
2569
+ const variant = verb === "\u5B89\u88C5" ? "primary" : "update";
2570
+ if (sameAsInstalled) {
2571
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "xps-mk-install", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Button, { size: props.size ?? "medium", variant: "primary", disabled: true, title: "\u5DF2\u5B89\u88C5\u6B64\u7248\u672C,\u91CD\u88C5\u540C\u7248\u672C\u4E0D\u4F1A\u5237\u65B0", children: "\u5DF2\u5B89\u88C5" }) });
2563
2572
  }
2564
2573
  if (compat.ok === false) {
2565
2574
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "xps-mk-install", onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
@@ -3233,12 +3242,32 @@ function PluginDetailPage(props) {
3233
3242
  const [dshVersion, setDshVersion] = (0, import_react8.useState)(null);
3234
3243
  const [installed, setInstalled] = (0, import_react8.useState)(null);
3235
3244
  const toasts = useToasts();
3245
+ const installedSeq = (0, import_react8.useRef)(0);
3236
3246
  const loadInstalled = (0, import_react8.useCallback)(
3237
- (name) => {
3238
- getInstalled().then((list) => setInstalled(list.find((item) => item.name === name) ?? null)).catch(() => setInstalled(null));
3247
+ (...names) => {
3248
+ const seq = ++installedSeq.current;
3249
+ getInstalled().then((list) => {
3250
+ if (seq === installedSeq.current) {
3251
+ setInstalled(list.find((item) => names.includes(item.name)) ?? null);
3252
+ }
3253
+ }).catch(() => {
3254
+ if (seq === installedSeq.current) setInstalled(null);
3255
+ });
3239
3256
  },
3240
3257
  []
3241
3258
  );
3259
+ const selectVersion = (version) => {
3260
+ setSelectedVersion(version);
3261
+ if (detail !== null) loadInstalled(pluginId, detail.name);
3262
+ };
3263
+ (0, import_react8.useEffect)(() => {
3264
+ if (detail === null) return;
3265
+ const onVisible = () => {
3266
+ if (document.visibilityState === "visible") loadInstalled(pluginId, detail.name);
3267
+ };
3268
+ document.addEventListener("visibilitychange", onVisible);
3269
+ return () => document.removeEventListener("visibilitychange", onVisible);
3270
+ }, [detail, pluginId, loadInstalled]);
3242
3271
  (0, import_react8.useEffect)(() => {
3243
3272
  let alive = true;
3244
3273
  getHostInfo().then((info) => {
@@ -3255,7 +3284,6 @@ function PluginDetailPage(props) {
3255
3284
  setDetail(null);
3256
3285
  setSelectedVersion(null);
3257
3286
  setInstalled(null);
3258
- if (pluginId !== "") loadInstalled(pluginId);
3259
3287
  getPluginDetail(pluginId).then((d) => {
3260
3288
  if (!alive) return;
3261
3289
  if (d === null) {
@@ -3264,6 +3292,7 @@ function PluginDetailPage(props) {
3264
3292
  setDetail(d);
3265
3293
  setSelectedVersion(d.version);
3266
3294
  setStatus("ready");
3295
+ loadInstalled(pluginId, d.name);
3267
3296
  }
3268
3297
  }).catch(() => {
3269
3298
  if (alive) setStatus("error");
@@ -3351,7 +3380,7 @@ function PluginDetailPage(props) {
3351
3380
  sourceType: detail.sourceType,
3352
3381
  dshVersion,
3353
3382
  installed: installed ?? void 0,
3354
- onInstalled: () => loadInstalled(detail.name),
3383
+ onInstalled: () => loadInstalled(pluginId, detail.name),
3355
3384
  onToast: toasts.push
3356
3385
  }
3357
3386
  )
@@ -3380,24 +3409,22 @@ function PluginDetailPage(props) {
3380
3409
  ] }),
3381
3410
  /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "xps-mk-card", children: [
3382
3411
  /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "xps-mk-card-title", children: "\u7248\u672C\u5386\u53F2" }),
3383
- detail.versions.map((v) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
3384
- "button",
3412
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
3413
+ "select",
3385
3414
  {
3386
- type: "button",
3387
- className: `xps-mk-version${v.version === displayVersion ? " xps-mk-version-active" : ""}`,
3388
- title: `\u5207\u6362\u5230 v${v.version}`,
3389
- onClick: () => setSelectedVersion(v.version),
3390
- children: [
3391
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("span", { children: [
3392
- "v",
3393
- v.version,
3394
- v.version === detail.version ? " \xB7 \u6700\u65B0" : ""
3395
- ] }),
3396
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "xps-mk-version-date", children: formatDate(v.publishedAt) })
3397
- ]
3398
- },
3399
- v.version
3400
- ))
3415
+ className: "xps-mk-select xps-mk-version-select",
3416
+ value: displayVersion,
3417
+ title: "\u5207\u6362\u7248\u672C",
3418
+ onChange: (e) => selectVersion(e.target.value),
3419
+ children: detail.versions.map((v) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("option", { value: v.version, children: [
3420
+ "v",
3421
+ v.version,
3422
+ " \xB7 ",
3423
+ formatShortDate(v.publishedAt),
3424
+ v.version === detail.version ? " \xB7 \u6700\u65B0" : ""
3425
+ ] }, v.version))
3426
+ }
3427
+ )
3401
3428
  ] })
3402
3429
  ] })
3403
3430
  ] })
@@ -3652,21 +3679,82 @@ var rightStyle2 = {
3652
3679
  alignItems: "flex-end",
3653
3680
  gap: 4
3654
3681
  };
3682
+ var PENDING_UPDATE_KEY = "xrxs-plugins-store:self-update-pending";
3683
+ function readPendingUpdate() {
3684
+ try {
3685
+ const raw = localStorage.getItem(PENDING_UPDATE_KEY);
3686
+ if (raw === null) return null;
3687
+ const parsed = JSON.parse(raw);
3688
+ return typeof parsed.version === "string" ? parsed.version : null;
3689
+ } catch {
3690
+ return null;
3691
+ }
3692
+ }
3693
+ function writePendingUpdate(version) {
3694
+ try {
3695
+ localStorage.setItem(PENDING_UPDATE_KEY, JSON.stringify({ version }));
3696
+ } catch {
3697
+ }
3698
+ }
3699
+ function clearPendingUpdate() {
3700
+ try {
3701
+ localStorage.removeItem(PENDING_UPDATE_KEY);
3702
+ } catch {
3703
+ }
3704
+ }
3655
3705
  function messageOf3(error, fallback) {
3656
3706
  return error instanceof Error && error.message !== "" ? error.message : fallback;
3657
3707
  }
3708
+ function versionAtLeast(current, target) {
3709
+ const parse = (text) => text.trim().replace(/^v/, "").split(".").map((part) => {
3710
+ const numeric = /^\d+/.exec(part);
3711
+ return numeric === null ? 0 : Number(numeric[0]);
3712
+ });
3713
+ const left = parse(current);
3714
+ const right = parse(target);
3715
+ const length = Math.max(left.length, right.length);
3716
+ for (let index = 0; index < length; index += 1) {
3717
+ const diff = (left[index] ?? 0) - (right[index] ?? 0);
3718
+ if (diff !== 0) return diff > 0;
3719
+ }
3720
+ return true;
3721
+ }
3658
3722
  function GeneralUpdateRow() {
3659
- const [phase, setPhase] = (0, import_react11.useState)({ kind: "checking" });
3723
+ const [phase, setPhase] = (0, import_react11.useState)(() => {
3724
+ const pending = readPendingUpdate();
3725
+ return pending !== null ? { kind: "updated", version: pending } : { kind: "checking" };
3726
+ });
3660
3727
  const [error, setError] = (0, import_react11.useState)("");
3661
3728
  const check = () => {
3662
3729
  setPhase({ kind: "checking" });
3663
3730
  setError("");
3664
- void checkSelfUpdate().then((result) => setPhase({ kind: "checked", check: result })).catch((err) => {
3731
+ void checkSelfUpdate().then((result) => {
3732
+ const pending = readPendingUpdate();
3733
+ if (pending !== null && result.current !== null && versionAtLeast(result.current, pending)) {
3734
+ clearPendingUpdate();
3735
+ setPhase({ kind: "checked", check: result });
3736
+ return;
3737
+ }
3738
+ setPhase({ kind: "checked", check: result });
3739
+ }).catch((err) => {
3665
3740
  setPhase({ kind: "checked", check: { current: null, latest: null, updateAvailable: false } });
3666
3741
  setError(messageOf3(err, "\u68C0\u67E5\u66F4\u65B0\u5931\u8D25"));
3667
3742
  });
3668
3743
  };
3669
- (0, import_react11.useEffect)(check, []);
3744
+ (0, import_react11.useEffect)(() => {
3745
+ if (readPendingUpdate() !== null) {
3746
+ void checkSelfUpdate().then((result) => {
3747
+ const pending = readPendingUpdate();
3748
+ if (pending !== null && result.current !== null && versionAtLeast(result.current, pending)) {
3749
+ clearPendingUpdate();
3750
+ setPhase({ kind: "checked", check: result });
3751
+ }
3752
+ }).catch(() => {
3753
+ });
3754
+ return;
3755
+ }
3756
+ check();
3757
+ }, []);
3670
3758
  const handleUpdate = (target, check0) => {
3671
3759
  setPhase({ kind: "updating", check: check0 });
3672
3760
  setError("");
@@ -3675,6 +3763,7 @@ function GeneralUpdateRow() {
3675
3763
  setPhase({ kind: "degraded", command: result.command, version: target });
3676
3764
  return;
3677
3765
  }
3766
+ writePendingUpdate(target);
3678
3767
  setPhase({ kind: "updated", version: target });
3679
3768
  }).catch((err) => {
3680
3769
  setPhase({ kind: "checked", check: check0 });
@@ -88,6 +88,9 @@ export interface InstalledEntry {
88
88
  managed: boolean;
89
89
  /** 已装包自身 package.json 的 description(file: 本地装的包不在市场里,简介只能从这里来);读不到为 null。 */
90
90
  description: string | null;
91
+ /** 安装时间(ISO 8601);取 node_modules/<name> 的创建时间(pnpm 装包时建立,更新重装会刷新)。
92
+ * 目录缺失/文件系统不支持 birthtime 时为 null,页面把它排在最后。 */
93
+ installedAt: string | null;
91
94
  }
92
95
  /** 已装列表:读 <DSH_HOME>/profiles/<profile>/package.json 依赖 + dsh.profile.bundles 层栈
93
96
  * (desktop profile 被 CLI 独占时读文件不受影响;设计文档 §3.3)。
@@ -1,7 +1,7 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { spawn } from 'node:child_process';
3
3
  import { existsSync, readdirSync, statSync } from 'node:fs';
4
- import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
4
+ import { lstat, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
5
5
  import { homedir } from 'node:os';
6
6
  import { basename, delimiter, dirname, join } from 'node:path';
7
7
  import { ApiError } from "./api-client.js";
@@ -437,7 +437,18 @@ export async function listInstalledFromProfile(host) {
437
437
  }
438
438
  if (version === '')
439
439
  version = versionFromFileSpec(spec) ?? '';
440
- return { name, version, managed: bundles.has(name), description };
440
+ // 安装时间:lstat 链接本身(pnpm 的 node_modules 项是指向 .pnpm 的符号链接,
441
+ // 链接创建时间 = 装包时间);birthtime 不可用(≤0/非法)退 mtime,再不行置 null
442
+ let installedAt = null;
443
+ try {
444
+ const link = await lstat(join(profileDir, 'node_modules', ...name.split('/')));
445
+ const time = link.birthtimeMs > 0 ? link.birthtime : link.mtime;
446
+ installedAt = Number.isNaN(time.getTime()) || time.getTime() <= 0 ? null : time.toISOString();
447
+ }
448
+ catch {
449
+ // 路径不存在:installedAt 保持 null
450
+ }
451
+ return { name, version, managed: bundles.has(name), description, installedAt };
441
452
  }));
442
453
  }
443
454
  const OUTPUT_CAP = 512 * 1024;
@@ -301,7 +301,11 @@ async function fetchLatestVersionMap(deps) {
301
301
  const version = optionalString(item.version);
302
302
  if (name === undefined || version === undefined)
303
303
  continue;
304
- map.set(name, { version, summary: optionalString(item.summary) ?? null });
304
+ map.set(name, {
305
+ version,
306
+ displayName: optionalString(item.displayName) ?? null,
307
+ summary: optionalString(item.summary) ?? null,
308
+ });
305
309
  }
306
310
  }
307
311
  catch (error) {
@@ -321,6 +325,9 @@ async function handleInstalled(rc) {
321
325
  latestVersion,
322
326
  updateAvailable: latestVersion !== null && latestVersion !== entry.version,
323
327
  managed: entry.managed,
328
+ installedAt: entry.installedAt,
329
+ // 展示名来自市场;本机手装/离线的包没有,client 半自行回退包名
330
+ displayName: meta?.displayName ?? null,
324
331
  // 市场简介优先;file: 本地装的包不在市场里,回退该包自己 package.json 的 description
325
332
  summary: meta?.summary ?? entry.description,
326
333
  };
@@ -12,7 +12,10 @@ import { type InstallOutcome, type InstallerDeps } from './installer.ts';
12
12
  export declare const SELF_PACKAGE_NAME = "@xrxs-ai/dsh-xrxs-plugins-store";
13
13
  /** 当前运行实例的版本(包根 package.json);读不到为 null(极端情况:裸文件运行)。
14
14
  * 依次尝试:模块相对层级(tsc 产物 lib/host/ 与源码 src/host/ 都是上一级的上一级)、
15
- * 按包名 resolve(profile 安装布局变化时的兜底);读到的包名必须匹配,防止层级猜错读成别的包。 */
15
+ * 按包名 resolve(profile 安装布局变化时的兜底);读到的包名必须匹配,防止层级猜错读成别的包。
16
+ * 模块级缓存且首次读取后不再重读:这里要报的是「运行实例」的版本,不是磁盘版本 ——
17
+ * 更新落盘后宿主可能只重挂 client 而不重载 host 模块,重读磁盘会把还在运行的旧实例
18
+ * 报成新版本;重启后 host 模块重新加载,缓存自然取到新版本。 */
16
19
  export declare function currentSelfVersion(): string | null;
17
20
  export interface SelfUpdateCheck {
18
21
  /** 当前运行实例版本;读不到为 null(无法比较,不可更新) */
@@ -21,7 +24,9 @@ export interface SelfUpdateCheck {
21
24
  latest: string | null;
22
25
  updateAvailable: boolean;
23
26
  }
24
- /** 查 npm latest 并与当前版本比较;registry 查询失败时 latest 为 null(不抛,便于 UI 展示「检查失败」)。 */
27
+ /** 查 npm latest 并与当前版本比较;registry 查询失败时 latest 为 null(不抛,便于 UI 展示「检查失败」)。
28
+ * 当前版本走模块级缓存:报运行实例的版本 —— 更新落盘、宿主尚未重载时,磁盘已是新版本,
29
+ * 但内存里跑的还是旧实例,报旧值才能让 client 半的「已更新待重启」记录正确保留到重启。 */
25
30
  export declare function checkSelfUpdate(): Promise<SelfUpdateCheck>;
26
31
  /** 应用更新:复用安装链路装确切版本(不装 latest tag,避免「检查到装的」漂移)。 */
27
32
  export declare function applySelfUpdate(version: string, deps: InstallerDeps): Promise<InstallOutcome>;
@@ -20,7 +20,10 @@ const LATEST_TIMEOUT_MS = 10_000;
20
20
  let cachedVersion = null;
21
21
  /** 当前运行实例的版本(包根 package.json);读不到为 null(极端情况:裸文件运行)。
22
22
  * 依次尝试:模块相对层级(tsc 产物 lib/host/ 与源码 src/host/ 都是上一级的上一级)、
23
- * 按包名 resolve(profile 安装布局变化时的兜底);读到的包名必须匹配,防止层级猜错读成别的包。 */
23
+ * 按包名 resolve(profile 安装布局变化时的兜底);读到的包名必须匹配,防止层级猜错读成别的包。
24
+ * 模块级缓存且首次读取后不再重读:这里要报的是「运行实例」的版本,不是磁盘版本 ——
25
+ * 更新落盘后宿主可能只重挂 client 而不重载 host 模块,重读磁盘会把还在运行的旧实例
26
+ * 报成新版本;重启后 host 模块重新加载,缓存自然取到新版本。 */
24
27
  export function currentSelfVersion() {
25
28
  if (cachedVersion !== null)
26
29
  return cachedVersion;
@@ -50,7 +53,9 @@ export function currentSelfVersion() {
50
53
  }
51
54
  return cachedVersion;
52
55
  }
53
- /** 查 npm latest 并与当前版本比较;registry 查询失败时 latest 为 null(不抛,便于 UI 展示「检查失败」)。 */
56
+ /** 查 npm latest 并与当前版本比较;registry 查询失败时 latest 为 null(不抛,便于 UI 展示「检查失败」)。
57
+ * 当前版本走模块级缓存:报运行实例的版本 —— 更新落盘、宿主尚未重载时,磁盘已是新版本,
58
+ * 但内存里跑的还是旧实例,报旧值才能让 client 半的「已更新待重启」记录正确保留到重启。 */
54
59
  export async function checkSelfUpdate() {
55
60
  const current = currentSelfVersion();
56
61
  let latest = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xrxs-ai/dsh-xrxs-plugins-store",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "DSH 私有插件市场插件(静态持久 bundle,双 face)",
5
5
  "type": "module",
6
6
  "license": "MIT",