@ljwei-stak/dsh-model-router 0.17.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh-plugin/client.js +244 -178
- package/.dsh-plugin/index.mjs +1 -1
- package/.dsh-plugin/official-tools-remote-service.mjs +46 -10
- package/.dsh-plugin/shared/official-tool-executor.mjs +135 -47
- package/.dsh-plugin/shared/official-tool-registry.mjs +113 -150
- package/.dsh-plugin/shared/official-tools-runtime.mjs +419 -222
- package/.dsh-plugin/shared/tool-install-preferences.mjs +77 -73
- package/.dsh-plugin/shared/vendor-mimo-grok-adapter.mjs +41 -16
- package/CHANGELOG.md +11 -1
- package/README.md +17 -8
- package/README.zh.md +17 -8
- package/docs/INSTALLATION_GUIDE.zh.md +14 -14
- package/docs/WORKBENCH_USER_GUIDE.zh.md +27 -5
- package/package.json +2 -2
package/.dsh-plugin/index.mjs
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
283
|
-
await ensureNpmPrefixOnPath()
|
|
284
|
-
const
|
|
285
|
-
|
|
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,
|
|
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 ?
|
|
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
|
|
346
|
-
if (
|
|
347
|
-
|
|
348
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
478
|
-
const
|
|
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
|
-
|
|
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 }
|