@ljwei-stak/dsh-model-router 0.18.1 → 0.18.2
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 +34 -5
- package/.dsh-plugin/shared/official-tool-executor.mjs +6 -6
- package/.dsh-plugin/shared/official-tool-registry.mjs +18 -1
- package/.dsh-plugin/shared/official-tools-runtime.mjs +136 -11
- package/.dsh-plugin/shared/task-executors.mjs +52 -3
- package/.dsh-plugin/shared/tool-install-preferences.mjs +20 -1
- package/CHANGELOG.md +10 -0
- package/README.md +7 -5
- package/README.zh.md +7 -5
- package/docs/INSTALLATION_GUIDE.zh.md +12 -12
- package/docs/WORKBENCH_USER_GUIDE.zh.md +9 -7
- package/package.json +2 -2
package/.dsh-plugin/client.js
CHANGED
|
@@ -77,6 +77,8 @@ function scriptMethod(id2, label, {
|
|
|
77
77
|
scriptBinaryNames,
|
|
78
78
|
registryEnv,
|
|
79
79
|
installDirSubdir,
|
|
80
|
+
legacySharedInstallDir = false,
|
|
81
|
+
sharedDefaultBin = false,
|
|
80
82
|
uninstallUnsupportedReason,
|
|
81
83
|
scriptFallbackUrls
|
|
82
84
|
}) {
|
|
@@ -97,6 +99,8 @@ function scriptMethod(id2, label, {
|
|
|
97
99
|
installDirEnv,
|
|
98
100
|
installDirSuffix,
|
|
99
101
|
...installDirSubdir ? { installDirSubdir } : {},
|
|
102
|
+
...legacySharedInstallDir ? { legacySharedInstallDir: true } : {},
|
|
103
|
+
...sharedDefaultBin ? { sharedDefaultBin: true } : {},
|
|
100
104
|
...uninstallUnsupportedReason ? { uninstallUnsupportedReason } : {},
|
|
101
105
|
...defaultScriptInstallDir ? { defaultScriptInstallDir } : {},
|
|
102
106
|
...defaultScriptInstallDirEnv ? { defaultScriptInstallDirEnv } : {},
|
|
@@ -131,6 +135,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
131
135
|
supportsInstallDir: true,
|
|
132
136
|
installDirEnv: "KIMI_INSTALL_DIR",
|
|
133
137
|
installDirSuffix: "bin",
|
|
138
|
+
installDirSubdir: "kimi-code",
|
|
139
|
+
legacySharedInstallDir: true,
|
|
134
140
|
env: ["KIMI_INSTALL_DIR"],
|
|
135
141
|
scriptBinaryNames: ["kimi"],
|
|
136
142
|
verify: Object.freeze({ mustInclude: ["kimi-code", "code.kimi.com/kimi-code"] })
|
|
@@ -142,6 +148,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
142
148
|
supportsInstallDir: true,
|
|
143
149
|
installDirEnv: "KIMI_INSTALL_DIR",
|
|
144
150
|
installDirSuffix: "bin",
|
|
151
|
+
installDirSubdir: "kimi-code",
|
|
152
|
+
legacySharedInstallDir: true,
|
|
145
153
|
env: ["KIMI_INSTALL_DIR"],
|
|
146
154
|
scriptBinaryNames: ["kimi.exe"],
|
|
147
155
|
verify: Object.freeze({ mustInclude: ["kimi-code", "code.kimi.com/kimi-code"] })
|
|
@@ -174,6 +182,7 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
174
182
|
scriptFallbackUrls: ["https://downloads.claude.ai/claude-code-releases/bootstrap.sh"],
|
|
175
183
|
platforms: ["linux", "darwin"],
|
|
176
184
|
scriptBinaryNames: ["claude"],
|
|
185
|
+
sharedDefaultBin: true,
|
|
177
186
|
verify: Object.freeze({ mustInclude: ["downloads.claude.ai/claude-code-releases", "manifest.json"] })
|
|
178
187
|
}),
|
|
179
188
|
scriptMethod("script-powershell", "irm | iex", {
|
|
@@ -182,6 +191,7 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
182
191
|
scriptFallbackUrls: ["https://downloads.claude.ai/claude-code-releases/bootstrap.ps1"],
|
|
183
192
|
platforms: ["win32"],
|
|
184
193
|
scriptBinaryNames: ["claude.exe"],
|
|
194
|
+
sharedDefaultBin: true,
|
|
185
195
|
verify: Object.freeze({ mustInclude: ["downloads.claude.ai/claude-code-releases", "manifest.json"] })
|
|
186
196
|
})
|
|
187
197
|
])
|
|
@@ -210,6 +220,9 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
210
220
|
supportsInstallDir: true,
|
|
211
221
|
installDirEnv: "CODEX_INSTALL_DIR",
|
|
212
222
|
env: ["CODEX_INSTALL_DIR"],
|
|
223
|
+
installDirSubdir: "codex/bin",
|
|
224
|
+
legacySharedInstallDir: true,
|
|
225
|
+
sharedDefaultBin: true,
|
|
213
226
|
scriptBinaryNames: ["codex"],
|
|
214
227
|
verify: Object.freeze({ mustInclude: ["releases.openai.com/codex", "CODEX_INSTALL_DIR"] })
|
|
215
228
|
}),
|
|
@@ -259,6 +272,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
259
272
|
supportsInstallDir: true,
|
|
260
273
|
installDirEnv: "MCODE_INSTALL_DIR",
|
|
261
274
|
installDirSuffix: "bin",
|
|
275
|
+
installDirSubdir: "minimax-code",
|
|
276
|
+
legacySharedInstallDir: true,
|
|
262
277
|
registryEnv: "MCODE_NPM_REGISTRY",
|
|
263
278
|
env: ["MCODE_INSTALL_DIR", "MCODE_NPM_REGISTRY"],
|
|
264
279
|
verify: Object.freeze({ mustInclude: ["MiniMax Code CLI", "@minimax-ai/code"] })
|
|
@@ -269,6 +284,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
269
284
|
platforms: ["win32"],
|
|
270
285
|
supportsInstallDir: true,
|
|
271
286
|
installDirEnv: "MCODE_INSTALL_DIR",
|
|
287
|
+
installDirSubdir: "minimax-code",
|
|
288
|
+
legacySharedInstallDir: true,
|
|
272
289
|
registryEnv: "MCODE_NPM_REGISTRY",
|
|
273
290
|
env: ["MCODE_INSTALL_DIR", "MCODE_NPM_REGISTRY"],
|
|
274
291
|
defaultScriptInstallDir: ".minimax-code",
|
|
@@ -307,6 +324,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
307
324
|
supportsInstallDir: true,
|
|
308
325
|
installDirEnv: "MIMOCODE_INSTALL_DIR",
|
|
309
326
|
env: ["MIMOCODE_INSTALL_DIR"],
|
|
327
|
+
installDirSubdir: "mimo-code",
|
|
328
|
+
legacySharedInstallDir: true,
|
|
310
329
|
scriptBinaryNames: ["mimo.exe"],
|
|
311
330
|
verify: Object.freeze({ mustInclude: ["mimo.xiaomi.com", ".mimocode"] })
|
|
312
331
|
})
|
|
@@ -336,6 +355,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
336
355
|
supportsInstallDir: true,
|
|
337
356
|
installDirEnv: "GROK_BIN_DIR",
|
|
338
357
|
env: ["GROK_BIN_DIR"],
|
|
358
|
+
installDirSubdir: "grok-build",
|
|
359
|
+
legacySharedInstallDir: true,
|
|
339
360
|
scriptBinaryNames: ["grok", "agent"],
|
|
340
361
|
verify: Object.freeze({ mustInclude: ["Grok CLI installer", "x.ai/cli"] })
|
|
341
362
|
}),
|
|
@@ -346,6 +367,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
346
367
|
supportsInstallDir: true,
|
|
347
368
|
installDirEnv: "GROK_BIN_DIR",
|
|
348
369
|
env: ["GROK_BIN_DIR"],
|
|
370
|
+
installDirSubdir: "grok-build",
|
|
371
|
+
legacySharedInstallDir: true,
|
|
349
372
|
scriptBinaryNames: ["grok.exe", "agent.exe", "grove.exe", "grove-fsmonitor.exe", "grove-credential.exe"],
|
|
350
373
|
verify: Object.freeze({ mustInclude: ["Grok CLI installer", "x.ai/cli"] })
|
|
351
374
|
})
|
|
@@ -428,6 +451,10 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
428
451
|
installArg: ["--install-dir"],
|
|
429
452
|
env: ["STEP_INSTALL_DIR", "STEP_RELEASE_BASE_URL"],
|
|
430
453
|
supportsInstallDir: true,
|
|
454
|
+
// The installer replaces its runtime directories, including the entire
|
|
455
|
+
// node_modules tree on Windows. It must own a dedicated directory.
|
|
456
|
+
installDirSubdir: "stepcode",
|
|
457
|
+
legacySharedInstallDir: true,
|
|
431
458
|
verify: Object.freeze({
|
|
432
459
|
mustInclude: ["static-openapi.stepfun.com", "stepcode installer"],
|
|
433
460
|
mustNotInclude: ["anomalyco/opencode"]
|
|
@@ -440,6 +467,8 @@ var OFFICIAL_TOOLS = Object.freeze([
|
|
|
440
467
|
installArg: ["-InstallDir"],
|
|
441
468
|
env: ["STEP_INSTALL_DIR", "STEP_RELEASE_BASE_URL"],
|
|
442
469
|
supportsInstallDir: true,
|
|
470
|
+
installDirSubdir: "stepcode",
|
|
471
|
+
legacySharedInstallDir: true,
|
|
443
472
|
verify: Object.freeze({
|
|
444
473
|
mustInclude: ["static-openapi.stepfun.com", "stepcode"],
|
|
445
474
|
mustNotInclude: ["anomalyco/opencode"]
|
|
@@ -12704,7 +12733,7 @@ var xterm_default = `/**
|
|
|
12704
12733
|
`;
|
|
12705
12734
|
|
|
12706
12735
|
// .dsh-plugin/client/host-version.mjs
|
|
12707
|
-
var ROUTER_CLIENT_VERSION = true ? "0.18.
|
|
12736
|
+
var ROUTER_CLIENT_VERSION = true ? "0.18.2" : "";
|
|
12708
12737
|
var STALE_HOST_MESSAGE = "\u63D2\u4EF6\u540E\u53F0\u7248\u672C\u8F83\u65E7\uFF0C\u8BF7\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u542F Harness\uFF08\u5305\u62EC\u6258\u76D8\u56FE\u6807\uFF09\u540E\u518D\u4F7F\u7528\u3002";
|
|
12709
12738
|
function isMissingRemoteMethod(message) {
|
|
12710
12739
|
const value = String(message ?? "");
|
|
@@ -13537,10 +13566,10 @@ function probeLabel(probe) {
|
|
|
13537
13566
|
var INSTALL_SETTING_FIELDS = [
|
|
13538
13567
|
{
|
|
13539
13568
|
key: "toolInstallDir",
|
|
13540
|
-
label: "\u7EDF\u4E00\u5B89\u88C5\u76EE\u5F55",
|
|
13569
|
+
label: "\u7EDF\u4E00\u5B89\u88C5\u6839\u76EE\u5F55",
|
|
13541
13570
|
fallback: "",
|
|
13542
13571
|
placeholder: "\u7559\u7A7A = \u5404\u5382\u5546\u9ED8\u8BA4\u76EE\u5F55",
|
|
13543
|
-
hint: "\
|
|
13572
|
+
hint: "\u4F8B\u5982 D:/AI/tools\u3002npm / pnpm \u4F7F\u7528\u8BE5\u6839\uFF1B\u5382\u5546\u811A\u672C\u4F7F\u7528\u5DE5\u5177\u4E13\u5C5E\u5B50\u76EE\u5F55\uFF0C\u5B9E\u9645\u5730\u5740\u89C1\u5DE5\u5177\u884C\u3002\u56FA\u5B9A\u5382\u5546\u5730\u5740\u7684\u65B9\u5F0F\u4F1A\u8BF4\u660E\u539F\u56E0\u3002"
|
|
13544
13573
|
},
|
|
13545
13574
|
{
|
|
13546
13575
|
key: "toolNpmRegistry",
|
|
@@ -14876,8 +14905,8 @@ function RouterSettingsCard(props) {
|
|
|
14876
14905
|
import_dsh_client_ui_primitives.SettingsValueField,
|
|
14877
14906
|
{
|
|
14878
14907
|
id: "model-router-tool-install-dir",
|
|
14879
|
-
label: "\u7EDF\u4E00\u5B89\u88C5\u76EE\u5F55",
|
|
14880
|
-
hint: "\u586B\u5199\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u7559\u7A7A\u4F7F\u7528\u5382\u5546\u9ED8\u8BA4\u4F4D\u7F6E\u3002npm / pnpm \
|
|
14908
|
+
label: "\u7EDF\u4E00\u5B89\u88C5\u6839\u76EE\u5F55",
|
|
14909
|
+
hint: "\u586B\u5199\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u7559\u7A7A\u4F7F\u7528\u5382\u5546\u9ED8\u8BA4\u4F4D\u7F6E\u3002npm / pnpm \u5171\u7528\u8BE5\u6839\uFF1B\u53EF\u914D\u7F6E\u5382\u5546\u811A\u672C\u4F7F\u7528\u5DE5\u5177\u4E13\u5C5E\u5B50\u76EE\u5F55\uFF0C\u5B9E\u9645\u4F4D\u7F6E\u89C1\u5B98\u65B9\u5DE5\u5177\u9875\u3002\u66F4\u65B0\u590D\u7528\u6240\u9009\u65B9\u5F0F\u3001\u76EE\u5F55\u4E0E\u4E0B\u8F7D\u6E90\u3002",
|
|
14881
14910
|
disabled,
|
|
14882
14911
|
...state.toolInstallDir,
|
|
14883
14912
|
overriddenLabel: FIELD_COPY.overridden,
|
|
@@ -345,7 +345,7 @@ function nodeVersion(entry, pattern = /^v(\d+\.\d+\.\d+)/) {
|
|
|
345
345
|
})
|
|
346
346
|
}
|
|
347
347
|
|
|
348
|
-
async function findNodeExecutable(workspace, minimum = [22, 19, 0], acceptedVersion = () => true) {
|
|
348
|
+
export async function findNodeExecutable(workspace, minimum = [22, 19, 0], acceptedVersion = () => true) {
|
|
349
349
|
await ensureNpmPrefixOnPath()
|
|
350
350
|
const candidates = [process.execPath, ...pathDirectories().map(directory => join(directory, IS_WINDOWS ? 'node.exe' : 'node'))]
|
|
351
351
|
for (const candidate of candidates) {
|
|
@@ -676,17 +676,17 @@ export function harnessSandboxBlocker(toolId, platform = process.platform) {
|
|
|
676
676
|
/** Check the actual trusted launch entry without starting an account call. */
|
|
677
677
|
export async function officialToolReadiness(toolId, workspace = process.cwd(), selectionOptions = {}) {
|
|
678
678
|
const tool = getOfficialTool(toolId)
|
|
679
|
-
if (!tool) return { id: String(toolId), ready: false, reason: '工具不在固定官方注册表中。' }
|
|
679
|
+
if (!tool) return { id: String(toolId), supported: false, ready: false, reason: '工具不在固定官方注册表中。' }
|
|
680
680
|
const capability = CAPABILITIES[tool.id]
|
|
681
|
-
if (!capability?.supported) return { id: tool.id, ready: false, reason: capability?.reason ?? '当前平台不支持托管执行。' }
|
|
681
|
+
if (!capability?.supported) return { id: tool.id, supported: false, ready: false, reason: capability?.reason ?? '当前平台不支持托管执行。' }
|
|
682
682
|
try {
|
|
683
683
|
const directory = await checkedWorkspace(workspace)
|
|
684
684
|
const spec = await launchSpec(tool.id, directory, capability.modes[0], null, undefined, selectionOptions)
|
|
685
685
|
return spec.unsupported
|
|
686
|
-
? { id: tool.id, ready: false, reason: spec.unsupported }
|
|
687
|
-
: { id: tool.id, ready: true, source: spec.source, modes: capability.modes }
|
|
686
|
+
? { id: tool.id, supported: true, ready: false, reason: spec.unsupported }
|
|
687
|
+
: { id: tool.id, supported: true, ready: true, source: spec.source, modes: capability.modes }
|
|
688
688
|
} catch (error) {
|
|
689
|
-
return { id: tool.id, ready: false, reason: String(error?.message ?? error).slice(0, 300) }
|
|
689
|
+
return { id: tool.id, supported: true, ready: false, reason: String(error?.message ?? error).slice(0, 300) }
|
|
690
690
|
}
|
|
691
691
|
}
|
|
692
692
|
|
|
@@ -45,7 +45,7 @@ function scriptMethod(id, label, {
|
|
|
45
45
|
shell, scriptUrl, verify, installArg = [], env = [], platforms,
|
|
46
46
|
supportsInstallDir = false, installDirEnv = '', installDirSuffix = '',
|
|
47
47
|
defaultScriptInstallDir, defaultScriptInstallDirEnv, scriptBinaryNames,
|
|
48
|
-
registryEnv, installDirSubdir, uninstallUnsupportedReason, scriptFallbackUrls,
|
|
48
|
+
registryEnv, installDirSubdir, legacySharedInstallDir = false, sharedDefaultBin = false, uninstallUnsupportedReason, scriptFallbackUrls,
|
|
49
49
|
}) {
|
|
50
50
|
return Object.freeze({
|
|
51
51
|
id,
|
|
@@ -64,6 +64,8 @@ function scriptMethod(id, label, {
|
|
|
64
64
|
installDirEnv,
|
|
65
65
|
installDirSuffix,
|
|
66
66
|
...(installDirSubdir ? { installDirSubdir } : {}),
|
|
67
|
+
...(legacySharedInstallDir ? { legacySharedInstallDir: true } : {}),
|
|
68
|
+
...(sharedDefaultBin ? { sharedDefaultBin: true } : {}),
|
|
67
69
|
...(uninstallUnsupportedReason ? { uninstallUnsupportedReason } : {}),
|
|
68
70
|
...(defaultScriptInstallDir ? { defaultScriptInstallDir } : {}),
|
|
69
71
|
...(defaultScriptInstallDirEnv ? { defaultScriptInstallDirEnv } : {}),
|
|
@@ -96,6 +98,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
96
98
|
shell: 'bash', scriptUrl: 'https://code.kimi.com/kimi-code/install.sh',
|
|
97
99
|
platforms: ['linux', 'darwin'], supportsInstallDir: true,
|
|
98
100
|
installDirEnv: 'KIMI_INSTALL_DIR', installDirSuffix: 'bin',
|
|
101
|
+
installDirSubdir: 'kimi-code', legacySharedInstallDir: true,
|
|
99
102
|
env: ['KIMI_INSTALL_DIR'],
|
|
100
103
|
scriptBinaryNames: ['kimi'],
|
|
101
104
|
verify: Object.freeze({ mustInclude: ['kimi-code', 'code.kimi.com/kimi-code'] }),
|
|
@@ -104,6 +107,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
104
107
|
shell: 'powershell', scriptUrl: 'https://code.kimi.com/kimi-code/install.ps1',
|
|
105
108
|
platforms: ['win32'], supportsInstallDir: true,
|
|
106
109
|
installDirEnv: 'KIMI_INSTALL_DIR', installDirSuffix: 'bin',
|
|
110
|
+
installDirSubdir: 'kimi-code', legacySharedInstallDir: true,
|
|
107
111
|
env: ['KIMI_INSTALL_DIR'],
|
|
108
112
|
scriptBinaryNames: ['kimi.exe'],
|
|
109
113
|
verify: Object.freeze({ mustInclude: ['kimi-code', 'code.kimi.com/kimi-code'] }),
|
|
@@ -135,6 +139,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
135
139
|
scriptFallbackUrls: ['https://downloads.claude.ai/claude-code-releases/bootstrap.sh'],
|
|
136
140
|
platforms: ['linux', 'darwin'],
|
|
137
141
|
scriptBinaryNames: ['claude'],
|
|
142
|
+
sharedDefaultBin: true,
|
|
138
143
|
verify: Object.freeze({ mustInclude: ['downloads.claude.ai/claude-code-releases', 'manifest.json'] }),
|
|
139
144
|
}),
|
|
140
145
|
scriptMethod('script-powershell', 'irm | iex', {
|
|
@@ -142,6 +147,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
142
147
|
scriptFallbackUrls: ['https://downloads.claude.ai/claude-code-releases/bootstrap.ps1'],
|
|
143
148
|
platforms: ['win32'],
|
|
144
149
|
scriptBinaryNames: ['claude.exe'],
|
|
150
|
+
sharedDefaultBin: true,
|
|
145
151
|
verify: Object.freeze({ mustInclude: ['downloads.claude.ai/claude-code-releases', 'manifest.json'] }),
|
|
146
152
|
}),
|
|
147
153
|
]),
|
|
@@ -167,6 +173,8 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
167
173
|
shell: 'sh', scriptUrl: 'https://chatgpt.com/codex/install.sh',
|
|
168
174
|
platforms: ['linux', 'darwin'], supportsInstallDir: true,
|
|
169
175
|
installDirEnv: 'CODEX_INSTALL_DIR', env: ['CODEX_INSTALL_DIR'],
|
|
176
|
+
installDirSubdir: 'codex/bin', legacySharedInstallDir: true,
|
|
177
|
+
sharedDefaultBin: true,
|
|
170
178
|
scriptBinaryNames: ['codex'],
|
|
171
179
|
verify: Object.freeze({ mustInclude: ['releases.openai.com/codex', 'CODEX_INSTALL_DIR'] }),
|
|
172
180
|
}),
|
|
@@ -210,6 +218,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
210
218
|
shell: 'bash', scriptUrl: 'https://filecdn.minimax.chat/public/install.sh',
|
|
211
219
|
platforms: ['linux', 'darwin'], supportsInstallDir: true,
|
|
212
220
|
installDirEnv: 'MCODE_INSTALL_DIR', installDirSuffix: 'bin',
|
|
221
|
+
installDirSubdir: 'minimax-code', legacySharedInstallDir: true,
|
|
213
222
|
registryEnv: 'MCODE_NPM_REGISTRY', env: ['MCODE_INSTALL_DIR', 'MCODE_NPM_REGISTRY'],
|
|
214
223
|
verify: Object.freeze({ mustInclude: ['MiniMax Code CLI', '@minimax-ai/code'] }),
|
|
215
224
|
}),
|
|
@@ -217,6 +226,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
217
226
|
shell: 'powershell', scriptUrl: 'https://filecdn.minimax.chat/public/install.ps1',
|
|
218
227
|
platforms: ['win32'], supportsInstallDir: true,
|
|
219
228
|
installDirEnv: 'MCODE_INSTALL_DIR',
|
|
229
|
+
installDirSubdir: 'minimax-code', legacySharedInstallDir: true,
|
|
220
230
|
registryEnv: 'MCODE_NPM_REGISTRY', env: ['MCODE_INSTALL_DIR', 'MCODE_NPM_REGISTRY'],
|
|
221
231
|
defaultScriptInstallDir: '.minimax-code',
|
|
222
232
|
scriptBinaryNames: ['mcode.cmd', 'mcode.ps1', 'mcode-tools.cmd', 'mcode-tools.ps1'],
|
|
@@ -250,6 +260,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
250
260
|
shell: 'powershell', scriptUrl: 'https://mimo.xiaomi.com/install.ps1',
|
|
251
261
|
platforms: ['win32'], supportsInstallDir: true,
|
|
252
262
|
installDirEnv: 'MIMOCODE_INSTALL_DIR', env: ['MIMOCODE_INSTALL_DIR'],
|
|
263
|
+
installDirSubdir: 'mimo-code', legacySharedInstallDir: true,
|
|
253
264
|
scriptBinaryNames: ['mimo.exe'],
|
|
254
265
|
verify: Object.freeze({ mustInclude: ['mimo.xiaomi.com', '.mimocode'] }),
|
|
255
266
|
}),
|
|
@@ -276,6 +287,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
276
287
|
shell: 'bash', scriptUrl: 'https://x.ai/cli/install.sh',
|
|
277
288
|
platforms: ['linux', 'darwin'], supportsInstallDir: true,
|
|
278
289
|
installDirEnv: 'GROK_BIN_DIR', env: ['GROK_BIN_DIR'],
|
|
290
|
+
installDirSubdir: 'grok-build', legacySharedInstallDir: true,
|
|
279
291
|
scriptBinaryNames: ['grok', 'agent'],
|
|
280
292
|
verify: Object.freeze({ mustInclude: ['Grok CLI installer', 'x.ai/cli'] }),
|
|
281
293
|
}),
|
|
@@ -283,6 +295,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
283
295
|
shell: 'powershell', scriptUrl: 'https://x.ai/cli/install.ps1',
|
|
284
296
|
platforms: ['win32'], supportsInstallDir: true,
|
|
285
297
|
installDirEnv: 'GROK_BIN_DIR', env: ['GROK_BIN_DIR'],
|
|
298
|
+
installDirSubdir: 'grok-build', legacySharedInstallDir: true,
|
|
286
299
|
scriptBinaryNames: ['grok.exe', 'agent.exe', 'grove.exe', 'grove-fsmonitor.exe', 'grove-credential.exe'],
|
|
287
300
|
verify: Object.freeze({ mustInclude: ['Grok CLI installer', 'x.ai/cli'] }),
|
|
288
301
|
}),
|
|
@@ -365,6 +378,9 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
365
378
|
installArg: ['--install-dir'],
|
|
366
379
|
env: ['STEP_INSTALL_DIR', 'STEP_RELEASE_BASE_URL'],
|
|
367
380
|
supportsInstallDir: true,
|
|
381
|
+
// The installer replaces its runtime directories, including the entire
|
|
382
|
+
// node_modules tree on Windows. It must own a dedicated directory.
|
|
383
|
+
installDirSubdir: 'stepcode', legacySharedInstallDir: true,
|
|
368
384
|
verify: Object.freeze({
|
|
369
385
|
mustInclude: ['static-openapi.stepfun.com', 'stepcode installer'],
|
|
370
386
|
mustNotInclude: ['anomalyco/opencode'],
|
|
@@ -377,6 +393,7 @@ export const OFFICIAL_TOOLS = Object.freeze([
|
|
|
377
393
|
installArg: ['-InstallDir'],
|
|
378
394
|
env: ['STEP_INSTALL_DIR', 'STEP_RELEASE_BASE_URL'],
|
|
379
395
|
supportsInstallDir: true,
|
|
396
|
+
installDirSubdir: 'stepcode', legacySharedInstallDir: true,
|
|
380
397
|
verify: Object.freeze({
|
|
381
398
|
mustInclude: ['static-openapi.stepfun.com', 'stepcode'],
|
|
382
399
|
mustNotInclude: ['anomalyco/opencode'],
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import {
|
|
27
27
|
expandInstallDir,
|
|
28
28
|
resolveInstallLocation,
|
|
29
|
+
resolveLegacyScriptLocation,
|
|
29
30
|
resolveInstallPlan,
|
|
30
31
|
resolveUninstallPlan,
|
|
31
32
|
} from './tool-install-preferences.mjs'
|
|
@@ -308,16 +309,22 @@ async function fetchOfficialMiniMaxInstaller(signal) {
|
|
|
308
309
|
}
|
|
309
310
|
|
|
310
311
|
export function minimaxOfficialInstallDir(selectedPreferences = {}, home = homedir(), existingRoot = '') {
|
|
311
|
-
return selectedPreferences.installDir
|
|
312
|
+
return selectedPreferences.installDir
|
|
313
|
+
? resolveInstallPlan(getOfficialTool('minimax-code'), 'script-powershell', selectedPreferences, { platform: 'win32', home }).installDir
|
|
314
|
+
: existingRoot || join(home, '.minimax-code')
|
|
312
315
|
}
|
|
313
316
|
|
|
314
317
|
/** Keep a discovered native installation in place when its legacy updater runs. */
|
|
315
318
|
export function miniMaxFallbackPlan(selectedPreferences, beforeProbe, prefix, options = currentInstallOptions()) {
|
|
316
319
|
const installDir = minimaxOfficialInstallDir(selectedPreferences, options.home,
|
|
317
320
|
beforeProbe?.installed ? beforeProbe.installDir : '')
|
|
318
|
-
const executionPreferences = { ...selectedPreferences,
|
|
321
|
+
const executionPreferences = { ...selectedPreferences,
|
|
319
322
|
methods: { ...selectedPreferences.methods, 'minimax-code': 'script-powershell' } }
|
|
320
|
-
|
|
323
|
+
const plan = resolveInstallPlan(getOfficialTool('minimax-code'), 'script-powershell', selectedPreferences, options)
|
|
324
|
+
// installDir is already the discovered exclusive native root or the resolved
|
|
325
|
+
// child of the configured base. Never reinterpret it as another shared base.
|
|
326
|
+
return { plan: { ...plan, installDir, binDir: installDir,
|
|
327
|
+
env: { ...plan.env, MCODE_INSTALL_DIR: installDir } }, executionPreferences }
|
|
321
328
|
}
|
|
322
329
|
|
|
323
330
|
async function runOfficialMiniMaxInstaller(job, { reason } = {}) {
|
|
@@ -330,6 +337,7 @@ async function runOfficialMiniMaxInstaller(job, { reason } = {}) {
|
|
|
330
337
|
pushLine(job, reason || '— npm 原生依赖未就绪;改用 MiniMax 官方 Windows 安装脚本(安装最新版)—')
|
|
331
338
|
const { plan, executionPreferences } = miniMaxFallbackPlan(job.preferences, job.beforeProbe, '')
|
|
332
339
|
const installDir = plan.installDir
|
|
340
|
+
await assertVendorInstallDirectory(job.tool, plan, job.preferences)
|
|
333
341
|
await mkdir(installDir, { recursive: true })
|
|
334
342
|
prefixRoot = await realpath(installDir)
|
|
335
343
|
const script = await fetchOfficialMiniMaxInstaller(job.controller.signal)
|
|
@@ -354,6 +362,7 @@ async function runOfficialMiniMaxInstaller(job, { reason } = {}) {
|
|
|
354
362
|
+ 'Import-Module -Name $env:MODEL_ROUTER_UTILITY_MODULE -ErrorAction Stop; '
|
|
355
363
|
+ '& $env:MODEL_ROUTER_MINIMAX_INSTALL_SCRIPT'
|
|
356
364
|
job.phase = 'installing'
|
|
365
|
+
await assertVendorInstallDirectory(job.tool, plan, job.preferences)
|
|
357
366
|
const outcome = await runCapture(powershell,
|
|
358
367
|
['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', command], {
|
|
359
368
|
timeoutMs: INSTALL_TIMEOUT_MS, signal: job.controller.signal,
|
|
@@ -378,7 +387,7 @@ async function runOfficialMiniMaxInstaller(job, { reason } = {}) {
|
|
|
378
387
|
probeCache.delete(job.tool.id)
|
|
379
388
|
const probe = await probeToolWith(job.tool, defaultRunner, { preferences: executionPreferences, plan, cacheMs: 0 })
|
|
380
389
|
job.postInstallProbe = probe
|
|
381
|
-
const readiness = await trustedExecutionReadiness(job.tool.id, { preferences: executionPreferences })
|
|
390
|
+
const readiness = await trustedExecutionReadiness(job.tool.id, { preferences: executionPreferences, plan })
|
|
382
391
|
if (!probe.installed || !readiness.ready) {
|
|
383
392
|
throw new Error(`官方安装器完成,但执行入口未通过 npm registry 摘要核验:${readiness.reason ?? probe.detail}`)
|
|
384
393
|
}
|
|
@@ -516,6 +525,95 @@ export function vendorScriptInvocation(plan, scriptPath) {
|
|
|
516
525
|
return { file: plan.shell === 'sh' ? 'sh' : 'bash', args: [scriptPath, ...directoryArgs] }
|
|
517
526
|
}
|
|
518
527
|
|
|
528
|
+
/** Resolve an existing ancestor too, so a missing child under a junction is checked. */
|
|
529
|
+
async function canonicalInstallPath(path) {
|
|
530
|
+
let current = resolve(path)
|
|
531
|
+
const suffix = []
|
|
532
|
+
for (;;) {
|
|
533
|
+
try { return join(await realpath(current), ...suffix) }
|
|
534
|
+
catch (error) {
|
|
535
|
+
if (error.code !== 'ENOENT') throw error
|
|
536
|
+
const parent = dirname(current)
|
|
537
|
+
if (parent === current) throw error
|
|
538
|
+
suffix.unshift(current.slice(parent.length).replace(/^[\\/]+/, ''))
|
|
539
|
+
current = parent
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** A vendor owns its target; its cleanup must never touch a shared npm prefix. */
|
|
545
|
+
export async function assertVendorInstallDirectory(tool, plan, selectedPreferences = {}, options = currentInstallOptions()) {
|
|
546
|
+
const directory = plan.installDir || plan.binDir
|
|
547
|
+
if (!directory) throw new Error('官方脚本安装目录无法确定,已停止安装。')
|
|
548
|
+
const canonical = await canonicalInstallPath(directory)
|
|
549
|
+
if (canonical.split(/[\\/]+/).some(part => ['node_modules', '.pnpm'].includes(part.toLowerCase()))) {
|
|
550
|
+
throw new Error('官方脚本不能写入 npm/pnpm 的包目录或共享存储目录。')
|
|
551
|
+
}
|
|
552
|
+
if (canonical === dirname(canonical) || canonical === await canonicalInstallPath(homedir())) {
|
|
553
|
+
throw new Error('官方脚本不能在磁盘根目录或用户主目录中执行。')
|
|
554
|
+
}
|
|
555
|
+
let codexOwnedRelease = false
|
|
556
|
+
if (tool.id === 'codex' && plan.shell === 'powershell') {
|
|
557
|
+
// The official Windows script keeps releases under the user's existing
|
|
558
|
+
// CODEX_HOME. CODEX_INSTALL_DIR selects only the visible bin junction.
|
|
559
|
+
// Respect that documented layout without redirecting the user's config.
|
|
560
|
+
const codexHome = options.env?.CODEX_HOME || join(options.home || homedir(), '.codex')
|
|
561
|
+
const homeRoot = await canonicalInstallPath(codexHome)
|
|
562
|
+
const releases = join(homeRoot, 'packages', 'standalone', 'releases')
|
|
563
|
+
const member = relative(releases, canonical)
|
|
564
|
+
const expectedVisible = !selectedPreferences.installDir || resolve(directory) === resolve(join(selectedPreferences.installDir, plan.method.installDirSubdir))
|
|
565
|
+
codexOwnedRelease = Boolean(expectedVisible && member && member !== '..' && !member.startsWith(`..${sep}`) && !isAbsolute(member))
|
|
566
|
+
}
|
|
567
|
+
if (selectedPreferences.installDir && plan.method?.supportsInstallDir) {
|
|
568
|
+
const base = await canonicalInstallPath(selectedPreferences.installDir)
|
|
569
|
+
const child = relative(base, canonical)
|
|
570
|
+
if (!codexOwnedRelease && (!child || child === '..' || child.startsWith(`..${sep}`) || isAbsolute(child))) {
|
|
571
|
+
throw new Error('官方脚本必须使用统一安装根目录下的工具专属子目录;已拒绝覆盖共享目录。')
|
|
572
|
+
}
|
|
573
|
+
const owner = plan.method.installDirSubdir?.split(/[\\/]+/)[0]
|
|
574
|
+
const ownedRoot = owner ? join(base, owner) : null
|
|
575
|
+
const ownedChild = ownedRoot ? relative(ownedRoot, canonical) : '..'
|
|
576
|
+
if (!codexOwnedRelease && (ownedChild === '..' || ownedChild.startsWith(`..${sep}`) || isAbsolute(ownedChild))) {
|
|
577
|
+
throw new Error('官方脚本的目录联接必须留在本工具专属目录中,不能指向其他工具。')
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
// These fixed/default bin installers replace only their own launcher, not
|
|
581
|
+
// another CLI's runtime directory. Sharing ~/.local/bin is intentional.
|
|
582
|
+
if (plan.method?.sharedDefaultBin && (!selectedPreferences.installDir || !plan.method.supportsInstallDir)) return
|
|
583
|
+
const targetStat = await stat(directory).catch(error => {
|
|
584
|
+
if (error.code === 'ENOENT') return null
|
|
585
|
+
throw error
|
|
586
|
+
})
|
|
587
|
+
if (targetStat && !targetStat.isDirectory()) throw new Error('官方脚本安装目标不是目录。')
|
|
588
|
+
const packageText = await readFile(join(directory, 'package.json'), 'utf8').catch(error => {
|
|
589
|
+
if (error.code === 'ENOENT') return null
|
|
590
|
+
throw error
|
|
591
|
+
})
|
|
592
|
+
if (packageText) {
|
|
593
|
+
let name
|
|
594
|
+
try { name = JSON.parse(packageText).name } catch { throw new Error('安装目录已有无法核验归属的 package.json,已停止运行官方脚本。') }
|
|
595
|
+
const ownPackage = tool.package || (tool.id === 'stepcode' ? '@step-harness/coding-agent' : '')
|
|
596
|
+
if (!name || name !== ownPackage) throw new Error('安装目录已有其他工具或项目的 package.json,已停止运行官方脚本。')
|
|
597
|
+
}
|
|
598
|
+
for (const other of OFFICIAL_TOOLS) {
|
|
599
|
+
if (other.id === tool.id) continue
|
|
600
|
+
const commandDirectories = [...new Set([directory, plan.binDir].filter(Boolean))]
|
|
601
|
+
const candidates = commandDirectories.flatMap(dir => (other.probeExecutables ?? []).flatMap(name =>
|
|
602
|
+
[name, `${name}.exe`, `${name}.cmd`, `${name}.ps1`].map(file => join(dir, file))))
|
|
603
|
+
if (other.package) {
|
|
604
|
+
candidates.push(join(directory, 'node_modules', ...other.package.split('/'), 'package.json'))
|
|
605
|
+
candidates.push(join(directory, 'lib', 'node_modules', ...other.package.split('/'), 'package.json'))
|
|
606
|
+
}
|
|
607
|
+
for (const path of candidates) {
|
|
608
|
+
const found = await stat(path).catch(error => {
|
|
609
|
+
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return null
|
|
610
|
+
throw error
|
|
611
|
+
})
|
|
612
|
+
if (found?.isFile()) throw new Error(`安装目录中已有 ${other.label} 的文件,已拒绝运行可能替换其资源的官方脚本。请使用独立目录。`)
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
519
617
|
/**
|
|
520
618
|
* Install one tool from its vendor script: download, verify, stage, run the
|
|
521
619
|
* staged copy. The binary lands in a directory the plugin appends to its own
|
|
@@ -527,6 +625,7 @@ async function runVendorScriptInstallJob(job, plan) {
|
|
|
527
625
|
let staging = null
|
|
528
626
|
try {
|
|
529
627
|
job.phase = 'preflight'
|
|
628
|
+
await assertVendorInstallDirectory(job.tool, plan, job.preferences)
|
|
530
629
|
const before = await probeToolWith(job.tool, defaultRunner, { plan, preferences: job.preferences, cacheMs: 0 })
|
|
531
630
|
job.beforeProbe = before
|
|
532
631
|
const latestBefore = before.installed ? await latestVersions.lookup(job.tool, { fresh: true }).catch(() => null) : null
|
|
@@ -548,6 +647,7 @@ async function runVendorScriptInstallJob(job, plan) {
|
|
|
548
647
|
await writeFile(scriptPath, isPowerShell ? `\uFEFF${source}` : source, 'utf8')
|
|
549
648
|
const invocation = vendorScriptInvocation(plan, scriptPath)
|
|
550
649
|
job.phase = 'installing'
|
|
650
|
+
await assertVendorInstallDirectory(job.tool, plan, job.preferences)
|
|
551
651
|
pushLine(job, `$ ${plan.label}${invocation.args.length > 1 ? ` ${invocation.args.slice(1).join(' ')}` : ''}`)
|
|
552
652
|
const outcome = await runCapture(invocation.file, invocation.args, {
|
|
553
653
|
timeoutMs: INSTALL_TIMEOUT_MS,
|
|
@@ -827,6 +927,22 @@ async function probeUncached(tool, runner, { selectedPreferences, plan, fileExis
|
|
|
827
927
|
bannerLine: banner.split('\n').find(Boolean)?.slice(0, 200) ?? '' }
|
|
828
928
|
}
|
|
829
929
|
}
|
|
930
|
+
const legacy = resolveLegacyScriptLocation(tool, selectedPlan.method, selectedPreferences, currentInstallOptions())
|
|
931
|
+
if (legacy) {
|
|
932
|
+
for (const executable of tool.probeExecutables) {
|
|
933
|
+
const names = IS_WINDOWS ? [`${executable}.exe`, `${executable}.cmd`, executable] : [executable]
|
|
934
|
+
for (const name of names) {
|
|
935
|
+
const target = join(legacy.dir, name)
|
|
936
|
+
if (!(await fileExists(target))) continue
|
|
937
|
+
// Discovery is read-only. The old root can never satisfy a new job's
|
|
938
|
+
// target probe or become a script update/uninstall destination.
|
|
939
|
+
return { id: tool.id, installed: false, version: null, status: 'legacy-install',
|
|
940
|
+
installDir: selectedPlan.installDir, binDir: selectedPlan.binDir,
|
|
941
|
+
legacyExecutablePath: target,
|
|
942
|
+
detail: `发现旧共享目录中的 ${tool.label}:${target}。请安装到独立目录 ${selectedPlan.installDir};旧文件会保留,不能用旧目录覆盖其他工具。` }
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
830
946
|
return { id: tool.id, installed: false, version: null, status: 'not-installed',
|
|
831
947
|
installDir: selectedPlan.installDir, binDir: selectedPlan.binDir,
|
|
832
948
|
detail: `未在所选安装目录 ${selectedPlan.binDir} 中找到官方命令。` }
|
|
@@ -1006,6 +1122,7 @@ function jobView(job) {
|
|
|
1006
1122
|
exitCode: job.exitCode,
|
|
1007
1123
|
timedOut: job.timedOut,
|
|
1008
1124
|
error: job.error,
|
|
1125
|
+
executionReadiness: job.executionReadiness ?? null,
|
|
1009
1126
|
outputTail: [...job.outputTail],
|
|
1010
1127
|
outputTruncated: job.outputTruncated,
|
|
1011
1128
|
postInstallProbe: job.postInstallProbe,
|
|
@@ -1272,8 +1389,10 @@ export function installEnvironment(plan, base = process.env) {
|
|
|
1272
1389
|
return env
|
|
1273
1390
|
}
|
|
1274
1391
|
|
|
1275
|
-
/**
|
|
1276
|
-
async function finishVerifiedInstall(job, probe
|
|
1392
|
+
/** Installation and version must pass; verify execution only where an adapter supports it. */
|
|
1393
|
+
export async function finishVerifiedInstall(job, probe, {
|
|
1394
|
+
lookupLatest = tool => latestVersions.lookup(tool), readinessFor = trustedExecutionReadiness,
|
|
1395
|
+
} = {}) {
|
|
1277
1396
|
job.postInstallProbe = probe
|
|
1278
1397
|
if (job.cancelRequested) { markCancelled(job); return true }
|
|
1279
1398
|
if (!probe.installed) {
|
|
@@ -1282,14 +1401,15 @@ async function finishVerifiedInstall(job, probe) {
|
|
|
1282
1401
|
job.error = `安装命令已完成,但重探测失败:${probe.detail}`
|
|
1283
1402
|
return true
|
|
1284
1403
|
}
|
|
1285
|
-
const latest = await
|
|
1404
|
+
const latest = await lookupLatest(job.tool).catch(() => null)
|
|
1286
1405
|
if (latest?.version && versionStillOlder(probe.version, latest.version)) {
|
|
1287
1406
|
failBecauseStillOlder(job, probe, latest.version)
|
|
1288
1407
|
return true
|
|
1289
1408
|
}
|
|
1290
|
-
const readiness = await
|
|
1409
|
+
const readiness = await readinessFor(job.tool.id, { preferences: job.preferences, plan: job.plan })
|
|
1410
|
+
job.executionReadiness = readiness
|
|
1291
1411
|
if (job.cancelRequested) { markCancelled(job); return true }
|
|
1292
|
-
if (!readiness.ready) {
|
|
1412
|
+
if (!readiness.ready && readiness.supported !== false) {
|
|
1293
1413
|
if (IS_WINDOWS && job.tool.id === 'minimax-code' && !job.configured) {
|
|
1294
1414
|
await runOfficialMiniMaxInstaller(job)
|
|
1295
1415
|
return true
|
|
@@ -1300,8 +1420,11 @@ async function finishVerifiedInstall(job, probe) {
|
|
|
1300
1420
|
return true
|
|
1301
1421
|
}
|
|
1302
1422
|
job.status = 'succeeded'
|
|
1423
|
+
job.error = null
|
|
1303
1424
|
job.finishedAt = new Date().toISOString()
|
|
1304
|
-
pushLine(job,
|
|
1425
|
+
pushLine(job, readiness.supported === false
|
|
1426
|
+
? `— 已安装 ${job.tool.label} ${probe.version ?? ''},版本探测已通过;受限执行未启用:${readiness.reason} —`
|
|
1427
|
+
: `— 已安装 ${job.tool.label} ${probe.version ?? ''},官方执行入口已核验(新版本未经插件测试)—`)
|
|
1305
1428
|
return true
|
|
1306
1429
|
}
|
|
1307
1430
|
|
|
@@ -1378,11 +1501,13 @@ async function runInstallJob(installPlan, job) {
|
|
|
1378
1501
|
// exactly the case the user pressed 修复 for.
|
|
1379
1502
|
if (job.repair) {
|
|
1380
1503
|
pushLine(job, `— ${job.tool.label} ${before.version} 已是最新版本,按修复流程重新安装 —`)
|
|
1381
|
-
} else if (readiness.ready || !CAPABILITY_REPAIRABLE.has(job.tool.id)) {
|
|
1504
|
+
} else if (readiness.ready || readiness.supported === false || !CAPABILITY_REPAIRABLE.has(job.tool.id)) {
|
|
1382
1505
|
job.status = 'succeeded'
|
|
1383
1506
|
job.finishedAt = new Date().toISOString()
|
|
1384
1507
|
job.postInstallProbe = before
|
|
1508
|
+
job.executionReadiness = readiness
|
|
1385
1509
|
pushLine(job, `— ${job.tool.label} ${before.version} 已是最新版本,无需重复安装 —`)
|
|
1510
|
+
if (readiness.supported === false) pushLine(job, `— 受限执行未启用:${readiness.reason} —`)
|
|
1386
1511
|
return
|
|
1387
1512
|
} else {
|
|
1388
1513
|
pushLine(job, `— 已是最新版本 ${before.version},但执行入口未就绪:${readiness.reason};重新安装修复 —`)
|
|
@@ -14,8 +14,11 @@
|
|
|
14
14
|
import { spawn } from 'node:child_process'
|
|
15
15
|
import { StringDecoder } from 'node:string_decoder'
|
|
16
16
|
import { realpath, stat } from 'node:fs/promises'
|
|
17
|
-
import { isAbsolute, win32 } from 'node:path'
|
|
17
|
+
import { dirname, isAbsolute, join, relative, sep, win32 } from 'node:path'
|
|
18
18
|
import { toolForProvider } from './official-tool-registry.mjs'
|
|
19
|
+
import { findGlobalPackageEntries, findNodeExecutable, officialToolEntrySelection } from './official-tool-executor.mjs'
|
|
20
|
+
import { installedModuleRoots } from './vendor-mimo-grok-adapter.mjs'
|
|
21
|
+
import { CODE_FILE, npmAttestor } from './npm-attestation.mjs'
|
|
19
22
|
import { normalizeExecutionPreference } from './model-profiles.mjs'
|
|
20
23
|
import { BILLING_MODE_LABEL, billingPlan, detectQuotaExhaustion, vendorKey } from './subscription-billing.mjs'
|
|
21
24
|
|
|
@@ -666,6 +669,45 @@ function officialSuccess({ route, adapter, preference, answer, exitCode, version
|
|
|
666
669
|
}
|
|
667
670
|
}
|
|
668
671
|
|
|
672
|
+
/** Gemini's Windows npm shim cannot be spawned without a shell; use its selected package bin. */
|
|
673
|
+
export async function resolvePortableToolLaunch(adapter, workspace, {
|
|
674
|
+
selection = adapter?.id === 'gemini' ? officialToolEntrySelection('gemini') : null,
|
|
675
|
+
findEntries = findGlobalPackageEntries, findNode = findNodeExecutable, attestor = npmAttestor,
|
|
676
|
+
} = {}) {
|
|
677
|
+
if (adapter?.id !== 'gemini') return { file: adapter.executable, args: [] }
|
|
678
|
+
const entries = await findEntries('@google/gemini-cli', 'gemini', 'dist/index.js', workspace, { selection })
|
|
679
|
+
const found = entries[0]
|
|
680
|
+
if (!found) return { reason: '未在所选安装目录中找到 @google/gemini-cli 的 gemini 入口;已回退模型目录 API。' }
|
|
681
|
+
const inside = (root, entry) => {
|
|
682
|
+
const part = relative(root, entry)
|
|
683
|
+
return part !== '' && part !== '..' && !part.startsWith(`..${sep}`) && !isAbsolute(part)
|
|
684
|
+
}
|
|
685
|
+
const roots = Array.isArray(selection?.plan?.packageModuleRoots) ? selection.plan.packageModuleRoots
|
|
686
|
+
: await installedModuleRoots(selection?.directories ?? [], { manager: selection?.plan?.manager })
|
|
687
|
+
const prefix = selection?.plan?.installDir ? await realpath(selection.plan.installDir).catch(() => null) : null
|
|
688
|
+
if (selection?.plan?.installDir && !prefix) return { reason: 'Gemini 所选安装根无法确认,已拒绝启动并回退模型目录 API。' }
|
|
689
|
+
let contained = false
|
|
690
|
+
for (const declared of roots) {
|
|
691
|
+
const boundary = await realpath(declared).catch(() => null)
|
|
692
|
+
const parent = await realpath(dirname(declared)).catch(() => null)
|
|
693
|
+
if (!boundary || !parent || !inside(parent, boundary) || (prefix && !inside(prefix, boundary))) continue
|
|
694
|
+
if (inside(boundary, found.packageRoot) && inside(found.packageRoot, found.entry)
|
|
695
|
+
&& !inside(workspace, found.packageRoot) && found.packageRoot !== workspace) { contained = true; break }
|
|
696
|
+
}
|
|
697
|
+
if (!contained) return { reason: 'Gemini 包或入口离开了所选安装目录的包存储树,已拒绝启动并回退模型目录 API。' }
|
|
698
|
+
// The manifest controls bin/import resolution, so attest it as well as all
|
|
699
|
+
// package code. Checking name/version or a signed Node alone is insufficient.
|
|
700
|
+
const include = new RegExp(`${CODE_FILE.source}|(?:^|/)package\\.json$`, 'i')
|
|
701
|
+
const checked = await attestor.matchesPackage(found.packageRoot, '@google/gemini-cli', found.version, include)
|
|
702
|
+
if (!checked.ok) return { reason: `Gemini 安装文件未通过官方 npm 摘要核验:${checked.reason};已回退模型目录 API。` }
|
|
703
|
+
const member = relative(found.packageRoot, found.entry).split(sep).join('/')
|
|
704
|
+
const entryChecked = await attestor.matches(found.entry, '@google/gemini-cli', found.version, member)
|
|
705
|
+
if (!entryChecked.ok) return { reason: `Gemini 入口未通过官方 npm 摘要核验:${entryChecked.reason};已回退模型目录 API。` }
|
|
706
|
+
const node = await findNode(workspace, [20, 0, 0])
|
|
707
|
+
if (!node) return { reason: '未找到工作区外的 Node.js 20+ 可执行文件;已回退模型目录 API。' }
|
|
708
|
+
return { file: node, args: [found.entry], version: found.version, source: 'official-npm-registry-attested' }
|
|
709
|
+
}
|
|
710
|
+
|
|
669
711
|
/**
|
|
670
712
|
* Run one assigned model task. `auto` and `official` try the vendor CLI first.
|
|
671
713
|
* `api` skips the CLI. A missing tool or a failed process uses `apiFallback`.
|
|
@@ -673,6 +715,7 @@ function officialSuccess({ route, adapter, preference, answer, exitCode, version
|
|
|
673
715
|
export async function executeAssignedTask({
|
|
674
716
|
route, task, workspace, timeoutMs, signal, credentials = null, spawnImpl = spawn,
|
|
675
717
|
apiFallback, runVerified = null, onChunk = null, skipOfficial = null, stopGraceMs, credentialMode = 'default',
|
|
718
|
+
resolvePortableLaunch = resolvePortableToolLaunch,
|
|
676
719
|
} = {}) {
|
|
677
720
|
const prompt = checkedTask(task)
|
|
678
721
|
const preference = executionPreference(route)
|
|
@@ -734,7 +777,13 @@ export async function executeAssignedTask({
|
|
|
734
777
|
})
|
|
735
778
|
}
|
|
736
779
|
const { env, secret, credentialSource } = childEnvironment(adapter, credentials, credentialMode)
|
|
737
|
-
|
|
780
|
+
let launch
|
|
781
|
+
try { launch = await resolvePortableLaunch(adapter, cwd) } catch (error) {
|
|
782
|
+
launch = { reason: `${adapter.label} 的安装入口无法读取:${String(error?.message ?? error).slice(0, 200)};已回退模型目录 API。` }
|
|
783
|
+
}
|
|
784
|
+
if (!launch?.file) return useApi(apiFallback, { route, task: prompt, signal, adapter, preference,
|
|
785
|
+
reason: launch?.reason ?? `${adapter.label} 未安装或无法启动,已回退模型目录 API。` })
|
|
786
|
+
const probe = await captureProcess(spawnImpl, launch.file, [...launch.args, '--version'], {
|
|
738
787
|
cwd, env, stdin: '', timeoutMs: Math.min(PROBE_TIMEOUT_MS, timeout), signal, secret,
|
|
739
788
|
})
|
|
740
789
|
if (probe.spawnError || probe.cancelled || probe.timedOut || probe.exitCode !== 0) {
|
|
@@ -748,7 +797,7 @@ export async function executeAssignedTask({
|
|
|
748
797
|
return useApi(apiFallback, { route, task: prompt, signal, adapter, preference, reason })
|
|
749
798
|
}
|
|
750
799
|
const args = adapter.buildArgs(modelId)
|
|
751
|
-
const run = await captureProcess(spawnImpl,
|
|
800
|
+
const run = await captureProcess(spawnImpl, launch.file, [...launch.args, ...args], {
|
|
752
801
|
cwd, env, stdin: prompt, timeoutMs: timeout, signal, onChunk, secret,
|
|
753
802
|
...(Number.isSafeInteger(stopGraceMs) && stopGraceMs >= 10 && stopGraceMs <= STOP_GRACE_MS ? { stopGraceMs } : {}),
|
|
754
803
|
})
|
|
@@ -249,12 +249,17 @@ export function resolveInstallPlan(tool, methodId, preferences = {}, { platform
|
|
|
249
249
|
const binDir = target
|
|
250
250
|
? (method.installDirSuffix ? joinPath(target, method.installDirSuffix) : target)
|
|
251
251
|
: vendorScriptDir(tool, { home, env, method })
|
|
252
|
-
const notices = target
|
|
252
|
+
const notices = target && installDir && method.installDirSubdir
|
|
253
|
+
? [`官方脚本在工具专属子目录 ${target} 维护资源,保护统一安装根中的其他工具。`]
|
|
254
|
+
: target ? [] : installDir
|
|
253
255
|
? [`${method.label} 无法使用自定义安装目录(官方脚本写死了安装路径),将安装到 ${vendorScriptDir(tool, { home, env, method })}。`]
|
|
254
256
|
: []
|
|
255
257
|
if (method.registryEnv === 'MCODE_NPM_REGISTRY' && preferences.registry && !minimaxScriptRegistry(preferences.registry)) {
|
|
256
258
|
notices.push('MiniMax 官方脚本只接受 https://registry.npmjs.org/ 或 https://registry.npmmirror.com/,本次不改写它的 npm 源。')
|
|
257
259
|
}
|
|
260
|
+
if (tool.id === 'codex' && method.id === 'script-powershell') {
|
|
261
|
+
notices.push('Codex Windows 脚本将命令目录联接到用户 Codex 资源目录;统一根控制命令位置,运行资源仍由厂商保存在用户目录。需要所有文件位于统一根时请选 npm/pnpm。')
|
|
262
|
+
}
|
|
258
263
|
return {
|
|
259
264
|
toolId: tool.id,
|
|
260
265
|
method,
|
|
@@ -332,6 +337,20 @@ export function resolveInstallLocation(tool, method, preferences = {}, { home =
|
|
|
332
337
|
return { kind: 'package-manager', dir: packageManagerBinDir(preferences?.installDir || '', { platform }), installDir: preferences?.installDir || '', binaries: [] }
|
|
333
338
|
}
|
|
334
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Read-only compatibility location for scripts installed directly into a
|
|
342
|
+
* configured shared root before dedicated script directories were introduced.
|
|
343
|
+
* Never use this result as an install/update target: those installers can
|
|
344
|
+
* replace a whole runtime tree belonging to another tool.
|
|
345
|
+
*/
|
|
346
|
+
export function resolveLegacyScriptLocation(tool, method, preferences = {}, options = {}) {
|
|
347
|
+
if (!preferences.installDir || !method?.legacySharedInstallDir || !methodHonoursInstallDir(method)) return null
|
|
348
|
+
return {
|
|
349
|
+
...resolveInstallLocation(tool, { ...method, installDirSubdir: undefined }, preferences, options),
|
|
350
|
+
legacySharedInstallDir: true,
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
335
354
|
function scriptEnv(method, { installDir, scriptUrl, registry }) {
|
|
336
355
|
const env = {}
|
|
337
356
|
if (installDir && method.installDirEnv) env[method.installDirEnv] = installDir
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.2 — 2026-10-12
|
|
4
|
+
|
|
5
|
+
Stable patch under the npm latest tag. Upgrade and fully restart Harness before using Step install, repair or update.
|
|
6
|
+
|
|
7
|
+
- Treat the configured install directory as a shared base. Configurable vendor scripts use dedicated tool subdirectories; npm/pnpm retain their existing shared prefix. Step's official installer replaces its entire runtime directories, so passing the shared root could delete every other npm package there.
|
|
8
|
+
- Check script targets before download or execution: refuse shared roots, other tool/project manifests and commands, package stores, and junctions into another tool's tree. Codex's junction within its own release tree and intentional shared default launcher directories remain supported. Legacy script roots are discovered read-only and cannot satisfy or redirect new install jobs.
|
|
9
|
+
- Separate successful version/install verification from unsupported restricted execution capabilities. Gemini, OpenCode and Step can be installed successfully while retaining explicit execution limitations; supported tools still require their original trusted-entry checks.
|
|
10
|
+
- Resolve Windows Gemini through its selected npm/pnpm package bin and a verified Node executable. Check package-tree containment and official registry file digests before launch, and preserve API fallback when the selected copy is missing or fails verification.
|
|
11
|
+
- Add regressions for destructive vendor runtime replacement, shared-prefix sentinels, junctions, legacy discovery, install capability status and Windows Gemini entry selection. Release notes record validation and the original-version recovery performed on the reported machine.
|
|
12
|
+
|
|
3
13
|
## 0.18.1 — 2026-10-12
|
|
4
14
|
|
|
5
15
|
Stable patch under the npm latest tag. Fully quit Harness, including the tray or WebUI host process, and restart after upgrading.
|
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Model Router · DeepSeek Harness Desktop
|
|
2
2
|
|
|
3
|
-
Version: **0.18.
|
|
3
|
+
Version: **0.18.2**. See [routing research](docs/ROUTING_RESEARCH.zh.md), [mathematical derivation and experiments](docs/ROUTING_DERIVATION_EXPERIMENTS.zh.md), and the [LiveBench evidence audit and validation protocol](docs/ROUTING_LIVEBENCH_VALIDATION.zh.md). The latest public 2026-06-25 score/cost tables are aggregates; its paired question-quality/cost matrix has not been obtained, and no real routing gain is claimed.
|
|
4
4
|
|
|
5
5
|
A cost-aware model-routing workbench for **DeepSeek Harness Desktop**. Describe a task, compare your configured models, inspect the plan, and explicitly choose when to execute it.
|
|
6
6
|
|
|
7
|
+
**0.18.2:** isolates configurable vendor scripts beneath the shared install root, preventing Step from replacing other tools’ npm packages. Gemini/OpenCode installation success is separate from restricted execution support; Windows Gemini uses its verified package entry through Node. Upgrade and restart Harness before updating Step.
|
|
8
|
+
|
|
7
9
|
**0.18.1:** fixes install-method selection stuck on saving. Saved volatile settings now update Host install preferences immediately; command previews refresh separately from tool inspection.
|
|
8
10
|
|
|
9
11
|
**0.18.0:** expands official vendor installer choices alongside npm/pnpm, preserves the configured install destination during updates, and verifies the target copy instead of an older PATH copy. MiniMax Windows scripts beginning with PowerShell `<# ... #>` help comments are accepted; HTML error pages remain rejected. Save unified install settings before installing or updating.
|
|
@@ -18,7 +20,7 @@ A cost-aware model-routing workbench for **DeepSeek Harness Desktop**. Describe
|
|
|
18
20
|
|
|
19
21
|
**0.16.0:** [dynamic data and adaptive feedback](docs/ROUTING_ADAPTIVE_LEARNING.zh.md) adds opt-in public benchmark/curated fixed-USD pricing refresh and task-specific, time-decayed, shrinkage-regularized subjective utility. Objective proxy quality floors remain separate. Public sources default off; preferences are shared within one local DSH_HOME and limited to the latest 200 runs. This is not a universal vendor-price scraper or evidence of real quality–cost gains.
|
|
20
22
|
|
|
21
|
-
Current version: **0.18.
|
|
23
|
+
Current version: **0.18.2** (`@ljwei-stak/dsh-model-router`). Its declared host compatibility is **DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2**. The router and [GAL](https://github.com/Alice-Marx/deepseek-harness-galgame) have been independent plugins since 0.12.0.
|
|
22
24
|
|
|
23
25
|
**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.
|
|
24
26
|
|
|
@@ -50,16 +52,16 @@ Generating a route recommendation is local: it does not start a vendor CLI or ma
|
|
|
50
52
|
Open **Plugins → Add plugin** and enter:
|
|
51
53
|
|
|
52
54
|
```text
|
|
53
|
-
@ljwei-stak/dsh-model-router@0.18.
|
|
55
|
+
@ljwei-stak/dsh-model-router@0.18.2
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
Use the official HTTPS npm source `https://registry.npmjs.org/` when a mirror lacks that exact version. Enable the plugin, then fully quit Harness (including its tray process) and restart it. Plugin details should show **0.18.
|
|
58
|
+
Use the official HTTPS npm source `https://registry.npmjs.org/` when a mirror lacks that exact version. Enable the plugin, then fully quit Harness (including its tray process) and restart it. Plugin details should show **0.18.2** and the sidebar should contain **模型路由** (Model Router). A global `npm install -g` alone does not register a Desktop plugin.
|
|
57
59
|
|
|
58
60
|
You can instead install a local `.tgz` archive or its extracted inner `package` directory through the plugin manager. Both must contain `package.json` and `.dsh-plugin`. See [releases](https://github.com/Alice-Marx/dsh-model-router/releases) for archived builds, or build your own checkout below.
|
|
59
61
|
|
|
60
62
|
### Upgrade from the old package name
|
|
61
63
|
|
|
62
|
-
The old name `@ljwei-stak/model-router-galgame` ended at 0.13.0. Remove that plugin entry while retaining your profile/application data, then add `@ljwei-stak/dsh-model-router@0.18.
|
|
64
|
+
The old name `@ljwei-stak/model-router-galgame` ended at 0.13.0. Remove that plugin entry while retaining your profile/application data, then add `@ljwei-stak/dsh-model-router@0.18.2`. 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.
|
|
63
65
|
|
|
64
66
|
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).
|
|
65
67
|
|
package/README.zh.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Model Router · DeepSeek Harness 模型路由插件
|
|
2
2
|
|
|
3
|
-
版本:**0.18.
|
|
3
|
+
版本:**0.18.2**。选模求解器概览见[路由研究说明](docs/ROUTING_RESEARCH.zh.md);公式、条件证明与实验见[数学推导与实验记录](docs/ROUTING_DERIVATION_EXPERIMENTS.zh.md)。[LiveBench 最新证据与验证协议](docs/ROUTING_LIVEBENCH_VALIDATION.zh.md)已核查 2026-06-25 聚合评分/费用;最新逐题质量—费用矩阵尚未取得,不声称真实路由收益。
|
|
4
4
|
|
|
5
5
|
为 **DeepSeek Harness Desktop** 提供任务规划、成本感知的模型分配和官方工具执行工作台。输入任务,比较已配置模型,审阅计划,再明确选择是否执行。
|
|
6
6
|
|
|
7
|
+
**0.18.2**:厂商脚本使用统一根目录下的工具专属子目录,修复 Step 安装替换其他 npm 工具包的问题。Gemini/OpenCode 安装成功与受限执行能力分别显示;Windows Gemini 使用核验后的包入口通过 Node 启动。先升级并重启 Harness,再更新 Step。
|
|
8
|
+
|
|
7
9
|
**0.18.1**:修复安装方式切换卡在“保存方式中…”的问题。已保存设置立即同步到宿主安装配置,命令预览单独刷新;工具体检延迟时仍能继续切换方式。
|
|
8
10
|
|
|
9
11
|
**0.18.0**:补齐厂商提供的 curl/irm 安装方式,安装与更新复用已保存的目录、方式和下载源,并核验目标目录里的版本。修复 MiniMax 官方 PowerShell 脚本以 `<# ... #>` 帮助注释开头时被误判为 HTML 的问题。统一安装设置改为明确保存后生效。
|
|
@@ -18,7 +20,7 @@
|
|
|
18
20
|
|
|
19
21
|
**0.16.0 新增**:[动态数据与持续反馈](docs/ROUTING_ADAPTIVE_LEARNING.zh.md):可选公开价格/LiveBench快照刷新;按任务类型、时间衰减和低样本收缩调整个人偏好,质量门槛独立保留。设置在“预算与安全”,公开源默认关闭;价格源需提供按官方文档核对的固定USD单价JSON,不是通用官网抓价器。本地DSH_HOME共享、最近200运行窗口,暂无真实收益证明。
|
|
20
22
|
|
|
21
|
-
当前版本为 **0.18.
|
|
23
|
+
当前版本为 **0.18.2**,包名为 `@ljwei-stak/dsh-model-router`;声明兼容的宿主为 **DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2**。自 0.12.0 起,路由与 [GAL](https://github.com/Alice-Marx/deepseek-harness-galgame) 已拆成两个独立插件。
|
|
22
24
|
|
|
23
25
|
**0.15.0 更新**:工作台分为四页,路由建议紧接任务规划展示;执行输入变化时丢弃旧预览;裁剪历史或重试后仍保留费用累计;源码开发新增独立模拟 UI 预览。
|
|
24
26
|
|
|
@@ -50,16 +52,16 @@
|
|
|
50
52
|
打开 **插件 → 添加插件**,填写:
|
|
51
53
|
|
|
52
54
|
```text
|
|
53
|
-
@ljwei-stak/dsh-model-router@0.18.
|
|
55
|
+
@ljwei-stak/dsh-model-router@0.18.2
|
|
54
56
|
```
|
|
55
57
|
|
|
56
|
-
镜像缺少该精确版本时使用官方 HTTPS npm 源 `https://registry.npmjs.org/`。安装并启用后,完全退出 Harness(包括托盘进程)再启动。插件详情应显示 **0.18.
|
|
58
|
+
镜像缺少该精确版本时使用官方 HTTPS npm 源 `https://registry.npmjs.org/`。安装并启用后,完全退出 Harness(包括托盘进程)再启动。插件详情应显示 **0.18.2**,侧栏出现**模型路由**。仅运行全局 `npm install -g` 不会把插件注册到桌面版 profile。
|
|
57
59
|
|
|
58
60
|
也可在插件管理器填写本地 `.tgz` 文件的绝对路径,或解压后的内层 `package` 目录;其中应包含 `package.json` 与 `.dsh-plugin`。[历史发布页](https://github.com/Alice-Marx/dsh-model-router/releases)提供归档;已克隆源码的开发方式见下文。
|
|
59
61
|
|
|
60
62
|
### 从旧包名升级
|
|
61
63
|
|
|
62
|
-
`@ljwei-stak/model-router-galgame` 的最后版本为 0.13.0。保留 profile 与应用数据,移除旧插件条目,再添加 `@ljwei-stak/dsh-model-router@0.18.
|
|
64
|
+
`@ljwei-stak/model-router-galgame` 的最后版本为 0.13.0。保留 profile 与应用数据,移除旧插件条目,再添加 `@ljwei-stak/dsh-model-router@0.18.2`。不要同时安装两个包:它们共用内部插件 id `model-router-galgame` 与工具名;id 保留使已有路由设置和执行历史能够延续。
|
|
63
65
|
|
|
64
66
|
从 0.11.x 合并版迁移时,先备份 profile 并导出希望保留的 GAL 进度。剧情、立绘、音乐、自由模式和存档属于独立 `@ljwei-stak/dsh-galgame` 插件。导出范围及同 profile 迁移的限制见 [MIGRATION.md](docs/archive/MIGRATION.md)。
|
|
65
67
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
模型路由从 **0.12.0** 起独立发布;GAL 是新的 **0.1.0** 插件。两者均声明支持官方 **DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2**,可分别安装和更新。
|
|
4
4
|
|
|
5
|
-
当前路由正式版为 **0.18.
|
|
5
|
+
当前路由正式版为 **0.18.2**。本版修复 Step 安装覆盖共享 npm 包目录的问题:统一目录是安装根,厂商脚本使用工具专属子目录。Gemini 安装状态与受限执行能力分开,Windows 无界面启动使用核验后的包入口。先升级并完全退出 Harness 后重启,再使用 Step 更新/修复。保留 0.18.0 的官方安装方法、MiniMax 脚本校验和统一目录改进。统一安装设置需点击保存,再选择工具的安装方式。公开数据默认关闭;安装时选择下方精确版本。
|
|
6
6
|
|
|
7
7
|
[路由 README](../README.zh.md) · [迁移说明](archive/MIGRATION.md) · [独立 GAL README](https://github.com/Alice-Marx/deepseek-harness-galgame#readme)
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
| 需求 | 添加插件输入框 | 安装后的入口 |
|
|
12
12
|
| --- | --- | --- |
|
|
13
|
-
| 分析复杂度、按质量和费用选模型、调用官方工具 | `@ljwei-stak/dsh-model-router@0.18.
|
|
13
|
+
| 分析复杂度、按质量和费用选模型、调用官方工具 | `@ljwei-stak/dsh-model-router@0.18.2` | 模型路由 |
|
|
14
14
|
| 剧情、立绘、音乐、存档、自由对话 | `@ljwei-stak/dsh-galgame@0.1.0` | Gal 模块 |
|
|
15
15
|
| 两者都要 | 分别安装上面两个包 | 模型路由 + Gal 模块 |
|
|
16
16
|
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
2. 在“包名、GitHub 仓库地址或本地目录路径”输入框填一个完整包名,例如:
|
|
25
25
|
|
|
26
26
|
```text
|
|
27
|
-
@ljwei-stak/dsh-model-router@0.18.
|
|
27
|
+
@ljwei-stak/dsh-model-router@0.18.2
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
只想安装 GAL 时填写:
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
4. 核对预览的包名、版本和宿主兼容信息,安装并启用。完全退出 Harness(包括托盘进程)再启动,确保 Host 加载新代码。
|
|
43
|
-
5. 检查插件详情与侧边栏:路由应为 **0.18.
|
|
43
|
+
5. 检查插件详情与侧边栏:路由应为 **0.18.2 / 模型路由**;GAL 应为 **0.1.0 / Gal 模块**。仅装路由时没有 GAL 入口是预期行为。
|
|
44
44
|
|
|
45
45
|
普通用户无需 `npm install -g`,该命令不会把插件注册到当前 Harness profile。
|
|
46
46
|
|
|
@@ -48,13 +48,13 @@
|
|
|
48
48
|
|
|
49
49
|
进入对应 Release 下载 `.tgz`:
|
|
50
50
|
|
|
51
|
-
- [模型路由 Releases](https://github.com/Alice-Marx/dsh-model-router/releases)(也可运行 `npm pack @ljwei-stak/dsh-model-router@0.18.
|
|
51
|
+
- [模型路由 Releases](https://github.com/Alice-Marx/dsh-model-router/releases)(也可运行 `npm pack @ljwei-stak/dsh-model-router@0.18.2` 获取同一安装包)
|
|
52
52
|
- [独立 GAL v0.1.0](https://github.com/Alice-Marx/deepseek-harness-galgame/releases/tag/v0.1.0)
|
|
53
53
|
|
|
54
54
|
在“添加插件”填写下载文件的**绝对路径**,例如:
|
|
55
55
|
|
|
56
56
|
```text
|
|
57
|
-
D:\Plugins\ljwei-stak-dsh-model-router-0.18.
|
|
57
|
+
D:\Plugins\ljwei-stak-dsh-model-router-0.18.2.tgz
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
```text
|
|
@@ -64,7 +64,7 @@ D:\Plugins\ljwei-stak-dsh-galgame-0.1.0.tgz
|
|
|
64
64
|
如附有 `.sha256` 文件,用 PowerShell 计算并比较摘要:
|
|
65
65
|
|
|
66
66
|
```powershell
|
|
67
|
-
Get-FileHash -Algorithm SHA256 -LiteralPath 'D:\Plugins\ljwei-stak-dsh-model-router-0.18.
|
|
67
|
+
Get-FileHash -Algorithm SHA256 -LiteralPath 'D:\Plugins\ljwei-stak-dsh-model-router-0.18.2.tgz'
|
|
68
68
|
Get-FileHash -Algorithm SHA256 -LiteralPath 'D:\Plugins\ljwei-stak-dsh-galgame-0.1.0.tgz'
|
|
69
69
|
```
|
|
70
70
|
|
|
@@ -74,7 +74,7 @@ Get-FileHash -Algorithm SHA256 -LiteralPath 'D:\Plugins\ljwei-stak-dsh-galgame-0
|
|
|
74
74
|
|
|
75
75
|
1. 在旧合并插件的 GAL 播放器导出需要保留的剧情进度,并备份整个 Harness profile。每份 JSON 仅保存**当前剧情状态**,不含所有手动槽、设置、已读记录或本地音乐;重要槽位可依次读档再导出。
|
|
76
76
|
2. 在**同一个 profile**安装独立 `@ljwei-stak/dsh-galgame@0.1.0`。
|
|
77
|
-
3. 0.13.0 起包名改为 `@ljwei-stak/dsh-model-router`(旧包名最后版本为 0.13.0,内容相同,之后不再更新):在插件管理器卸载原 `@ljwei-stak/model-router-galgame`(保留 profile 与应用数据),再添加 `@ljwei-stak/dsh-model-router@0.18.
|
|
77
|
+
3. 0.13.0 起包名改为 `@ljwei-stak/dsh-model-router`(旧包名最后版本为 0.13.0,内容相同,之后不再更新):在插件管理器卸载原 `@ljwei-stak/model-router-galgame`(保留 profile 与应用数据),再添加 `@ljwei-stak/dsh-model-router@0.18.2`。不要两个同时安装(条目 id 与工具名相同);条目 id 未变,路由设置与执行记录会保留。完成两项更新后再玩剧情,避免同时操作旧合并入口和新入口。
|
|
78
78
|
4. 现在路由与 GAL 各有一个入口。独立 GAL 沿用旧存档 localStorage 键,核对当前进度和三槽;更换 profile 或进度未恢复时,先切换到匹配剧目,再导入 JSON 备份。
|
|
79
79
|
5. 后续分别更新需要的插件;只用路由可以不启用或不安装 GAL。保留 profile 与应用数据,卸载插件时不要清理故事数据。
|
|
80
80
|
|
|
@@ -82,7 +82,7 @@ GAL 核心只包含**《回声之城·正篇》**与**《旧城迁移篇:未
|
|
|
82
82
|
|
|
83
83
|
## 5. 模型路由第一次使用
|
|
84
84
|
|
|
85
|
-
工作台自 0.15.0 起分成**任务与执行、模型配置、官方工具、预算与安全**四页;0.18.
|
|
85
|
+
工作台自 0.15.0 起分成**任务与执行、模型配置、官方工具、预算与安全**四页;0.18.2 沿用该布局。切换页面保留安装任务、终端会话和未完成的输入。
|
|
86
86
|
|
|
87
87
|
1. 在 Harness 官方**模型**页配置供应商、模型与凭据。插件读取准确的 `provider/model` 目录,模型档案不保存 API Key。
|
|
88
88
|
2. 在**模型配置**刷新目录,逐条填写自报质量评分、核对过的输入/输出单价(USD / 百万 token)和擅长方向;缺价留空。可选 `cliModel` 必须是该厂商 CLI 已验证接受的名字。可选 LiveBench 名称须准确对应版本与推理档位;它是用户确认的映射,不自动证明实际执行档位。
|
|
@@ -114,7 +114,7 @@ GAL 核心只包含**《回声之城·正篇》**与**《旧城迁移篇:未
|
|
|
114
114
|
| 检查 | 预期 |
|
|
115
115
|
| --- | --- |
|
|
116
116
|
| 宿主版本 | 0.2.0-rc.1 或 0.2.0-rc.2;包括 `rc` 后缀。 |
|
|
117
|
-
| 路由独立安装 | 0.18.
|
|
117
|
+
| 路由独立安装 | 0.18.2 详情、模型路由入口、目录和模型档案可用;不出现旧合并 GAL 入口。 |
|
|
118
118
|
| GAL 独立安装 | 0.1.0 详情、Gal 模块入口、标题与两部核心剧情可用;不要求安装路由。 |
|
|
119
119
|
| 共同安装 | 两个入口各一份,路由工具和 GAL 播放器均能使用。 |
|
|
120
120
|
| 存档迁移 | 同 profile 可读旧键;跨 profile 用匹配剧目 JSON 导入;旧篇目的数据不覆盖。 |
|
|
@@ -125,11 +125,11 @@ GAL 核心只包含**《回声之城·正篇》**与**《旧城迁移篇:未
|
|
|
125
125
|
| 动态公开数据 | 默认关闭;自行启用后核对快照版本、校验时间及失败/过期状态;人工单价仍优先。 |
|
|
126
126
|
| 可编辑执行 | 在干净测试仓库核对审批、隔离工作区、补丁整合、厂商实际模型与计费。 |
|
|
127
127
|
|
|
128
|
-
历史 0.15.0 的自动测试验证为 220 项通过、3 项 Windows 平台跳过;组件 UI 预览检查使用模拟数据。**0.18.
|
|
128
|
+
历史 0.15.0 的自动测试验证为 220 项通过、3 项 Windows 平台跳过;组件 UI 预览检查使用模拟数据。**0.18.2 的发布验证见[更新记录](CHANGELOG.md)**,不把模拟公开源/模型流和组件预览当作真实付费调用或 Harness Desktop 端到端验收;上表中的实际安装、账号和工作区检查仍需在使用环境中完成。
|
|
129
129
|
|
|
130
130
|
## 8. 开发者从完整源码构建
|
|
131
131
|
|
|
132
|
-
路由需要 Node.js **22.19+** 与 **pnpm 10.34.6**。从完整源码仓库根目录构建;复现正式版 0.18.
|
|
132
|
+
路由需要 Node.js **22.19+** 与 **pnpm 10.34.6**。从完整源码仓库根目录构建;复现正式版 0.18.2 时检出对应发布标签。旧版(如 0.15.0)标签保留其当时内容。运行:
|
|
133
133
|
|
|
134
134
|
```powershell
|
|
135
135
|
pnpm install --frozen-lockfile --strict-peer-dependencies
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 模型路由工作台:配置、规划与实际执行
|
|
2
2
|
|
|
3
|
-
适用版本:正式版 `@ljwei-stak/dsh-model-router@0.18.
|
|
3
|
+
适用版本:正式版 `@ljwei-stak/dsh-model-router@0.18.2`;声明兼容 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
|
|
|
@@ -113,11 +113,13 @@
|
|
|
113
113
|
|
|
114
114
|
| 设置 | 作用 | 限制 |
|
|
115
115
|
| --- | --- | --- |
|
|
116
|
-
|
|
|
116
|
+
| 统一安装根目录 | npm/pnpm 使用该根;可配置厂商脚本使用各工具子目录 | 必须是绝对路径,不能是磁盘或共享根目录;留空=各厂商默认位置 |
|
|
117
117
|
| npm 源地址 | npm / pnpm 方式的下载源 | 必须 https,可填国内镜像 |
|
|
118
118
|
| 安装脚本源覆盖 | 按工具 ID 指定厂商安装脚本的镜像 | JSON,键是官方工具 ID,值是 https 链接 |
|
|
119
119
|
| 允许 curl / irm 安装脚本 | 是否允许执行厂商安装脚本 | 关闭后只剩 npm / pnpm |
|
|
120
120
|
|
|
121
|
+
0.18.2 起,以 `D:/AI/tools` 为根时,Step 脚本安装到 `D:/AI/tools/stepcode`,MiniMax 脚本安装到 `D:/AI/tools/minimax-code`,其他可配置脚本同样使用专属子目录。厂商脚本可能替换整个运行资源目录,不能直接交给共享 npm 根。旧共享目录中的文件仅供识别并保留,不用于新脚本更新;可重新安装到独立目录。升级并重启 Host 后再操作 Step。
|
|
122
|
+
|
|
121
123
|
几个容易踩的点:
|
|
122
124
|
|
|
123
125
|
- **不同工具对"自定义目录"的支持不一样。** npm 使用 `--prefix`;pnpm 使用自身的全局包目录、命令目录和存储目录参数。阶跃的安装脚本接受 `--install-dir`(Windows 上是 `-InstallDir`),也生效。OpenCode 的官方脚本把路径写死成 `~/.opencode/bin`,**无法**接受自定义目录——如果设置里填了目录,它的行内会明确写出将装到哪里。
|
|
@@ -132,20 +134,20 @@ MiniMax 官方 Windows 脚本的 `<# ... #>` 是 PowerShell 帮助注释。0.18.
|
|
|
132
134
|
|
|
133
135
|
| 工具 | npm / pnpm | curl(Linux / macOS) | irm(Windows) | 统一目录支持 |
|
|
134
136
|
| --- | --- | --- | --- | --- |
|
|
135
|
-
| Kimi Code | ✓ | bash | PowerShell |
|
|
137
|
+
| Kimi Code | ✓ | bash | PowerShell | 脚本使用根目录/kimi-code/bin |
|
|
136
138
|
| Claude Code | ✓ | bash | PowerShell | 包管理器支持;脚本固定在 ~/.local/bin |
|
|
137
139
|
| Codex | ✓ | sh | PowerShell | 支持;Windows 脚本使用统一目录/codex/bin,避免把共享目录变成目录联接 |
|
|
138
|
-
| MiniMax Code | ✓ | bash | PowerShell |
|
|
140
|
+
| MiniMax Code | ✓ | bash | PowerShell | 脚本使用根目录/minimax-code |
|
|
139
141
|
| MiMo Code | ✓ | bash | PowerShell | 包管理器与 Windows 脚本支持;Unix 脚本固定 ~/.mimocode/bin |
|
|
140
|
-
| Grok Build | ✓ | bash | PowerShell |
|
|
142
|
+
| Grok Build | ✓ | bash | PowerShell | 脚本使用根目录/grok-build |
|
|
141
143
|
| Gemini CLI | ✓ | — | — | 支持 |
|
|
142
144
|
| OpenCode | ✓ | bash | — | 包管理器支持;脚本固定 ~/.opencode/bin |
|
|
143
|
-
| Step Code | — | bash | PowerShell |
|
|
145
|
+
| Step Code | — | bash | PowerShell | 脚本使用根目录/stepcode |
|
|
144
146
|
| ZCode | — | — | — | 官方 Windows 桌面安装窗口中选择 |
|
|
145
147
|
|
|
146
148
|
“—”表示当前厂商未提供相应渠道。每个工具的安装方式下拉框保留所有声明方式,并说明本机不适用的原因。MiniMax 官方脚本只接受官方 npm 源或 npmmirror;其他 npm 镜像请选 npm/pnpm 方式。
|
|
147
149
|
|
|
148
|
-
|
|
150
|
+
安装成功只表示所选目录的命令入口和版本已确认,不代表工具支持受限执行。Gemini 使用核验后的 npm/pnpm 包入口无界面执行;OpenCode、Step 当前可安装和在终端中使用,尚不支持受限任务。缺少这类能力不再报安装失败。Kimi、MiMo、Grok 的独立原生脚本安装目前没有插件需要的 npm 文件摘要证明,自动任务会显示执行入口未就绪并回退到 API;npm/pnpm 安装保留可核验的执行路径。Windows Codex 脚本使用目录联接,插件不提供通用文件删除卸载;请按官方方式移除原生安装。
|
|
149
151
|
|
|
150
152
|
### 安装方式的会话与 Agent 等价物
|
|
151
153
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ljwei-stak/dsh-model-router",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Model Router for official DeepSeek Harness Desktop: cost-aware task routing, dependent work packages and official vendor CLI discovery, installation and execution. GAL is available separately as @ljwei-stak/dsh-galgame.",
|
|
6
6
|
"repository": {
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
"experiment:routing-summary": "node scripts/summarize-routing-search.mjs",
|
|
104
104
|
"experiment:routing-adaptive": "node scripts/adaptive-routing-study.mjs",
|
|
105
105
|
"audit:livebench": "node scripts/livebench-aggregate-audit.mjs",
|
|
106
|
-
"test": "node --test tests/adaptive-*.test.mjs tests/dynamic-data.test.mjs tests/router.test.mjs tests/routing-optimizer.test.mjs tests/routing-evaluation.test.mjs tests/routing-paired-statistics.test.mjs tests/routing-selftest-tools.test.mjs tests/livebench-aggregate-audit.test.mjs tests/livebench-prior-study.test.mjs tests/routing-search-experiment.test.mjs tests/routing-regression-study.test.mjs tests/routing-search-summary.test.mjs tests/harness-*.test.mjs tests/official-tools.test.mjs tests/official-team-runtime.test.mjs tests/model-profiles.test.mjs tests/model-profile-editor.test.mjs tests/tool-install-state.test.mjs tests/tool-install-sdk-ui.test.mjs tests/vendor-adapters.test.mjs tests/desktop-compat.test.mjs tests/plugin-split.test.mjs tests/install-settings-host.test.mjs tests/task-executors.test.mjs tests/tool-install-preferences.test.mjs tests/roadmap.test.mjs tests/insights-state.test.mjs tests/billing-team-ledger.test.mjs tests/subscription-billing.test.mjs tests/subscription-host.test.mjs tests/test-pass-regressions.test.mjs tests/roadmap-gaps.test.mjs tests/cordis-inject.test.mjs tests/cli-terminal.test.mjs tests/cli-terminal-gateway.test.mjs tests/latest-versions.test.mjs tests/vendor-installer-validation.test.mjs tests/install-entry-selection.test.mjs",
|
|
106
|
+
"test": "node --test tests/adaptive-*.test.mjs tests/dynamic-data.test.mjs tests/router.test.mjs tests/routing-optimizer.test.mjs tests/routing-evaluation.test.mjs tests/routing-paired-statistics.test.mjs tests/routing-selftest-tools.test.mjs tests/livebench-aggregate-audit.test.mjs tests/livebench-prior-study.test.mjs tests/routing-search-experiment.test.mjs tests/routing-regression-study.test.mjs tests/routing-search-summary.test.mjs tests/harness-*.test.mjs tests/official-tools.test.mjs tests/official-team-runtime.test.mjs tests/model-profiles.test.mjs tests/model-profile-editor.test.mjs tests/tool-install-state.test.mjs tests/tool-install-sdk-ui.test.mjs tests/vendor-adapters.test.mjs tests/desktop-compat.test.mjs tests/plugin-split.test.mjs tests/install-settings-host.test.mjs tests/task-executors.test.mjs tests/tool-install-preferences.test.mjs tests/roadmap.test.mjs tests/insights-state.test.mjs tests/billing-team-ledger.test.mjs tests/subscription-billing.test.mjs tests/subscription-host.test.mjs tests/test-pass-regressions.test.mjs tests/roadmap-gaps.test.mjs tests/cordis-inject.test.mjs tests/cli-terminal.test.mjs tests/cli-terminal-gateway.test.mjs tests/latest-versions.test.mjs tests/vendor-installer-validation.test.mjs tests/install-entry-selection.test.mjs tests/script-directory-safety.test.mjs",
|
|
107
107
|
"prepack": "node scripts/prepack.mjs",
|
|
108
108
|
"pack:local": "npm pack --pack-destination dist"
|
|
109
109
|
},
|