@ljwei-stak/dsh-model-router 0.17.0 → 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 +331 -85
- 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 +145 -12
- package/.dsh-plugin/shared/official-tools-runtime.mjs +426 -118
- package/.dsh-plugin/shared/tool-install-preferences.mjs +82 -26
- package/.dsh-plugin/shared/vendor-mimo-grok-adapter.mjs +41 -16
- package/CHANGELOG.md +22 -1
- package/README.md +19 -10
- package/README.zh.md +19 -10
- package/{INSTALLATION_GUIDE.zh.md → docs/INSTALLATION_GUIDE.zh.md} +16 -16
- package/docs/WORKBENCH_USER_GUIDE.zh.md +27 -5
- package/{MIGRATION.md → docs/archive/MIGRATION.md} +1 -1
- package/docs/archive/README.md +21 -0
- package/package.json +4 -4
|
@@ -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,22 +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
155
|
/** The location the vendor script uses on its own, ignoring any configured directory. */
|
|
150
|
-
export function vendorScriptDir(tool, { home = '' } = {}) {
|
|
151
|
-
|
|
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)) : ''
|
|
152
160
|
}
|
|
153
161
|
|
|
154
|
-
/**
|
|
155
|
-
export function
|
|
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')) : ''
|
|
165
|
+
}
|
|
166
|
+
|
|
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' } = {}) {
|
|
156
182
|
const registry = preferences?.registry || DEFAULT_NPM_REGISTRY
|
|
157
183
|
const args = method.installArgs.filter(arg => !arg.startsWith('--registry='))
|
|
158
184
|
args.push(`--registry=${registry}`)
|
|
159
|
-
|
|
185
|
+
args.push(...packageManagerDirectoryArgs(method, installDir, { platform }))
|
|
160
186
|
return [...args]
|
|
161
187
|
}
|
|
162
188
|
|
|
@@ -167,6 +193,13 @@ function baseUrlOf(scriptUrl) {
|
|
|
167
193
|
} catch { return '' }
|
|
168
194
|
}
|
|
169
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
|
+
|
|
170
203
|
/**
|
|
171
204
|
* The method to use: the requested one, else the user's saved choice, else the
|
|
172
205
|
* registry default — and if none of those run on this platform, the first
|
|
@@ -176,7 +209,12 @@ function baseUrlOf(scriptUrl) {
|
|
|
176
209
|
function pickMethod(tool, methodId, preferences, platform) {
|
|
177
210
|
const requested = methodId ?? preferences?.methods?.[tool.id]
|
|
178
211
|
if (!requested) {
|
|
179
|
-
|
|
212
|
+
const available = installMethodsFor(tool).filter(method => !installMethodBlockReason(method, platform))
|
|
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)
|
|
180
218
|
}
|
|
181
219
|
// A named method is never silently swapped. If the tool does not declare it,
|
|
182
220
|
// or it does not run here, the caller gets the reason instead of a surprise.
|
|
@@ -195,7 +233,7 @@ function unsupportedMethodError(tool) {
|
|
|
195
233
|
* One concrete plan for one tool and one method. A thrown Error means the user
|
|
196
234
|
* asked for something this tool cannot do, with a reason they can act on.
|
|
197
235
|
*/
|
|
198
|
-
export function resolveInstallPlan(tool, methodId, preferences = {}, { platform = 'linux', home = '' } = {}) {
|
|
236
|
+
export function resolveInstallPlan(tool, methodId, preferences = {}, { platform = 'linux', home = '', env = {} } = {}) {
|
|
199
237
|
if (!tool || tool.unsupported) throw new Error(tool?.unsupportedReason ?? '此工具不支持一键安装。')
|
|
200
238
|
const method = pickMethod(tool, methodId, preferences, platform)
|
|
201
239
|
if (!method) throw new Error(unsupportedMethodError(tool))
|
|
@@ -207,7 +245,16 @@ export function resolveInstallPlan(tool, methodId, preferences = {}, { platform
|
|
|
207
245
|
const installDir = preferences?.installDir || ''
|
|
208
246
|
if (method.kind === 'script') {
|
|
209
247
|
const scriptUrl = preferences?.scriptUrls?.[tool.id] || method.scriptUrl
|
|
210
|
-
const target = methodHonoursInstallDir(method) ? scriptInstallDir(tool, preferences, { home }) : ''
|
|
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)) {
|
|
256
|
+
notices.push('MiniMax 官方脚本只接受 https://registry.npmjs.org/ 或 https://registry.npmmirror.com/,本次不改写它的 npm 源。')
|
|
257
|
+
}
|
|
211
258
|
return {
|
|
212
259
|
toolId: tool.id,
|
|
213
260
|
method,
|
|
@@ -216,13 +263,13 @@ export function resolveInstallPlan(tool, methodId, preferences = {}, { platform
|
|
|
216
263
|
label: method.label,
|
|
217
264
|
shell: method.shell,
|
|
218
265
|
scriptUrl,
|
|
266
|
+
downloadUrls: preferences?.scriptUrls?.[tool.id] ? [scriptUrl] : [...new Set([scriptUrl, ...(method.scriptFallbackUrls ?? [])])],
|
|
219
267
|
verify: method.verify,
|
|
220
268
|
installArg: method.installArgs,
|
|
221
269
|
installDir: target,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
: [],
|
|
270
|
+
binDir,
|
|
271
|
+
env: scriptEnv(method, { installDir: target, scriptUrl, registry: preferences.registry }),
|
|
272
|
+
notices,
|
|
226
273
|
}
|
|
227
274
|
}
|
|
228
275
|
return {
|
|
@@ -232,8 +279,9 @@ export function resolveInstallPlan(tool, methodId, preferences = {}, { platform
|
|
|
232
279
|
kind: 'package-manager',
|
|
233
280
|
label: method.label,
|
|
234
281
|
manager: method.manager,
|
|
235
|
-
args: packageManagerArgs(method, preferences, { installDir }),
|
|
282
|
+
args: packageManagerArgs(method, preferences, { installDir, platform }),
|
|
236
283
|
installDir,
|
|
284
|
+
binDir: packageManagerBinDir(installDir, { platform }),
|
|
237
285
|
env: {},
|
|
238
286
|
notices: [],
|
|
239
287
|
}
|
|
@@ -248,6 +296,7 @@ export function resolveUninstallPlan(tool, methodId, preferences = {}, options =
|
|
|
248
296
|
: `${tool?.label ?? '此工具'} 没有可用的卸载方式。`)
|
|
249
297
|
const blocked = installMethodBlockReason(method, platform)
|
|
250
298
|
if (blocked) throw new Error(`${tool.label}:${blocked}`)
|
|
299
|
+
if (method.uninstallUnsupportedReason) throw new Error(method.uninstallUnsupportedReason)
|
|
251
300
|
if (method.kind === 'script') {
|
|
252
301
|
return { toolId: tool.id, method, methodId: method.id, kind: 'script', label: method.label, ...resolveInstallLocation(tool, method, preferences, options) }
|
|
253
302
|
}
|
|
@@ -261,28 +310,35 @@ export function resolveUninstallPlan(tool, methodId, preferences = {}, options =
|
|
|
261
310
|
args: [
|
|
262
311
|
...method.uninstallArgs,
|
|
263
312
|
...(preferences?.registry ? [`--registry=${preferences.registry}`] : []),
|
|
264
|
-
...(preferences?.installDir
|
|
313
|
+
...packageManagerDirectoryArgs(method, preferences?.installDir || '', { platform }),
|
|
265
314
|
],
|
|
266
315
|
installDir: preferences?.installDir || '',
|
|
316
|
+
binDir: packageManagerBinDir(preferences?.installDir || '', { platform }),
|
|
267
317
|
}
|
|
268
318
|
}
|
|
269
319
|
|
|
270
320
|
/** Where the tool's binary lives, so a later uninstall can find it. */
|
|
271
|
-
export function resolveInstallLocation(tool, method, preferences = {}, { home = '' } = {}) {
|
|
321
|
+
export function resolveInstallLocation(tool, method, preferences = {}, { home = '', platform = 'linux', env = {} } = {}) {
|
|
272
322
|
if (!method) return null
|
|
273
323
|
if (method.kind === 'script') {
|
|
274
324
|
// A method that cannot honour the configured directory always lands in the
|
|
275
325
|
// vendor's own location, whatever the user typed.
|
|
276
326
|
const dir = methodHonoursInstallDir(method)
|
|
277
|
-
? scriptInstallDir(tool, preferences, { home })
|
|
278
|
-
: vendorScriptDir(tool, { home })
|
|
279
|
-
|
|
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 ?? [])] }
|
|
280
331
|
}
|
|
281
|
-
return { kind: 'package-manager', dir: preferences?.installDir || '', binaries: [] }
|
|
332
|
+
return { kind: 'package-manager', dir: packageManagerBinDir(preferences?.installDir || '', { platform }), installDir: preferences?.installDir || '', binaries: [] }
|
|
282
333
|
}
|
|
283
334
|
|
|
284
|
-
function scriptEnv(method, { installDir, scriptUrl }) {
|
|
335
|
+
function scriptEnv(method, { installDir, scriptUrl, registry }) {
|
|
285
336
|
const env = {}
|
|
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
|
+
}
|
|
286
342
|
if (installDir && method.env?.includes('STEP_INSTALL_DIR')) env.STEP_INSTALL_DIR = installDir
|
|
287
343
|
// The step installers read their release base from this variable, so a mirror
|
|
288
344
|
// URL also moves the binaries and the checksum manifest, not just the script.
|
|
@@ -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,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
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.
|
|
14
|
+
|
|
15
|
+
## 0.17.1 — 2026-10-11
|
|
16
|
+
|
|
17
|
+
Stable patch under the npm `latest` tag. Fully quit Harness, including the tray or WebUI host process, and restart after upgrading so the Host reloads. Existing 0.17.0 artifacts remain unchanged.
|
|
18
|
+
|
|
19
|
+
- **MiniMax Windows one-click update accepts the official installer again.** The published `install.ps1` opens with PowerShell comment-based help (`<#`), and the sanity check treated every leading `<` as an HTML page, so the real script was refused before it ran. HTML and XML documents are still refused. UTF-8/UTF-16 BOMs are decoded first. Content-Type is not used: the CDN serves the script as `application/octet-stream`. A download that is empty, HTML, or missing `@minimax-ai/code`, `MCODE_INSTALL_DIR` and `MiniMax Code` still fails with the missing piece named.
|
|
20
|
+
- **Official script installers are selectable.** Claude Code, Codex, MiniMax, Kimi, MiMo and Grok now offer the vendor `curl | bash` / `irm | iex` installer next to npm and pnpm, on the platforms that script supports. Gemini stays on npm/pnpm. ZCode stays the signed desktop installer. One-click install, repair and uninstall run the method shown on the row. MiniMax on Windows defaults to `irm | iex` (that is the copy npm does not update); pinning npm or pnpm runs that manager instead, and still falls back to the official script if the probed version does not advance.
|
|
21
|
+
- **Updates use the configured install directory.** npm/pnpm keep `--prefix`. Scripts that document a directory variable (`MCODE_INSTALL_DIR`, `CODEX_INSTALL_DIR`, `KIMI_INSTALL_DIR`, `GROK_BIN_DIR`, `MIMOCODE_INSTALL_DIR`, Step Code's flag) receive that path. The MiniMax Windows fallback no longer substitutes the npm prefix: a configured directory wins, otherwise an existing official layout is updated in place, otherwise `%USERPROFILE%\.minimax-code`. Scripts that hardcode a path (Claude, OpenCode, MiMo on Unix) say so on the row.
|
|
22
|
+
- Windows validation for this patch: MiniMax `irm | iex` against a configured install directory completed with `mcode.cmd` present and version 0.6.8; the false “not an official Windows install script” refusal did not recur on main tip `1162f276`.
|
|
23
|
+
|
|
3
24
|
## 0.17.0 — 2026-10-11
|
|
4
25
|
|
|
5
26
|
Stable minor under the npm `latest` tag. Fully quit Harness, including the tray or WebUI host process, and restart after upgrading so the Host reloads. Existing 0.16.4 artifacts remain unchanged.
|
|
@@ -144,7 +165,7 @@ Prerelease for DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2, published under
|
|
|
144
165
|
|
|
145
166
|
### Package renamed
|
|
146
167
|
- The npm package is now **`@ljwei-stak/dsh-model-router`**. The last version under the old name `@ljwei-stak/model-router-galgame` is 0.13.0, with the same content as `@ljwei-stak/dsh-model-router@0.13.0`; the old name will get no further updates. Upgrade by removing the old plugin and adding the new one; do not install both.
|
|
147
|
-
- The profile entry id, panel id, settings namespace and Cordis plugin name stay `model-router-galgame`, and state stays in `~/.dsh/model-router/`, so saved settings and run history carry over. See [MIGRATION.md](MIGRATION.md).
|
|
168
|
+
- The profile entry id, panel id, settings namespace and Cordis plugin name stay `model-router-galgame`, and state stays in `~/.dsh/model-router/`, so saved settings and run history carry over. See [MIGRATION.md](docs/archive/MIGRATION.md).
|
|
148
169
|
|
|
149
170
|
### Official CLI execution (PR #1)
|
|
150
171
|
- Assigned work packages run through the official vendor CLIs (Claude Code, Codex, Gemini CLI, Kimi Code, MiniMax Code, MiMo Code, Grok Build) with headless adapters; failures fall back to the Harness model catalog API.
|
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,11 +16,11 @@ 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
|
|
|
21
|
-
[简体中文](README.zh.md) · [Workbench guide (Chinese)](docs/WORKBENCH_USER_GUIDE.zh.md) · [Installation guide (Chinese)](INSTALLATION_GUIDE.zh.md) · [Migration](MIGRATION.md) · [Changelog](CHANGELOG.md)
|
|
23
|
+
[简体中文](README.zh.md) · [Workbench guide (Chinese)](docs/WORKBENCH_USER_GUIDE.zh.md) · [Installation guide (Chinese)](docs/INSTALLATION_GUIDE.zh.md) · [Migration](docs/archive/MIGRATION.md) · [Changelog](CHANGELOG.md)
|
|
22
24
|
|
|
23
25
|
For self-testing, follow the [desktop acceptance and quality–cost experiment steps (Chinese)](docs/ROUTING_SELFTEST_STEPS.zh.md). Start with an 18-question, three-model pilot and an explicit spending limit. Offline helpers do not collect answers or call models; first-stage selection replay is distinct from end-to-end team execution.
|
|
24
26
|
|
|
@@ -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,18 +48,18 @@ 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
|
-
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](MIGRATION.md).
|
|
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
|
|
|
62
64
|
## Use the workbench
|
|
63
65
|
|
|
@@ -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,11 +16,11 @@
|
|
|
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
|
|
|
21
|
-
[English](README.md) · [工作台完整指南](docs/WORKBENCH_USER_GUIDE.zh.md) · [安装与验证](INSTALLATION_GUIDE.zh.md) · [迁移说明](MIGRATION.md) · [更新记录](CHANGELOG.md)
|
|
23
|
+
[English](README.md) · [工作台完整指南](docs/WORKBENCH_USER_GUIDE.zh.md) · [安装与验证](docs/INSTALLATION_GUIDE.zh.md) · [迁移说明](docs/archive/MIGRATION.md) · [更新记录](CHANGELOG.md)
|
|
22
24
|
|
|
23
25
|
需要自行实测时,按[安装验收与质量—成本实验步骤](docs/ROUTING_SELFTEST_STEPS.zh.md)先完成桌面检查和 18 题×3 模型小样,再决定正式样本与费用上限。方案区分首包选模投影和实际团队执行;配套离线入口不采集、不调用模型,真实收益仍待验证。
|
|
24
26
|
|
|
@@ -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,18 +48,18 @@
|
|
|
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
|
-
从 0.11.x 合并版迁移时,先备份 profile 并导出希望保留的 GAL 进度。剧情、立绘、音乐、自由模式和存档属于独立 `@ljwei-stak/dsh-galgame` 插件。导出范围及同 profile 迁移的限制见 [MIGRATION.md](MIGRATION.md)。
|
|
62
|
+
从 0.11.x 合并版迁移时,先备份 profile 并导出希望保留的 GAL 进度。剧情、立绘、音乐、自由模式和存档属于独立 `@ljwei-stak/dsh-galgame` 插件。导出范围及同 profile 迁移的限制见 [MIGRATION.md](docs/archive/MIGRATION.md)。
|
|
61
63
|
|
|
62
64
|
## 工作台的四个页面
|
|
63
65
|
|
|
@@ -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
|
|