@ljwei-stak/dsh-model-router 0.16.4 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh-plugin/client.js +920 -102
- package/.dsh-plugin/index.mjs +133 -27
- package/.dsh-plugin/official-tools-remote-service.mjs +82 -10
- package/.dsh-plugin/shared/latest-versions.mjs +19 -0
- package/.dsh-plugin/shared/official-tool-executor.mjs +8 -0
- package/.dsh-plugin/shared/official-tool-registry.mjs +393 -12
- package/.dsh-plugin/shared/official-tools-remote.mjs +38 -2
- package/.dsh-plugin/shared/official-tools-runtime.mjs +599 -68
- package/.dsh-plugin/shared/tool-health.mjs +7 -0
- package/.dsh-plugin/shared/tool-install-preferences.mjs +346 -0
- package/CHANGELOG.md +26 -1
- package/README.md +13 -3
- package/README.zh.md +13 -3
- package/{INSTALLATION_GUIDE.zh.md → docs/INSTALLATION_GUIDE.zh.md} +7 -7
- package/docs/WORKBENCH_USER_GUIDE.zh.md +36 -5
- package/{MIGRATION.md → docs/archive/MIGRATION.md} +1 -1
- package/docs/archive/README.md +21 -0
- package/package.json +5 -4
|
@@ -28,6 +28,8 @@ export const LOGIN_GUIDES = Object.freeze({
|
|
|
28
28
|
'mimo-code': Object.freeze({ command: 'mimo auth login', steps: '在终端运行 mimo auth login,选择 Xiaomi MiMo Platform 登录小米账号或填入 API Key;mimo auth list 可查看已保存的凭据。未登录时 MiMo Auto 免费匿名通道可能仍可用。' }),
|
|
29
29
|
'grok-build': Object.freeze({ command: 'grok login', steps: '在终端运行 grok login,按浏览器提示登录 xAI 账号;无浏览器环境用 grok login --device-auth,或设置 XAI_API_KEY 使用 API 计费。' }),
|
|
30
30
|
zcode: Object.freeze({ command: null, steps: '打开 ZCode 桌面版,在欢迎页选择“连接 BigModel 继续使用”或“连接 Z.ai 继续使用”完成授权;已订阅 GLM Coding Plan 时在“模型设置 → BigModel”右上角选择“编程套餐”绑定。改用 API Key 时 OpenAI 地址须填 Coding 专用端点 https://open.bigmodel.cn/api/coding/paas/v4。ZCode 没有可调用的登录状态命令。' }),
|
|
31
|
+
opencode: Object.freeze({ command: 'opencode auth login', steps: '在终端运行 opencode auth login,按提示完成登录;OpenCode 支持多家供应商,用 /connect 在会话内选择并保存凭据。也可在 opencode.json 中配置 provider 的 API Key。' }),
|
|
32
|
+
stepcode: Object.freeze({ command: 'step login', steps: '在终端运行 step login(step login status --json 可查看状态),按提示完成阶跃账号登录;登录信息保存在 ~/.stepcode/auth.json。卸载本工具不会删除该文件。' }),
|
|
31
33
|
})
|
|
32
34
|
|
|
33
35
|
/** Environment variable names whose presence means a CLI can authenticate without a session. */
|
|
@@ -39,6 +41,8 @@ const KEY_ENV = Object.freeze({
|
|
|
39
41
|
'minimax-code': ['MINIMAX_API_KEY'],
|
|
40
42
|
'mimo-code': ['MIMO_API_KEY'],
|
|
41
43
|
'grok-build': ['XAI_API_KEY'],
|
|
44
|
+
opencode: ['OPENCODE_API_KEY', 'ANTHROPIC_API_KEY', 'OPENAI_API_KEY'],
|
|
45
|
+
stepcode: ['STEP_API_KEY', 'STEPCODE_API_KEY'],
|
|
42
46
|
})
|
|
43
47
|
|
|
44
48
|
/**
|
|
@@ -54,6 +58,8 @@ export function credentialFileFor(toolId, { home = '', env = process.env } = {})
|
|
|
54
58
|
if (toolId === 'mimo-code') { const root = value('XDG_DATA_HOME') || (base && `${base}/.local/share`); return root ? `${root}/mimocode/auth.json` : '' }
|
|
55
59
|
if (toolId === 'grok-build') { const root = value('GROK_HOME') || (base && `${base}/.grok`); return root ? `${root}/auth.json` : '' }
|
|
56
60
|
if (toolId === 'gemini') return base ? `${base}/.gemini/oauth_creds.json` : ''
|
|
61
|
+
if (toolId === 'stepcode') { const root = value('STEP_CODING_AGENT_DIR') || (base && `${base}/.stepcode`); return root ? `${root}/auth.json` : '' }
|
|
62
|
+
if (toolId === 'opencode') return base ? `${base}/.local/share/opencode/auth.json` : ''
|
|
57
63
|
return ''
|
|
58
64
|
}
|
|
59
65
|
|
|
@@ -61,6 +67,7 @@ const FILE_LOGIN = Object.freeze({
|
|
|
61
67
|
'kimi-code': { found: path => `检测到 Kimi Code 登录凭据文件 ${path}(未验证是否过期)。`, missing: path => `未找到 Kimi Code 登录凭据文件 ${path}(也可能通过 config.toml 中的自定义供应商认证),登录状态未知;如未登录请运行 kimi login。`, missingState: 'unknown' },
|
|
62
68
|
'mimo-code': { found: path => `检测到 MiMo Code 凭据文件 ${path}(未验证是否有效,也可能只含 API Key)。`, missing: path => `未找到 MiMo Code 凭据文件 ${path};MiMo Auto 免费匿名通道可能仍可用,订阅/账号状态未知。`, missingState: 'unknown' },
|
|
63
69
|
'grok-build': { found: path => `检测到 Grok 登录凭据文件 ${path}(未验证是否过期)。`, missing: path => `未找到 Grok 登录凭据文件 ${path},登录状态未知;如未登录请运行 grok login。`, missingState: 'unknown' },
|
|
70
|
+
'stepcode': { found: path => `检测到 Step Code 登录凭据文件 ${path}(未验证是否过期)。`, missing: path => `未找到 Step Code 凭据文件 ${path},登录状态未知;如未登录请运行 step login。`, missingState: 'unknown' },
|
|
64
71
|
})
|
|
65
72
|
|
|
66
73
|
/**
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns the user's plugin settings into one concrete, reviewable install plan.
|
|
3
|
+
*
|
|
4
|
+
* Two separate "addresses" are configurable, and both stay on the Host: a
|
|
5
|
+
* unified install directory and a set of download sources (an npm registry plus
|
|
6
|
+
* per-tool installer-script URLs). The client never sends either — the RPC
|
|
7
|
+
* carries only a registry id and a method id, so a hostile client cannot point
|
|
8
|
+
* this plugin at an arbitrary package or URL. Everything here is Host config
|
|
9
|
+
* the user typed into their own settings form.
|
|
10
|
+
*
|
|
11
|
+
* Script installs are never piped into an interpreter. A plan carries the
|
|
12
|
+
* vendor URL plus the markers that still identify that vendor's script; the
|
|
13
|
+
* runtime downloads it with a size cap, checks the markers, stages a copy and
|
|
14
|
+
* runs that. So a mirror may serve different bytes, but it may not serve a
|
|
15
|
+
* different program.
|
|
16
|
+
*
|
|
17
|
+
* This module is imported by the browser bundle too, so it must stay free of
|
|
18
|
+
* Node built-ins: paths are handled with plain string rules, and the Host passes
|
|
19
|
+
* the home directory in when it needs one.
|
|
20
|
+
*/
|
|
21
|
+
import {
|
|
22
|
+
DEFAULT_NPM_REGISTRY,
|
|
23
|
+
defaultInstallMethod,
|
|
24
|
+
installMethodBlockReason,
|
|
25
|
+
installMethodFor,
|
|
26
|
+
installMethodsFor,
|
|
27
|
+
methodHonoursInstallDir,
|
|
28
|
+
} from './official-tool-registry.mjs'
|
|
29
|
+
|
|
30
|
+
export const MAX_INSTALL_DIR_CHARS = 4_096
|
|
31
|
+
export const MAX_SOURCE_URL_CHARS = 2_048
|
|
32
|
+
export const MAX_OVERRIDES = 64
|
|
33
|
+
|
|
34
|
+
/** Windows drive paths, UNC paths, or anything rooted at `/`. */
|
|
35
|
+
export function isAbsolutePath(value) {
|
|
36
|
+
const raw = String(value ?? '')
|
|
37
|
+
if (!raw) return false
|
|
38
|
+
if (raw.startsWith('/')) return true
|
|
39
|
+
if (/^[A-Za-z]:[\\/]/.test(raw)) return true
|
|
40
|
+
return /^\\\\[^\\]+\\[^\\]+/.test(raw)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Join path segments with the separator the first absolute segment already uses. */
|
|
44
|
+
export function joinPath(base, ...segments) {
|
|
45
|
+
const separator = String(base ?? '').includes('\\') && !String(base ?? '').includes('/') ? '\\' : '/'
|
|
46
|
+
const parts = [String(base ?? '').replace(/[\\/]+$/, ''), ...segments.map(item => String(item ?? '').replace(/^[\\/]+|[\\/]+$/g, ''))]
|
|
47
|
+
return parts.filter(Boolean).join(separator) || separator
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Expand `~` and environment references the way a shell would on this host. */
|
|
51
|
+
export function expandInstallDir(value, { home = '', env = {} } = {}) {
|
|
52
|
+
const raw = String(value ?? '').trim()
|
|
53
|
+
if (!raw) return ''
|
|
54
|
+
if (raw.includes('\0')) throw new Error('安装目录包含无效字符。')
|
|
55
|
+
let expanded = raw
|
|
56
|
+
const userHome = String(home ?? '')
|
|
57
|
+
if (expanded === '~') expanded = userHome
|
|
58
|
+
else if (expanded.startsWith('~/') || expanded.startsWith('~\\')) expanded = joinPath(userHome, expanded.slice(2))
|
|
59
|
+
// `%VAR%` on Windows, `$VAR` elsewhere — both forms the vendor scripts accept.
|
|
60
|
+
expanded = expanded
|
|
61
|
+
.replace(/%([A-Za-z_][A-Za-z0-9_]*)%/g, (match, name) => env[name] ?? match)
|
|
62
|
+
.replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, name) => env[name] ?? match)
|
|
63
|
+
if (expanded.length > MAX_INSTALL_DIR_CHARS) throw new Error('安装目录过长。')
|
|
64
|
+
if (!isAbsolutePath(expanded)) throw new Error('安装目录必须是绝对路径。')
|
|
65
|
+
// A bare root ("/", "D:\\", "\\\\server\\share") would take the machine with it.
|
|
66
|
+
const withoutTrailing = expanded.replace(/[\\/]+$/, '')
|
|
67
|
+
if (!withoutTrailing) throw new Error('安装目录不能是文件系统根目录。')
|
|
68
|
+
if (/^[A-Za-z]:$/.test(withoutTrailing)) throw new Error('安装目录不能是文件系统根目录。')
|
|
69
|
+
if (/^\\\\[^\\]+\\[^\\]+$/.test(withoutTrailing)) throw new Error('安装目录不能是网络共享根目录。')
|
|
70
|
+
return expanded
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Only https mirrors are accepted; a plain-http script source can be tampered with in transit. */
|
|
74
|
+
export function normalizeSourceUrl(value, subject) {
|
|
75
|
+
const raw = String(value ?? '').trim()
|
|
76
|
+
if (!raw) return ''
|
|
77
|
+
if (raw.length > MAX_SOURCE_URL_CHARS) throw new Error(`${subject}地址过长。`)
|
|
78
|
+
let url
|
|
79
|
+
try { url = new URL(raw) } catch { throw new Error(`${subject}不是有效网址。`) }
|
|
80
|
+
if (url.protocol !== 'https:') throw new Error(`${subject}必须使用 https。`)
|
|
81
|
+
if (url.username || url.password) throw new Error(`${subject}不能包含账号密码。`)
|
|
82
|
+
return url.toString()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function parseJsonObject(value, subject) {
|
|
86
|
+
const raw = String(value ?? '').trim()
|
|
87
|
+
if (!raw) return {}
|
|
88
|
+
let parsed
|
|
89
|
+
try { parsed = JSON.parse(raw) } catch { throw new Error(`${subject}不是有效 JSON。`) }
|
|
90
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
91
|
+
throw new Error(`${subject}必须是 JSON 对象。`)
|
|
92
|
+
}
|
|
93
|
+
const entries = Object.entries(parsed)
|
|
94
|
+
if (entries.length > MAX_OVERRIDES) throw new Error(`${subject}条目过多。`)
|
|
95
|
+
return Object.fromEntries(entries.filter(([, item]) => typeof item === 'string' && item.trim()))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function valueOf(config, key, fallback) {
|
|
99
|
+
const value = config?.[key]
|
|
100
|
+
return value !== undefined && typeof value?.get === 'function' ? value.get() : value ?? fallback
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Registry entries used by the installer are the only keys an override may name. */
|
|
104
|
+
function keepKnownToolIds(raw, known, subject) {
|
|
105
|
+
for (const key of Object.keys(raw)) {
|
|
106
|
+
if (!known.has(key)) throw new Error(`${subject}中的 ${key} 不是官方工具 ID。`)
|
|
107
|
+
}
|
|
108
|
+
return raw
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Read the plugin settings into a normalized, already-validated shape.
|
|
113
|
+
* Throws with a user-facing message on the first bad value, so the settings
|
|
114
|
+
* form and the installer fail the same way.
|
|
115
|
+
*/
|
|
116
|
+
export function parseInstallPreferences(config = {}, { tools = [] } = {}) {
|
|
117
|
+
const known = new Set(tools.map(tool => tool.id))
|
|
118
|
+
const installDir = expandInstallDir(valueOf(config, 'toolInstallDir', ''))
|
|
119
|
+
const registry = normalizeSourceUrl(valueOf(config, 'toolNpmRegistry', DEFAULT_NPM_REGISTRY), 'npm 源') || DEFAULT_NPM_REGISTRY
|
|
120
|
+
const methodRaw = keepKnownToolIds(
|
|
121
|
+
parseJsonObject(valueOf(config, 'toolInstallMethodsJson', '{}'), '安装方式覆盖'), known, '安装方式覆盖')
|
|
122
|
+
const scriptRaw = keepKnownToolIds(
|
|
123
|
+
parseJsonObject(valueOf(config, 'toolScriptUrlsJson', '{}'), '安装脚本源覆盖'), known, '安装脚本源覆盖')
|
|
124
|
+
const methods = {}
|
|
125
|
+
const scriptUrls = {}
|
|
126
|
+
for (const [id, methodId] of Object.entries(methodRaw)) methods[id] = methodId.trim()
|
|
127
|
+
for (const [id, url] of Object.entries(scriptRaw)) scriptUrls[id] = normalizeSourceUrl(url, `${id} 安装脚本源`)
|
|
128
|
+
for (const [id, methodId] of Object.entries(methods)) {
|
|
129
|
+
if (!installMethodFor({ id, installMethods: tools.find(tool => tool.id === id)?.installMethods }, methodId)) {
|
|
130
|
+
throw new Error(`${id} 不支持安装方式 ${methodId}。`)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
installDir,
|
|
135
|
+
registry,
|
|
136
|
+
methods,
|
|
137
|
+
scriptUrls,
|
|
138
|
+
/** Script execution stays a deliberate act; a config file alone must not run one. */
|
|
139
|
+
allowScriptInstall: valueOf(config, 'toolAllowScriptInstall', true) !== false,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Where a script install puts the binary: the user's directory, else the vendor default. */
|
|
144
|
+
export function scriptInstallDir(tool, preferences, { home = '', method = null } = {}) {
|
|
145
|
+
if (!vendorScriptRelative(tool, method)) return ''
|
|
146
|
+
return preferences?.installDir || vendorScriptDir(tool, { home, method })
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function vendorScriptRelative(tool, method) {
|
|
150
|
+
return String(method?.defaultInstallDir || tool?.defaultScriptInstallDir || '')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The location the vendor script uses on its own, ignoring any configured directory. */
|
|
154
|
+
export function vendorScriptDir(tool, { home = '', method = null } = {}) {
|
|
155
|
+
return joinPath(home, ...vendorScriptRelative(tool, method).split('/').filter(Boolean))
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** argv for a package-manager method, honouring the configured registry and prefix. */
|
|
159
|
+
export function packageManagerArgs(method, preferences, { installDir = '' } = {}) {
|
|
160
|
+
const registry = preferences?.registry || DEFAULT_NPM_REGISTRY
|
|
161
|
+
const args = method.installArgs.filter(arg => !arg.startsWith('--registry='))
|
|
162
|
+
args.push(`--registry=${registry}`)
|
|
163
|
+
if (installDir) args.push('--prefix', installDir)
|
|
164
|
+
return [...args]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function baseUrlOf(scriptUrl) {
|
|
168
|
+
try {
|
|
169
|
+
const url = new URL(scriptUrl)
|
|
170
|
+
return `${url.origin}${url.pathname.replace(/\/[^/]*$/, '')}`
|
|
171
|
+
} catch { return '' }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The method to use: the requested one, else the user's saved choice, else the
|
|
176
|
+
* registry default — and if none of those run on this platform, the first
|
|
177
|
+
* declared method that does, so a Windows user is not asked to pick between
|
|
178
|
+
* two Unix shells.
|
|
179
|
+
*/
|
|
180
|
+
function pickMethod(tool, methodId, preferences, platform) {
|
|
181
|
+
const requested = methodId ?? preferences?.methods?.[tool.id]
|
|
182
|
+
if (!requested) {
|
|
183
|
+
const available = installMethodsFor(tool).filter(method => !installMethodBlockReason(method, platform))
|
|
184
|
+
// An installed Windows MiniMax lives in the official installer layout.
|
|
185
|
+
// npm does not move that copy, so the unpinned one-click update is the
|
|
186
|
+
// vendor PowerShell script. Pinning npm or pnpm still runs that manager.
|
|
187
|
+
if (platform === 'win32' && tool?.id === 'minimax-code' && preferences?.allowScriptInstall !== false) {
|
|
188
|
+
const powershell = available.find(method => method.id === 'script-powershell')
|
|
189
|
+
if (powershell) return powershell
|
|
190
|
+
}
|
|
191
|
+
return available[0] ?? defaultInstallMethod(tool)
|
|
192
|
+
}
|
|
193
|
+
// A named method is never silently swapped. If the tool does not declare it,
|
|
194
|
+
// or it does not run here, the caller gets the reason instead of a surprise.
|
|
195
|
+
const chosen = installMethodFor(tool, requested)
|
|
196
|
+
if (!chosen) throw new Error(`${tool.label} 没有名为 ${requested} 的安装方式。`)
|
|
197
|
+
return chosen
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function unsupportedMethodError(tool) {
|
|
201
|
+
return tool?.manager === 'signed-windows-installer'
|
|
202
|
+
? 'ZCode 使用官方签名桌面安装器,由用户在安装窗口选择目录。'
|
|
203
|
+
: `${tool?.label ?? '此工具'} 没有可用的安装方式。`
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* One concrete plan for one tool and one method. A thrown Error means the user
|
|
208
|
+
* asked for something this tool cannot do, with a reason they can act on.
|
|
209
|
+
*/
|
|
210
|
+
export function resolveInstallPlan(tool, methodId, preferences = {}, { platform = 'linux', home = '' } = {}) {
|
|
211
|
+
if (!tool || tool.unsupported) throw new Error(tool?.unsupportedReason ?? '此工具不支持一键安装。')
|
|
212
|
+
const method = pickMethod(tool, methodId, preferences, platform)
|
|
213
|
+
if (!method) throw new Error(unsupportedMethodError(tool))
|
|
214
|
+
const blocked = installMethodBlockReason(method, platform)
|
|
215
|
+
if (blocked) throw new Error(`${tool.label}:${blocked}`)
|
|
216
|
+
if (method.kind === 'script' && preferences?.allowScriptInstall === false) {
|
|
217
|
+
throw new Error('安装脚本方式已在插件设置中关闭;请改用包管理器方式,或在设置里重新允许。')
|
|
218
|
+
}
|
|
219
|
+
const installDir = preferences?.installDir || ''
|
|
220
|
+
if (method.kind === 'script') {
|
|
221
|
+
const scriptUrl = preferences?.scriptUrls?.[tool.id] || method.scriptUrl
|
|
222
|
+
const target = methodHonoursInstallDir(method) ? scriptInstallDir(tool, preferences, { home, method }) : ''
|
|
223
|
+
const notices = []
|
|
224
|
+
if (!target && installDir) {
|
|
225
|
+
notices.push(`${method.label} 无法使用自定义安装目录(官方脚本写死了安装路径),将安装到 ${vendorScriptDir(tool, { home, method })}。`)
|
|
226
|
+
}
|
|
227
|
+
if (method.env?.includes('MCODE_NPM_REGISTRY') && preferences?.registry && !minimaxScriptRegistry(preferences.registry)) {
|
|
228
|
+
notices.push('MiniMax 官方脚本只接受 https://registry.npmjs.org/ 或 https://registry.npmmirror.com/,本次不改写它的 npm 源。')
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
toolId: tool.id,
|
|
232
|
+
method,
|
|
233
|
+
methodId: method.id,
|
|
234
|
+
kind: 'script',
|
|
235
|
+
label: method.label,
|
|
236
|
+
shell: method.shell,
|
|
237
|
+
scriptUrl,
|
|
238
|
+
verify: method.verify,
|
|
239
|
+
installArg: method.installArgs,
|
|
240
|
+
installDirEnv: method.installDirEnv || (method.env?.includes('STEP_INSTALL_DIR') ? 'STEP_INSTALL_DIR' : ''),
|
|
241
|
+
installDir: target,
|
|
242
|
+
relatives: scriptRelatives(tool, method),
|
|
243
|
+
env: scriptEnv(method, { installDir: target, scriptUrl, registry: preferences?.registry }),
|
|
244
|
+
notices,
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
toolId: tool.id,
|
|
249
|
+
method,
|
|
250
|
+
methodId: method.id,
|
|
251
|
+
kind: 'package-manager',
|
|
252
|
+
label: method.label,
|
|
253
|
+
manager: method.manager,
|
|
254
|
+
args: packageManagerArgs(method, preferences, { installDir }),
|
|
255
|
+
installDir,
|
|
256
|
+
env: {},
|
|
257
|
+
notices: [],
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Same plan shape for removal, so a UI can show exactly what will be deleted. */
|
|
262
|
+
export function resolveUninstallPlan(tool, methodId, preferences = {}, options = {}) {
|
|
263
|
+
const platform = options.platform ?? 'linux'
|
|
264
|
+
const method = pickMethod(tool, methodId, preferences, platform)
|
|
265
|
+
if (!method) throw new Error(tool?.manager === 'signed-windows-installer'
|
|
266
|
+
? 'ZCode 是桌面安装版,请在系统“应用”中卸载它。'
|
|
267
|
+
: `${tool?.label ?? '此工具'} 没有可用的卸载方式。`)
|
|
268
|
+
const blocked = installMethodBlockReason(method, platform)
|
|
269
|
+
if (blocked) throw new Error(`${tool.label}:${blocked}`)
|
|
270
|
+
if (method.kind === 'script') {
|
|
271
|
+
return { toolId: tool.id, method, methodId: method.id, kind: 'script', label: method.label, ...resolveInstallLocation(tool, method, preferences, options) }
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
toolId: tool.id,
|
|
275
|
+
method,
|
|
276
|
+
methodId: method.id,
|
|
277
|
+
kind: 'package-manager',
|
|
278
|
+
label: method.label,
|
|
279
|
+
manager: method.manager,
|
|
280
|
+
args: [
|
|
281
|
+
...method.uninstallArgs,
|
|
282
|
+
...(preferences?.registry ? [`--registry=${preferences.registry}`] : []),
|
|
283
|
+
...(preferences?.installDir ? ['--prefix', preferences.installDir] : []),
|
|
284
|
+
],
|
|
285
|
+
installDir: preferences?.installDir || '',
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Paths under the install directory that an uninstall may delete. No `..`. */
|
|
290
|
+
export function scriptRelatives(tool, method) {
|
|
291
|
+
const listed = method?.binaryRelatives?.length
|
|
292
|
+
? method.binaryRelatives
|
|
293
|
+
: tool?.scriptBinaryRelatives?.length
|
|
294
|
+
? tool.scriptBinaryRelatives
|
|
295
|
+
: tool?.scriptBinaryNames
|
|
296
|
+
return [...(listed ?? [])]
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Where the tool's binary lives, so a later uninstall can find it. */
|
|
300
|
+
export function resolveInstallLocation(tool, method, preferences = {}, { home = '' } = {}) {
|
|
301
|
+
if (!method) return null
|
|
302
|
+
if (method.kind === 'script') {
|
|
303
|
+
// A method that cannot honour the configured directory always lands in the
|
|
304
|
+
// vendor's own location, whatever the user typed.
|
|
305
|
+
const dir = methodHonoursInstallDir(method)
|
|
306
|
+
? scriptInstallDir(tool, preferences, { home, method })
|
|
307
|
+
: vendorScriptDir(tool, { home, method })
|
|
308
|
+
return {
|
|
309
|
+
kind: 'script',
|
|
310
|
+
dir,
|
|
311
|
+
binaries: [...(tool.scriptBinaryNames ?? [])],
|
|
312
|
+
relatives: scriptRelatives(tool, method),
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return { kind: 'package-manager', dir: preferences?.installDir || '', binaries: [], relatives: [] }
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** MiniMax's installer rejects every registry except these two. */
|
|
319
|
+
const MINIMAX_SCRIPT_REGISTRIES = new Set([
|
|
320
|
+
'https://registry.npmjs.org/',
|
|
321
|
+
'https://registry.npmmirror.com/',
|
|
322
|
+
])
|
|
323
|
+
|
|
324
|
+
export function minimaxScriptRegistry(registry) {
|
|
325
|
+
const value = String(registry ?? '').trim()
|
|
326
|
+
if (!value) return ''
|
|
327
|
+
const normalized = value.endsWith('/') ? value : `${value}/`
|
|
328
|
+
return MINIMAX_SCRIPT_REGISTRIES.has(normalized) ? normalized : ''
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function scriptEnv(method, { installDir, scriptUrl, registry }) {
|
|
332
|
+
const env = {}
|
|
333
|
+
const dirEnv = method.installDirEnv || (method.env?.includes('STEP_INSTALL_DIR') ? 'STEP_INSTALL_DIR' : '')
|
|
334
|
+
if (installDir && dirEnv) env[dirEnv] = installDir
|
|
335
|
+
// The step installers read their release base from this variable, so a mirror
|
|
336
|
+
// URL also moves the binaries and the checksum manifest, not just the script.
|
|
337
|
+
if (method.env?.includes('STEP_RELEASE_BASE_URL') && scriptUrl) {
|
|
338
|
+
const base = baseUrlOf(scriptUrl)
|
|
339
|
+
if (base) env.STEP_RELEASE_BASE_URL = base
|
|
340
|
+
}
|
|
341
|
+
if (method.env?.includes('MCODE_NPM_REGISTRY')) {
|
|
342
|
+
const allowed = minimaxScriptRegistry(registry)
|
|
343
|
+
if (allowed) env.MCODE_NPM_REGISTRY = allowed
|
|
344
|
+
}
|
|
345
|
+
return env
|
|
346
|
+
}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.17.1 — 2026-10-11
|
|
6
|
+
|
|
7
|
+
Stable patch under the npm `latest` tag. Fully quit Harness, including the tray or WebUI host process, and restart after upgrading so the Host reloads. Existing 0.17.0 artifacts remain unchanged.
|
|
8
|
+
|
|
9
|
+
- **MiniMax Windows one-click update accepts the official installer again.** The published `install.ps1` opens with PowerShell comment-based help (`<#`), and the sanity check treated every leading `<` as an HTML page, so the real script was refused before it ran. HTML and XML documents are still refused. UTF-8/UTF-16 BOMs are decoded first. Content-Type is not used: the CDN serves the script as `application/octet-stream`. A download that is empty, HTML, or missing `@minimax-ai/code`, `MCODE_INSTALL_DIR` and `MiniMax Code` still fails with the missing piece named.
|
|
10
|
+
- **Official script installers are selectable.** Claude Code, Codex, MiniMax, Kimi, MiMo and Grok now offer the vendor `curl | bash` / `irm | iex` installer next to npm and pnpm, on the platforms that script supports. Gemini stays on npm/pnpm. ZCode stays the signed desktop installer. One-click install, repair and uninstall run the method shown on the row. MiniMax on Windows defaults to `irm | iex` (that is the copy npm does not update); pinning npm or pnpm runs that manager instead, and still falls back to the official script if the probed version does not advance.
|
|
11
|
+
- **Updates use the configured install directory.** npm/pnpm keep `--prefix`. Scripts that document a directory variable (`MCODE_INSTALL_DIR`, `CODEX_INSTALL_DIR`, `KIMI_INSTALL_DIR`, `GROK_BIN_DIR`, `MIMOCODE_INSTALL_DIR`, Step Code's flag) receive that path. The MiniMax Windows fallback no longer substitutes the npm prefix: a configured directory wins, otherwise an existing official layout is updated in place, otherwise `%USERPROFILE%\.minimax-code`. Scripts that hardcode a path (Claude, OpenCode, MiMo on Unix) say so on the row.
|
|
12
|
+
- Windows validation for this patch: MiniMax `irm | iex` against a configured install directory completed with `mcode.cmd` present and version 0.6.8; the false “not an official Windows install script” refusal did not recur on main tip `1162f276`.
|
|
13
|
+
|
|
14
|
+
## 0.17.0 — 2026-10-11
|
|
15
|
+
|
|
16
|
+
Stable minor under the npm `latest` tag. Fully quit Harness, including the tray or WebUI host process, and restart after upgrading so the Host reloads. Existing 0.16.4 artifacts remain unchanged.
|
|
17
|
+
|
|
18
|
+
- **Two new official tools.** `opencode` (OpenCode, `opencode-ai` on npm or the official `curl -fsSL … | bash` installer) and `stepcode` (Step Code from StepFun, installed only by the vendor's `curl | bash` / `irm | iex` script — it publishes no npm package). Both are covered by the registry's probe, version lookup, health check and login guidance. Step Code's latest version is read from the same `latest.json` release manifest its own installer uses. Neither has a verified restricted execution entry yet, and both say so rather than claiming one.
|
|
19
|
+
- **Every tool can now declare several install methods.** `installMethods` is the closed list the Host may use: `npm` and `pnpm` everywhere a package exists, plus the vendor installer script where the vendor ships one. The existing `manager` / `installArgs` stay the default method, so nothing about the old one-click path changed.
|
|
20
|
+
- **Unified install settings.** New volatile config: `toolInstallDir`, `toolNpmRegistry`, `toolInstallMethodsJson`, `toolScriptUrlsJson`, `toolAllowScriptInstall`. They are editable from the **官方工具** page and from the plugin settings form, and apply to one-click install, repair and uninstall as well as `/tools` and the agent tools. An install directory becomes `--prefix` for npm/pnpm and `-InstallDir` / `--install-dir` for Step Code. OpenCode's official script hardcodes `~/.opencode/bin` and takes no directory, so for that tool the row says where it will actually land.
|
|
21
|
+
- **Vendor scripts are verified before they run, never piped.** The plugin downloads the script with a size cap, requires the vendor's own markers, refuses an HTML error page or another vendor's installer, writes a staged copy and runs that. A configured mirror may rehost the same script; it may not substitute another program. A Step Code script mirror also redirects that vendor's binary and checksum downloads. Script installs can be switched off entirely.
|
|
22
|
+
- **One-click repair and uninstall.** Repair re-runs the same fixed official command even when the tool already reports the latest version, which is the case a broken install actually presents. Uninstall removes only the program: package-manager installs go back through the same manager, script installs delete the single executable the vendor installer placed plus that install's own `# stepcode` PATH block in the shell profile. Configuration, credentials and session history are always kept. `checkedRemovableFile` re-checks the basename, the install directory and the root/home guards on every delete.
|
|
23
|
+
- **The RPC stays closed.** `installTool`, `uninstallTool` and `repairTool` take one strict `{ tool, method? }` request; an unknown field, an unknown tool id or a method the tool does not declare is refused. Install addresses still come only from Host config, so a compromised client cannot redirect an install. `list()` now also reports the effective install directory, source and per-tool commands so the panel can show what will run.
|
|
24
|
+
- `/tools` accepts `install`, `uninstall`, `repair`, `cancel` and `method`, each with an optional method id. New agent tools `model_router_tool_repair` and `model_router_tool_uninstall`, plus a `method` parameter on `model_router_tool_install`. Install, repair and uninstall each ask for approval, and uninstall's prompt states that settings and login are kept.
|
|
25
|
+
- The workbench panel shows the settings block, a per-row method selector with the platform reason for anything unavailable, the resolved command, and repair/uninstall buttons. Install settings also appear in the plugin settings card with the same client-side validation as the Host.
|
|
26
|
+
- Release validation on Windows 11: 649 tests passed, 3 conditional skips, `npm run build:client` and `npm run check:client` passed. No real vendor CLI was installed, no vendor script was executed, and no paid model call was made as part of this verification.
|
|
27
|
+
|
|
3
28
|
## 0.16.4 — 2026-10-09
|
|
4
29
|
|
|
5
30
|
Stable patch under the npm `latest` tag. Fully quit Harness, including the tray or WebUI host process, and restart after upgrading so the Host reloads. Existing 0.16.3 artifacts remain unchanged.
|
|
@@ -130,7 +155,7 @@ Prerelease for DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2, published under
|
|
|
130
155
|
|
|
131
156
|
### Package renamed
|
|
132
157
|
- The npm package is now **`@ljwei-stak/dsh-model-router`**. The last version under the old name `@ljwei-stak/model-router-galgame` is 0.13.0, with the same content as `@ljwei-stak/dsh-model-router@0.13.0`; the old name will get no further updates. Upgrade by removing the old plugin and adding the new one; do not install both.
|
|
133
|
-
- The profile entry id, panel id, settings namespace and Cordis plugin name stay `model-router-galgame`, and state stays in `~/.dsh/model-router/`, so saved settings and run history carry over. See [MIGRATION.md](MIGRATION.md).
|
|
158
|
+
- The profile entry id, panel id, settings namespace and Cordis plugin name stay `model-router-galgame`, and state stays in `~/.dsh/model-router/`, so saved settings and run history carry over. See [MIGRATION.md](docs/archive/MIGRATION.md).
|
|
134
159
|
|
|
135
160
|
### Official CLI execution (PR #1)
|
|
136
161
|
- Assigned work packages run through the official vendor CLIs (Claude Code, Codex, Gemini CLI, Kimi Code, MiniMax Code, MiMo Code, Grok Build) with headless adapters; failures fall back to the Harness model catalog API.
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Current version: **0.16.4** (`@ljwei-stak/dsh-model-router`). Its declared host
|
|
|
18
18
|
|
|
19
19
|
**0.15.0 changes:** a four-tab workbench with routing advice directly below task planning, protection against stale execution previews, preserved spending totals across history pruning and retries, and a standalone mock UI preview for source development.
|
|
20
20
|
|
|
21
|
-
[简体中文](README.zh.md) · [Workbench guide (Chinese)](docs/WORKBENCH_USER_GUIDE.zh.md) · [Installation guide (Chinese)](INSTALLATION_GUIDE.zh.md) · [Migration](MIGRATION.md) · [Changelog](CHANGELOG.md)
|
|
21
|
+
[简体中文](README.zh.md) · [Workbench guide (Chinese)](docs/WORKBENCH_USER_GUIDE.zh.md) · [Installation guide (Chinese)](docs/INSTALLATION_GUIDE.zh.md) · [Migration](docs/archive/MIGRATION.md) · [Changelog](CHANGELOG.md)
|
|
22
22
|
|
|
23
23
|
For self-testing, follow the [desktop acceptance and quality–cost experiment steps (Chinese)](docs/ROUTING_SELFTEST_STEPS.zh.md). Start with an 18-question, three-model pilot and an explicit spending limit. Offline helpers do not collect answers or call models; first-stage selection replay is distinct from end-to-end team execution.
|
|
24
24
|
|
|
@@ -57,7 +57,7 @@ You can instead install a local `.tgz` archive or its extracted inner `package`
|
|
|
57
57
|
|
|
58
58
|
The old name `@ljwei-stak/model-router-galgame` ended at 0.13.0. Remove that plugin entry while retaining your profile/application data, then add `@ljwei-stak/dsh-model-router@0.16.4`. Do not keep both entries installed: they share the internal plugin id `model-router-galgame` and tool names. The retained id lets existing router settings and history carry over.
|
|
59
59
|
|
|
60
|
-
For the combined 0.11.x package, back up the profile and export the GAL saves you want to preserve before upgrading. Story playback, artwork, music and saves now belong to the independent `@ljwei-stak/dsh-galgame` package. The detailed save limitations are in [MIGRATION.md](MIGRATION.md).
|
|
60
|
+
For the combined 0.11.x package, back up the profile and export the GAL saves you want to preserve before upgrading. Story playback, artwork, music and saves now belong to the independent `@ljwei-stak/dsh-galgame` package. The detailed save limitations are in [MIGRATION.md](docs/archive/MIGRATION.md).
|
|
61
61
|
|
|
62
62
|
## Use the workbench
|
|
63
63
|
|
|
@@ -143,12 +143,22 @@ The CLI team runner is the plugin's own sequential dependency executor. It does
|
|
|
143
143
|
| `mimo-code` | MiMo Code | `read-only`, approved `workspace-write` |
|
|
144
144
|
| `grok-build` | Grok Build | `read-only`, approved `workspace-write` |
|
|
145
145
|
| `gemini` | Gemini CLI | Routed headless adapter; not the managed single-tool/team runner |
|
|
146
|
+
| `opencode` | OpenCode | Install, probe and login check; no verified restricted execution entry yet |
|
|
147
|
+
| `stepcode` | Step Code | Install, probe and login check; no verified restricted execution entry yet |
|
|
146
148
|
| `zcode` | ZCode | Windows; approved `workspace-write` |
|
|
147
149
|
|
|
148
|
-
Installers use fixed official npm packages at each vendor's `@latest`, or the official publisher-signed Windows ZCode installer. An installation newer than the reported latest is not downgraded. Latest-version lookups are cached for about 12 hours; unavailable lookups leave the version unknown. New vendor releases are not automatically validated by this project. ZCode's installation window still requires you to choose a directory and finish installation.
|
|
150
|
+
Installers use fixed official npm packages at each vendor's `@latest`, the vendor's own installer script, or the official publisher-signed Windows ZCode installer. An installation newer than the reported latest is not downgraded. Latest-version lookups are cached for about 12 hours; unavailable lookups leave the version unknown. New vendor releases are not automatically validated by this project. ZCode's installation window still requires you to choose a directory and finish installation.
|
|
151
|
+
|
|
152
|
+
**Unified install settings.** The top of the **官方工具** page (also in the plugin settings form) sets the install address for every tool at once: one install directory (empty means each vendor's default location), an npm source, and per-tool overrides for vendor installer-script mirrors. Directories must be absolute; sources must be https. Each row picks its own install method from `npm`, `pnpm` or the vendor script (`curl | bash` or `irm | iex`); a method that cannot run on this platform says so on the row instead of being silently swapped. The exact command is shown above the buttons before anything runs.
|
|
153
|
+
|
|
154
|
+
**Script installs never pipe remote content into an interpreter.** The plugin downloads the vendor script, checks it is still that vendor's official installer (vendor markers required, HTML pages and other vendors' scripts refused), and runs the local copy. A mirror may rehost the same script; it may not substitute a different program. Step Code's script accepts an install directory; the OpenCode script hardcodes `~/.opencode/bin`, so a custom directory only applies to it through npm/pnpm's `--prefix`. Script installs can be switched off entirely, leaving only the package managers.
|
|
155
|
+
|
|
156
|
+
**One-click repair and uninstall.** Repair re-runs the same fixed official command, which is what a broken install needs even when it already reports the latest version. Uninstall removes only the program: a package-manager install goes back through the same manager, and a script install deletes just the executable the vendor installer placed plus that install's own PATH entry in the shell profile. **Configuration, credentials and session history are always kept.**
|
|
149
157
|
|
|
150
158
|
Installation, trusted launch readiness, account login and model entitlement are separate checks. An unknown login status is not proof of logout. On Windows, supported managed launch entries are checked against publisher signatures or official npm-tarball attestations; ZCode also records trust for its bundled script per signed build. See [the registry](.dsh-plugin/shared/official-tool-registry.mjs) and [executor](.dsh-plugin/shared/official-tool-executor.mjs) for the exact platform rules.
|
|
151
159
|
|
|
160
|
+
In a session, `/tools install|uninstall|repair|method <tool-id> [method]` does the same; agents can call `model_router_tool_install`, `model_router_tool_repair` and `model_router_tool_uninstall`. The install directory and download sources always come from the user's own plugin settings and never travel over the RPC, so no call can redirect an install at an arbitrary package or URL.
|
|
161
|
+
|
|
152
162
|
`model_router_execute` has fixed headless adapters for Claude, Codex and Gemini. Other suppliers use the model-directory API unless a supported subscription route applies. Missing/unusable CLIs can fall back to that API; subscription failures follow the policy below.
|
|
153
163
|
|
|
154
164
|
Editable execution needs a clean Git repository, Host approval and the Harness process sandbox. Changes are made in an isolated worktree and applied back only after successful execution and a matching clean source workspace. Failed or conflicting integration is reported. Git-ignored outputs need separate review. Read-only CLI flags do not necessarily limit file reads to the workspace; check **安全边界**, especially Codex's broader readable scope.
|
package/README.zh.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
**0.15.0 更新**:工作台分为四页,路由建议紧接任务规划展示;执行输入变化时丢弃旧预览;裁剪历史或重试后仍保留费用累计;源码开发新增独立模拟 UI 预览。
|
|
20
20
|
|
|
21
|
-
[English](README.md) · [工作台完整指南](docs/WORKBENCH_USER_GUIDE.zh.md) · [安装与验证](INSTALLATION_GUIDE.zh.md) · [迁移说明](MIGRATION.md) · [更新记录](CHANGELOG.md)
|
|
21
|
+
[English](README.md) · [工作台完整指南](docs/WORKBENCH_USER_GUIDE.zh.md) · [安装与验证](docs/INSTALLATION_GUIDE.zh.md) · [迁移说明](docs/archive/MIGRATION.md) · [更新记录](CHANGELOG.md)
|
|
22
22
|
|
|
23
23
|
需要自行实测时,按[安装验收与质量—成本实验步骤](docs/ROUTING_SELFTEST_STEPS.zh.md)先完成桌面检查和 18 题×3 模型小样,再决定正式样本与费用上限。方案区分首包选模投影和实际团队执行;配套离线入口不采集、不调用模型,真实收益仍待验证。
|
|
24
24
|
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
|
|
58
58
|
`@ljwei-stak/model-router-galgame` 的最后版本为 0.13.0。保留 profile 与应用数据,移除旧插件条目,再添加 `@ljwei-stak/dsh-model-router@0.16.4`。不要同时安装两个包:它们共用内部插件 id `model-router-galgame` 与工具名;id 保留使已有路由设置和执行历史能够延续。
|
|
59
59
|
|
|
60
|
-
从 0.11.x 合并版迁移时,先备份 profile 并导出希望保留的 GAL 进度。剧情、立绘、音乐、自由模式和存档属于独立 `@ljwei-stak/dsh-galgame` 插件。导出范围及同 profile 迁移的限制见 [MIGRATION.md](MIGRATION.md)。
|
|
60
|
+
从 0.11.x 合并版迁移时,先备份 profile 并导出希望保留的 GAL 进度。剧情、立绘、音乐、自由模式和存档属于独立 `@ljwei-stak/dsh-galgame` 插件。导出范围及同 profile 迁移的限制见 [MIGRATION.md](docs/archive/MIGRATION.md)。
|
|
61
61
|
|
|
62
62
|
## 工作台的四个页面
|
|
63
63
|
|
|
@@ -143,12 +143,22 @@ CLI 团队是插件自有的顺序依赖执行器。Harness 内建 Agent Teams
|
|
|
143
143
|
| `mimo-code` | MiMo Code | `read-only`、经审批的 `workspace-write` |
|
|
144
144
|
| `grok-build` | Grok Build | `read-only`、经审批的 `workspace-write` |
|
|
145
145
|
| `gemini` | Gemini CLI | 路由执行的无界面适配器;不属于单工具/团队托管执行器 |
|
|
146
|
+
| `opencode` | OpenCode | 安装、检测与登录体检;尚未完成受限执行入口核验 |
|
|
147
|
+
| `stepcode` | Step Code | 安装、检测与登录体检;尚未完成受限执行入口核验 |
|
|
146
148
|
| `zcode` | ZCode | Windows;经审批的 `workspace-write` |
|
|
147
149
|
|
|
148
|
-
安装使用固定官方 npm 包的 `@latest
|
|
150
|
+
安装使用固定官方 npm 包的 `@latest`、厂商官方安装脚本,或 ZCode 官方、经过发布者签名验证的 Windows 安装器。已安装版本比查询到的最新版更高时不会降级。最新版查询缓存约 12 小时;离线/查询失败时保持未知。厂商新版本没有自动获得本项目验证。ZCode 打开安装窗口后,仍需手动选目录并完成安装。
|
|
151
|
+
|
|
152
|
+
**统一安装设置。** 在**官方工具**页顶部(或插件设置页)可以一次设定所有工具的安装目录与下载源:统一安装目录(留空=各厂商默认位置)、npm 源地址,以及按工具 ID 覆盖厂商安装脚本的镜像地址。目录必须是绝对路径,下载地址必须是 https。每个工具都可以在**安装方式**下拉框里单独选择 `npm`、`pnpm`、厂商安装脚本(`curl | bash` 或 `irm | iex`);选了某个平台不支持的方式时,行内会写明原因而不是悄悄换成别的。每行上方实时显示将要执行的完整命令。
|
|
153
|
+
|
|
154
|
+
**脚本方式不会把远程内容直接管道给解释器。** 插件先下载厂商安装脚本、核对它仍然是该厂商的官方脚本(校验厂商标记、拒绝 HTML 页面和其他厂商的脚本),再执行本地副本。镜像可以重新托管同一个脚本,但不能替换成别的程序。阶跃的脚本还支持安装目录参数,OpenCode 的官方脚本写死了 `~/.opencode/bin`,因此自定义安装目录对它只能通过 npm/pnpm 的 `--prefix` 生效。脚本方式可以整体关闭,只保留包管理器方式。
|
|
155
|
+
|
|
156
|
+
**一键修复与一键卸载。** 修复会用同一条固定官方命令重新安装一遍,适合修复损坏或不完整的安装;卸载只删除程序本身——包管理器安装走同一个管理器卸载,脚本安装只删除厂商安装器放下的那一个可执行文件,并顺带清掉它在 shell 配置里写的 PATH 记录。**配置、登录信息和历史记录一律保留**,重新安装后仍在。
|
|
149
157
|
|
|
150
158
|
**已安装、可信入口就绪、账号已登录、模型有权限是不同状态。** 登录未知不代表已退出。在 Windows,托管入口按发布者签名或官方 npm tarball 校验;ZCode 另对签名构建中的脚本记录首次信任摘要。平台细则见[注册表](.dsh-plugin/shared/official-tool-registry.mjs)和[执行器](.dsh-plugin/shared/official-tool-executor.mjs)。
|
|
151
159
|
|
|
160
|
+
会话内可用 `/tools install|uninstall|repair|method <工具id> [安装方式]`;Agent 可调用 `model_router_tool_install`、`model_router_tool_repair` 和 `model_router_tool_uninstall`。安装目录与下载源始终来自用户自己的插件设置,不会由客户端或 Agent 传入,因此任何一条调用都无法把安装指向自定义包名或任意地址。
|
|
161
|
+
|
|
152
162
|
`model_router_execute` 的固定无界面适配器为 Claude、Codex 和 Gemini;其他供应商经模型目录 API 或适用的订阅路线执行。CLI 缺失/不可用时可回退 API;真正尝试后的订阅失败按下方策略处理。
|
|
153
163
|
|
|
154
164
|
可编辑执行要求干净 Git 仓库、Harness 审批与进程沙箱。修改先在独立工作树进行,执行成功且原仓库仍干净并匹配时才整合源码补丁;执行或整合失败会明确报告。Git 忽略的输出另行检查。只读参数不一定把可读文件限制在工作区,尤其需在**安全边界**核对 Codex 的较大可读范围。
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
当前路由正式版为 **0.16.4**。本版修复 PATH 探测丢输出:没有安装日志回调时,`where` 和 `--version` 的 stdout 仍会保留,已安装的 CLI 不再被报成未安装。0.16.3 的命令行参数修复、0.16.2 的更新按钮修复和 0.16.1 的反馈缓存仍然保留。公开数据默认关闭;主观偏好调整不改变公共能力评分或质量门槛。源码开发可使用独立模拟 UI 预览;安装时选择下方精确版本。
|
|
6
6
|
|
|
7
|
-
[路由 README](README.zh.md) · [迁移说明](MIGRATION.md) · [独立 GAL README](https://github.com/Alice-Marx/deepseek-harness-galgame#readme)
|
|
7
|
+
[路由 README](../README.zh.md) · [迁移说明](archive/MIGRATION.md) · [独立 GAL README](https://github.com/Alice-Marx/deepseek-harness-galgame#readme)
|
|
8
8
|
|
|
9
9
|
## 1. 选择需要的插件
|
|
10
10
|
|
|
@@ -93,15 +93,15 @@ GAL 核心只包含**《回声之城·正篇》**与**《旧城迁移篇:未
|
|
|
93
93
|
7. 在**预算与安全**查看每日/月度预算、订阅失败策略及可读/写边界;**持续反馈与本地偏好**可设置学习启停、半衰期、收缩强度和调整上限,**动态公共数据**需自行启用。官方工具终端不经过 Harness 沙箱,继承用户登录环境,需单独确认启动。
|
|
94
94
|
8. 需要 CLI 修改文件时进入官方会话、选择工作区,明确请求 `model_router_tool_run` 或 `model_router_team_execute` 的 `workspace-write` 模式。可编辑托管执行要求干净 Git 仓库、宿主审批和进程沙箱。`model_router_execute` 用于只读路由执行,`model_router_consult` 用于模型 API 意见。
|
|
95
95
|
|
|
96
|
-
完整教程见[工作台使用指南](
|
|
96
|
+
完整教程见[工作台使用指南](WORKBENCH_USER_GUIDE.zh.md);路线算法、订阅与数据保存说明见[README](../README.zh.md)。
|
|
97
97
|
|
|
98
98
|
在成功结果上点 👍 / 👎,再次点已选评价可撤回。评价按准确路线和任务类型形成时间衰减、向零收缩的**主观偏好效用**,默认最大幅度 ±0.04,可配置到不超过 ±0.1;不抬高客观能力分或公共质量门槛。模型复核不视作人工评价,模型侧评分工具需要用户审批;本版不发起付费探索。
|
|
99
99
|
|
|
100
|
-
学习范围是同一个本地 **DSH_HOME** 共享的最近 **200 次运行**,不是多账号隔离或无限长期记忆。“从现在重新学习”只让旧结果不再参与学习,不删除执行记录和费用累计。动态价格只读取用户指定的公开、严格结构化、按官方文档策展的 USD/百万 token 固定费率快照;不是自动抓取所有厂商价目表,复杂阶梯/时段计费不能强行折成该格式。人工单价优先;公开数据默认关闭,详情见[动态数据与持续反馈说明](
|
|
100
|
+
学习范围是同一个本地 **DSH_HOME** 共享的最近 **200 次运行**,不是多账号隔离或无限长期记忆。“从现在重新学习”只让旧结果不再参与学习,不删除执行记录和费用累计。动态价格只读取用户指定的公开、严格结构化、按官方文档策展的 USD/百万 token 固定费率快照;不是自动抓取所有厂商价目表,复杂阶梯/时段计费不能强行折成该格式。人工单价优先;公开数据默认关闭,详情见[动态数据与持续反馈说明](ROUTING_ADAPTIVE_LEARNING.zh.md)。
|
|
101
101
|
|
|
102
|
-
注册表包含 Kimi、Claude、Codex、MiniMax、MiMo、Grok、Gemini 和 ZCode。Claude、Codex、MiMo、Grok 支持单工具/团队只读或可编辑执行;Kimi、MiniMax、ZCode 仅支持获批的可编辑托管执行。Gemini 有路由无界面适配器,不进入单工具/团队托管执行器。详见[官方工具与托管模式](README.zh.md#官方工具与托管模式)。CLI 模型名可能不同于 Harness 目录;多数 CLI 不提供可核验的实际模型 ID,要看厂商记录与账单。
|
|
102
|
+
注册表包含 Kimi、Claude、Codex、MiniMax、MiMo、Grok、Gemini 和 ZCode。Claude、Codex、MiMo、Grok 支持单工具/团队只读或可编辑执行;Kimi、MiniMax、ZCode 仅支持获批的可编辑托管执行。Gemini 有路由无界面适配器,不进入单工具/团队托管执行器。详见[官方工具与托管模式](../README.zh.md#官方工具与托管模式)。CLI 模型名可能不同于 Harness 目录;多数 CLI 不提供可核验的实际模型 ID,要看厂商记录与账单。
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
安装使用固定官方来源:npm/pnpm 的 `@latest`,厂商发布的 `curl | bash` / `irm | iex` 脚本(Claude、Codex、MiniMax、Kimi、MiMo、Grok、OpenCode、Step Code,按平台显示),或 ZCode 签名安装器。在**官方工具**里为每个工具选择方式,并填写统一安装目录;一键安装和更新都使用这一选择。npm/pnpm 会带上 `--prefix`。支持目录的脚本把该路径传给厂商自己的变量(MiniMax 是 `MCODE_INSTALL_DIR`)。写死路径的脚本会在该行写明实际落点。未填写目录时,MiniMax Windows 更新使用已有官方目录,否则使用 `%USERPROFILE%\.minimax-code`,不再改放到 npm 全局前缀。Grok 数据目录与登录应使用一致的 `GROK_HOME`。MiniMax Windows 原生依赖失败时,从固定官方 HTTPS 来源下载最新版安装脚本,检查大小、UTF-8/UTF-16 文本和厂商标记后原样执行(PowerShell 的 `<#` 说明头不是 HTML),**没有锁定该安装脚本的哈希**。安装后的 MiniMax CLI 代码另按已安装版本的官方 npm tarball 摘要核验。MiMo/Grok 同样使用官方 npm 内容证明;适用的 Claude/Codex/ZCode Windows 入口验证发布者签名,ZCode 内置脚本还有每个签名构建的首次信任摘要检查。具体规则见[执行器](../.dsh-plugin/shared/official-tool-executor.mjs)和[npm 内容核验](../.dsh-plugin/shared/npm-attestation.mjs)。
|
|
105
105
|
|
|
106
106
|
## 6. GAL 第一次使用
|
|
107
107
|
|
|
@@ -125,7 +125,7 @@ GAL 核心只包含**《回声之城·正篇》**与**《旧城迁移篇:未
|
|
|
125
125
|
| 动态公开数据 | 默认关闭;自行启用后核对快照版本、校验时间及失败/过期状态;人工单价仍优先。 |
|
|
126
126
|
| 可编辑执行 | 在干净测试仓库核对审批、隔离工作区、补丁整合、厂商实际模型与计费。 |
|
|
127
127
|
|
|
128
|
-
历史 0.15.0 的自动测试验证为 220 项通过、3 项 Windows 平台跳过;组件 UI 预览检查使用模拟数据。**0.16.4 的发布验证见[更新记录](CHANGELOG.md)**,不把模拟公开源/模型流和组件预览当作真实付费调用或 Harness Desktop 端到端验收;上表中的实际安装、账号和工作区检查仍需在使用环境中完成。
|
|
128
|
+
历史 0.15.0 的自动测试验证为 220 项通过、3 项 Windows 平台跳过;组件 UI 预览检查使用模拟数据。**0.16.4 的发布验证见[更新记录](../CHANGELOG.md)**,不把模拟公开源/模型流和组件预览当作真实付费调用或 Harness Desktop 端到端验收;上表中的实际安装、账号和工作区检查仍需在使用环境中完成。
|
|
129
129
|
|
|
130
130
|
## 8. 开发者从完整源码构建
|
|
131
131
|
|
|
@@ -145,4 +145,4 @@ pnpm 10 没有 `pnpm peers check`;使用严格依赖安装。客户端修改
|
|
|
145
145
|
|
|
146
146
|
## 历史兼容提醒
|
|
147
147
|
|
|
148
|
-
合并版 **0.11.1 / 0.11.0** 声明 rc.1 与 rc.2;**0.10.2** 是未发布的本地兼容修复。已发布 **0.10.1** 仅声明 rc.1,rc.2 拒绝安装是 peer 依赖问题,换镜像不能修复;**0.8.0** 使用 0.1.7-rc.2 依赖,不用于当前宿主。历史 Git 标签和安装包保留,详见[更新记录](CHANGELOG.md)和[迁移说明](MIGRATION.md)。
|
|
148
|
+
合并版 **0.11.1 / 0.11.0** 声明 rc.1 与 rc.2;**0.10.2** 是未发布的本地兼容修复。已发布 **0.10.1** 仅声明 rc.1,rc.2 拒绝安装是 peer 依赖问题,换镜像不能修复;**0.8.0** 使用 0.1.7-rc.2 依赖,不用于当前宿主。历史 Git 标签和安装包保留,详见[更新记录](../CHANGELOG.md)和[迁移说明](archive/MIGRATION.md)。
|