@ljwei-stak/dsh-model-router 0.17.1 → 0.18.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/.dsh-plugin/client.js +244 -178
- package/.dsh-plugin/index.mjs +1 -1
- package/.dsh-plugin/official-tools-remote-service.mjs +46 -10
- package/.dsh-plugin/shared/official-tool-executor.mjs +135 -47
- package/.dsh-plugin/shared/official-tool-registry.mjs +113 -150
- package/.dsh-plugin/shared/official-tools-runtime.mjs +419 -222
- package/.dsh-plugin/shared/tool-install-preferences.mjs +77 -73
- package/.dsh-plugin/shared/vendor-mimo-grok-adapter.mjs +41 -16
- package/CHANGELOG.md +11 -1
- package/README.md +17 -8
- package/README.zh.md +17 -8
- package/docs/INSTALLATION_GUIDE.zh.md +14 -14
- package/docs/WORKBENCH_USER_GUIDE.zh.md +27 -5
- package/package.json +2 -2
|
@@ -113,9 +113,9 @@ function keepKnownToolIds(raw, known, subject) {
|
|
|
113
113
|
* Throws with a user-facing message on the first bad value, so the settings
|
|
114
114
|
* form and the installer fail the same way.
|
|
115
115
|
*/
|
|
116
|
-
export function parseInstallPreferences(config = {}, { tools = [] } = {}) {
|
|
116
|
+
export function parseInstallPreferences(config = {}, { tools = [], home = '', env = {} } = {}) {
|
|
117
117
|
const known = new Set(tools.map(tool => tool.id))
|
|
118
|
-
const installDir = expandInstallDir(valueOf(config, 'toolInstallDir', ''))
|
|
118
|
+
const installDir = expandInstallDir(valueOf(config, 'toolInstallDir', ''), { home, env })
|
|
119
119
|
const registry = normalizeSourceUrl(valueOf(config, 'toolNpmRegistry', DEFAULT_NPM_REGISTRY), 'npm 源') || DEFAULT_NPM_REGISTRY
|
|
120
120
|
const methodRaw = keepKnownToolIds(
|
|
121
121
|
parseJsonObject(valueOf(config, 'toolInstallMethodsJson', '{}'), '安装方式覆盖'), known, '安装方式覆盖')
|
|
@@ -126,9 +126,11 @@ export function parseInstallPreferences(config = {}, { tools = [] } = {}) {
|
|
|
126
126
|
for (const [id, methodId] of Object.entries(methodRaw)) methods[id] = methodId.trim()
|
|
127
127
|
for (const [id, url] of Object.entries(scriptRaw)) scriptUrls[id] = normalizeSourceUrl(url, `${id} 安装脚本源`)
|
|
128
128
|
for (const [id, methodId] of Object.entries(methods)) {
|
|
129
|
-
|
|
129
|
+
const selected = installMethodFor({ id, installMethods: tools.find(tool => tool.id === id)?.installMethods }, methodId)
|
|
130
|
+
if (!selected) {
|
|
130
131
|
throw new Error(`${id} 不支持安装方式 ${methodId}。`)
|
|
131
132
|
}
|
|
133
|
+
methods[id] = selected.id
|
|
132
134
|
}
|
|
133
135
|
return {
|
|
134
136
|
installDir,
|
|
@@ -141,26 +143,46 @@ export function parseInstallPreferences(config = {}, { tools = [] } = {}) {
|
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
/** Where a script install puts the binary: the user's directory, else the vendor default. */
|
|
144
|
-
export function scriptInstallDir(tool, preferences, { home = '',
|
|
145
|
-
|
|
146
|
-
|
|
146
|
+
export function scriptInstallDir(tool, preferences, { home = '', env = {}, method } = {}) {
|
|
147
|
+
const binaryDir = vendorScriptDir(tool, { home, env, method })
|
|
148
|
+
if (!binaryDir) return ''
|
|
149
|
+
if (preferences?.installDir) return method?.installDirSubdir ? joinPath(preferences.installDir, method.installDirSubdir) : preferences.installDir
|
|
150
|
+
// Environment-only installers take a root and then add their own bin suffix.
|
|
151
|
+
const suffix = String(method?.installDirSuffix ?? '')
|
|
152
|
+
return suffix && new RegExp(`[\\\\/]${suffix}$`).test(binaryDir) ? binaryDir.slice(0, -suffix.length - 1) : binaryDir
|
|
147
153
|
}
|
|
148
154
|
|
|
149
|
-
|
|
150
|
-
|
|
155
|
+
/** The location the vendor script uses on its own, ignoring any configured directory. */
|
|
156
|
+
export function vendorScriptDir(tool, { home = '', env = {}, method } = {}) {
|
|
157
|
+
const value = method?.defaultScriptInstallDir ?? tool?.defaultScriptInstallDir
|
|
158
|
+
const root = method?.defaultScriptInstallDirEnv ? env[method.defaultScriptInstallDirEnv] || home : home
|
|
159
|
+
return value ? joinPath(root, ...String(value).split('/').filter(Boolean)) : ''
|
|
151
160
|
}
|
|
152
161
|
|
|
153
|
-
/** The
|
|
154
|
-
export function
|
|
155
|
-
return
|
|
162
|
+
/** The directory containing the package manager's global command shims. */
|
|
163
|
+
export function packageManagerBinDir(installDir, { platform = 'linux' } = {}) {
|
|
164
|
+
return installDir ? (platform === 'win32' ? installDir : joinPath(installDir, 'bin')) : ''
|
|
156
165
|
}
|
|
157
166
|
|
|
158
|
-
|
|
159
|
-
|
|
167
|
+
function packageManagerDirectoryArgs(method, installDir, options) {
|
|
168
|
+
if (!installDir) return []
|
|
169
|
+
if (method.manager !== 'pnpm') return ['--prefix', installDir]
|
|
170
|
+
// pnpm does not implement npm's global --prefix layout. Its package tree and
|
|
171
|
+
// bin directory are separate settings; the config namespace works for add,
|
|
172
|
+
// install, remove and bin commands across pnpm 10.
|
|
173
|
+
return [
|
|
174
|
+
`--config.global-dir=${joinPath(installDir, '.pnpm', 'global')}`,
|
|
175
|
+
`--config.global-bin-dir=${packageManagerBinDir(installDir, options)}`,
|
|
176
|
+
`--config.store-dir=${joinPath(installDir, '.pnpm', 'store')}`,
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** argv for a package-manager method, honouring its actual global layout. */
|
|
181
|
+
export function packageManagerArgs(method, preferences, { installDir = '', platform = 'linux' } = {}) {
|
|
160
182
|
const registry = preferences?.registry || DEFAULT_NPM_REGISTRY
|
|
161
183
|
const args = method.installArgs.filter(arg => !arg.startsWith('--registry='))
|
|
162
184
|
args.push(`--registry=${registry}`)
|
|
163
|
-
|
|
185
|
+
args.push(...packageManagerDirectoryArgs(method, installDir, { platform }))
|
|
164
186
|
return [...args]
|
|
165
187
|
}
|
|
166
188
|
|
|
@@ -171,6 +193,13 @@ function baseUrlOf(scriptUrl) {
|
|
|
171
193
|
} catch { return '' }
|
|
172
194
|
}
|
|
173
195
|
|
|
196
|
+
/** MiniMax's official installer itself accepts exactly these npm registries. */
|
|
197
|
+
export function minimaxScriptRegistry(registry) {
|
|
198
|
+
const raw = String(registry ?? '').trim()
|
|
199
|
+
const normalized = raw && !raw.endsWith('/') ? `${raw}/` : raw
|
|
200
|
+
return normalized === 'https://registry.npmjs.org/' || normalized === 'https://registry.npmmirror.com/' ? normalized : ''
|
|
201
|
+
}
|
|
202
|
+
|
|
174
203
|
/**
|
|
175
204
|
* The method to use: the requested one, else the user's saved choice, else the
|
|
176
205
|
* registry default — and if none of those run on this platform, the first
|
|
@@ -181,14 +210,11 @@ function pickMethod(tool, methodId, preferences, platform) {
|
|
|
181
210
|
const requested = methodId ?? preferences?.methods?.[tool.id]
|
|
182
211
|
if (!requested) {
|
|
183
212
|
const available = installMethodsFor(tool).filter(method => !installMethodBlockReason(method, platform))
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
if (powershell) return powershell
|
|
190
|
-
}
|
|
191
|
-
return available[0] ?? defaultInstallMethod(tool)
|
|
213
|
+
const allowed = method => !installMethodBlockReason(method, platform)
|
|
214
|
+
&& !(method.kind === 'script' && preferences?.allowScriptInstall === false)
|
|
215
|
+
const platformDefault = installMethodFor(tool, tool.defaultMethodsByPlatform?.[platform])
|
|
216
|
+
if (platformDefault && allowed(platformDefault)) return platformDefault
|
|
217
|
+
return available.find(allowed) ?? available[0] ?? defaultInstallMethod(tool)
|
|
192
218
|
}
|
|
193
219
|
// A named method is never silently swapped. If the tool does not declare it,
|
|
194
220
|
// or it does not run here, the caller gets the reason instead of a surprise.
|
|
@@ -207,7 +233,7 @@ function unsupportedMethodError(tool) {
|
|
|
207
233
|
* One concrete plan for one tool and one method. A thrown Error means the user
|
|
208
234
|
* asked for something this tool cannot do, with a reason they can act on.
|
|
209
235
|
*/
|
|
210
|
-
export function resolveInstallPlan(tool, methodId, preferences = {}, { platform = 'linux', home = '' } = {}) {
|
|
236
|
+
export function resolveInstallPlan(tool, methodId, preferences = {}, { platform = 'linux', home = '', env = {} } = {}) {
|
|
211
237
|
if (!tool || tool.unsupported) throw new Error(tool?.unsupportedReason ?? '此工具不支持一键安装。')
|
|
212
238
|
const method = pickMethod(tool, methodId, preferences, platform)
|
|
213
239
|
if (!method) throw new Error(unsupportedMethodError(tool))
|
|
@@ -219,12 +245,14 @@ export function resolveInstallPlan(tool, methodId, preferences = {}, { platform
|
|
|
219
245
|
const installDir = preferences?.installDir || ''
|
|
220
246
|
if (method.kind === 'script') {
|
|
221
247
|
const scriptUrl = preferences?.scriptUrls?.[tool.id] || method.scriptUrl
|
|
222
|
-
const target = methodHonoursInstallDir(method) ? scriptInstallDir(tool, preferences, { home, method }) : ''
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
248
|
+
const target = methodHonoursInstallDir(method) ? scriptInstallDir(tool, preferences, { home, env, method }) : ''
|
|
249
|
+
const binDir = target
|
|
250
|
+
? (method.installDirSuffix ? joinPath(target, method.installDirSuffix) : target)
|
|
251
|
+
: vendorScriptDir(tool, { home, env, method })
|
|
252
|
+
const notices = target ? [] : installDir
|
|
253
|
+
? [`${method.label} 无法使用自定义安装目录(官方脚本写死了安装路径),将安装到 ${vendorScriptDir(tool, { home, env, method })}。`]
|
|
254
|
+
: []
|
|
255
|
+
if (method.registryEnv === 'MCODE_NPM_REGISTRY' && preferences.registry && !minimaxScriptRegistry(preferences.registry)) {
|
|
228
256
|
notices.push('MiniMax 官方脚本只接受 https://registry.npmjs.org/ 或 https://registry.npmmirror.com/,本次不改写它的 npm 源。')
|
|
229
257
|
}
|
|
230
258
|
return {
|
|
@@ -235,12 +263,12 @@ export function resolveInstallPlan(tool, methodId, preferences = {}, { platform
|
|
|
235
263
|
label: method.label,
|
|
236
264
|
shell: method.shell,
|
|
237
265
|
scriptUrl,
|
|
266
|
+
downloadUrls: preferences?.scriptUrls?.[tool.id] ? [scriptUrl] : [...new Set([scriptUrl, ...(method.scriptFallbackUrls ?? [])])],
|
|
238
267
|
verify: method.verify,
|
|
239
268
|
installArg: method.installArgs,
|
|
240
|
-
installDirEnv: method.installDirEnv || (method.env?.includes('STEP_INSTALL_DIR') ? 'STEP_INSTALL_DIR' : ''),
|
|
241
269
|
installDir: target,
|
|
242
|
-
|
|
243
|
-
env: scriptEnv(method, { installDir: target, scriptUrl, registry: preferences
|
|
270
|
+
binDir,
|
|
271
|
+
env: scriptEnv(method, { installDir: target, scriptUrl, registry: preferences.registry }),
|
|
244
272
|
notices,
|
|
245
273
|
}
|
|
246
274
|
}
|
|
@@ -251,8 +279,9 @@ export function resolveInstallPlan(tool, methodId, preferences = {}, { platform
|
|
|
251
279
|
kind: 'package-manager',
|
|
252
280
|
label: method.label,
|
|
253
281
|
manager: method.manager,
|
|
254
|
-
args: packageManagerArgs(method, preferences, { installDir }),
|
|
282
|
+
args: packageManagerArgs(method, preferences, { installDir, platform }),
|
|
255
283
|
installDir,
|
|
284
|
+
binDir: packageManagerBinDir(installDir, { platform }),
|
|
256
285
|
env: {},
|
|
257
286
|
notices: [],
|
|
258
287
|
}
|
|
@@ -267,6 +296,7 @@ export function resolveUninstallPlan(tool, methodId, preferences = {}, options =
|
|
|
267
296
|
: `${tool?.label ?? '此工具'} 没有可用的卸载方式。`)
|
|
268
297
|
const blocked = installMethodBlockReason(method, platform)
|
|
269
298
|
if (blocked) throw new Error(`${tool.label}:${blocked}`)
|
|
299
|
+
if (method.uninstallUnsupportedReason) throw new Error(method.uninstallUnsupportedReason)
|
|
270
300
|
if (method.kind === 'script') {
|
|
271
301
|
return { toolId: tool.id, method, methodId: method.id, kind: 'script', label: method.label, ...resolveInstallLocation(tool, method, preferences, options) }
|
|
272
302
|
}
|
|
@@ -280,67 +310,41 @@ export function resolveUninstallPlan(tool, methodId, preferences = {}, options =
|
|
|
280
310
|
args: [
|
|
281
311
|
...method.uninstallArgs,
|
|
282
312
|
...(preferences?.registry ? [`--registry=${preferences.registry}`] : []),
|
|
283
|
-
...(preferences?.installDir
|
|
313
|
+
...packageManagerDirectoryArgs(method, preferences?.installDir || '', { platform }),
|
|
284
314
|
],
|
|
285
315
|
installDir: preferences?.installDir || '',
|
|
316
|
+
binDir: packageManagerBinDir(preferences?.installDir || '', { platform }),
|
|
286
317
|
}
|
|
287
318
|
}
|
|
288
319
|
|
|
289
|
-
/** Paths under the install directory that an uninstall may delete. No `..`. */
|
|
290
|
-
export function scriptRelatives(tool, method) {
|
|
291
|
-
const listed = method?.binaryRelatives?.length
|
|
292
|
-
? method.binaryRelatives
|
|
293
|
-
: tool?.scriptBinaryRelatives?.length
|
|
294
|
-
? tool.scriptBinaryRelatives
|
|
295
|
-
: tool?.scriptBinaryNames
|
|
296
|
-
return [...(listed ?? [])]
|
|
297
|
-
}
|
|
298
|
-
|
|
299
320
|
/** Where the tool's binary lives, so a later uninstall can find it. */
|
|
300
|
-
export function resolveInstallLocation(tool, method, preferences = {}, { home = '' } = {}) {
|
|
321
|
+
export function resolveInstallLocation(tool, method, preferences = {}, { home = '', platform = 'linux', env = {} } = {}) {
|
|
301
322
|
if (!method) return null
|
|
302
323
|
if (method.kind === 'script') {
|
|
303
324
|
// A method that cannot honour the configured directory always lands in the
|
|
304
325
|
// vendor's own location, whatever the user typed.
|
|
305
326
|
const dir = methodHonoursInstallDir(method)
|
|
306
|
-
? scriptInstallDir(tool, preferences, { home, method })
|
|
307
|
-
: vendorScriptDir(tool, { home, method })
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
dir,
|
|
311
|
-
binaries: [...(tool.scriptBinaryNames ?? [])],
|
|
312
|
-
relatives: scriptRelatives(tool, method),
|
|
313
|
-
}
|
|
327
|
+
? scriptInstallDir(tool, preferences, { home, env, method })
|
|
328
|
+
: vendorScriptDir(tool, { home, env, method })
|
|
329
|
+
const binDir = methodHonoursInstallDir(method) && method.installDirSuffix ? joinPath(dir, method.installDirSuffix) : dir
|
|
330
|
+
return { kind: 'script', dir: binDir, installDir: dir, binaries: [...(method.scriptBinaryNames ?? tool.scriptBinaryNames ?? [])] }
|
|
314
331
|
}
|
|
315
|
-
return { kind: 'package-manager', dir: preferences?.installDir || '',
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/** MiniMax's installer rejects every registry except these two. */
|
|
319
|
-
const MINIMAX_SCRIPT_REGISTRIES = new Set([
|
|
320
|
-
'https://registry.npmjs.org/',
|
|
321
|
-
'https://registry.npmmirror.com/',
|
|
322
|
-
])
|
|
323
|
-
|
|
324
|
-
export function minimaxScriptRegistry(registry) {
|
|
325
|
-
const value = String(registry ?? '').trim()
|
|
326
|
-
if (!value) return ''
|
|
327
|
-
const normalized = value.endsWith('/') ? value : `${value}/`
|
|
328
|
-
return MINIMAX_SCRIPT_REGISTRIES.has(normalized) ? normalized : ''
|
|
332
|
+
return { kind: 'package-manager', dir: packageManagerBinDir(preferences?.installDir || '', { platform }), installDir: preferences?.installDir || '', binaries: [] }
|
|
329
333
|
}
|
|
330
334
|
|
|
331
335
|
function scriptEnv(method, { installDir, scriptUrl, registry }) {
|
|
332
336
|
const env = {}
|
|
333
|
-
|
|
334
|
-
if (
|
|
337
|
+
if (installDir && method.installDirEnv) env[method.installDirEnv] = installDir
|
|
338
|
+
if (registry && method.registryEnv) {
|
|
339
|
+
const allowed = method.registryEnv === 'MCODE_NPM_REGISTRY' ? minimaxScriptRegistry(registry) : registry
|
|
340
|
+
if (allowed) env[method.registryEnv] = allowed
|
|
341
|
+
}
|
|
342
|
+
if (installDir && method.env?.includes('STEP_INSTALL_DIR')) env.STEP_INSTALL_DIR = installDir
|
|
335
343
|
// The step installers read their release base from this variable, so a mirror
|
|
336
344
|
// URL also moves the binaries and the checksum manifest, not just the script.
|
|
337
345
|
if (method.env?.includes('STEP_RELEASE_BASE_URL') && scriptUrl) {
|
|
338
346
|
const base = baseUrlOf(scriptUrl)
|
|
339
347
|
if (base) env.STEP_RELEASE_BASE_URL = base
|
|
340
348
|
}
|
|
341
|
-
if (method.env?.includes('MCODE_NPM_REGISTRY')) {
|
|
342
|
-
const allowed = minimaxScriptRegistry(registry)
|
|
343
|
-
if (allowed) env.MCODE_NPM_REGISTRY = allowed
|
|
344
|
-
}
|
|
345
349
|
return env
|
|
346
350
|
}
|
|
@@ -17,11 +17,11 @@ import { createReadStream } from 'node:fs'
|
|
|
17
17
|
import { createHash } from 'node:crypto'
|
|
18
18
|
import { npmAttestor } from './npm-attestation.mjs'
|
|
19
19
|
import { isReleaseVersion } from './latest-versions.mjs'
|
|
20
|
-
import { readFile, realpath, stat } from 'node:fs/promises'
|
|
20
|
+
import { readFile, readdir, realpath, stat } from 'node:fs/promises'
|
|
21
21
|
import { createBrotliDecompress } from 'node:zlib'
|
|
22
22
|
import { homedir } from 'node:os'
|
|
23
23
|
import { basename, delimiter, isAbsolute, join, relative, resolve, sep } from 'node:path'
|
|
24
|
-
import { ensureNpmPrefixOnPath } from './official-tools-runtime.mjs'
|
|
24
|
+
import { ensureNpmPrefixOnPath, preferredToolDirectories } from './official-tools-runtime.mjs'
|
|
25
25
|
import { usageFromEvents } from './task-executors.mjs'
|
|
26
26
|
|
|
27
27
|
const TOOL_IDS = Object.freeze(['mimo-code', 'grok-build'])
|
|
@@ -50,18 +50,37 @@ function inside(parent, child) {
|
|
|
50
50
|
|
|
51
51
|
function packagePath(modules, name) { return join(modules, ...name.split('/')) }
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
/** npm prefixes and pnpm's versioned global tree share fixed package names. */
|
|
54
|
+
export async function installedModuleRoots(directories, { manager = null } = {}) {
|
|
55
55
|
const roots = new Set()
|
|
56
|
-
for (const raw of
|
|
56
|
+
for (const raw of directories) {
|
|
57
57
|
const directory = raw.trim().replace(/^"|"$/g, '')
|
|
58
58
|
if (!directory) continue
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
if (manager !== 'pnpm') {
|
|
60
|
+
roots.add(join(directory, 'node_modules'))
|
|
61
|
+
if (basename(directory).toLowerCase() === '.bin') roots.add(resolve(directory, '..'))
|
|
62
|
+
if (process.platform !== 'win32') roots.add(resolve(directory, '..', 'lib', 'node_modules'))
|
|
63
|
+
}
|
|
64
|
+
if (manager !== 'npm') {
|
|
65
|
+
if (/[\\/]global[\\/]\d+[\\/]node_modules[\\/]\.bin$/i.test(directory)) roots.add(resolve(directory, '..'))
|
|
66
|
+
const prefix = process.platform !== 'win32' && basename(directory).toLowerCase() === 'bin' ? resolve(directory, '..') : directory
|
|
67
|
+
for (const globalRoot of [join(prefix, '.pnpm', 'global'), join(prefix, 'global')]) {
|
|
68
|
+
try {
|
|
69
|
+
for (const entry of await readdir(globalRoot, { withFileTypes: true })) {
|
|
70
|
+
if (entry.isDirectory() && /^\d+$/.test(entry.name)) roots.add(join(globalRoot, entry.name, 'node_modules'))
|
|
71
|
+
}
|
|
72
|
+
} catch { /* this command directory has no pnpm global tree */ }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
61
75
|
}
|
|
62
76
|
return [...roots]
|
|
63
77
|
}
|
|
64
78
|
|
|
79
|
+
function entryDirectories(toolId) {
|
|
80
|
+
const preferred = preferredToolDirectories(toolId)
|
|
81
|
+
return preferred.length ? preferred : (process.env.PATH ?? process.env.Path ?? '').split(delimiter)
|
|
82
|
+
}
|
|
83
|
+
|
|
65
84
|
async function manifestAt(directory, name, version = null) {
|
|
66
85
|
try {
|
|
67
86
|
const root = await realpath(directory)
|
|
@@ -91,24 +110,29 @@ async function realFileWithin(root, candidate, workspace) {
|
|
|
91
110
|
}
|
|
92
111
|
|
|
93
112
|
async function optionalPackage(wrapper, platform, modules, workspace) {
|
|
113
|
+
const boundary = await realpath(modules).catch(() => null)
|
|
114
|
+
if (!boundary || !inside(boundary, wrapper.root) || wrapper.root === workspace || inside(workspace, wrapper.root)) return null
|
|
94
115
|
// The wrapper pins its platform package to its own version.
|
|
95
116
|
const version = wrapper.manifest.optionalDependencies?.[platform.package]
|
|
96
117
|
if (version !== wrapper.manifest.version) return null
|
|
97
118
|
const candidates = [
|
|
98
119
|
packagePath(join(wrapper.root, 'node_modules'), platform.package),
|
|
120
|
+
// pnpm links an optional dependency beside the real wrapper in the
|
|
121
|
+
// virtual store, rather than inside the wrapper's own node_modules.
|
|
122
|
+
packagePath(resolve(wrapper.root, '..', '..'), platform.package),
|
|
99
123
|
packagePath(modules, platform.package),
|
|
100
124
|
]
|
|
101
125
|
for (const candidate of candidates) {
|
|
102
126
|
const optional = await manifestAt(candidate, platform.package, version)
|
|
103
|
-
if (!optional || optional.root === workspace || inside(workspace, optional.root)) continue
|
|
127
|
+
if (!optional || !inside(boundary, optional.root) || optional.root === workspace || inside(workspace, optional.root)) continue
|
|
104
128
|
return optional
|
|
105
129
|
}
|
|
106
130
|
return null
|
|
107
131
|
}
|
|
108
132
|
|
|
109
|
-
async function findMiMoExecutable(workspace, platform, attestor, problems) {
|
|
133
|
+
async function findMiMoExecutable(workspace, platform, attestor, problems, modulesRoots) {
|
|
110
134
|
const wrapperInfo = WRAPPERS['mimo-code']
|
|
111
|
-
for (const modules of
|
|
135
|
+
for (const modules of modulesRoots) {
|
|
112
136
|
const wrapper = await manifestAt(packagePath(modules, wrapperInfo.name), wrapperInfo.name)
|
|
113
137
|
if (!wrapper || String(wrapper.manifest.bin?.[wrapperInfo.command]).replace(/^\.\//, '') !== wrapperInfo.bin) continue
|
|
114
138
|
const optional = await optionalPackage(wrapper, platform, modules, workspace)
|
|
@@ -129,9 +153,9 @@ async function grokHome() {
|
|
|
129
153
|
catch { return join(homedir(), '.grok') }
|
|
130
154
|
}
|
|
131
155
|
|
|
132
|
-
async function findGrokExecutable(workspace, platform, attestor, problems) {
|
|
156
|
+
async function findGrokExecutable(workspace, platform, attestor, problems, modulesRoots) {
|
|
133
157
|
const wrapperInfo = WRAPPERS['grok-build']
|
|
134
|
-
for (const modules of
|
|
158
|
+
for (const modules of modulesRoots) {
|
|
135
159
|
const wrapper = await manifestAt(packagePath(modules, wrapperInfo.name), wrapperInfo.name)
|
|
136
160
|
if (!wrapper || String(wrapper.manifest.bin?.[wrapperInfo.command]).replace(/^\.\//, '') !== wrapperInfo.bin) continue
|
|
137
161
|
const optional = await optionalPackage(wrapper, platform, modules, workspace)
|
|
@@ -167,7 +191,7 @@ async function findGrokExecutable(workspace, platform, attestor, problems) {
|
|
|
167
191
|
* process runner, or an unsupported reason. The Host retains its own task,
|
|
168
192
|
* workspace, model, approval, timeout, and cancellation validation.
|
|
169
193
|
*/
|
|
170
|
-
export async function resolveMiMoGrokLaunch({ toolId, workspace, mode, modelId, task, attestor = npmAttestor }) {
|
|
194
|
+
export async function resolveMiMoGrokLaunch({ toolId, workspace, mode, modelId, task, attestor = npmAttestor, directories, moduleRoots, manager = null }) {
|
|
171
195
|
if (!TOOL_IDS.includes(toolId)) return { unsupported: '未知的 MiMo/Grok 官方工具 ID。' }
|
|
172
196
|
if (process.platform !== 'win32') return { unsupported: 'MiMo/Grok 原生入口目前仅完成 Windows 适配。' }
|
|
173
197
|
if (!isAbsolute(workspace) || !['read-only', 'workspace-write'].includes(mode)) {
|
|
@@ -181,12 +205,13 @@ export async function resolveMiMoGrokLaunch({ toolId, workspace, mode, modelId,
|
|
|
181
205
|
}
|
|
182
206
|
const platform = PLATFORMS[toolId][process.arch]
|
|
183
207
|
if (!platform) return { unsupported: '当前 Windows CPU 架构没有核验的官方原生程序。' }
|
|
184
|
-
await ensureNpmPrefixOnPath()
|
|
208
|
+
if (!Array.isArray(moduleRoots) && !directories && preferredToolDirectories(toolId).length === 0) await ensureNpmPrefixOnPath()
|
|
185
209
|
const directory = await realpath(workspace)
|
|
210
|
+
const modulesRoots = Array.isArray(moduleRoots) ? moduleRoots : await installedModuleRoots(directories ?? entryDirectories(toolId), { manager })
|
|
186
211
|
const problems = []
|
|
187
212
|
const found = toolId === 'mimo-code'
|
|
188
|
-
? await findMiMoExecutable(directory, platform, attestor, problems)
|
|
189
|
-
: await findGrokExecutable(directory, platform, attestor, problems)
|
|
213
|
+
? await findMiMoExecutable(directory, platform, attestor, problems, modulesRoots)
|
|
214
|
+
: await findGrokExecutable(directory, platform, attestor, problems, modulesRoots)
|
|
190
215
|
if (!found) {
|
|
191
216
|
return { unsupported: problems.length
|
|
192
217
|
? `官方原生程序未通过 npm registry 摘要核验:${problems[0]}`
|
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.18.0 — 2026-10-12
|
|
4
|
+
|
|
5
|
+
Stable minor under the npm latest tag. Fully quit Harness, including the tray or WebUI host process, and restart after upgrading. Existing releases remain unchanged.
|
|
6
|
+
|
|
7
|
+
- Add verified vendor curl/irm channels for Kimi, Claude, Codex, MiniMax, MiMo and Grok. Gemini retains npm/pnpm, OpenCode adds its Unix script, Step uses vendor scripts, and ZCode opens its signed Windows installer. Fixed vendor paths and unsupported platform methods are explained on each row.
|
|
8
|
+
- Fix MiniMax’s official Windows installer rejection: leading PowerShell `<# ... #>` help is accepted; HTML pages and missing vendor markers are still refused. Legacy MiniMax updates preserve the discovered native install root, including attested repair-release pointers.
|
|
9
|
+
- Correct pnpm global package/bin/store directory options and retain MiniMax native dependency build flags. Updates probe the selected directory, never accept an unrelated newer PATH copy, and refuse known downgrades. Queued jobs keep their selected method, source and directory snapshot.
|
|
10
|
+
- Make shared installation settings an explicit Save/Discard form. Pending edits, saves and summary refreshes block install actions; saved unavailable methods retain their explanation. Removal stays available when future script installations are disabled.
|
|
11
|
+
- Resolve restricted execution entries from the selected installation. Claude/Codex native Windows files require publisher verification. Kimi/MiMo/Grok standalone script installations currently lack npm-file attestation and report this execution limit; npm/pnpm execution remains supported. Do not delete the Windows Codex standalone junction through generic script uninstall.
|
|
12
|
+
- Vendor downloads remain size-limited, staged and marker-checked. Windows retries through the system network/proxy transport when Node cannot retrieve an installer; the downloaded response is never executed during transport.
|
|
13
|
+
- Validation results are recorded in the release notes. Verification uses automated regressions, current official script downloads without execution, and the actual-component mock UI. No real vendor CLI installation or paid model request is part of release acceptance.
|
|
4
14
|
|
|
5
15
|
## 0.17.1 — 2026-10-11
|
|
6
16
|
|
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Model Router · DeepSeek Harness Desktop
|
|
2
2
|
|
|
3
|
-
Version: **0.
|
|
3
|
+
Version: **0.18.0**. See [routing research](docs/ROUTING_RESEARCH.zh.md), [mathematical derivation and experiments](docs/ROUTING_DERIVATION_EXPERIMENTS.zh.md), and the [LiveBench evidence audit and validation protocol](docs/ROUTING_LIVEBENCH_VALIDATION.zh.md). The latest public 2026-06-25 score/cost tables are aggregates; its paired question-quality/cost matrix has not been obtained, and no real routing gain is claimed.
|
|
4
4
|
|
|
5
5
|
A cost-aware model-routing workbench for **DeepSeek Harness Desktop**. Describe a task, compare your configured models, inspect the plan, and explicitly choose when to execute it.
|
|
6
6
|
|
|
7
|
+
**0.18.0:** expands official vendor installer choices alongside npm/pnpm, preserves the configured install destination during updates, and verifies the target copy instead of an older PATH copy. MiniMax Windows scripts beginning with PowerShell `<# ... #>` help comments are accepted; HTML error pages remain rejected. Save unified install settings before installing or updating.
|
|
8
|
+
|
|
7
9
|
**0.16.4:** PATH probes keep command output even when no install log callback is attached. 0.16.3 captured stdout only inside `onOutput?.(...)`, so `where` and `--version` looked empty and every PATH CLI was reported not-installed. MiniMax and ZCode were unaffected because they do not use that probe.
|
|
8
10
|
|
|
9
11
|
**0.16.3:** restores official-tools detection and install for every registry card. Windows probes and `npm` are spawned with separate arguments again, so cmd no longer launches Node against a missing script. A version banner still counts as installed, one card's probe error stays on that row, and a CLI that is already current is not sent through “修复官方执行入口”.
|
|
@@ -14,7 +16,7 @@ A cost-aware model-routing workbench for **DeepSeek Harness Desktop**. Describe
|
|
|
14
16
|
|
|
15
17
|
**0.16.0:** [dynamic data and adaptive feedback](docs/ROUTING_ADAPTIVE_LEARNING.zh.md) adds opt-in public benchmark/curated fixed-USD pricing refresh and task-specific, time-decayed, shrinkage-regularized subjective utility. Objective proxy quality floors remain separate. Public sources default off; preferences are shared within one local DSH_HOME and limited to the latest 200 runs. This is not a universal vendor-price scraper or evidence of real quality–cost gains.
|
|
16
18
|
|
|
17
|
-
Current version: **0.
|
|
19
|
+
Current version: **0.18.0** (`@ljwei-stak/dsh-model-router`). Its declared host compatibility is **DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2**. The router and [GAL](https://github.com/Alice-Marx/deepseek-harness-galgame) have been independent plugins since 0.12.0.
|
|
18
20
|
|
|
19
21
|
**0.15.0 changes:** a four-tab workbench with routing advice directly below task planning, protection against stale execution previews, preserved spending totals across history pruning and retries, and a standalone mock UI preview for source development.
|
|
20
22
|
|
|
@@ -31,7 +33,7 @@ For self-testing, follow the [desktop acceptance and quality–cost experiment s
|
|
|
31
33
|
| Routing presets | Economy, balanced and quality-first presets; quality floors, estimated token costs and bounded assignment search. Missing prices stay unknown. |
|
|
32
34
|
| Read-only execution | Preview and confirm a routed run from the workbench, or invoke it in an official Harness session. Supported headless CLIs and model-directory API calls return recorded results. |
|
|
33
35
|
| Editable CLI execution | One supported official CLI or a sequential CLI team, using approved isolated Git worktrees and conditional patch integration. |
|
|
34
|
-
| Official tools | Detect, install/update, check launch readiness and inspect login status for
|
|
36
|
+
| Official tools | Detect, install/update, check launch readiness and inspect login status for ten registry tools; installation jobs have logs and cancellation. |
|
|
35
37
|
| Interactive terminal | PowerShell/system shell and seven CLI targets, including fixed login commands, live output and multiple sessions. |
|
|
36
38
|
| Costs and subscriptions | Daily/monthly budget checks, economy downgrade or pause, subscription-first routing, quota cooldown and explicit handling of other subscription failures. |
|
|
37
39
|
| History and quality | Per-package results, dependency views, failure details, rerun/reassignment, ratings and optional stronger-model review. |
|
|
@@ -46,16 +48,16 @@ Generating a route recommendation is local: it does not start a vendor CLI or ma
|
|
|
46
48
|
Open **Plugins → Add plugin** and enter:
|
|
47
49
|
|
|
48
50
|
```text
|
|
49
|
-
@ljwei-stak/dsh-model-router@0.
|
|
51
|
+
@ljwei-stak/dsh-model-router@0.18.0
|
|
50
52
|
```
|
|
51
53
|
|
|
52
|
-
Use the official HTTPS npm source `https://registry.npmjs.org/` when a mirror lacks that exact version. Enable the plugin, then fully quit Harness (including its tray process) and restart it. Plugin details should show **0.
|
|
54
|
+
Use the official HTTPS npm source `https://registry.npmjs.org/` when a mirror lacks that exact version. Enable the plugin, then fully quit Harness (including its tray process) and restart it. Plugin details should show **0.18.0** and the sidebar should contain **模型路由** (Model Router). A global `npm install -g` alone does not register a Desktop plugin.
|
|
53
55
|
|
|
54
56
|
You can instead install a local `.tgz` archive or its extracted inner `package` directory through the plugin manager. Both must contain `package.json` and `.dsh-plugin`. See [releases](https://github.com/Alice-Marx/dsh-model-router/releases) for archived builds, or build your own checkout below.
|
|
55
57
|
|
|
56
58
|
### Upgrade from the old package name
|
|
57
59
|
|
|
58
|
-
The old name `@ljwei-stak/model-router-galgame` ended at 0.13.0. Remove that plugin entry while retaining your profile/application data, then add `@ljwei-stak/dsh-model-router@0.
|
|
60
|
+
The old name `@ljwei-stak/model-router-galgame` ended at 0.13.0. Remove that plugin entry while retaining your profile/application data, then add `@ljwei-stak/dsh-model-router@0.18.0`. Do not keep both entries installed: they share the internal plugin id `model-router-galgame` and tool names. The retained id lets existing router settings and history carry over.
|
|
59
61
|
|
|
60
62
|
For the combined 0.11.x package, back up the profile and export the GAL saves you want to preserve before upgrading. Story playback, artwork, music and saves now belong to the independent `@ljwei-stak/dsh-galgame` package. The detailed save limitations are in [MIGRATION.md](docs/archive/MIGRATION.md).
|
|
61
63
|
|
|
@@ -151,9 +153,16 @@ Installers use fixed official npm packages at each vendor's `@latest`, the vendo
|
|
|
151
153
|
|
|
152
154
|
**Unified install settings.** The top of the **官方工具** page (also in the plugin settings form) sets the install address for every tool at once: one install directory (empty means each vendor's default location), an npm source, and per-tool overrides for vendor installer-script mirrors. Directories must be absolute; sources must be https. Each row picks its own install method from `npm`, `pnpm` or the vendor script (`curl | bash` or `irm | iex`); a method that cannot run on this platform says so on the row instead of being silently swapped. The exact command is shown above the buttons before anything runs.
|
|
153
155
|
|
|
154
|
-
**Script installs never pipe remote content into an interpreter.** The plugin downloads the vendor script, checks it is still that vendor's official installer (vendor markers required, HTML pages and other vendors' scripts refused), and runs the local copy. A mirror may rehost the same script; it may not substitute a different program. Step Code's script accepts an install directory; the OpenCode script hardcodes `~/.opencode/bin`, so a custom directory only applies to it through
|
|
156
|
+
**Script installs never pipe remote content into an interpreter.** The plugin downloads the vendor script, checks it is still that vendor's official installer (vendor markers required, HTML pages and other vendors' scripts refused), and runs the local copy. A mirror may rehost the same script; it may not substitute a different program. Step Code's script accepts an install directory; the OpenCode script hardcodes `~/.opencode/bin`, so a custom directory only applies to it through the package managers' configured global directories. Script installs can be switched off entirely, leaving only the package managers.
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
**Update destination.** Click **保存安装设置** after changing the shared directory or sources. Tool actions wait until saving and command refresh finish. npm uses `--prefix`; pnpm uses its own `global-dir`, `global-bin-dir` and store settings. Install jobs retain the chosen configuration even if another settings page changes it while they are queued. Updates check the selected command directory, so a different current copy on PATH cannot make a missing target look installed. Vendor scripts with fixed paths explain their actual destination; ZCode still requires directory selection in its signed installer window.
|
|
160
|
+
|
|
161
|
+
The [workbench guide](./docs/WORKBENCH_USER_GUIDE.zh.md#厂商安装渠道) lists each vendor’s actual channels and native-install execution limits.
|
|
162
|
+
|
|
163
|
+
With no shared directory, ordinary checks discover the selected manager's packages on PATH. An install, update, repair or removal job resolves and locks that manager's default global destination before comparing versions. If that destination cannot be resolved safely, set and save a shared directory first.
|
|
155
164
|
|
|
156
|
-
**One-click repair and uninstall.** Repair re-runs the same fixed official command, which is what a broken install needs even when it already reports the latest version. Uninstall removes only the program: a package-manager install goes back through the same manager, and a script install
|
|
165
|
+
**One-click repair and uninstall.** Repair re-runs the same fixed official command, which is what a broken install needs even when it already reports the latest version. Uninstall removes only the program: a package-manager install goes back through the same manager, and a script install removes its registered command entries and supported PATH records; vendor version directories may remain. The Windows Codex native directory junction has no generic delete-based uninstall. The row explains this limitation. **Configuration, credentials and session history are always kept.**
|
|
157
166
|
|
|
158
167
|
Installation, trusted launch readiness, account login and model entitlement are separate checks. An unknown login status is not proof of logout. On Windows, supported managed launch entries are checked against publisher signatures or official npm-tarball attestations; ZCode also records trust for its bundled script per signed build. See [the registry](.dsh-plugin/shared/official-tool-registry.mjs) and [executor](.dsh-plugin/shared/official-tool-executor.mjs) for the exact platform rules.
|
|
159
168
|
|
package/README.zh.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Model Router · DeepSeek Harness 模型路由插件
|
|
2
2
|
|
|
3
|
-
版本:**0.
|
|
3
|
+
版本:**0.18.0**。选模求解器概览见[路由研究说明](docs/ROUTING_RESEARCH.zh.md);公式、条件证明与实验见[数学推导与实验记录](docs/ROUTING_DERIVATION_EXPERIMENTS.zh.md)。[LiveBench 最新证据与验证协议](docs/ROUTING_LIVEBENCH_VALIDATION.zh.md)已核查 2026-06-25 聚合评分/费用;最新逐题质量—费用矩阵尚未取得,不声称真实路由收益。
|
|
4
4
|
|
|
5
5
|
为 **DeepSeek Harness Desktop** 提供任务规划、成本感知的模型分配和官方工具执行工作台。输入任务,比较已配置模型,审阅计划,再明确选择是否执行。
|
|
6
6
|
|
|
7
|
+
**0.18.0**:补齐厂商提供的 curl/irm 安装方式,安装与更新复用已保存的目录、方式和下载源,并核验目标目录里的版本。修复 MiniMax 官方 PowerShell 脚本以 `<# ... #>` 帮助注释开头时被误判为 HTML 的问题。统一安装设置改为明确保存后生效。
|
|
8
|
+
|
|
7
9
|
**0.16.4**:PATH 探测在没有安装日志回调时也会保留命令输出。0.16.3 把 stdout 写进了 `onOutput?.(...)`,`where` 和 `--version` 因此变成空字符串,PATH 上的 CLI 全被报成未安装。MiniMax 与 ZCode 不走这条探测,所以仍能显示。
|
|
8
10
|
|
|
9
11
|
**0.16.3**:恢复全部官方工具卡片的检测和安装。Windows 上的探测与 `npm` 再次按独立参数启动,避免 cmd 把 Node 指到不存在的脚本。只要输出版本号就视为已安装;单张卡片的检测错误留在该行;已经是最新版的 CLI 不再被收成「修复官方执行入口」。
|
|
@@ -14,7 +16,7 @@
|
|
|
14
16
|
|
|
15
17
|
**0.16.0 新增**:[动态数据与持续反馈](docs/ROUTING_ADAPTIVE_LEARNING.zh.md):可选公开价格/LiveBench快照刷新;按任务类型、时间衰减和低样本收缩调整个人偏好,质量门槛独立保留。设置在“预算与安全”,公开源默认关闭;价格源需提供按官方文档核对的固定USD单价JSON,不是通用官网抓价器。本地DSH_HOME共享、最近200运行窗口,暂无真实收益证明。
|
|
16
18
|
|
|
17
|
-
当前版本为 **0.
|
|
19
|
+
当前版本为 **0.18.0**,包名为 `@ljwei-stak/dsh-model-router`;声明兼容的宿主为 **DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2**。自 0.12.0 起,路由与 [GAL](https://github.com/Alice-Marx/deepseek-harness-galgame) 已拆成两个独立插件。
|
|
18
20
|
|
|
19
21
|
**0.15.0 更新**:工作台分为四页,路由建议紧接任务规划展示;执行输入变化时丢弃旧预览;裁剪历史或重试后仍保留费用累计;源码开发新增独立模拟 UI 预览。
|
|
20
22
|
|
|
@@ -31,7 +33,7 @@
|
|
|
31
33
|
| 路由方案 | 省钱优先、均衡、效果优先;按质量门槛、预估 token 成本与有限组合搜索分配模型。未知价格保持未知。 |
|
|
32
34
|
| 只读执行 | 工作台先预览、再确认执行;也可在官方会话调用工具。支持的无界面 CLI 或模型目录 API 返回结果并留存记录。 |
|
|
33
35
|
| 可编辑执行 | 通过官方会话启动单个 CLI 或顺序 CLI 团队,经审批在独立 Git 工作树修改,满足条件后整合补丁。 |
|
|
34
|
-
| 官方工具 |
|
|
36
|
+
| 官方工具 | 十个固定注册表工具的检测、安装/更新、可信入口与登录体检;安装有日志和取消操作。 |
|
|
35
37
|
| 交互终端 | 系统终端/PowerShell 与七个 CLI,含固定登录命令、多会话和实时输出。 |
|
|
36
38
|
| 成本与订阅 | 每日/月度预算检查、自动尝试省钱方案或暂停、订阅优先、额度冷却,以及其他订阅失败的确认流程。 |
|
|
37
39
|
| 执行与质量记录 | 逐工作包结果、依赖图、失败详情、重跑/改派、用户评价与可选强模型复核。 |
|
|
@@ -46,16 +48,16 @@
|
|
|
46
48
|
打开 **插件 → 添加插件**,填写:
|
|
47
49
|
|
|
48
50
|
```text
|
|
49
|
-
@ljwei-stak/dsh-model-router@0.
|
|
51
|
+
@ljwei-stak/dsh-model-router@0.18.0
|
|
50
52
|
```
|
|
51
53
|
|
|
52
|
-
镜像缺少该精确版本时使用官方 HTTPS npm 源 `https://registry.npmjs.org/`。安装并启用后,完全退出 Harness(包括托盘进程)再启动。插件详情应显示 **0.
|
|
54
|
+
镜像缺少该精确版本时使用官方 HTTPS npm 源 `https://registry.npmjs.org/`。安装并启用后,完全退出 Harness(包括托盘进程)再启动。插件详情应显示 **0.18.0**,侧栏出现**模型路由**。仅运行全局 `npm install -g` 不会把插件注册到桌面版 profile。
|
|
53
55
|
|
|
54
56
|
也可在插件管理器填写本地 `.tgz` 文件的绝对路径,或解压后的内层 `package` 目录;其中应包含 `package.json` 与 `.dsh-plugin`。[历史发布页](https://github.com/Alice-Marx/dsh-model-router/releases)提供归档;已克隆源码的开发方式见下文。
|
|
55
57
|
|
|
56
58
|
### 从旧包名升级
|
|
57
59
|
|
|
58
|
-
`@ljwei-stak/model-router-galgame` 的最后版本为 0.13.0。保留 profile 与应用数据,移除旧插件条目,再添加 `@ljwei-stak/dsh-model-router@0.
|
|
60
|
+
`@ljwei-stak/model-router-galgame` 的最后版本为 0.13.0。保留 profile 与应用数据,移除旧插件条目,再添加 `@ljwei-stak/dsh-model-router@0.18.0`。不要同时安装两个包:它们共用内部插件 id `model-router-galgame` 与工具名;id 保留使已有路由设置和执行历史能够延续。
|
|
59
61
|
|
|
60
62
|
从 0.11.x 合并版迁移时,先备份 profile 并导出希望保留的 GAL 进度。剧情、立绘、音乐、自由模式和存档属于独立 `@ljwei-stak/dsh-galgame` 插件。导出范围及同 profile 迁移的限制见 [MIGRATION.md](docs/archive/MIGRATION.md)。
|
|
61
63
|
|
|
@@ -151,9 +153,16 @@ CLI 团队是插件自有的顺序依赖执行器。Harness 内建 Agent Teams
|
|
|
151
153
|
|
|
152
154
|
**统一安装设置。** 在**官方工具**页顶部(或插件设置页)可以一次设定所有工具的安装目录与下载源:统一安装目录(留空=各厂商默认位置)、npm 源地址,以及按工具 ID 覆盖厂商安装脚本的镜像地址。目录必须是绝对路径,下载地址必须是 https。每个工具都可以在**安装方式**下拉框里单独选择 `npm`、`pnpm`、厂商安装脚本(`curl | bash` 或 `irm | iex`);选了某个平台不支持的方式时,行内会写明原因而不是悄悄换成别的。每行上方实时显示将要执行的完整命令。
|
|
153
155
|
|
|
154
|
-
**脚本方式不会把远程内容直接管道给解释器。** 插件先下载厂商安装脚本、核对它仍然是该厂商的官方脚本(校验厂商标记、拒绝 HTML 页面和其他厂商的脚本),再执行本地副本。镜像可以重新托管同一个脚本,但不能替换成别的程序。阶跃的脚本还支持安装目录参数,OpenCode 的官方脚本写死了 `~/.opencode/bin`,因此自定义安装目录对它只能通过 npm/pnpm
|
|
156
|
+
**脚本方式不会把远程内容直接管道给解释器。** 插件先下载厂商安装脚本、核对它仍然是该厂商的官方脚本(校验厂商标记、拒绝 HTML 页面和其他厂商的脚本),再执行本地副本。镜像可以重新托管同一个脚本,但不能替换成别的程序。阶跃的脚本还支持安装目录参数,OpenCode 的官方脚本写死了 `~/.opencode/bin`,因此自定义安装目录对它只能通过 npm/pnpm 的全局目录参数 生效。脚本方式可以整体关闭,只保留包管理器方式。
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
**更新位置。** 修改统一目录或下载源后,点击**保存安装设置**。保存和命令刷新完成前,安装与更新暂不可用。npm 使用 `--prefix`;pnpm 使用自身的全局包目录、命令目录和存储目录。排队中的任务保留点击时的配置快照。更新优先核验所选目录中的命令,避免 PATH 上另一份最新版让目标安装被误判为已完成。厂商固定目录的脚本会说明实际位置;ZCode 仍需在官方安装窗口选择目录。
|
|
160
|
+
|
|
161
|
+
完整渠道与原生安装执行限制见[工作台指南](./docs/WORKBENCH_USER_GUIDE.zh.md#厂商安装渠道)。
|
|
162
|
+
|
|
163
|
+
未设统一目录时,普通体检会从 PATH 发现所选管理器的安装;安装、更新、修复和卸载任务则先确定并锁定该管理器的默认全局目录,再比较版本。无法可靠确定默认目录时,请先填写并保存统一目录。
|
|
155
164
|
|
|
156
|
-
**一键修复与一键卸载。**
|
|
165
|
+
**一键修复与一键卸载。** 修复会用同一条固定官方命令重新安装一遍,适合修复损坏或不完整的安装;卸载只删除程序本身——包管理器安装走同一个管理器卸载,脚本安装移除登记的命令入口,并按厂商支持清理 PATH 记录,可能保留版本目录。Windows Codex 原生目录联接不使用通用删除卸载,界面会给出说明。**配置、登录信息和历史记录一律保留**,重新安装后仍在。
|
|
157
166
|
|
|
158
167
|
**已安装、可信入口就绪、账号已登录、模型有权限是不同状态。** 登录未知不代表已退出。在 Windows,托管入口按发布者签名或官方 npm tarball 校验;ZCode 另对签名构建中的脚本记录首次信任摘要。平台细则见[注册表](.dsh-plugin/shared/official-tool-registry.mjs)和[执行器](.dsh-plugin/shared/official-tool-executor.mjs)。
|
|
159
168
|
|