@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.
@@ -1437,7 +1437,7 @@ export function apply(ctx, config = {}) {
1437
1437
  // live in volatile config, so re-apply picks up whatever the user last saved.
1438
1438
  // A malformed value is reported here rather than at the first click.
1439
1439
  let installPreferenceError = null
1440
- try { setInstallPreferences(parseInstallPreferences(config, { tools: OFFICIAL_TOOLS })) }
1440
+ try { setInstallPreferences(parseInstallPreferences(config, { tools: OFFICIAL_TOOLS, ...currentInstallOptions() })) }
1441
1441
  catch (error) { installPreferenceError = errorText(error) }
1442
1442
  // The official Host injects typert; direct lightweight uses of apply may
1443
1443
  // supply only the model/command services and do not expose the Desktop RPC.
@@ -1,8 +1,8 @@
1
1
  /** Host receiver for the official desktop's typed one-click installer RPC. */
2
2
  import { readFileSync } from 'node:fs'
3
3
  import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol'
4
- import { getOfficialTool, installMethodBlockReason, OFFICIAL_TOOLS } from './shared/official-tool-registry.mjs'
5
- import { resolveInstallPlan, resolveUninstallPlan } from './shared/tool-install-preferences.mjs'
4
+ import { getOfficialTool, installCommandLine, installMethodBlockReason, OFFICIAL_TOOLS } from './shared/official-tool-registry.mjs'
5
+ import { resolveInstallLocation, resolveInstallPlan, resolveUninstallPlan } from './shared/tool-install-preferences.mjs'
6
6
  import { officialToolExecutionCapabilities, officialToolReadiness } from './shared/official-tool-executor.mjs'
7
7
  import {
8
8
  cancelInstall,
@@ -41,8 +41,7 @@ const unavailable = () => { throw new Error('模型路由工作台服务尚未
41
41
  * command line that would actually run. The panel shows this before the user
42
42
  * clicks, so an install is never a surprise command.
43
43
  */
44
- function installSummary() {
45
- const preferences = installPreferences()
44
+ export function installSummary(preferences = installPreferences(), options = currentInstallOptions()) {
46
45
  return {
47
46
  installDir: preferences.installDir ?? '',
48
47
  registry: preferences.registry ?? '',
@@ -56,22 +55,59 @@ function installSummary() {
56
55
  command: null,
57
56
  uninstallCommand: null,
58
57
  methodId: null,
59
- // What the user pinned, which may differ from the effective method when
60
- // the pinned one does not run on this platform.
58
+ // A pinned unavailable method stays selected and reports its reason.
61
59
  savedMethodId: preferences.methods?.[tool.id] ?? null,
62
60
  notices: [],
63
61
  }
62
+ if (tool.manager === 'signed-windows-installer') {
63
+ entry.methodId = 'signed-windows-installer'
64
+ if (options.platform === 'win32') {
65
+ entry.command = installCommandLine(tool)
66
+ entry.updateCommand = entry.command
67
+ entry.notices = [preferences.installDir
68
+ ? `请在官方安装窗口手动选择 ${preferences.installDir},完成后重新体检。`
69
+ : '请在官方安装窗口选择目录并完成安装,然后重新体检。']
70
+ } else entry.installError = entry.error = 'ZCode 签名安装器仅支持 Windows。'
71
+ entry.uninstallError = 'ZCode 是桌面安装版,请在系统“应用”中卸载它。'
72
+ return entry
73
+ }
64
74
  for (const method of tool.installMethods ?? []) {
65
- entry.methods.push({ id: method.id, label: method.label, kind: method.kind, blocked: installMethodBlockReason(method) })
75
+ const item = { id: method.id, label: method.label, kind: method.kind,
76
+ supportsInstallDir: method.supportsInstallDir === true,
77
+ blocked: installMethodBlockReason(method, options.platform) }
78
+ try {
79
+ const plan = resolveInstallPlan(tool, method.id, preferences, options)
80
+ item.command = planCommandLine(plan)
81
+ item.installDir = resolveInstallLocation(tool, method, preferences, options)?.dir ?? ''
82
+ item.notices = plan.notices ?? []
83
+ } catch (error) {
84
+ item.blocked = errorText(error).slice(0, 200)
85
+ }
86
+ try {
87
+ const removal = resolveUninstallPlan(tool, method.id, preferences, options)
88
+ item.uninstallCommand = planCommandLine(removal, 'uninstall')
89
+ } catch (error) { item.uninstallError = errorText(error).slice(0, 200) }
90
+ entry.methods.push(item)
66
91
  }
67
92
  try {
68
- const plan = resolveInstallPlan(tool, undefined, preferences, currentInstallOptions())
93
+ const plan = resolveInstallPlan(tool, undefined, preferences, options)
69
94
  entry.methodId = plan.methodId
70
95
  entry.command = planCommandLine(plan)
96
+ entry.installDir = resolveInstallLocation(tool, plan.method, preferences, options)?.dir ?? ''
97
+ entry.updateMethodId = plan.methodId
98
+ entry.updateCommand = entry.command
99
+ entry.updateInstallDir = entry.installDir
71
100
  entry.notices = plan.notices ?? []
72
- entry.uninstallCommand = planCommandLine(resolveUninstallPlan(tool, plan.methodId, preferences, currentInstallOptions()), 'uninstall')
73
101
  } catch (error) {
74
- entry.error = errorText(error).slice(0, 200)
102
+ entry.installError = entry.error = errorText(error).slice(0, 200)
103
+ }
104
+ try {
105
+ const removal = resolveUninstallPlan(tool, undefined, preferences, options)
106
+ entry.methodId ??= removal.methodId
107
+ entry.installDir ??= resolveInstallLocation(tool, removal.method, preferences, options)?.dir ?? ''
108
+ entry.uninstallCommand = planCommandLine(removal, 'uninstall')
109
+ } catch (error) {
110
+ entry.uninstallError = errorText(error).slice(0, 200)
75
111
  }
76
112
  return entry
77
113
  }),
@@ -18,13 +18,14 @@ import { access, readFile, realpath, stat } from 'node:fs/promises'
18
18
  import { constants } from 'node:fs'
19
19
  import { StringDecoder } from 'node:string_decoder'
20
20
  import { basename, delimiter, isAbsolute, join, relative, resolve, sep } from 'node:path'
21
- import { getOfficialTool, OFFICIAL_TOOLS } from './official-tool-registry.mjs'
22
- import { ensureNpmPrefixOnPath } from './official-tools-runtime.mjs'
21
+ import { getOfficialTool, installMethodFor, OFFICIAL_TOOLS } from './official-tool-registry.mjs'
22
+ import { currentInstallOptions, ensureNpmPrefixOnPath, installPreferences, preferredToolDirectories, usesConfiguredInstallPlan } from './official-tools-runtime.mjs'
23
+ import { resolveInstallPlan } from './tool-install-preferences.mjs'
23
24
  import { buildMiniMaxInvocation, createMiniMaxStreamParser } from './vendor-minimax-adapter.mjs'
24
25
  import { discoverZCodeBundle } from './zcode-bundle.mjs'
25
26
  import { npmAttestor } from './npm-attestation.mjs'
26
27
  import { compareReleaseVersions, isReleaseVersion } from './latest-versions.mjs'
27
- import { resolveMiMoGrokLaunch, createMiMoGrokParser } from './vendor-mimo-grok-adapter.mjs'
28
+ import { resolveMiMoGrokLaunch, createMiMoGrokParser, installedModuleRoots } from './vendor-mimo-grok-adapter.mjs'
28
29
  import { emptyOutputError, outputIsEmpty, usageFromEvents, usageFromOutput } from './task-executors.mjs'
29
30
 
30
31
  const IS_WINDOWS = process.platform === 'win32'
@@ -169,6 +170,22 @@ function pathDirectories() {
169
170
  return [...new Set(value.split(delimiter).map(part => part.trim().replace(/^"|"$/g, '')).filter(Boolean))]
170
171
  }
171
172
 
173
+ /** The configured install location is authoritative even if another copy is newer. */
174
+ export function officialToolEntrySelection(toolId, { preferences = installPreferences(), plan: selectedPlan, preferredDirs } = {}) {
175
+ const tool = getOfficialTool(toolId)
176
+ if (!tool || tool.manager === 'signed-windows-installer') return { directories: pathDirectories(), plan: null, configured: false }
177
+ // A saved script choice stays usable after future script installs are
178
+ // disabled. Automatic selection still follows the same switch as installation.
179
+ const selectedScript = installMethodFor(tool, preferences.methods?.[toolId])?.kind === 'script'
180
+ const discoveryPreferences = selectedScript ? { ...preferences, allowScriptInstall: true } : preferences
181
+ const plan = selectedPlan ?? resolveInstallPlan(tool, undefined, discoveryPreferences, currentInstallOptions())
182
+ const preferred = preferredDirs ?? (selectedPlan ? (selectedPlan.binDir ? [selectedPlan.binDir] : [])
183
+ : preferredToolDirectories(toolId, discoveryPreferences))
184
+ return { directories: Array.isArray(preferredDirs) || preferred.length ? [...preferred] : pathDirectories(), plan,
185
+ configured: Boolean(selectedPlan) || usesConfiguredInstallPlan(toolId, preferences),
186
+ preferred: Array.isArray(preferredDirs) || preferred.length > 0, directoryOverride: Array.isArray(preferredDirs) }
187
+ }
188
+
172
189
  function versionAtLeast(value, minimum) {
173
190
  const found = /^(\d+)\.(\d+)\.(\d+)(?:$|-)/.exec(String(value ?? ''))
174
191
  if (!found) return false
@@ -216,27 +233,35 @@ async function readPackageEntry(packageDirectory, packageName, binName, expected
216
233
  }
217
234
 
218
235
  /** Resolve the package's real `bin` file, never its Windows .cmd shim. */
219
- async function findGlobalPackageEntries(packageName, binName, expectedBin, workspace) {
220
- await ensureNpmPrefixOnPath()
236
+ export async function findGlobalPackageEntries(packageName, binName, expectedBin, workspace, { directories, manager = null, moduleRoots, selection: selectedSelection } = {}) {
237
+ const tool = OFFICIAL_TOOLS.find(item => item.package === packageName)
238
+ const selection = selectedSelection ?? (directories || Array.isArray(moduleRoots) ? null : officialToolEntrySelection(tool?.id))
239
+ const exactRoots = Array.isArray(moduleRoots) ? moduleRoots : selection?.plan?.packageModuleRoots
240
+ if (!Array.isArray(exactRoots) && !directories && !selection?.preferred) await ensureNpmPrefixOnPath()
241
+ const targets = directories ?? (selection?.preferred ? selection.directories : pathDirectories())
242
+ const selectedManager = manager ?? selection?.plan?.manager ?? null
221
243
  const packageParts = packageName.split('/')
222
244
  const found = []
223
245
  const seen = new Set()
224
- for (const directory of pathDirectories()) {
225
- const moduleParents = [join(directory, 'node_modules')]
226
- if (basename(directory).toLowerCase() === '.bin') moduleParents.push(resolve(directory, '..'))
227
- if (!IS_WINDOWS) moduleParents.push(resolve(directory, '..', 'lib', 'node_modules'))
228
- const candidates = moduleParents.map(modules => join(modules, ...packageParts))
229
- // Native installs put the exe in `<package>/bin` and that directory on PATH
230
- // (for example D:\Program Files\Claude\node_modules\@anthropic-ai\claude-code\bin).
231
- candidates.push(directory, resolve(directory, '..'))
232
- for (const packageDirectory of candidates) {
233
- const entry = await readPackageEntry(packageDirectory, packageName, binName, expectedBin, workspace, seen)
234
- if (entry) found.push(entry)
235
- }
246
+ const roots = Array.isArray(exactRoots) ? exactRoots : await installedModuleRoots(targets, { manager: selectedManager })
247
+ const candidates = roots.map(modules => join(modules, ...packageParts))
248
+ // Native npm installs can put their package/bin directory directly on PATH.
249
+ if (!Array.isArray(exactRoots) && selectedManager !== 'pnpm') candidates.push(...targets.flatMap(directory => [directory, resolve(directory, '..')]))
250
+ for (const packageDirectory of candidates) {
251
+ const entry = await readPackageEntry(packageDirectory, packageName, binName, expectedBin, workspace, seen)
252
+ if (entry) found.push(entry)
236
253
  }
237
254
  return found
238
255
  }
239
256
 
257
+ /** Official Windows repairs use a distinct directory key for the same npm version. */
258
+ export function miniMaxReleaseVersion(releaseKey) {
259
+ const repair = /^(\d{1,6}\.\d{1,6}\.\d{1,6})-repair-[1-9]\d{0,9}-[0-9a-f]{32}$/i.exec(String(releaseKey ?? ''))
260
+ if (repair) return repair[1]
261
+ if (String(releaseKey ?? '').includes('-repair-')) return null
262
+ return isReleaseVersion(releaseKey) ? releaseKey : null
263
+ }
264
+
240
265
  /**
241
266
  * The official MiniMax Windows installer uses a versioned releases tree rather
242
267
  * than npm -g. Any release named by its `current` file is accepted, but every
@@ -252,10 +277,13 @@ export async function findManagedMiniMaxEntry(workspace,
252
277
  const root = await realpath(directory)
253
278
  if (root === workspace || inside(workspace, root)) continue
254
279
  if (!(await stat(join(root, 'mcode.cmd'))).isFile()) continue
255
- const version = (await readFile(join(root, 'current'), 'utf8')).trim()
256
- if (!isReleaseVersion(version)) continue
257
- const releaseRoot = await realpath(join(root, 'releases', version))
258
- if (!inside(root, releaseRoot)) continue
280
+ const releaseKey = (await readFile(join(root, 'current'), 'utf8')).trim()
281
+ const version = miniMaxReleaseVersion(releaseKey)
282
+ if (!version) continue
283
+ const releasesRoot = await realpath(join(root, 'releases'))
284
+ if (!inside(root, releasesRoot)) continue
285
+ const releaseRoot = await realpath(join(releasesRoot, releaseKey))
286
+ if (!inside(releasesRoot, releaseRoot)) continue
259
287
  const packageRoot = await realpath(join(releaseRoot, 'node_modules', '@minimax-ai', 'code'))
260
288
  if (!inside(releaseRoot, packageRoot)) continue
261
289
  const manifest = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8'))
@@ -268,7 +296,7 @@ export async function findManagedMiniMaxEntry(workspace,
268
296
  const native = await realpath(join(packageRoot, 'node_modules', 'better-sqlite3',
269
297
  'build', 'Release', 'better_sqlite3.node'))
270
298
  if (!inside(packageRoot, native) || !(await stat(native)).isFile()) continue
271
- return { entry, packageRoot, version, source: 'official-windows-installer-npm-attested' }
299
+ return { entry, packageRoot, version, installDir: root, binDir: root, source: 'official-windows-installer-npm-attested' }
272
300
  } catch { /* inspect the next PATH directory */ }
273
301
  }
274
302
  return null
@@ -279,24 +307,25 @@ function newestFirst(entries) {
279
307
  return [...entries].sort((left, right) => (compareReleaseVersions(right.version, left.version) ?? 0))
280
308
  }
281
309
 
282
- async function findCodexExecutable(workspace) {
283
- await ensureNpmPrefixOnPath()
284
- const filename = IS_WINDOWS ? 'codex.exe' : 'codex'
285
- for (const directory of pathDirectories()) {
310
+ async function findSignedNativeExecutable(toolId, workspace, signer, selection = officialToolEntrySelection(toolId)) {
311
+ if (!selection.preferred) await ensureNpmPrefixOnPath()
312
+ const command = toolId === 'claude-code' ? 'claude' : toolId
313
+ const filename = IS_WINDOWS ? `${command}.exe` : command
314
+ for (const directory of selection.preferred ? selection.directories : pathDirectories()) {
286
315
  try {
287
316
  const pathRoot = await realpath(directory)
288
317
  if (pathRoot === workspace || inside(workspace, pathRoot)) continue
289
318
  const entry = await realpath(join(directory, filename))
290
319
  if (inside(workspace, entry) || !(await stat(entry)).isFile()) continue
291
320
  await access(entry, IS_WINDOWS ? constants.F_OK : constants.X_OK)
292
- if (IS_WINDOWS && !(await hasOfficialWindowsSignature(entry, CODEX_SIGNER))) continue
321
+ if (IS_WINDOWS && !(await hasOfficialWindowsSignature(entry, signer))) continue
293
322
  return { entry, source: IS_WINDOWS ? 'signed-native-executable' : 'path-native-executable' }
294
323
  } catch { /* try the next PATH entry */ }
295
324
  }
296
325
  return null
297
326
  }
298
327
 
299
- function nodeVersion(entry) {
328
+ function nodeVersion(entry, pattern = /^v(\d+\.\d+\.\d+)/) {
300
329
  return new Promise(resolveVersion => {
301
330
  let child
302
331
  try { child = spawn(entry, ['--version'], { shell: false, windowsHide: true, stdio: ['ignore', 'pipe', 'ignore'] }) }
@@ -312,7 +341,7 @@ function nodeVersion(entry) {
312
341
  const timer = setTimeout(() => { child.kill('SIGTERM'); finish(null) }, 4_000)
313
342
  child.stdout.on('data', bytes => { output = tail(output, String(bytes)) })
314
343
  child.on('error', () => finish(null))
315
- child.on('close', code => finish(code === 0 ? /^v(\d+\.\d+\.\d+)/.exec(output.trim())?.[1] ?? null : null))
344
+ child.on('close', code => finish(code === 0 ? pattern.exec(output.trim())?.[1] ?? null : null))
316
345
  })
317
346
  }
318
347
 
@@ -335,17 +364,53 @@ async function findNodeExecutable(workspace, minimum = [22, 19, 0], acceptedVers
335
364
  }
336
365
 
337
366
  /** Resolve the signed native binary carried by the official npm wrapper. */
338
- async function findSignedPackagedCodex(packageRoot, workspace) {
367
+ export async function findSignedPackagedCodex(packageRoot, workspace, {
368
+ installDir = '', manager = null, moduleRoots = [], verifySignature = hasOfficialWindowsSignature,
369
+ } = {}) {
339
370
  if (!IS_WINDOWS) return null
340
371
  const target = process.arch === 'arm64' ? ['codex-win32-arm64', 'aarch64-pc-windows-msvc']
341
372
  : process.arch === 'x64' ? ['codex-win32-x64', 'x86_64-pc-windows-msvc'] : null
342
373
  if (!target) return null
343
- const candidate = join(packageRoot, 'node_modules', '@openai', target[0], 'vendor', target[1], 'bin', 'codex.exe')
344
374
  try {
345
- const entry = await realpath(candidate)
346
- if (!inside(packageRoot, entry) || inside(workspace, entry) || !(await stat(entry)).isFile()) return null
347
- if (!(await hasOfficialWindowsSignature(entry, CODEX_SIGNER))) return null
348
- return { entry, source: 'signed-official-npm-native' }
375
+ const wrapperRoot = await realpath(packageRoot)
376
+ if (wrapperRoot === workspace || inside(workspace, wrapperRoot)) return null
377
+ const wrapper = JSON.parse(await readFile(join(wrapperRoot, 'package.json'), 'utf8'))
378
+ const nativeName = `@openai/${target[0]}`
379
+ const dependency = wrapper.optionalDependencies?.[nativeName]
380
+ const alias = /^npm:(@openai\/[A-Za-z0-9-]+)@(.+)$/.exec(String(dependency ?? ''))
381
+ const expectedName = alias ? alias[1] : nativeName
382
+ const expectedVersion = alias ? alias[2] : dependency
383
+ if (wrapper.name !== '@openai/codex' || !isReleaseVersion(wrapper.version)
384
+ || !['@openai/codex', nativeName].includes(expectedName) || !isReleaseVersion(expectedVersion)) return null
385
+ let virtualStore = null
386
+ for (let directory = wrapperRoot; directory !== resolve(directory, '..'); directory = resolve(directory, '..')) {
387
+ if (basename(directory) === '.pnpm') { virtualStore = directory; break }
388
+ }
389
+ // pnpm's optional platform package is a sibling symlink in its virtual
390
+ // store. A configured prefix confines both wrapper and native package to
391
+ // that prefix's global tree, never another store or arbitrary directory.
392
+ const boundary = manager === 'pnpm' && installDir
393
+ ? await realpath(join(installDir, '.pnpm', 'global'))
394
+ : virtualStore ?? await realpath(resolve(wrapperRoot, '..', '..'))
395
+ if (!inside(boundary, wrapperRoot)) return null
396
+ const nativePackages = [
397
+ join(wrapperRoot, 'node_modules', '@openai', target[0]),
398
+ resolve(wrapperRoot, '..', target[0]),
399
+ ...moduleRoots.map(modules => join(modules, '@openai', target[0])),
400
+ ]
401
+ for (const nativePackage of nativePackages) {
402
+ try {
403
+ const nativeRoot = await realpath(nativePackage)
404
+ if (!inside(boundary, nativeRoot) || inside(workspace, nativeRoot)) continue
405
+ const manifest = JSON.parse(await readFile(join(nativeRoot, 'package.json'), 'utf8'))
406
+ if (manifest.name !== expectedName || manifest.version !== expectedVersion) continue
407
+ const entry = await realpath(join(nativeRoot, 'vendor', target[1], 'bin', 'codex.exe'))
408
+ if (!inside(nativeRoot, entry) || inside(workspace, entry) || !(await stat(entry)).isFile()) continue
409
+ if (!(await verifySignature(entry, CODEX_SIGNER))) continue
410
+ return { entry, source: 'signed-official-npm-native' }
411
+ } catch { /* try the wrapper's other official optional-package location */ }
412
+ }
413
+ return null
349
414
  } catch { return null }
350
415
  }
351
416
 
@@ -404,13 +469,18 @@ function hasOfficialWindowsSignature(entry, expectedSigner) {
404
469
  })
405
470
  }
406
471
 
407
- async function launchSpec(toolId, workspace, mode, modelId, task = 'Check readiness.') {
472
+ async function launchSpec(toolId, workspace, mode, modelId, task = 'Check readiness.', selectionOptions = {}) {
408
473
  if (toolId === 'claude-code') {
409
- const entries = await findGlobalPackageEntries('@anthropic-ai/claude-code', 'claude', 'bin/claude.exe', workspace)
474
+ const selection = officialToolEntrySelection(toolId, selectionOptions)
475
+ const native = selection.plan.kind === 'script'
476
+ ? await findSignedNativeExecutable(toolId, workspace, CLAUDE_SIGNER, selection) : null
477
+ if (native) native.version = await nodeVersion(native.entry, /(?:^|\s)(\d+\.\d+\.\d+(?:-[\w.-]+)?)/)
478
+ const entries = selection.plan.kind === 'script' ? (native ? [native] : [])
479
+ : await findGlobalPackageEntries('@anthropic-ai/claude-code', 'claude', 'bin/claude.exe', workspace, { selection })
410
480
  const compatible = entries.filter(entry => versionAtLeast(entry.version, CLAUDE_MIN_RESTRICTED_VERSION))
411
481
  let found = null
412
482
  for (const entry of compatible) {
413
- if (!IS_WINDOWS || await hasOfficialWindowsSignature(entry.entry, CLAUDE_SIGNER)) {
483
+ if (entry === native || !IS_WINDOWS || await hasOfficialWindowsSignature(entry.entry, CLAUDE_SIGNER)) {
414
484
  found = entry
415
485
  break
416
486
  }
@@ -440,13 +510,20 @@ async function launchSpec(toolId, workspace, mode, modelId, task = 'Check readin
440
510
  }
441
511
  if (toolId === 'codex') {
442
512
  if (!IS_WINDOWS) return { unsupported: CAPABILITIES.codex.reason }
443
- const packages = await findGlobalPackageEntries('@openai/codex', 'codex', 'bin/codex.js', workspace)
513
+ const selection = officialToolEntrySelection(toolId, selectionOptions)
514
+ const packages = selection.plan.kind === 'script' ? []
515
+ : await findGlobalPackageEntries('@openai/codex', 'codex', 'bin/codex.js', workspace, { selection })
516
+ const moduleRoots = selection.plan.packageModuleRoots ?? (packages.length
517
+ ? await installedModuleRoots(selection.preferred ? selection.directories : pathDirectories(), { manager: selection.plan.manager }) : [])
444
518
  let found = null
445
519
  for (const entry of packages) {
446
- found = await findSignedPackagedCodex(entry.packageRoot, workspace)
520
+ found = await findSignedPackagedCodex(entry.packageRoot, workspace,
521
+ { installDir: selection.plan.installDir, manager: selection.plan.manager, moduleRoots })
447
522
  if (found) break
448
523
  }
449
- found ??= await findCodexExecutable(workspace)
524
+ if (!found && selection.plan.kind === 'script') {
525
+ found = await findSignedNativeExecutable(toolId, workspace, CODEX_SIGNER, selection)
526
+ }
450
527
  if (!found) return { unsupported: '未找到具有 OpenAI 有效签名的 codex.exe。' }
451
528
  return {
452
529
  file: found.entry,
@@ -456,8 +533,10 @@ async function launchSpec(toolId, workspace, mode, modelId, task = 'Check readin
456
533
  }
457
534
  }
458
535
  if (toolId === 'kimi-code') {
536
+ const selection = officialToolEntrySelection(toolId, selectionOptions)
537
+ if (selection.plan.kind === 'script') return { unsupported: '所选 Kimi 原生脚本安装没有可核验的 npm 执行入口;不会改用其他安装目录中的 Kimi。' }
459
538
  const tool = getOfficialTool(toolId)
460
- const entries = await findGlobalPackageEntries(tool.package, 'kimi', 'dist/main.mjs', workspace)
539
+ const entries = await findGlobalPackageEntries(tool.package, 'kimi', 'dist/main.mjs', workspace, { selection })
461
540
  const found = newestFirst(entries)[0]
462
541
  if (!found) return { unsupported: `未找到官方 npm 包 ${tool.package} 的 kimi 入口(dist/main.mjs)。` }
463
542
  const nodeExecutable = await findNodeExecutable(workspace)
@@ -474,8 +553,12 @@ async function launchSpec(toolId, workspace, mode, modelId, task = 'Check readin
474
553
  }
475
554
  if (toolId === 'minimax-code') {
476
555
  const tool = getOfficialTool(toolId)
477
- const entries = await findGlobalPackageEntries(tool.package, 'mcode', 'cli.js', workspace)
478
- const found = newestFirst(entries)[0] ?? await findManagedMiniMaxEntry(workspace)
556
+ const selection = officialToolEntrySelection(toolId, selectionOptions)
557
+ const managed = selection.plan.kind === 'script' || selection.directoryOverride
558
+ ? await findManagedMiniMaxEntry(workspace, selection.directories)
559
+ : !selection.configured ? await findManagedMiniMaxEntry(workspace) : null
560
+ const entries = selection.plan.kind === 'script' ? [] : await findGlobalPackageEntries(tool.package, 'mcode', 'cli.js', workspace, { selection })
561
+ const found = managed ?? newestFirst(entries)[0]
479
562
  if (!found) return { unsupported: `未找到官方 npm 包 ${tool.package} 的 mcode 入口(cli.js)。` }
480
563
  const nodeExecutable = await findNodeExecutable(workspace, [22, 19, 0], version => {
481
564
  const major = Number(String(version).split('.')[0])
@@ -492,7 +575,12 @@ async function launchSpec(toolId, workspace, mode, modelId, task = 'Check readin
492
575
  }
493
576
  if (toolId === 'mimo-code' || toolId === 'grok-build') {
494
577
  try {
495
- return await resolveMiMoGrokLaunch({ toolId, workspace, mode, modelId, task })
578
+ const selection = officialToolEntrySelection(toolId, selectionOptions)
579
+ if (selection.plan.kind === 'script') return { unsupported: `所选 ${getOfficialTool(toolId).label} 原生脚本安装没有可核验的 npm 元数据;不会改用其他安装目录中的工具。` }
580
+ return await resolveMiMoGrokLaunch({ toolId, workspace, mode, modelId, task,
581
+ directories: selection.preferred ? selection.directories : undefined,
582
+ moduleRoots: selection.plan.packageModuleRoots,
583
+ manager: selection.plan.manager })
496
584
  } catch (error) {
497
585
  return { unsupported: `官方原生入口校验失败:${String(error?.message ?? error).slice(0, 240)}` }
498
586
  }
@@ -586,14 +674,14 @@ export function harnessSandboxBlocker(toolId, platform = process.platform) {
586
674
  }
587
675
 
588
676
  /** Check the actual trusted launch entry without starting an account call. */
589
- export async function officialToolReadiness(toolId, workspace = process.cwd()) {
677
+ export async function officialToolReadiness(toolId, workspace = process.cwd(), selectionOptions = {}) {
590
678
  const tool = getOfficialTool(toolId)
591
679
  if (!tool) return { id: String(toolId), ready: false, reason: '工具不在固定官方注册表中。' }
592
680
  const capability = CAPABILITIES[tool.id]
593
681
  if (!capability?.supported) return { id: tool.id, ready: false, reason: capability?.reason ?? '当前平台不支持托管执行。' }
594
682
  try {
595
683
  const directory = await checkedWorkspace(workspace)
596
- const spec = await launchSpec(tool.id, directory, capability.modes[0], null)
684
+ const spec = await launchSpec(tool.id, directory, capability.modes[0], null, undefined, selectionOptions)
597
685
  return spec.unsupported
598
686
  ? { id: tool.id, ready: false, reason: spec.unsupported }
599
687
  : { id: tool.id, ready: true, source: spec.source, modes: capability.modes }