@ljwei-stak/dsh-model-router 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh-plugin/client.js +3092 -0
- package/.dsh-plugin/index.mjs +1651 -0
- package/.dsh-plugin/official-tools-remote-service.mjs +104 -0
- package/.dsh-plugin/shared/harness-plan.mjs +179 -0
- package/.dsh-plugin/shared/livebench.mjs +264 -0
- package/.dsh-plugin/shared/model-profiles.mjs +142 -0
- package/.dsh-plugin/shared/official-team-runtime.mjs +411 -0
- package/.dsh-plugin/shared/official-tool-executor.mjs +801 -0
- package/.dsh-plugin/shared/official-tool-registry.mjs +138 -0
- package/.dsh-plugin/shared/official-tools-remote.mjs +173 -0
- package/.dsh-plugin/shared/official-tools-runtime.mjs +642 -0
- package/.dsh-plugin/shared/router-state.mjs +207 -0
- package/.dsh-plugin/shared/router.mjs +1134 -0
- package/.dsh-plugin/shared/routing-presets.mjs +49 -0
- package/.dsh-plugin/shared/run-ledger.mjs +348 -0
- package/.dsh-plugin/shared/security-boundaries.mjs +54 -0
- package/.dsh-plugin/shared/subscription-billing.mjs +340 -0
- package/.dsh-plugin/shared/task-executors.mjs +1154 -0
- package/.dsh-plugin/shared/tool-health.mjs +311 -0
- package/.dsh-plugin/shared/vendor-mimo-grok-adapter.mjs +308 -0
- package/.dsh-plugin/shared/vendor-minimax-adapter.mjs +247 -0
- package/.dsh-plugin/shared/zcode-bundle.mjs +208 -0
- package/.dsh-plugin/shared/zcode-installer.mjs +247 -0
- package/CHANGELOG.md +36 -0
- package/INSTALLATION_GUIDE.zh.md +134 -0
- package/LICENSE +21 -0
- package/MIGRATION.md +53 -0
- package/README.i18n.yaml +3 -0
- package/README.md +424 -0
- package/README.zh.md +413 -0
- package/cordis.patch.yml +12 -0
- package/docs/assets/candidate-pruning.svg +80 -0
- package/docs/assets/desktop-official-tools-0.9.0.png +0 -0
- package/docs/assets/router-only-0.12.0.png +0 -0
- package/docs/assets/routing-workflow.svg +96 -0
- package/docs/assets/workbench-usage.svg +119 -0
- package/package.json +161 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch and open the pinned official ZCode Windows installer.
|
|
3
|
+
*
|
|
4
|
+
* This module does not perform an unattended install. The visible vendor
|
|
5
|
+
* installer owns the destination picker and any UAC prompt. A successful
|
|
6
|
+
* return means only that the verified installer process was opened.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn, execFile } from 'node:child_process'
|
|
9
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
10
|
+
import { createReadStream, createWriteStream } from 'node:fs'
|
|
11
|
+
import { mkdir, realpath, rename, stat, unlink } from 'node:fs/promises'
|
|
12
|
+
import { tmpdir } from 'node:os'
|
|
13
|
+
import { dirname, isAbsolute, join, relative, sep } from 'node:path'
|
|
14
|
+
import { Readable, Transform } from 'node:stream'
|
|
15
|
+
import { pipeline } from 'node:stream/promises'
|
|
16
|
+
import { promisify } from 'node:util'
|
|
17
|
+
import {
|
|
18
|
+
ZCODE_SIGNER, ZCODE_SUPPORTED_VERSION,
|
|
19
|
+
ZCODE_WINDOWS_INSTALLER_SHA256, ZCODE_WINDOWS_INSTALLER_URL,
|
|
20
|
+
} from './zcode-bundle.mjs'
|
|
21
|
+
|
|
22
|
+
const execFileAsync = promisify(execFile)
|
|
23
|
+
const INSTALLER_NAME = `ZCode-${ZCODE_SUPPORTED_VERSION}-win-x64.exe`
|
|
24
|
+
const DOWNLOAD_ENV_VAR = 'MODEL_ROUTER_ZCODE_DOWNLOAD_DIR'
|
|
25
|
+
const MAX_DOWNLOAD_BYTES = 2_000_000_000
|
|
26
|
+
const MAX_REDIRECTS = 4
|
|
27
|
+
|
|
28
|
+
function drive(path) {
|
|
29
|
+
if (typeof path !== 'string' || path.includes('\0')) return null
|
|
30
|
+
const local = path.startsWith('\\\\?\\') ? path.slice(4) : path
|
|
31
|
+
const match = /^([A-Za-z]):[\\/]/.exec(local)
|
|
32
|
+
return match ? match[1].toUpperCase() : null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function nonCAbsolute(path) {
|
|
36
|
+
return typeof path === 'string' && isAbsolute(path)
|
|
37
|
+
&& drive(path) !== null && drive(path) !== 'C'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function inside(root, child) {
|
|
41
|
+
const part = relative(root, child)
|
|
42
|
+
return part !== '' && part !== '..' && !part.startsWith(`..${sep}`) && !isAbsolute(part)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Select a cache on a non-C local drive. The explicit download directory wins;
|
|
47
|
+
* otherwise the caller's npm prefix drive is preferred, then a non-C TEMP.
|
|
48
|
+
*/
|
|
49
|
+
export async function resolveZCodeDownloadDirectory({
|
|
50
|
+
downloadDir, npmPrefix, env = process.env,
|
|
51
|
+
} = {}) {
|
|
52
|
+
if (process.platform !== 'win32') throw new Error('ZCode 安装器仅支持 Windows。')
|
|
53
|
+
const explicit = downloadDir ?? env[DOWNLOAD_ENV_VAR]
|
|
54
|
+
if (explicit != null && !nonCAbsolute(explicit)) {
|
|
55
|
+
throw new TypeError(`${DOWNLOAD_ENV_VAR} 必须是非 C 盘的本地绝对目录。`)
|
|
56
|
+
}
|
|
57
|
+
const candidates = explicit != null
|
|
58
|
+
? [{ path: explicit, source: 'operator-selected' }]
|
|
59
|
+
: [
|
|
60
|
+
{ path: npmPrefix, source: 'npm-prefix' },
|
|
61
|
+
{ path: env.npm_config_prefix ?? env.NPM_CONFIG_PREFIX, source: 'npm-prefix-env' },
|
|
62
|
+
{ path: env.TEMP, source: 'system-temp' },
|
|
63
|
+
{ path: env.TMP, source: 'system-temp' },
|
|
64
|
+
{ path: tmpdir(), source: 'system-temp' },
|
|
65
|
+
].filter(candidate => nonCAbsolute(candidate.path))
|
|
66
|
+
for (const candidate of candidates) {
|
|
67
|
+
try {
|
|
68
|
+
let ancestor = candidate.path
|
|
69
|
+
for (;;) {
|
|
70
|
+
try { await stat(ancestor); break } catch (error) {
|
|
71
|
+
if (error?.code !== 'ENOENT') throw error
|
|
72
|
+
const next = dirname(ancestor)
|
|
73
|
+
if (next === ancestor) throw error
|
|
74
|
+
ancestor = next
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!nonCAbsolute(await realpath(ancestor))) continue
|
|
78
|
+
await mkdir(candidate.path, { recursive: true })
|
|
79
|
+
const parent = await realpath(candidate.path)
|
|
80
|
+
if (!nonCAbsolute(parent)) continue
|
|
81
|
+
const cache = join(parent, 'model-router-zcode', ZCODE_SUPPORTED_VERSION)
|
|
82
|
+
await mkdir(cache, { recursive: true })
|
|
83
|
+
const canonical = await realpath(cache)
|
|
84
|
+
if (!nonCAbsolute(canonical) || !inside(parent, canonical)) continue
|
|
85
|
+
return { directory: canonical, source: candidate.source }
|
|
86
|
+
} catch {
|
|
87
|
+
// An unavailable prefix can fall back to another non-C directory.
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
throw new Error(`没有可写的非 C 盘下载目录;请设置 ${DOWNLOAD_ENV_VAR}。`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function sha256File(path) {
|
|
94
|
+
const hash = createHash('sha256')
|
|
95
|
+
for await (const chunk of createReadStream(path)) hash.update(chunk)
|
|
96
|
+
return hash.digest('hex').toUpperCase()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function verifyInstallerSignature(path, env) {
|
|
100
|
+
const systemRoot = env.SystemRoot || env.windir || 'C:\\Windows'
|
|
101
|
+
const powershell = join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe')
|
|
102
|
+
const script = '[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new($false); '
|
|
103
|
+
+ '$s=Get-AuthenticodeSignature -LiteralPath $env:MODEL_ROUTER_ZCODE_INSTALLER_PATH; '
|
|
104
|
+
+ '@{status=[string]$s.Status;subject=[string]$s.SignerCertificate.Subject} '
|
|
105
|
+
+ '| ConvertTo-Json -Compress'
|
|
106
|
+
const encoded = Buffer.from(script, 'utf16le').toString('base64')
|
|
107
|
+
try {
|
|
108
|
+
const { stdout } = await execFileAsync(powershell, [
|
|
109
|
+
'-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', encoded,
|
|
110
|
+
], {
|
|
111
|
+
shell: false,
|
|
112
|
+
windowsHide: true,
|
|
113
|
+
encoding: 'utf8',
|
|
114
|
+
timeout: 120_000,
|
|
115
|
+
maxBuffer: 16_384,
|
|
116
|
+
env: { ...env, MODEL_ROUTER_ZCODE_INSTALLER_PATH: path },
|
|
117
|
+
})
|
|
118
|
+
const signature = JSON.parse(stdout.trim().replace(/^\uFEFF/, ''))
|
|
119
|
+
return signature.status === 'Valid'
|
|
120
|
+
&& typeof signature.subject === 'string'
|
|
121
|
+
&& signature.subject.includes(ZCODE_SIGNER)
|
|
122
|
+
} catch { return false }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function boundedResponse(url, signal) {
|
|
126
|
+
let current = new URL(url)
|
|
127
|
+
for (let redirects = 0; redirects <= MAX_REDIRECTS; redirects += 1) {
|
|
128
|
+
if (current.protocol !== 'https:') throw new Error('ZCode 下载地址必须使用 HTTPS。')
|
|
129
|
+
const response = await fetch(current, { redirect: 'manual', signal })
|
|
130
|
+
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
131
|
+
const location = response.headers.get('location')
|
|
132
|
+
await response.body?.cancel()
|
|
133
|
+
if (!location || redirects === MAX_REDIRECTS) throw new Error('ZCode 安装器重定向过多。')
|
|
134
|
+
current = new URL(location, current)
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
if (!response.ok || !response.body) {
|
|
138
|
+
throw new Error(`ZCode 官方安装器下载失败:HTTP ${response.status}。`)
|
|
139
|
+
}
|
|
140
|
+
const length = Number(response.headers.get('content-length'))
|
|
141
|
+
if (Number.isFinite(length) && length > MAX_DOWNLOAD_BYTES) {
|
|
142
|
+
await response.body.cancel()
|
|
143
|
+
throw new Error('ZCode 安装器超出下载大小限制。')
|
|
144
|
+
}
|
|
145
|
+
return response
|
|
146
|
+
}
|
|
147
|
+
throw new Error('ZCode 安装器重定向过多。')
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function fetchVerifiedInstaller(path, { env, signal, onProgress } = {}) {
|
|
151
|
+
const expected = ZCODE_WINDOWS_INSTALLER_SHA256.toUpperCase()
|
|
152
|
+
try {
|
|
153
|
+
const info = await stat(path)
|
|
154
|
+
const canonical = await realpath(path)
|
|
155
|
+
if (info.isFile() && info.size <= MAX_DOWNLOAD_BYTES
|
|
156
|
+
&& nonCAbsolute(canonical) && inside(dirname(path), canonical)
|
|
157
|
+
&& await sha256File(path) === expected
|
|
158
|
+
&& await verifyInstallerSignature(path, env)) return { path, reused: true }
|
|
159
|
+
throw new Error('已有 ZCode 安装器缓存未通过 SHA-256 或签名校验;请处理该缓存文件后重试。')
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error?.code !== 'ENOENT') throw error
|
|
162
|
+
}
|
|
163
|
+
// Authenticode's PE SIP is selected by the .exe extension on Windows.
|
|
164
|
+
const temporary = join(dirname(path), `.download-${randomUUID()}.exe`)
|
|
165
|
+
let created = false
|
|
166
|
+
try {
|
|
167
|
+
const response = await boundedResponse(ZCODE_WINDOWS_INSTALLER_URL, signal)
|
|
168
|
+
const hash = createHash('sha256')
|
|
169
|
+
let receivedBytes = 0
|
|
170
|
+
const declaredBytes = Number(response.headers.get('content-length')) || null
|
|
171
|
+
const meter = new Transform({
|
|
172
|
+
transform(chunk, encoding, callback) {
|
|
173
|
+
receivedBytes += chunk.length
|
|
174
|
+
if (receivedBytes > MAX_DOWNLOAD_BYTES) {
|
|
175
|
+
callback(new Error('ZCode 安装器超出下载大小限制。'))
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
hash.update(chunk)
|
|
179
|
+
try { onProgress?.({ receivedBytes, declaredBytes }) } catch { /* progress is advisory */ }
|
|
180
|
+
callback(null, chunk)
|
|
181
|
+
},
|
|
182
|
+
})
|
|
183
|
+
const output = createWriteStream(temporary, { flags: 'wx', mode: 0o600 })
|
|
184
|
+
output.once('open', () => { created = true })
|
|
185
|
+
await pipeline(Readable.fromWeb(response.body), meter,
|
|
186
|
+
output, { signal })
|
|
187
|
+
const actual = hash.digest('hex').toUpperCase()
|
|
188
|
+
if (actual !== expected) throw new Error('ZCode 安装器 SHA-256 与官方固定值不符。')
|
|
189
|
+
if (!(await verifyInstallerSignature(temporary, env))) {
|
|
190
|
+
throw new Error('ZCode 安装器未通过官方发布者 Authenticode 签名校验。')
|
|
191
|
+
}
|
|
192
|
+
const canonical = await realpath(temporary)
|
|
193
|
+
if (!nonCAbsolute(canonical) || !inside(dirname(path), canonical)) {
|
|
194
|
+
throw new Error('ZCode 安装器下载路径离开了非 C 盘缓存目录。')
|
|
195
|
+
}
|
|
196
|
+
await rename(temporary, path)
|
|
197
|
+
created = false
|
|
198
|
+
return { path, reused: false }
|
|
199
|
+
} finally {
|
|
200
|
+
if (created) await unlink(temporary).catch(() => {})
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function openVisibleInstaller(path) {
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
let child
|
|
207
|
+
try {
|
|
208
|
+
child = spawn(path, [], {
|
|
209
|
+
shell: false,
|
|
210
|
+
windowsHide: false,
|
|
211
|
+
detached: true,
|
|
212
|
+
stdio: 'ignore',
|
|
213
|
+
})
|
|
214
|
+
} catch (error) { reject(error); return }
|
|
215
|
+
child.once('error', reject)
|
|
216
|
+
child.once('spawn', () => {
|
|
217
|
+
child.unref()
|
|
218
|
+
resolve(child.pid ?? null)
|
|
219
|
+
})
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Download, verify, and visibly open the vendor installer. The return value
|
|
225
|
+
* intentionally does not claim that installation or sign-in has completed.
|
|
226
|
+
*/
|
|
227
|
+
export async function openZCodeInstaller({
|
|
228
|
+
downloadDir, npmPrefix, env = process.env, signal, onProgress,
|
|
229
|
+
} = {}) {
|
|
230
|
+
if (process.platform !== 'win32' || process.arch !== 'x64') {
|
|
231
|
+
throw new Error('此固定安装包仅适用于 Windows x64。')
|
|
232
|
+
}
|
|
233
|
+
const cache = await resolveZCodeDownloadDirectory({ downloadDir, npmPrefix, env })
|
|
234
|
+
const installerPath = join(cache.directory, INSTALLER_NAME)
|
|
235
|
+
const verified = await fetchVerifiedInstaller(installerPath, { env, signal, onProgress })
|
|
236
|
+
const pid = await openVisibleInstaller(verified.path)
|
|
237
|
+
return {
|
|
238
|
+
status: 'installer-opened',
|
|
239
|
+
version: ZCODE_SUPPORTED_VERSION,
|
|
240
|
+
installerPath: verified.path,
|
|
241
|
+
sha256: ZCODE_WINDOWS_INSTALLER_SHA256,
|
|
242
|
+
cacheSource: cache.source,
|
|
243
|
+
reusedDownload: verified.reused,
|
|
244
|
+
installerPid: pid,
|
|
245
|
+
destinationSelection: 'visible-vendor-installer',
|
|
246
|
+
}
|
|
247
|
+
}
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.13.0 — 2026-10-03
|
|
4
|
+
|
|
5
|
+
Prerelease for DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2, published under the npm `next` tag. Merges PR #1 and PR #2.
|
|
6
|
+
|
|
7
|
+
### Package renamed
|
|
8
|
+
- The npm package is now **`@ljwei-stak/dsh-model-router`**. `@ljwei-stak/model-router-galgame` stops at 0.12.0 and is no longer updated. Upgrade by removing the old plugin and adding the new one; do not install both.
|
|
9
|
+
- 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).
|
|
10
|
+
|
|
11
|
+
### Official CLI execution (PR #1)
|
|
12
|
+
- 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.
|
|
13
|
+
- `model_router_execute` can pin a single `provider`/`model` (direct mode) instead of comparing routes.
|
|
14
|
+
- Claude instructions stay positional; Codex reconnect notices are no longer treated as failures.
|
|
15
|
+
|
|
16
|
+
### Health check, routing visibility and cost control (PR #2)
|
|
17
|
+
- **Health check** (`model_router_health`, onboarding banner): install state, pinned version and login state per CLI. Uses read-only status commands (`claude auth status`, `codex login status`) or the presence of credential/state files (Gemini, Kimi, MiMo, Grok, MiniMax `auth-state.json`); credentials are never read. Logged-out CLIs are skipped immediately.
|
|
18
|
+
- **Routing decisions** carry the vendor's sanitized error, difficulty, estimated cost and channel per package; manual reassignment.
|
|
19
|
+
- **Cost control**: daily/monthly budgets, pre-run estimates (also for `model_router_tool_run`), actual cost from CLI-reported cost or token usage × configured price (Claude, Codex, Gemini, Grok, MiMo, MiniMax), auto-downgrade or pause when over budget.
|
|
20
|
+
- **Presets**: cheapest / balanced / best quality.
|
|
21
|
+
- **Subscription-first billing** for every vendor: CLI account logins and coding-plan key routes are used first; quota exhaustion or rate limits switch to the API key until the vendor's reset time; other subscription failures pause and ask (retry subscription / use API / cancel). Only API-billed spend counts toward budgets.
|
|
22
|
+
- **Run history and DAG**: every execute / team / tool run is recorded locally with status per step; single-step rerun (read-only teams), continuation of a failed editable team run in a fresh worktree seeded with earlier changes, and rerun of failed tool calls.
|
|
23
|
+
- **Workbench run launcher**: start a read-only run from the workbench with plan preview, cost estimate and one confirmation listing every reason.
|
|
24
|
+
- **Security boundaries**: per-route read/write scope and sandbox status; one combined approval prompt for file writes, API-key fallback, over-budget and unsandboxed CLI starts.
|
|
25
|
+
- **Quality loop**: optional stronger-model review (off / sample / always) and 👍/👎 ratings with bounded influence.
|
|
26
|
+
- Multi-process safe state (`state.json.lock`, merged writes); quota and auth marks from other processes apply without restart; corrupted state is backed up and reported.
|
|
27
|
+
- Windows: CRLF-safe client build check, Grok `GROK_HOME` login hint, ZCode unverified-version notice.
|
|
28
|
+
|
|
29
|
+
### Known limitations
|
|
30
|
+
- The workbench launcher starts read-only runs only; editable runs start from a session.
|
|
31
|
+
- Kimi and ZCode headless output reports no token usage; ZCode login state stays unknown; only ZCode 3.14.3 is verified.
|
|
32
|
+
- Not yet verified end to end inside the Harness Desktop UI.
|
|
33
|
+
|
|
34
|
+
## 0.12.0
|
|
35
|
+
|
|
36
|
+
First independent router release; GAL moved to `@ljwei-stak/dsh-galgame`.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# DeepSeek Harness Desktop · 独立插件安装与验证
|
|
2
|
+
|
|
3
|
+
模型路由从 **0.12.0** 起独立发布;GAL 是新的 **0.1.0** 插件。两者均声明支持官方 **DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2**,可分别安装和更新。
|
|
4
|
+
|
|
5
|
+
[路由 README](README.zh.md) · [迁移说明](MIGRATION.md) · [独立 GAL README](https://github.com/Alice-Marx/deepseek-harness-galgame#readme)
|
|
6
|
+
|
|
7
|
+
## 1. 选择需要的插件
|
|
8
|
+
|
|
9
|
+
| 需求 | 添加插件输入框 | 安装后的入口 |
|
|
10
|
+
| --- | --- | --- |
|
|
11
|
+
| 分析复杂度、按质量和费用选模型、调用官方工具 | `@ljwei-stak/dsh-model-router@0.13.0` | 模型路由 |
|
|
12
|
+
| 剧情、立绘、音乐、存档、自由对话 | `@ljwei-stak/dsh-galgame@0.1.0` | Gal 模块 |
|
|
13
|
+
| 两者都要 | 分别安装上面两个包 | 模型路由 + Gal 模块 |
|
|
14
|
+
|
|
15
|
+
路由保留旧 npm 包名供原用户升级,包名中的 `galgame` 是历史名称;**路由 0.12.0 不含 GAL 入口或剧情美术**。GAL 不依赖路由,路由也不依赖 GAL。
|
|
16
|
+
|
|
17
|
+
本轮采用 npm **`next`** 标签发布。推荐填写精确版本,`@next` 会跟随后续预发布更新。不要省略版本号:`latest` 或镜像状态可能选择与当前宿主不兼容的旧包。
|
|
18
|
+
|
|
19
|
+
## 2. 从 npm 安装
|
|
20
|
+
|
|
21
|
+
1. 打开 **DeepSeek Harness Desktop → 插件 → 添加插件**。
|
|
22
|
+
2. 在“包名、GitHub 仓库地址或本地目录路径”输入框填一个完整包名,例如:
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
@ljwei-stak/dsh-model-router@0.13.0
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
只想安装 GAL 时填写:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
@ljwei-stak/dsh-galgame@0.1.0
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
3. 安装源选可连接的 **HTTPS** npm 源。新版本尚未同步到镜像时,选“自定义地址”并填官方源:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
https://registry.npmjs.org/
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
4. 核对预览的包名、版本和宿主兼容信息,安装并启用。有重启提示时重启桌面程序。
|
|
41
|
+
5. 检查插件详情与侧边栏:路由应为 **0.13.0 / 模型路由**;GAL 应为 **0.1.0 / Gal 模块**。仅装路由时没有 GAL 入口是预期行为。
|
|
42
|
+
|
|
43
|
+
普通用户无需 `npm install -g`,该命令不会把插件注册到当前 Harness profile。
|
|
44
|
+
|
|
45
|
+
## 3. 从 GitHub 版本化安装包安装
|
|
46
|
+
|
|
47
|
+
进入对应 Release 下载 `.tgz`:
|
|
48
|
+
|
|
49
|
+
- [模型路由 v0.13.0](https://github.com/Alice-Marx/dsh-model-router/releases/tag/v0.13.0)
|
|
50
|
+
- [独立 GAL v0.1.0](https://github.com/Alice-Marx/deepseek-harness-galgame/releases/tag/v0.1.0)
|
|
51
|
+
|
|
52
|
+
在“添加插件”填写下载文件的**绝对路径**,例如:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
D:\Plugins\ljwei-stak-dsh-model-router-0.13.0.tgz
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
D:\Plugins\ljwei-stak-dsh-galgame-0.1.0.tgz
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
如附有 `.sha256` 文件,用 PowerShell 计算并比较摘要:
|
|
63
|
+
|
|
64
|
+
```powershell
|
|
65
|
+
Get-FileHash -Algorithm SHA256 -LiteralPath 'D:\Plugins\ljwei-stak-dsh-model-router-0.13.0.tgz'
|
|
66
|
+
Get-FileHash -Algorithm SHA256 -LiteralPath 'D:\Plugins\ljwei-stak-dsh-galgame-0.1.0.tgz'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
也可解压后填内层含 `package.json` 和 `.dsh-plugin` 的 `package` 目录。下载文件可放在 D 盘等自选位置,运行时数据目录由宿主 profile 决定。通过插件管理器更新,不修改 `app.asar` 或绕过版本检查。
|
|
70
|
+
|
|
71
|
+
## 4. 从合并版迁移:先备份,再拆分安装
|
|
72
|
+
|
|
73
|
+
1. 在旧合并插件的 GAL 播放器导出需要保留的剧情进度,并备份整个 Harness profile。每份 JSON 仅保存**当前剧情状态**,不含所有手动槽、设置、已读记录或本地音乐;重要槽位可依次读档再导出。
|
|
74
|
+
2. 在**同一个 profile**安装独立 `@ljwei-stak/dsh-galgame@0.1.0`。
|
|
75
|
+
3. 0.13.0 起包名改为 `@ljwei-stak/dsh-model-router`:在插件管理器卸载原 `@ljwei-stak/model-router-galgame`(保留 profile 与应用数据),再添加 `@ljwei-stak/dsh-model-router@0.13.0`。不要两个同时安装(条目 id 与工具名相同);条目 id 未变,路由设置与执行记录会保留。完成两项更新后再玩剧情,避免同时操作旧合并入口和新入口。
|
|
76
|
+
4. 现在路由与 GAL 各有一个入口。独立 GAL 沿用旧存档 localStorage 键,核对当前进度和三槽;更换 profile 或进度未恢复时,先切换到匹配剧目,再导入 JSON 备份。
|
|
77
|
+
5. 后续分别更新需要的插件;只用路由可以不启用或不安装 GAL。保留 profile 与应用数据,卸载插件时不要清理故事数据。
|
|
78
|
+
|
|
79
|
+
GAL 核心只包含**《回声之城·正篇》**与**《旧城迁移篇:未写完的约定》**。已分出的其他篇目保存在源代码归档,不在任一核心包里。其旧存档字节保留,但独立 GAL 核心不能运行那些旧剧目。
|
|
80
|
+
|
|
81
|
+
## 5. 模型路由第一次使用
|
|
82
|
+
|
|
83
|
+
1. 在 Harness 官方**模型**页配置供应商、模型与凭据。插件只读取准确的 `provider/model` 目录,不另存 API Key。
|
|
84
|
+
2. 打开**模型路由 → 逐模型价格与能力**,逐条填写自报质量评分、核对过的输入/输出单价(USD / 百万 token)、擅长方向;缺价留空。厂商 CLI 模型名 `cliModel` 可选,必须是该 CLI 接受的名字。
|
|
85
|
+
3. 在**任务规划**输入目标、步骤与验收标准。单任务给一项建议,团队分工把复合任务拆成有依赖的工作包。预算是本地估价目标,0 表示不设估价约束,不限制实际厂商账单。
|
|
86
|
+
4. 点**生成路由建议**,向下滚过模型档案编辑区,查看**路由建议**和各工作包。规划不启动模型。
|
|
87
|
+
5. 在**官方工具**卡片检测、安装或修复可信执行入口;ZCode 使用原厂安装窗口,用户选择安装目录后重新检测。就绪只证明本地入口通过检查。
|
|
88
|
+
6. 实际执行时进入官方会话、选工作区,明确请求 `model_router_consult`、`model_router_tool_run` 或 `model_router_team_execute`。可编辑运行需要干净 Git 仓库与宿主工具审批。
|
|
89
|
+
|
|
90
|
+
完整页面教程、参数和结果位置见[工作台使用指南](docs/WORKBENCH_USER_GUIDE.zh.md),算法推导见[README](README.zh.md#路由算法从输入到分配)。
|
|
91
|
+
|
|
92
|
+
Claude、Codex、MiMo、Grok 支持只读或可编辑;Kimi、MiniMax、ZCode 无界面模式仅在获批的隔离可编辑工作区运行。CLI 模型名可能不同于 Harness 目录;多数 CLI 不提供可核验的实际模型 ID,要看厂商记录与账单。
|
|
93
|
+
|
|
94
|
+
希望官方 CLI 尽量装在非 C 盘时,先按自己的空间规划配置 npm 全局前缀。MiniMax Windows npm 原生依赖失败时使用锁定哈希的官方安装脚本兜底,内容变化会拒绝执行;Grok 非 C 盘 npm 安装的数据目录与登录须使用一致的 `GROK_HOME`。
|
|
95
|
+
|
|
96
|
+
## 6. GAL 第一次使用
|
|
97
|
+
|
|
98
|
+
安装独立 GAL 后,从**Gal 模块**打开标题画面,选择正篇或旧城迁移篇;**Gal 设置**可调整完整立绘/半身表情、主角色与同伴大小位置、音乐与文字阅读。音乐首次播放需用户点击;剧情模式离线可玩。自由模式发送消息是真实模型调用,需宿主官方模型凭据,可能计费。
|
|
99
|
+
|
|
100
|
+
剧目章节、存档、快捷键、自动阅读、素材鉴赏与故障处理由[独立 GAL 仓库](https://github.com/Alice-Marx/deepseek-harness-galgame#readme)的教程维护。
|
|
101
|
+
|
|
102
|
+
## 7. 安装后验收
|
|
103
|
+
|
|
104
|
+
| 检查 | 预期 |
|
|
105
|
+
| --- | --- |
|
|
106
|
+
| 宿主版本 | 0.2.0-rc.1 或 0.2.0-rc.2;包括 `rc` 后缀。 |
|
|
107
|
+
| 路由独立安装 | 0.13.0 详情、模型路由入口、目录和模型档案可用;不出现旧合并 GAL 入口。 |
|
|
108
|
+
| GAL 独立安装 | 0.1.0 详情、Gal 模块入口、标题与两部核心剧情可用;不要求安装路由。 |
|
|
109
|
+
| 共同安装 | 两个入口各一份,路由工具和 GAL 播放器均能使用。 |
|
|
110
|
+
| 存档迁移 | 同 profile 可读旧键;跨 profile 用匹配剧目 JSON 导入;旧篇目的数据不覆盖。 |
|
|
111
|
+
| 本地规划 | 简单与复杂工作包分配可解释;价格未知保持未知;生成计划不调用模型。 |
|
|
112
|
+
| 官方工具 | 探测、固定来源安装、修复、取消和日志可检查;登录另验。 |
|
|
113
|
+
| 真实执行 | 在测试仓库核对审批、隔离工作区、补丁整合、厂商实际模型与计费。 |
|
|
114
|
+
|
|
115
|
+
构建、自动测试、包内容和发布校验由本次总项目报告记录。真实账号调用与用户现用 profile 的验收仍需账号持有人进行。
|
|
116
|
+
|
|
117
|
+
## 8. 开发者从完整源码构建
|
|
118
|
+
|
|
119
|
+
路由仓库检出 `v0.13.0`;GAL 仓库检出 `v0.1.0`。使用 Node.js **22.19+** 与 pnpm,各自在根目录运行:
|
|
120
|
+
|
|
121
|
+
```powershell
|
|
122
|
+
pnpm install --frozen-lockfile
|
|
123
|
+
npm run build:client
|
|
124
|
+
npm test
|
|
125
|
+
npm run check:client
|
|
126
|
+
pnpm peers check
|
|
127
|
+
npm pack --pack-destination dist
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
两项目的生成包互相独立。客户端有修改时必须重建,再将包绝对路径填入插件管理器验证。
|
|
131
|
+
|
|
132
|
+
## 历史兼容提醒
|
|
133
|
+
|
|
134
|
+
合并版 **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 标签和安装包保留,详见 [README 版本表](README.zh.md#历史版本)。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ljwei-stak
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# 迁移到独立模型路由与 GAL 插件
|
|
2
|
+
|
|
3
|
+
从模型路由 **0.12.0** 开始,原合并插件拆成两份,均面向官方 **DeepSeek Harness Desktop 0.2.0-rc.1 / 0.2.0-rc.2**。
|
|
4
|
+
|
|
5
|
+
| 插件 | 安装输入 | 所属项目 |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| 模型路由 | `@ljwei-stak/dsh-model-router@0.13.0`(0.12.0 起独立;0.13.0 起改名) | [路由仓库](https://github.com/Alice-Marx/dsh-model-router);旧包名 `@ljwei-stak/model-router-galgame` 停在 0.12.0,不再更新。 |
|
|
8
|
+
| GAL | `@ljwei-stak/dsh-galgame@0.1.0` | [新 GAL 仓库](https://github.com/Alice-Marx/deepseek-harness-galgame);独立安装与更新。 |
|
|
9
|
+
|
|
10
|
+
路由 0.12.0 只注册**模型路由**侧边栏入口。剧情引擎、角色美术、音乐、存档与自由模式全部归独立 GAL;两个插件互不依赖。
|
|
11
|
+
|
|
12
|
+
## 0.13.0 包名变更
|
|
13
|
+
|
|
14
|
+
0.13.0 起 npm 包名由 `@ljwei-stak/model-router-galgame` 改为 **`@ljwei-stak/dsh-model-router`**。旧包名停在 0.12.0(`next`)与 0.4.32(`latest`),不再更新,也不会被弃用或删除。
|
|
15
|
+
|
|
16
|
+
- 插件管理器把两者视为不同的包,不能原地“更新”:先卸载旧包(保留 profile 与应用数据),再添加新包。
|
|
17
|
+
- 不要同时安装:二者使用同一个 profile 条目 id `model-router-galgame` 和相同的 `model_router_*` 工具名。
|
|
18
|
+
- 保留的标识:profile 条目 id、侧边栏面板 id、设置命名空间与 Cordis 插件名仍为 `model-router-galgame`,执行记录仍在 `~/.dsh/model-router/state.json`,因此已保存的路由设置和执行记录继续生效。
|
|
19
|
+
- 改变的标识:npm 包名、`cordis.patch.yml` 中被加载的模块名、插件管理器设置卡识别的包名、宿主 Remote(typert)包键和更新检查查询的包名。
|
|
20
|
+
|
|
21
|
+
## 从合并版 0.11.x 迁移
|
|
22
|
+
|
|
23
|
+
1. **升级前备份**:旧 GAL 播放器导出需要保留的进度 JSON,并备份 Harness profile。JSON 保存当前剧情状态,不包含全部槽位、设置、已读记录或音乐;重要手动槽可依次读取后导出。
|
|
24
|
+
2. 在**同一个 Harness profile**安装 `@ljwei-stak/dsh-galgame@0.1.0`。
|
|
25
|
+
3. 在官方插件管理器卸载原包 `@ljwei-stak/model-router-galgame`,再添加 `@ljwei-stak/dsh-model-router@0.13.0`。完成两项安装后再打开剧情,避免并行使用旧合并 GAL 与新 GAL 入口。
|
|
26
|
+
4. 核对只有一个**模型路由**和一个**Gal 模块**入口。打开独立 GAL 检查当前进度、三槽及设置;同 profile 继续使用原 localStorage 键。
|
|
27
|
+
5. 若使用不同 profile 或没有读到进度,在 GAL 选择匹配剧目,再导入保存的 JSON。导入上限 **2 MB**,确认后替换当前剧情进度。
|
|
28
|
+
|
|
29
|
+
独立 GAL 保留 `model-router:gal-story:v1` 等既有存档命名空间,不因包名改变而主动清理旧数据。旧城迁移篇保留历史基础键,正篇使用其独立剧目键;其他已分出的故事存档字节不清理,但不能在仅有两部核心剧目的新播放器里运行。旧版所有故事的代码与专属素材保存在源代码归档。
|
|
30
|
+
|
|
31
|
+
两部核心剧目是**《回声之城·正篇》**和**《旧城迁移篇:未写完的约定》**。需要继续扩展正篇时只更新 GAL,路由功能调整只更新路由。只用路由无需安装 GAL。
|
|
32
|
+
|
|
33
|
+
## 从旧桌面适配迁移到官方 Harness
|
|
34
|
+
|
|
35
|
+
历史 **0.8.0** 声明 0.1.7-rc.2 依赖,当前官方桌面会拒绝它。历史 **0.10.1** 只接受 0.2.0-rc.1;rc.2 用户应安装上表精确版本,不修改 `app.asar` 或绕过版本检查。
|
|
36
|
+
|
|
37
|
+
1. 备份原 profile 与需要的故事状态。在官方插件管理器按升级流程替换重复旧实例,不清理应用数据。
|
|
38
|
+
2. 在 Harness 官方**模型**页核对供应商、模型与凭据。旧插件凭据不由本插件迁移或保存。
|
|
39
|
+
3. 打开**模型路由**核对模型目录、档案、建议、官方工具卡和设置。需要 GAL 时再单独安装其插件。
|
|
40
|
+
4. 真实模型意见使用 `model_router_consult`;单 CLI 任务使用 `model_router_tool_run`;依赖工作包使用 `model_router_team_execute`。可编辑执行需要干净 Git 仓库与官方工具审批。
|
|
41
|
+
|
|
42
|
+
## 功能归属与差异
|
|
43
|
+
|
|
44
|
+
| 功能 | 独立插件的行为 |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| 模型配置 | 在宿主官方“模型”页完成,两个插件都不保存 API Key。 |
|
|
47
|
+
| 路由规划 | 路由插件从官方目录与用户档案估算难度、质量、费用和工作包。 |
|
|
48
|
+
| 官方工具 | 路由插件维护七家固定来源安装与执行入口;ZCode 原厂安装器由用户选目录。 |
|
|
49
|
+
| 执行团队 | 路由插件的 CLI 团队执行器按依赖顺序运行;官方 Harness Agent Teams 的成员、消息与生命周期由宿主管理。 |
|
|
50
|
+
| 剧情与自由对话 | 独立 GAL 管理故事、立绘、音乐、设置和存档;自由模式经宿主模型服务调用,可能计费。 |
|
|
51
|
+
| 更新 | 通过官方插件管理器分别安装各自的新版本;发布使用 npm `next`,推荐精确版本。 |
|
|
52
|
+
|
|
53
|
+
登录、厂商实际模型和账单仍由账号持有人验收。完整步骤与安装后的检查见 [INSTALLATION_GUIDE.zh.md](INSTALLATION_GUIDE.zh.md),GAL 播放器教程见[独立仓库](https://github.com/Alice-Marx/deepseek-harness-galgame#readme)。
|
package/README.i18n.yaml
ADDED