dsh-plugin-update 0.2.0 → 0.3.0

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.
@@ -0,0 +1,927 @@
1
+ // AUTO-GENERATED by node build.mjs — DO NOT EDIT. Source: src/panel-batch.ts
2
+ import { assertPrefix, MIN_PANEL_POLL_MS } from "./config.js";
3
+ import {
4
+ isTerminalPhase,
5
+ normalizeBatchSession
6
+ } from "./batch.js";
7
+ import {
8
+ UPDATE_PANEL_CSS,
9
+ UPDATE_PANEL_D5_CSS,
10
+ buildDiagnosticText,
11
+ createBrowserSkipStore,
12
+ failureCopy,
13
+ isKnownFailureCode,
14
+ renderUpdatePanelHTML
15
+ } from "./panel.js";
16
+ const BATCH_PANEL_POLL = {
17
+ defaultMs: 1500,
18
+ minMs: MIN_PANEL_POLL_MS
19
+ };
20
+ function buildBatchPhoneNames(prefix) {
21
+ const p = assertPrefix(prefix, "\u6279\u91CF\u7535\u8BDD\u524D\u7F00 prefix");
22
+ return {
23
+ status: p + ".batchStatus",
24
+ check: p + ".batchCheck",
25
+ install: p + ".batchInstall",
26
+ resume: p + ".batchResume",
27
+ cancel: p + ".batchCancel"
28
+ };
29
+ }
30
+ function batchLedgerCounts(rows) {
31
+ const counts = {
32
+ updatable: 0,
33
+ installing: 0,
34
+ pending: 0,
35
+ restart: 0,
36
+ failed: 0,
37
+ skipped: 0,
38
+ settled: 0
39
+ };
40
+ for (const row of rows) {
41
+ const phase = asBatchPhase(row.phase);
42
+ if (isQueueBusyRow(row)) counts.updatable += 1;
43
+ else if (phase === "failed") counts.failed += 1;
44
+ else if (phase === "installing") counts.installing += 1;
45
+ else if (phase === "ready") counts.updatable += 1;
46
+ else if (phase === "pending" || phase === "checking") counts.pending += 1;
47
+ else if (phase === "skipped") counts.skipped += 1;
48
+ else if (row.restartRequired === true) counts.restart += 1;
49
+ else counts.settled += 1;
50
+ }
51
+ return counts;
52
+ }
53
+ function batchLedgerText(counts) {
54
+ const parts = [];
55
+ if (counts.updatable > 0) parts.push(counts.updatable + " \u5BB6\u53EF\u66F4\u65B0");
56
+ if (counts.installing > 0) parts.push(counts.installing + " \u5BB6\u5B89\u88C5\u4E2D");
57
+ if (counts.pending > 0) parts.push(counts.pending + " \u5BB6\u5F85\u67E5");
58
+ if (counts.restart > 0) parts.push(counts.restart + " \u5BB6\u5F85\u91CD\u542F");
59
+ if (counts.failed > 0) parts.push(counts.failed + " \u5BB6\u5931\u8D25");
60
+ if (counts.skipped > 0) parts.push(counts.skipped + " \u5BB6\u5DF2\u8DF3\u8FC7");
61
+ if (counts.settled > 0) parts.push(counts.settled + " \u5BB6\u5DF2\u6700\u65B0");
62
+ return parts.length > 0 ? parts.join(" \xB7 ") : "\u8FD8\u6CA1\u6709\u76EE\u6807";
63
+ }
64
+ function batchQueuedStatus(position) {
65
+ if (typeof position !== "number" || !Number.isFinite(position) || position < 1) return "\u5DF2\u6392\u961F \xB7 \u7B49\u524D\u9762\u5B89\u88C5\u5B8C";
66
+ return "\u5DF2\u6392\u961F \xB7 \u524D\u65B9 " + position + " \u4E2A";
67
+ }
68
+ function batchRowStatus(row, skippedVersion) {
69
+ const skipped = typeof skippedVersion === "string" && skippedVersion ? skippedVersion : null;
70
+ if (skipped !== null && asBatchPhase(row.phase) === "ready") return "\u5DF2\u8DF3\u8FC7 " + skipped;
71
+ switch (asBatchPhase(row.phase)) {
72
+ case "pending":
73
+ return "\u7B49\u5B83\uFF0C\u8F6E\u5230\u5C31\u81EA\u52A8\u67E5\u65B0\u7248";
74
+ case "checking":
75
+ return "\u6B63\u5728\u67E5\u65B0\u7248\uFF0C\u7A0D\u7B49";
76
+ case "ready":
77
+ return row.targetVersion ? "\u70B9\u300C\u5B89\u88C5\u8FD9\u5BB6\u300D\u5B89\u88C5 " + row.targetVersion : "\u70B9\u300C\u5B89\u88C5\u8FD9\u5BB6\u300D\u5B89\u88C5\u65B0\u7248";
78
+ case "installing":
79
+ return "\u6B63\u5728\u5B89\u88C5\uFF0C\u522B\u52A8";
80
+ case "current":
81
+ return "\u5DF2\u662F\u6700\u65B0\uFF0C\u4E0D\u7528\u52A8";
82
+ case "done":
83
+ return row.restartRequired === true ? "\u5B89\u88C5\u597D\u4E86\uFF0C\u91CD\u542F\u5BBF\u4E3B\u624D\u751F\u6548" : "\u5B89\u88C5\u597D\u4E86\uFF0C\u4E0D\u7528\u52A8";
84
+ case "failed":
85
+ return "\u5B89\u88C5\u6CA1\u6210\u529F\uFF0C\u70B9\u300C\u91CD\u8BD5\u300D\u518D\u6765\u4E00\u6B21";
86
+ case "skipped":
87
+ return "\u8FD9\u4E00\u7248\u5DF2\u8DF3\u8FC7\uFF0C\u4E0D\u7528\u52A8";
88
+ default:
89
+ return "\u72B6\u6001\u8BA4\u4E0D\u51FA\uFF0C\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u91CD\u67E5\u4E00\u6B21";
90
+ }
91
+ }
92
+ function renderBatchPanelHTML(input) {
93
+ const rows = Array.isArray(input.rows) ? input.rows : [];
94
+ const theme = input.theme === "d5-paper" ? "d5-paper" : "default";
95
+ const mode = input.mode === "dialog" ? "dialog" : "embedded";
96
+ const titles = input.titles && typeof input.titles === "object" ? input.titles : {};
97
+ const skippedVersions = input.skippedVersions && typeof input.skippedVersions === "object" ? input.skippedVersions : {};
98
+ const expandedKey = typeof input.expandedKey === "string" && input.expandedKey ? input.expandedKey : null;
99
+ const lastError = typeof input.lastError === "string" && input.lastError ? input.lastError : null;
100
+ const loaded = input.loaded === void 0 ? rows.length > 0 : input.loaded === true;
101
+ const counts = batchLedgerCounts(rows);
102
+ const installing = counts.installing > 0;
103
+ const macroBusy = input.inFlight === true || installing;
104
+ const total = rows.length;
105
+ const terminal = total > 0 && rows.every((row) => isTerminalPhase(asBatchPhase(row.phase)));
106
+ const stalled = total > 0 && !terminal && !rows.some((row) => {
107
+ const phase = asBatchPhase(row.phase);
108
+ return phase === "installing" || phase === "checking";
109
+ });
110
+ const disabled = macroBusy ? " disabled" : "";
111
+ const notice = typeof input.notice === "string" && input.notice ? input.notice : null;
112
+ const noticeKey = typeof input.noticeKey === "string" && input.noticeKey ? input.noticeKey : null;
113
+ const detailNoticeKey = notice !== null && noticeKey !== null && noticeKey === expandedKey ? noticeKey : null;
114
+ const ctx = {
115
+ expanded: false,
116
+ busy: installing,
117
+ inFlight: input.inFlight === true,
118
+ theme,
119
+ titles,
120
+ skipped: null,
121
+ notice: null
122
+ };
123
+ const parts = [];
124
+ parts.push('<div class="dsh-upd-batch-head">');
125
+ parts.push('<div class="dsh-upd-batch-title">\u66F4\u65B0\u6863\u6848 \xB7 <i>\u603B\u8D26</i></div>');
126
+ parts.push('<div class="dsh-upd-batch-macros">');
127
+ parts.push('<button type="button" data-act="check"' + disabled + ">\u68C0\u67E5\u66F4\u65B0</button>");
128
+ parts.push('<button type="button" data-act="install" data-primary="1"' + disabled + ">\u5168\u90E8\u66F4\u65B0</button>");
129
+ if (mode === "dialog") parts.push('<button type="button" data-act="close">\u5173\u95ED</button>');
130
+ parts.push("</div>");
131
+ parts.push("</div>");
132
+ const summary = loaded ? batchSummaryText(counts, rows, total, installing, lastError !== null) : "\u6B63\u5728\u8BFB\u53D6\u6279\u91CF\u66F4\u65B0\u72B6\u6001\u2026";
133
+ parts.push('<div class="dsh-upd-batch-sum" role="status" aria-live="polite">' + escapeHtml(summary) + "</div>");
134
+ parts.push(bannersHTML(rows, titles, lastError));
135
+ parts.push('<div class="dsh-upd-btable">');
136
+ for (const row of rows) {
137
+ const raw = skippedVersions[row.key];
138
+ const skipped = typeof raw === "string" && raw ? raw : null;
139
+ const expanded = row.key === expandedKey;
140
+ parts.push(
141
+ rowSetHTML(row, {
142
+ ...ctx,
143
+ expanded,
144
+ skipped,
145
+ notice: expanded && row.key === detailNoticeKey ? notice : null
146
+ })
147
+ );
148
+ }
149
+ parts.push("</div>");
150
+ if (total > 0) {
151
+ parts.push('<div class="dsh-upd-batch-ledger">' + escapeHtml(batchLedgerText(counts)) + "</div>");
152
+ }
153
+ if (total > 0 && !terminal) {
154
+ const more = [];
155
+ if (stalled) more.push('<button type="button" data-act="resume">\u63A5\u7740\u4E0A\u6B21</button>');
156
+ more.push('<button type="button" data-act="cancel"' + disabled + ">\u53D6\u6D88\u8FD9\u4E00\u6279</button>");
157
+ parts.push('<div class="dsh-upd-batch-more">' + more.join("") + "</div>");
158
+ }
159
+ if (notice !== null && detailNoticeKey === null) {
160
+ parts.push('<div class="dsh-upd-batch-notice" role="status">' + escapeHtml(notice) + "</div>");
161
+ }
162
+ const kernel = parts.join(String.fromCharCode(10));
163
+ const d5 = theme === "d5-paper";
164
+ const attr = d5 ? ' data-theme="d5-paper"' : "";
165
+ const seal = batchSealOf(counts);
166
+ const root = '<div class="dsh-upd dsh-upd-batch" data-mode="' + mode + '" data-seal="' + escapeHtml(seal.text) + '" data-seal-tone="' + seal.tone + '"' + attr + ">\n" + kernel + "\n</div>";
167
+ const body = mode === "dialog" ? '<div class="dsh-upd-overlay" data-mode="dialog">' + root + "</div>" : root;
168
+ const css = d5 ? [UPDATE_PANEL_CSS, UPDATE_BATCH_PANEL_CSS, UPDATE_PANEL_D5_CSS, UPDATE_BATCH_PANEL_D5_CSS].join(String.fromCharCode(10)) : [UPDATE_PANEL_CSS, UPDATE_BATCH_PANEL_CSS].join(String.fromCharCode(10));
169
+ return "<style>" + css + "</style>\n" + body;
170
+ }
171
+ function batchSummaryText(counts, rows, total, installing, hasError) {
172
+ if (hasError) return "\u521A\u624D\u90A3\u6B21\u6CA1\u6210\u529F\uFF1A\u770B\u4E0B\u9762\u7684\u7EA2\u6761\uFF0C\u7167\u5B83\u8BF4\u7684\u505A\u4E00\u6B21\u3002";
173
+ if (total === 0) return "\u8FD8\u6CA1\u6709\u76EE\u6807\uFF1A\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u770B\u770B\u54EA\u51E0\u5BB6\u6709\u65B0\u7248\u3002";
174
+ if (installing) {
175
+ const canQueue = rows.filter((row) => asBatchPhase(row.phase) === "ready" && !isQueuedRow(row)).length;
176
+ return canQueue > 0 ? "\u6B63\u5728\u5B89\u88C5 " + counts.installing + " \u5BB6\uFF1B\u8FD8\u6709 " + canQueue + " \u5BB6\u53EF\u4EE5\u70B9\u300C\u52A0\u5165\u961F\u5217\u300D\u6392\u961F\u7B49\u3002" : "\u6B63\u5728\u5B89\u88C5 " + counts.installing + " \u5BB6\uFF0C\u5B89\u88C5\u5B8C\u81EA\u52A8\u4E0B\u4E00\u5BB6\u3002";
177
+ }
178
+ if (counts.failed > 0) return counts.failed + " \u5BB6\u5B89\u88C5\u5931\u8D25\uFF1B\u7167\u4E0B\u9762\u7684\u5931\u8D25\u63D0\u793A\u9010\u5BB6\u91CD\u8BD5\u3002";
179
+ if (counts.updatable > 0)
180
+ return counts.updatable + " \u5BB6\u53EF\u66F4\u65B0\uFF1B\u70B9\u300C\u5168\u90E8\u66F4\u65B0\u300D\u4E00\u6B21\u5B89\u88C5\u5B8C\uFF0C\u4E5F\u53EF\u4EE5\u9010\u5BB6\u70B9\u300C\u5B89\u88C5\u8FD9\u5BB6\u300D\u3002";
181
+ if (counts.restart > 0) return counts.restart + " \u5BB6\u5DF2\u5B89\u88C5\u597D\uFF0C\u91CD\u542F\u5BBF\u4E3B\u540E\u751F\u6548\u3002";
182
+ if (counts.pending > 0) return counts.pending + " \u5BB6\u8FD8\u6CA1\u67E5\u8FC7\uFF1B\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u67E5\u4E00\u8F6E\u3002";
183
+ return "\u5168\u90E8\u5DF2\u6700\u65B0\uFF0C\u6CA1\u6709\u8981\u505A\u7684\u3002";
184
+ }
185
+ function batchSealOf(counts) {
186
+ if (counts.failed > 0) return { text: "\u603B\u8D26", tone: "red" };
187
+ if (counts.installing > 0 || counts.restart > 0) return { text: "\u603B\u8D26", tone: "yellow" };
188
+ if (counts.updatable > 0) return { text: "\u603B\u8D26", tone: "green" };
189
+ return { text: "\u603B\u8D26", tone: "ink" };
190
+ }
191
+ function bannersHTML(rows, titles, lastError) {
192
+ const parts = [];
193
+ if (lastError) {
194
+ const copy = failureCopy(lastError);
195
+ parts.push(
196
+ '<div class="dsh-upd-banner" data-kind="failed" data-mini="\u963B" role="status"><div><strong>\u8FD9\u6B21\u6CA1\u6210\u529F\uFF08' + escapeHtml(lastError) + "\uFF09\uFF1A" + escapeHtml(copy ? copy.zh : "\u8BA4\u4E0D\u51FA\u5177\u4F53\u539F\u56E0") + "\u3002</strong></div><div>" + escapeHtml(copy ? copy.act : "\u5148\u91CD\u8BD5\u4E00\u6B21\uFF1B\u4E00\u76F4\u8FD9\u6837\u5C31\u628A\u590D\u5236\u8BCA\u65AD\u4EA4\u7ED9\u63D2\u4EF6\u4F5C\u8005\u3002") + "</div></div>"
197
+ );
198
+ }
199
+ const failed = rows.filter((row) => asBatchPhase(row.phase) === "failed" && !isQueueBusyRow(row));
200
+ if (failed.length > 0) {
201
+ const lines = failed.map((row) => {
202
+ const code = row.error || "install-failed";
203
+ const copy = failureCopy(code);
204
+ return "<div>" + escapeHtml(titleOf(row, titles)) + "\uFF1A<code>" + escapeHtml(code) + "</code> " + escapeHtml(copy ? copy.zh : "\u8BA4\u4E0D\u51FA\u5177\u4F53\u539F\u56E0") + "</div>";
205
+ }).join("");
206
+ parts.push(
207
+ '<div class="dsh-upd-banner" data-kind="failed" data-mini="\u963B" role="status"><div><strong>' + failed.length + ' \u5BB6\u5B89\u88C5\u5931\u8D25\u3002</strong></div><div>\u9010\u5BB6\u70B9\u884C\u5185\u300C\u91CD\u8BD5\u300D\u518D\u6765\u4E00\u6B21\uFF1B\u4E00\u76F4\u5931\u8D25\u5C31\u628A\u590D\u5236\u8BCA\u65AD\u4EA4\u7ED9\u63D2\u4EF6\u4F5C\u8005\u3002</div><div class="dsh-upd-blist">' + lines + "</div></div>"
208
+ );
209
+ }
210
+ const restart = rows.filter((row) => row.restartRequired === true);
211
+ if (restart.length > 0) {
212
+ parts.push(
213
+ '<div class="dsh-upd-banner" data-kind="restart" role="status"><div><strong>' + restart.length + ' \u5BB6\u5DF2\u5B89\u88C5\u597D\uFF0C\u91CD\u542F\u5BBF\u4E3B\u540E\u751F\u6548\u3002</strong></div><div>\u91CD\u542F\u5BBF\u4E3B\uFF0C\u8BA9\u65B0\u7248\u8DD1\u8D77\u6765\uFF1B\u8FD9\u662F\u6B63\u5E38\u7EC8\u6001\uFF0C\u4E0D\u662F\u5931\u8D25\u3002</div><div class="dsh-upd-blist">' + restart.map((row) => escapeHtml(titleOf(row, titles))).join("\u3001") + '</div><div><button type="button" data-act="restart" data-primary="1">\u91CD\u542F\u5BBF\u4E3B</button></div></div>'
214
+ );
215
+ }
216
+ return parts.join("");
217
+ }
218
+ function rowSetHTML(row, ctx) {
219
+ const phase = asBatchPhase(row.phase);
220
+ const keyAttr = escapeHtml(row.key);
221
+ const skipped = ctx.skipped;
222
+ const queued = isQueuedRow(row);
223
+ const position = queued ? queuePositionOf(row) : null;
224
+ const current = currentVersionOf(row);
225
+ const version = row.targetVersion ? (current === null ? "?" : current) + " \u2192 " + row.targetVersion : current === null ? "" : current;
226
+ const actions = [];
227
+ const off = ctx.inFlight ? " disabled" : "";
228
+ if (phase === "installing") {
229
+ actions.push(
230
+ '<button type="button" data-act="row-install" data-key="' + keyAttr + '" disabled>\u5B89\u88C5\u4E2D\u2026</button>'
231
+ );
232
+ } else if (queued) {
233
+ if (cancelQueuePhoneOf(row) !== null && queuedRequestIdOf(row) !== null) {
234
+ actions.push(
235
+ '<button type="button" data-act="row-cancel-queue" data-key="' + keyAttr + '"' + off + ">\u53D6\u6D88\u6392\u961F</button>"
236
+ );
237
+ }
238
+ } else if (phase === "ready" && skipped === null) {
239
+ actions.push(
240
+ '<button type="button" data-act="row-install" data-key="' + keyAttr + '" data-primary="1"' + off + ">" + (ctx.busy ? "\u52A0\u5165\u961F\u5217" : "\u5B89\u88C5\u8FD9\u5BB6") + "</button>"
241
+ );
242
+ } else if (phase === "ready" && skipped !== null) {
243
+ actions.push(
244
+ '<button type="button" data-act="row-resume-skip" data-key="' + keyAttr + '"' + off + ">\u6062\u590D\uFF08" + escapeHtml(skipped) + "\uFF09</button>"
245
+ );
246
+ } else if (phase === "failed") {
247
+ actions.push(
248
+ '<button type="button" data-act="row-install" data-key="' + keyAttr + '"' + off + ">" + (ctx.busy ? "\u52A0\u5165\u961F\u5217" : "\u91CD\u8BD5") + "</button>"
249
+ );
250
+ }
251
+ if (row.restartRequired === true) {
252
+ actions.push('<button type="button" data-act="restart" data-key="' + keyAttr + '">\u91CD\u542F\u5BBF\u4E3B</button>');
253
+ }
254
+ actions.push(
255
+ '<button type="button" data-act="toggle-details" data-key="' + keyAttr + '">' + (ctx.expanded ? "\u6536\u8D77" : "\u8BE6\u60C5") + "</button>"
256
+ );
257
+ const failLine = phase === "failed" && !queued ? '<span class="dsh-upd-bfail">\u5931\u8D25 <code>' + escapeHtml(row.error || "install-failed") + "</code>\uFF1A" + escapeHtml(failureCopy(row.error || "install-failed")?.zh ?? "\u8BA4\u4E0D\u51FA\u5177\u4F53\u539F\u56E0") + "</span>" : "";
258
+ const main = '<button type="button" class="dsh-upd-brow-main" data-act="toggle-details" data-key="' + keyAttr + '" aria-expanded="' + (ctx.expanded ? "true" : "false") + '"><span class="dsh-upd-updot" data-tone="' + dotToneOf(row, phase, skipped, queued) + '"></span><span class="dsh-upd-bname">' + escapeHtml(titleOf(row, ctx.titles)) + "</span>" + (version ? '<span class="dsh-upd-bver">' + escapeHtml(version) + "</span>" : "") + '<span class="dsh-upd-bstat">' + escapeHtml(queued ? batchQueuedStatus(position) : batchRowStatus(row, skipped)) + "</span>" + failLine + "</button>";
259
+ const detail = ctx.expanded ? '<div class="dsh-upd-bdetail" data-key="' + keyAttr + '">' + detailActionsHTML(row, ctx) + (ctx.notice !== null ? '<div class="dsh-upd-bdetail-notice" role="status">' + escapeHtml(ctx.notice) + "</div>" : "") + detailHTML(row, ctx) + "</div>" : "";
260
+ return '<div class="dsh-upd-brow-set" data-key="' + keyAttr + '"><div class="dsh-upd-brow" data-phase="' + escapeHtml(phase) + '" data-key="' + keyAttr + '"' + (queued ? ' data-queued="1"' : "") + ">" + main + '<span class="dsh-upd-brow-actions">' + actions.join("") + "</span></div>" + detail + "</div>";
261
+ }
262
+ function detailHTML(row, ctx) {
263
+ const html = renderUpdatePanelHTML({
264
+ snapshot: asSnapshotLike(row.snapshot),
265
+ manual: typeof row.manual === "string" ? row.manual : null,
266
+ queue: asQueueLike(row.queue),
267
+ // 跳过是面板本地记录(与单插件面板同一套语义):内核按「已跳过」画 tag 与提示行,
268
+ // 恢复按钮由上面的动作行提供,措辞与内核提示里的「恢复」对得上。
269
+ skippedLatest: ctx.skipped !== null,
270
+ lastError: row.error,
271
+ errorKind: row.error,
272
+ mode: "embedded",
273
+ showOthers: false,
274
+ pluginId: pluginIdOf(row),
275
+ copyNotice: null,
276
+ theme: ctx.theme,
277
+ profileName: typeof row.profileName === "string" && row.profileName ? row.profileName : null,
278
+ actions: "none"
279
+ });
280
+ return stripActionButtons(html);
281
+ }
282
+ function detailActionsHTML(row, ctx) {
283
+ const phase = asBatchPhase(row.phase);
284
+ const keyAttr = escapeHtml(row.key);
285
+ const version = latestVersionOf(row);
286
+ const queued = isQueuedRow(row);
287
+ const off = ctx.inFlight ? " disabled" : "";
288
+ const buttons = [];
289
+ if (phase === "installing") {
290
+ buttons.push('<button type="button" data-act="row-install" data-key="' + keyAttr + '" disabled>\u5B89\u88C5\u4E2D\u2026</button>');
291
+ } else if (queued) {
292
+ if (cancelQueuePhoneOf(row) !== null && queuedRequestIdOf(row) !== null) {
293
+ buttons.push(
294
+ '<button type="button" data-act="row-cancel-queue" data-key="' + keyAttr + '"' + off + ">\u53D6\u6D88\u6392\u961F</button>"
295
+ );
296
+ }
297
+ } else if (phase === "failed") {
298
+ buttons.push(
299
+ '<button type="button" data-act="row-install" data-key="' + keyAttr + '"' + off + ">" + (ctx.busy ? "\u52A0\u5165\u961F\u5217" : "\u91CD\u8BD5") + "</button>"
300
+ );
301
+ } else if (phase === "ready" && ctx.skipped === null) {
302
+ buttons.push(
303
+ '<button type="button" data-act="row-install" data-key="' + keyAttr + '" data-primary="1"' + off + ">" + (ctx.busy ? "\u52A0\u5165\u961F\u5217" : "\u5B89\u88C5 " + escapeHtml(version ?? "\u65B0\u7248")) + "</button>"
304
+ );
305
+ }
306
+ if (!queued && phase === "ready" && ctx.skipped === null && version !== null) {
307
+ buttons.push(
308
+ '<button type="button" data-act="row-skip" data-key="' + keyAttr + '"' + off + ">\u8DF3\u8FC7\u8FD9\u4E00\u7248</button>"
309
+ );
310
+ }
311
+ if (ctx.skipped !== null) {
312
+ buttons.push(
313
+ '<button type="button" data-act="row-resume-skip" data-key="' + keyAttr + '"' + off + ">\u6062\u590D\uFF08" + escapeHtml(ctx.skipped) + "\uFF09</button>"
314
+ );
315
+ }
316
+ if (typeof row.manual === "string" && row.manual) {
317
+ buttons.push(
318
+ '<button type="button" data-act="row-copy-manual" data-key="' + keyAttr + '"' + off + ">\u590D\u5236\u624B\u5DE5\u547D\u4EE4</button>"
319
+ );
320
+ }
321
+ buttons.push('<button type="button" data-act="row-copy-diag" data-key="' + keyAttr + '"' + off + ">\u590D\u5236\u8BCA\u65AD</button>");
322
+ return '<span class="dsh-upd-bdetail-actions">' + buttons.join("") + "</span>";
323
+ }
324
+ function stripActionButtons(html) {
325
+ return html.replace(/<button\b[^>]*data-action=[^>]*>[\s\S]*?<\/button>/g, "");
326
+ }
327
+ function dotToneOf(row, phase, skipped, queued) {
328
+ if (phase === "failed" && !queued) return "bad";
329
+ if (phase === "installing" || phase === "checking") return "busy";
330
+ if (queued) return "warn";
331
+ if (row.restartRequired === true) return "warn";
332
+ if (phase === "ready") return skipped === null ? "todo" : "idle";
333
+ if (phase === "current" || phase === "done") return "ok";
334
+ return "idle";
335
+ }
336
+ const UPDATE_BATCH_PANEL_CSS = [
337
+ "/* \u6279\u91CF\u9762\u677F\uFF08#25\uFF09\uFF1A\u7C7B\u540D\u6CBF\u7528 dsh-upd-* \u524D\u7F00\uFF1B\u9ED8\u8BA4\uFF08\u6700\u5C0F\uFF09\u4E3B\u9898\uFF0CD5 \u76AE\u80A4\u89C1 UPDATE_BATCH_PANEL_D5_CSS\u3002 */",
338
+ ".dsh-upd-batch-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;flex-wrap:wrap}",
339
+ ".dsh-upd-batch-title{font-weight:700;font-size:15px;letter-spacing:.06em}",
340
+ ".dsh-upd-batch-title i{font-style:normal;color:var(--dsh-upd-primary,#2563eb)}",
341
+ ".dsh-upd-batch-macros{flex:none}",
342
+ ".dsh-upd-batch-sum{margin:4px 0 8px;font-size:13.5px}",
343
+ ".dsh-upd-btable{margin-top:2px}",
344
+ ".dsh-upd-brow-set{border-top:1px solid var(--dsh-upd-line,#e5e7eb)}",
345
+ ".dsh-upd-brow-set:first-child{border-top:0}",
346
+ ".dsh-upd-brow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:6px 0}",
347
+ ".dsh-upd .dsh-upd-brow-main{display:flex;align-items:center;gap:8px;flex-wrap:wrap;flex:1 1 260px;min-width:0;",
348
+ "text-align:left;cursor:pointer;color:inherit;font:inherit;background:transparent;border:0;padding:2px 0;margin:0}",
349
+ ".dsh-upd .dsh-upd-brow-main:hover{color:var(--dsh-upd-primary,#2563eb)}",
350
+ ".dsh-upd-brow-actions{flex:none;margin-left:auto}",
351
+ ".dsh-upd-brow-actions button{font-size:12.5px}",
352
+ ".dsh-upd-updot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--dsh-upd-line,#9ca3af);opacity:.55}",
353
+ '.dsh-upd-updot[data-tone="todo"]{background:var(--dsh-upd-ok-line,#059669);opacity:1}',
354
+ '.dsh-upd-updot[data-tone="ok"]{background:var(--dsh-upd-ok-line,#059669);opacity:1}',
355
+ '.dsh-upd-updot[data-tone="busy"]{background:var(--dsh-upd-busy-line,#2563eb);opacity:1}',
356
+ '.dsh-upd-updot[data-tone="warn"]{background:var(--dsh-upd-warn-line,#d97706);opacity:1}',
357
+ '.dsh-upd-updot[data-tone="bad"]{background:var(--dsh-upd-bad-line,#dc2626);opacity:1}',
358
+ ".dsh-upd-bname{font-weight:700;flex:none}",
359
+ ".dsh-upd-bver{font-family:Consolas,Menlo,monospace;font-size:12px;opacity:.8;flex:none}",
360
+ ".dsh-upd-bstat{font-size:12.5px;opacity:.9}",
361
+ ".dsh-upd-bfail{flex:1 1 100%;display:block;font-size:12.5px;color:var(--dsh-upd-bad-line,#dc2626)}",
362
+ ".dsh-upd-blist{font-size:12.5px;margin-top:2px}",
363
+ ".dsh-upd-batch-ledger{margin:8px 0 0;font-size:13px;opacity:.85}",
364
+ ".dsh-upd-batch-more{margin-top:6px}",
365
+ ".dsh-upd-batch-more button{font-size:12px}",
366
+ ".dsh-upd-batch-notice{margin-top:6px;font-size:12.5px;opacity:.85}",
367
+ ".dsh-upd-bdetail-actions{margin:0 0 6px}",
368
+ ".dsh-upd .dsh-upd-bdetail-actions button{font-size:12.5px}",
369
+ ".dsh-upd-bdetail-notice{margin:0 0 6px;font-size:12.5px;opacity:.85}",
370
+ "/* \u8BE6\u60C5\u590D\u7528\u5355\u63D2\u4EF6\u5185\u6838\uFF1A\u4E0D\u8BB8\u5D4C\u5957\u6EDA\u52A8\u2014\u2014\u4E2D\u6027\u5316 overlay \u7684 max-height/overflow\uFF0C\u81EA\u8EAB\u4E5F\u4E0D\u7ED9 overflow\u3002 */",
371
+ ".dsh-upd-bdetail{margin:2px 0 10px;overflow:visible}",
372
+ ".dsh-upd .dsh-upd-bdetail .dsh-upd{max-width:none}",
373
+ ".dsh-upd-overlay .dsh-upd-bdetail .dsh-upd{max-height:none;overflow:visible}",
374
+ "@media (prefers-color-scheme: dark){.dsh-upd-batch-title i{color:var(--dsh-upd-focus,#93c5fd)}}",
375
+ "@media (prefers-reduced-motion: reduce){.dsh-upd-batch *{transition:none !important;animation:none !important}}"
376
+ ].join(String.fromCharCode(10));
377
+ const UPDATE_BATCH_PANEL_D5_CSS = [
378
+ "/* \u6279\u91CF\u9762\u677F\u7684 D5 \u76AE\u80A4\uFF1A\u53EA\u6362\u989C\u8272/\u5B57\u4F53/\u95F4\u8DDD\uFF1B\u6839\u4E0A\u7684\u5927\u5370\u7AE0\u7531 UPDATE_PANEL_D5_CSS \u8D1F\u8D23\u3002 */",
379
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-batch-head{padding-right:120px}',
380
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-batch-title{font-family:var(--d5-serif);font-size:21px;letter-spacing:.04em}',
381
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-batch-title i{color:var(--d5-accent)}',
382
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-brow-main{background:transparent;border:0;color:var(--d5-ink)}',
383
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-brow-main:hover{color:var(--d5-accent)}',
384
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-bname{font-family:var(--d5-serif)}',
385
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-bver{font-family:var(--d5-mono);color:var(--d5-muted)}',
386
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-batch-ledger{color:var(--d5-muted)}',
387
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-batch-sum{color:var(--d5-ink)}',
388
+ '.dsh-upd[data-theme="d5-paper"] .dsh-upd-brow-set{border-top-color:var(--d5-line)}'
389
+ ].join(String.fromCharCode(10));
390
+ function escapeHtml(text) {
391
+ return String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
392
+ }
393
+ function isObject(value) {
394
+ return !!value && typeof value === "object" && !Array.isArray(value);
395
+ }
396
+ const BATCH_PHASES = [
397
+ "pending",
398
+ "checking",
399
+ "ready",
400
+ "installing",
401
+ "current",
402
+ "done",
403
+ "failed",
404
+ "skipped"
405
+ ];
406
+ function isQueueBusyRow(row) {
407
+ return asBatchPhase(row.phase) === "failed" && row.error === "update-busy";
408
+ }
409
+ function queuePositionOf(row) {
410
+ const queue = asQueueLike(row.queue);
411
+ if (!queue || queue.busy !== true) return null;
412
+ const position = queue.position;
413
+ return typeof position === "number" && Number.isFinite(position) && position > 0 ? position : null;
414
+ }
415
+ function isQueuedRow(row) {
416
+ const phase = asBatchPhase(row.phase);
417
+ if (phase === "installing") return false;
418
+ if (isQueueBusyRow(row)) return true;
419
+ if (isTerminalPhase(phase)) return false;
420
+ return queuePositionOf(row) !== null;
421
+ }
422
+ function queuedRequestIdOf(row) {
423
+ const queue = asQueueLike(row.queue);
424
+ if (!queue || !Array.isArray(queue.waiting)) return null;
425
+ for (const entry of queue.waiting) {
426
+ if (entry && typeof entry.requestId === "string" && entry.requestId) return entry.requestId;
427
+ }
428
+ return null;
429
+ }
430
+ function cancelQueuePhoneOf(row) {
431
+ const phones = isObject(row.phoneNames) ? row.phoneNames : null;
432
+ if (!phones) return null;
433
+ const install = phones["updateInstall"];
434
+ return typeof install === "string" && install ? install : null;
435
+ }
436
+ function asBatchPhase(value) {
437
+ return typeof value === "string" && BATCH_PHASES.includes(value) ? value : "pending";
438
+ }
439
+ function asSnapshotLike(value) {
440
+ if (!isObject(value)) return null;
441
+ if (typeof value["runningVersion"] !== "string") return null;
442
+ if (typeof value["canInstall"] !== "boolean") return null;
443
+ return value;
444
+ }
445
+ function asQueueLike(value) {
446
+ if (!isObject(value) || typeof value["busy"] !== "boolean") return null;
447
+ return value;
448
+ }
449
+ function titleOf(row, titles) {
450
+ const override = titles[row.key];
451
+ if (typeof override === "string" && override) return override;
452
+ return row.title || row.key;
453
+ }
454
+ function pluginIdOf(row) {
455
+ return typeof row.pluginId === "string" && row.pluginId ? row.pluginId : row.key;
456
+ }
457
+ function latestVersionOf(row) {
458
+ if (typeof row.targetVersion === "string" && row.targetVersion) return row.targetVersion;
459
+ const snapshot = row.snapshot;
460
+ if (!isObject(snapshot)) return null;
461
+ const latest = snapshot["latestVersion"];
462
+ return typeof latest === "string" && latest ? latest : null;
463
+ }
464
+ async function defaultCopyText(text) {
465
+ try {
466
+ const holder = globalThis["navigator"];
467
+ const clip = holder?.clipboard;
468
+ const write = clip?.writeText;
469
+ if (clip && typeof write === "function") {
470
+ await write.call(clip, text);
471
+ return;
472
+ }
473
+ } catch {
474
+ }
475
+ }
476
+ function currentVersionOf(row) {
477
+ const snapshot = row.snapshot;
478
+ if (!isObject(snapshot)) return null;
479
+ const running = snapshot["runningVersion"];
480
+ if (typeof running === "string" && running) return running;
481
+ const installed = snapshot["installedVersion"];
482
+ if (typeof installed === "string" && installed) return installed;
483
+ return null;
484
+ }
485
+ function jobFailureOf(row) {
486
+ const snapshot = isObject(row.snapshot) ? row.snapshot : null;
487
+ if (!snapshot) return null;
488
+ const job = isObject(snapshot["job"]) ? snapshot["job"] : null;
489
+ if (!job) return null;
490
+ const state = typeof job["state"] === "string" ? job["state"] : "";
491
+ if (state !== "failed" && state !== "interrupted") return null;
492
+ const message = typeof job["message"] === "string" ? job["message"].trim() : "";
493
+ if (!message) return null;
494
+ const at = message.indexOf(":");
495
+ if (at < 0) {
496
+ return isKnownFailureCode(message) ? { code: message, detail: null } : { code: null, detail: message };
497
+ }
498
+ const code = message.slice(0, at).trim();
499
+ const detail = message.slice(at + 1).trim();
500
+ return { code: code || null, detail: detail || null };
501
+ }
502
+ function diagTextOf(row) {
503
+ const snapshot = isObject(row.snapshot) ? row.snapshot : null;
504
+ const queue = isObject(row.queue) ? row.queue : null;
505
+ const diag = isObject(row.diag) ? row.diag : null;
506
+ const text = (value) => typeof value === "string" && value ? value : null;
507
+ const job = jobFailureOf(row);
508
+ const diagDetail = diag ? text(diag["detail"]) : null;
509
+ const detail = diagDetail !== null ? diagDetail : job !== null && job.detail !== null ? job.detail + "\uFF08\u6765\u6E90\uFF1A\u540E\u53F0\u4EFB\u52A1\u6536\u5C3E\u8BB0\u5F55\uFF09" : null;
510
+ const input = {
511
+ pluginId: pluginIdOf(row),
512
+ code: job !== null && job.code !== null ? job.code : row.error || "check-failed",
513
+ detail,
514
+ runningVersion: text(snapshot ? snapshot["runningVersion"] : null),
515
+ installedVersion: text(snapshot ? snapshot["installedVersion"] : null),
516
+ latestVersion: text(snapshot ? snapshot["latestVersion"] : null),
517
+ hostKind: text(row.hostKind),
518
+ queuePosition: queue && typeof queue["position"] === "number" ? queue["position"] : null,
519
+ manual: text(row.manual),
520
+ diag: row.diag ?? null,
521
+ profileName: text(row.profileName)
522
+ };
523
+ return buildDiagnosticText(input);
524
+ }
525
+ function isBusyReply(reply) {
526
+ if (!isObject(reply) || reply["ok"] === true) return false;
527
+ const kind = typeof reply["errorKind"] === "string" ? reply["errorKind"] : "";
528
+ const err = typeof reply["error"] === "string" ? reply["error"] : "";
529
+ return kind === "update-busy" || err === "update-busy";
530
+ }
531
+ function readRows(value) {
532
+ if (!Array.isArray(value)) return [];
533
+ const out = [];
534
+ const seen = /* @__PURE__ */ new Set();
535
+ for (const item of value) {
536
+ if (!isObject(item)) continue;
537
+ const key = typeof item["key"] === "string" && item["key"] ? item["key"] : "";
538
+ if (!key || seen.has(key)) continue;
539
+ seen.add(key);
540
+ out.push({
541
+ key,
542
+ title: typeof item["title"] === "string" && item["title"] ? item["title"] : key,
543
+ phase: asBatchPhase(item["phase"]),
544
+ targetVersion: typeof item["targetVersion"] === "string" && item["targetVersion"] ? item["targetVersion"] : null,
545
+ restartRequired: item["restartRequired"] === true,
546
+ error: typeof item["error"] === "string" && item["error"] ? item["error"] : null,
547
+ snapshot: item["snapshot"] === void 0 ? null : item["snapshot"],
548
+ manual: typeof item["manual"] === "string" ? item["manual"] : null,
549
+ queue: item["queue"] === void 0 ? null : item["queue"],
550
+ profileName: typeof item["profileName"] === "string" && item["profileName"] ? item["profileName"] : null,
551
+ pluginId: typeof item["pluginId"] === "string" && item["pluginId"] ? item["pluginId"] : null,
552
+ diag: item["diag"] === void 0 ? null : item["diag"],
553
+ hostKind: typeof item["hostKind"] === "string" && item["hostKind"] ? item["hostKind"] : null,
554
+ phoneNames: item["phoneNames"] === void 0 ? null : item["phoneNames"]
555
+ });
556
+ }
557
+ return out;
558
+ }
559
+ function rowsFromSession(session) {
560
+ return session.entries.map((entry) => ({
561
+ key: entry.key,
562
+ title: entry.key,
563
+ phase: entry.phase,
564
+ targetVersion: entry.targetVersion,
565
+ restartRequired: entry.restartRequired,
566
+ error: entry.error,
567
+ snapshot: null,
568
+ manual: null,
569
+ queue: null,
570
+ profileName: null
571
+ }));
572
+ }
573
+ function getTimer() {
574
+ const g = globalThis;
575
+ const set = g["setInterval"];
576
+ const clear = g["clearInterval"];
577
+ if (typeof set === "function" && typeof clear === "function") {
578
+ return {
579
+ set: (fn, ms) => set(fn, ms),
580
+ clear: (h) => clear(h)
581
+ };
582
+ }
583
+ const setT = g["setTimeout"];
584
+ const clearT = g["clearTimeout"];
585
+ return {
586
+ set: (fn, ms) => setT(fn, ms),
587
+ clear: (h) => clearT(h)
588
+ };
589
+ }
590
+ function mountUpdateBatchPanel(container, options) {
591
+ if (!container || typeof container.innerHTML !== "string") {
592
+ throw new Error("[dsh-plugin-update] \u6302\u8F7D\u6279\u91CF\u9762\u677F\u9700\u8981\u4E00\u4E2A\u6709 innerHTML \u7684\u5BB9\u5668");
593
+ }
594
+ if (!options || typeof options !== "object") {
595
+ throw new Error("[dsh-plugin-update] \u6302\u8F7D\u6279\u91CF\u9762\u677F\u7F3A\u5C11\u914D\u7F6E\uFF1A\u6279\u91CF\u7535\u8BDD\u524D\u7F00 prefix \u5FC5\u586B");
596
+ }
597
+ const phones = buildBatchPhoneNames(options.prefix);
598
+ if (typeof options.call !== "function") {
599
+ throw new Error("[dsh-plugin-update] \u6302\u8F7D\u6279\u91CF\u9762\u677F\u9700\u8981\u4F20\u8F93\u51FD\u6570 call\uFF08\u9762\u677F\u8C03\u5BBF\u4E3B\u7535\u8BDD\u7684\u552F\u4E00\u63A5\u89E6\u9762\uFF09");
600
+ }
601
+ const pollMs = options.pollMs === void 0 ? BATCH_PANEL_POLL.defaultMs : options.pollMs;
602
+ if (typeof pollMs !== "number" || !Number.isFinite(pollMs) || pollMs < BATCH_PANEL_POLL.minMs) {
603
+ throw new Error(
604
+ "[dsh-plugin-update] \u6279\u91CF\u9762\u677F\u8F6E\u8BE2\u95F4\u9694\u975E\u6CD5\uFF1A\u4E0D\u5F97\u5C0F\u4E8E 250 \u6BEB\u79D2\uFF08\u6536\u5230 " + JSON.stringify(options.pollMs) + "\uFF09"
605
+ );
606
+ }
607
+ let mode = options.mode === void 0 ? "embedded" : options.mode;
608
+ if (mode !== "embedded" && mode !== "dialog") {
609
+ throw new Error("[dsh-plugin-update] \u6446\u653E\u5F62\u6001\u975E\u6CD5\uFF1A\u53EA\u6536 embedded \u6216 dialog\uFF08\u6536\u5230 " + JSON.stringify(options.mode) + "\uFF09");
610
+ }
611
+ let theme = options.theme === void 0 ? "default" : options.theme;
612
+ if (theme !== "default" && theme !== "d5-paper") {
613
+ throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 d5-paper\uFF08\u6536\u5230 " + JSON.stringify(options.theme) + "\uFF09");
614
+ }
615
+ const call = options.call;
616
+ const titles = options.titles && typeof options.titles === "object" ? options.titles : {};
617
+ const onRestartRequested = options.onRestartRequested;
618
+ const copyText = options.copyText ?? defaultCopyText;
619
+ const skipStores = /* @__PURE__ */ new Map();
620
+ let rows = [];
621
+ let expandedKey = null;
622
+ let lastError = null;
623
+ let notice = null;
624
+ let noticeKey = null;
625
+ let inFlight = false;
626
+ let loaded = false;
627
+ let refreshing = false;
628
+ let mounted = true;
629
+ function render() {
630
+ if (!mounted) return;
631
+ container.innerHTML = renderBatchPanelHTML({
632
+ rows,
633
+ theme,
634
+ mode,
635
+ expandedKey,
636
+ lastError,
637
+ notice,
638
+ noticeKey,
639
+ skippedVersions: skippedVersionsOf(),
640
+ inFlight,
641
+ loaded,
642
+ titles
643
+ });
644
+ }
645
+ function rowOfKey(key) {
646
+ return rows.find((row) => row.key === key) ?? null;
647
+ }
648
+ function skipStoreFor(pluginId) {
649
+ let store = skipStores.get(pluginId);
650
+ if (!store) {
651
+ store = createBrowserSkipStore(pluginId);
652
+ skipStores.set(pluginId, store);
653
+ }
654
+ return store;
655
+ }
656
+ function skippedVersionOf(row) {
657
+ if (asBatchPhase(row.phase) !== "ready") return null;
658
+ const version = latestVersionOf(row);
659
+ if (!version) return null;
660
+ try {
661
+ return skipStoreFor(pluginIdOf(row)).has(version) ? version : null;
662
+ } catch {
663
+ return null;
664
+ }
665
+ }
666
+ function skippedVersionsOf() {
667
+ const out = {};
668
+ for (const row of rows) out[row.key] = skippedVersionOf(row);
669
+ return out;
670
+ }
671
+ function say(text, key = null) {
672
+ notice = text;
673
+ noticeKey = key;
674
+ }
675
+ function clearNotice() {
676
+ notice = null;
677
+ noticeKey = null;
678
+ }
679
+ async function copyWith(text, okNotice, key) {
680
+ try {
681
+ await copyText(text);
682
+ say(okNotice, key);
683
+ } catch {
684
+ say("\u590D\u5236\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u9009\u4E2D\u4E0A\u9762\u7684\u4FE1\u606F\u3002", key);
685
+ }
686
+ render();
687
+ }
688
+ function applyReply(reply) {
689
+ if (!isObject(reply)) {
690
+ lastError = "internal";
691
+ return;
692
+ }
693
+ if (reply["ok"] === true) {
694
+ const session = normalizeBatchSession(reply["session"]);
695
+ const parsed = readRows(reply["rows"]);
696
+ rows = parsed.length > 0 ? parsed : rowsFromSession(session);
697
+ loaded = true;
698
+ lastError = null;
699
+ if (expandedKey !== null && !rows.some((row) => row.key === expandedKey)) expandedKey = null;
700
+ return;
701
+ }
702
+ const err = typeof reply["error"] === "string" ? reply["error"].trim() : "";
703
+ const kind = typeof reply["errorKind"] === "string" ? reply["errorKind"].trim() : "";
704
+ lastError = kind || err || "internal";
705
+ }
706
+ async function refresh() {
707
+ if (!mounted || refreshing) return;
708
+ refreshing = true;
709
+ try {
710
+ const reply = await call(phones.status, {});
711
+ if (!mounted) return;
712
+ applyReply(reply);
713
+ } catch {
714
+ if (!mounted) return;
715
+ lastError = "check-failed";
716
+ } finally {
717
+ refreshing = false;
718
+ }
719
+ render();
720
+ }
721
+ async function phone(name, args, busyRowKey = null) {
722
+ if (!mounted || inFlight) return;
723
+ inFlight = true;
724
+ clearNotice();
725
+ render();
726
+ try {
727
+ const reply = await call(name, args);
728
+ if (!mounted) return;
729
+ if (busyRowKey !== null && isBusyReply(reply)) {
730
+ say("\u524D\u9762\u8FD8\u5728\u88C5\uFF1A\u8FD9\u4E00\u5BB6\u8FD8\u6CA1\u6392\u4E0A\uFF0C\u7B49\u90A3\u5BB6\u88C5\u5B8C\u518D\u70B9\u4E00\u6B21\u3002", busyRowKey);
731
+ return;
732
+ }
733
+ applyReply(reply);
734
+ } catch {
735
+ if (!mounted) return;
736
+ lastError = "check-failed";
737
+ } finally {
738
+ inFlight = false;
739
+ if (mounted) render();
740
+ }
741
+ }
742
+ async function act(kind, key) {
743
+ if (!mounted) return;
744
+ switch (kind) {
745
+ case "check":
746
+ return phone(phones.check, {});
747
+ case "install":
748
+ return phone(phones.install, {});
749
+ case "row-install": {
750
+ if (typeof key !== "string" || !key) return;
751
+ return phone(phones.install, { keys: [key] }, key);
752
+ }
753
+ case "resume": {
754
+ await phone(phones.resume, {});
755
+ if (!mounted) return;
756
+ say("\u5DF2\u63A5\u7740\u4E0A\u6B21\u7684\u4F1A\u8BDD\u63A8\u8FDB\u4E00\u6B65\u3002");
757
+ render();
758
+ return;
759
+ }
760
+ case "cancel": {
761
+ await phone(phones.cancel, {});
762
+ if (!mounted) return;
763
+ say("\u5DF2\u53D6\u6D88\u8FD9\u4E00\u6279\uFF1A\u5269\u4E0B\u7684\u4E0D\u518D\u63A8\u8FDB\uFF1B\u8981\u91CD\u6765\u70B9\u300C\u68C0\u67E5\u66F4\u65B0\u300D\u3002");
764
+ render();
765
+ return;
766
+ }
767
+ // —— 详情里的动作行(与行内同一通道:都走 row-install / row-skip 这些 data-act)——
768
+ case "row-skip": {
769
+ const row = typeof key === "string" && key ? rowOfKey(key) : null;
770
+ const version = row ? latestVersionOf(row) : null;
771
+ if (!row || !version) return;
772
+ try {
773
+ skipStoreFor(pluginIdOf(row)).skip(version);
774
+ } catch {
775
+ }
776
+ say("\u5DF2\u8DF3\u8FC7 " + version + "\uFF1A\u8FD9\u4E00\u7248\u4E0D\u518D\u63D0\u9192\uFF1B\u70B9\u300C\u6062\u590D\u300D\u53EF\u64A4\u9500\u3002", row.key);
777
+ render();
778
+ return;
779
+ }
780
+ case "row-resume-skip": {
781
+ const row = typeof key === "string" && key ? rowOfKey(key) : null;
782
+ if (!row) return;
783
+ const version = latestVersionOf(row);
784
+ try {
785
+ skipStoreFor(pluginIdOf(row)).reset(version ?? void 0);
786
+ } catch {
787
+ }
788
+ say(version ? "\u5DF2\u6062\u590D " + version + "\uFF1A\u8FD9\u4E00\u7248\u4F1A\u7167\u5E38\u63D0\u9192\u3002" : "\u5DF2\u6062\u590D\u8DF3\u8FC7\u63D0\u9192\u3002", row.key);
789
+ render();
790
+ return;
791
+ }
792
+ // 「取消排队」:走**该家自己的**单插件安装电话(宿主支持 cancelQueued:只撤自己那条 waiting,
793
+ // 不碰别家、也不碰正在装的拥有者)。批量取消会清掉整轮会话,所以这里绝不用它顶。
794
+ case "row-cancel-queue": {
795
+ const row = typeof key === "string" && key ? rowOfKey(key) : null;
796
+ if (!row) return;
797
+ const phoneName = cancelQueuePhoneOf(row);
798
+ const requestId = queuedRequestIdOf(row);
799
+ if (phoneName === null || requestId === null) {
800
+ say("\u8FD9\u4E00\u884C\u6CA1\u5E26\u53D6\u6D88\u6392\u961F\u8981\u7528\u7684\u7535\u8BDD\u540D\u6216\u7F16\u53F7\uFF0C\u6682\u4E0D\u80FD\u53D6\u6D88\uFF1A\u7B49\u4E0B\u4E00\u6B21\u5237\u65B0\u518D\u770B\u3002", row.key);
801
+ render();
802
+ return;
803
+ }
804
+ if (inFlight) return;
805
+ inFlight = true;
806
+ clearNotice();
807
+ render();
808
+ try {
809
+ await call(phoneName, { cancelQueued: true, requestId });
810
+ if (mounted) say("\u5DF2\u53D6\u6D88\u6392\u961F\uFF1A\u8FD9\u4E00\u5BB6\u4E0D\u7B49\u4E86\u3002", row.key);
811
+ } catch {
812
+ if (mounted) say("\u53D6\u6D88\u6392\u961F\u6CA1\u6210\u529F\uFF08\u53EF\u80FD\u5DF2\u7ECF\u5F00\u59CB\u88C5\u4E86\uFF09\uFF1A\u770B\u4E0B\u9762\u6700\u65B0\u72B6\u6001\u3002", row.key);
813
+ } finally {
814
+ inFlight = false;
815
+ }
816
+ await refresh();
817
+ return;
818
+ }
819
+ case "row-copy-manual": {
820
+ const row = typeof key === "string" && key ? rowOfKey(key) : null;
821
+ const manual = row && typeof row.manual === "string" ? row.manual : "";
822
+ if (!row || !manual) return;
823
+ await copyWith(manual, "\u624B\u5DE5\u547D\u4EE4\u5DF2\u590D\u5236\uFF0C\u7C98\u5230\u7EC8\u7AEF\u6574\u884C\u6267\u884C\u5373\u53EF\u3002", row.key);
824
+ return;
825
+ }
826
+ case "row-copy-diag": {
827
+ const row = typeof key === "string" && key ? rowOfKey(key) : null;
828
+ if (!row) return;
829
+ await copyWith(diagTextOf(row), "\u8BCA\u65AD\u5DF2\u590D\u5236\uFF0C\u76F4\u63A5\u7C98\u7ED9\u63D2\u4EF6\u4F5C\u8005\u5373\u53EF\uFF08\u5DF2\u8131\u654F\uFF09\u3002", row.key);
830
+ return;
831
+ }
832
+ case "toggle-details": {
833
+ if (typeof key !== "string" || !key) return;
834
+ expandedKey = expandedKey === key ? null : key;
835
+ render();
836
+ return;
837
+ }
838
+ // 「重启宿主」:宿主没有重启自己的电话,只做入口——调用方给了 onRestartRequested 就交给它,
839
+ // 没给就如实说「请手动重启」,不假装能重启(与单插件面板逐字同一口径)。
840
+ case "restart": {
841
+ try {
842
+ if (typeof onRestartRequested === "function") {
843
+ await onRestartRequested();
844
+ say("\u5DF2\u6309\u8C03\u7528\u65B9\u7684\u91CD\u542F\u6D41\u7A0B\u5904\u7406\uFF1B\u91CD\u542F\u540E\u65B0\u7248\u751F\u6548\u3002");
845
+ } else {
846
+ say("\u672C\u5BBF\u4E3B\u672A\u63D0\u4F9B\u91CD\u542F\u5165\u53E3\uFF1A\u8BF7\u624B\u52A8\u91CD\u542F\u5BBF\u4E3B\uFF0C\u91CD\u542F\u540E\u65B0\u7248\u751F\u6548\u3002");
847
+ }
848
+ } catch {
849
+ say("\u91CD\u542F\u5165\u53E3\u8C03\u7528\u5931\u8D25\uFF1A\u8BF7\u624B\u52A8\u91CD\u542F\u5BBF\u4E3B\uFF0C\u91CD\u542F\u540E\u65B0\u7248\u751F\u6548\u3002");
850
+ }
851
+ render();
852
+ return;
853
+ }
854
+ case "close": {
855
+ if (mode === "dialog") unmount();
856
+ return;
857
+ }
858
+ default:
859
+ return;
860
+ }
861
+ }
862
+ function setTheme(next) {
863
+ if (next !== "default" && next !== "d5-paper") {
864
+ throw new Error("[dsh-plugin-update] \u4E3B\u9898\u975E\u6CD5\uFF1A\u53EA\u6536 default \u6216 d5-paper\uFF08\u6536\u5230 " + JSON.stringify(next) + "\uFF09");
865
+ }
866
+ theme = next;
867
+ render();
868
+ }
869
+ function setMode(next) {
870
+ if (next !== "embedded" && next !== "dialog") {
871
+ throw new Error("[dsh-plugin-update] \u6446\u653E\u5F62\u6001\u975E\u6CD5\uFF1A\u53EA\u6536 embedded \u6216 dialog\uFF08\u6536\u5230 " + JSON.stringify(next) + "\uFF09");
872
+ }
873
+ mode = next;
874
+ render();
875
+ }
876
+ function onClick(ev) {
877
+ try {
878
+ const t = ev;
879
+ const btn = t?.target && typeof t.target.closest === "function" ? t.target.closest("[data-act]") : null;
880
+ const kind = btn?.getAttribute ? btn.getAttribute("data-act") : null;
881
+ if (!kind) return;
882
+ const key = btn?.getAttribute ? btn.getAttribute("data-key") : null;
883
+ void act(kind, key === null ? void 0 : key);
884
+ } catch {
885
+ }
886
+ }
887
+ function unmount() {
888
+ if (!mounted) return;
889
+ mounted = false;
890
+ try {
891
+ timer.clear(handle);
892
+ } catch {
893
+ }
894
+ try {
895
+ container.removeEventListener?.("click", onClick);
896
+ } catch {
897
+ }
898
+ }
899
+ render();
900
+ try {
901
+ container.addEventListener?.("click", onClick);
902
+ } catch {
903
+ }
904
+ const timer = getTimer();
905
+ const handle = timer.set(() => {
906
+ void refresh();
907
+ }, pollMs);
908
+ try {
909
+ const h = handle;
910
+ if (h && typeof h.unref === "function") h.unref();
911
+ } catch {
912
+ }
913
+ void refresh();
914
+ return { refresh, act, setTheme, setMode, unmount };
915
+ }
916
+ export {
917
+ BATCH_PANEL_POLL,
918
+ UPDATE_BATCH_PANEL_CSS,
919
+ UPDATE_BATCH_PANEL_D5_CSS,
920
+ batchLedgerCounts,
921
+ batchLedgerText,
922
+ batchQueuedStatus,
923
+ batchRowStatus,
924
+ buildBatchPhoneNames,
925
+ mountUpdateBatchPanel,
926
+ renderBatchPanelHTML
927
+ };