@stardustlc/dsh-dream 0.13.0 → 0.13.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/CHANGELOG.md +10 -0
- package/README.en.md +1 -1
- package/README.md +1 -1
- package/lib/client.js +73 -53
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
[历史英文记录](docs/CHANGELOG.en.md)
|
|
6
6
|
|
|
7
|
+
## 0.13.2 (2026-10-27)
|
|
8
|
+
|
|
9
|
+
- 更新控件移至原有工作台或原生插件详情页底部;更新记录与选项原地展开,移除独立更新菜单与页面。
|
|
10
|
+
- 保留原页面操作与草稿,自动安装默认关闭;安装前确认,遇到未保存修改或运行任务时暂停。
|
|
11
|
+
|
|
12
|
+
## 0.13.1 (2026-10-27)
|
|
13
|
+
|
|
14
|
+
- Keep this component's own update entry visible when installed transitively through Suite. Confirm whole-Suite update scope before opening its controls.
|
|
15
|
+
- Keep automatic installation off by default and preserve independent installation behavior.
|
|
16
|
+
|
|
7
17
|
## 0.13.0 (2026-10-26)
|
|
8
18
|
|
|
9
19
|
- Add this plugin’s own update controls and independent preferences. Automatic installation stays off until explicitly enabled.
|
package/README.en.md
CHANGED
|
@@ -56,7 +56,7 @@ Detailed configuration, tool arguments and troubleshooting are in the [usage gui
|
|
|
56
56
|
|
|
57
57
|
## Plugin updates
|
|
58
58
|
|
|
59
|
-
Requires DSH 0.2.1-rc.3 or newer.
|
|
59
|
+
Requires DSH 0.2.1-rc.3 or newer. Update controls sit at the bottom of the existing workbench, or native plugin details for plugins without a workbench. Checks, release notes and preferences expand in place, with no separate update page or settings menu. Automatic checks are on by default; **automatic installation is off by default**. Only explicit opt-in permits compatible stable patches while idle with no unsaved changes. Preferences are independent, with per-plugin disable, deferral and ignore controls. No forced DSH restart or upgrade, and local-source installations stay unchanged. Suite components explain and operate on the whole Suite in the same footer.
|
|
60
60
|
|
|
61
61
|
Older releases need one manual update in native plugin management to acquire these controls. Publishing to npm does not replace an existing user installation.
|
|
62
62
|
|
package/README.md
CHANGED
|
@@ -56,7 +56,7 @@ dsh plugin --profile desktop add @stardustlc/dsh-dream
|
|
|
56
56
|
|
|
57
57
|
## 插件更新
|
|
58
58
|
|
|
59
|
-
需要 DSH 0.2.1-rc.3
|
|
59
|
+
需要 DSH 0.2.1-rc.3 或更新版本。更新控件直接位于原有工作台内容末尾;没有工作台的插件,位于原生插件详情页底部。检查、更新记录和偏好在原地展开,不增加独立更新页面或设置菜单。自动检查默认开启;**自动安装默认关闭**,明确授权后才会在空闲且没有未保存修改时安装兼容的稳定补丁。开关按插件独立保存,可关闭、推迟或忽略提醒;不会强制重启 DSH、升级 DSH 本体或替换本地源码安装。Suite 组件在当前页面底部明确说明并操作 Suite 整包更新。
|
|
60
60
|
|
|
61
61
|
旧版用户先在官方插件管理中手动更新一次,之后才会获得此入口。发布到 npm 不会直接替换用户已经安装的版本。
|
|
62
62
|
|
package/lib/client.js
CHANGED
|
@@ -173,16 +173,33 @@ function __stardustUpdatesGuard(React, name, { dirty = false, busy = false, reta
|
|
|
173
173
|
for (const listener of pool.listeners) listener();
|
|
174
174
|
}, []);
|
|
175
175
|
}
|
|
176
|
-
function
|
|
176
|
+
function __StardustPluginUpdatesFooter({ React, name }) {
|
|
177
177
|
const [, render] = React.useState(0), pool = __stardustUpdatesGuards();
|
|
178
178
|
React.useEffect(() => { const fn = () => render(value => value + 1); pool.viewListeners.add(fn); return () => pool.viewListeners.delete(fn); }, []);
|
|
179
179
|
const client = [...pool.clients].find(client => client.has(name));
|
|
180
180
|
if (!client) return null;
|
|
181
|
-
|
|
182
|
-
return React.createElement(client.Button, { icon: 'refresh', variant: available ? undefined : 'ghost', onClick: () => client.open(name), 'data-plugin-update': name }, available ? client.t('有更新', 'Update available') : client.t('插件更新', 'Update plugin'));
|
|
181
|
+
return React.createElement(client.Footer, { name });
|
|
183
182
|
}
|
|
184
|
-
function
|
|
185
|
-
return
|
|
183
|
+
function __stardustUpdatesInlinePage(React, element, name) {
|
|
184
|
+
return React.cloneElement(element, null, element.props.children, React.createElement(__StardustPluginUpdatesFooter, { key: 'plugin-updates', React, name }));
|
|
185
|
+
}
|
|
186
|
+
function __stardustUpdatesDetailName(subject, rows) {
|
|
187
|
+
if (!subject || !['bundle', 'row'].includes(subject.kind)) return null;
|
|
188
|
+
if (subject.kind === 'row' && subject.pkg?.name === '@stardustlc/dsh-suite') {
|
|
189
|
+
const component = rows.find(row => row.bundledBy === subject.pkg.name && (subject.row?.moduleName === row.name || subject.row?.moduleName?.startsWith(row.name + '/')));
|
|
190
|
+
if (component) return component.name;
|
|
191
|
+
}
|
|
192
|
+
return rows.some(row => row.name === subject.pkg?.name) ? subject.pkg.name : null;
|
|
193
|
+
}
|
|
194
|
+
function __stardustUpdatesNativeFormState(document) {
|
|
195
|
+
let dirty = false, busy = false;
|
|
196
|
+
for (const scope of document.querySelectorAll('[data-plugin-detail],[data-plugin-row-detail]')) for (const button of scope.querySelectorAll('button')) {
|
|
197
|
+
if (!button.getClientRects().length || button.closest('.plugin-updates-footer')) continue;
|
|
198
|
+
const label = (button.getAttribute('aria-label') || button.textContent || '').trim();
|
|
199
|
+
if (/^(?:正在保存|保存中|Saving)(?:[.…\s]|$)/i.test(label) || button.getAttribute('aria-busy') === 'true') busy = true;
|
|
200
|
+
if (!button.disabled && button.getAttribute('aria-disabled') !== 'true' && /^(?:保存(?:更改|设置|配置)?|Save(?: changes| settings| configuration)?)(?:\s+(?:Ctrl|⌘|Enter|↵).*)?$/i.test(label)) dirty = true;
|
|
201
|
+
}
|
|
202
|
+
return { dirty, busy };
|
|
186
203
|
}
|
|
187
204
|
function __stardustUpdatesDisplayRows(next, known) {
|
|
188
205
|
for (const row of next.rows) known.set(row.name, row);
|
|
@@ -191,6 +208,19 @@ function __stardustUpdatesDisplayRows(next, known) {
|
|
|
191
208
|
for (const row of known.values()) if ((row.name === next.job.name || row.bundledBy === next.job.name) && !rows.some(item => item.name === row.name)) rows.push({ ...row, updating: true });
|
|
192
209
|
return { ...next, rows };
|
|
193
210
|
}
|
|
211
|
+
function __stardustUpdatesSuiteRows(next, participants) {
|
|
212
|
+
// The official manager lists top-level dependencies, not Suite's transitive
|
|
213
|
+
// components. Project active, build-identified clients for display only.
|
|
214
|
+
// Installation and preferences still use the real Suite row from the host.
|
|
215
|
+
const suite = next.rows.find(row => row.name === '@stardustlc/dsh-suite' && row.enabled);
|
|
216
|
+
if (!suite) return next;
|
|
217
|
+
const rows = [...next.rows], names = new Set(rows.map(row => row.name));
|
|
218
|
+
for (const item of participants) if (item.suiteComponent && item.packageVersion && item.label && !names.has(item.name)) {
|
|
219
|
+
names.add(item.name);
|
|
220
|
+
rows.push({ name: item.name, version: item.packageVersion, enabled: true, sourceKind: 'suite', loadError: false, label: item.label, labelEn: item.labelEn, bundledBy: suite.name });
|
|
221
|
+
}
|
|
222
|
+
return { ...next, rows };
|
|
223
|
+
}
|
|
194
224
|
function __stardustUpdatesWrap(definition, participant) {
|
|
195
225
|
return { ...definition, factory(require) {
|
|
196
226
|
const original = definition.factory(require);
|
|
@@ -231,8 +261,8 @@ function __stardustUpdatesJoin(host, React, require, participant) {
|
|
|
231
261
|
}
|
|
232
262
|
function __stardustUpdatesClient(ctx, React, require, shared) {
|
|
233
263
|
const h = React.createElement, W = createWorkbenchUI(React), route = new URL('api/stardustlc/plugin-updates', document.baseURI);
|
|
234
|
-
const { useState, useEffect } = React, id = crypto.randomUUID(), listeners = new Set(), pool = __stardustUpdatesGuards(), requests = new Set(),
|
|
235
|
-
let alive = true, registered = false, state = null, lastInteraction = Date.now(), inPoll = false,
|
|
264
|
+
const { useState, useEffect } = React, id = crypto.randomUUID(), listeners = new Set(), pool = __stardustUpdatesGuards(), requests = new Set(), notified = new Set(), knownRows = new Map();
|
|
265
|
+
let alive = true, registered = false, state = null, lastInteraction = Date.now(), inPoll = false, manualOperation = false;
|
|
236
266
|
const language = () => String(ctx.locale?.getSnapshot?.().active || '').startsWith('en') ? 'en' : 'zh';
|
|
237
267
|
const t = (zh, en) => language() === 'en' ? en : zh;
|
|
238
268
|
const preferences = name => state?.preferences.packages[name] || { checkEnabled: true, autoInstall: false, deferredUntil: 0, ignoredVersion: null };
|
|
@@ -252,18 +282,18 @@ function __stardustUpdatesClient(ctx, React, require, shared) {
|
|
|
252
282
|
finally { clearTimeout(timeout); requests.delete(controller); }
|
|
253
283
|
}
|
|
254
284
|
function pageState(manual = manualOperation) {
|
|
255
|
-
const values = [...pool.values.values()];
|
|
256
|
-
const dialog = [...document.querySelectorAll('[role="dialog"],dialog[open]')].some(node => node.getClientRects().length && !(manual && (node.closest('.plugin-updates-
|
|
257
|
-
return { action: 'heartbeat', id, dirty: values.some(value => value.dirty), busy: values.some(value => value.busy) || dialog, idleForMs: Math.min(86400000, Math.max(0, Date.now() - lastInteraction)) };
|
|
285
|
+
const values = [...pool.values.values()], nativeForm = __stardustUpdatesNativeFormState(document);
|
|
286
|
+
const dialog = [...document.querySelectorAll('[role="dialog"],dialog[open]')].some(node => node.getClientRects().length && !(manual && (node.closest('.plugin-updates-footer') || node.querySelector('.plugin-updates-footer'))));
|
|
287
|
+
return { action: 'heartbeat', id, dirty: nativeForm.dirty || values.some(value => value.dirty), busy: nativeForm.busy || values.some(value => value.busy) || dialog, idleForMs: Math.min(86400000, Math.max(0, Date.now() - lastInteraction)) };
|
|
258
288
|
}
|
|
259
289
|
const heartbeat = () => api(pageState());
|
|
260
|
-
function interaction() { lastInteraction = Date.now(); }
|
|
290
|
+
function interaction() { lastInteraction = Date.now(); if (registered && alive) void heartbeat().catch(() => {}); }
|
|
261
291
|
const guardChanged = () => { if (registered && alive) void heartbeat().catch(() => {}); };
|
|
262
292
|
for (const event of ['pointerdown', 'keydown', 'input']) document.addEventListener(event, interaction, { capture: true, passive: true });
|
|
263
293
|
pool.listeners.add(guardChanged);
|
|
264
294
|
const close = () => { void fetch(route, { method: 'POST', credentials: 'same-origin', keepalive: true, headers: { 'content-type': 'application/json', 'x-stardustlc-updates': '1' }, body: JSON.stringify({ action: 'client-close', id }) }).catch(() => {}); };
|
|
265
295
|
window.addEventListener('pagehide', close);
|
|
266
|
-
async function refresh() { const next = await api(); if (alive) { state = __stardustUpdatesDisplayRows(next, knownRows); notify(); } return next; }
|
|
296
|
+
async function refresh() { const next = await api(); if (alive) { state = __stardustUpdatesDisplayRows(__stardustUpdatesSuiteRows(next, shared.refs.values()), knownRows); notify(); } return next; }
|
|
267
297
|
function useSnapshot() {
|
|
268
298
|
const [, render] = useState(0);
|
|
269
299
|
useEffect(() => { const fn = () => render(value => value + 1); listeners.add(fn); const stop = ctx.locale?.subscribe?.(fn); return () => { listeners.delete(fn); stop?.(); }; }, []);
|
|
@@ -291,8 +321,14 @@ function __stardustUpdatesClient(ctx, React, require, shared) {
|
|
|
291
321
|
function jobLabel(job) {
|
|
292
322
|
return ({ verifying: t('正在核对版本', 'Verifying release'), installing: t('正在安装', 'Installing'), cancelling: t('正在请求取消', 'Requesting cancellation'), complete: t('已更新', 'Updated'), 'restart-required': t('已安装,等待你手动重启', 'Installed; manual restart needed'), restored: t('更新未成功,已恢复原版本', 'Update failed; previous version restored'), 'restore-failed': t('恢复未成功,请检查官方插件管理', 'Recovery failed; check native plugin management'), unknown: t('结果未确认,请检查官方插件管理', 'Result unconfirmed; check native plugin management'), cancelled: t('已取消', 'Cancelled'), failed: t('更新未成功', 'Update failed') })[job.state] || job.state;
|
|
293
323
|
}
|
|
294
|
-
function
|
|
295
|
-
|
|
324
|
+
function FooterFrame({ name, label, subtitle, actions, children }) {
|
|
325
|
+
return h('section', { className: 'dshw plugin-updates-footer', 'data-plugin-update-footer': name, 'aria-label': label + t(' · 版本与更新', ' · Version and updates') },
|
|
326
|
+
h('style', null, W.styles),
|
|
327
|
+
h('style', null, '.plugin-updates-footer{box-sizing:border-box;flex-shrink:0;margin-top:28px;padding-top:18px;border-top:1px solid var(--dsw-alias-border-l3,#e5e7eb);min-width:0;max-width:100%;color:var(--dsw-alias-label-primary,#202124)}.plugin-updates-footer .updates-head{display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:12px}.plugin-updates-footer h2{font-size:14px;line-height:22px;margin:0;font-weight:600}.plugin-updates-footer .updates-subtitle,.plugin-updates-footer .updates-version{font:12px/1.6 ui-monospace,monospace;overflow-wrap:anywhere;margin:4px 0}.plugin-updates-footer .updates-options{margin-top:12px}.plugin-updates-footer .updates-options>summary{cursor:pointer;font-size:12px;line-height:22px;color:var(--dsw-alias-label-secondary,#657080)}.plugin-updates-footer .updates-options>summary:focus-visible{outline:2px solid var(--dsw-focus-ring-color,#6b79df);outline-offset:4px}.plugin-updates-footer .updates-settings{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,260px),1fr));gap:16px;padding:14px 0 0}.plugin-updates-footer .updates-note{margin:5px 0 0;font-size:12px;line-height:1.6}.plugin-updates-footer>.w-section{margin:10px 0 0;padding:0;border:0}.plugin-updates-footer .w-toolbar{margin-top:10px!important}.plugin-updates-footer .w-switch-label{max-width:100%;gap:12px;align-items:flex-start}.plugin-updates-footer .w-switch-track{flex-shrink:0}.plugin-updates-footer .updates-notes{margin-top:12px;padding:12px;border:1px solid var(--dsw-alias-border-l3,#e5e7eb);border-radius:10px}.plugin-updates-footer .updates-notes pre{white-space:pre-wrap;overflow-wrap:anywhere;max-height:240px;overflow:auto;font:12px/1.7 ui-monospace,monospace}.plugin-updates-footer .w-overlay{z-index:1002}@media(max-width:600px){.plugin-updates-footer .updates-head{gap:8px}.plugin-updates-footer .w-toolbar{gap:6px;flex-wrap:wrap}.plugin-updates-footer .w-button{max-width:100%}}'),
|
|
328
|
+
h('div', { className: 'updates-head' }, h('div', null, h('h2', null, t('版本与更新', 'Version and updates')), h('p', { className: 'updates-subtitle w-muted' }, subtitle)), actions), children);
|
|
329
|
+
}
|
|
330
|
+
function Panel({ name }) {
|
|
331
|
+
const snapshot = useSnapshot(), [error, setError] = useState(''), [working, setWorking] = useState(false), [checking, setChecking] = useState(false), [dialog, setDialog] = useState(null), [notes, setNotes] = useState(null), [notesOpen, setNotesOpen] = useState(false), [loadingNotes, setLoadingNotes] = useState(false);
|
|
296
332
|
const row = rowFor(name), original = originalRow(name), target = row?.name || name, prefs = preferences(target), job = snapshot?.job?.name === target ? snapshot.job : null;
|
|
297
333
|
async function perform(callback) { setError(''); setWorking(true); try { await callback(); await refresh(); } catch (error) { setError(error.message); await refresh().catch(() => {}); } finally { setWorking(false); } }
|
|
298
334
|
async function save(changes, consent) { await api({ action: 'preferences', name: target, revision: state.preferences.revision, changes, consent }); }
|
|
@@ -312,47 +348,41 @@ function __stardustUpdatesClient(ctx, React, require, shared) {
|
|
|
312
348
|
});
|
|
313
349
|
}
|
|
314
350
|
async function showNotes() {
|
|
315
|
-
|
|
351
|
+
if (notesOpen) { setNotesOpen(false); return; }
|
|
352
|
+
setNotesOpen(true); setNotes(null); setLoadingNotes(true); setError('');
|
|
316
353
|
try { setNotes(await api({ action: 'notes', name: target, version: row.release.version })); } catch (error) { setError(error.message); } finally { setLoadingNotes(false); }
|
|
317
354
|
}
|
|
318
355
|
if (!snapshot || !row) return h(W.Status, null, t('此插件尚未安装或已从当前 profile 移除。', 'This plugin is not installed in the current profile.'));
|
|
319
356
|
const title = language() === 'en' ? original.labelEn : original.label;
|
|
320
|
-
|
|
321
|
-
const unconfirmed = ['unknown', 'restore-failed'].includes(snapshot.job?.state), pageBusy = pageState(true).busy || (snapshot.pages.clients || []).some(client => client.id !== id && client.busy), installLocked = working || activeJob() || unconfirmed || snapshot.updaterRestart || snapshot.preferenceError || !snapshot.activity.known || snapshot.activity.busy || snapshot.pages.dirty || pageBusy || !snapshot.pages.known;
|
|
357
|
+
const ownPage = pageState(true), unconfirmed = ['unknown', 'restore-failed'].includes(snapshot.job?.state), pageBusy = ownPage.busy || (snapshot.pages.clients || []).some(client => client.id !== id && client.busy), installLocked = working || activeJob() || unconfirmed || snapshot.updaterRestart || snapshot.preferenceError || !snapshot.activity.known || snapshot.activity.busy || snapshot.pages.dirty || ownPage.dirty || pageBusy || !snapshot.pages.known;
|
|
322
358
|
const checkingThis = checking || snapshot.checkingNames?.includes(target);
|
|
323
359
|
const status = row.sourceKind !== 'registry' ? t('本地 / 外部来源,保留原安装方式', 'Local / external source; keep original install method') : row.loadError ? t('加载异常,请先检查官方插件管理', 'Loading error; check native plugin management') : row.error ? t('检查失败,可以重试', 'Check failed; retry available') : row.release && !row.release.compatible ? t('新版本暂不兼容当前环境', 'Latest release is incompatible with this environment') : row.available ? (prefs.ignoredVersion === row.release.version ? t('已忽略提醒,仍可手动更新', 'Reminder ignored; manual update available') : t('有新版本', 'Update available')) : row.checkedAt ? t('当前已是最新稳定版', 'Latest stable version') : t('尚未检查', 'Not checked yet');
|
|
324
|
-
return h(
|
|
325
|
-
h('style', null, '.plugin-updates-page{min-height:0;padding:4px}.plugin-updates-page .updates-settings{display:grid;gap:24px;padding:20px 0}.plugin-updates-page .updates-note{margin-top:8px}.plugin-updates-page .updates-version{font:13px/1.7 ui-monospace,monospace;overflow-wrap:anywhere}.plugin-updates-page .w-overlay{z-index:1002}.plugin-updates-page .w-switch-label{max-width:100%}.plugin-updates-page .w-switch-track{flex-shrink:0}@media(max-width:700px){[data-shortcut-modal="settings"]:has(.plugin-updates-page){flex-direction:column!important;width:calc(100vw - 16px)!important;max-width:calc(100vw - 16px)!important}[data-shortcut-modal="settings"]:has(.plugin-updates-page)>nav{width:100%!important;flex-basis:auto!important;flex-shrink:0;max-height:180px;overflow:auto;border-right:0!important}[data-shortcut-modal="settings"]:has(.plugin-updates-page)>nav>div:has(>button){display:flex;flex-wrap:wrap;gap:4px}[data-shortcut-modal="settings"]:has(.plugin-updates-page)>div{min-width:0!important;width:100%!important}.plugin-updates-page .w-switch-label{display:flex;justify-content:space-between;align-items:flex-start;gap:16px}.plugin-updates-page .w-switch-label>span:first-child{min-width:0}.plugin-updates-page .w-switch-label>input{inset-inline-end:0}}'),
|
|
360
|
+
return h(FooterFrame, { name, label: title, subtitle: original.name + ' · ' + original.version, actions: h(W.Button, { icon: 'refresh', disabled: checkingThis || row.sourceKind !== 'registry', onClick: check }, checkingThis ? t('正在检查…', 'Checking…') : target === '@stardustlc/dsh-suite' ? t('检查整包更新', 'Check Suite updates') : t('检查更新', 'Check for updates')) },
|
|
326
361
|
original.bundledBy && h(W.Status, null, t('此插件随 Suite 安装。以下设置与安装作用于 Suite 整包,以保持组件版本一致。单独安装的插件可以独立更新。', 'This plugin was installed through Suite. These controls update Suite as a package so its components stay consistent. Independently installed plugins update individually.')),
|
|
327
|
-
h('section', { className: 'w-section' }, h('p', { className: 'updates-version' }, target + ' · ' + row.version, row.release?.version && row.release.version !== row.version ? ' → ' + row.release.version : ''), h('p', { className: 'w-muted updates-note' }, status), !row.enabled && h('p', { className: 'w-muted' }, t('当前已停用,更新后仍保持停用。', 'Currently disabled; it will remain disabled after updating.')), row.checkedAt && h('p', { className: 'w-muted' }, t('上次检查:', 'Last checked: ') + new Date(row.checkedAt).toLocaleString(language() === 'en' ? 'en-US' : 'zh-CN')),
|
|
328
|
-
h('div', { className: 'w-toolbar'
|
|
329
|
-
h('section', { className: 'updates-
|
|
362
|
+
h('section', { className: 'w-section' }, (original.bundledBy || row.available) && h('p', { className: 'updates-version' }, target + ' · ' + row.version, row.release?.version && row.release.version !== row.version ? ' → ' + row.release.version : ''), h('p', { className: 'w-muted updates-note' }, status), !row.enabled && h('p', { className: 'w-muted' }, t('当前已停用,更新后仍保持停用。', 'Currently disabled; it will remain disabled after updating.')), row.checkedAt && h('p', { className: 'w-muted updates-note' }, t('上次检查:', 'Last checked: ') + new Date(row.checkedAt).toLocaleString(language() === 'en' ? 'en-US' : 'zh-CN')),
|
|
363
|
+
h('div', { className: 'w-toolbar' }, row.available && row.sourceKind === 'registry' && h(W.Button, { variant: 'primary', icon: 'download', disabled: installLocked || row.loadError, onClick: () => setDialog('install') }, target === '@stardustlc/dsh-suite' ? t('更新 Suite 整包', 'Update whole Suite') : t('更新此插件', 'Update this plugin')), row.release && h(W.Button, { variant: 'ghost', onClick: showNotes, 'aria-expanded': notesOpen }, t('更新记录', 'Release notes')), row.available && h(W.Button, { variant: 'ghost', disabled: working, onClick: () => perform(() => api({ action: 'later', name: target, revision: state.preferences.revision })) }, t('明天再提醒', 'Remind me tomorrow')), row.available && prefs.ignoredVersion !== row.release.version && h(W.Button, { variant: 'ghost', disabled: working, onClick: () => perform(() => api({ action: 'ignore', name: target, revision: state.preferences.revision, version: row.release.version })) }, t('忽略此版本提醒', 'Ignore this reminder')))),
|
|
364
|
+
notesOpen && h('section', { className: 'updates-notes', 'aria-label': t('更新记录', 'Release notes') }, h('strong', null, target + ' · ' + (notes?.version || row.release?.version || '')), loadingNotes ? h('p', null, t('正在读取…', 'Loading…')) : notes?.text ? h('pre', null, notes.text) : h('p', null, t('未找到此版本的独立更新记录。', 'No separate notes were found for this version.')), notes?.url && h('a', { href: notes.url, target: '_blank', rel: 'noreferrer noopener' }, t('查看仓库更新记录', 'View repository changelog'))),
|
|
365
|
+
h('details', { className: 'updates-options' }, h('summary', null, t('更新选项', 'Update options'), ' · ', prefs.checkEnabled ? t('自动检查:开', 'Checks: on') : t('自动检查:关', 'Checks: off'), ' · ', prefs.autoInstall ? t('自动安装:开', 'Install: on') : t('自动安装:关', 'Install: off')), h('section', { className: 'updates-settings', 'aria-label': t('这个插件的更新偏好', 'Update preferences for this plugin') },
|
|
330
366
|
h('div', null, h(W.Switch, { label: t('自动检查这个插件的更新', 'Automatically check this plugin'), checked: prefs.checkEnabled, disabled: working || !!snapshot.preferenceError || row.sourceKind !== 'registry', onChange: event => perform(() => save({ checkEnabled: event.target.checked })) }), h('p', { className: 'w-muted updates-note' }, t('默认开启,每天最多一次。只影响这个插件,关闭后仍可手动检查。', 'On by default, at most once daily. This affects only this plugin; manual checks remain available.'))),
|
|
331
|
-
h('div', null, h(W.Switch, { label: t('空闲时自动安装这个插件的补丁', 'Install patches for this plugin while idle'), checked: prefs.autoInstall, disabled: working || !!snapshot.preferenceError || row.sourceKind !== 'registry', onChange: event => event.target.checked ? setDialog('automatic') : perform(() => save({ autoInstall: false })) }), h('p', { className: 'w-muted updates-note' }, t('默认关闭。明确开启后,仅空闲且没有未保存修改时安装兼容补丁。其他插件的开关不变。', 'Off by default. Explicitly enable compatible patches while idle with no unsaved changes. Other plugins’ settings stay unchanged.')))),
|
|
367
|
+
h('div', null, h(W.Switch, { label: t('空闲时自动安装这个插件的补丁', 'Install patches for this plugin while idle'), checked: prefs.autoInstall, disabled: working || !!snapshot.preferenceError || row.sourceKind !== 'registry', onChange: event => event.target.checked ? setDialog('automatic') : perform(() => save({ autoInstall: false })) }), h('p', { className: 'w-muted updates-note' }, t('默认关闭。明确开启后,仅空闲且没有未保存修改时安装兼容补丁。其他插件的开关不变。', 'Off by default. Explicitly enable compatible patches while idle with no unsaved changes. Other plugins’ settings stay unchanged.'))))),
|
|
332
368
|
error && h(W.Status, { tone: 'error' }, message(error)), snapshot.preferenceError && h(W.Status, { tone: 'error' }, message('preferences-unreadable')),
|
|
333
369
|
snapshot.pendingRestart && h(W.Status, null, snapshot.updaterRestart ? message('restart-required') : t('已有更新等待手动重启后生效。你可以继续工作,自动安装已暂停。', 'Some updates need a manual restart. Continue your work; automatic installation is paused.')),
|
|
334
370
|
prefs.autoInstall && snapshot.autoReady === false && h(W.Status, null, message('page-not-ready')),
|
|
335
371
|
snapshot.pages.clients.some(client => !client.fresh) && h(W.Status, null, h('p', null, t('有窗口已断开,无法确认是否还有未保存修改。仍在使用的窗口请先保存并刷新;已关闭的窗口可以手动重新确认。', 'A disconnected window may contain unsaved changes. Save and refresh windows still in use. If they are closed, you can confirm that manually.')), h(W.Button, { onClick: () => setDialog('windows') }, t('重新确认已关闭的窗口', 'Confirm closed windows'))),
|
|
336
|
-
snapshot.pages.dirty && h(W.Status, null, message('page-dirty')), (snapshot.activity.busy || pageBusy) && h(W.Status, null, message(snapshot.activity.busy ? 'busy' : 'page-busy')),
|
|
372
|
+
(snapshot.pages.dirty || ownPage.dirty) && h(W.Status, null, message('page-dirty')), (snapshot.activity.busy || pageBusy) && h(W.Status, null, message(snapshot.activity.busy ? 'busy' : 'page-busy')),
|
|
337
373
|
prefs.deferredUntil > Date.now() && h(W.Status, null, t('此插件已推迟提醒和自动安装 24 小时,仍可手动更新。', 'Reminders and automatic installation for this plugin are deferred for 24 hours. Manual updates remain available.')),
|
|
338
374
|
unconfirmed && h(W.Status, { tone: 'error' }, message('operation-unconfirmed')),
|
|
339
375
|
job && h('section', { className: 'w-section', 'aria-label': t('这个插件的更新进度', 'Update progress for this plugin') }, h('h3', null, jobLabel(job)), h('p', { className: 'w-muted' }, job.from + ' → ' + job.to + (job.mode === 'automatic' ? t(' · 自动补丁更新', ' · Automatic patch') : '')), (job.reason || job.outcome?.result?.error?.code) && h(W.Status, { tone: 'error' }, message(job.reason || job.outcome.result.error.code)), activeJob() && h(W.Button, { disabled: job.state === 'cancelling', onClick: () => perform(() => api({ action: 'cancel', id: job.id })) }, t('取消更新', 'Cancel update')), h('p', { className: 'w-muted updates-note' }, t('安装开始后,取消由官方包管理器处理;已落盘的写入以实际结果或恢复状态为准。', 'After installation starts, the native package manager handles cancellation. Completed writes and recovery are reported as they occur.'))),
|
|
340
|
-
dialog && h(W.Dialog, { title: dialog === 'windows' ? t('断开的窗口都已关闭?', 'Are the disconnected windows closed?') : dialog === 'automatic' ? t('允许这个插件自动安装补丁?', 'Allow automatic patches for this plugin?') :
|
|
376
|
+
dialog && h(W.Dialog, { title: dialog === 'windows' ? t('断开的窗口都已关闭?', 'Are the disconnected windows closed?') : dialog === 'automatic' ? t('允许这个插件自动安装补丁?', 'Allow automatic patches for this plugin?') : t('确认更新此插件', 'Confirm this plugin update'), closeLabel: t('关闭', 'Close'), onClose: () => setDialog(null) },
|
|
377
|
+
target === '@stardustlc/dsh-suite' && h('p', null, t('此处操作的是 Suite 整包,包含 18 个组件。独立安装的其他插件不受影响。', 'This action applies to the whole Suite, including its 18 components. Other independently installed plugins are unaffected.')),
|
|
341
378
|
dialog === 'windows' ? h('div', { className: 'w-stack' }, h('p', null, t('只清除超过 45 秒未回应的其他窗口记录。请先确认它们已经关闭;仍在使用的窗口请保存修改并刷新。当前或仍在回应的窗口保护会保留。', 'Only other windows silent for over 45 seconds are cleared. Confirm that they are closed; save and refresh windows still in use. Current and responsive windows remain protected.')), h('div', { className: 'w-toolbar' }, h(W.Button, { onClick: () => setDialog(null) }, t('取消', 'Cancel')), h(W.Button, { onClick: () => { setDialog(null); void perform(() => api({ ...pageState(true), action: 'reconfirm-windows', confirmation: 'other-stale-windows-are-closed' })); } }, t('已关闭,重新确认状态', 'They are closed; reconfirm')))) :
|
|
342
379
|
dialog === 'automatic' ? h('div', { className: 'w-stack' }, h('p', null, (language() === 'en' ? row.labelEn : row.label) + ' · ' + target), h('p', null, t('你正在主动启用这个插件的自动安装。只处理兼容的稳定补丁;有任务、未保存修改、未知窗口或等待重启时暂停。', 'You are explicitly enabling automatic installation for this plugin. Only compatible stable patches qualify; running tasks, unsaved changes, unknown windows or a pending restart pause it.')), h('p', null, t('不会自动重启 DSH,其他插件不会跟着开启。可随时关闭。', 'DSH will not restart automatically, and other plugins will not be enabled for automatic updates. Turn it off anytime.')), h('div', { className: 'w-toolbar' }, h(W.Button, { onClick: () => setDialog(null) }, t('保持关闭', 'Keep off')), h(W.Button, { variant: 'primary', onClick: () => { setDialog(null); void perform(() => save({ checkEnabled: true, autoInstall: true }, 'enable-idle-patch-updates')); } }, t('允许此插件自动安装补丁', 'Allow patches for this plugin')))) :
|
|
343
|
-
|
|
344
|
-
h('div', { className: 'w-stack' }, h('p', { className: 'updates-version' }, target + ': ' + row.version + ' → ' + row.release.version), h('p', null, t('通过 DSH 官方插件管理器安装。需要重启时,由你手动决定时间;停用状态会保留。', 'Install through DSH’s native plugin manager. Choose when to restart if needed; disabled status is preserved.')), h('p', { className: 'w-muted' }, t('来源:npm 官方 registry,网络不可达时回退 npmmirror。安装前再次核对版本与完整性。', 'Source: official npm registry, with npmmirror fallback if unreachable. Version and integrity are checked again before installation.')), h('div', { className: 'w-toolbar' }, h(W.Button, { onClick: () => setDialog(null) }, t('取消', 'Cancel')), h(W.Button, { variant: 'primary', onClick: install }, t('确认安装此版本', 'Install this version')))))
|
|
380
|
+
h('div', { className: 'w-stack' }, h('p', { className: 'updates-version' }, target + ': ' + row.version + ' → ' + row.release.version), h('p', null, t('通过 DSH 官方插件管理器安装。需要重启时,由你手动决定时间;停用状态会保留。', 'Install through DSH’s native plugin manager. Choose when to restart if needed; disabled status is preserved.')), h('p', { className: 'w-muted' }, t('来源:npm 官方 registry,网络不可达时回退 npmmirror。安装前再次核对版本与完整性。', 'Source: official npm registry, with npmmirror fallback if unreachable. Version and integrity are checked again before installation.')), (ownPage.dirty || snapshot.pages.dirty) && h(W.Status, null, message('page-dirty')), h('div', { className: 'w-toolbar' }, h(W.Button, { onClick: () => setDialog(null) }, t('取消', 'Cancel')), h(W.Button, { variant: 'primary', disabled: installLocked || row.loadError, onClick: install }, t('确认安装此版本', 'Install this version')))))
|
|
345
381
|
);
|
|
346
382
|
}
|
|
347
|
-
function
|
|
348
|
-
useSnapshot();
|
|
349
|
-
|
|
350
|
-
if (!row) return null;
|
|
351
|
-
const content = h('div', { className: 'dshw' }, h('style', null, W.styles), h(W.Dialog, { title: (language() === 'en' ? row.labelEn : row.label) + t(' · 更新设置', ' · Update settings'), closeLabel: t('关闭更新设置', 'Close update settings'), onClose: () => { activeName = null; notify(); } }, h(Panel, { key: activeName, name: activeName })));
|
|
352
|
-
const settings = document.querySelector('[data-shortcut-modal="settings"]');
|
|
353
|
-
// Keep the dialog inside the native settings focus boundary when its
|
|
354
|
-
// per-plugin entry was opened there. The shared fiber survives HMR.
|
|
355
|
-
return settings ? require('react-dom').createPortal(content, settings) : content;
|
|
383
|
+
function NativeFooter({ subject }) {
|
|
384
|
+
const snapshot = useSnapshot(), name = __stardustUpdatesDetailName(subject, snapshot?.rows || []);
|
|
385
|
+
return name ? h(Panel, { name }) : null;
|
|
356
386
|
}
|
|
357
387
|
function Reminder() {
|
|
358
388
|
useSnapshot();
|
|
@@ -361,26 +391,16 @@ function __stardustUpdatesClient(ctx, React, require, shared) {
|
|
|
361
391
|
let Toast; try { Toast = require('@deepseek-ai/dsh-client-ui-primitives').Toast; } catch { return null; }
|
|
362
392
|
if (!Toast) return null;
|
|
363
393
|
const key = row.name + '@' + row.release.version;
|
|
364
|
-
return h(Toast, { key, text: (language() === 'en' ? row.labelEn : row.label) + t('
|
|
394
|
+
return h(Toast, { key, text: (language() === 'en' ? row.labelEn : row.label) + t('有新版本,可在页面底部查看。', ' has an update. Review it at the bottom of its page.'), icon: h(W.Icon, { name: 'refresh', size: 18 }), holdMs: 7000, onDone: () => { notified.add(key); notify(); } });
|
|
365
395
|
}
|
|
366
396
|
function register() {
|
|
367
397
|
if (!alive) return;
|
|
368
398
|
if (!registered) {
|
|
369
399
|
registered = true; pool.clients.add(client);
|
|
370
|
-
ctx.slots.inject('
|
|
400
|
+
ctx.slots.inject('plugins.detail.section', () => ctx.slots.register({ name: 'plugins.detail.section', id: 'stardustlc.plugin-update-footer', order: 1000 }, NativeFooter));
|
|
371
401
|
ctx.slots.inject('shell.overlay', () => ctx.slots.register({ name: 'shell.overlay', id: 'stardustlc.plugin-update-reminder' }, Reminder));
|
|
372
402
|
notify();
|
|
373
403
|
}
|
|
374
|
-
for (const row of state.rows) {
|
|
375
|
-
const hasWorkbench = [...shared.refs.values()].some(participant => participant.name === row.name && participant.hasWorkbench);
|
|
376
|
-
if (hasWorkbench) { if (sections.has(row.name)) { void sections.get(row.name).dispose(); sections.delete(row.name); } continue; }
|
|
377
|
-
if (sections.has(row.name)) continue;
|
|
378
|
-
const label = () => (language() === 'en' ? row.labelEn : row.label) + t(' · 更新', ' · Updates');
|
|
379
|
-
const fiber = ctx.plugin({ name: 'plugin-update-settings-' + row.name, inject: ['slots', 'locale'], apply(section) {
|
|
380
|
-
section.slots.inject('settings.section', () => section.slots.register({ name: 'settings.section', id: row.name + '.updates', label, order: 89 }, () => h(Panel, { name: row.name })));
|
|
381
|
-
} });
|
|
382
|
-
sections.set(row.name, fiber);
|
|
383
|
-
}
|
|
384
404
|
}
|
|
385
405
|
async function poll() {
|
|
386
406
|
if (inPoll || !alive || !shared.refs.size) return;
|
|
@@ -390,7 +410,7 @@ function __stardustUpdatesClient(ctx, React, require, shared) {
|
|
|
390
410
|
finally { inPoll = false; }
|
|
391
411
|
}
|
|
392
412
|
const timer = setInterval(() => { void poll(); }, 15000), first = setTimeout(() => { void poll(); }, 100);
|
|
393
|
-
const client = { t,
|
|
413
|
+
const client = { t, Footer: Panel, has: name => registered && !!originalRow(name), row: rowFor, dispose() {
|
|
394
414
|
close(); alive = false; clearTimeout(first); clearInterval(timer); pool.clients.delete(client);
|
|
395
415
|
pool.listeners.delete(guardChanged); listeners.clear();
|
|
396
416
|
for (const event of ['pointerdown', 'keydown', 'input']) document.removeEventListener(event, interaction, true);
|
|
@@ -2812,15 +2832,15 @@ pristine ? null : h(Btn, {
|
|
|
2812
2832
|
})),
|
|
2813
2833
|
]);
|
|
2814
2834
|
};
|
|
2815
|
-
return h(W.Page,
|
|
2816
|
-
actions:h(React.Fragment,null,h(AutomaticMemoryCard,{reloadToken,onReview:()=>{setActiveTab('review');reload();}}),h(W.Button,{variant:'ghost',onClick:togglePrivacy,'aria-pressed':privacy,title:t('显示层模糊;DOM 仍为明文,不改文件、不加密')},privacy?t('隐私模式:开'):t('隐私模式')),h(W.IconButton,{icon:'refresh',label:t('刷新'),onClick:reload,disabled:loading || knowledgeLoading}))},
|
|
2835
|
+
return __stardustUpdatesInlinePage(React,h(W.Page, {workbench,title:dreamText('Dream 记忆','Dream memory'),subtitle:dreamText('审阅、复用和管理任务经验。','Review, reuse and manage task experience.'),className:'dshd-root dream-workbench' + (privacy ? ' dshd-privacy' : ''),
|
|
2836
|
+
actions:h(React.Fragment,null,h(AutomaticMemoryCard,{reloadToken,onReview:()=>{setActiveTab('review');reload();}}),h(W.Button,{variant:'ghost',onClick:togglePrivacy,'aria-pressed':privacy,title:t('显示层模糊;DOM 仍为明文,不改文件、不加密')},privacy?t('隐私模式:开'):t('隐私模式')),h(W.IconButton,{icon:'refresh',label:t('刷新'),onClick:reload,disabled:loading || knowledgeLoading}))},
|
|
2817
2837
|
h('style',null,DREAM_WORKBENCH_CSS),
|
|
2818
2838
|
h(W.Status,null,actionNotice),
|
|
2819
2839
|
h(W.Tabs,{label:dreamText('Dream 视图','Dream views'),value:activeTab,onChange:value=>{setFilterOpen(false);setActiveTab(value);},items:[
|
|
2820
2840
|
{id:'review',label:dreamText('待审阅','Awaiting review'),count:knowledgeVm.rows.filter(row=>HELD_STATES.includes(row.state)).length},
|
|
2821
2841
|
{id:'knowledge',label:dreamText('已采纳','Accepted')},{id:'journal',label:t('日记')},{id:'bridge',label:dreamText('规则记录','Rule history')}]},
|
|
2822
2842
|
activeTab === 'journal' ? h('div',{className:'dream-journal'},errorBox,toolbar,body(),h('div',{className:'dshd-footer'},h('span',null,footerTextOf(query,limit,vm.total,vm.count)),canLoadMore?h(W.Button,{onClick:showMore,disabled:loading},t('显示更早的梦')):null)) : body(),
|
|
2823
|
-
privacy ? h('p',{className:'w-muted',role:'status'},t('隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。')) : null));
|
|
2843
|
+
privacy ? h('p',{className:'w-muted',role:'status'},t('隐私模式:显示层模糊;DOM 仍为明文,不改文件、不加密。点击卡片可临时查看。')) : null)),"@stardustlc/dsh-dream");
|
|
2824
2844
|
}
|
|
2825
2845
|
function DreamSection({locale,workbench=false}={}) { useLanguage(locale); return h(DreamPanel,{workbench}); }
|
|
2826
2846
|
/* @ui-css-start */
|
|
@@ -3064,7 +3084,7 @@ W.register(ctx,{id:'dsh-dream.workbench',label:()=>dreamText('Dream 记忆','Dre
|
|
|
3064
3084
|
DEFAULT_LIMIT: DEFAULT_LIMIT,
|
|
3065
3085
|
};
|
|
3066
3086
|
return module.exports;
|
|
3067
|
-
} },{"name":"@stardustlc/dsh-dream","version":"0.1.0","hasWorkbench":true}));
|
|
3087
|
+
} },{"name":"@stardustlc/dsh-dream","version":"0.1.0","hasWorkbench":true,"packageVersion":"0.13.2","label":"Dream 记忆","labelEn":"Dream","suiteComponent":true}));
|
|
3068
3088
|
|
|
3069
3089
|
})();
|
|
3070
3090
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardustlc/dsh-dream",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.2",
|
|
4
4
|
"description": "回顾会话、保存反思,并把经过核验和审阅的经验用于后续任务。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"dsh": ">=0.2.1-rc.3"
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@stardustlc/dsh-updates": "0.1.
|
|
61
|
+
"@stardustlc/dsh-updates": "0.1.2"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
64
|
"@types/node": "^24.0.0",
|