kdczyz-dsh-model 1.1.0 → 1.1.1

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.
@@ -52,7 +52,45 @@ const PROFILE = process.env.DSH_PROFILE_DIR
52
52
  ?? process.env.DSH_PROFILE
53
53
  ?? path.join(os.homedir(), '.dsh', 'profiles', 'desktop');
54
54
  const PROFILE_NM = path.join(PROFILE, 'node_modules');
55
- const PLUGIN = path.join(PROFILE_NM, 'dsh-kdczyz-mode');
55
+
56
+ /**
57
+ * 这个包在 profile 里装成什么名字,取决于安装方式,**不能硬编码**:
58
+ *
59
+ * - 直接 `pnpm add kdczyz-dsh-model` -> node_modules/kdczyz-dsh-model
60
+ * - `pnpm add dsh-kdczyz-mode@npm:kdczyz-dsh-model`(别名,插件页安装后的形态)
61
+ * -> node_modules/dsh-kdczyz-mode
62
+ *
63
+ * 早期这里写死 `node_modules/dsh-kdczyz-mode`,于是别名安装时能跑、
64
+ * 直接安装时永远走"跳过"分支 —— 2026-10-12 真机故障(三个子插件全部未运行)
65
+ * 正是被这条跳过掩盖的。所以改成:**从 profile 的 package.json 读真实声明**,
66
+ * 读不到再退化为按目录扫描候选名。
67
+ */
68
+ function resolvePluginRoot() {
69
+ const candidates = [];
70
+ try {
71
+ const manifest = JSON.parse(fs.readFileSync(path.join(PROFILE, 'package.json'), 'utf8'));
72
+ // dsh.profile.bundles 是宿主认的唯一真相:entry name 必须与它一致
73
+ for (const name of manifest.dsh?.profile?.bundles ?? []) {
74
+ if (typeof name === 'string' && name !== 'kdczyz-dsh-model' || name?.includes('kdczyz')) {
75
+ candidates.push(name);
76
+ }
77
+ }
78
+ for (const name of Object.keys(manifest.dependencies ?? {})) {
79
+ if (name.includes('kdczyz')) candidates.push(name);
80
+ }
81
+ } catch {}
82
+ candidates.push('dsh-kdczyz-mode', 'kdczyz-dsh-model');
83
+
84
+ for (const name of [...new Set(candidates)]) {
85
+ const dir = path.join(PROFILE_NM, name);
86
+ if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
87
+ }
88
+ return undefined;
89
+ }
90
+
91
+ const PLUGIN = resolvePluginRoot();
92
+ /** 实际安装名 —— entry name 必须与它一致,否则 loader 解析不到。 */
93
+ const PLUGIN_NAME = PLUGIN === undefined ? undefined : path.basename(PLUGIN);
56
94
 
57
95
  /** 三个入口在模块顶层直接 import 的宿主内包。 */
58
96
  const REQUIRED = [
@@ -64,8 +102,9 @@ const REQUIRED = [
64
102
 
65
103
  /* —— 1) profile 里到底装没装 —— */
66
104
  console.log('profile:', PROFILE);
67
- if (!fs.existsSync(PLUGIN)) {
68
- skipped('profile 里没有 dsh-kdczyz-mode', '尚未部署到该 profile');
105
+ console.log('插件安装名:', PLUGIN_NAME ?? '(未找到)');
106
+ if (PLUGIN === undefined) {
107
+ skipped('profile 里没找到插件包', '尚未部署到该 profile');
69
108
  } else {
70
109
  for (const spec of REQUIRED) {
71
110
  const pkgName = spec.split('/').slice(0, 2).join('/');
@@ -97,32 +136,81 @@ if (!fs.existsSync(PLUGIN)) {
97
136
  }
98
137
 
99
138
  /* —— 3) 三个入口都能加载(顶层 import 全过 + 导出 apply)—— */
139
+ //
140
+ // 入口文件路径**跟随 manifest 的 exports**,不能写死 `lib/`:
141
+ // wrapper 布局(node_modules/dsh-kdczyz-mode,其内再套一层 dsh-kdczyz-mode/)
142
+ // 与裸包布局的相对深度不同,写死会在wrapper 布局下全部报「不存在」。
143
+ const manifest = JSON.parse(fs.readFileSync(path.join(PLUGIN, 'package.json'), 'utf8'));
144
+ const resolveExport = (sub) => {
145
+ const target = manifest.exports?.[sub];
146
+ return typeof target === 'string' ? path.resolve(PLUGIN, target) : undefined;
147
+ };
100
148
  const entries = [
101
- ['lib/index.js', 'llm-workbuddy-xdpool'],
102
- ['lib/trae.js', 'dsh-connect-trae'],
103
- ['lib/qoder.js', 'dsh-connect-qoder'],
149
+ ['.', 'llm-workbuddy-xdpool'],
150
+ ['./trae', 'dsh-connect-trae'],
151
+ ['./qoder', 'dsh-connect-qoder'],
104
152
  ];
105
- for (const [rel, entryId] of entries) {
106
- const file = path.join(PLUGIN, rel);
107
- if (!fs.existsSync(file)) { ok(`${rel} 存在`, false, file); continue; }
153
+ for (const [sub, entryId] of entries) {
154
+ const file = sub === '.' ? path.resolve(PLUGIN, manifest.main) : resolveExport(sub);
155
+ if (file === undefined || !fs.existsSync(file)) {
156
+ ok(`${sub} 解析到真实文件`, false, String(file));
157
+ continue;
158
+ }
108
159
  try {
109
160
  const mod = await import(pathToFileURL(file).href);
110
- ok(`${rel} 可加载且导出 apply`, typeof mod.apply === 'function',
161
+ ok(`${sub} 可加载且导出 apply`, typeof mod.apply === 'function',
111
162
  `apply=${typeof mod.apply}`);
112
163
  } catch (error) {
113
164
  // 这里失败 = 宿主里也会失败,且**宿主不会报错**,只有 provider 静默消失
114
- ok(`${rel} 可加载且导出 apply`, false, `${error.code ?? ''} ${String(error.message).split('\n')[0]}`);
165
+ ok(`${sub} 可加载且导出 apply`, false, `${error.code ?? ''} ${String(error.message).split('\n')[0]}`);
115
166
  }
116
167
  }
117
168
 
118
169
  /* —— 4) package.json 的 exports 白名单(子路径入口的前提)—— */
119
170
  try {
120
171
  const pkg = JSON.parse(fs.readFileSync(path.join(PLUGIN, 'package.json'), 'utf8'));
121
- ok('exports 声明了 ./qoder', pkg.exports?.['./qoder'] === './lib/qoder.js', String(pkg.exports?.['./qoder']));
122
- ok('exports 声明了 ./trae', typeof pkg.exports?.['./trae'] === 'string');
172
+ ok('exports 声明了 ./qoder', typeof manifest.exports?.['./qoder'] === 'string', String(manifest.exports?.['./qoder']));
173
+ ok('exports 声明了 ./trae', typeof manifest.exports?.['./trae'] === 'string');
123
174
  } catch (error) {
124
175
  ok('package.json 可读', false, error.message);
125
176
  }
177
+
178
+ /* —— 5) client bundle 唯一性(2026-10-12 启动崩溃的根因)—— */
179
+ //
180
+ // DSH 的 client-modules 对每个「声明了 ./client 的 manifest」各 emit 份
181
+ // client bundle。wrapper 与内层 manifest 若都声明了 ./client,且归一化后
182
+ // 指向**同一个物理文件**,那这份代码会被打进同一个 module scope 两次:
183
+ //
184
+ // Uncaught SyntaxError: Identifier 'XDPOOL_CLIENT_SRC' has already been declared
185
+ // -> entry 激活失败 -> web boot: 1 entry did not activate -> 应用起不来
186
+ //
187
+ // 关键在于 loader 的归属判定(client-modules 的 nearestPackage):
188
+ // - 裸名 entry expectedPackageName = 包名 -> 向上找 name 相等的 manifest
189
+ // - 子路径 entry expectedPackageName = undefined -> 向上找**第一个** manifest 即接受
190
+ // 于是裸名 entry 归wrapper、子路径 entry 归内层 —— 两个不同 packageName
191
+ // 各自声称同一个 client.js。这条断言就是拦住这个组合。
192
+ const claims = [];
193
+ for (const dir of [PLUGIN, path.join(PLUGIN, 'dsh-kdczyz-mode')]) {
194
+ const manifestPath = path.join(dir, 'package.json');
195
+ if (!fs.existsSync(manifestPath)) continue;
196
+ let pkg;
197
+ try { pkg = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); } catch { continue; }
198
+ const target = pkg.exports?.['./client'];
199
+ if (typeof target !== 'string') continue;
200
+ claims.push({
201
+ name: pkg.name,
202
+ manifest: path.relative(PROFILE_NM, manifestPath),
203
+ file: path.resolve(dir, target),
204
+ });
205
+ }
206
+ const byFile = new Map();
207
+ for (const c of claims) byFile.set(c.file, [...(byFile.get(c.file) ?? []), c.name]);
208
+ const collided = [...byFile.entries()].filter(([, names]) => names.length > 1);
209
+ ok(
210
+ '同一 client.js 只被一个 manifest 声明',
211
+ collided.length === 0,
212
+ collided.map(([f, names]) => `${path.relative(PROFILE_NM, f)} 被 ${names.join(' + ')} 同时声明`).join('; '),
213
+ );
126
214
  }
127
215
 
128
216
  console.log(`\nhost-assembly: ${pass} 通过, ${fail} 失败${skip > 0 ? `, ${skip} 跳过` : ''}`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kdczyz-dsh-model",
3
- "version": "1.1.0",
4
- "description": "Installable wrapper for the DSH plugin in dsh-kdczyz-mode/: WorkBuddy model pool + Trae connector + Qoder account pool as three independent providers, plus one settings page with six tabs (XD Pool / Trae config [provider switch, account binding, model selection, account pool] / accounts [WorkBuddy + Trae + Qoder, three columns] / Qoder pool [device-flow login, per-account credits, cooldown] / credits / usage history). This root manifest exists so `pnpm add github:kdczyz/dsh-kdczyz-mode` (DSH 插件管理页) installs a package that declares dsh.bundle.patch at its root.",
3
+ "version": "1.1.1",
4
+ "description": "Installable wrapper for the DSH plugin in dsh-kdczyz-mode/: WorkBuddy model pool + Trae connector + Qoder account pool as three independent providers, plus one settings page with six tabs (XD Pool / Trae config [provider switch, account binding, model selection, account pool] / accounts [WorkBuddy + Trae + Qoder, three columns] / Qoder pool [device-flow login, per-account credits, cooldown] / credits / usage history). This root manifest exists so the DSH plugin manager can install the package: it declares dsh.bundle.patch at its root and re-exports the inner package's entry points. It deliberately declares NO dsh.client and NO ./client export - the client half belongs to the inner dsh-kdczyz-mode package. Re-exporting ./client here would make two manifests claim the same client.js; DSH emits one client bundle per claiming package, and the duplicate makes the renderer throw 'Identifier XDPOOL_CLIENT_SRC has already been declared', which fails web boot and prevents the app from starting.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/kdczyz/kdczyz-dsh-model.git"
@@ -24,7 +24,6 @@
24
24
  ".": "./dsh-kdczyz-mode/lib/index.js",
25
25
  "./trae": "./dsh-kdczyz-mode/lib/trae.js",
26
26
  "./qoder": "./dsh-kdczyz-mode/lib/qoder.js",
27
- "./client": "./dsh-kdczyz-mode/lib/client.js",
28
27
  "./package.json": "./package.json"
29
28
  },
30
29
  "keywords": [
@@ -69,17 +68,6 @@
69
68
  "dsh": {
70
69
  "bundle": {
71
70
  "patch": "./dsh-kdczyz-mode/cordis.patch.yml"
72
- },
73
- "client": {
74
- "inject": [
75
- "@deepseek-ai/dsh-client-ui-renderer",
76
- "@deepseek-ai/dsh-client-ui-slots",
77
- "@deepseek-ai/dsh-client-locale",
78
- "@deepseek-ai/dsh-client-ui-settings",
79
- "@deepseek-ai/dsh-client-ui-settings-plugins",
80
- "@deepseek-ai/dsh-client-ui-primitives"
81
- ],
82
- "platform": "web"
83
71
  }
84
72
  }
85
73
  }