dsh-m 0.9.22 → 0.9.23
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/CHANGELOG.md +14 -0
- package/docs/DESIGN.md +2 -2
- package/lib/client.js +62 -10
- package/lib/core/host-api.js +28 -0
- package/lib/core/market.js +8 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,13 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
|
|
|
8
8
|
|
|
9
9
|
## 中文
|
|
10
10
|
|
|
11
|
+
### 0.9.23 变更:已装页两段加载——列表秒开 + 更新提示就地补 ⬆(ADR-0008)
|
|
12
|
+
|
|
13
|
+
- **问题(实机 2026-10-03 实证)**:更新探测焊在已装列表接口里且带 60min 内存 TTL——发版后重开面板吃到陈旧缓存,升级提示迟迟不出现(quota-watch 新版发布 17 分钟后面板仍无提示,dshmarket 同刻已见)。
|
|
14
|
+
- **两段并行(零新增按钮)**:第一段 `installed {probe:false}` 立即返回已装列表(registry 匹配与开关相位不受影响);第二段新方法 `installedUpdates`(`probeMode: 'only'`,**TTL=0 每次挂载真实探测**)完成后把 ⬆ 徽标 / tab 红点 / 「全部升级 (N)」就地补上;探测失败仍逐项走「检查未完成」,不冒充「没得更新」。
|
|
15
|
+
- **边界与副作用(ADR-0008 如实留痕)**:范围仅已装页——市场浏览页探测、`dshm_list`/`dshm_outdated` 工具、CLI 行为不变(缺省 `probeMode: 'full'`);ttl=0 会先删 host namespace 共享缓存条目再重探,浏览页/工具的 TTL 内命中被刷新为更新值(数据只更新鲜、代码路径与自身 TTL 制度不变),GitHub 被动预算消耗速率上升(上限 25/req、50/h 滚动不变),超限走 latestError 优雅降级。
|
|
16
|
+
- **文档**:决策全文 `docs/adr/0008-installed-two-phase-probe.md`;DESIGN「已装页/缓存语义」补句;GLOSSARY 新词「两段加载」。
|
|
17
|
+
|
|
11
18
|
### 0.9.22 变更:升级生效判定——tarball 差异三态分类 + 三端重启提示分流(ADR-0007)
|
|
12
19
|
|
|
13
20
|
- **问题(0.9.21 实证)**:`@iasiv5/dsh-skins` 1.2.3→1.3.0 升级后未重启即已生效(客户端 bundle rev 热更),三端仍无条件提示「需要重启」——警报疲劳会侵蚀提示的权威性,重启本身也有真实成本(web 服务瞬断 / desktop 手动重开)。
|
|
@@ -242,6 +249,13 @@ The full release history of dsh-m, maintained bilingually: **Chinese first, Engl
|
|
|
242
249
|
|
|
243
250
|
## English
|
|
244
251
|
|
|
252
|
+
### Changed in 0.9.23 — two-phase Installed tab: instant list + update hints filled in place (ADR-0008)
|
|
253
|
+
|
|
254
|
+
- **Problem (observed live 2026-10-03)**: update probes were welded into the installed-list endpoint behind a 60-min in-memory TTL — after publishing a release, reopening the panel served the stale cache and update hints lagged (quota-watch's new version stayed invisible 17 minutes after publish while dsh-market already showed it).
|
|
255
|
+
- **Two parallel phases (zero new buttons)**: phase 1 `installed {probe:false}` returns the installed list immediately (registry matching and toggle phase untouched); phase 2 is a new `installedUpdates` method (`probeMode: 'only'`, **TTL=0 — a real probe on every panel mount**) that fills in ⬆ badges / the tab dot / "Upgrade all (N)" in place; failed checks still surface per item as "check incomplete" instead of pretending "up to date".
|
|
256
|
+
- **Scope & side effects (documented in ADR-0008)**: installed tab only — browse-page probes, `dshm_list`/`dshm_outdated` tools and CLI are unchanged (default `probeMode: 'full'`); ttl=0 evicts shared host-namespace cache entries before re-probing, so browse/tools TTL hits receive fresher write-backs (data only gets fresher; code paths and their own TTL regimes unchanged); GitHub passive budget consumption rate rises (limits unchanged: 25/req, 50/h rolling) with graceful `latestError` degradation.
|
|
257
|
+
- **Docs**: full decision record in `docs/adr/0008-installed-two-phase-probe.md`; DESIGN notes on the installed page / cache semantics; GLOSSARY term "Two-phase Load".
|
|
258
|
+
|
|
245
259
|
### Changed in 0.9.22 — upgrade activation classification (tarball diff, three states) + three-surface restart-hint split (ADR-0007)
|
|
246
260
|
|
|
247
261
|
- **Problem (observed in 0.9.21)**: after upgrading `@iasiv5/dsh-skins` 1.2.3→1.3.0 the plugin was already live without a restart (client bundle rev hot-reload), yet all three surfaces still said "restart required" — the noise trains users to ignore the hint, and restarts have real costs (web service blip / manual desktop reopen).
|
package/docs/DESIGN.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- **严格 v1 schema**:顶层只允许 `version/plugins`,条目只允许 `id/name/description/category/tags/source/npm/github/homepage/icon`;未知字段、非法 ID/npm/GitHub/URL、重复 ID/tag、字段超限、`plugins` 超过 1,000 条均拒绝整份清单(不截断、不部分加载);超过 200 条提示性能边界。
|
|
22
22
|
- **缓存模型**:cache 按 namespace 分目录(Host 与 Agent tools 用 `host/`,独立 CLI 用 `cli/`,互不删除),按带算法版本的 cacheKey 分文件(`CacheFile v2`:原子临时文件 + fsync + rename,0700/0600,symlink 拒写,同 key 进程内写锁)。**候选验证只写候选 cache,绝不 prune 旧源**;settings 写入成功后才 `commitActiveSource`:先原子写 accepted-source metadata(`host/active-source.json`,仅 Host),再清理非当前 custom cache;metadata 失败不 prune、prune 失败保留旧 cache,均只降级为 warning,不撤销已生效的配置。切换后只保留默认缓存与当前 custom 缓存。
|
|
23
23
|
- **统一安全 HTTP**:所有 JSON/text/HEAD 请求共享同一 primitive——手动重定向(每跳校验协议、最多 3 跳、循环检测、signal 传播、返回最终 URL)+ 响应大小上限 + 超时。
|
|
24
|
-
- **秒开缓存语义(0.9.14 SWR)**:`loadRegistry` 与社区获取链的 TTL 过期不再同步等网络——磁盘缓存存在即**先回 stale**(registry `source='default-cache'`、社区 `status='stale'` 既有契约;stale 状态由设置页社区卡承接,市场页不提示临时缓存状态——0.9.15 修订),后台**单飞**刷新写回 cache(同 key 进行中复用;不接调用者 signal,后台自愈不被请求 abort 腰斩;`force`/candidate/显式下载三路径不经 SWR)。默认双线路**粘性**:`loadDefaultChain` 读 cache 记录的上次成功 `source` 排序线路(稳定排序,cache 缺失 = 原序,force 同样生效)。社区 bg 自愈是 force flight——**不走同版本短路**(`if (!opts.force)` 守卫),每 TTL 至多全量重拉一次正文(同步路径的短路优化保留)。页条目 latest 探测缓存为 `latest-cache.ts` 的**纯内存 Map + TTL**(0.9.20 ADR-0006,推翻 0.9.14 的「磁盘信封 write-through + 懒 seed、跨服务重启存活」——2026-10-03 事故实证发版窗口内重启/重开面板全吃陈旧值,重启即失效回归为特性;install/upgrade/uninstall 成功点由 market.ts 按 itemId 定向作废该条目全部 registryKey 变体,只失效不回写;0.9.14 遗留 `<cacheRoot>/latest/<ns>.json` 惰性文件由探测段一次性 best-effort 清扫,`latest/` 在 nsDir 之外、不受 `pruneCaches` 清扫的不变量不变)。客户端默认首页响应存 localStorage(10min TTL、双 zone 分键、读侧再过 normalize 收敛)——面板打开先渲染快照后 background
|
|
24
|
+
- **秒开缓存语义(0.9.14 SWR)**:`loadRegistry` 与社区获取链的 TTL 过期不再同步等网络——磁盘缓存存在即**先回 stale**(registry `source='default-cache'`、社区 `status='stale'` 既有契约;stale 状态由设置页社区卡承接,市场页不提示临时缓存状态——0.9.15 修订),后台**单飞**刷新写回 cache(同 key 进行中复用;不接调用者 signal,后台自愈不被请求 abort 腰斩;`force`/candidate/显式下载三路径不经 SWR)。默认双线路**粘性**:`loadDefaultChain` 读 cache 记录的上次成功 `source` 排序线路(稳定排序,cache 缺失 = 原序,force 同样生效)。社区 bg 自愈是 force flight——**不走同版本短路**(`if (!opts.force)` 守卫),每 TTL 至多全量重拉一次正文(同步路径的短路优化保留)。页条目 latest 探测缓存为 `latest-cache.ts` 的**纯内存 Map + TTL**(0.9.20 ADR-0006,推翻 0.9.14 的「磁盘信封 write-through + 懒 seed、跨服务重启存活」——2026-10-03 事故实证发版窗口内重启/重开面板全吃陈旧值,重启即失效回归为特性;install/upgrade/uninstall 成功点由 market.ts 按 itemId 定向作废该条目全部 registryKey 变体,只失效不回写;0.9.14 遗留 `<cacheRoot>/latest/<ns>.json` 惰性文件由探测段一次性 best-effort 清扫,`latest/` 在 nsDir 之外、不受 `pruneCaches` 清扫的不变量不变)。客户端默认首页响应存 localStorage(10min TTL、双 zone 分键、读侧再过 normalize 收敛)——面板打开先渲染快照后 background 换新。已装面板第二段探测为 TTL=0 例外(ADR-0008)。
|
|
25
25
|
- **live 语义**:registry 地址是 live 设置,应用成功即时生效(无需重启);只有**首次部署新 Host 代码**需要一次用户确认后的 DSH Web 重启。`timeoutMs`/`cacheTtlMin` 亦为 live 读取;agent 框架注册时的静态工具 deadline 不承诺热更新。
|
|
26
26
|
|
|
27
27
|
### 2.2 Schema(v1 定稿,严格校验)
|
|
@@ -162,7 +162,7 @@ npm 安装 / GitHub 安装 / 升级 / 自升级 / 卸载是**同一个事务模
|
|
|
162
162
|
3 个视图,**中文优先**,跟随 DSH Web 深色主题(0.7.0:卡片展开式详情已由 Modal 取代):
|
|
163
163
|
|
|
164
164
|
1. **市场页**(默认):**分区制**(0.7.0 §2.6)——社区/精选/收藏三分区,各分区独立分类/搜索/排序/分页状态;搜索/分类/排序为**服务端处理**(Host 强制 `withLatest=true`、limit clamp 1..96 缺省 24,探测仅当前页);`MarketPanel` 是市场/已装数据唯一 owner(请求 generation + AbortController 丢弃旧响应);安装/卸载/升级完成后通过统一协调器同时刷新市场与已装快照,不要求关闭并重新打开面板;页码窗口化分页(24/48/96)+ 默认/自定义/缓存/不可用短提示(不含本地路径;0.5.0 的「超过 200 条性能提示」随分区退役);详情 Modal 保留 README markdown 预览与 npm/GitHub 官方外链。
|
|
165
|
-
2. **已装页**:profile 实际安装列表,标注来源(市场/非市场/未知);registry 不可用时仍列出已装并标记;「可升级 → x.y.z」徽标
|
|
165
|
+
2. **已装页**:profile 实际安装列表,标注来源(市场/非市场/未知);registry 不可用时仍列出已装并标记;「可升级 → x.y.z」徽标 +「升级」;「卸载」。两段加载(ADR-0008):列表不等待探测(`probe:false`),更新提示由 `installedUpdates`(TTL=0)就地补上。
|
|
166
166
|
3. **设置页**:registry 地址草稿 +「校验并应用」(先校验候选再写 settings,失败不落盘)/「恢复默认」/「下载默认 registry.json」(不改当前配置)/「检查条目可达性」(probe 统计 + 最多 100 条 issue,只读不改配置);同时展示配置地址、当前生效配置、生效来源与 configStatus(含 rejected/回滚原因与维护性 warnings);完整本地路径仅在此页显示。registry 配置为 **live 生效,不出现安装类重启横幅**;安装/卸载/升级仍保留重启横幅。
|
|
167
167
|
|
|
168
168
|
技术:`src/client.js` 经 **esbuild** 打包为 `lib/client.js`;`window.__ModuleLoader__.load({ id: "dsh-m", factory })` 注册;**React 从 module loader require**(零额外运行时依赖);manifest 注入 `@deepseek-ai/dsh-client-runtime`、`@deepseek-ai/dsh-client-ui-slots`、`@deepseek-ai/dsh-client-ui-settings`。
|
package/lib/client.js
CHANGED
|
@@ -689,10 +689,11 @@ var view_refresh_exports = {};
|
|
|
689
689
|
__export(view_refresh_exports, {
|
|
690
690
|
refreshAfterMutation: () => refreshAfterMutation
|
|
691
691
|
});
|
|
692
|
-
async function refreshAfterMutation({ marketReload, installedReload }) {
|
|
692
|
+
async function refreshAfterMutation({ marketReload, installedReload, updatesReload }) {
|
|
693
693
|
await Promise.all([
|
|
694
694
|
marketReload(false),
|
|
695
|
-
installedReload()
|
|
695
|
+
installedReload(),
|
|
696
|
+
updatesReload()
|
|
696
697
|
]);
|
|
697
698
|
}
|
|
698
699
|
var init_view_refresh = __esm({
|
|
@@ -701,6 +702,53 @@ var init_view_refresh = __esm({
|
|
|
701
702
|
}
|
|
702
703
|
});
|
|
703
704
|
|
|
705
|
+
// src/client/installed-updates.js
|
|
706
|
+
var installed_updates_exports = {};
|
|
707
|
+
__export(installed_updates_exports, {
|
|
708
|
+
applyInstalledUpdates: () => applyInstalledUpdates,
|
|
709
|
+
installedUpdateStats: () => installedUpdateStats
|
|
710
|
+
});
|
|
711
|
+
function applyInstalledUpdates(items, updates) {
|
|
712
|
+
if (!Array.isArray(items)) return [];
|
|
713
|
+
const byPkg = /* @__PURE__ */ new Map();
|
|
714
|
+
if (Array.isArray(updates)) {
|
|
715
|
+
for (const u of updates) {
|
|
716
|
+
if (!u || typeof u !== "object" || typeof u.pkg !== "string" || u.pkg === "") continue;
|
|
717
|
+
byPkg.set(u.pkg, u);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return items.map((it) => {
|
|
721
|
+
if (!it || typeof it !== "object") return it;
|
|
722
|
+
const u = byPkg.get(it.pkg);
|
|
723
|
+
if (!u) return it;
|
|
724
|
+
return {
|
|
725
|
+
...it,
|
|
726
|
+
latestVersion: u.latestVersion ?? void 0,
|
|
727
|
+
latestTag: u.latestTag ?? void 0,
|
|
728
|
+
latestSha: u.latestSha ?? void 0,
|
|
729
|
+
outdated: u.outdated === true,
|
|
730
|
+
latestError: u.latestError ?? void 0,
|
|
731
|
+
latestErrorCode: u.latestErrorCode ?? void 0
|
|
732
|
+
};
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
function installedUpdateStats(items) {
|
|
736
|
+
const arr = Array.isArray(items) ? items : [];
|
|
737
|
+
let outdatedCount = 0;
|
|
738
|
+
let incompleteCount = 0;
|
|
739
|
+
for (const it of arr) {
|
|
740
|
+
if (!it || typeof it !== "object") continue;
|
|
741
|
+
if (it.outdated === true) outdatedCount += 1;
|
|
742
|
+
if (typeof it.latestError === "string" && it.latestError !== "") incompleteCount += 1;
|
|
743
|
+
}
|
|
744
|
+
return { outdatedCount, incompleteCount };
|
|
745
|
+
}
|
|
746
|
+
var init_installed_updates = __esm({
|
|
747
|
+
"src/client/installed-updates.js"() {
|
|
748
|
+
"use strict";
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
|
|
704
752
|
// src/client/operations.js
|
|
705
753
|
var operations_exports = {};
|
|
706
754
|
__export(operations_exports, {
|
|
@@ -1169,6 +1217,7 @@ var { toggleViewModel: toggleViewModel2, toggleNoticeKeys: toggleNoticeKeys2 } =
|
|
|
1169
1217
|
var { pickPayload: pickPayload2, parseToolArgs: parseToolArgs2 } = (init_tool_view(), __toCommonJS(tool_view_exports));
|
|
1170
1218
|
var { RESTART_POLL_MS: RESTART_POLL_MS2, RESTART_DEADLINE_MS: RESTART_DEADLINE_MS2, nextRestartWait: nextRestartWait2, isAmbiguousRestartRequestError: isAmbiguousRestartRequestError2 } = (init_restart_wait(), __toCommonJS(restart_wait_exports));
|
|
1171
1219
|
var { refreshAfterMutation: refreshAfterMutation2 } = (init_view_refresh(), __toCommonJS(view_refresh_exports));
|
|
1220
|
+
var { applyInstalledUpdates: applyInstalledUpdates2, installedUpdateStats: installedUpdateStats2 } = (init_installed_updates(), __toCommonJS(installed_updates_exports));
|
|
1172
1221
|
var { createOperationsStore: createOperationsStore2, restoreRecords: restoreRecords2, createOpsPump: createOpsPump2, opAppliesTo: opAppliesTo2, TERMINAL_CLEARABLE: TERMINAL_CLEARABLE2, upgradeNotify: upgradeNotify2 } = (init_operations(), __toCommonJS(operations_exports));
|
|
1173
1222
|
var { createFavoritesStore: createFavoritesStore2, partitionStale: partitionStale2 } = (init_favorites(), __toCommonJS(favorites_exports));
|
|
1174
1223
|
var { readSelfCheckCache: readSelfCheckCache2, writeSelfCheckCache: writeSelfCheckCache2, clearSelfCheckCache: clearSelfCheckCache2, deriveChipState: deriveChipState2 } = (init_self_check(), __toCommonJS(self_check_exports));
|
|
@@ -2924,8 +2973,9 @@ function ReadmeBlock({ pkg }) {
|
|
|
2924
2973
|
state.truncated ? h("div", { className: "dshm-md-note" }, lookup("readme.truncated")) : null
|
|
2925
2974
|
);
|
|
2926
2975
|
}
|
|
2927
|
-
function InstalledTab({ notify, installed, onMutation, ops }) {
|
|
2976
|
+
function InstalledTab({ notify, installed, updates, onMutation, ops }) {
|
|
2928
2977
|
const { loading, data, error, reload } = installed;
|
|
2978
|
+
const updateRows = updates && updates.data && Array.isArray(updates.data.updates) ? updates.data.updates : [];
|
|
2929
2979
|
const [openPkg, setOpenPkg] = useState(null);
|
|
2930
2980
|
const [readmePkg, setReadmePkg] = useState(null);
|
|
2931
2981
|
const activeMutateTarget = (ops.records.find((r) => r.kind !== "install" && (r.status === "running" || r.status === "queued" || r.status === "input")) || {}).target || null;
|
|
@@ -3004,7 +3054,7 @@ function InstalledTab({ notify, installed, onMutation, ops }) {
|
|
|
3004
3054
|
};
|
|
3005
3055
|
if (loading && !data) return h("div", { className: "dshm-empty" }, lookup("installed.loading"), Spin());
|
|
3006
3056
|
if (error) return h("div", { className: "dshm-err" }, lookup("failed.read", { err: error }));
|
|
3007
|
-
const items = data && data.items || [];
|
|
3057
|
+
const items = applyInstalledUpdates2(data && data.items || [], updateRows);
|
|
3008
3058
|
if (!items.length) return h("div", { className: "dshm-empty" }, `${lookup("installed.empty")} (${data.profileDir})`);
|
|
3009
3059
|
return h(
|
|
3010
3060
|
React.Fragment,
|
|
@@ -3626,7 +3676,8 @@ function MarketPanel({ onClose }) {
|
|
|
3626
3676
|
const marketCommunity = useMarketData("community");
|
|
3627
3677
|
const marketPrimary = useMarketData("primary");
|
|
3628
3678
|
const markets = { community: marketCommunity, primary: marketPrimary };
|
|
3629
|
-
const installed = useAsync(() => api("installed"), []);
|
|
3679
|
+
const installed = useAsync(() => api("installed", { probe: false }), []);
|
|
3680
|
+
const updates = useAsync(() => api("installedUpdates"), []);
|
|
3630
3681
|
const [opRecords, setOpRecords] = useState(() => opsStore.list().map((r) => ({ ...r })));
|
|
3631
3682
|
const syncOps = useCallback(() => setOpRecords(opsStore.list().map((r) => ({ ...r }))), []);
|
|
3632
3683
|
const runOp = useCallback(
|
|
@@ -3683,14 +3734,14 @@ function MarketPanel({ onClose }) {
|
|
|
3683
3734
|
[marketCommunity.reload, marketPrimary.reload]
|
|
3684
3735
|
);
|
|
3685
3736
|
const refreshViews = useCallback(
|
|
3686
|
-
() => refreshAfterMutation2({ marketReload: marketReloadAll, installedReload: installed.reload }),
|
|
3687
|
-
[marketReloadAll, installed.reload]
|
|
3737
|
+
() => refreshAfterMutation2({ marketReload: marketReloadAll, installedReload: installed.reload, updatesReload: updates.reload }),
|
|
3738
|
+
[marketReloadAll, installed.reload, updates.reload]
|
|
3688
3739
|
);
|
|
3689
3740
|
const onRegistryChanged = refreshViews;
|
|
3690
3741
|
const stillApplies = useCallback(async (r) => {
|
|
3691
3742
|
let fresh = null;
|
|
3692
3743
|
try {
|
|
3693
|
-
fresh = await api("installed");
|
|
3744
|
+
fresh = await api("installed", { probe: false });
|
|
3694
3745
|
} catch {
|
|
3695
3746
|
fresh = null;
|
|
3696
3747
|
}
|
|
@@ -3737,7 +3788,8 @@ function MarketPanel({ onClose }) {
|
|
|
3737
3788
|
market: marketCommunity.data ? marketCommunity.data.total : null,
|
|
3738
3789
|
installed: installed.data ? installed.data.items.length : null
|
|
3739
3790
|
};
|
|
3740
|
-
const
|
|
3791
|
+
const mergedItems = installed.data && Array.isArray(installed.data.items) ? applyInstalledUpdates2(installed.data.items, updates.data && Array.isArray(updates.data.updates) ? updates.data.updates : []) : null;
|
|
3792
|
+
const outdatedCount = installedUpdateStats2(mergedItems || []).outdatedCount;
|
|
3741
3793
|
const [banner, setBanner] = useState(null);
|
|
3742
3794
|
const [toast, setToast] = useState(null);
|
|
3743
3795
|
useEffect(() => {
|
|
@@ -3817,7 +3869,7 @@ function MarketPanel({ onClose }) {
|
|
|
3817
3869
|
"div",
|
|
3818
3870
|
{ className: "dshm-body" },
|
|
3819
3871
|
tab === "market" ? h(MarketTab, { notify, markets, onMutation: refreshViews, ops, favorites, profileKind: profile?.kind ?? null }) : null,
|
|
3820
|
-
tab === "installed" ? h(InstalledTab, { notify, installed, onMutation: refreshViews, ops }) : null,
|
|
3872
|
+
tab === "installed" ? h(InstalledTab, { notify, installed, updates, onMutation: refreshViews, ops }) : null,
|
|
3821
3873
|
tab === "settings" ? h(SettingsTab, { notify, onRegistryChanged }) : null,
|
|
3822
3874
|
h(OperationsPanel, {
|
|
3823
3875
|
records: opRecords,
|
package/lib/core/host-api.js
CHANGED
|
@@ -385,15 +385,43 @@ export function createApiDispatcher(ctx) {
|
|
|
385
385
|
// 0.9.5 双 profile 读路径接线(实机回归 2026-10-01):desktop 下已装列表此前
|
|
386
386
|
// 漏传 profile → 落回 webProfileDir,已装页恒显「web profile 尚未安装任何插件」。
|
|
387
387
|
// 与 tools.ts dshm_list 同款接线(profile.dir 单一事实源)。
|
|
388
|
+
// 两段加载(ADR-0008):body.probe === false → 跳过探测段(面板第一段快列表);
|
|
389
|
+
// 缺省 'full'——工具/CLI/旧客户端行为不变。
|
|
388
390
|
const result = await d.listInstalledWithMeta(cfg(), {
|
|
389
391
|
namespace: 'host',
|
|
390
392
|
profileDir: profile.dir,
|
|
391
393
|
profile: profile.name,
|
|
394
|
+
probeMode: body.probe === false ? 'none' : 'full',
|
|
392
395
|
signal,
|
|
393
396
|
});
|
|
394
397
|
payload = { ...result };
|
|
395
398
|
break;
|
|
396
399
|
}
|
|
400
|
+
case 'installedUpdates': {
|
|
401
|
+
// 两段加载(ADR-0008)第二段:probeMode 'only' → ttlMin=0 永远新鲜(先删共享缓存条目再重探)。
|
|
402
|
+
// 响应只保留更新字段且含全部已装项(latestError 项也在——client 的「检查未完成」依赖它),
|
|
403
|
+
// 不携带 items/others 全量负载。
|
|
404
|
+
await ctx.controller.ensureReady();
|
|
405
|
+
const result = await d.listInstalledWithMeta(cfg(), {
|
|
406
|
+
namespace: 'host',
|
|
407
|
+
profileDir: profile.dir,
|
|
408
|
+
profile: profile.name,
|
|
409
|
+
probeMode: 'only',
|
|
410
|
+
signal,
|
|
411
|
+
});
|
|
412
|
+
payload = {
|
|
413
|
+
updates: result.items.map((it) => ({
|
|
414
|
+
pkg: it.pkg,
|
|
415
|
+
latestVersion: it.latestVersion ?? null,
|
|
416
|
+
latestTag: it.latestTag ?? null,
|
|
417
|
+
latestSha: it.latestSha ?? null,
|
|
418
|
+
outdated: it.outdated,
|
|
419
|
+
latestError: it.latestError ?? null,
|
|
420
|
+
latestErrorCode: it.latestErrorCode ?? null,
|
|
421
|
+
})),
|
|
422
|
+
};
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
397
425
|
case 'readme': {
|
|
398
426
|
const target = strArg(body, 'pkg');
|
|
399
427
|
if (!target)
|
package/lib/core/market.js
CHANGED
|
@@ -637,9 +637,14 @@ export async function listInstalledWithMeta(cfg = {}, opts = {}, deps = {}) {
|
|
|
637
637
|
}
|
|
638
638
|
const registryState = stateOf(loaded);
|
|
639
639
|
matchInstalled(items, community.merged);
|
|
640
|
-
//
|
|
641
|
-
|
|
642
|
-
|
|
640
|
+
// 两段加载(ADR-0008):probeMode 控制探测段——缺省 'full' 行为不变;'none' 面板快列表跳过探测;
|
|
641
|
+
// 'only' 面板第二段 ttlMin=0 永远新鲜(readLatestCache(key,0) 先删共享条目再重探:host ns 下浏览页/
|
|
642
|
+
// 工具的 TTL 命中被刷新为更新值,预算消耗速率上升、上限不变,超限走 latestError 降级)。
|
|
643
|
+
if ((opts.probeMode ?? 'full') !== 'none') {
|
|
644
|
+
// 0.9.20:latest 缓存纯内存(ADR-0006)——探测段前仅一次性清扫 0.9.14 遗留磁盘信封
|
|
645
|
+
await ensureLatestCacheSwept({ namespace, profile: opts.profile });
|
|
646
|
+
await probeLatest(items, { merged: community.merged, registryAddress: loaded.configuredAddress, ttlMin: opts.probeMode === 'only' ? 0 : Math.max(0, cfg.cacheTtlMin ?? 60) }, d, { namespace, signal, githubBudget, remaining, timeoutMs: cfg.timeoutMs ?? 20_000 });
|
|
647
|
+
}
|
|
643
648
|
return { items, others: installed.others, profileDir: installed.profileDir, registryState, community: community.summary };
|
|
644
649
|
}
|
|
645
650
|
/** matching(主+社区合并条目):命中主条目或社区条目都写 registryId;社区命中再标 community。 */
|