ygc-dsh-plugin-autoupdate 0.9.5 → 1.0.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.
package/README.md CHANGED
@@ -278,17 +278,19 @@ Electron 的 main / gpu-process / renderer / utility,以及以 `ELECTRON_RUN_A
278
278
  ```
279
279
  D:\Ai工作空间\插件库\ ← 共享文件夹,两台机器都能读写
280
280
  ├─ <本机标识>\ ← 我的格子,例 DSH-7e6b1bb7e052
281
- │ ├─ PLUGINS.json 我的插件清单(含副本指纹表)
281
+ │ ├─ PLUGINS.json 我的插件清单(**只有清单,没有任何插件文件**)
282
282
  │ ├─ FEATURE.json 我的身份说明
283
283
  │ ├─ OTHERS-PLUGINS.json 我的汇总(本机自用)
284
- │ ├─ OTHERS-PLUGINS.md 同一份的人读版
285
- │ └─ local\ 本地 / Git 来源插件的 .tgz 副本
284
+ │ └─ OTHERS-PLUGINS.md 同一份的人读版
286
285
  └─ <别人的标识>\ ← 别人写,我只读
287
286
  ├─ PLUGINS.json
288
- ├─ FEATURE.json
289
- └─ local\
287
+ └─ FEATURE.json
290
288
  ```
291
289
 
290
+ > **0.9.6 起:特征文件夹只存清单,不存插件副本。** 以前这里还有 `local/` 放 .tgz,
291
+ > 现在没有了,而且会自动清掉老版本留下的 `local/` 与散落 tgz。
292
+ > 安装一律按清单里的「安装坐标」去 registry 联网拉取,不存在本地副本安装这条路。
293
+
292
294
  **没有公共分区。** 早先版本在插件库下分出「特征 / 汇集」两级子目录,那是错的:
293
295
  插件库是多人共享的盘,公共目录等于公共广场,所有人的东西往一处堆,谁的产出分不清,
294
296
  而且「汇集」会成为多方争写的热点。现在改成:
@@ -323,31 +325,45 @@ D:\Ai工作空间\插件库\ ← 共享文件夹,两台机器都能
323
325
  `POST /settings { shareResetHash: true }`——这是唯一的重算入口,其它情况一律沿用旧值。
324
326
 
325
327
 
326
- ### 清单里写什么
328
+ ### 清单里写什么(只有清单,没有文件)
329
+
330
+ 每一项只有五个字段:`name` / `version` / `kind` / `installSpec` / `installable`。
327
331
 
328
- | 插件来源 | 清单里写什么 | 要不要打副本 |
332
+ | 插件来源 | 安装坐标 `installSpec` | 能装吗 |
329
333
  |---|---|---|
330
- | `registry`(能联网更新) | 名称 + 版本 | **不打**,对方自己从 registry 拉 |
331
- | `local`(`file:` 本地路径) | 名称 + 版本 + 来源说明 | **打**,`local/<name>-<version>.tgz` |
332
- | `git`(GitHub 等) | 名称 + 版本 | **打**,同上 |
333
- | `installation`(随 DSH 安装提供) | **不写** | 别人本来就有,也没版本号可装 |
334
+ | `registry`(能联网更新) | `<name>@<version>` | 能,registry 拉 |
335
+ | `git`(GitHub 等) | 原 git 写法(`github:o/r#ref`) | 能,联网拉 |
336
+ | `local`(`file:` 本地路径) | 空 | **不能**:未发布的东西没地方拉 |
337
+ | `installation`(随 DSH 安装提供) | — | **不写进清单**:别人本来就有 |
338
+
339
+ `PLUGINS.json` 里的 `policy` 段把这条规矩写死,别人一看就知道这份清单不含文件:
340
+
341
+ ```json
342
+ {
343
+ "schemaVersion": 2,
344
+ "policy": {
345
+ "manifestOnly": true,
346
+ "storesBundles": false,
347
+ "installVia": "registry",
348
+ "note": "特征文件夹只存清单;安装一律按 installSpec 联网拉取,不使用本地副本。"
349
+ },
350
+ "plugins": [
351
+ { "name": "dsh-tavern", "version": "2.8.1", "kind": "registry",
352
+ "installSpec": "dsh-tavern@2.8.1", "installable": true }
353
+ ]
354
+ }
355
+ ```
356
+
357
+ 历史遗留的 `local/` 与根上散落的 `*.tgz` 会在每轮同步时被删掉(删除记录写进
358
+ `PLUGINS.json` 的 `legacyPurged`,便于排查「我的 local/ 怎么没了」)。
334
359
 
335
- 副本是 npm 标准 tgz(`package/` 前缀),排除 `node_modules`、`.git`、`__pycache__`、`*.bak-*`。
336
- 单包上限 96 MB,单个特征的副本总量上限 512 MB,同名插件只留最近 2 个版本。
337
- `link:` 装的包在 `node_modules` 下是符号链接,打包时按 `realpath` 追到真实目录。
338
360
 
339
- ### 什么时候跑
340
361
 
341
362
  **每次巡检跑一次**——巡检 = 自动更新的那一轮定时检查(间隔见「检查频率」)。
342
363
  顺序是先查更新、再同步共享;更新失败不影响共享同步。面板上也有「立即同步一次」。
343
364
 
344
365
  每次同步都幂等:
345
366
 
346
- - 清单内容(除 `generatedAt` / `lastSyncAt` 这类时间字段)没变 → 不重写文件
347
- - 源目录指纹(文件数 + 最大 mtime + 总字节)没变 → 不重压 `.tgz`,直接复用
348
- - 本机标识第一次同步时固定进 state,之后每轮原样传回,不做任何重算
349
-
350
- 所以几百 MB 的本地插件不会把每轮巡检拖慢。
351
367
 
352
368
  ### 拉别人的清单
353
369
 
@@ -361,28 +377,31 @@ D:\Ai工作空间\插件库\ ← 共享文件夹,两台机器都能
361
377
 
362
378
  合并去重规则:
363
379
 
364
- - 去重键是包名;多本特征都有同一插件时,版本取**更高**的那份(按语义版本比,不看文件时间)
365
- - 副本跟着最高版本走;`features` 里记下它出现在哪几本
366
- - 「本机有没有」按 profile 的 `dsh.profile.bundles` 判断,不看 `node_modules`(那里塞满传递依赖)
380
+ - 去重键是包名;多本特征都有同一插件时,版本与安装坐标都取**更高**的那份(按语义版本比,不看文件时间)
381
+ - `features` 里记下它出现在哪几本;老清单里的 `bundles` 数组一律忽略
367
382
  - 没 `PLUGINS.json` 的目录只记一条提示,不报错
368
383
 
369
- ### 装的时候走哪条路
384
+ ### 装的时候走哪条路(只有一条:联网)
385
+
386
+ | 清单里的 `installSpec` | 用的 spec | 结果 |
387
+ |---|---|---|
388
+ | `<name>@<version>` | 原样 | 从 registry 拉 |
389
+ | `github:o/r#ref` 这类 git 写法 | 原样 | 联网拉 |
390
+ | 空(`local` 来源) | — | **拒绝**,说明「对方未发布到 registry」 |
391
+ | `file:` / `link:` 开头 | — | **拒绝**:只走 registry 联网拉取 |
392
+
393
+ 就算对方是老版本写的清单、里面还带着 `bundles` 数组,也一律忽略:
394
+ 拿别人打包过的旧代码装上,版本号写着新的、行为是老的那份,比装不上更难查。
370
395
 
371
- | 对方特征里有 | 用的 spec |
372
- |---|---|
373
- | `.tgz` 副本 | `file:<对方副本的绝对路径>`(正斜杠) |
374
- | 只有名称(registry 来源) | `<name>@<version>` |
375
- | 既没副本又不是 registry | 拒绝,说明原因 |
376
396
 
377
- 装完自动把包名补进 profile 的 `dsh.profile.bundles`——不补的话插件装下来但不会加载。
378
397
  装之前同样会快照 profile 三个清单文件,出问题用 `/restore` 回滚。**装完必须完全退出 DSH 再重开。**
379
398
 
380
399
  ### 共享相关接口
381
400
 
382
401
  | 方法 | 路径 | 作用 |
383
402
  |---|---|---|
384
- | GET | `/share` | 我的特征现状 + 他人清单(含每项是否已装、有没有副本) |
385
- | POST | `/share/sync` | 立即同步一次(我的清单 + 副本 + 他人汇集) |
403
+ | GET | `/share` | 我的特征现状 + 他人清单(含每项是否已装、能不能装、安装坐标) |
404
+ | POST | `/share/sync` | 立即同步一次(我的清单 + 他人汇集;不打包任何文件) |
386
405
  | POST | `/share/install` | 安装他人清单里的一个插件(body: `{ name }`) |
387
406
 
388
407
  设置项(走 `/settings`):`shareEnabled`、`shareLibraryRoot`、`shareFeatureName`、`shareResetHash`。
@@ -408,7 +427,7 @@ $node = "C:\DeepSeekHarness\resources\runtime\primary-runtime\dependencies\node\
408
427
  | `update-guard.test.mjs` | 只向上更新、来源判定、发布年龄门槛 |
409
428
  | `git-source.test.mjs` | Git 来源的 commit 比较与安装 spec |
410
429
  | `machine.test.mjs` | 本机标识:继承会话同步的 machineId、固定后不漂移、改名不换特征值、卸载会话同步后旧值仍有效、写边界(别人的格 / `../` 逃逸 / 前缀相近) |
411
- | `share.test.mjs` | 特征文件夹:清单分类(registry 只写名 / 本地打副本 / 宿主自带不写)、tgz 结构、同步幂等与副本复用、他人清单合并去重、安装路径选择、特征名防上跳 |
430
+ | `share.test.mjs` | 特征文件夹:清单只写元数据、**目录里不允许出现任何插件文件**、历史 `local/` 被清理、同步幂等、他人清单合并去重(忽略老清单 bundles)、安装只走 registry(有副本也拒绝、`file:` 坐标也拒绝)、特征名防上跳 |
412
431
  | `schedule.test.mjs` | 定时排期:更新与共享同步各自独立开关 |
413
432
  | `system-plugins.test.mjs` | **本体插件隐藏契约**(源码级):`isBuiltinPlugin` 判据只有一处定义、listPlugins 出口过滤并缓存隐藏名单、runCheck 不查、snapshot 再兜一层并透出 `builtin.count`、策略/更新接口对本体插件一律 400、共享清单同一判据、面板整组隐藏只留一行说明 |
414
433
  | `system-plugins.behavior.test.mjs` | **本体插件隐藏行为**(真执行 + 假宿主 + 假 profile):`@deepseek-ai/*` 一行都不出现、第三方插件照常可写策略、`builtin.count` 数对、`pluginManager` 缺失 / 抛错 / 脏数据三种降级都不让 `/state` 失败 |
@@ -549,7 +568,7 @@ POST /api/local/plugin-autoupdate/plugin-policy {"name":"@deepseek-ai/dsh-base"
549
568
  版本跟桌面端走,`registry` 上的同名包与它无关;策略/更新接口对它们一律 400
550
569
  - 更新失败只记历史,不自动回滚(避免把用户自己手改的清单吞掉);要回滚用 `/restore`
551
570
  - DSH 关闭期间不检查(进程内定时器的固有限制);重开后会立刻按上游发布时间重新判定
552
- - 共享只**同步清单与副本**,不自动安装别人的插件;安装必须人工点选
571
+ - 共享只**同步清单**(不存任何插件文件),不自动安装别人的插件;安装必须人工点选,且只走 registry 联网拉取
553
572
  - 只写 `插件库\<本机标识>\` 那一格,别人的格子只读(写入口有守卫)
554
573
  - 不在插件库根下留任何公共文件或公共目录
555
574
  - **重启只认显式 pid**:任何「按映像名清场」的写法都是回归,`restart-safety.test.mjs` 会红
package/client.js CHANGED
@@ -46,7 +46,6 @@ const CSS = `
46
46
  .pau-switch{display:inline-flex;align-items:center;gap:8px;font-size:13px;cursor:pointer;user-select:none}
47
47
  .pau-switch input{width:16px;height:16px;cursor:pointer}
48
48
  .pau-actions{display:flex;gap:10px;flex-wrap:wrap;align-items:center}
49
- .pau-share-path{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;word-break:break-all;color:var(--dsw-alias-label-secondary,#555)}
50
49
  .pau-tag-mine{background:var(--dsw-alias-info-tint,#e0f2fe);color:var(--dsw-alias-info,#0369a1)}
51
50
  .pau-grid{display:flex;flex-direction:column;gap:8px}
52
51
  .pau-other{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--dsw-alias-border-l25,#f0f0f0);flex-wrap:wrap}
@@ -96,7 +95,6 @@ function makePanel(React) {
96
95
 
97
96
  return function AutoUpdatePanel() {
98
97
  const [snap, setSnap] = useState(null);
99
- const [shareDetail, setShareDetail] = useState(null);
100
98
  const [busy, setBusy] = useState('');
101
99
  const [error, setError] = useState('');
102
100
  const [note, setNote] = useState('');
@@ -105,18 +103,6 @@ function makePanel(React) {
105
103
  const apply = useCallback((next) => {
106
104
  if (!next) return;
107
105
  setSnap(next);
108
- // 共享面板的明细单独取一次:它是扫盘结果,比状态文件重,不跟 15 秒轮询一起刷
109
- if (next.share && next.share.detail) setShareDetail(next.share.detail);
110
- }, []);
111
-
112
- const loadShareDetail = useCallback(async () => {
113
- try {
114
- const data = await call('/share');
115
- if (data && data.share && data.share.detail) setShareDetail(data.share.detail);
116
- if (data) setSnap(data);
117
- } catch (err) {
118
- setError(String(err.message || err));
119
- }
120
106
  }, []);
121
107
 
122
108
  const refresh = useCallback(async () => {
@@ -137,11 +123,6 @@ function makePanel(React) {
137
123
  };
138
124
  }, [refresh]);
139
125
 
140
- useEffect(() => {
141
- // 共享明细只取一次,之后靠「立即同步」/ 安装后刷新
142
- loadShareDetail();
143
- }, [loadShareDetail]);
144
-
145
126
  const run = async (name, fn) => {
146
127
  setBusy(name);
147
128
  setError('');
@@ -676,237 +657,7 @@ function makePanel(React) {
676
657
  ),
677
658
  );
678
659
 
679
- const share = snap.share || {};
680
- const detail = shareDetail || null;
681
- const myFeature = (detail && detail.myFeature) || share.effectiveFeature || '';
682
- const libRoot = (detail && detail.libraryRoot) || share.effectiveRoot || '';
683
- const others = (detail && Array.isArray(detail.others) ? detail.others : []).filter((p) => !p.installed);
684
- const installedFromOthers = (detail && Array.isArray(detail.others) ? detail.others : []).filter(
685
- (p) => p.installed,
686
- );
687
- const sources = (detail && Array.isArray(detail.sources) ? detail.sources : []) || [];
688
660
 
689
- const shareCard = h(
690
- 'div',
691
- { className: 'pau-card' },
692
- h(
693
- 'div',
694
- { className: 'pau-card-h' },
695
- h('h3', { className: 'pau-card-t' }, '插件共享(特征文件夹)'),
696
- h(
697
- 'span',
698
- { className: share.enabled ? 'pau-tag pau-tag-ok' : 'pau-tag' },
699
- share.enabled ? '每次巡检自动同步' : '自动同步已关闭',
700
- ),
701
- ),
702
- h(
703
- 'p',
704
- { className: 'pau-sub' },
705
- '插件库是共享文件夹,一个人一格:我的清单、本地插件副本、我自己的汇总全都写在「插件库\\<我的特征>」这一格里面。能联网更新的插件只写名称,本地/Git 来源的打成 .tgz 副本一起放过去。别人的格子只读,汇总也是各机扫一遍自己算,不放在公共位置。',
706
- ),
707
- h(
708
- 'label',
709
- { className: 'pau-switch' },
710
- h('input', {
711
- type: 'checkbox',
712
- checked: !!share.enabled,
713
- onChange: (e) => patchSettings({ shareEnabled: e.target.checked }),
714
- }),
715
- '每次巡检顺带同步一次特征文件夹',
716
- ),
717
- h(
718
- 'div',
719
- { className: 'pau-field' },
720
- h('span', { className: 'pau-field-l' }, '插件库根目录'),
721
- h('input', {
722
- className: 'pau-input',
723
- style: { minWidth: 320, flex: 1 },
724
- placeholder: '留空用默认 D:\\Ai工作空间\\插件库',
725
- defaultValue: share.libraryRoot || '',
726
- onBlur: (e) => patchSettings({ shareLibraryRoot: e.target.value.trim() }),
727
- }),
728
- ),
729
- h(
730
- 'div',
731
- { className: 'pau-field' },
732
- h('span', { className: 'pau-field-l' }, '我的特征文件夹名'),
733
- h('input', {
734
- className: 'pau-input',
735
- style: { minWidth: 220 },
736
- placeholder: '名称部分,留空用电脑名',
737
- defaultValue: share.featureName || '',
738
- onBlur: (e) => patchSettings({ shareFeatureName: e.target.value.trim() }),
739
- }),
740
- h(
741
- 'span',
742
- { className: 'pau-row-d' },
743
- share.machineHash ? `特征值 ${share.machineHash}(已固定,改名字不会变)` : '特征值首次同步时固定',
744
- ),
745
- ),
746
- (detail && detail.identity && detail.identity.sharesWithSyncBackup) !== false
747
- ? h(
748
- 'div',
749
- { className: 'pau-row-d' },
750
- '与会话同步插件共用同一个本机标识,两台机器之间只有「一个我」。',
751
- )
752
- : h(
753
- 'div',
754
- { className: 'pau-row-d' },
755
- '注意:没读到会话同步插件的标识,本插件用了自己的特征值。',
756
- ),
757
- h('div', { className: 'pau-row-d' }, `我的特征目录:`),
758
- h('div', { className: 'pau-share-path' }, `${libRoot}${myFeature ? `\\${myFeature}` : ''}`),
759
- h(
760
- 'div',
761
- { className: 'pau-row-d' },
762
- `我的汇总(只给本机看,各机自己汇集):${myFeature ? `\\${myFeature}\\OTHERS-PLUGINS.json` : ''}`,
763
- ),
764
- h(
765
- 'div',
766
- { className: 'pau-row-d', style: { marginTop: 6 } },
767
- `上次同步:${fmtAgo(share.lastSyncAt)}${
768
- share.lastResult
769
- ? ` · 本机 ${share.lastResult.total} 个插件(副本 ${share.lastResult.bundled},${share.lastResult.totalMb} MB) · 他人 ${share.lastResult.others} 个,可装 ${share.lastResult.installable}`
770
- : ''
771
- }`,
772
- ),
773
- share.lastError
774
- ? h('div', { className: 'pau-banner pau-banner-warn', style: { marginTop: 8 } }, `上次同步失败:${share.lastError}`)
775
- : null,
776
- h(
777
- 'div',
778
- { className: 'pau-actions', style: { marginTop: 12 } },
779
- h(
780
- 'button',
781
- {
782
- className: 'pau-btn pau-btn-primary',
783
- disabled: !!busy,
784
- onClick: () =>
785
- run('share-sync', async () => {
786
- const next = await call('/share/sync', {});
787
- const result = next.result || {};
788
- if (result && result.ok === false) {
789
- setNote(`同步失败:${result.error || '未知错误'}`);
790
- } else {
791
- const counts = (result.mine && result.mine.counts) || {};
792
- const others2 = (result.aggregate && result.aggregate.counts) || {};
793
- setNote(
794
- `同步完成:本机 ${counts.total || 0} 个插件(副本 ${counts.bundled || 0}),他人 ${others2.othersTotal || 0} 个,可装 ${others2.installable || 0} 个`,
795
- );
796
- }
797
- if (next.share && next.share.detail) setShareDetail(next.share.detail);
798
- return next;
799
- }),
800
- },
801
- busy === 'share-sync' ? '同步中…' : '立即同步一次',
802
- ),
803
- h(
804
- 'button',
805
- {
806
- className: 'pau-btn',
807
- disabled: !!busy,
808
- onClick: () => run('share-reload', async () => {
809
- await loadShareDetail();
810
- setNote('已重新读取特征文件夹');
811
- return null;
812
- }),
813
- },
814
- '重新读取',
815
- ),
816
- ),
817
- h(
818
- 'div',
819
- { className: 'pau-card-h', style: { marginTop: 18, marginBottom: 6 } },
820
- h('h4', { className: 'pau-card-t' }, `他人插件(本机没有的 ${others.length} 个)`),
821
- h(
822
- 'span',
823
- { className: 'pau-row-d' },
824
- sources.length ? `${sources.filter((s) => !s.mine).length} 本外部特征` : '还没有拉到别人的特征',
825
- ),
826
- ),
827
- others.length
828
- ? h(
829
- 'div',
830
- { className: 'pau-scroll' },
831
- others.map((plugin) =>
832
- h(
833
- 'div',
834
- { className: 'pau-other', key: plugin.name },
835
- h(
836
- 'div',
837
- { className: 'pau-other-main' },
838
- h(
839
- 'div',
840
- { className: 'pau-row-t' },
841
- h('span', { className: 'pau-mono' }, plugin.name),
842
- plugin.version ? h('span', { className: 'pau-tag' }, `v${plugin.version}`) : null,
843
- h(
844
- 'span',
845
- {
846
- className:
847
- plugin.bundle || plugin.kind === 'registry'
848
- ? 'pau-tag pau-tag-ok'
849
- : 'pau-tag pau-tag-warn',
850
- },
851
- plugin.bundle
852
- ? '含副本,可离线装'
853
- : plugin.kind === 'registry'
854
- ? '需联网拉取'
855
- : '无副本,装不了',
856
- ),
857
- h('span', { className: 'pau-tag pau-tag-mine' }, plugin.features.join(' / ')),
858
- ),
859
- plugin.description
860
- ? h('div', { className: 'pau-row-d' }, plugin.description.slice(0, 140))
861
- : null,
862
- ),
863
- h(
864
- 'button',
865
- {
866
- className: 'pau-btn pau-btn-sm pau-btn-primary',
867
- disabled: !!busy || (!plugin.bundle && plugin.kind !== 'registry'),
868
- title: plugin.bundle ? `安装对方特征里的副本` : '从 registry 安装该版本',
869
- onClick: () =>
870
- run(`share-install:${plugin.name}`, async () => {
871
- const next = await call('/share/install', { name: plugin.name });
872
- setNote((next.result && next.result.message) || `${plugin.name} 已安装`);
873
- if (next.share && next.share.detail) setShareDetail(next.share.detail);
874
- return next;
875
- }),
876
- },
877
- busy === `share-install:${plugin.name}` ? '安装中…' : '安装',
878
- ),
879
- ),
880
- ),
881
- )
882
- : h(
883
- 'div',
884
- { className: 'pau-empty' },
885
- sources.length ? '他人清单里的插件本机都有了' : '把别人的特征文件夹直接放进 插件库\\ 后点「重新读取」',
886
- ),
887
- installedFromOthers.length
888
- ? h(
889
- 'div',
890
- { className: 'pau-row-d', style: { marginTop: 10 } },
891
- `已从共享装上 ${installedFromOthers.length} 个:${installedFromOthers
892
- .map((p) => p.name)
893
- .slice(0, 8)
894
- .join('、')}${installedFromOthers.length > 8 ? ' 等' : ''}`,
895
- )
896
- : null,
897
- (detail && detail.warnings && detail.warnings.length) || (detail && detail.myManifest && detail.myManifest.skipped && detail.myManifest.skipped.length)
898
- ? h(
899
- 'div',
900
- { className: 'pau-row-d', style: { marginTop: 10, color: 'var(--dsw-alias-warning,#a16207)' } },
901
- [
902
- ...((detail && detail.warnings) || []),
903
- ...((detail && detail.myManifest && detail.myManifest.skipped) || []).map(
904
- (item) => `${item.name}:${item.reason}`,
905
- ),
906
- ].slice(0, 5),
907
- )
908
- : null,
909
- );
910
661
 
911
662
  const historyCard = h(
912
663
  'div',
@@ -939,7 +690,6 @@ function makePanel(React) {
939
690
  masterCard,
940
691
  policyCard,
941
692
  pluginsCard,
942
- shareCard,
943
693
  restartCard,
944
694
  historyCard,
945
695
  );
package/index.js CHANGED
@@ -38,8 +38,6 @@ import { backupProfile, installSpec, installVersion } from './lib/updater.js';
38
38
  import { gracefulHost, scheduleRestart } from './lib/restart.js';
39
39
  import { createUninstall } from './lib/uninstall.js';
40
40
  import { createApi } from './lib/api.js';
41
- import { installSharedPlugin, libraryRoot, shareSnapshot, syncMyFeature, writeAggregate } from './lib/share.js';
42
- import { resolveIdentity } from './lib/machine.js';
43
41
  import fs from 'node:fs';
44
42
  import path from 'node:path';
45
43
 
@@ -259,133 +257,6 @@ export function apply(ctx, config = {}) {
259
257
  return '';
260
258
  };
261
259
 
262
- /* -------------------------------------------------------------- 共享 */
263
-
264
- /** 共享相关的设置(目录、特征名)从状态里读,面板改完立即生效。 */
265
- const shareConfig = () => {
266
- const state = loadState();
267
- return {
268
- libraryRoot: state.shareLibraryRoot || '',
269
- featureName: state.shareFeatureName || '',
270
- // 已固定的特征值必须原样传回:不传就会重新推导,目录会漂移
271
- machineHash: state.shareMachineHash || '',
272
- publishedNames: state.publishedNames || [],
273
- };
274
- };
275
-
276
- /**
277
- * 把本机标识固定进状态。
278
- *
279
- * 规则与会话同步插件一致:第一次确定后永久不变。所以只有 state 里没有特征值时
280
- * 才解析一次并落盘;已有值绝不重算(哪怕主机名改了、换用户名了)。
281
- */
282
- const ensureIdentityFixed = () => {
283
- const state = loadState();
284
- if (state.shareMachineHash) return state.shareMachineHash;
285
- const identity = resolveIdentity({ name: state.shareFeatureName });
286
- const next = loadState();
287
- next.shareMachineHash = identity.machineHash;
288
- next.shareHashFixedAt = Date.now();
289
- if (!next.shareFeatureName) next.shareFeatureName = identity.machineName;
290
- saveState(next);
291
- log(`本机标识已固定:${identity.machineId}(特征值来源:${identity.hashSource})`);
292
- return identity.machineHash;
293
- };
294
-
295
- let sharing = false;
296
-
297
- /**
298
- * 同步我自己的特征文件夹 + 汇集他人清单。
299
- * 巡检每轮跑一次;面板上的「立即同步」也走这里。
300
- */
301
- const runShareSync = async ({ reason } = {}) => {
302
- const state = loadState();
303
- if (!state.shareEnabled && reason !== '手动') {
304
- return { ok: true, skipped: '共享同步已关闭' };
305
- }
306
- if (sharing) return { ok: false, error: '正在同步中' };
307
- sharing = true;
308
- try {
309
- // 同步前先把本机标识钉死:第一次运行时确定,之后永久不变
310
- ensureIdentityFixed();
311
- const config = shareConfig();
312
- const mine = await syncMyFeature({
313
- config,
314
- publishedNames: config.publishedNames,
315
- log,
316
- });
317
- const aggregate = writeAggregate({
318
- config,
319
- publishedNames: config.publishedNames,
320
- log,
321
- });
322
- const next = loadState();
323
- next.shareLastSyncAt = Date.now();
324
- next.shareLastResult = {
325
- feature: mine.feature,
326
- root: mine.root,
327
- total: mine.counts.total,
328
- bundled: mine.counts.bundled,
329
- skipped: mine.counts.skipped,
330
- totalMb: mine.totalMb,
331
- others: aggregate.doc.counts.othersTotal,
332
- installable: aggregate.doc.counts.installable,
333
- sources: aggregate.doc.counts.sources,
334
- reason: reason || '定时',
335
- };
336
- next.shareLastError = '';
337
- pushHistory(next, {
338
- kind: 'share',
339
- reason: reason || '定时',
340
- text: `共享同步:本机 ${mine.counts.total} 个插件 / 副本 ${mine.counts.bundled};他人 ${aggregate.doc.counts.othersTotal} 个,可装 ${aggregate.doc.counts.installable}`,
341
- });
342
- saveState(next);
343
- return { ok: true, mine, aggregate: { counts: aggregate.doc.counts }, root: libraryRoot(config) };
344
- } catch (error) {
345
- const message = String(error?.message || error);
346
- const next = loadState();
347
- next.shareLastError = message;
348
- pushHistory(next, { kind: 'share-failed', text: `共享同步失败:${message}` });
349
- saveState(next);
350
- log(`共享同步失败:${message}`);
351
- return { ok: false, error: message };
352
- } finally {
353
- sharing = false;
354
- }
355
- };
356
-
357
- const shareState = () => {
358
- try {
359
- ensureIdentityFixed();
360
- } catch {
361
- /* 标识固定失败也要能看面板(会用临时值显示) */
362
- }
363
- const config = shareConfig();
364
- return shareSnapshot({ config, publishedNames: config.publishedNames });
365
- };
366
-
367
- /** 人工安装他人清单里的一个插件。 */
368
- const installFromShare = async (name) => {
369
- ensureIdentityFixed();
370
- const config = shareConfig();
371
- const result = await installSharedPlugin({
372
- ctx,
373
- config,
374
- publishedNames: config.publishedNames,
375
- name,
376
- installSpec,
377
- log,
378
- });
379
- const next = loadState();
380
- pushHistory(next, {
381
- kind: result.ok ? 'share-install' : 'share-install-failed',
382
- name,
383
- text: result.ok ? `从共享安装 ${name}:${result.message}` : `从共享安装 ${name} 失败:${result.message}`,
384
- });
385
- saveState(next);
386
- return result;
387
- };
388
-
389
260
  /* -------------------------------------------------------------- 检查 */
390
261
 
391
262
  /**
@@ -724,7 +595,6 @@ export function apply(ctx, config = {}) {
724
595
  // 两件事各自排期:自动更新按「启用 + 间隔」跑;共享同步只受它自己的开关约束。
725
596
  // 共用同一个 tick,但状态独立——用户把自动更新关掉、只想要清单同步是完全合理的。
726
597
  let updateDueAt = 0;
727
- let shareDueAt = 0;
728
598
  let booted = false;
729
599
  let lastIntervalMs = 0;
730
600
 
@@ -739,7 +609,6 @@ export function apply(ctx, config = {}) {
739
609
  booted = true;
740
610
  // 开机首跑稍晚,避开启动高峰
741
611
  updateDueAt = state.enabled ? Date.now() + 20 * 1000 : 0;
742
- shareDueAt = state.shareEnabled ? Date.now() + 40 * 1000 : 0;
743
612
  lastIntervalMs = intervalMsOf(state);
744
613
  return;
745
614
  }
@@ -764,18 +633,6 @@ export function apply(ctx, config = {}) {
764
633
  }
765
634
  }
766
635
 
767
- // ---- 共享同步:每次巡检跑一次 ----
768
- if (!state.shareEnabled) {
769
- shareDueAt = 0;
770
- } else {
771
- if (!shareDueAt) shareDueAt = Date.now() + 1000;
772
- if (Date.now() >= shareDueAt) {
773
- shareDueAt = Date.now() + intervalMs;
774
- runShareSync({ reason: '定时' }).catch((error) =>
775
- log(`共享同步失败:${error?.message || error}`),
776
- );
777
- }
778
- }
779
636
  } catch (error) {
780
637
  log(`调度失败:${error?.message || error}`);
781
638
  }
@@ -876,9 +733,6 @@ export function apply(ctx, config = {}) {
876
733
  requestRestart,
877
734
  isBusy,
878
735
  log,
879
- runShareSync,
880
- shareState,
881
- installFromShare,
882
736
  // 卸载:走宿主官方 pluginManager.removeBundle,本插件不自己改清单/包目录
883
737
  uninstall: createUninstall({
884
738
  getManager: () => {