@max-null/dsh-plugin-center 0.2.11 → 0.2.13
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 +4 -4
- package/client.js +2 -2
- package/dist/engine.d.ts +6 -2
- package/dist/engine.js +25 -4
- package/dist/rpc.js +6 -3
- package/dist/toggle.d.ts +15 -1
- package/dist/toggle.js +69 -4
- package/dist/update.d.ts +9 -0
- package/dist/update.js +44 -1
- package/package.json +85 -85
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# dsh-plugin-center
|
|
2
2
|
|
|
3
|
+
This plugin belongs to the **`@max-null/*` family** — a set of plugins that together form the **[SSID (思灵 · Seek Soul in Darkness)](https://github.com/Max-Null/seek-soul-in-darkness)** desktop experience. SSID is the box that bundles them all: `dsh-capture` · `dsh-chat-rail` · `dsh-chinese-thinking` · `dsh-draft-polish` · `dsh-guardian` · `dsh-habit` · `dsh-header-unify` · `dsh-memory` · `dsh-node-appearance` · `dsh-plugin-center` · `dsh-skill-mcp-center` · `dsh-ssid-panels` · `dsh-ssid-zh-ui`.
|
|
4
|
+
|
|
5
|
+
本插件属于 **`@max-null/*` 插件系列**——这一系列共同构成 **[SSID(思灵 · Seek Soul in Darkness)](https://github.com/Max-Null/seek-soul-in-darkness)** 桌面体验。SSID 是整合它们的盒:`dsh-capture` · `dsh-chat-rail` · `dsh-chinese-thinking` · `dsh-draft-polish` · `dsh-guardian` · `dsh-habit` · `dsh-header-unify` · `dsh-memory` · `dsh-node-appearance` · `dsh-plugin-center` · `dsh-skill-mcp-center` · `dsh-ssid-panels` · `dsh-ssid-zh-ui`。
|
|
6
|
+
|
|
3
7
|
Plugin center for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`) — browse, install, and update community plugins from inside the Web UI.
|
|
4
8
|
|
|
5
9
|
插件管理中心:在 DSH Web 界面里管理已安装插件、浏览社区市场、一键安装与更新。
|
|
@@ -15,10 +19,6 @@ Plugin center for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-har
|
|
|
15
19
|
|
|
16
20
|
## Part of the SSID family / SSID 系列成员
|
|
17
21
|
|
|
18
|
-
This plugin belongs to the **`@max-null/*` family** — a set of plugins that together form the **[SSID (思灵 · Seek Soul in Darkness)](https://github.com/Max-Null/seek-soul-in-darkness)** desktop experience. SSID is the box that bundles them all: `dsh-chat-rail` · `dsh-chinese-thinking` · `dsh-draft-polish` · `dsh-guardian` · `dsh-habit` · `dsh-header-unify` · `dsh-memory` · `dsh-node-appearance` · `dsh-plugin-center` · `dsh-skill-mcp-center` · `dsh-ssid-panels` · `dsh-ssid-zh-ui`.
|
|
19
|
-
|
|
20
|
-
本插件属于 **`@max-null/*` 插件系列**——这一系列共同构成 **[SSID(思灵 · Seek Soul in Darkness)](https://github.com/Max-Null/seek-soul-in-darkness)** 桌面体验。SSID 是整合它们的盒:`dsh-chat-rail` · `dsh-chinese-thinking` · `dsh-draft-polish` · `dsh-guardian` · `dsh-habit` · `dsh-header-unify` · `dsh-memory` · `dsh-node-appearance` · `dsh-plugin-center` · `dsh-skill-mcp-center` · `dsh-ssid-panels` · `dsh-ssid-zh-ui`。
|
|
21
|
-
|
|
22
22
|
## Screenshots / 截图
|
|
23
23
|
|
|
24
24
|
| Installed / 已安装 | Market / 市场 | Updates / 更新 |
|
package/client.js
CHANGED
|
@@ -1041,9 +1041,9 @@ function CenterPanel({ variant = "section" }) {
|
|
|
1041
1041
|
const id = p.entryId;
|
|
1042
1042
|
const nextEnabled = !p.enabled;
|
|
1043
1043
|
const wasPending = pendingToggles.has(id);
|
|
1044
|
-
console.log("[plugin-center] toggle", { id, fromEnabled: p.enabled, toEnabled: nextEnabled, wasPending });
|
|
1044
|
+
console.log("[plugin-center] toggle", { id, name: p.name, fromEnabled: p.enabled, toEnabled: nextEnabled, wasPending });
|
|
1045
1045
|
setTogglingId(p.name);
|
|
1046
|
-
void rpc("toggle", { id, disabled: !nextEnabled }).then(
|
|
1046
|
+
void rpc("toggle", { id, name: p.name, disabled: !nextEnabled }).then(
|
|
1047
1047
|
(v) => {
|
|
1048
1048
|
setTogglingId(null);
|
|
1049
1049
|
const nowDisabled = typeof v === "object" && v !== null ? v.nowDisabled : null;
|
package/dist/engine.d.ts
CHANGED
|
@@ -116,8 +116,12 @@ export declare class PluginCenterEngine extends Service {
|
|
|
116
116
|
source: string;
|
|
117
117
|
}[];
|
|
118
118
|
}>;
|
|
119
|
-
/** Disable/enable one loader entry through the profile patch layer.
|
|
120
|
-
|
|
119
|
+
/** Disable/enable one loader entry through the profile patch layer.
|
|
120
|
+
* 2026-08-25 禁用失效:`dsh plugin add` 清单的 insert 子条目无 id,loader
|
|
121
|
+
* 每次启动分配随机运行时 id,按它写禁用行重启后永远匹配不到。当 patch
|
|
122
|
+
* 文件中没有 `- id: <entryId>` 行时,改用该条目的包名 name 作寻址键
|
|
123
|
+
* (setDisabled 内按 name 把 insert 子条目升级为稳定 id 后再写禁用行)。 */
|
|
124
|
+
toggle(id: string, name: string, disabled: boolean): Promise<{
|
|
121
125
|
ok: boolean;
|
|
122
126
|
detail: string;
|
|
123
127
|
nowDisabled: boolean | null;
|
package/dist/engine.js
CHANGED
|
@@ -14,7 +14,7 @@ import { buildInstalledPlugin, clearPackageCache, resolvePackage } from "./meta.
|
|
|
14
14
|
import { fetchAwesomePluginsJson, fetchDshMarketPlugins, fetchOhMyDshOverrides, fetchOhMyDshPlugins, mapConcurrent, mergePlugins, } from "./market.js";
|
|
15
15
|
import { detectUpdate, installPlugin, preparePluginUpdate, updatePlugin } from "./update.js";
|
|
16
16
|
import { reconcileInstalled, readDependencyKeys } from "./reconcile.js";
|
|
17
|
-
import { readDisabledState, setDisabled } from "./toggle.js";
|
|
17
|
+
import { readDisabledState, setDisabled, escapeRegExp } from "./toggle.js";
|
|
18
18
|
/** Runtime mirror of cordis FiberState (a cross-package const enum). */
|
|
19
19
|
const FIBER_PHASE = {
|
|
20
20
|
0: 'pending',
|
|
@@ -378,9 +378,30 @@ export class PluginCenterEngine extends Service {
|
|
|
378
378
|
installed: (await this.listInstalled()).map(p => ({ name: p.name, version: p.version, source: p.source })),
|
|
379
379
|
};
|
|
380
380
|
}
|
|
381
|
-
/** Disable/enable one loader entry through the profile patch layer.
|
|
382
|
-
|
|
383
|
-
|
|
381
|
+
/** Disable/enable one loader entry through the profile patch layer.
|
|
382
|
+
* 2026-08-25 禁用失效:`dsh plugin add` 清单的 insert 子条目无 id,loader
|
|
383
|
+
* 每次启动分配随机运行时 id,按它写禁用行重启后永远匹配不到。当 patch
|
|
384
|
+
* 文件中没有 `- id: <entryId>` 行时,改用该条目的包名 name 作寻址键
|
|
385
|
+
* (setDisabled 内按 name 把 insert 子条目升级为稳定 id 后再写禁用行)。 */
|
|
386
|
+
async toggle(id, name, disabled) {
|
|
387
|
+
// 老调用方(未透传 name 的 client)兜底:从 loader 实时取包名。
|
|
388
|
+
const entryName = name !== '' ? name : [...this.ctx.loader.entries()].find(e => e.id === id)?.options.name ?? '';
|
|
389
|
+
if (entryName === '') {
|
|
390
|
+
return { ok: false, detail: `entry "${id}" not found in loader`, nowDisabled: null };
|
|
391
|
+
}
|
|
392
|
+
// 寻址键:patch 文件中已有 `- id: <id>` 行 → 稳定 id 直接用;否则是该
|
|
393
|
+
// 条目的随机运行时 id → 只能用 name 找到它的 insert 子条目。
|
|
394
|
+
const patchId = id.replace(/^include:/u, '');
|
|
395
|
+
let key = entryName;
|
|
396
|
+
try {
|
|
397
|
+
const patchPath = join(this.baseUrl, 'cordis.patch.yml');
|
|
398
|
+
if (existsSync(patchPath)
|
|
399
|
+
&& new RegExp(`^- id: ${escapeRegExp(patchId)}$`, 'm').test(readFileSync(patchPath, 'utf8'))) {
|
|
400
|
+
key = patchId;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
catch { /* 读失败:走 name 寻址,由 setDisabled 报具体错误 */ }
|
|
404
|
+
const result = await setDisabled(this.baseUrl, key, entryName, disabled);
|
|
384
405
|
this.installedNamesCache = null;
|
|
385
406
|
return result;
|
|
386
407
|
}
|
package/dist/rpc.js
CHANGED
|
@@ -55,11 +55,14 @@ export class PluginCenterRpc extends Service {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
case 'toggle': {
|
|
58
|
-
const
|
|
59
|
-
const
|
|
58
|
+
const payload2 = payload;
|
|
59
|
+
const id = payload2?.id;
|
|
60
|
+
const name = payload2?.name;
|
|
61
|
+
const disabled = payload2?.disabled;
|
|
60
62
|
if (typeof id !== 'string' || id === '')
|
|
61
63
|
return internal('toggle: id is required');
|
|
62
|
-
|
|
64
|
+
// name 用于无稳定 id 条目的 seek-by-name 寻址(2026-08-25 禁用失效修复)。
|
|
65
|
+
const result = await ctx.pluginCenter.toggle(id, typeof name === 'string' ? name : '', disabled === true);
|
|
63
66
|
if (!result.ok)
|
|
64
67
|
return internal(`toggle ${id} 失败:${result.detail}`);
|
|
65
68
|
return { ok: true, value: { nowDisabled: result.nowDisabled } };
|
package/dist/toggle.d.ts
CHANGED
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
* followed by an optional `disabled:` line), serialized so concurrent
|
|
11
11
|
* toggles cannot interleave a read-modify-write, refused when the file is
|
|
12
12
|
* not a plain entry list, and protected for host-infrastructure rows.
|
|
13
|
+
*
|
|
14
|
+
* Stable ids: `dsh plugin add` install lists mount entries as id-less
|
|
15
|
+
* `insert` children (`- name: X`), which the Loader gives a RANDOM runtime
|
|
16
|
+
* id on every boot (cordis-plugin-loader ensureId). Toggling by that
|
|
17
|
+
* runtime id writes a row no later boot matches (applyEntryPatches warns
|
|
18
|
+
* and skips) — the 2026-08-25 disable-broken bug. When no `- id:` row
|
|
19
|
+
* matches, this module addresses the entry by `name` instead: the id-less
|
|
20
|
+
* insert child is upgraded in place to a stable `- id: X` so the appended
|
|
21
|
+
* disable row actually hits. It never guesses: no match = refused write.
|
|
13
22
|
*/
|
|
14
23
|
export interface ToggleResult {
|
|
15
24
|
ok: boolean;
|
|
@@ -19,13 +28,18 @@ export interface ToggleResult {
|
|
|
19
28
|
}
|
|
20
29
|
/** What the user patch layer currently says about every row id. */
|
|
21
30
|
export declare function readDisabledState(patchPath: string): Map<string, boolean>;
|
|
31
|
+
/** Escape a literal for use inside a RegExp (plugin names may contain `.` etc.). */
|
|
32
|
+
export declare function escapeRegExp(text: string): string;
|
|
22
33
|
/**
|
|
23
34
|
* Set one entry's disabled stance in the profile patch layer. The file is
|
|
24
35
|
* only touched when the stance changes; a malformed file (not a plain
|
|
25
36
|
* entry list) is reported instead of being made worse.
|
|
26
37
|
* @param profileDir - the profile directory holding cordis.patch.yml.
|
|
27
38
|
* @param id - the loader entry id to toggle.
|
|
39
|
+
* @param name - the entry's package name; used as the addressing key when
|
|
40
|
+
* `id` is a Loader-assigned random runtime id with no `- id:` row in the
|
|
41
|
+
* patch file (id-less insert children of `dsh plugin add` lists).
|
|
28
42
|
* @param disabled - the target stance.
|
|
29
43
|
* @returns the outcome; `nowDisabled` mirrors the stance or null when refused.
|
|
30
44
|
*/
|
|
31
|
-
export declare function setDisabled(profileDir: string, entryId: string, disabled: boolean): Promise<ToggleResult>;
|
|
45
|
+
export declare function setDisabled(profileDir: string, entryId: string, name: string, disabled: boolean): Promise<ToggleResult>;
|
package/dist/toggle.js
CHANGED
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
* followed by an optional `disabled:` line), serialized so concurrent
|
|
11
11
|
* toggles cannot interleave a read-modify-write, refused when the file is
|
|
12
12
|
* not a plain entry list, and protected for host-infrastructure rows.
|
|
13
|
+
*
|
|
14
|
+
* Stable ids: `dsh plugin add` install lists mount entries as id-less
|
|
15
|
+
* `insert` children (`- name: X`), which the Loader gives a RANDOM runtime
|
|
16
|
+
* id on every boot (cordis-plugin-loader ensureId). Toggling by that
|
|
17
|
+
* runtime id writes a row no later boot matches (applyEntryPatches warns
|
|
18
|
+
* and skips) — the 2026-08-25 disable-broken bug. When no `- id:` row
|
|
19
|
+
* matches, this module addresses the entry by `name` instead: the id-less
|
|
20
|
+
* insert child is upgraded in place to a stable `- id: X` so the appended
|
|
21
|
+
* disable row actually hits. It never guesses: no match = refused write.
|
|
13
22
|
*/
|
|
14
23
|
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
15
24
|
import { join } from 'node:path';
|
|
@@ -89,6 +98,46 @@ export function readDisabledState(patchPath) {
|
|
|
89
98
|
}
|
|
90
99
|
return state;
|
|
91
100
|
}
|
|
101
|
+
/** Escape a literal for use inside a RegExp (plugin names may contain `.` etc.). */
|
|
102
|
+
export function escapeRegExp(text) {
|
|
103
|
+
return text.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Address a `dsh plugin add` install-list insert child by name: an id-less
|
|
107
|
+
* child (` - name: X`, 4-space indent under `- insert:`) gets upgraded in
|
|
108
|
+
* place to a stable-id form (` - id: X` + ` name: X`), then a
|
|
109
|
+
* `- id: X / disabled: <bool>` row is appended. A child that already carries
|
|
110
|
+
* the stable id (` - id: X`) just gets the row appended (the upgrade must
|
|
111
|
+
* not regress to a random runtime id). The insert block always stays before
|
|
112
|
+
* the appended row, so applyEntryPatches registers the id before the toggle
|
|
113
|
+
* row reads it. Returns false when nothing matches (or `name` is empty) —
|
|
114
|
+
* callers must refuse the write, never append blindly.
|
|
115
|
+
*/
|
|
116
|
+
function patchInsertChildByName(lines, name, disabled) {
|
|
117
|
+
if (name === '')
|
|
118
|
+
return false;
|
|
119
|
+
const idPattern = new RegExp(`^ {4}- id: ${escapeRegExp(name)}$`, 'u');
|
|
120
|
+
const namePattern = new RegExp(`^ {4}- name: ${escapeRegExp(name)}$`, 'u');
|
|
121
|
+
let found = false;
|
|
122
|
+
for (let i = 0; i < lines.length; i++) {
|
|
123
|
+
if (namePattern.test(lines[i])) {
|
|
124
|
+
// 无 id 子条目 → 原地升级为稳定 id(4 空格 + 6 空格 name)。
|
|
125
|
+
lines[i] = ` - id: ${name}\n name: ${name}`;
|
|
126
|
+
found = true;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
if (idPattern.test(lines[i])) {
|
|
130
|
+
// 已是稳定 id 子条目 → 无需升级,直接追加禁用行。
|
|
131
|
+
found = true;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (!found)
|
|
136
|
+
return false;
|
|
137
|
+
const tail = lines.length > 0 && lines[lines.length - 1] !== '' ? '\n' : '';
|
|
138
|
+
lines.push(`${tail}- id: ${name}\n disabled: ${String(disabled)}`);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
92
141
|
/** Serialize toggles so concurrent writes cannot interleave. */
|
|
93
142
|
let toggleChain = Promise.resolve();
|
|
94
143
|
/**
|
|
@@ -97,10 +146,13 @@ let toggleChain = Promise.resolve();
|
|
|
97
146
|
* entry list) is reported instead of being made worse.
|
|
98
147
|
* @param profileDir - the profile directory holding cordis.patch.yml.
|
|
99
148
|
* @param id - the loader entry id to toggle.
|
|
149
|
+
* @param name - the entry's package name; used as the addressing key when
|
|
150
|
+
* `id` is a Loader-assigned random runtime id with no `- id:` row in the
|
|
151
|
+
* patch file (id-less insert children of `dsh plugin add` lists).
|
|
100
152
|
* @param disabled - the target stance.
|
|
101
153
|
* @returns the outcome; `nowDisabled` mirrors the stance or null when refused.
|
|
102
154
|
*/
|
|
103
|
-
export function setDisabled(profileDir, entryId, disabled) {
|
|
155
|
+
export function setDisabled(profileDir, entryId, name, disabled) {
|
|
104
156
|
const run = toggleChain.then(async () => {
|
|
105
157
|
// Loader runtime ids (include:<id>) never match patch composition —
|
|
106
158
|
// always address rows by their original patch id.
|
|
@@ -157,9 +209,22 @@ export function setDisabled(profileDir, entryId, disabled) {
|
|
|
157
209
|
out.push(line);
|
|
158
210
|
}
|
|
159
211
|
if (!patched) {
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
212
|
+
// 2026-08-25 禁用失效根因:id-less insert 子条目每次启动拿随机运行时
|
|
213
|
+
// id,按它追加的禁用行重启后永远匹配不到(applyEntryPatches warn 后
|
|
214
|
+
// 静默跳过)。所以这里绝不静默追加:先按 name 寻址 insert 子条目行,
|
|
215
|
+
// 原地升级为稳定 id 再追加;都找不到 → 拒绝(不写文件)。
|
|
216
|
+
if (!patchInsertChildByName(out, name, disabled)) {
|
|
217
|
+
if (/^[0-9a-f]{8}$/u.test(id)) {
|
|
218
|
+
return {
|
|
219
|
+
ok: false,
|
|
220
|
+
detail: `entry "${id}" has no stable patch id (random runtime id); ` +
|
|
221
|
+
'edit cordis.patch.yml to give its insert child an explicit id',
|
|
222
|
+
nowDisabled: null,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return { ok: false, detail: `no patch row or insert child matches "${id}"`, nowDisabled: null };
|
|
226
|
+
}
|
|
227
|
+
patched = true;
|
|
163
228
|
}
|
|
164
229
|
try {
|
|
165
230
|
writeFileSync(patchPath, out.join('\n') + '\n');
|
package/dist/update.d.ts
CHANGED
|
@@ -64,6 +64,15 @@ export declare function logPnpm(profileDir: string, args: readonly string[], res
|
|
|
64
64
|
* CreateProcess 只找 pnpm.exe(.cmd/.ps1 必须经 shell)——2026-08-17 实测
|
|
65
65
|
* spawn('pnpm', shell:false) 直接 ENOENT,更新永远假成功。 */
|
|
66
66
|
export declare function pnpmCandidates(): string[];
|
|
67
|
+
/** Wrap a bundled pnpm CLI path into a runnable command line. SSID_PNPM
|
|
68
|
+
* (SSiD 捆绑 pnpm) points at `pnpm.cjs` — a node script. On Windows,
|
|
69
|
+
* spawning it directly through `shell: true` makes cmd hand the .cjs to
|
|
70
|
+
* its file association (ShellExecute): cmd returns exit 0 immediately and
|
|
71
|
+
* node never runs (2026-08-25 another machine: 142ms fake success, npm
|
|
72
|
+
* add 未生效; output redirection test produced a 0-byte file). Non-.cjs
|
|
73
|
+
* paths (e.g. pnpm.exe) are used as-is.
|
|
74
|
+
*/
|
|
75
|
+
export declare function pnpmExecCommand(bundled: string): string;
|
|
67
76
|
/** 归档 profile 的 node_modules 由 pnpm `<major>` 生成(SSiD 部署时把构建机
|
|
68
77
|
* store 元数据改写成本机路径且保留 major 后缀——shell/main.mjs rewire)。
|
|
69
78
|
* 若执行机全局 pnpm 是另一个 major(常见:机器装 pnpm 10,归档是 pnpm 11
|
package/dist/update.js
CHANGED
|
@@ -185,6 +185,32 @@ export function pnpmCandidates() {
|
|
|
185
185
|
}
|
|
186
186
|
return commands;
|
|
187
187
|
}
|
|
188
|
+
/** One node executable that can run pnpm scripts.
|
|
189
|
+
* SSiD 注入的 SSID_MCP_NODE(与 SSID_PNPM 同模式注入,存在即用)→ 本进程
|
|
190
|
+
* execPath(官方 dsh 是 node 进程)→ PATH 的 node。 */
|
|
191
|
+
function nodeCandidate() {
|
|
192
|
+
const fromEnv = process.env.SSID_MCP_NODE;
|
|
193
|
+
if (fromEnv !== undefined && fromEnv !== '')
|
|
194
|
+
return fromEnv;
|
|
195
|
+
const exe = process.execPath;
|
|
196
|
+
if (/node(?:\.exe)?$/i.test(exe))
|
|
197
|
+
return exe;
|
|
198
|
+
return 'node';
|
|
199
|
+
}
|
|
200
|
+
/** Wrap a bundled pnpm CLI path into a runnable command line. SSID_PNPM
|
|
201
|
+
* (SSiD 捆绑 pnpm) points at `pnpm.cjs` — a node script. On Windows,
|
|
202
|
+
* spawning it directly through `shell: true` makes cmd hand the .cjs to
|
|
203
|
+
* its file association (ShellExecute): cmd returns exit 0 immediately and
|
|
204
|
+
* node never runs (2026-08-25 another machine: 142ms fake success, npm
|
|
205
|
+
* add 未生效; output redirection test produced a 0-byte file). Non-.cjs
|
|
206
|
+
* paths (e.g. pnpm.exe) are used as-is.
|
|
207
|
+
*/
|
|
208
|
+
export function pnpmExecCommand(bundled) {
|
|
209
|
+
if (!/\.(cjs|mjs|js)$/i.test(bundled))
|
|
210
|
+
return bundled;
|
|
211
|
+
const node = nodeCandidate();
|
|
212
|
+
return node === null ? bundled : `"${node}" "${bundled}"`;
|
|
213
|
+
}
|
|
188
214
|
/** 归档 profile 的 node_modules 由 pnpm `<major>` 生成(SSiD 部署时把构建机
|
|
189
215
|
* store 元数据改写成本机路径且保留 major 后缀——shell/main.mjs rewire)。
|
|
190
216
|
* 若执行机全局 pnpm 是另一个 major(常见:机器装 pnpm 10,归档是 pnpm 11
|
|
@@ -214,7 +240,7 @@ export function pnpmCommandCandidates(profileDir) {
|
|
|
214
240
|
const commands = [];
|
|
215
241
|
const bundled = process.env.SSID_PNPM;
|
|
216
242
|
if (bundled !== undefined && bundled !== '')
|
|
217
|
-
commands.push(bundled);
|
|
243
|
+
commands.push(pnpmExecCommand(bundled));
|
|
218
244
|
commands.push(...pnpmCandidates());
|
|
219
245
|
const major = detectStoreMajor(profileDir);
|
|
220
246
|
if (major !== undefined) {
|
|
@@ -424,6 +450,23 @@ export async function updatePlugin(packageName, version, profileDir) {
|
|
|
424
450
|
const pkgDir = join(profileDir, 'node_modules', packageName);
|
|
425
451
|
const before = snapshotHostFiles(pkgDir);
|
|
426
452
|
const result = await runPnpm(['add', '-w', `${packageName}@${version}`], profileDir);
|
|
453
|
+
if (result.ok) {
|
|
454
|
+
// no-op 识破:pnpm add 可能 exit 0 但什么都不装(2026-08-25 SSiD 内实测:
|
|
455
|
+
// 连续三次 142ms 静默成功、spec/版本均未变,hot 判定把「没变化」误报成
|
|
456
|
+
// 「已热生效」)。逐一核对已安装版本,不一致即报真实错误并附手动命令,
|
|
457
|
+
// 而不是继续走 hot / 待重启链路。
|
|
458
|
+
let installed = null;
|
|
459
|
+
try {
|
|
460
|
+
const pkg = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8'));
|
|
461
|
+
installed = typeof pkg.version === 'string' ? pkg.version : null;
|
|
462
|
+
}
|
|
463
|
+
catch { /* 包目录缺失:视为未生效 */ }
|
|
464
|
+
if (installed !== version) {
|
|
465
|
+
result.ok = false;
|
|
466
|
+
result.detail = `pnpm add 未生效(exit 0,安装版本仍为 ${installed ?? '缺失'})。若重试仍失败,请在 profile 目录(${profileDir})手动执行:pnpm add -w ${packageName}@${version}`;
|
|
467
|
+
return result;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
427
470
|
if (result.ok && before !== null && before.length > 0) {
|
|
428
471
|
const after = snapshotHostFiles(pkgDir);
|
|
429
472
|
if (after !== null && !hostFilesChanged(before, after)) {
|
package/package.json
CHANGED
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@max-null/dsh-plugin-center",
|
|
3
|
+
"version": "0.2.13",
|
|
4
|
+
"description": "Plugin center for DeepSeek Harness 閳?installed metadata, community market, update detection, and What's New",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./client": "./client.js",
|
|
18
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"client.js",
|
|
24
|
+
"cordis.patch.yml",
|
|
25
|
+
"assets"
|
|
26
|
+
],
|
|
27
|
+
"dsh": {
|
|
28
|
+
"bundle": {
|
|
29
|
+
"patch": "./cordis.patch.yml"
|
|
30
|
+
},
|
|
31
|
+
"client": {
|
|
32
|
+
"inject": [
|
|
33
|
+
"@deepseek-ai/dsh-client-connection",
|
|
34
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
35
|
+
"@deepseek-ai/dsh-client-locale",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-settings",
|
|
37
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
38
|
+
"@deepseek-ai/dsh-client-ui-slots",
|
|
39
|
+
"@deepseek-ai/dsh-api-remotes"
|
|
40
|
+
],
|
|
41
|
+
"platform": "web",
|
|
42
|
+
"immediately": true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"deepseek",
|
|
47
|
+
"harness",
|
|
48
|
+
"dsh",
|
|
49
|
+
"dsh-plugin",
|
|
50
|
+
"deepseek-harness",
|
|
51
|
+
"plugin",
|
|
52
|
+
"market",
|
|
53
|
+
"update",
|
|
54
|
+
"cordis"
|
|
55
|
+
],
|
|
56
|
+
"repository": {
|
|
57
|
+
"type": "git",
|
|
58
|
+
"url": "git+https://github.com/Max-Null/dsh-plugin-center.git"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"build": "tsc -p tsconfig.json && node build-client.mjs",
|
|
62
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"js-yaml": "^4.1.0"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
69
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
70
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.1-rc.1",
|
|
71
|
+
"@deepseek-ai/dsh-host-apiproxy": "^0.1.1-rc.1"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
75
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
76
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.1-rc.1",
|
|
77
|
+
"@deepseek-ai/dsh-host-apiproxy": "^0.1.1-rc.1",
|
|
78
|
+
"@types/js-yaml": "^4.0.9",
|
|
79
|
+
"@types/node": "^26.2.0",
|
|
80
|
+
"@types/react": "~18.3.1",
|
|
81
|
+
"esbuild": "^0.24.0",
|
|
82
|
+
"react": "^18.2.0",
|
|
83
|
+
"typescript": "^5.5.0"
|
|
84
|
+
}
|
|
85
|
+
}
|