@ljwei-stak/dsh-model-router 0.17.0 → 0.17.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.
- package/.dsh-plugin/client.js +199 -19
- package/.dsh-plugin/shared/official-tool-registry.mjs +178 -8
- package/.dsh-plugin/shared/official-tools-runtime.mjs +153 -42
- package/.dsh-plugin/shared/tool-install-preferences.mjs +69 -17
- package/CHANGELOG.md +12 -1
- package/README.md +2 -2
- package/README.zh.md +2 -2
- package/{INSTALLATION_GUIDE.zh.md → docs/INSTALLATION_GUIDE.zh.md} +7 -7
- package/docs/WORKBENCH_USER_GUIDE.zh.md +1 -1
- package/{MIGRATION.md → docs/archive/MIGRATION.md} +1 -1
- package/docs/archive/README.md +21 -0
- package/package.json +3 -3
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
OFFICIAL_TOOLS,
|
|
24
24
|
} from './official-tool-registry.mjs'
|
|
25
25
|
import {
|
|
26
|
+
minimaxScriptRegistry,
|
|
26
27
|
resolveInstallLocation,
|
|
27
28
|
resolveInstallPlan,
|
|
28
29
|
resolveUninstallPlan,
|
|
@@ -39,7 +40,9 @@ const OUTPUT_LINE_CAP = 120
|
|
|
39
40
|
const IS_WINDOWS = process.platform === 'win32'
|
|
40
41
|
const CREATE_NO_WINDOW = 0x0800_0000
|
|
41
42
|
const MINIMAX_INSTALLER_URL = 'https://filecdn.minimax.chat/public/install.ps1'
|
|
42
|
-
|
|
43
|
+
// Codex's Windows installer is already ~70KB and MiniMax's is ~57KB. 1MiB still
|
|
44
|
+
// rejects an HTML error page or a substituted archive.
|
|
45
|
+
const MAX_INSTALLER_BYTES = 1_048_576
|
|
43
46
|
/** Windows ships where.exe in System32; POSIX which is ubiquitous. */
|
|
44
47
|
const LOCATOR = IS_WINDOWS ? 'where' : 'which'
|
|
45
48
|
/** Cached probe results keep plan generation fast; installs invalidate them. */
|
|
@@ -128,11 +131,16 @@ export function staleInstallMessage(label, installedVersion, latestVersion) {
|
|
|
128
131
|
* Which updater to run. `order` is compareReleaseVersions(installed, latest):
|
|
129
132
|
* -1 older, 0 same, 1 newer, null when either version is unknown.
|
|
130
133
|
*/
|
|
131
|
-
export function officialUpdatePlan({ toolId, installed, order, platform }) {
|
|
134
|
+
export function officialUpdatePlan({ toolId, installed, order, platform, methodId }) {
|
|
132
135
|
if (installed && order === 1) return 'refuse-downgrade'
|
|
133
136
|
if (installed && order === 0) return 'already-current'
|
|
134
137
|
if (installed && order === -1 && toolId === 'claude-code') return 'claude-update-then-registry'
|
|
135
138
|
if (installed && (order === -1 || order === null) && toolId === 'minimax-code' && platform === 'win32') {
|
|
139
|
+
// npm/pnpm is the method the user picked, so that command runs first. The
|
|
140
|
+
// official Windows installer stays the fallback when that copy does not
|
|
141
|
+
// advance, and it is the direct plan when no package manager was named
|
|
142
|
+
// (the historical one-click update of an official-layout install).
|
|
143
|
+
if (methodId === 'npm' || methodId === 'pnpm') return 'registry-install'
|
|
136
144
|
return 'minimax-official-installer'
|
|
137
145
|
}
|
|
138
146
|
return 'registry-install'
|
|
@@ -237,20 +245,62 @@ function runCapture(executable, args, { timeoutMs, onOutput, useShell = false, s
|
|
|
237
245
|
})
|
|
238
246
|
}
|
|
239
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Decode installer bytes the way a terminal would. The MiniMax CDN serves the
|
|
250
|
+
* PowerShell script as `application/octet-stream` UTF-8; a UTF-16 copy (BOM or
|
|
251
|
+
* null-padded) is the same script and must not be rejected as a mismatch.
|
|
252
|
+
*/
|
|
253
|
+
export function installerScriptText(source) {
|
|
254
|
+
return decodeProcessOutput(Buffer.isBuffer(source) ? source : Buffer.from(source ?? '')).replace(/^\uFEFF/, '')
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* An HTML/XML document is not an installer. PowerShell comment-based help
|
|
259
|
+
* starts with `<#`, which is not a tag: the published MiniMax, Kimi and MiMo
|
|
260
|
+
* Windows scripts all open that way. Treating every leading `<` as HTML
|
|
261
|
+
* rejected those official scripts.
|
|
262
|
+
*/
|
|
263
|
+
export function isMarkupDocument(text) {
|
|
264
|
+
const head = String(text ?? '').replace(/^\uFEFF/, '').trimStart()
|
|
265
|
+
if (!head.startsWith('<')) return false
|
|
266
|
+
if (head.startsWith('<#')) return false
|
|
267
|
+
return /^<(?:!|\?|[A-Za-z])/.test(head)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const MINIMAX_INSTALLER_MARKERS = Object.freeze(['@minimax-ai/code', 'MCODE_INSTALL_DIR', 'MiniMax Code'])
|
|
271
|
+
|
|
240
272
|
/**
|
|
241
273
|
* MiniMax's official Windows installer script is run as published (it installs
|
|
242
|
-
* `@minimax-ai/code@latest`).
|
|
243
|
-
*
|
|
274
|
+
* `@minimax-ai/code@latest`). Structural checks only: the vendor's own markers,
|
|
275
|
+
* and a refusal of HTML/XML. Content-Type is not consulted — the CDN labels
|
|
276
|
+
* the real script `application/octet-stream`.
|
|
244
277
|
*/
|
|
245
278
|
export function officialMiniMaxInstaller(source) {
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
if (
|
|
249
|
-
throw new Error('下载内容不是 MiniMax Code 官方 Windows
|
|
279
|
+
const text = installerScriptText(source)
|
|
280
|
+
if (!text.trim()) throw new Error('下载内容为空,已拒绝执行。')
|
|
281
|
+
if (isMarkupDocument(text)) {
|
|
282
|
+
throw new Error('下载内容不是 MiniMax Code 官方 Windows 安装脚本(收到 HTML 页面),已拒绝执行。')
|
|
283
|
+
}
|
|
284
|
+
const missing = MINIMAX_INSTALLER_MARKERS.filter(marker => !text.includes(marker))
|
|
285
|
+
if (missing.length) {
|
|
286
|
+
throw new Error(`下载内容不是 MiniMax Code 官方 Windows 安装脚本(缺少 ${missing.join('、')}),已拒绝执行。`)
|
|
250
287
|
}
|
|
251
288
|
return text
|
|
252
289
|
}
|
|
253
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Directory the Windows installer must use. A configured path wins. Otherwise
|
|
293
|
+
* an already-installed official layout is updated in place. With neither, the
|
|
294
|
+
* vendor default is `%USERPROFILE%\.minimax-code` — not the npm prefix.
|
|
295
|
+
*/
|
|
296
|
+
export function minimaxOfficialInstallDir(preferences = {}, home = homedir(), existingRoot = '') {
|
|
297
|
+
const configured = String(preferences?.installDir ?? '').trim()
|
|
298
|
+
if (configured) return configured
|
|
299
|
+
const existing = String(existingRoot ?? '').trim()
|
|
300
|
+
if (existing) return existing
|
|
301
|
+
return join(home, '.minimax-code')
|
|
302
|
+
}
|
|
303
|
+
|
|
254
304
|
async function fetchOfficialMiniMaxInstaller(signal) {
|
|
255
305
|
const response = await fetch(MINIMAX_INSTALLER_URL, {
|
|
256
306
|
signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(30_000)]) : AbortSignal.timeout(30_000),
|
|
@@ -266,30 +316,50 @@ async function fetchOfficialMiniMaxInstaller(signal) {
|
|
|
266
316
|
return officialMiniMaxInstaller(Buffer.concat(chunks))
|
|
267
317
|
}
|
|
268
318
|
|
|
319
|
+
async function existingMiniMaxInstallRoot() {
|
|
320
|
+
const paths = (process.env.PATH ?? '').split(IS_WINDOWS ? ';' : ':').filter(Boolean)
|
|
321
|
+
const candidates = []
|
|
322
|
+
for (const directory of paths) candidates.push(directory, join(directory, '.minimax-code'))
|
|
323
|
+
candidates.push(join(homedir(), '.minimax-code'))
|
|
324
|
+
const seen = new Set()
|
|
325
|
+
for (const directory of candidates) {
|
|
326
|
+
const key = directory.toLowerCase()
|
|
327
|
+
if (seen.has(key)) continue
|
|
328
|
+
seen.add(key)
|
|
329
|
+
try {
|
|
330
|
+
if (!(await stat(join(directory, 'mcode.cmd'))).isFile()) continue
|
|
331
|
+
if (!(await stat(join(directory, 'current'))).isFile()) continue
|
|
332
|
+
return await realpath(directory)
|
|
333
|
+
} catch { /* try the next candidate */ }
|
|
334
|
+
}
|
|
335
|
+
return ''
|
|
336
|
+
}
|
|
337
|
+
|
|
269
338
|
async function runOfficialMiniMaxInstaller(job, { reason } = {}) {
|
|
270
339
|
let staging = null
|
|
271
|
-
let
|
|
340
|
+
let stagingParent = null
|
|
272
341
|
try {
|
|
273
342
|
job.finishedAt = null
|
|
274
343
|
job.error = null
|
|
275
344
|
job.phase = 'downloading-and-verifying'
|
|
276
345
|
pushLine(job, reason || '— npm 原生依赖未就绪;改用 MiniMax 官方 Windows 安装脚本(安装最新版)—')
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
346
|
+
const existingRoot = preferences.installDir ? '' : await existingMiniMaxInstallRoot()
|
|
347
|
+
const installDir = minimaxOfficialInstallDir(preferences, homedir(), existingRoot)
|
|
348
|
+
pushLine(job, preferences.installDir
|
|
349
|
+
? `— 使用已配置的安装目录:${installDir} —`
|
|
350
|
+
: existingRoot
|
|
351
|
+
? `— 未配置统一安装目录,沿用已安装的官方目录:${installDir} —`
|
|
352
|
+
: `— 未配置统一安装目录,使用 MiniMax 官方默认位置:${installDir} —`)
|
|
353
|
+
await mkdir(installDir, { recursive: true })
|
|
354
|
+
stagingParent = await realpath(installDir)
|
|
284
355
|
const script = await fetchOfficialMiniMaxInstaller(job.controller.signal)
|
|
285
|
-
staging = await mkdtemp(join(
|
|
356
|
+
staging = await mkdtemp(join(stagingParent, '.model-router-minimax-installer-'))
|
|
286
357
|
const scriptPath = join(staging, 'install.ps1')
|
|
287
358
|
const tempPath = join(staging, 'temp')
|
|
288
359
|
await mkdir(tempPath)
|
|
289
360
|
// Windows PowerShell 5 reads a BOM-less .ps1 using the system ANSI page.
|
|
290
361
|
// The upstream installer contains Unicode art, so write a UTF-8 BOM.
|
|
291
362
|
await writeFile(scriptPath, `\uFEFF${script}`, 'utf8')
|
|
292
|
-
const installDir = join(prefixRoot, '.minimax-code')
|
|
293
363
|
const powershell = join(process.env.SystemRoot ?? 'C:\\Windows', 'System32',
|
|
294
364
|
'WindowsPowerShell', 'v1.0', 'powershell.exe')
|
|
295
365
|
const utilityModule = join(process.env.SystemRoot ?? 'C:\\Windows', 'System32',
|
|
@@ -299,13 +369,17 @@ async function runOfficialMiniMaxInstaller(job, { reason } = {}) {
|
|
|
299
369
|
+ 'Import-Module -Name $env:MODEL_ROUTER_UTILITY_MODULE -ErrorAction Stop; '
|
|
300
370
|
+ '& $env:MODEL_ROUTER_MINIMAX_INSTALL_SCRIPT'
|
|
301
371
|
job.phase = 'installing'
|
|
372
|
+
const scriptRegistry = minimaxScriptRegistry(preferences.registry || 'https://registry.npmjs.org/')
|
|
373
|
+
if (!scriptRegistry && preferences.registry) {
|
|
374
|
+
pushLine(job, '— MiniMax 官方脚本不接受当前 npm 源,本次使用脚本自己的默认源 —')
|
|
375
|
+
}
|
|
302
376
|
const outcome = await runCapture(powershell,
|
|
303
377
|
['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', command], {
|
|
304
378
|
timeoutMs: INSTALL_TIMEOUT_MS, signal: job.controller.signal,
|
|
305
379
|
env: { ...process.env, MCODE_INSTALL_DIR: installDir,
|
|
306
|
-
MCODE_NO_MODIFY_PATH: '1',
|
|
307
|
-
MCODE_NPM_REGISTRY:
|
|
308
|
-
npm_config_cache: join(
|
|
380
|
+
MCODE_NO_MODIFY_PATH: '1',
|
|
381
|
+
...(scriptRegistry ? { MCODE_NPM_REGISTRY: scriptRegistry } : {}),
|
|
382
|
+
npm_config_cache: join(staging, 'npm-cache'),
|
|
309
383
|
TEMP: tempPath, TMP: tempPath,
|
|
310
384
|
MODEL_ROUTER_UTILITY_MODULE: utilityModule,
|
|
311
385
|
MODEL_ROUTER_MINIMAX_INSTALL_SCRIPT: scriptPath },
|
|
@@ -319,8 +393,7 @@ async function runOfficialMiniMaxInstaller(job, { reason } = {}) {
|
|
|
319
393
|
if (!outcome.ok) throw new Error(outcome.timedOut
|
|
320
394
|
? '官方安装脚本超时并已终止。'
|
|
321
395
|
: `官方安装脚本退出码 ${outcome.code ?? '信号终止'}。${outcome.stderr.split('\n').find(Boolean)?.slice(0, 180) ?? ''}`)
|
|
322
|
-
|
|
323
|
-
if (!currentPath.split(';').includes(installDir)) process.env.PATH = `${currentPath};${installDir}`
|
|
396
|
+
appendToProcessPath(installDir)
|
|
324
397
|
job.phase = 'verifying'
|
|
325
398
|
probeCache.delete(job.tool.id)
|
|
326
399
|
const probe = await probeToolWith(job.tool, defaultRunner)
|
|
@@ -342,9 +415,9 @@ async function runOfficialMiniMaxInstaller(job, { reason } = {}) {
|
|
|
342
415
|
job.error = `MiniMax 官方安装兜底失败:${String(error?.message ?? error).slice(0, 300)}`
|
|
343
416
|
job.finishedAt = new Date().toISOString()
|
|
344
417
|
} finally {
|
|
345
|
-
if (staging &&
|
|
346
|
-
const part = relative(
|
|
347
|
-
if (part.startsWith('.model-router-minimax-installer-') && !part.includes(sep)) {
|
|
418
|
+
if (staging && stagingParent) {
|
|
419
|
+
const part = relative(stagingParent, staging)
|
|
420
|
+
if (part.startsWith('.model-router-minimax-installer-') && !part.includes(sep) && !part.startsWith('..')) {
|
|
348
421
|
await rm(staging, { recursive: true, force: true }).catch(() => {})
|
|
349
422
|
}
|
|
350
423
|
}
|
|
@@ -359,10 +432,9 @@ async function runOfficialMiniMaxInstaller(job, { reason } = {}) {
|
|
|
359
432
|
* different program.
|
|
360
433
|
*/
|
|
361
434
|
export function verifiedVendorScript(source, verify = {}) {
|
|
362
|
-
const
|
|
363
|
-
const text = bytes.toString('utf8').replace(/^\uFEFF/, '')
|
|
435
|
+
const text = installerScriptText(source)
|
|
364
436
|
if (!text.trim()) throw new Error('下载内容为空,已拒绝执行。')
|
|
365
|
-
if (
|
|
437
|
+
if (isMarkupDocument(text)) throw new Error('下载内容不是安装脚本(收到 HTML 页面),已拒绝执行。')
|
|
366
438
|
// Check "wrong vendor" first: a mirror that substituted another installer is
|
|
367
439
|
// a different diagnosis from a truncated download, and the clearer one.
|
|
368
440
|
const foreign = (verify.mustNotInclude ?? []).filter(marker => text.includes(marker))
|
|
@@ -441,7 +513,13 @@ async function runVendorScriptInstallJob(job, plan) {
|
|
|
441
513
|
: `官方安装脚本退出码 ${outcome.code ?? '信号终止'}。${String(outcome.stderr ?? '').split('\n').find(Boolean)?.slice(0, 200) ?? ''}`)
|
|
442
514
|
}
|
|
443
515
|
const location = resolveInstallLocation(job.tool, plan.method, preferences, currentInstallOptions())
|
|
444
|
-
if (location?.dir)
|
|
516
|
+
if (location?.dir) {
|
|
517
|
+
appendToProcessPath(location.dir)
|
|
518
|
+
for (const relative of location.relatives ?? []) {
|
|
519
|
+
const folder = String(relative).split(/[\\/]/).slice(0, -1).filter(Boolean).join(sep)
|
|
520
|
+
if (folder) appendToProcessPath(join(location.dir, folder))
|
|
521
|
+
}
|
|
522
|
+
}
|
|
445
523
|
job.phase = 'verifying'
|
|
446
524
|
probeCache.delete(job.tool.id)
|
|
447
525
|
const probe = await probeToolWith(job.tool, defaultRunner, { cacheMs: 0 })
|
|
@@ -473,6 +551,15 @@ async function runVendorScriptInstallJob(job, plan) {
|
|
|
473
551
|
* the file must sit inside that tool's install directory, and it can never be a
|
|
474
552
|
* home directory or a filesystem root.
|
|
475
553
|
*/
|
|
554
|
+
/** One relative path under an install directory. `..` never leaves that directory. */
|
|
555
|
+
export function scriptBinaryCandidate(installDir, relative) {
|
|
556
|
+
const parts = String(relative ?? '').split(/[\\/]/).filter(Boolean)
|
|
557
|
+
if (parts.length === 0 || parts.some(part => part === '.' || part === '..')) {
|
|
558
|
+
throw new Error('拒绝删除安装目录之外的文件。')
|
|
559
|
+
}
|
|
560
|
+
return join(installDir, ...parts)
|
|
561
|
+
}
|
|
562
|
+
|
|
476
563
|
export function checkedRemovableFile(candidate, { allowedNames, installDir, home = homedir() }) {
|
|
477
564
|
const target = resolve(candidate)
|
|
478
565
|
const base = target.split(sep).pop() ?? ''
|
|
@@ -502,8 +589,9 @@ async function runVendorScriptUninstallJob(job, plan) {
|
|
|
502
589
|
job.phase = 'removing'
|
|
503
590
|
const location = resolveInstallLocation(job.tool, plan.method, preferences, currentInstallOptions())
|
|
504
591
|
const removed = []
|
|
505
|
-
|
|
506
|
-
|
|
592
|
+
const relatives = location?.relatives?.length ? location.relatives : (location?.binaries ?? [])
|
|
593
|
+
for (const relative of relatives) {
|
|
594
|
+
const candidate = scriptBinaryCandidate(location.dir, relative)
|
|
507
595
|
// checkedRemovableFile re-checks the basename, the install directory and
|
|
508
596
|
// the root/home guards even though every part came from the registry.
|
|
509
597
|
const target = checkedRemovableFile(candidate, {
|
|
@@ -624,9 +712,18 @@ async function probeUncached(tool, runner) {
|
|
|
624
712
|
if (tool.id === 'minimax-code' && IS_WINDOWS) {
|
|
625
713
|
// The official Windows installer keeps mcode under a versioned releases
|
|
626
714
|
// directory. A partial npm install can leave a broken shim earlier on PATH;
|
|
627
|
-
// prefer the checksum-verified installer release in that case.
|
|
715
|
+
// prefer the checksum-verified installer release in that case. Also look at
|
|
716
|
+
// the configured directory and the vendor default, which may not be on PATH
|
|
717
|
+
// yet when the installer was told not to edit the user PATH.
|
|
628
718
|
const { findManagedMiniMaxEntry } = await import('./official-tool-executor.mjs')
|
|
629
|
-
const
|
|
719
|
+
const directed = minimaxOfficialInstallDir(preferences, homedir())
|
|
720
|
+
// The configured directory is the copy updates write, so it wins over an
|
|
721
|
+
// older install that happens to sit earlier on PATH.
|
|
722
|
+
let managed = preferences.installDir
|
|
723
|
+
? await findManagedMiniMaxEntry(process.cwd(), [directed, join(directed, '.minimax-code')])
|
|
724
|
+
: null
|
|
725
|
+
if (!managed) managed = await findManagedMiniMaxEntry(process.cwd())
|
|
726
|
+
if (!managed) managed = await findManagedMiniMaxEntry(process.cwd(), [directed, join(directed, '.minimax-code')])
|
|
630
727
|
if (managed) return { id: tool.id, installed: true, version: managed.version,
|
|
631
728
|
status: 'installed', detail: '已按官方 npm registry 摘要核验 MiniMax 官方 Windows 安装器中的 CLI 文件。',
|
|
632
729
|
bannerLine: `MiniMax Code ${managed.version}` }
|
|
@@ -686,15 +783,17 @@ export const defaultRunner = (executable, args, options = {}) =>
|
|
|
686
783
|
let prefixPromise = null
|
|
687
784
|
|
|
688
785
|
/** Add one directory to this process's PATH unless it is already there. */
|
|
689
|
-
function appendToProcessPath(directory) {
|
|
786
|
+
function appendToProcessPath(directory, { includeBin = true } = {}) {
|
|
690
787
|
const target = String(directory ?? '').trim()
|
|
691
788
|
if (!target || !isAbsolute(target)) return
|
|
692
789
|
const separator = IS_WINDOWS ? ';' : ':'
|
|
693
790
|
const current = process.env.PATH ?? ''
|
|
694
|
-
if (current.split(separator).some(entry => entry.toLowerCase() === target.toLowerCase()))
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
791
|
+
if (!current.split(separator).some(entry => entry.toLowerCase() === target.toLowerCase())) {
|
|
792
|
+
process.env.PATH = current ? `${current}${separator}${target}` : target
|
|
793
|
+
}
|
|
794
|
+
// POSIX package managers put global binaries in <prefix>/bin. One level only:
|
|
795
|
+
// recursing would append bin/bin/bin until the process runs out of memory.
|
|
796
|
+
if (includeBin && !IS_WINDOWS) appendToProcessPath(join(target, 'bin'), { includeBin: false })
|
|
698
797
|
}
|
|
699
798
|
|
|
700
799
|
/**
|
|
@@ -728,6 +827,14 @@ export function ensureNpmPrefixOnPath() {
|
|
|
728
827
|
// A --prefix install lands outside the npm prefix, so it needs its own
|
|
729
828
|
// PATH entry or every later probe reports the tool as missing.
|
|
730
829
|
try { appendToProcessPath(preferences.installDir) } catch { /* environment stays untouched */ }
|
|
830
|
+
// Vendor script defaults, so a one-click install that did not edit the
|
|
831
|
+
// user PATH is still visible to the next probe in this process.
|
|
832
|
+
for (const relative of [
|
|
833
|
+
'.minimax-code', '.local/bin', '.kimi-code/bin', '.grok/bin', '.mimocode/bin',
|
|
834
|
+
'.opencode/bin', '.stepcode/bin', 'AppData/Local/Programs/OpenAI/Codex/bin',
|
|
835
|
+
]) {
|
|
836
|
+
try { appendToProcessPath(join(homedir(), ...relative.split('/'))) } catch { /* skip */ }
|
|
837
|
+
}
|
|
731
838
|
})()
|
|
732
839
|
}
|
|
733
840
|
return prefixPromise
|
|
@@ -841,9 +948,12 @@ function beginJob(tool, { operation, methodId, command, installDir = '', run })
|
|
|
841
948
|
export function planCommandLine(plan, operation = 'install') {
|
|
842
949
|
if (plan.kind === 'script') {
|
|
843
950
|
const piped = plan.shell === 'powershell' ? `irm ${plan.scriptUrl} | iex` : `curl -fsSL ${plan.scriptUrl} | bash`
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
951
|
+
if (operation === 'uninstall') {
|
|
952
|
+
return `删除 ${plan.dir} 下的 ${(plan.binaries ?? []).join('、') || '可执行文件'}`
|
|
953
|
+
}
|
|
954
|
+
if (plan.installDir && plan.installArg?.[0]) return `${piped} ${plan.installArg[0]} ${plan.installDir}`
|
|
955
|
+
if (plan.installDir && plan.installDirEnv) return `${piped}(${plan.installDirEnv}=${plan.installDir})`
|
|
956
|
+
return piped
|
|
847
957
|
}
|
|
848
958
|
return `${plan.manager} ${plan.args.join(' ')}`
|
|
849
959
|
}
|
|
@@ -1089,6 +1199,7 @@ async function runInstallJob(manager, args, job) {
|
|
|
1089
1199
|
installed: before.installed === true,
|
|
1090
1200
|
order,
|
|
1091
1201
|
platform: process.platform,
|
|
1202
|
+
methodId: job.methodId,
|
|
1092
1203
|
})
|
|
1093
1204
|
let triedClaudeUpdate = false
|
|
1094
1205
|
if (plan === 'claude-update-then-registry') {
|
|
@@ -141,14 +141,18 @@ export function parseInstallPreferences(config = {}, { tools = [] } = {}) {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/** Where a script install puts the binary: the user's directory, else the vendor default. */
|
|
144
|
-
export function scriptInstallDir(tool, preferences, { home = '' } = {}) {
|
|
145
|
-
if (!tool
|
|
146
|
-
return preferences?.installDir || vendorScriptDir(tool, { home })
|
|
144
|
+
export function scriptInstallDir(tool, preferences, { home = '', method = null } = {}) {
|
|
145
|
+
if (!vendorScriptRelative(tool, method)) return ''
|
|
146
|
+
return preferences?.installDir || vendorScriptDir(tool, { home, method })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function vendorScriptRelative(tool, method) {
|
|
150
|
+
return String(method?.defaultInstallDir || tool?.defaultScriptInstallDir || '')
|
|
147
151
|
}
|
|
148
152
|
|
|
149
153
|
/** The location the vendor script uses on its own, ignoring any configured directory. */
|
|
150
|
-
export function vendorScriptDir(tool, { home = '' } = {}) {
|
|
151
|
-
return joinPath(home, ...
|
|
154
|
+
export function vendorScriptDir(tool, { home = '', method = null } = {}) {
|
|
155
|
+
return joinPath(home, ...vendorScriptRelative(tool, method).split('/').filter(Boolean))
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
/** argv for a package-manager method, honouring the configured registry and prefix. */
|
|
@@ -176,7 +180,15 @@ function baseUrlOf(scriptUrl) {
|
|
|
176
180
|
function pickMethod(tool, methodId, preferences, platform) {
|
|
177
181
|
const requested = methodId ?? preferences?.methods?.[tool.id]
|
|
178
182
|
if (!requested) {
|
|
179
|
-
|
|
183
|
+
const available = installMethodsFor(tool).filter(method => !installMethodBlockReason(method, platform))
|
|
184
|
+
// An installed Windows MiniMax lives in the official installer layout.
|
|
185
|
+
// npm does not move that copy, so the unpinned one-click update is the
|
|
186
|
+
// vendor PowerShell script. Pinning npm or pnpm still runs that manager.
|
|
187
|
+
if (platform === 'win32' && tool?.id === 'minimax-code' && preferences?.allowScriptInstall !== false) {
|
|
188
|
+
const powershell = available.find(method => method.id === 'script-powershell')
|
|
189
|
+
if (powershell) return powershell
|
|
190
|
+
}
|
|
191
|
+
return available[0] ?? defaultInstallMethod(tool)
|
|
180
192
|
}
|
|
181
193
|
// A named method is never silently swapped. If the tool does not declare it,
|
|
182
194
|
// or it does not run here, the caller gets the reason instead of a surprise.
|
|
@@ -207,7 +219,14 @@ export function resolveInstallPlan(tool, methodId, preferences = {}, { platform
|
|
|
207
219
|
const installDir = preferences?.installDir || ''
|
|
208
220
|
if (method.kind === 'script') {
|
|
209
221
|
const scriptUrl = preferences?.scriptUrls?.[tool.id] || method.scriptUrl
|
|
210
|
-
const target = methodHonoursInstallDir(method) ? scriptInstallDir(tool, preferences, { home }) : ''
|
|
222
|
+
const target = methodHonoursInstallDir(method) ? scriptInstallDir(tool, preferences, { home, method }) : ''
|
|
223
|
+
const notices = []
|
|
224
|
+
if (!target && installDir) {
|
|
225
|
+
notices.push(`${method.label} 无法使用自定义安装目录(官方脚本写死了安装路径),将安装到 ${vendorScriptDir(tool, { home, method })}。`)
|
|
226
|
+
}
|
|
227
|
+
if (method.env?.includes('MCODE_NPM_REGISTRY') && preferences?.registry && !minimaxScriptRegistry(preferences.registry)) {
|
|
228
|
+
notices.push('MiniMax 官方脚本只接受 https://registry.npmjs.org/ 或 https://registry.npmmirror.com/,本次不改写它的 npm 源。')
|
|
229
|
+
}
|
|
211
230
|
return {
|
|
212
231
|
toolId: tool.id,
|
|
213
232
|
method,
|
|
@@ -218,11 +237,11 @@ export function resolveInstallPlan(tool, methodId, preferences = {}, { platform
|
|
|
218
237
|
scriptUrl,
|
|
219
238
|
verify: method.verify,
|
|
220
239
|
installArg: method.installArgs,
|
|
240
|
+
installDirEnv: method.installDirEnv || (method.env?.includes('STEP_INSTALL_DIR') ? 'STEP_INSTALL_DIR' : ''),
|
|
221
241
|
installDir: target,
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
: [],
|
|
242
|
+
relatives: scriptRelatives(tool, method),
|
|
243
|
+
env: scriptEnv(method, { installDir: target, scriptUrl, registry: preferences?.registry }),
|
|
244
|
+
notices,
|
|
226
245
|
}
|
|
227
246
|
}
|
|
228
247
|
return {
|
|
@@ -267,6 +286,16 @@ export function resolveUninstallPlan(tool, methodId, preferences = {}, options =
|
|
|
267
286
|
}
|
|
268
287
|
}
|
|
269
288
|
|
|
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
|
+
|
|
270
299
|
/** Where the tool's binary lives, so a later uninstall can find it. */
|
|
271
300
|
export function resolveInstallLocation(tool, method, preferences = {}, { home = '' } = {}) {
|
|
272
301
|
if (!method) return null
|
|
@@ -274,21 +303,44 @@ export function resolveInstallLocation(tool, method, preferences = {}, { home =
|
|
|
274
303
|
// A method that cannot honour the configured directory always lands in the
|
|
275
304
|
// vendor's own location, whatever the user typed.
|
|
276
305
|
const dir = methodHonoursInstallDir(method)
|
|
277
|
-
? scriptInstallDir(tool, preferences, { home })
|
|
278
|
-
: vendorScriptDir(tool, { home })
|
|
279
|
-
return {
|
|
306
|
+
? scriptInstallDir(tool, preferences, { home, method })
|
|
307
|
+
: vendorScriptDir(tool, { home, method })
|
|
308
|
+
return {
|
|
309
|
+
kind: 'script',
|
|
310
|
+
dir,
|
|
311
|
+
binaries: [...(tool.scriptBinaryNames ?? [])],
|
|
312
|
+
relatives: scriptRelatives(tool, method),
|
|
313
|
+
}
|
|
280
314
|
}
|
|
281
|
-
return { kind: 'package-manager', dir: preferences?.installDir || '', binaries: [] }
|
|
315
|
+
return { kind: 'package-manager', dir: preferences?.installDir || '', binaries: [], relatives: [] }
|
|
282
316
|
}
|
|
283
317
|
|
|
284
|
-
|
|
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 : ''
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function scriptEnv(method, { installDir, scriptUrl, registry }) {
|
|
285
332
|
const env = {}
|
|
286
|
-
|
|
333
|
+
const dirEnv = method.installDirEnv || (method.env?.includes('STEP_INSTALL_DIR') ? 'STEP_INSTALL_DIR' : '')
|
|
334
|
+
if (installDir && dirEnv) env[dirEnv] = installDir
|
|
287
335
|
// The step installers read their release base from this variable, so a mirror
|
|
288
336
|
// URL also moves the binaries and the checksum manifest, not just the script.
|
|
289
337
|
if (method.env?.includes('STEP_RELEASE_BASE_URL') && scriptUrl) {
|
|
290
338
|
const base = baseUrlOf(scriptUrl)
|
|
291
339
|
if (base) env.STEP_RELEASE_BASE_URL = base
|
|
292
340
|
}
|
|
341
|
+
if (method.env?.includes('MCODE_NPM_REGISTRY')) {
|
|
342
|
+
const allowed = minimaxScriptRegistry(registry)
|
|
343
|
+
if (allowed) env.MCODE_NPM_REGISTRY = allowed
|
|
344
|
+
}
|
|
293
345
|
return env
|
|
294
346
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.17.1 — 2026-10-11
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
- **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.
|
|
10
|
+
- **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.
|
|
11
|
+
- **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.
|
|
12
|
+
- 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`.
|
|
13
|
+
|
|
3
14
|
## 0.17.0 — 2026-10-11
|
|
4
15
|
|
|
5
16
|
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 +155,7 @@ Prerelease for DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2, published under
|
|
|
144
155
|
|
|
145
156
|
### Package renamed
|
|
146
157
|
- 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).
|
|
158
|
+
- 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
159
|
|
|
149
160
|
### Official CLI execution (PR #1)
|
|
150
161
|
- 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
|
@@ -18,7 +18,7 @@ Current version: **0.16.4** (`@ljwei-stak/dsh-model-router`). Its declared host
|
|
|
18
18
|
|
|
19
19
|
**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
20
|
|
|
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)
|
|
21
|
+
[简体中文](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
22
|
|
|
23
23
|
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
24
|
|
|
@@ -57,7 +57,7 @@ You can instead install a local `.tgz` archive or its extracted inner `package`
|
|
|
57
57
|
|
|
58
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.16.4`. 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
59
|
|
|
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).
|
|
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](docs/archive/MIGRATION.md).
|
|
61
61
|
|
|
62
62
|
## Use the workbench
|
|
63
63
|
|
package/README.zh.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
**0.15.0 更新**:工作台分为四页,路由建议紧接任务规划展示;执行输入变化时丢弃旧预览;裁剪历史或重试后仍保留费用累计;源码开发新增独立模拟 UI 预览。
|
|
20
20
|
|
|
21
|
-
[English](README.md) · [工作台完整指南](docs/WORKBENCH_USER_GUIDE.zh.md) · [安装与验证](INSTALLATION_GUIDE.zh.md) · [迁移说明](MIGRATION.md) · [更新记录](CHANGELOG.md)
|
|
21
|
+
[English](README.md) · [工作台完整指南](docs/WORKBENCH_USER_GUIDE.zh.md) · [安装与验证](docs/INSTALLATION_GUIDE.zh.md) · [迁移说明](docs/archive/MIGRATION.md) · [更新记录](CHANGELOG.md)
|
|
22
22
|
|
|
23
23
|
需要自行实测时,按[安装验收与质量—成本实验步骤](docs/ROUTING_SELFTEST_STEPS.zh.md)先完成桌面检查和 18 题×3 模型小样,再决定正式样本与费用上限。方案区分首包选模投影和实际团队执行;配套离线入口不采集、不调用模型,真实收益仍待验证。
|
|
24
24
|
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
`@ljwei-stak/model-router-galgame` 的最后版本为 0.13.0。保留 profile 与应用数据,移除旧插件条目,再添加 `@ljwei-stak/dsh-model-router@0.16.4`。不要同时安装两个包:它们共用内部插件 id `model-router-galgame` 与工具名;id 保留使已有路由设置和执行历史能够延续。
|
|
59
59
|
|
|
60
|
-
从 0.11.x 合并版迁移时,先备份 profile 并导出希望保留的 GAL 进度。剧情、立绘、音乐、自由模式和存档属于独立 `@ljwei-stak/dsh-galgame` 插件。导出范围及同 profile 迁移的限制见 [MIGRATION.md](MIGRATION.md)。
|
|
60
|
+
从 0.11.x 合并版迁移时,先备份 profile 并导出希望保留的 GAL 进度。剧情、立绘、音乐、自由模式和存档属于独立 `@ljwei-stak/dsh-galgame` 插件。导出范围及同 profile 迁移的限制见 [MIGRATION.md](docs/archive/MIGRATION.md)。
|
|
61
61
|
|
|
62
62
|
## 工作台的四个页面
|
|
63
63
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
当前路由正式版为 **0.16.4**。本版修复 PATH 探测丢输出:没有安装日志回调时,`where` 和 `--version` 的 stdout 仍会保留,已安装的 CLI 不再被报成未安装。0.16.3 的命令行参数修复、0.16.2 的更新按钮修复和 0.16.1 的反馈缓存仍然保留。公开数据默认关闭;主观偏好调整不改变公共能力评分或质量门槛。源码开发可使用独立模拟 UI 预览;安装时选择下方精确版本。
|
|
6
6
|
|
|
7
|
-
[路由 README](README.zh.md) · [迁移说明](MIGRATION.md) · [独立 GAL README](https://github.com/Alice-Marx/deepseek-harness-galgame#readme)
|
|
7
|
+
[路由 README](../README.zh.md) · [迁移说明](archive/MIGRATION.md) · [独立 GAL README](https://github.com/Alice-Marx/deepseek-harness-galgame#readme)
|
|
8
8
|
|
|
9
9
|
## 1. 选择需要的插件
|
|
10
10
|
|
|
@@ -93,15 +93,15 @@ GAL 核心只包含**《回声之城·正篇》**与**《旧城迁移篇:未
|
|
|
93
93
|
7. 在**预算与安全**查看每日/月度预算、订阅失败策略及可读/写边界;**持续反馈与本地偏好**可设置学习启停、半衰期、收缩强度和调整上限,**动态公共数据**需自行启用。官方工具终端不经过 Harness 沙箱,继承用户登录环境,需单独确认启动。
|
|
94
94
|
8. 需要 CLI 修改文件时进入官方会话、选择工作区,明确请求 `model_router_tool_run` 或 `model_router_team_execute` 的 `workspace-write` 模式。可编辑托管执行要求干净 Git 仓库、宿主审批和进程沙箱。`model_router_execute` 用于只读路由执行,`model_router_consult` 用于模型 API 意见。
|
|
95
95
|
|
|
96
|
-
完整教程见[工作台使用指南](
|
|
96
|
+
完整教程见[工作台使用指南](WORKBENCH_USER_GUIDE.zh.md);路线算法、订阅与数据保存说明见[README](../README.zh.md)。
|
|
97
97
|
|
|
98
98
|
在成功结果上点 👍 / 👎,再次点已选评价可撤回。评价按准确路线和任务类型形成时间衰减、向零收缩的**主观偏好效用**,默认最大幅度 ±0.04,可配置到不超过 ±0.1;不抬高客观能力分或公共质量门槛。模型复核不视作人工评价,模型侧评分工具需要用户审批;本版不发起付费探索。
|
|
99
99
|
|
|
100
|
-
学习范围是同一个本地 **DSH_HOME** 共享的最近 **200 次运行**,不是多账号隔离或无限长期记忆。“从现在重新学习”只让旧结果不再参与学习,不删除执行记录和费用累计。动态价格只读取用户指定的公开、严格结构化、按官方文档策展的 USD/百万 token 固定费率快照;不是自动抓取所有厂商价目表,复杂阶梯/时段计费不能强行折成该格式。人工单价优先;公开数据默认关闭,详情见[动态数据与持续反馈说明](
|
|
100
|
+
学习范围是同一个本地 **DSH_HOME** 共享的最近 **200 次运行**,不是多账号隔离或无限长期记忆。“从现在重新学习”只让旧结果不再参与学习,不删除执行记录和费用累计。动态价格只读取用户指定的公开、严格结构化、按官方文档策展的 USD/百万 token 固定费率快照;不是自动抓取所有厂商价目表,复杂阶梯/时段计费不能强行折成该格式。人工单价优先;公开数据默认关闭,详情见[动态数据与持续反馈说明](ROUTING_ADAPTIVE_LEARNING.zh.md)。
|
|
101
101
|
|
|
102
|
-
注册表包含 Kimi、Claude、Codex、MiniMax、MiMo、Grok、Gemini 和 ZCode。Claude、Codex、MiMo、Grok 支持单工具/团队只读或可编辑执行;Kimi、MiniMax、ZCode 仅支持获批的可编辑托管执行。Gemini 有路由无界面适配器,不进入单工具/团队托管执行器。详见[官方工具与托管模式](README.zh.md#官方工具与托管模式)。CLI 模型名可能不同于 Harness 目录;多数 CLI 不提供可核验的实际模型 ID,要看厂商记录与账单。
|
|
102
|
+
注册表包含 Kimi、Claude、Codex、MiniMax、MiMo、Grok、Gemini 和 ZCode。Claude、Codex、MiMo、Grok 支持单工具/团队只读或可编辑执行;Kimi、MiniMax、ZCode 仅支持获批的可编辑托管执行。Gemini 有路由无界面适配器,不进入单工具/团队托管执行器。详见[官方工具与托管模式](../README.zh.md#官方工具与托管模式)。CLI 模型名可能不同于 Harness 目录;多数 CLI 不提供可核验的实际模型 ID,要看厂商记录与账单。
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
安装使用固定官方来源:npm/pnpm 的 `@latest`,厂商发布的 `curl | bash` / `irm | iex` 脚本(Claude、Codex、MiniMax、Kimi、MiMo、Grok、OpenCode、Step Code,按平台显示),或 ZCode 签名安装器。在**官方工具**里为每个工具选择方式,并填写统一安装目录;一键安装和更新都使用这一选择。npm/pnpm 会带上 `--prefix`。支持目录的脚本把该路径传给厂商自己的变量(MiniMax 是 `MCODE_INSTALL_DIR`)。写死路径的脚本会在该行写明实际落点。未填写目录时,MiniMax Windows 更新使用已有官方目录,否则使用 `%USERPROFILE%\.minimax-code`,不再改放到 npm 全局前缀。Grok 数据目录与登录应使用一致的 `GROK_HOME`。MiniMax Windows 原生依赖失败时,从固定官方 HTTPS 来源下载最新版安装脚本,检查大小、UTF-8/UTF-16 文本和厂商标记后原样执行(PowerShell 的 `<#` 说明头不是 HTML),**没有锁定该安装脚本的哈希**。安装后的 MiniMax CLI 代码另按已安装版本的官方 npm tarball 摘要核验。MiMo/Grok 同样使用官方 npm 内容证明;适用的 Claude/Codex/ZCode Windows 入口验证发布者签名,ZCode 内置脚本还有每个签名构建的首次信任摘要检查。具体规则见[执行器](../.dsh-plugin/shared/official-tool-executor.mjs)和[npm 内容核验](../.dsh-plugin/shared/npm-attestation.mjs)。
|
|
105
105
|
|
|
106
106
|
## 6. GAL 第一次使用
|
|
107
107
|
|
|
@@ -125,7 +125,7 @@ GAL 核心只包含**《回声之城·正篇》**与**《旧城迁移篇:未
|
|
|
125
125
|
| 动态公开数据 | 默认关闭;自行启用后核对快照版本、校验时间及失败/过期状态;人工单价仍优先。 |
|
|
126
126
|
| 可编辑执行 | 在干净测试仓库核对审批、隔离工作区、补丁整合、厂商实际模型与计费。 |
|
|
127
127
|
|
|
128
|
-
历史 0.15.0 的自动测试验证为 220 项通过、3 项 Windows 平台跳过;组件 UI 预览检查使用模拟数据。**0.16.4 的发布验证见[更新记录](CHANGELOG.md)**,不把模拟公开源/模型流和组件预览当作真实付费调用或 Harness Desktop 端到端验收;上表中的实际安装、账号和工作区检查仍需在使用环境中完成。
|
|
128
|
+
历史 0.15.0 的自动测试验证为 220 项通过、3 项 Windows 平台跳过;组件 UI 预览检查使用模拟数据。**0.16.4 的发布验证见[更新记录](../CHANGELOG.md)**,不把模拟公开源/模型流和组件预览当作真实付费调用或 Harness Desktop 端到端验收;上表中的实际安装、账号和工作区检查仍需在使用环境中完成。
|
|
129
129
|
|
|
130
130
|
## 8. 开发者从完整源码构建
|
|
131
131
|
|
|
@@ -145,4 +145,4 @@ pnpm 10 没有 `pnpm peers check`;使用严格依赖安装。客户端修改
|
|
|
145
145
|
|
|
146
146
|
## 历史兼容提醒
|
|
147
147
|
|
|
148
|
-
合并版 **0.11.1 / 0.11.0** 声明 rc.1 与 rc.2;**0.10.2** 是未发布的本地兼容修复。已发布 **0.10.1** 仅声明 rc.1,rc.2 拒绝安装是 peer 依赖问题,换镜像不能修复;**0.8.0** 使用 0.1.7-rc.2 依赖,不用于当前宿主。历史 Git 标签和安装包保留,详见[更新记录](CHANGELOG.md)和[迁移说明](MIGRATION.md)。
|
|
148
|
+
合并版 **0.11.1 / 0.11.0** 声明 rc.1 与 rc.2;**0.10.2** 是未发布的本地兼容修复。已发布 **0.10.1** 仅声明 rc.1,rc.2 拒绝安装是 peer 依赖问题,换镜像不能修复;**0.8.0** 使用 0.1.7-rc.2 依赖,不用于当前宿主。历史 Git 标签和安装包保留,详见[更新记录](../CHANGELOG.md)和[迁移说明](archive/MIGRATION.md)。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 模型路由工作台:配置、规划与实际执行
|
|
2
2
|
|
|
3
|
-
适用版本:正式版 `@ljwei-stak/dsh-model-router@0.16.0`;声明兼容 DeepSeek Harness Desktop `0.2.0-rc.1 / 0.2.0-rc.2`。安装与开发见[中文 README](../README.zh.md),旧包迁移见[迁移说明](
|
|
3
|
+
适用版本:正式版 `@ljwei-stak/dsh-model-router@0.16.0`;声明兼容 DeepSeek Harness Desktop `0.2.0-rc.1 / 0.2.0-rc.2`。安装与开发见[中文 README](../README.zh.md),旧包迁移见[迁移说明](archive/MIGRATION.md)。
|
|
4
4
|
|
|
5
5
|
0.15.0 新增四页布局、规划后直接展示路由建议、执行输入变化时丢弃旧预览,以及历史裁剪和重试后的费用累计保留。源码中的独立 UI 预览使用模拟数据,用于组件检查,不代表真实账号调用或桌面端到端验收。
|
|
6
6
|
|
|
@@ -50,4 +50,4 @@
|
|
|
50
50
|
| 剧情与自由对话 | 独立 GAL 管理故事、立绘、音乐、设置和存档;自由模式经宿主模型服务调用,可能计费。 |
|
|
51
51
|
| 更新 | 通过官方插件管理器分别安装各自的新版本;路由正式版发布在 npm `latest`(预发布版在 `next`),推荐精确版本。 |
|
|
52
52
|
|
|
53
|
-
登录、厂商实际模型和账单仍由账号持有人验收。完整步骤与安装后的检查见 [INSTALLATION_GUIDE.zh.md](INSTALLATION_GUIDE.zh.md),GAL 播放器教程见[独立仓库](https://github.com/Alice-Marx/deepseek-harness-galgame#readme)。
|
|
53
|
+
登录、厂商实际模型和账单仍由账号持有人验收。完整步骤与安装后的检查见 [INSTALLATION_GUIDE.zh.md](../INSTALLATION_GUIDE.zh.md),GAL 播放器教程见[独立仓库](https://github.com/Alice-Marx/deepseek-harness-galgame#readme)。
|