dsh-m 0.4.0 → 0.4.2

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.en.md CHANGED
@@ -38,7 +38,7 @@ The default registry includes DSH Skins, ModSearch, the Lark / QQ / Weixin / WeC
38
38
  |---|---|
39
39
  | **Market** | Card flow with search and category filters (server-side filtering + pagination, 50 per page — even a 1,000-entry registry probes the current page only); expandable details; "Install" — npm sources pin the latest exact version, GitHub sources pin the release/tag commit |
40
40
  | **Installed** | What your web profile actually has, annotated "via market / non-market"; update badges, upgrade, two-step confirm uninstall; 📖 README preview (64KB cap); **live phase badge** (●active / ●failed / ○pending) and a **one-click toggle** (0.4.0: delegates to the official pluginManager service for live application; falls back to file-level edits + restart notice when absent; dsh-m itself and official host lifelines are locked) |
41
- | **Settings** | Registry address draft with "Validate & apply / Restore default / Download default registry.json / Check entries reachability"; configured vs active address and status at a glance; force refresh; dsh-m self-update; **metadata source display** (npmjs / npmmirror ping race winner — affects metadata reads only, never the install path) |
41
+ | **Settings** | Registry address draft with "Validate & apply / Restore default / Download default registry.json / Check entries reachability"; configured vs active address and status at a glance; force refresh; dsh-m self-update |
42
42
 
43
43
  After any mutation, the already-open Market and Installed views refresh the profile state together, keeping badges and cards in sync without closing and reopening the marketplace; a "⚡ Restart" banner appears — under systemd, the DSH launcher's `appExit` hook hands the restart back to a unit configured with `Restart=on-failure` or `Restart=always`, avoiding a `systemctl` helper inside the unit cgroup that is about to stop; if `appExit` is unavailable, the fallback uses a manager-owned transient `systemd-run` service and only then a detached helper. The client confirms the replacement by boot id and dismisses the banner, leaving DSH Web's own background connection recovery in control; it does not force a full-page reload during the auth/route handoff. The current DSH Web `0.1.5-rc.1` runtime has now live-loaded dsh-m `0.2.11`; `/dshm` ping returned version `0.2.11`, and an authenticated page request completed `303 → 200`. The reported 404 was not reproducible once the service stabilized; journal evidence showed repeated `status=75/TEMPFAIL` restarts during the incident. Live DSH `0.1.2-rc.1`, transient-fallback, and repeated install/uninstall E2E experiments remain pre-release gaps. Installs stream live pnpm progress (resolve → download → link → build).
44
44
 
@@ -51,7 +51,10 @@ After any mutation, the already-open Market and Installed views refresh the prof
51
51
  - **Peer compatibility precheck**: install/upgrade validates `@deepseek-ai/dsh(-*)` peers against the runtime version before touching the profile (GitHub sources state the check was skipped); on mismatch the GUI asks, the agent tool returns structured data, the CLI takes `--force`.
52
52
  - **Verified runtimes**: an optional `verified` array per registry entry records DSH runtimes actually tested — a claim of record, not a prediction; display-only, never gates installs.
53
53
  - **Bundle identity check**: post-install warning when a package lands without a patch layer ("installed as a plain dependency").
54
- - **Metadata source probe**: npmjs / npmmirror ping race (1500ms budget, 5-minute cache by default) that only selects dsh-m's metadata read source.
54
+
55
+ ### Retired in 0.4.x
56
+
57
+ - **Metadata source probe**: 0.4.0 introduced an npmjs / npmmirror ping race to pick the metadata read source; it has been removed entirely (including the `probeEnabled` / `probeTimeoutMs` / `probeCacheTtlMin` settings and the settings-page display). The official counterpart probe only pre-selects a registry in the interactive install dialog, which dsh-m does not have, and npmjs measured consistently faster from the host, so the probe always equaled the default. Metadata reads now always use npmjs, matching the install path (profile `.npmrc` default).
55
58
 
56
59
  ## Agent tools (8)
57
60
 
package/README.md CHANGED
@@ -38,7 +38,7 @@
38
38
  |---|---|
39
39
  | **市场** | 收录卡片流;关键词搜索、分类筛选(服务端过滤 + 分页,每页 50 条,1,000 条清单也只探测当前页);卡片展开详情与「安装」;npm 源锁定最新精确版本,GitHub 源锁定 release/tag 指向的 commit |
40
40
  | **已装** | web profile 实装列表,标注「市场安装 / 非市场安装」;可升级徽标、升级、两段式确认卸载;📖 README 预览(64KB 截断);**运行相位徽标**(●active / ●failed / ○pending)与**一键开关**(0.4.0:委派官方 pluginManager 服务活体生效,服务缺席时文件级编辑 + 重启提示;dsh-m 自身与官方宿主命脉锁定不可开关) |
41
- | **设置** | registry 地址草稿 +「校验并应用 / 恢复默认 / 下载默认 registry.json / 检查条目可达性」;配置地址、生效来源与状态一目了然;强制刷新;dsh-m 自更新;**元数据源展示**(npmjs / npmmirror ping 竞速胜出,只影响元数据读取,不动安装链路) |
41
+ | **设置** | registry 地址草稿 +「校验并应用 / 恢复默认 / 下载默认 registry.json / 检查条目可达性」;配置地址、生效来源与状态一目了然;强制刷新;dsh-m 自更新 |
42
42
 
43
43
  安装 / 卸载 / 升级完成后,当前已打开的市场页与已装页会一起重新读取 profile 状态并同步徽标/卡片,不需要关闭后重新打开插件市场;随后出现「⚡ 一键重启」横幅——受 systemd 管理时通过 DSH launcher 的 `appExit` 交给服务的 `Restart` 策略,避免在待停止 unit 的 cgroup 内启动 `systemctl` helper;无 `appExit` 的 systemd 兜底改用 manager-owned transient `systemd-run`,最后才退回 detached-helper。客户端按 boot id 确认新进程已恢复后关闭横幅,交由 DSH Web 自身的后台连接重试恢复页面,不强制整页刷新,避免认证/路由切换期间白屏。已完成当前 DSH Web `0.1.5-rc.1` 运行时的 live 核验:web profile 已加载 dsh-m `0.2.11`,重启后 `/dshm` ping 返回 `version: 0.2.11`,携带当前认证 token 的页面请求流程为 `303 → 200`;无认证请求会被拒绝。此次白屏截图对应的 404 在服务稳定后未复现;journal 显示截图时段发生多次 `status=75/TEMPFAIL` 重启,当前暂判定为重启/认证过渡窗口现象,未发现 dsh-m Host 路由崩溃。DSH `0.1.2-rc.1` live E2E、transient fallback live E2E,以及连续安装/卸载实验仍是正式发布前的验证 gap。安装过程实时显示 pnpm 进度(解析 → 下载 → 链接 → 构建)。
44
44
 
@@ -51,7 +51,10 @@
51
51
  - **peer 兼容预检**:安装/升级前校验 `@deepseek-ai/dsh(-*)` peers 与运行时版本(GitHub 源明示未检);不兼容时 GUI 弹确认、agent 回结构化结果、CLI `--force`。
52
52
  - **实测版本清单(verified)**:registry 条目可选 `verified` 数组记录实测过的 DSH 运行时版本——实测声明而非预测声明,只展示不拦截。
53
53
  - **bundle 身份验证**:装后检测无补丁层的包并警告「已装入为纯依赖」。
54
- - **元数据源竞速**:npmjs / npmmirror ping 并发探测(默认 1500ms 预算、5 分钟缓存),只影响 dsh-m 元数据读取源。
54
+
55
+ ### 0.4.x 退役
56
+
57
+ - **元数据源竞速**:0.4.0 曾引入 npmjs / npmmirror ping 竞速选择元数据读取源,现整体移除(含 `probeEnabled` / `probeTimeoutMs` / `probeCacheTtlMin` 三个设置项与设置页展示)。官方同款探测只服务于「安装对话框 registry 默认预选」,dsh-m 无此交互;实测宿主机 npmjs 稳定更快,探测恒等默认行为。元数据读取固定走 npmjs,与安装链路(profile `.npmrc` 默认源)一致。
55
58
 
56
59
  ## Agent 工具(8 个)
57
60
 
package/docs/DESIGN.md CHANGED
@@ -93,7 +93,7 @@ npm 安装 / GitHub 安装 / 升级 / 自升级 / 卸载是**同一个事务模
93
93
  - **精确构建放行(0.4.0,[ADR-0002](./adr/0002-precise-build-approval.md))**:needs-builds 拦截点(adapter 的 `makeAddViaLadder`)先读 `pnpm-workspace.yaml` 的 pnpm 待决名单(值为 `set this to true or false` 的无通配符键),逐键写 `allowBuilds.<pkg>: true`;名单空/不可读才走全量兜底并如实标注。放行结果沿 `RunnerOutcome.buildApprovals`/`fallbackAllBuilds` 上行(`usedAllowAllBuilds` 字段已删除)。
94
94
  - **bundle 身份验证(0.4.0)**:事务 verify 阶段观察——包内无 `cordis.patch.yml` 且不在 bundles 数组 → `bundleWarning: 'no-patch-layer'`(装成纯依赖),只警告不回滚(存在合法的无补丁层 dsh 包)。
95
95
  - **实测版本清单(0.4.0,术语「实测版本清单」)**:registry 条目可选 `verified: string[]`(精确 semver),来自各仓库对照源的实测声明;只展示与收录质量提示,**不做安装拦截依据**。
96
- - **元数据源竞速(0.4.0)**:npmjs/npmmirror `/-/ping` 并发竞速(首个 2xx 胜出、败者 abort、双败保持默认 npmjs;1500ms 预算 / 5 分钟缓存,`probeEnabled/probeTimeoutMs/probeCacheTtlMin` 可调);**只读不写**——只影响 dsh-m 元数据/packument 读取源与市场页展示,安装链路(pnpm 读 profile .npmrc)不动。
96
+ - **元数据源竞速(0.4.0 引入,0.4.x 退役)**:曾照搬官方 `dsh-client-ui-plugin-manager` 的 `PluginRegistryProbe`(npmjs/npmmirror `/-/ping` 竞速选元数据读取源)。退役理由:官方竞速只服务于「安装对话框 registry 默认预选」这一交互,dsh-m 无此交互;host 实测 npmjs(~175ms)稳定快于 npmmirror(~360-1400ms),探测恒等默认值;且元数据读取已有 TTL cache + deadline 预算。npmjs 真不可达时按官方 host 语义补**失败驱动 fallback**(对 network/timeout/not-found/no-matching-version 顺序重试镜像),不再引入主动探测。残留 `probeEnabled/probeTimeoutMs/probeCacheTtlMin` 配置键被非 strict Config schema 静默忽略,无需迁移。
97
97
  - **重启**:内置**一键重启**。在服务管理器托管的 DSH 进程内,只有确认 unit 的 `Restart=on-failure` / `Restart=always` 且 `75` 未被 `SuccessExitStatus` / `RestartPreventExitStatus` 覆盖时,才调用 launcher 提供的 `appExit(75)`;策略未知或不满足时改用 manager-owned transient `systemd-run` 调用 `systemctl`,不猜测或硬编码 unit 名称,也不在即将停止的 cgroup 内 detached spawn `systemctl`。无 systemd/appExit 时再退回 detached-helper 兼容路径。安装/卸载/升级完成后 GUI 弹「需重启生效 [一键重启]」横幅;客户端以 boot id 确认替换进程后关闭横幅,交由 DSH Web 自身后台连接恢复,不强制整页刷新;工具返回重启提示。
98
98
  - **安全基线(5 条)**:
99
99
  1. 所有拉取仅 HTTPS + 响应大小上限 + 超时;
@@ -104,7 +104,7 @@ npm 安装 / GitHub 安装 / 升级 / 自升级 / 卸载是**同一个事务模
104
104
 
105
105
  ## 4. GUI(旗舰,v1 必须做好)
106
106
 
107
- 0.4.0 增补:已装卡右上 `dshm-switch` 开关(受 `toggleable` 控制,锁因 title 提示);sub 行相位点 `● active · v1.0.8 · npm`(相位点只映射 phase 五值,「已停用」归 Switch,两输入源各管各的);开关结果通知按 `applied` 分流(live → 绿 toast「即时生效」;restart-required → 沿用重启横幅 + 一键重启);安装遇兼容拦截(409 + issue)弹「仍要安装」确认(红字风险 + peers 清单 → `forceIncompatible` 重发);设置页「元数据源」行展示竞速胜出源。
107
+ 0.4.0 增补:已装卡右上 `dshm-switch` 开关(受 `toggleable` 控制,锁因 title 提示);sub 行相位点 `● active · v1.0.8 · npm`(相位点只映射 phase 五值,「已停用」归 Switch,两输入源各管各的);开关结果通知按 `applied` 分流(live → 绿 toast「即时生效」;restart-required → 沿用重启横幅 + 一键重启);安装遇兼容拦截(409 + issue)弹「仍要安装」确认(红字风险 + peers 清单 → `forceIncompatible` 重发)。
108
108
 
109
109
  3 个视图,卡片展开式详情(不做独立详情页),**中文优先**,跟随 DSH Web 深色主题:
110
110
 
package/lib/client.js CHANGED
@@ -644,14 +644,6 @@ var ZH = {
644
644
  "compat.risk": "\u7EE7\u7EED\u5B89\u88C5\u53EF\u80FD\u5BFC\u81F4\u5D29\u6E83\u6216\u6570\u636E\u4E22\u5931\u3002\u786E\u5B9A\u4ECD\u8981\u5B89\u88C5\u5417\uFF1F",
645
645
  "compat.force": "\u4ECD\u8981\u5B89\u88C5",
646
646
  "common.cancel": "\u53D6\u6D88",
647
- "settings.probe": "\u5143\u6570\u636E\u6E90",
648
- "settings.probe.npmjs": "npmjs\uFF08\u7ADE\u901F\u80DC\u51FA\uFF09",
649
- "settings.probe.mirror": "npmmirror\uFF08\u7ADE\u901F\u80DC\u51FA\uFF09",
650
- "settings.probe.none": "\u9ED8\u8BA4 npmjs\uFF08\u672A\u63A2\u6D4B/\u53CC\u4E0D\u901A\uFF09",
651
- "settings.probe.toggle": "\u7ADE\u901F\u63A2\u6D4B",
652
- "settings.probe.apply": "\u5E94\u7528",
653
- "settings.probe.applied": "\u63A2\u6D4B\u914D\u7F6E\u5DF2\u66F4\u65B0\uFF08\u5373\u65F6\u751F\u6548\uFF09",
654
- "settings.probe.failed": "\u63A2\u6D4B\u914D\u7F6E\u4FDD\u5B58\u5931\u8D25\uFF1A{err}",
655
647
  "notify.uninstalled": "\u5DF2\u5378\u8F7D {pkg}",
656
648
  "notify.livedisabled": "\uFF08\u5DF2\u5148\u4E0B\u7EBF\u8FD0\u884C\u4E2D\u7684\u754C\u9762\uFF09",
657
649
  "notify.leftovers": "\uFF1B\u68C0\u6D4B\u5230\u7591\u4F3C\u6B8B\u7559\u6570\u636E\uFF1A{paths}",
@@ -843,14 +835,6 @@ var EN = {
843
835
  "compat.risk": "Proceeding may cause crashes or data loss. Install anyway?",
844
836
  "compat.force": "Install anyway",
845
837
  "common.cancel": "Cancel",
846
- "settings.probe": "Metadata source",
847
- "settings.probe.npmjs": "npmjs (probe winner)",
848
- "settings.probe.mirror": "npmmirror (probe winner)",
849
- "settings.probe.none": "default npmjs (not probed/both down)",
850
- "settings.probe.toggle": "Probe",
851
- "settings.probe.apply": "Apply",
852
- "settings.probe.applied": "Probe settings updated (live)",
853
- "settings.probe.failed": "Failed to save probe settings: {err}",
854
838
  "notify.uninstalled": "Uninstalled {pkg}",
855
839
  "notify.livedisabled": " (live UI disabled first)",
856
840
  "notify.leftovers": "; possible leftover data: {paths}",
@@ -1631,41 +1615,10 @@ function configStatusLabel(status) {
1631
1615
  return lookup(`settings.status.${status || "loading"}` || "settings.status.loading");
1632
1616
  }
1633
1617
  var STATUS_BADGE = { ready: "", pending: "info", rejected: "warn", unavailable: "err", loading: "info" };
1634
- function probeLabel(statusInfo) {
1635
- const probe = statusInfo.data && statusInfo.data.probe;
1636
- if (!probe) return lookup("settings.probe.none");
1637
- if (probe.source === "npmjs") return lookup("settings.probe.npmjs");
1638
- if (probe.source === "npmmirror") return lookup("settings.probe.mirror");
1639
- return lookup("settings.probe.none");
1640
- }
1641
1618
  function SettingsTab({ notify, onRegistryChanged }) {
1642
1619
  const reg = useAsync((force) => api("registry", force ? { force: true } : {}), []);
1643
1620
  const cfgState = useAsync(() => api("registry-config"), []);
1644
1621
  const self = useAsync(() => api("self-check"), []);
1645
- const statusInfo = useAsync(() => api("status"), []);
1646
- const probeCfg = useAsync(() => api("probe-config"), []);
1647
- const [probeDraft, setProbeDraft] = useState(null);
1648
- const [probeBusy, setProbeBusy] = useState(false);
1649
- useEffect(() => {
1650
- if (probeCfg.data && probeDraft === null) setProbeDraft({ ...probeCfg.data });
1651
- }, [probeCfg.data, probeDraft]);
1652
- const applyProbe = async () => {
1653
- if (!probeDraft) return;
1654
- setProbeBusy(true);
1655
- try {
1656
- const patch = {};
1657
- if (probeDraft.probeEnabled !== void 0) patch.probeEnabled = probeDraft.probeEnabled === true;
1658
- if (Number.isFinite(Number(probeDraft.probeTimeoutMs))) patch.probeTimeoutMs = Number(probeDraft.probeTimeoutMs);
1659
- if (Number.isFinite(Number(probeDraft.probeCacheTtlMin))) patch.probeCacheTtlMin = Number(probeDraft.probeCacheTtlMin);
1660
- const res = await api("probe-config-set", patch);
1661
- setProbeDraft({ ...res });
1662
- notify({ kind: "ok", text: lookup("settings.probe.applied"), needsRestart: false });
1663
- } catch (e) {
1664
- notify({ kind: "err", text: lookup("settings.probe.failed", { err: e && e.message || e }) });
1665
- } finally {
1666
- setProbeBusy(false);
1667
- }
1668
- };
1669
1622
  const [busy, setBusy] = useState(false);
1670
1623
  const [upgrading, setUpgrading] = useState(false);
1671
1624
  const [draftAddress, setDraftAddress] = useState(null);
@@ -1806,46 +1759,7 @@ function SettingsTab({ notify, onRegistryChanged }) {
1806
1759
  h("span", { className: "k" }, lookup("settings.count")),
1807
1760
  h("span", null, state ? lookup("settings.count.v", { n: state.count ?? 0 }) : "\u2014"),
1808
1761
  h("span", { className: "k" }, lookup("settings.policy")),
1809
- h("span", null, lookup("settings.policy.v")),
1810
- h("span", { className: "k" }, lookup("settings.probe")),
1811
- h("span", null, probeLabel(statusInfo)),
1812
- h("span", { className: "k" }, lookup("settings.probe.toggle")),
1813
- h(
1814
- "span",
1815
- { className: "dshm-probe-ctl" },
1816
- probeDraft ? [
1817
- h(ToggleSwitch, {
1818
- key: "sw",
1819
- vm: { switchOn: probeDraft.probeEnabled === true, switchDisabled: false },
1820
- title: null,
1821
- onChange: (v) => setProbeDraft({ ...probeDraft, probeEnabled: v })
1822
- }),
1823
- h("input", {
1824
- key: "timeout",
1825
- className: "dshm-input",
1826
- type: "number",
1827
- style: { maxWidth: "90px" },
1828
- title: "probeTimeoutMs",
1829
- value: probeDraft.probeTimeoutMs ?? "",
1830
- onChange: (e) => setProbeDraft({ ...probeDraft, probeTimeoutMs: e.target.value })
1831
- }),
1832
- h("input", {
1833
- key: "ttl",
1834
- className: "dshm-input",
1835
- type: "number",
1836
- style: { maxWidth: "70px" },
1837
- title: "probeCacheTtlMin",
1838
- value: probeDraft.probeCacheTtlMin ?? "",
1839
- onChange: (e) => setProbeDraft({ ...probeDraft, probeCacheTtlMin: e.target.value })
1840
- }),
1841
- h("button", {
1842
- key: "apply",
1843
- className: "dshm-btn sm",
1844
- disabled: probeBusy,
1845
- onClick: applyProbe
1846
- }, probeBusy ? h(Spin) : lookup("settings.probe.apply"))
1847
- ] : lookup("settings.probe.none")
1848
- )
1762
+ h("span", null, lookup("settings.policy.v"))
1849
1763
  ),
1850
1764
  state && state.stale && state.status !== "unavailable" ? h("div", { className: "dshm-note" }, lookup("settings.cache.hint")) : null,
1851
1765
  state && !state.isDefault ? h("div", { className: "dshm-note warn" }, lookup("settings.trust.hint")) : null,
@@ -48,7 +48,7 @@ export async function precheckNpmCompat(pkg, version, opts) {
48
48
  if (runtime === null || runtime === '')
49
49
  return null;
50
50
  const fetchVersion = opts.fetchVersion ?? npmVersion;
51
- const meta = await fetchVersion(pkg, version, opts.timeoutMs, opts.signal, opts.registry);
51
+ const meta = await fetchVersion(pkg, version, opts.timeoutMs, opts.signal);
52
52
  const failing = evaluatePeers(meta.peers ?? {}, runtime);
53
53
  if (Object.keys(failing).length === 0)
54
54
  return null;
@@ -176,13 +176,7 @@ export function createApiDispatcher(ctx) {
176
176
  resolveDshVersion: ctx.deps?.resolveDshVersion ?? resolveDshVersion,
177
177
  getService: ctx.deps?.getService,
178
178
  toggle: ctx.deps?.togglePlugin ?? togglePlugin,
179
- probe: ctx.deps?.probe,
180
- probeSnapshot: ctx.deps?.probeSnapshot,
181
- probeConfigGet: ctx.deps?.probeConfigGet,
182
- probeConfigSet: ctx.deps?.probeConfigSet,
183
179
  };
184
- // 元数据源探测注入(Task 8/14):市场/安装/升级/已装的元数据读取共用
185
- const probeDeps = d.probe !== undefined ? { probe: d.probe } : {};
186
180
  const cfg = () => ctx.controller.config;
187
181
  // DSH 版本一次性解析,dispatcher 创建即预热(首个 ping 不吃 spawn 回退的延迟)
188
182
  const dshVersionP = Promise.resolve()
@@ -259,13 +253,13 @@ export function createApiDispatcher(ctx) {
259
253
  withLatest: true,
260
254
  namespace: 'host',
261
255
  signal,
262
- }, probeDeps);
256
+ });
263
257
  payload = { ...result };
264
258
  break;
265
259
  }
266
260
  case 'installed': {
267
261
  await ctx.controller.ensureReady();
268
- const result = await d.listInstalledWithMeta(cfg(), { namespace: 'host', signal }, probeDeps);
262
+ const result = await d.listInstalledWithMeta(cfg(), { namespace: 'host', signal });
269
263
  payload = { ...result };
270
264
  break;
271
265
  }
@@ -278,38 +272,8 @@ export function createApiDispatcher(ctx) {
278
272
  break;
279
273
  }
280
274
  case 'status':
281
- payload = { ...publicInstallStatus(), probe: d.probeSnapshot?.() ?? null };
275
+ payload = { ...publicInstallStatus() };
282
276
  break;
283
- case 'probe-config': {
284
- if (!d.probeConfigGet)
285
- throw new ApiProtocolError(501, 'probe 配置读取不可用');
286
- payload = { ...d.probeConfigGet(), writable: typeof d.probeConfigSet === 'function' };
287
- break;
288
- }
289
- case 'probe-config-set': {
290
- if (!d.probeConfigGet || !d.probeConfigSet)
291
- throw new ApiProtocolError(501, 'probe 配置写入不可用(设置服务缺席)');
292
- const patch = {};
293
- if (body.probeEnabled !== undefined)
294
- patch.probeEnabled = boolArg(body.probeEnabled);
295
- if (body.probeTimeoutMs !== undefined) {
296
- const n = Number(body.probeTimeoutMs);
297
- if (!Number.isFinite(n) || n < 100 || n > 30_000)
298
- throw new ApiProtocolError(400, 'probeTimeoutMs 须在 100..30000 之间');
299
- patch.probeTimeoutMs = Math.floor(n);
300
- }
301
- if (body.probeCacheTtlMin !== undefined) {
302
- const n = Number(body.probeCacheTtlMin);
303
- if (!Number.isFinite(n) || n < 0 || n > 1440)
304
- throw new ApiProtocolError(400, 'probeCacheTtlMin 须在 0..1440 之间');
305
- patch.probeCacheTtlMin = Math.floor(n);
306
- }
307
- if (Object.keys(patch).length === 0)
308
- throw new ApiProtocolError(400, '缺少可更新字段');
309
- await d.probeConfigSet(patch);
310
- payload = { ...d.probeConfigGet(), writable: true };
311
- break;
312
- }
313
277
  case 'install': {
314
278
  const id = strArg(body, 'id');
315
279
  if (!id)
@@ -320,7 +284,7 @@ export function createApiDispatcher(ctx) {
320
284
  forceIncompatible: boolArg(body.forceIncompatible),
321
285
  namespace: 'host',
322
286
  signal,
323
- }, probeDeps);
287
+ });
324
288
  payload = { ...result };
325
289
  break;
326
290
  }
@@ -350,7 +314,7 @@ export function createApiDispatcher(ctx) {
350
314
  forceIncompatible: boolArg(body.forceIncompatible),
351
315
  namespace: 'host',
352
316
  signal,
353
- }, probeDeps);
317
+ });
354
318
  payload = { ...result };
355
319
  break;
356
320
  }
@@ -51,22 +51,6 @@ function loaderEntriesOrNull() {
51
51
  return null;
52
52
  }
53
53
  }
54
- /** 探测结果 → versions.ts 的 registry base;探测失败/禁用 → undefined(默认 npmjs)。 */
55
- async function probeRegistryBase(probe) {
56
- if (probe === undefined)
57
- return undefined;
58
- try {
59
- const source = await probe.fastest();
60
- if (source === 'npmjs')
61
- return 'https://registry.npmjs.org';
62
- if (source === 'npmmirror')
63
- return 'https://registry.npmmirror.com';
64
- }
65
- catch {
66
- /* 探测失败 → 默认源 */
67
- }
68
- return undefined;
69
- }
70
54
  // ---------- 通用工具 ----------
71
55
  const DEFAULT_DEADLINE_MS = 60_000;
72
56
  const WITH_LATEST_MAX = 50;
@@ -168,9 +152,9 @@ function writeLatestCache(key, value) {
168
152
  }
169
153
  latestCache.set(key, { at: Date.now(), value });
170
154
  }
171
- function probeTask(item, deps, timeoutMs, signal, registry) {
155
+ function probeTask(item, deps, timeoutMs, signal) {
172
156
  if (item.source === 'npm' && item.npm) {
173
- return deps.npmLatest(item.npm, timeoutMs, signal, registry).then((r) => ({ version: r.version }));
157
+ return deps.npmLatest(item.npm, timeoutMs, signal).then((r) => ({ version: r.version }));
174
158
  }
175
159
  return deps.githubLatestTag(item.github, timeoutMs, signal).then((r) => ({ tag: r.tag, sha: r.sha }));
176
160
  }
@@ -331,10 +315,9 @@ export async function listMarket(cfg = {}, opts = {}, deps = {}) {
331
315
  latestTimedOut = true;
332
316
  }
333
317
  else {
334
- const probeRegistry = await probeRegistryBase(d.probe);
335
318
  await mapWithConcurrency(todo, LATEST_WORKERS, async (item) => {
336
319
  const perBudget = Math.max(1, remaining());
337
- const task = probeTask(item, d, Math.min(cfg.timeoutMs ?? 20_000, perBudget), signal, probeRegistry);
320
+ const task = probeTask(item, d, Math.min(cfg.timeoutMs ?? 20_000, perBudget), signal);
338
321
  const outcome = await probeWithBudget(task, perBudget, signal);
339
322
  if (outcome.ok && outcome.value) {
340
323
  applyProbe(item, outcome.value);
@@ -480,7 +463,6 @@ export async function installEntry(entry, cfg = {}, opts = {}, deps) {
480
463
  const d = {
481
464
  npmLatest: deps?.npmLatest ?? defaultNpmLatest,
482
465
  npmVersion: deps?.npmVersion ?? npmVersion,
483
- registry: await probeRegistryBase(deps?.probe),
484
466
  };
485
467
  if (entry.source === 'npm' && entry.npm) {
486
468
  const pkg = entry.npm;
@@ -488,12 +470,12 @@ export async function installEntry(entry, cfg = {}, opts = {}, deps) {
488
470
  let version;
489
471
  let expectedIntegrity;
490
472
  if (opts.version) {
491
- const meta = await d.npmVersion(pkg, opts.version, timeoutMs, opts.signal, d.registry);
473
+ const meta = await d.npmVersion(pkg, opts.version, timeoutMs, opts.signal);
492
474
  version = meta.version;
493
475
  expectedIntegrity = meta.integrity;
494
476
  }
495
477
  else {
496
- const latest = await d.npmLatest(pkg, timeoutMs, opts.signal, d.registry);
478
+ const latest = await d.npmLatest(pkg, timeoutMs, opts.signal);
497
479
  version = latest.version;
498
480
  expectedIntegrity = latest.integrity;
499
481
  }
@@ -506,7 +488,6 @@ export async function installEntry(entry, cfg = {}, opts = {}, deps) {
506
488
  compat = await (deps?.precheck ?? precheckNpmCompat)(pkg, version, {
507
489
  timeoutMs,
508
490
  signal: opts.signal,
509
- registry: d.registry,
510
491
  });
511
492
  }
512
493
  catch {
@@ -42,20 +42,6 @@ export function unwrapConfig(source) {
42
42
  }
43
43
  return out;
44
44
  }
45
- /** 从任意形态提取 probe 三字段(Task 7/14;类型不符丢弃,缺省交调用方)。 */
46
- export function unwrapProbeConfig(source) {
47
- const raw = unwrapValue(source);
48
- const out = {};
49
- if (raw && typeof raw === 'object') {
50
- if (typeof raw.probeEnabled === 'boolean')
51
- out.probeEnabled = raw.probeEnabled;
52
- if (typeof raw.probeTimeoutMs === 'number')
53
- out.probeTimeoutMs = raw.probeTimeoutMs;
54
- if (typeof raw.probeCacheTtlMin === 'number')
55
- out.probeCacheTtlMin = raw.probeCacheTtlMin;
56
- }
57
- return out;
58
- }
59
45
  /** 运行时形态探测:register 在前(老服务不会带 describe/update),forms 次之。 */
60
46
  export function detectSettingsKind(settings) {
61
47
  const s = settings;
package/lib/host.js CHANGED
@@ -3,10 +3,9 @@ import Schema from '@deepseek-ai/schemastery';
3
3
  import { createApiDispatcher } from './core/host-api.js';
4
4
  import { bindLoaderHost } from './core/live-plugin.js';
5
5
  import { createRegistryController } from './core/registry-controller.js';
6
- import { wireRegistrySettings, unwrapConfig, unwrapProbeConfig, detectSettingsKind, ownEntryId } from './core/settings-compat.js';
6
+ import { wireRegistrySettings, unwrapConfig } from './core/settings-compat.js';
7
7
  import { registerTools } from './tools.js';
8
8
  import { appExitFromContext, scheduleRestart } from './core/restart.js';
9
- import { RegistryProbe } from './core/registry-probe.js';
10
9
  const require = createRequire(import.meta.url);
11
10
  const pkg = require('../package.json');
12
11
  export const name = 'dshm';
@@ -25,9 +24,6 @@ export const Config = Schema.object({
25
24
  registryUrl: live(Schema.string().description('registry 地址:空值使用默认官方清单;支持 HTTPS URL、loopback HTTP URL 或本机绝对路径/file://(整体覆盖默认清单,live 生效)')),
26
25
  timeoutMs: live(Schema.number().default(20000).description('上游请求超时(毫秒)')),
27
26
  cacheTtlMin: live(Schema.number().default(60).description('registry 缓存时长(分钟)')),
28
- probeEnabled: live(Schema.boolean().default(true).description('元数据源竞速探测:npmjs 与 npmmirror ping 并发,首个 2xx 胜出;只影响 dsh-m 元数据读取源与市场页展示,不影响安装链路')),
29
- probeTimeoutMs: live(Schema.number().default(1500).description('竞速探测单侧超时(毫秒)')),
30
- probeCacheTtlMin: live(Schema.number().default(5).description('竞速探测结果缓存时长(分钟)')),
31
27
  });
32
28
  export function apply(ctx, config) {
33
29
  // 卸载前的 live-disable 依赖 loader(skillhub 同款)
@@ -40,19 +36,6 @@ export function apply(ctx, config) {
40
36
  // using it avoids guessing the service unit from release-specific cgroups.
41
37
  const appExit = appExitFromContext(ctx);
42
38
  const restart = (port = null) => scheduleRestart(port, { appExit });
43
- // 元数据源竞速探测(Task 7/14):timeoutMs 取启动时配置(实例持有缓存,改参数重启生效);
44
- // probeEnabled 每次调用实时读取(设置页关掉立即生效)。
45
- const probe = new RegistryProbe({
46
- timeoutMs: unwrapProbeConfig(config).probeTimeoutMs ?? 1500,
47
- cacheTtlMs: (unwrapProbeConfig(config).probeCacheTtlMin ?? 5) * 60_000,
48
- });
49
- const probeLike = {
50
- fastest: () => {
51
- if (unwrapProbeConfig(config).probeEnabled === false)
52
- return Promise.resolve(null);
53
- return probe.fastest();
54
- },
55
- };
56
39
  // 开关委派的官方服务探测(ADR-0001):运行时按服务存在性探测,不判 DSH 版本号
57
40
  const getService = () => ctx.get?.('pluginManager');
58
41
  registerTools(ctx, controller.config, { restart });
@@ -61,25 +44,8 @@ export function apply(ctx, config) {
61
44
  // - 0.1.7-rc.1 / rc.2:Config `.volatile()` 字段自动生成设置页(autoGenerate 默认开,无需 configure),
62
45
  // 读 = loader 解析的 Config 引用,写 = settings.update(ns, patch),通知 = loader/volatile-update;
63
46
  // - 其他形态:降级 cordis 配置文件通路。任何失败只 warn,不拖垮 webServer / tools。
64
- // probe 配置写入口(Task 18 Rev):forms 形态走 settings.update(落盘 + 活体);
65
- // legacy/其余形态就地改 config(probeLike 每次调用现读,活体生效但不保证持久化)。
66
- let probeWriter = null;
67
47
  ctx.inject(['settings'], (c) => {
68
48
  const settings = c.settings;
69
- if (detectSettingsKind(settings) === 'forms') {
70
- const ns = ownEntryId(ctx, pkg.name);
71
- if (ns !== undefined) {
72
- const forms = settings;
73
- probeWriter = async (patch) => { await forms.update(ns, patch); };
74
- }
75
- }
76
- if (probeWriter === null) {
77
- probeWriter = async (patch) => {
78
- const target = config;
79
- for (const [k, v] of Object.entries(patch))
80
- target[k] = v;
81
- };
82
- }
83
49
  try {
84
50
  const outcome = wireRegistrySettings({
85
51
  ctx,
@@ -107,15 +73,7 @@ export function apply(ctx, config) {
107
73
  pkg,
108
74
  deps: {
109
75
  scheduleRestart: restart,
110
- probe: probeLike,
111
- probeSnapshot: () => probe.cachedSnapshot(),
112
76
  getService,
113
- probeConfigGet: () => unwrapProbeConfig(config),
114
- probeConfigSet: async (patch) => {
115
- if (!probeWriter)
116
- throw new Error('设置服务缺席,probe 配置仅可经 cordis 配置文件修改');
117
- await probeWriter(patch);
118
- },
119
77
  },
120
78
  });
121
79
  server.register({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-m",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "可自定义收录清单的 DSH 插件市场:收录、安装、卸载、升级一站完成,附 agent 工具与 dshm CLI。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -69,8 +69,8 @@
69
69
  },
70
70
  "devDependencies": {
71
71
  "@deepseek-ai/cordis": "^4.0.1",
72
- "@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
73
- "@deepseek-ai/schemastery": "^3.18.1",
72
+ "@deepseek-ai/dsh-tools": "0.1.7-rc.2",
73
+ "@deepseek-ai/schemastery": "^3.18.4",
74
74
  "@types/node": "^24.0.0",
75
75
  "@types/react": "^18.3.0",
76
76
  "@types/react-dom": "^18.3.0",
package/registry.json CHANGED
@@ -25,7 +25,7 @@
25
25
  {
26
26
  "id": "dsh-skins",
27
27
  "name": "DSH Skins",
28
- "description": "DSH Web 皮肤插件:多套品牌皮肤侧栏热切换,一键回到官方界面。",
28
+ "description": "DSH Web 皮肤插件:多套品牌皮肤侧栏热切换,一键回到官方界面。已适配 0.1.7-rc.2,详见仓库。",
29
29
  "category": "ui",
30
30
  "tags": [
31
31
  "皮肤",
@@ -33,7 +33,8 @@
33
33
  "美化"
34
34
  ],
35
35
  "verified": [
36
- "0.1.2-rc.1"
36
+ "0.1.2-rc.1",
37
+ "0.1.7-rc.2"
37
38
  ],
38
39
  "source": "npm",
39
40
  "npm": "@iasiv5/dsh-skins",
@@ -119,6 +120,24 @@
119
120
  "github": "iasiv5/dsh-obmc-web",
120
121
  "homepage": "https://github.com/iasiv5/dsh-obmc-web"
121
122
  },
123
+ {
124
+ "id": "dsh-onetree-log",
125
+ "name": "OneTree Log",
126
+ "description": "OneTree/dreport 日志包上传解包,14 域清单与预览;可登记服务器本地路径;已适配 0.1.7-rc.2,详见仓库。",
127
+ "category": "tools",
128
+ "tags": [
129
+ "日志",
130
+ "OneTree",
131
+ "诊断"
132
+ ],
133
+ "verified": [
134
+ "0.1.7-rc.2"
135
+ ],
136
+ "source": "npm",
137
+ "npm": "@inventec/dsh-onetree-log",
138
+ "github": "iasiv5/dsh-onetree-log",
139
+ "homepage": "https://github.com/iasiv5/dsh-onetree-log"
140
+ },
122
141
  {
123
142
  "id": "dsh-better-sidebar",
124
143
  "name": "DSH Better Sidebar",
@@ -1,82 +0,0 @@
1
- /**
2
- * 元数据源竞速(0.4.0,plan Task 7;对齐官方 ui-plugin-manager 的 registry probe
3
- * 语义:npmjs vs npmmirror `/-/ping` 并发,首个 2xx 胜出即定、败者 abort,
4
- * 双败 → null 保持默认 npmjs;结果缓存 TTL,默认 1500ms 预算 / 5 分钟缓存)。
5
- *
6
- * 只读不写:探测结果仅用于 dsh-m 自身的元数据/packument 读取源选择与市场页展示,
7
- * 安装链路(pnpm 读 profile .npmrc)完全不动(grilling Q9 定稿)。
8
- */
9
- import { fetchLimited } from './httpx.js';
10
- export const PROBE_URLS = {
11
- npmjs: 'https://registry.npmjs.org/-/ping',
12
- npmmirror: 'https://registry.npmmirror.com/-/ping',
13
- };
14
- /** 探测源 → registry base(versions.ts 元数据读取的 registry 参数值)。 */
15
- export const PROBE_REGISTRY_BASES = {
16
- npmjs: 'https://registry.npmjs.org',
17
- npmmirror: 'https://registry.npmmirror.com',
18
- };
19
- export class RegistryProbe {
20
- options;
21
- cached = null;
22
- inflight = null;
23
- constructor(options) {
24
- this.options = options;
25
- }
26
- /** 最近一次快照(不发请求);无记录 → null。 */
27
- cachedSnapshot() {
28
- return this.cached;
29
- }
30
- /** 缓存期内直接回放;否则并发竞速一次(并发调用共享同一次探测)。 */
31
- async fastest() {
32
- const now = this.options.now?.() ?? Date.now();
33
- if (this.cached !== null && now - this.cached.checkedAt < this.options.cacheTtlMs) {
34
- return this.cached.source;
35
- }
36
- if (this.inflight !== null)
37
- return this.inflight;
38
- this.inflight = this.race().finally(() => {
39
- this.inflight = null;
40
- });
41
- return this.inflight;
42
- }
43
- async race() {
44
- const fetchImpl = this.options.fetchImpl ?? fetchLimited;
45
- const sources = Object.keys(PROBE_URLS);
46
- const racers = sources.map((source) => {
47
- const controller = new AbortController();
48
- const promise = Promise.resolve()
49
- .then(() => fetchImpl(PROBE_URLS[source], { timeoutMs: this.options.timeoutMs, signal: controller.signal }))
50
- .then((res) => res.ok === true);
51
- return { source, controller, promise };
52
- });
53
- const winner = await new Promise((resolve) => {
54
- let settled = false;
55
- let pending = racers.length;
56
- const done = (source) => {
57
- if (settled)
58
- return;
59
- settled = true;
60
- for (const racer of racers) {
61
- if (racer.source !== source)
62
- racer.controller.abort();
63
- }
64
- resolve(source);
65
- };
66
- const oneFinished = (ok, source) => {
67
- if (ok) {
68
- done(source);
69
- return;
70
- }
71
- pending -= 1;
72
- if (pending <= 0)
73
- done(null);
74
- };
75
- for (const racer of racers) {
76
- racer.promise.then((ok) => oneFinished(ok, racer.source)).catch(() => oneFinished(false, racer.source));
77
- }
78
- });
79
- this.cached = { source: winner, checkedAt: this.options.now?.() ?? Date.now() };
80
- return winner;
81
- }
82
- }