@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.
@@ -46,6 +46,53 @@ module.exports = __toCommonJS(official_harness_exports);
46
46
  var import_react6 = __toESM(require("react"), 1);
47
47
 
48
48
  // .dsh-plugin/shared/official-tool-registry.mjs
49
+ var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/";
50
+ function packageManagerMethod(id2, label, manager, spec, { registryFlag = "--registry=" } = {}) {
51
+ return Object.freeze({
52
+ id: id2,
53
+ label,
54
+ kind: "package-manager",
55
+ manager,
56
+ spec,
57
+ registryFlag,
58
+ installArgs: Object.freeze(["install", "-g", spec]),
59
+ uninstallArgs: Object.freeze(["uninstall", "-g", spec.replace(/@latest$/, "")]),
60
+ /** npm/pnpm both accept --prefix to place a global install off the default. */
61
+ supportsInstallDir: true,
62
+ supportsRegistry: true
63
+ });
64
+ }
65
+ function scriptMethod(id2, label, {
66
+ shell,
67
+ scriptUrl,
68
+ verify,
69
+ installArg = [],
70
+ env = [],
71
+ platforms,
72
+ installDirEnv = "",
73
+ defaultInstallDir = ""
74
+ }) {
75
+ return Object.freeze({
76
+ id: id2,
77
+ label,
78
+ kind: "script",
79
+ shell,
80
+ scriptUrl,
81
+ verify,
82
+ /** Passed after the staged script path; empty means the script takes no argument. */
83
+ installArgs: Object.freeze(installArg),
84
+ /** Environment the vendor script itself documents for this install. */
85
+ env: Object.freeze(env),
86
+ /** When set, the configured directory is this environment variable, not an argv flag. */
87
+ installDirEnv,
88
+ /** Home-relative directory the vendor uses when the user sets none. Slashes only. */
89
+ defaultInstallDir,
90
+ platforms: Object.freeze(platforms),
91
+ /** Whether the vendor script honours a caller-chosen directory at all. */
92
+ supportsInstallDir: Boolean(installDirEnv) || installArg.length > 0,
93
+ supportsRegistry: false
94
+ });
95
+ }
49
96
  var OFFICIAL_TOOLS = Object.freeze([
50
97
  Object.freeze({
51
98
  id: "kimi-code",
@@ -55,9 +102,41 @@ var OFFICIAL_TOOLS = Object.freeze([
55
102
  package: "@moonshot-ai/kimi-code",
56
103
  manager: "npm",
57
104
  installArgs: ["install", "-g", "@moonshot-ai/kimi-code@latest", "--registry=https://registry.npmjs.org/"],
105
+ uninstallArgs: ["uninstall", "-g", "@moonshot-ai/kimi-code"],
58
106
  probeExecutables: ["kimi"],
59
107
  probeNote: "kimi \u4E0E\u65E7 Python \u7248 kimi-cli \u540C\u540D\uFF1B\u8BF7\u6838\u5BF9\u53EF\u6267\u884C\u6587\u4EF6\u6765\u6E90\u548C\u7248\u672C\u3002",
60
- providerHints: ["moonshot", "kimi"]
108
+ providerHints: ["moonshot", "kimi"],
109
+ scriptBinaryNames: Object.freeze(["kimi", "kimi.exe"]),
110
+ scriptBinaryRelatives: Object.freeze(["bin/kimi", "bin/kimi.exe"]),
111
+ defaultScriptInstallDir: ".kimi-code",
112
+ installMethods: Object.freeze([
113
+ packageManagerMethod("npm", "npm", "npm", "@moonshot-ai/kimi-code@latest"),
114
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@moonshot-ai/kimi-code@latest"),
115
+ scriptMethod("script-bash", "curl | bash", {
116
+ shell: "bash",
117
+ scriptUrl: "https://code.kimi.com/kimi-code/install.sh",
118
+ platforms: ["linux", "darwin"],
119
+ installDirEnv: "KIMI_INSTALL_DIR",
120
+ defaultInstallDir: ".kimi-code",
121
+ env: ["KIMI_INSTALL_DIR"],
122
+ verify: Object.freeze({
123
+ mustInclude: ["code.kimi.com/kimi-code", "KIMI_INSTALL_DIR"],
124
+ mustNotInclude: ["downloads.claude.ai/claude-code-releases", "@minimax-ai/code"]
125
+ })
126
+ }),
127
+ scriptMethod("script-powershell", "irm | iex", {
128
+ shell: "powershell",
129
+ scriptUrl: "https://code.kimi.com/kimi-code/install.ps1",
130
+ platforms: ["win32"],
131
+ installDirEnv: "KIMI_INSTALL_DIR",
132
+ defaultInstallDir: ".kimi-code",
133
+ env: ["KIMI_INSTALL_DIR"],
134
+ verify: Object.freeze({
135
+ mustInclude: ["code.kimi.com/kimi-code", "KIMI_INSTALL_DIR"],
136
+ mustNotInclude: ["downloads.claude.ai/claude-code-releases", "@minimax-ai/code"]
137
+ })
138
+ })
139
+ ])
61
140
  }),
62
141
  Object.freeze({
63
142
  id: "claude-code",
@@ -67,8 +146,36 @@ var OFFICIAL_TOOLS = Object.freeze([
67
146
  package: "@anthropic-ai/claude-code",
68
147
  manager: "npm",
69
148
  installArgs: ["install", "-g", "@anthropic-ai/claude-code@latest", "--registry=https://registry.npmjs.org/"],
149
+ uninstallArgs: ["uninstall", "-g", "@anthropic-ai/claude-code"],
70
150
  probeExecutables: ["claude"],
71
- providerHints: ["anthropic", "claude"]
151
+ providerHints: ["anthropic", "claude"],
152
+ // The native installer always lands in ~/.local/bin; it has no directory flag.
153
+ scriptBinaryNames: Object.freeze(["claude", "claude.exe"]),
154
+ defaultScriptInstallDir: ".local/bin",
155
+ installMethods: Object.freeze([
156
+ packageManagerMethod("npm", "npm", "npm", "@anthropic-ai/claude-code@latest"),
157
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@anthropic-ai/claude-code@latest"),
158
+ scriptMethod("script-bash", "curl | bash", {
159
+ shell: "bash",
160
+ scriptUrl: "https://claude.ai/install.sh",
161
+ platforms: ["linux", "darwin"],
162
+ defaultInstallDir: ".local/bin",
163
+ verify: Object.freeze({
164
+ mustInclude: ["downloads.claude.ai/claude-code-releases", "claude.ai/install.sh"],
165
+ mustNotInclude: ["@minimax-ai/code", "releases.openai.com/codex"]
166
+ })
167
+ }),
168
+ scriptMethod("script-powershell", "irm | iex", {
169
+ shell: "powershell",
170
+ scriptUrl: "https://claude.ai/install.ps1",
171
+ platforms: ["win32"],
172
+ defaultInstallDir: ".local/bin",
173
+ verify: Object.freeze({
174
+ mustInclude: ["downloads.claude.ai/claude-code-releases"],
175
+ mustNotInclude: ["@minimax-ai/code", "releases.openai.com/codex"]
176
+ })
177
+ })
178
+ ])
72
179
  }),
73
180
  Object.freeze({
74
181
  id: "codex",
@@ -76,11 +183,42 @@ var OFFICIAL_TOOLS = Object.freeze([
76
183
  vendor: "OpenAI",
77
184
  purpose: "OpenAI \u5B98\u65B9\u7F16\u7A0B CLI\uFF0C\u63D0\u4F9B codex \u547D\u4EE4\u3002",
78
185
  package: "@openai/codex",
79
- installArgs: ["install", "-g", "@openai/codex@latest", "--registry=https://registry.npmjs.org/"],
80
186
  manager: "npm",
187
+ installArgs: ["install", "-g", "@openai/codex@latest", "--registry=https://registry.npmjs.org/"],
188
+ uninstallArgs: ["uninstall", "-g", "@openai/codex"],
81
189
  probeExecutables: ["codex"],
82
190
  probeNote: "Codex \u7248\u672C\u6A2A\u5E45\u7531\u9002\u914D\u5C42\u5BBD\u5339\u914D\uFF1B\u5B89\u88C5\u65F6\u53D6 npm \u6700\u65B0\u7248\u3002",
83
- providerHints: ["openai", "gpt", "codex"]
191
+ providerHints: ["openai", "gpt", "codex"],
192
+ scriptBinaryNames: Object.freeze(["codex", "codex.exe"]),
193
+ installMethods: Object.freeze([
194
+ packageManagerMethod("npm", "npm", "npm", "@openai/codex@latest"),
195
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@openai/codex@latest"),
196
+ scriptMethod("script-bash", "curl | bash", {
197
+ shell: "bash",
198
+ scriptUrl: "https://chatgpt.com/codex/install.sh",
199
+ platforms: ["linux", "darwin"],
200
+ installDirEnv: "CODEX_INSTALL_DIR",
201
+ defaultInstallDir: ".local/bin",
202
+ env: ["CODEX_INSTALL_DIR"],
203
+ verify: Object.freeze({
204
+ mustInclude: ["releases.openai.com/codex", "CODEX_INSTALL_DIR"],
205
+ mustNotInclude: ["downloads.claude.ai/claude-code-releases", "@minimax-ai/code"]
206
+ })
207
+ }),
208
+ scriptMethod("script-powershell", "irm | iex", {
209
+ shell: "powershell",
210
+ scriptUrl: "https://chatgpt.com/codex/install.ps1",
211
+ platforms: ["win32"],
212
+ installDirEnv: "CODEX_INSTALL_DIR",
213
+ // %LOCALAPPDATA%\Programs\OpenAI\Codex\bin under a normal user profile.
214
+ defaultInstallDir: "AppData/Local/Programs/OpenAI/Codex/bin",
215
+ env: ["CODEX_INSTALL_DIR"],
216
+ verify: Object.freeze({
217
+ mustInclude: ["releases.openai.com/codex", "CODEX_INSTALL_DIR"],
218
+ mustNotInclude: ["downloads.claude.ai/claude-code-releases", "@minimax-ai/code"]
219
+ })
220
+ })
221
+ ])
84
222
  }),
85
223
  Object.freeze({
86
224
  id: "minimax-code",
@@ -90,8 +228,55 @@ var OFFICIAL_TOOLS = Object.freeze([
90
228
  package: "@minimax-ai/code",
91
229
  manager: "npm",
92
230
  installArgs: ["install", "-g", "@minimax-ai/code@latest", "--registry=https://registry.npmjs.org/", "--ignore-scripts=false", "--include=optional", "--allow-scripts=@minimax-ai/code,better-sqlite3"],
231
+ uninstallArgs: ["uninstall", "-g", "@minimax-ai/code"],
93
232
  probeExecutables: ["mcode"],
94
- providerHints: ["minimax"]
233
+ providerHints: ["minimax"],
234
+ scriptBinaryNames: Object.freeze([
235
+ "mcode",
236
+ "mcode.exe",
237
+ "mcode.cmd",
238
+ "mcode.ps1",
239
+ "mcode-tools",
240
+ "mcode-tools.cmd",
241
+ "mcode-tools.ps1"
242
+ ]),
243
+ scriptBinaryRelatives: Object.freeze([
244
+ "mcode.cmd",
245
+ "mcode.ps1",
246
+ "mcode-tools.cmd",
247
+ "mcode-tools.ps1",
248
+ "bin/mcode",
249
+ "bin/mcode-tools"
250
+ ]),
251
+ defaultScriptInstallDir: ".minimax-code",
252
+ installMethods: Object.freeze([
253
+ packageManagerMethod("npm", "npm", "npm", "@minimax-ai/code@latest"),
254
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@minimax-ai/code@latest"),
255
+ scriptMethod("script-bash", "curl | bash", {
256
+ shell: "bash",
257
+ scriptUrl: "https://filecdn.minimax.chat/public/install.sh",
258
+ platforms: ["linux", "darwin"],
259
+ installDirEnv: "MCODE_INSTALL_DIR",
260
+ defaultInstallDir: ".minimax-code",
261
+ env: ["MCODE_INSTALL_DIR", "MCODE_NPM_REGISTRY"],
262
+ verify: Object.freeze({
263
+ mustInclude: ["@minimax-ai/code", "MCODE_INSTALL_DIR", "MiniMax Code"],
264
+ mustNotInclude: ["downloads.claude.ai/claude-code-releases", "releases.openai.com/codex"]
265
+ })
266
+ }),
267
+ scriptMethod("script-powershell", "irm | iex", {
268
+ shell: "powershell",
269
+ scriptUrl: "https://filecdn.minimax.chat/public/install.ps1",
270
+ platforms: ["win32"],
271
+ installDirEnv: "MCODE_INSTALL_DIR",
272
+ defaultInstallDir: ".minimax-code",
273
+ env: ["MCODE_INSTALL_DIR", "MCODE_NPM_REGISTRY"],
274
+ verify: Object.freeze({
275
+ mustInclude: ["@minimax-ai/code", "MCODE_INSTALL_DIR", "MiniMax Code"],
276
+ mustNotInclude: ["downloads.claude.ai/claude-code-releases", "releases.openai.com/codex"]
277
+ })
278
+ })
279
+ ])
95
280
  }),
96
281
  Object.freeze({
97
282
  id: "mimo-code",
@@ -101,8 +286,38 @@ var OFFICIAL_TOOLS = Object.freeze([
101
286
  package: "@mimo-ai/cli",
102
287
  manager: "npm",
103
288
  installArgs: ["install", "-g", "@mimo-ai/cli@latest", "--registry=https://registry.npmjs.org/"],
289
+ uninstallArgs: ["uninstall", "-g", "@mimo-ai/cli"],
104
290
  probeExecutables: ["mimo"],
105
- providerHints: ["mimo", "xiaomi"]
291
+ providerHints: ["mimo", "xiaomi"],
292
+ scriptBinaryNames: Object.freeze(["mimo", "mimo.exe"]),
293
+ defaultScriptInstallDir: ".mimocode/bin",
294
+ installMethods: Object.freeze([
295
+ packageManagerMethod("npm", "npm", "npm", "@mimo-ai/cli@latest"),
296
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@mimo-ai/cli@latest"),
297
+ // The Unix script hardcodes ~/.mimocode/bin and ignores MIMOCODE_INSTALL_DIR.
298
+ scriptMethod("script-bash", "curl | bash", {
299
+ shell: "bash",
300
+ scriptUrl: "https://mimo.xiaomi.com/install",
301
+ platforms: ["linux", "darwin"],
302
+ defaultInstallDir: ".mimocode/bin",
303
+ verify: Object.freeze({
304
+ mustInclude: ["MiMoCode", "mimo.xiaomi.com"],
305
+ mustNotInclude: ["@minimax-ai/code", "downloads.claude.ai/claude-code-releases"]
306
+ })
307
+ }),
308
+ scriptMethod("script-powershell", "irm | iex", {
309
+ shell: "powershell",
310
+ scriptUrl: "https://mimo.xiaomi.com/install.ps1",
311
+ platforms: ["win32"],
312
+ installDirEnv: "MIMOCODE_INSTALL_DIR",
313
+ defaultInstallDir: ".mimocode/bin",
314
+ env: ["MIMOCODE_INSTALL_DIR"],
315
+ verify: Object.freeze({
316
+ mustInclude: ["MiMoCode", "mimo.xiaomi.com", "MIMOCODE_INSTALL_DIR"],
317
+ mustNotInclude: ["@minimax-ai/code", "downloads.claude.ai/claude-code-releases"]
318
+ })
319
+ })
320
+ ])
106
321
  }),
107
322
  Object.freeze({
108
323
  id: "grok-build",
@@ -112,8 +327,39 @@ var OFFICIAL_TOOLS = Object.freeze([
112
327
  package: "@xai-official/grok",
113
328
  manager: "npm",
114
329
  installArgs: ["install", "-g", "@xai-official/grok@latest", "--registry=https://registry.npmjs.org/"],
330
+ uninstallArgs: ["uninstall", "-g", "@xai-official/grok"],
115
331
  probeExecutables: ["grok"],
116
- providerHints: ["xai", "grok"]
332
+ providerHints: ["xai", "grok"],
333
+ scriptBinaryNames: Object.freeze(["grok", "grok.exe", "agent", "agent.exe"]),
334
+ defaultScriptInstallDir: ".grok/bin",
335
+ installMethods: Object.freeze([
336
+ packageManagerMethod("npm", "npm", "npm", "@xai-official/grok@latest"),
337
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@xai-official/grok@latest"),
338
+ scriptMethod("script-bash", "curl | bash", {
339
+ shell: "bash",
340
+ scriptUrl: "https://x.ai/cli/install.sh",
341
+ platforms: ["linux", "darwin"],
342
+ installDirEnv: "GROK_BIN_DIR",
343
+ defaultInstallDir: ".grok/bin",
344
+ env: ["GROK_BIN_DIR"],
345
+ verify: Object.freeze({
346
+ mustInclude: ["x.ai/cli", "GROK_BIN_DIR"],
347
+ mustNotInclude: ["@minimax-ai/code", "downloads.claude.ai/claude-code-releases"]
348
+ })
349
+ }),
350
+ scriptMethod("script-powershell", "irm | iex", {
351
+ shell: "powershell",
352
+ scriptUrl: "https://x.ai/cli/install.ps1",
353
+ platforms: ["win32"],
354
+ installDirEnv: "GROK_BIN_DIR",
355
+ defaultInstallDir: ".grok/bin",
356
+ env: ["GROK_BIN_DIR"],
357
+ verify: Object.freeze({
358
+ mustInclude: ["x.ai/cli", "GROK_BIN_DIR"],
359
+ mustNotInclude: ["@minimax-ai/code", "downloads.claude.ai/claude-code-releases"]
360
+ })
361
+ })
362
+ ])
117
363
  }),
118
364
  Object.freeze({
119
365
  id: "gemini",
@@ -123,11 +369,91 @@ var OFFICIAL_TOOLS = Object.freeze([
123
369
  package: "@google/gemini-cli",
124
370
  manager: "npm",
125
371
  installArgs: ["install", "-g", "@google/gemini-cli@latest", "--registry=https://registry.npmjs.org/"],
372
+ uninstallArgs: ["uninstall", "-g", "@google/gemini-cli"],
126
373
  probeExecutables: ["gemini"],
127
374
  providerHints: ["gemini", "google"],
128
375
  // Headless runs go through the task adapter. The signed sandbox runner does
129
376
  // not launch this CLI; a missing or failed process falls back to the API.
130
- headlessAdapter: true
377
+ headlessAdapter: true,
378
+ installMethods: Object.freeze([
379
+ packageManagerMethod("npm", "npm", "npm", "@google/gemini-cli@latest"),
380
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@google/gemini-cli@latest")
381
+ ])
382
+ }),
383
+ Object.freeze({
384
+ id: "opencode",
385
+ label: "OpenCode",
386
+ vendor: "anomalyco / SST",
387
+ purpose: "\u5F00\u6E90\u7EC8\u7AEF\u7F16\u7A0B\u4EE3\u7406\uFF0C\u63D0\u4F9B opencode \u547D\u4EE4\uFF1B\u53EF\u7528 npm/pnpm \u6216\u5B98\u65B9\u5B89\u88C5\u811A\u672C\u83B7\u53D6\u3002",
388
+ package: "opencode-ai",
389
+ manager: "npm",
390
+ installArgs: ["install", "-g", "opencode-ai@latest", "--registry=https://registry.npmjs.org/"],
391
+ uninstallArgs: ["uninstall", "-g", "opencode-ai"],
392
+ probeExecutables: ["opencode"],
393
+ probeNote: "opencode \u547D\u4EE4\u7531 npm \u5168\u5C40\u5305\u6216\u5B98\u65B9\u811A\u672C\u5B89\u88C5\u7684\u4E8C\u8FDB\u5236\u63D0\u4F9B\uFF1B\u811A\u672C\u5B89\u88C5\u56FA\u5B9A\u843D\u5728 ~/.opencode/bin\u3002",
394
+ providerHints: ["opencode", "anomalyco"],
395
+ /** File an uninstall may delete, when the copy came from a script install. */
396
+ scriptBinaryNames: Object.freeze(["opencode", "opencode.exe"]),
397
+ /** Where the vendor script puts the binary when the user sets no directory. */
398
+ defaultScriptInstallDir: ".opencode/bin",
399
+ installMethods: Object.freeze([
400
+ packageManagerMethod("npm", "npm", "npm", "opencode-ai@latest"),
401
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "opencode-ai@latest"),
402
+ // The published script hardcodes $HOME/.opencode/bin, so it cannot honour
403
+ // a custom install directory; the npm/pnpm methods above can.
404
+ scriptMethod("script-bash", "curl | bash", {
405
+ shell: "bash",
406
+ scriptUrl: "https://opencode.ai/install",
407
+ platforms: ["linux", "darwin"],
408
+ defaultInstallDir: ".opencode/bin",
409
+ verify: Object.freeze({
410
+ mustInclude: ["anomalyco/opencode", "opencode.ai"],
411
+ mustNotInclude: ["static-openapi.stepfun.com"]
412
+ })
413
+ })
414
+ ])
415
+ }),
416
+ Object.freeze({
417
+ id: "stepcode",
418
+ label: "Step Code",
419
+ vendor: "StepFun \u9636\u8DC3\u661F\u8FB0",
420
+ purpose: "\u9636\u8DC3\u661F\u8FB0\u5F00\u6E90\u7EC8\u7AEF\u7F16\u7A0B\u4EE3\u7406\uFF0C\u63D0\u4F9B step \u547D\u4EE4\uFF1B\u5B98\u65B9\u53EA\u53D1\u5E03\u5B89\u88C5\u811A\u672C\uFF0C\u6CA1\u6709 npm \u5305\u3002",
421
+ manager: "script-installer",
422
+ installArgs: [],
423
+ uninstallArgs: [],
424
+ probeExecutables: ["step"],
425
+ probeNote: "step \u547D\u4EE4\u6765\u81EA\u9636\u8DC3\u5B98\u65B9\u5B89\u88C5\u811A\u672C\uFF0C\u6821\u9A8C\u548C\u5199\u5165 ~/.stepcode\uFF1B\u914D\u7F6E\u4E0E\u51ED\u636E\u540C\u5728\u8BE5\u76EE\u5F55\uFF0C\u5378\u8F7D\u53EA\u5220\u9664\u53EF\u6267\u884C\u6587\u4EF6\u3002",
426
+ providerHints: ["stepfun", "stepcode"],
427
+ scriptBinaryNames: Object.freeze(["step", "step.exe"]),
428
+ defaultScriptInstallDir: ".stepcode/bin",
429
+ /** The vendor installer writes this marked block into the shell profile. */
430
+ pathProfileMarkers: Object.freeze(["# stepcode"]),
431
+ installMethods: Object.freeze([
432
+ scriptMethod("script-bash", "curl | bash", {
433
+ shell: "bash",
434
+ scriptUrl: "https://static-openapi.stepfun.com/stepcode/install.sh",
435
+ platforms: ["linux", "darwin"],
436
+ installArg: ["--install-dir"],
437
+ env: ["STEP_INSTALL_DIR", "STEP_RELEASE_BASE_URL"],
438
+ defaultInstallDir: ".stepcode/bin",
439
+ verify: Object.freeze({
440
+ mustInclude: ["static-openapi.stepfun.com", "stepcode installer"],
441
+ mustNotInclude: ["anomalyco/opencode"]
442
+ })
443
+ }),
444
+ scriptMethod("script-powershell", "irm | iex", {
445
+ shell: "powershell",
446
+ scriptUrl: "https://static-openapi.stepfun.com/stepcode/install.ps1",
447
+ platforms: ["win32"],
448
+ installArg: ["-InstallDir"],
449
+ env: ["STEP_INSTALL_DIR", "STEP_RELEASE_BASE_URL"],
450
+ defaultInstallDir: ".stepcode/bin",
451
+ verify: Object.freeze({
452
+ mustInclude: ["static-openapi.stepfun.com", "stepcode"],
453
+ mustNotInclude: ["anomalyco/opencode"]
454
+ })
455
+ })
456
+ ])
131
457
  }),
132
458
  Object.freeze({
133
459
  id: "zcode",
@@ -136,6 +462,7 @@ var OFFICIAL_TOOLS = Object.freeze([
136
462
  purpose: "\u667A\u8C31\u5B98\u65B9 ZCode \u684C\u9762\u7248\uFF0C\u5185\u542B GLM \u7F16\u7A0B\u4EE3\u7406\u3002Windows \u5B89\u88C5\u5668\u53EF\u9009\u62E9 D \u76D8\u76EE\u5F55\u3002",
137
463
  manager: "signed-windows-installer",
138
464
  installArgs: [],
465
+ uninstallArgs: [],
139
466
  probeExecutables: [],
140
467
  probeNote: "\u68C0\u6D4B\u7ECF\u8FC7\u6709\u6548\u7B7E\u540D\u7684 ZCode.exe \u548C\u540C\u76EE\u5F55 GLM \u8D44\u6E90\uFF1B\u684C\u9762\u5B89\u88C5\u5668\u9700\u4EBA\u5DE5\u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E\u3002",
141
468
  providerHints: ["zai", "z.ai", "zcode", "glm", "zhipu", "bigmodel"]
@@ -153,10 +480,30 @@ function toolForProvider(provider) {
153
480
  }
154
481
  return null;
155
482
  }
483
+ function installMethodsFor(tool) {
484
+ return Array.isArray(tool?.installMethods) ? tool.installMethods : [];
485
+ }
486
+ function installMethodFor(tool, methodId) {
487
+ const wanted = String(methodId ?? "").trim();
488
+ if (!wanted) return null;
489
+ return installMethodsFor(tool).find((method) => method.id === wanted) ?? null;
490
+ }
491
+ function defaultInstallMethod(tool) {
492
+ return installMethodsFor(tool)[0] ?? null;
493
+ }
156
494
  function installCommandLine(tool) {
157
495
  if (!tool || tool.unsupported) return null;
158
496
  if (tool.manager === "signed-windows-installer") return "\u6253\u5F00\u5B98\u65B9\u7B7E\u540D\u5B89\u88C5\u5668\uFF08\u9009\u62E9\u5B89\u88C5\u76EE\u5F55\uFF09";
159
- return tool.manager === "npm" ? `npm ${tool.installArgs.join(" ")}` : `${tool.manager} ${tool.installArgs.join(" ")}`;
497
+ return installCommandLineFor(tool, defaultInstallMethod(tool));
498
+ }
499
+ function installCommandLineFor(tool, method) {
500
+ if (!tool || tool.unsupported) return null;
501
+ if (!method) return null;
502
+ if (method.kind === "script") {
503
+ const piped = method.shell === "powershell" ? `irm ${method.scriptUrl} | iex` : `curl -fsSL ${method.scriptUrl} | bash`;
504
+ return method.installArgs.length ? `${piped} ${method.installArgs[0]} <\u5B89\u88C5\u76EE\u5F55>` : piped;
505
+ }
506
+ return `${method.manager} ${method.installArgs.join(" ")}`;
160
507
  }
161
508
 
162
509
  // .dsh-plugin/shared/model-profiles.mjs
@@ -411,6 +758,112 @@ function toolBoundary(toolId, { sandboxed = false, platform = "unknown" } = {})
411
758
  };
412
759
  }
413
760
 
761
+ // .dsh-plugin/shared/tool-install-preferences.mjs
762
+ var MAX_INSTALL_DIR_CHARS = 4096;
763
+ var MAX_SOURCE_URL_CHARS = 2048;
764
+ var MAX_OVERRIDES = 64;
765
+ function isAbsolutePath(value) {
766
+ const raw = String(value ?? "");
767
+ if (!raw) return false;
768
+ if (raw.startsWith("/")) return true;
769
+ if (/^[A-Za-z]:[\\/]/.test(raw)) return true;
770
+ return /^\\\\[^\\]+\\[^\\]+/.test(raw);
771
+ }
772
+ function joinPath(base, ...segments) {
773
+ const separator = String(base ?? "").includes("\\") && !String(base ?? "").includes("/") ? "\\" : "/";
774
+ const parts = [String(base ?? "").replace(/[\\/]+$/, ""), ...segments.map((item) => String(item ?? "").replace(/^[\\/]+|[\\/]+$/g, ""))];
775
+ return parts.filter(Boolean).join(separator) || separator;
776
+ }
777
+ function expandInstallDir(value, { home = "", env = {} } = {}) {
778
+ const raw = String(value ?? "").trim();
779
+ if (!raw) return "";
780
+ if (raw.includes("\0")) throw new Error("\u5B89\u88C5\u76EE\u5F55\u5305\u542B\u65E0\u6548\u5B57\u7B26\u3002");
781
+ let expanded = raw;
782
+ const userHome = String(home ?? "");
783
+ if (expanded === "~") expanded = userHome;
784
+ else if (expanded.startsWith("~/") || expanded.startsWith("~\\")) expanded = joinPath(userHome, expanded.slice(2));
785
+ expanded = expanded.replace(/%([A-Za-z_][A-Za-z0-9_]*)%/g, (match, name) => env[name] ?? match).replace(/\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, name) => env[name] ?? match);
786
+ if (expanded.length > MAX_INSTALL_DIR_CHARS) throw new Error("\u5B89\u88C5\u76EE\u5F55\u8FC7\u957F\u3002");
787
+ if (!isAbsolutePath(expanded)) throw new Error("\u5B89\u88C5\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u3002");
788
+ const withoutTrailing = expanded.replace(/[\\/]+$/, "");
789
+ if (!withoutTrailing) throw new Error("\u5B89\u88C5\u76EE\u5F55\u4E0D\u80FD\u662F\u6587\u4EF6\u7CFB\u7EDF\u6839\u76EE\u5F55\u3002");
790
+ if (/^[A-Za-z]:$/.test(withoutTrailing)) throw new Error("\u5B89\u88C5\u76EE\u5F55\u4E0D\u80FD\u662F\u6587\u4EF6\u7CFB\u7EDF\u6839\u76EE\u5F55\u3002");
791
+ if (/^\\\\[^\\]+\\[^\\]+$/.test(withoutTrailing)) throw new Error("\u5B89\u88C5\u76EE\u5F55\u4E0D\u80FD\u662F\u7F51\u7EDC\u5171\u4EAB\u6839\u76EE\u5F55\u3002");
792
+ return expanded;
793
+ }
794
+ function normalizeSourceUrl(value, subject) {
795
+ const raw = String(value ?? "").trim();
796
+ if (!raw) return "";
797
+ if (raw.length > MAX_SOURCE_URL_CHARS) throw new Error(`${subject}\u5730\u5740\u8FC7\u957F\u3002`);
798
+ let url;
799
+ try {
800
+ url = new URL(raw);
801
+ } catch {
802
+ throw new Error(`${subject}\u4E0D\u662F\u6709\u6548\u7F51\u5740\u3002`);
803
+ }
804
+ if (url.protocol !== "https:") throw new Error(`${subject}\u5FC5\u987B\u4F7F\u7528 https\u3002`);
805
+ if (url.username || url.password) throw new Error(`${subject}\u4E0D\u80FD\u5305\u542B\u8D26\u53F7\u5BC6\u7801\u3002`);
806
+ return url.toString();
807
+ }
808
+ function parseJsonObject(value, subject) {
809
+ const raw = String(value ?? "").trim();
810
+ if (!raw) return {};
811
+ let parsed;
812
+ try {
813
+ parsed = JSON.parse(raw);
814
+ } catch {
815
+ throw new Error(`${subject}\u4E0D\u662F\u6709\u6548 JSON\u3002`);
816
+ }
817
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
818
+ throw new Error(`${subject}\u5FC5\u987B\u662F JSON \u5BF9\u8C61\u3002`);
819
+ }
820
+ const entries = Object.entries(parsed);
821
+ if (entries.length > MAX_OVERRIDES) throw new Error(`${subject}\u6761\u76EE\u8FC7\u591A\u3002`);
822
+ return Object.fromEntries(entries.filter(([, item]) => typeof item === "string" && item.trim()));
823
+ }
824
+ function valueOf(config, key, fallback) {
825
+ const value = config?.[key];
826
+ return value !== void 0 && typeof value?.get === "function" ? value.get() : value ?? fallback;
827
+ }
828
+ function keepKnownToolIds(raw, known, subject) {
829
+ for (const key of Object.keys(raw)) {
830
+ if (!known.has(key)) throw new Error(`${subject}\u4E2D\u7684 ${key} \u4E0D\u662F\u5B98\u65B9\u5DE5\u5177 ID\u3002`);
831
+ }
832
+ return raw;
833
+ }
834
+ function parseInstallPreferences(config = {}, { tools = [] } = {}) {
835
+ const known = new Set(tools.map((tool) => tool.id));
836
+ const installDir = expandInstallDir(valueOf(config, "toolInstallDir", ""));
837
+ const registry = normalizeSourceUrl(valueOf(config, "toolNpmRegistry", DEFAULT_NPM_REGISTRY), "npm \u6E90") || DEFAULT_NPM_REGISTRY;
838
+ const methodRaw = keepKnownToolIds(
839
+ parseJsonObject(valueOf(config, "toolInstallMethodsJson", "{}"), "\u5B89\u88C5\u65B9\u5F0F\u8986\u76D6"),
840
+ known,
841
+ "\u5B89\u88C5\u65B9\u5F0F\u8986\u76D6"
842
+ );
843
+ const scriptRaw = keepKnownToolIds(
844
+ parseJsonObject(valueOf(config, "toolScriptUrlsJson", "{}"), "\u5B89\u88C5\u811A\u672C\u6E90\u8986\u76D6"),
845
+ known,
846
+ "\u5B89\u88C5\u811A\u672C\u6E90\u8986\u76D6"
847
+ );
848
+ const methods = {};
849
+ const scriptUrls = {};
850
+ for (const [id2, methodId] of Object.entries(methodRaw)) methods[id2] = methodId.trim();
851
+ for (const [id2, url] of Object.entries(scriptRaw)) scriptUrls[id2] = normalizeSourceUrl(url, `${id2} \u5B89\u88C5\u811A\u672C\u6E90`);
852
+ for (const [id2, methodId] of Object.entries(methods)) {
853
+ if (!installMethodFor({ id: id2, installMethods: tools.find((tool) => tool.id === id2)?.installMethods }, methodId)) {
854
+ throw new Error(`${id2} \u4E0D\u652F\u6301\u5B89\u88C5\u65B9\u5F0F ${methodId}\u3002`);
855
+ }
856
+ }
857
+ return {
858
+ installDir,
859
+ registry,
860
+ methods,
861
+ scriptUrls,
862
+ /** Script execution stays a deliberate act; a config file alone must not run one. */
863
+ allowScriptInstall: valueOf(config, "toolAllowScriptInstall", true) !== false
864
+ };
865
+ }
866
+
414
867
  // .dsh-plugin/client/router-main.jsx
415
868
  var import_react5 = __toESM(require("react"), 1);
416
869
 
@@ -621,8 +1074,8 @@ var OPENCODE_CATALOG_PROVIDERS = Object.freeze([
621
1074
  "opencode-zen",
622
1075
  "opencode-go-zen"
623
1076
  ]);
624
- function classifyTask(text6) {
625
- const value = String(text6 ?? "");
1077
+ function classifyTask(text7) {
1078
+ const value = String(text7 ?? "");
626
1079
  if (value.length < 80 && /翻译|解释|translate|explain/i.test(value)) return "general";
627
1080
  return detectTaskTypes(value)[0] ?? "general";
628
1081
  }
@@ -642,8 +1095,8 @@ var TASK_TYPE_LABELS = Object.freeze({
642
1095
  summarization: "\u6458\u8981\u4E0E\u6574\u7406",
643
1096
  writing: "\u5199\u4F5C\u4E0E\u8868\u8FBE"
644
1097
  });
645
- function detectTaskTypes(text6) {
646
- const value = String(text6 ?? "");
1098
+ function detectTaskTypes(text7) {
1099
+ const value = String(text7 ?? "");
647
1100
  const ranked = TASK_TYPE_RULES.map(([type, pattern]) => ({
648
1101
  type,
649
1102
  signals: value.match(new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`))?.length ?? 0
@@ -651,8 +1104,8 @@ function detectTaskTypes(text6) {
651
1104
  ranked.sort((left, right) => right.signals - left.signals || left.type.localeCompare(right.type));
652
1105
  return ranked.map((item) => item.type);
653
1106
  }
654
- function assessComplexity(text6) {
655
- const value = String(text6 ?? "");
1107
+ function assessComplexity(text7) {
1108
+ const value = String(text7 ?? "");
656
1109
  const lengthScore = clamp(value.length / 2200);
657
1110
  const requirementScore = clamp((value.match(/(?:^|\n)\s*(?:[-*]|\d+[.)]|[一二三四五六七八九十]+[、.])/g) ?? []).length / 8);
658
1111
  const codeScore = /(代码|工程|架构|接口|实现|部署|测试|code|api|debug)/i.test(value) ? 0.22 : 0;
@@ -732,10 +1185,10 @@ function normalizedCacheRatios(cacheReadRatio = 0, cacheWriteRatio = 0) {
732
1185
  return { read, write };
733
1186
  }
734
1187
  var COST_REFERENCE_PRICING = Object.freeze({ input: 1, output: 5, cacheRead: 1, cacheWrite: 1, currency: "USD" });
735
- function estimateCost(model, text6, outputTokens = 900, pricingOverrides = {}, cacheReadRatio = 0, cacheWriteRatio = 0) {
1188
+ function estimateCost(model, text7, outputTokens = 900, pricingOverrides = {}, cacheReadRatio = 0, cacheWriteRatio = 0) {
736
1189
  const pricing = pricingFor(model, pricingOverrides);
737
1190
  if (pricing === null) return null;
738
- const inputTokens = Math.max(80, Math.ceil(String(text6 ?? "").length / 3.7));
1191
+ const inputTokens = Math.max(80, Math.ceil(String(text7 ?? "").length / 3.7));
739
1192
  const ratios = normalizedCacheRatios(cacheReadRatio, cacheWriteRatio);
740
1193
  const cacheReadTokens = Math.min(inputTokens, Math.max(0, Math.round(inputTokens * ratios.read)));
741
1194
  const cacheWriteTokens = Math.min(inputTokens - cacheReadTokens, Math.max(0, Math.round(inputTokens * ratios.write)));
@@ -747,8 +1200,8 @@ function modelMetadata(name) {
747
1200
  if (!key) return null;
748
1201
  return MODEL_CATALOG.find((model) => model.aliases.some((alias) => key === normalize(alias))) ?? null;
749
1202
  }
750
- function taskTokenBudget(text6, task, complexity, cacheReadRatio = 0, cacheWriteRatio = 0) {
751
- const inputTokens = Math.max(80, Math.ceil(String(text6 ?? "").length / 3.7));
1203
+ function taskTokenBudget(text7, task, complexity, cacheReadRatio = 0, cacheWriteRatio = 0) {
1204
+ const inputTokens = Math.max(80, Math.ceil(String(text7 ?? "").length / 3.7));
752
1205
  const multipliers = {
753
1206
  analysis: { input: 0.9, output: 0.55 },
754
1207
  execution: { input: 1.2, output: (task.difficulty ?? complexity) === "complex" ? 1.45 : 1 },
@@ -812,10 +1265,10 @@ function looksLikeRequirement(value) {
812
1265
  const item = requirementText(value);
813
1266
  return item.length >= 3 && !/[::]$/u.test(item) && !/(?:以下|下列|如下)(?:的)?(?:任务|需求|工作|事项|要求)/u.test(item) && REQUIREMENT_ACTION.test(item.slice(0, 32));
814
1267
  }
815
- function explicitRequirements(text6) {
1268
+ function explicitRequirements(text7) {
816
1269
  const visibleLines = [];
817
1270
  let fence = null;
818
- for (const line of String(text6 ?? "").split(/\r?\n/u)) {
1271
+ for (const line of String(text7 ?? "").split(/\r?\n/u)) {
819
1272
  const marker2 = /^\s*(`{3,}|~{3,})/u.exec(line);
820
1273
  if (fence) {
821
1274
  if (marker2 && marker2[1][0] === fence.char && marker2[1].length >= fence.length && !line.slice(marker2[0].length).trim()) fence = null;
@@ -888,16 +1341,16 @@ function explicitStepReferences(objective) {
888
1341
  }
889
1342
  return [...found].sort((a, b2) => a - b2);
890
1343
  }
891
- function taskPackages(taskType, text6, band) {
1344
+ function taskPackages(taskType, text7, band) {
892
1345
  if (band !== "complex") {
893
1346
  const task = { id: "execution", name: "\u76F4\u63A5\u56DE\u7B54\u4E0E\u5FC5\u8981\u6821\u9A8C", type: taskType, purpose: "execution", difficulty: band, criticality: 0.65, dependsOn: [], preferredReasoningEffort: band === "simple" ? "low" : "medium" };
894
1347
  return [{ ...task, qualityFloor: taskQualityFloor(band, task) }];
895
1348
  }
896
- const value = String(text6 ?? "");
1349
+ const value = String(text7 ?? "");
897
1350
  const packages = [
898
1351
  { id: "analysis", name: "\u95EE\u9898\u5EFA\u6A21\u4E0E\u7EA6\u675F\u63D0\u53D6", type: "reasoning", purpose: "analysis", difficulty: "balanced", criticality: 0.8, dependsOn: [], preferredReasoningEffort: "medium" }
899
1352
  ];
900
- const requirements = explicitRequirements(text6);
1353
+ const requirements = explicitRequirements(text7);
901
1354
  if (requirements.length >= 2) {
902
1355
  const groups = requirements.length > MAX_EXPLICIT_EXECUTION_PACKAGES ? [
903
1356
  ...requirements.slice(0, MAX_EXPLICIT_EXECUTION_PACKAGES - 1).map((item) => [item]),
@@ -924,7 +1377,7 @@ function taskPackages(taskType, text6, band) {
924
1377
  });
925
1378
  });
926
1379
  } else {
927
- const domains = [...new Set([...detectTaskTypes(text6), taskType].filter((type) => type !== "general"))];
1380
+ const domains = [...new Set([...detectTaskTypes(text7), taskType].filter((type) => type !== "general"))];
928
1381
  for (const type of domains.length > 0 ? domains : [taskType]) {
929
1382
  const objective = value.split(/[,,。;;]|最后|然后|接着|并且/u).map((item) => item.trim()).filter((item) => detectTaskTypes(item).includes(type)).join("\uFF1B") || value;
930
1383
  const difficulty = requirementDifficulty(objective, type);
@@ -1025,31 +1478,31 @@ function reasoningDecision(row, task) {
1025
1478
  multiplier: reasoningEffortMultiplier(chosen)
1026
1479
  };
1027
1480
  }
1028
- function candidateUtility(row, task, weights, cacheReadRatio = 0, cacheWriteRatio = 0, text6 = "", complexity = "balanced") {
1481
+ function candidateUtility(row, task, weights, cacheReadRatio = 0, cacheWriteRatio = 0, text7 = "", complexity = "balanced") {
1029
1482
  const quality = qualityForTask(row, task.type);
1030
1483
  const rawPreference = row.preferenceAdjustments?.[task.type];
1031
1484
  const preferenceAdjustment = Number.isFinite(rawPreference) ? clamp(rawPreference, -0.1, 0.1) : 0;
1032
1485
  const floor = Number(task.qualityFloor ?? taskQualityFloor("complex", task));
1033
1486
  const qualityGap = Math.max(0, floor - quality);
1034
1487
  const reasoning = reasoningDecision(row, task);
1035
- const reference = taskCost({ pricing: COST_REFERENCE_PRICING, reasoningKnown: true, reasoningEfforts: [] }, task, text6, complexity, cacheReadRatio, cacheWriteRatio);
1036
- const cost = row.pricing === null ? 0 : 1 / (1 + taskCost(row, task, text6, complexity, cacheReadRatio, cacheWriteRatio) / Math.max(reference, 1e-12));
1488
+ const reference = taskCost({ pricing: COST_REFERENCE_PRICING, reasoningKnown: true, reasoningEfforts: [] }, task, text7, complexity, cacheReadRatio, cacheWriteRatio);
1489
+ const cost = row.pricing === null ? 0 : 1 / (1 + taskCost(row, task, text7, complexity, cacheReadRatio, cacheWriteRatio) / Math.max(reference, 1e-12));
1037
1490
  const score = weights.quality * quality + weights.cost * cost + weights.latency * (1 - clamp(row.latency * reasoning.multiplier.latency)) + weights.specialty * specialtyForTask(row, task.type) + (weights.reasoning ?? 0) * reasoning.reasoningFit - weights.risk * row.risk - qualityGap * (task.criticality ?? 0.75) + preferenceAdjustment;
1038
1491
  return { score, floor, qualityGap, preferenceAdjustment, ...reasoning };
1039
1492
  }
1040
- function taskCost(row, task, text6, complexity, cacheReadRatio = 0, cacheWriteRatio = 0) {
1493
+ function taskCost(row, task, text7, complexity, cacheReadRatio = 0, cacheWriteRatio = 0) {
1041
1494
  if (row?.pricing === null) return 0;
1042
1495
  const decision = row === null ? null : reasoningDecision(row, task);
1043
- const tokens = taskTokenBudget(text6, { ...task, reasoningEffort: decision?.reasoningEffort }, complexity, cacheReadRatio, cacheWriteRatio);
1496
+ const tokens = taskTokenBudget(text7, { ...task, reasoningEffort: decision?.reasoningEffort }, complexity, cacheReadRatio, cacheWriteRatio);
1044
1497
  return row === null ? 0 : ((tokens.inputTokens - tokens.cacheReadTokens - tokens.cacheWriteTokens) * row.pricing.input + tokens.cacheReadTokens * row.pricing.cacheRead + tokens.cacheWriteTokens * row.pricing.cacheWrite + tokens.outputTokens * row.pricing.output) / 1e6;
1045
1498
  }
1046
- function dominates(left, right, task, text6, complexity, cacheReadRatio, cacheWriteRatio) {
1499
+ function dominates(left, right, task, text7, complexity, cacheReadRatio, cacheWriteRatio) {
1047
1500
  if (left.pricing === null || right.pricing === null) return false;
1048
1501
  const leftReasoning = reasoningDecision(left, task);
1049
1502
  const rightReasoning = reasoningDecision(right, task);
1050
1503
  const leftValues = {
1051
1504
  quality: qualityForTask(left, task.type),
1052
- cost: taskCost(left, task, text6, complexity, cacheReadRatio, cacheWriteRatio),
1505
+ cost: taskCost(left, task, text7, complexity, cacheReadRatio, cacheWriteRatio),
1053
1506
  latency: clamp(left.latency * leftReasoning.multiplier.latency),
1054
1507
  specialty: specialtyForTask(left, task.type),
1055
1508
  reasoning: leftReasoning.reasoningFit,
@@ -1057,7 +1510,7 @@ function dominates(left, right, task, text6, complexity, cacheReadRatio, cacheWr
1057
1510
  };
1058
1511
  const rightValues = {
1059
1512
  quality: qualityForTask(right, task.type),
1060
- cost: taskCost(right, task, text6, complexity, cacheReadRatio, cacheWriteRatio),
1513
+ cost: taskCost(right, task, text7, complexity, cacheReadRatio, cacheWriteRatio),
1061
1514
  latency: clamp(right.latency * rightReasoning.multiplier.latency),
1062
1515
  specialty: specialtyForTask(right, task.type),
1063
1516
  reasoning: rightReasoning.reasoningFit,
@@ -1070,7 +1523,7 @@ function dominates(left, right, task, text6, complexity, cacheReadRatio, cacheWr
1070
1523
  function eligibleRowsForTask(rows, task) {
1071
1524
  return task.type === "vision" ? rows.filter((row) => row.inputModalities.length === 0 || row.inputModalities.includes("image")) : rows;
1072
1525
  }
1073
- function candidatePool(rows, task, weights, text6, complexity, cacheReadRatio, cacheWriteRatio, dependencySensitive = false) {
1526
+ function candidatePool(rows, task, weights, text7, complexity, cacheReadRatio, cacheWriteRatio, dependencySensitive = false) {
1074
1527
  const eligibleRows = eligibleRowsForTask(rows, task);
1075
1528
  const floor = Number(task.qualityFloor ?? 0);
1076
1529
  const feasible = eligibleRows.filter((row) => qualityForTask(row, task.type) >= floor);
@@ -1078,10 +1531,10 @@ function candidatePool(rows, task, weights, text6, complexity, cacheReadRatio, c
1078
1531
  const taskWeights = weightsForTask(weights, task);
1079
1532
  const scored = source.map((row) => ({
1080
1533
  row,
1081
- decision: candidateUtility(row, task, taskWeights, cacheReadRatio, cacheWriteRatio, text6, complexity),
1082
- cost: taskCost(row, task, text6, complexity, cacheReadRatio, cacheWriteRatio)
1534
+ decision: candidateUtility(row, task, taskWeights, cacheReadRatio, cacheWriteRatio, text7, complexity),
1535
+ cost: taskCost(row, task, text7, complexity, cacheReadRatio, cacheWriteRatio)
1083
1536
  }));
1084
- const frontier = dependencySensitive ? scored : scored.filter((item) => !scored.some((other) => other !== item && other.decision.score >= item.decision.score && dominates(other.row, item.row, task, text6, complexity, cacheReadRatio, cacheWriteRatio)));
1537
+ const frontier = dependencySensitive ? scored : scored.filter((item) => !scored.some((other) => other !== item && other.decision.score >= item.decision.score && dominates(other.row, item.row, task, text7, complexity, cacheReadRatio, cacheWriteRatio)));
1085
1538
  const essential = [
1086
1539
  scored.filter((item) => item.row.pricing !== null).sort((left, right) => left.cost - right.cost || compareRowsStable(left.row, right.row))[0],
1087
1540
  scored.slice().sort((left, right) => right.decision.score - left.decision.score || compareRowsStable(left.row, right.row))[0],
@@ -1099,9 +1552,9 @@ function candidatePool(rows, task, weights, text6, complexity, cacheReadRatio, c
1099
1552
  dependencySensitive
1100
1553
  };
1101
1554
  }
1102
- function solveAssignments({ rows, tasks, weights, text: text6, complexity, budget, cacheReadRatio, cacheWriteRatio, minimizeCost = false }) {
1555
+ function solveAssignments({ rows, tasks, weights, text: text7, complexity, budget, cacheReadRatio, cacheWriteRatio, minimizeCost = false }) {
1103
1556
  const dependencySensitive = tasks.some((task) => (task.dependsOn ?? []).length > 0);
1104
- const pools = tasks.map((task) => candidatePool(rows, task, weights, text6, complexity, cacheReadRatio, cacheWriteRatio, dependencySensitive));
1557
+ const pools = tasks.map((task) => candidatePool(rows, task, weights, text7, complexity, cacheReadRatio, cacheWriteRatio, dependencySensitive));
1105
1558
  const result = solveCandidateAssignments({ tasks, pools: pools.map((pool, index) => pool.options.map((option) => ({
1106
1559
  route: routeKey(option.row.provider, option.row.model),
1107
1560
  score: option.decision.score,
@@ -1125,15 +1578,15 @@ function solveAssignments({ rows, tasks, weights, text: text6, complexity, budge
1125
1578
  minimumFeasibleCost: result.minimumCostLowerBound
1126
1579
  };
1127
1580
  }
1128
- function buildPlan({ text: text6 = "", available = [], mode = "collective", pricing = {}, liveBench = null, liveBenchError = "", dataVersions = null, learning = null, budgetUsd = 0, cacheReadRatio = 0, cacheWriteRatio = 0, preset = DEFAULT_ROUTING_PRESET } = {}) {
1581
+ function buildPlan({ text: text7 = "", available = [], mode = "collective", pricing = {}, liveBench = null, liveBenchError = "", dataVersions = null, learning = null, budgetUsd = 0, cacheReadRatio = 0, cacheWriteRatio = 0, preset = DEFAULT_ROUTING_PRESET } = {}) {
1129
1582
  const presetId = normalizeRoutingPreset(preset);
1130
- const firstLine = String(text6 ?? "").split(/\r?\n/u)[0].trim();
1583
+ const firstLine = String(text7 ?? "").split(/\r?\n/u)[0].trim();
1131
1584
  const transformOnly = /^(?:请|帮我)?(?:总结|概括|翻译|摘要|解释)(?:以下|下列|下面|这份|这些)/u.test(firstLine) && !/(?:执行|完成|实施|分配)/u.test(firstLine);
1132
- const assessed = assessComplexity(transformOnly ? firstLine : text6);
1133
- const requirements = explicitRequirements(text6);
1585
+ const assessed = assessComplexity(transformOnly ? firstLine : text7);
1586
+ const requirements = explicitRequirements(text7);
1134
1587
  const compound = shouldSplitRequirements(requirements);
1135
1588
  const complexity = compound && assessed.band !== "complex" ? { value: Math.max(0.66, assessed.value), band: "complex" } : assessed;
1136
- const taskType = classifyTask(transformOnly ? firstLine : text6);
1589
+ const taskType = classifyTask(transformOnly ? firstLine : text7);
1137
1590
  const weights = presetWeights(OBJECTIVE_WEIGHTS[complexity.band], presetId);
1138
1591
  const discovered = Array.isArray(available) ? available.map((entry) => {
1139
1592
  const rawEfforts = Array.isArray(entry.reasoningEfforts) ? entry.reasoningEfforts : [];
@@ -1212,10 +1665,10 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1212
1665
  costOut: pricingRow.output,
1213
1666
  cacheRead: pricingRow.cacheRead,
1214
1667
  cacheWrite: pricingRow.cacheWrite
1215
- }, text6, 900, {}, cacheReadRatio, cacheWriteRatio)
1668
+ }, text7, 900, {}, cacheReadRatio, cacheWriteRatio)
1216
1669
  });
1217
1670
  }
1218
- const taskNodes = taskPackages(taskType, text6, complexity.band).map((task) => presetId === DEFAULT_ROUTING_PRESET ? task : {
1671
+ const taskNodes = taskPackages(taskType, text7, complexity.band).map((task) => presetId === DEFAULT_ROUTING_PRESET ? task : {
1219
1672
  ...task,
1220
1673
  qualityFloor: presetFloor(task.qualityFloor, presetId),
1221
1674
  weights: presetWeights(weightsForTask(weights, task), presetId)
@@ -1226,7 +1679,7 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1226
1679
  rows,
1227
1680
  tasks: taskNodes,
1228
1681
  weights,
1229
- text: text6,
1682
+ text: text7,
1230
1683
  complexity: complexity.band,
1231
1684
  budget: Number.POSITIVE_INFINITY,
1232
1685
  cacheReadRatio,
@@ -1236,7 +1689,7 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1236
1689
  rows,
1237
1690
  tasks: taskNodes,
1238
1691
  weights,
1239
- text: text6,
1692
+ text: text7,
1240
1693
  complexity: complexity.band,
1241
1694
  budget,
1242
1695
  cacheReadRatio,
@@ -1246,7 +1699,7 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1246
1699
  rows,
1247
1700
  tasks: taskNodes,
1248
1701
  weights,
1249
- text: text6,
1702
+ text: text7,
1250
1703
  complexity: complexity.band,
1251
1704
  budget: Number.POSITIVE_INFINITY,
1252
1705
  cacheReadRatio,
@@ -1258,8 +1711,8 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1258
1711
  const usedRoutes = optimized?.usedRoutes ?? /* @__PURE__ */ new Set();
1259
1712
  const constraintRelaxed = (optimized?.relaxedCount ?? 0) > 0;
1260
1713
  for (const row of rows) {
1261
- row.score = candidateUtility(row, taskNodes[0] ?? { type: taskType, qualityFloor: QUALITY_FLOORS[complexity.band] }, weights, cacheReadRatio, cacheWriteRatio, text6, complexity.band).score;
1262
- if (row.pricing !== null && taskNodes[0]) row.estimatedCost = taskCost(row, taskNodes[0], text6, complexity.band, cacheReadRatio, cacheWriteRatio);
1714
+ row.score = candidateUtility(row, taskNodes[0] ?? { type: taskType, qualityFloor: QUALITY_FLOORS[complexity.band] }, weights, cacheReadRatio, cacheWriteRatio, text7, complexity.band).score;
1715
+ if (row.pricing !== null && taskNodes[0]) row.estimatedCost = taskCost(row, taskNodes[0], text7, complexity.band, cacheReadRatio, cacheWriteRatio);
1263
1716
  }
1264
1717
  rows.sort((left, right) => right.score - left.score || compareRowsStable(left, right));
1265
1718
  const selectedAssignment = assignments[0];
@@ -1285,8 +1738,8 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1285
1738
  dependsOn: [...task.dependsOn ?? []]
1286
1739
  }));
1287
1740
  const costBreakdown = assignments.map(({ task, row, decision, estimatedCost, handoffPenalty }, index) => {
1288
- const tokens = taskTokenBudget(text6, { ...task, reasoningEffort: decision?.reasoningEffort }, complexity.band, cacheReadRatio, cacheWriteRatio);
1289
- const taskEstimate = estimatedCost ?? taskCost(row, task, text6, complexity.band, cacheReadRatio, cacheWriteRatio);
1741
+ const tokens = taskTokenBudget(text7, { ...task, reasoningEffort: decision?.reasoningEffort }, complexity.band, cacheReadRatio, cacheWriteRatio);
1742
+ const taskEstimate = estimatedCost ?? taskCost(row, task, text7, complexity.band, cacheReadRatio, cacheWriteRatio);
1290
1743
  return {
1291
1744
  stage: index + 1,
1292
1745
  purpose: task.purpose,
@@ -1309,12 +1762,12 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1309
1762
  };
1310
1763
  });
1311
1764
  const pricingComplete = assignments.length > 0 && assignments.every(({ row }) => row?.pricing !== null);
1312
- const totalEstimate = pricingComplete ? assignments.reduce((sum, { task, row, estimatedCost }) => sum + (estimatedCost ?? taskCost(row, task, text6, complexity.band, cacheReadRatio, cacheWriteRatio)), 0) : null;
1765
+ const totalEstimate = pricingComplete ? assignments.reduce((sum, { task, row, estimatedCost }) => sum + (estimatedCost ?? taskCost(row, task, text7, complexity.band, cacheReadRatio, cacheWriteRatio)), 0) : null;
1313
1766
  const baselineRows = assignments.map(({ task }) => {
1314
1767
  const strongest = eligibleRowsForTask(rows, task).reduce((best, row) => qualityForTask(row, task.type) > (best === null ? -1 : qualityForTask(best, task.type)) ? row : best, null);
1315
1768
  return { task, strongest };
1316
1769
  });
1317
- const baselineCost = baselineRows.every((item) => item.strongest?.pricing !== null && item.strongest !== null) ? baselineRows.reduce((sum, { task, strongest }) => sum + taskCost(strongest, task, text6, complexity.band, cacheReadRatio, cacheWriteRatio), 0) : null;
1770
+ const baselineCost = baselineRows.every((item) => item.strongest?.pricing !== null && item.strongest !== null) ? baselineRows.reduce((sum, { task, strongest }) => sum + taskCost(strongest, task, text7, complexity.band, cacheReadRatio, cacheWriteRatio), 0) : null;
1318
1771
  const qualityEvidenceComplete = assignments.length > 0 && assignments.every(({ row, task }) => ["livebench", "route", "user"].includes(qualitySourceForTask(row, task.type))) && baselineRows.every(({ strongest, task }) => ["livebench", "route", "user"].includes(qualitySourceForTask(strongest, task.type)));
1319
1772
  const budgetExceeded = Number(budgetUsd) > 0 && totalEstimate !== null ? totalEstimate > Number(budgetUsd) : null;
1320
1773
  const savings = baselineCost === null || totalEstimate === null || !qualityEvidenceComplete ? null : baselineCost <= 0 ? 0 : clamp((baselineCost - totalEstimate) / baselineCost);
@@ -1331,7 +1784,7 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1331
1784
  taskTypes: [...new Set(taskNodes.map((task) => task.type).filter((type) => type !== "reasoning"))],
1332
1785
  objectiveWeights: weights,
1333
1786
  candidates: rows.slice(0, 8).map((row) => {
1334
- const decision = candidateUtility(row, taskNodes[0] ?? { type: taskType, qualityFloor: QUALITY_FLOORS[complexity.band], preferredReasoningEffort: complexity.band === "simple" ? "low" : "medium" }, weights, cacheReadRatio, cacheWriteRatio, text6, complexity.band);
1787
+ const decision = candidateUtility(row, taskNodes[0] ?? { type: taskType, qualityFloor: QUALITY_FLOORS[complexity.band], preferredReasoningEffort: complexity.band === "simple" ? "low" : "medium" }, weights, cacheReadRatio, cacheWriteRatio, text7, complexity.band);
1335
1788
  return { provider: row.provider, model: row.model, score: Number(row.score.toFixed(3)), preferenceAdjustment: decision.preferenceAdjustment, quality: row.qualitySource === "unknown" ? null : Number(row.quality.toFixed(3)), qualitySource: row.qualitySource, specialty: Number(row.specialty.toFixed(3)), reasoningEffort: decision.reasoningEffort, preferredReasoningEffort: decision.preferredReasoningEffort, reasoningFit: Number(decision.reasoningFit.toFixed(3)), reasoningKnown: row.reasoningKnown, reasoningEfforts: row.reasoningEfforts, estimatedCost: row.estimatedCost === null ? null : Number(row.estimatedCost.toFixed(6)), inputPrice: row.pricing?.input ?? null, outputPrice: row.pricing?.output ?? null, pricingSource: row.pricingSource, pricingVersion: row.pricingVersion, pricingAsOf: row.snapshotAsOf };
1336
1789
  }),
1337
1790
  selected: selected === null ? null : { provider: selected.provider, model: selected.model, reasoningEffort: selectedAssignment?.decision?.reasoningEffort, estimatedCost: selected.estimatedCost === null ? null : Number(selected.estimatedCost.toFixed(6)), qualitySource: qualitySourceForTask(selected, selectedAssignment?.task.type ?? taskType), pricingSource: selected.pricingSource },
@@ -1909,10 +2362,34 @@ function toolInstallAction({ tool, probe, readiness, job, probeStatus, latestVer
1909
2362
  disabled: !interactive || running || installed && (order === 1 || current)
1910
2363
  };
1911
2364
  }
1912
- function installClickRefusal({ tool, submitting = false, running = false } = {}) {
2365
+ function toolMaintenanceActions({ tool, probe, job, probeStatus, summary }) {
2366
+ const running = job?.status === "running";
2367
+ const installed = probe?.installed === true;
2368
+ const interactive = probeStatus === "ready" || probeStatus === "refreshing";
2369
+ const desktop = tool.manager === "signed-windows-installer";
2370
+ const busy = running || !interactive;
2371
+ return {
2372
+ repair: {
2373
+ label: "\u4E00\u952E\u4FEE\u590D",
2374
+ title: "\u7528\u540C\u4E00\u6761\u56FA\u5B9A\u5B98\u65B9\u5B89\u88C5\u547D\u4EE4\u91CD\u65B0\u5B89\u88C5\u4E00\u904D\uFF0C\u4FEE\u590D\u635F\u574F\u6216\u4E0D\u5B8C\u6574\u7684\u5B89\u88C5",
2375
+ disabled: busy || desktop || summary?.installable === false
2376
+ },
2377
+ uninstall: {
2378
+ label: "\u4E00\u952E\u5378\u8F7D",
2379
+ title: "\u53EA\u5220\u9664\u7A0B\u5E8F\u672C\u8EAB\uFF1B\u914D\u7F6E\u3001\u767B\u5F55\u4FE1\u606F\u548C\u5386\u53F2\u8BB0\u5F55\u4FDD\u7559",
2380
+ disabled: busy || desktop || !installed || summary?.removable === false
2381
+ }
2382
+ };
2383
+ }
2384
+ function installClickRefusal({ tool, submitting = false, running = false, operation = "install" } = {}) {
1913
2385
  if (!tool) return "\u672A\u77E5\u5B98\u65B9\u5DE5\u5177\uFF0C\u65E0\u6CD5\u5F00\u59CB\u5B89\u88C5\u3002";
1914
2386
  if (tool.unsupported) return String(tool.unsupportedReason ?? "").trim() || "\u6B64\u5DE5\u5177\u6682\u4E0D\u652F\u6301\u4E00\u952E\u5B89\u88C5\u3002";
1915
- if (submitting || running) return "\u8BE5\u5DE5\u5177\u6B63\u5728\u5B89\u88C5\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u3002";
2387
+ if (tool.manager === "signed-windows-installer" && operation !== "install") {
2388
+ return `${tool.label} \u7531\u5B98\u65B9\u7B7E\u540D\u684C\u9762\u5B89\u88C5\u5668\u5B89\u88C5\uFF0C\u8BF7\u5728\u7CFB\u7EDF\u201C\u5E94\u7528\u201D\u4E2D\u5378\u8F7D\u5B83\u3002`;
2389
+ }
2390
+ if (submitting || running) {
2391
+ return operation === "uninstall" ? "\u8BE5\u5DE5\u5177\u6B63\u5728\u5378\u8F7D\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u3002" : operation === "repair" ? "\u8BE5\u5DE5\u5177\u6B63\u5728\u4FEE\u590D\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u3002" : "\u8BE5\u5DE5\u5177\u6B63\u5728\u5B89\u88C5\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u3002";
2392
+ }
1916
2393
  return "";
1917
2394
  }
1918
2395
  function payloadError(payload, fallback) {
@@ -2727,7 +3204,7 @@ function LaunchPreview({ value, changed, running, confirmRef, onConfirm, onCance
2727
3204
  // .dsh-plugin/client/cli-terminal.jsx
2728
3205
  var import_react4 = __toESM(require("react"), 1);
2729
3206
 
2730
- // node_modules/.pnpm/@xterm+xterm@6.0.0/node_modules/@xterm/xterm/lib/xterm.mjs
3207
+ // node_modules/@xterm/xterm/lib/xterm.mjs
2731
3208
  var zs = Object.defineProperty;
2732
3209
  var Rl = Object.getOwnPropertyDescriptor;
2733
3210
  var Ll = (s15, t) => {
@@ -11841,7 +12318,7 @@ var Dl = class extends D {
11841
12318
  }
11842
12319
  };
11843
12320
 
11844
- // node_modules/.pnpm/@xterm+addon-fit@0.11.0/node_modules/@xterm/addon-fit/lib/addon-fit.mjs
12321
+ // node_modules/@xterm/addon-fit/lib/addon-fit.mjs
11845
12322
  var h = 2;
11846
12323
  var _ = 1;
11847
12324
  var o = class {
@@ -11865,7 +12342,7 @@ var o = class {
11865
12342
  }
11866
12343
  };
11867
12344
 
11868
- // node_modules/.pnpm/@xterm+xterm@6.0.0/node_modules/@xterm/xterm/css/xterm.css
12345
+ // node_modules/@xterm/xterm/css/xterm.css
11869
12346
  var xterm_default = `/**
11870
12347
  * Copyright (c) 2014 The xterm.js authors. All rights reserved.
11871
12348
  * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
@@ -12154,7 +12631,7 @@ var xterm_default = `/**
12154
12631
  `;
12155
12632
 
12156
12633
  // .dsh-plugin/client/host-version.mjs
12157
- var ROUTER_CLIENT_VERSION = true ? "0.16.4" : "";
12634
+ var ROUTER_CLIENT_VERSION = true ? "0.17.1" : "";
12158
12635
  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";
12159
12636
  function isMissingRemoteMethod(message) {
12160
12637
  const value = String(message ?? "");
@@ -12752,6 +13229,16 @@ var router_main_default = `.mr-workspace {
12752
13229
  .mr-tool-log { margin-top: 7px; color: var(--mr-muted); font-size: 11px; }
12753
13230
  .mr-tool-log summary { cursor: pointer; }
12754
13231
  .mr-tool-log pre { max-height: 140px; overflow: auto; padding: 8px; border-radius: 7px; background: var(--dsw-alias-markdown-code-block); font-size: 10px; white-space: pre-wrap; overflow-wrap: anywhere; }
13232
+ .mr-tool-methods { display: flex; align-items: center; gap: 6px; margin-right: 2px; }
13233
+ .mr-tool-methods label { display: flex; align-items: center; gap: 6px; color: var(--mr-muted); font-size: 11px; }
13234
+ .mr-install-settings { margin-bottom: 14px; padding: 13px 14px; border: 1px solid var(--mr-line); border-radius: 11px; background: var(--mr-card); }
13235
+ .mr-install-settings-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
13236
+ .mr-install-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
13237
+ .mr-install-field { display: flex; flex-direction: column; gap: 5px; font-size: 12px; color: var(--mr-muted); }
13238
+ .mr-install-field > span { font-weight: 600; color: var(--mr-ink); }
13239
+ .mr-install-field small { font-size: 11px; line-height: 1.6; }
13240
+ .mr-install-error { color: #d95360; }
13241
+ .mr-install-toggle-row { display: flex; align-items: center; gap: 7px; }
12755
13242
  .mr-channel-line { display: flex; align-items: center; gap: 8px; margin: 8px 0; flex-wrap: wrap; }
12756
13243
  .mr-pill-channel-ok { border-color: var(--dsw-alias-success, #2f9e63); color: var(--dsw-alias-success, #2f9e63); }
12757
13244
  .mr-pill-warn { border-color: var(--dsw-alias-warning, #c27c0e); color: var(--dsw-alias-warning, #c27c0e); }
@@ -12972,9 +13459,115 @@ function probeLabel(probe) {
12972
13459
  if (probe.status === "probe-failed") return "\u68C0\u6D4B\u5931\u8D25";
12973
13460
  return probe.detail || "\u672A\u5B89\u88C5";
12974
13461
  }
12975
- function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth }) {
13462
+ var INSTALL_SETTING_FIELDS = [
13463
+ {
13464
+ key: "toolInstallDir",
13465
+ label: "\u7EDF\u4E00\u5B89\u88C5\u76EE\u5F55",
13466
+ fallback: "",
13467
+ placeholder: "\u7559\u7A7A = \u5404\u5382\u5546\u9ED8\u8BA4\u76EE\u5F55",
13468
+ hint: "npm / pnpm \u4F1A\u52A0 --prefix\u3002\u652F\u6301\u76EE\u5F55\u7684\u5B98\u65B9\u811A\u672C\u628A\u8FD9\u4E2A\u8DEF\u5F84\u4F20\u7ED9\u5382\u5546\u81EA\u5DF1\u7684\u5B89\u88C5\u76EE\u5F55\u53D8\u91CF\uFF1B\u5199\u6B7B\u8DEF\u5F84\u7684\u811A\u672C\u4F1A\u6807\u660E\u5B9E\u9645\u843D\u70B9\u3002\u66F4\u65B0\u4E5F\u4F7F\u7528\u8FD9\u91CC\uFF0C\u4E0D\u4F1A\u6539\u53BB\u522B\u7684\u9ED8\u8BA4\u4F4D\u7F6E\u3002",
13469
+ validate: () => null
13470
+ },
13471
+ {
13472
+ key: "toolNpmRegistry",
13473
+ label: "npm \u6E90\u5730\u5740",
13474
+ fallback: "https://registry.npmjs.org/",
13475
+ placeholder: "https://registry.npmjs.org/",
13476
+ hint: "\u5FC5\u987B\u4EE5 https:// \u5F00\u5934\u3002\u6362\u6210\u56FD\u5185\u955C\u50CF\u540E\uFF0C\u6240\u6709\u5305\u7BA1\u7406\u5668\u65B9\u5F0F\u90FD\u4ECE\u8FD9\u91CC\u4E0B\u8F7D\u3002",
13477
+ validate: (raw) => /^https:\/\/\S+$/i.test(raw.trim()) ? null : "\u9700\u8981 https \u5F00\u5934\u7684\u5B8C\u6574\u5730\u5740\uFF0C\u4F8B\u5982 https://registry.npmmirror.com/"
13478
+ },
13479
+ {
13480
+ key: "toolScriptUrlsJson",
13481
+ label: "\u5B89\u88C5\u811A\u672C\u6E90\u8986\u76D6\uFF08JSON\uFF0C\u53EF\u9009\uFF09",
13482
+ fallback: "{}",
13483
+ placeholder: '{ "stepcode": "https://mirror/stepcode/install.ps1" }',
13484
+ hint: "\u6309\u5DE5\u5177 ID \u6307\u5B9A\u5382\u5546\u5B89\u88C5\u811A\u672C\u7684\u955C\u50CF\u5730\u5740\u3002\u811A\u672C\u4ECD\u4F1A\u4E0B\u8F7D\u540E\u6821\u9A8C\u5382\u5546\u6807\u8BB0\u518D\u6267\u884C\uFF0C\u955C\u50CF\u4E0D\u80FD\u6362\u6210\u522B\u7684\u7A0B\u5E8F\u3002",
13485
+ validate: (raw) => {
13486
+ if (!raw.trim()) return null;
13487
+ let parsed;
13488
+ try {
13489
+ parsed = JSON.parse(raw);
13490
+ } catch {
13491
+ return "JSON \u683C\u5F0F\u65E0\u6548\u3002";
13492
+ }
13493
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return "\u9700\u8981 JSON \u5BF9\u8C61\u3002";
13494
+ for (const [id2, url] of Object.entries(parsed)) {
13495
+ if (!OFFICIAL_TOOLS.some((tool) => tool.id === id2)) return `${id2} \u4E0D\u662F\u5B98\u65B9\u5DE5\u5177 ID\u3002`;
13496
+ if (typeof url !== "string" || !/^https:\/\/\S+$/i.test(url.trim())) return `${id2} \u7684\u5730\u5740\u5FC5\u987B\u662F https \u94FE\u63A5\u3002`;
13497
+ }
13498
+ return null;
13499
+ }
13500
+ }
13501
+ ];
13502
+ function InstallSettings({ settingsScope, install }) {
13503
+ const [snapshot, setSnapshot] = import_react5.default.useState(() => settingsScope?.getSnapshot?.() ?? { value: {}, writable: false });
13504
+ const [drafts, setDrafts] = import_react5.default.useState({});
13505
+ const [saving, setSaving] = import_react5.default.useState(false);
13506
+ const [notice, setNotice] = import_react5.default.useState("");
13507
+ import_react5.default.useEffect(() => {
13508
+ if (typeof settingsScope?.subscribe !== "function") return void 0;
13509
+ const unsubscribe = settingsScope.subscribe(() => {
13510
+ setSnapshot(settingsScope.getSnapshot());
13511
+ setDrafts({});
13512
+ });
13513
+ setSnapshot(settingsScope.getSnapshot());
13514
+ return unsubscribe;
13515
+ }, [settingsScope]);
13516
+ if (!settingsScope) return null;
13517
+ const writable = snapshot.writable === true && !saving;
13518
+ const shown = (field2) => drafts[field2.key] ?? String(snapshot.value?.[field2.key] ?? field2.fallback);
13519
+ const errors = Object.fromEntries(INSTALL_SETTING_FIELDS.map((field2) => [field2.key, field2.validate(shown(field2))]));
13520
+ const apply2 = async (entries, successText) => {
13521
+ setSaving(true);
13522
+ setNotice("");
13523
+ try {
13524
+ const accepted = await settingsScope.mutate(entries, snapshot.revision);
13525
+ if (!accepted) throw new Error("\u8BBE\u7F6E\u672A\u88AB\u4FDD\u5B58\uFF0C\u53EF\u80FD\u88AB\u5176\u4ED6\u9875\u9762\u4FEE\u6539\uFF1B\u8F93\u5165\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u91CD\u65B0\u6838\u5BF9\u3002");
13526
+ setNotice(successText);
13527
+ } catch (error) {
13528
+ setNotice(text5(error?.message) || "\u5B89\u88C5\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25\u3002");
13529
+ } finally {
13530
+ setSaving(false);
13531
+ }
13532
+ };
13533
+ const commit = (field2) => {
13534
+ const saved = String(snapshot.value?.[field2.key] ?? field2.fallback);
13535
+ const raw = shown(field2);
13536
+ if (raw === saved || errors[field2.key]) return;
13537
+ return apply2([{ op: "set", path: [field2.key], value: raw }], `\u5DF2\u4FDD\u5B58${field2.label}\uFF0C\u4E0B\u4E00\u6B21\u5B89\u88C5\u8D77\u751F\u6548\u3002`);
13538
+ };
13539
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-install-settings", "aria-label": "\u7EDF\u4E00\u5B89\u88C5\u8BBE\u7F6E" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-install-settings-head" }, /* @__PURE__ */ import_react5.default.createElement("strong", null, "\u7EDF\u4E00\u5B89\u88C5\u8BBE\u7F6E"), /* @__PURE__ */ import_react5.default.createElement("span", { className: "mr-caption" }, "\u5BF9\u6240\u6709\u5B98\u65B9\u5DE5\u5177\u751F\u6548\uFF0C\u6539\u52A8\u540E\u4E0B\u4E00\u6B21\u4E00\u952E\u5B89\u88C5\u3001\u4FEE\u590D\u6216\u5378\u8F7D\u7ACB\u5373\u4F7F\u7528\u3002\u63D2\u4EF6\u8BBE\u7F6E\u9875\u91CC\u6709\u540C\u6837\u7684\u5B57\u6BB5\u3002")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-install-grid" }, INSTALL_SETTING_FIELDS.map((field2) => /* @__PURE__ */ import_react5.default.createElement("label", { className: "mr-install-field", key: field2.key }, /* @__PURE__ */ import_react5.default.createElement("span", null, field2.label), /* @__PURE__ */ import_react5.default.createElement(
13540
+ "input",
13541
+ {
13542
+ className: "mr-input",
13543
+ value: shown(field2),
13544
+ placeholder: field2.placeholder,
13545
+ disabled: !writable,
13546
+ "aria-invalid": Boolean(errors[field2.key]),
13547
+ onChange: (event) => setDrafts((previous) => ({ ...previous, [field2.key]: event.target.value })),
13548
+ onBlur: () => {
13549
+ void commit(field2);
13550
+ }
13551
+ }
13552
+ ), errors[field2.key] ? /* @__PURE__ */ import_react5.default.createElement("small", { className: "mr-install-error", role: "alert" }, errors[field2.key]) : /* @__PURE__ */ import_react5.default.createElement("small", null, field2.hint))), /* @__PURE__ */ import_react5.default.createElement("label", { className: "mr-install-field" }, /* @__PURE__ */ import_react5.default.createElement("span", null, "\u5B89\u88C5\u811A\u672C\u65B9\u5F0F"), /* @__PURE__ */ import_react5.default.createElement("span", { className: "mr-install-toggle-row" }, /* @__PURE__ */ import_react5.default.createElement(
13553
+ "input",
13554
+ {
13555
+ type: "checkbox",
13556
+ checked: snapshot.value?.toolAllowScriptInstall !== false,
13557
+ disabled: !writable,
13558
+ onChange: (event) => {
13559
+ const allowed = event.target.checked;
13560
+ void apply2(
13561
+ [{ op: "set", path: ["toolAllowScriptInstall"], value: allowed }],
13562
+ allowed ? "\u5DF2\u5141\u8BB8\u811A\u672C\u5B89\u88C5\u65B9\u5F0F\uFF08curl / irm\uFF09\u3002" : "\u5DF2\u5173\u95ED\u811A\u672C\u5B89\u88C5\u65B9\u5F0F\uFF0C\u53EA\u80FD\u4F7F\u7528 npm / pnpm\u3002"
13563
+ );
13564
+ }
13565
+ }
13566
+ ), /* @__PURE__ */ import_react5.default.createElement("span", null, "\u5141\u8BB8 curl / irm \u5B89\u88C5\u811A\u672C")), /* @__PURE__ */ import_react5.default.createElement("small", null, "\u5173\u95ED\u540E\u53EA\u80FD\u4F7F\u7528 npm / pnpm \u65B9\u5F0F\uFF1B\u811A\u672C\u65B9\u5F0F\u9700\u8981\u5148\u4E0B\u8F7D\u5E76\u6821\u9A8C\u5382\u5546\u6807\u8BB0\u518D\u6267\u884C\u3002"))), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { marginTop: 8 }, role: notice ? "status" : void 0 }, "\u5F53\u524D\u751F\u6548\uFF1A\u5B89\u88C5\u76EE\u5F55 ", install?.installDir || "\uFF08\u5404\u5382\u5546\u9ED8\u8BA4\uFF09", " \xB7 npm \u6E90 ", install?.registry || "\uFF08\u9ED8\u8BA4\uFF09", notice ? ` \xB7 ${notice}` : ""));
13567
+ }
13568
+ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth, settingsScope }) {
12976
13569
  const healthById = Object.fromEntries((health?.tools ?? []).map((item) => [item.id, item]));
12977
- const [probeState, setProbeState] = import_react5.default.useState({ status: "loading", probes: [], capabilities: [], readiness: [], error: "" });
13570
+ const [probeState, setProbeState] = import_react5.default.useState({ status: "loading", probes: [], capabilities: [], readiness: [], install: null, error: "" });
12978
13571
  const [jobs, setJobs] = import_react5.default.useState({});
12979
13572
  const [rowErrors, setRowErrors] = import_react5.default.useState({});
12980
13573
  const mounted = import_react5.default.useRef(false);
@@ -13005,7 +13598,7 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
13005
13598
  const probes = Array.isArray(response.value?.tools) ? response.value.tools : [];
13006
13599
  const capabilities = Array.isArray(response.value?.executionCapabilities) ? response.value.executionCapabilities : [];
13007
13600
  const readiness = Array.isArray(response.value?.executionReadiness) ? response.value.executionReadiness : [];
13008
- setProbeState({ status: "ready", probes, capabilities, readiness, error: "" });
13601
+ setProbeState({ status: "ready", probes, capabilities, readiness, install: response.value?.install ?? null, error: "" });
13009
13602
  onProbes({ probes, capabilities, readiness, hostVersion: typeof response.value?.hostVersion === "string" ? response.value.hostVersion : null });
13010
13603
  } catch (error) {
13011
13604
  if (!mounted.current || current !== request.current) return;
@@ -13016,7 +13609,7 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
13016
13609
  kept = true;
13017
13610
  return { ...previous, status: "ready", error: message };
13018
13611
  }
13019
- return { status: "error", probes: [], capabilities: [], readiness: [], error: message };
13612
+ return { status: "error", probes: [], capabilities: [], readiness: [], install: null, error: message };
13020
13613
  });
13021
13614
  if (!kept) onProbes({ probes: [], capabilities: [], readiness: [] });
13022
13615
  }
@@ -13088,34 +13681,36 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
13088
13681
  clearInterval(timer);
13089
13682
  };
13090
13683
  }, [jobs, officialToolInstallStatus]);
13091
- const install = async (id2) => {
13684
+ const run = async (id2, operation) => {
13092
13685
  const tool = OFFICIAL_TOOLS.find((item) => item.id === id2);
13686
+ const bridge = { install: installOfficialTool, repair: repairOfficialTool, uninstall: uninstallOfficialTool }[operation];
13093
13687
  const refusal = installClickRefusal({
13094
13688
  tool,
13095
13689
  submitting: submitting.current.has(id2),
13096
- running: jobsRef.current[id2]?.status === "running"
13690
+ running: jobsRef.current[id2]?.status === "running",
13691
+ operation
13097
13692
  });
13098
13693
  if (refusal) {
13099
13694
  setRowErrors((previous) => ({ ...previous, [id2]: refusal }));
13100
13695
  return;
13101
13696
  }
13697
+ if (typeof bridge !== "function") throw new Error("\u5B98\u65B9\u5DE5\u5177\u5B89\u88C5\u6865\u5C1A\u672A\u52A0\u8F7D\u3002");
13102
13698
  submitting.current.add(id2);
13103
13699
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
13104
13700
  setRowErrors((previous) => ({ ...previous, [id2]: "" }));
13105
13701
  rememberJobs((previous) => ({
13106
13702
  ...previous,
13107
- [id2]: { tool: id2, status: "running", outputTail: [], startedAt, error: null }
13703
+ [id2]: { tool: id2, operation, status: "running", outputTail: [], startedAt, error: null }
13108
13704
  }));
13109
13705
  try {
13110
- if (typeof installOfficialTool !== "function") throw new Error("\u5B98\u65B9\u5DE5\u5177\u5B89\u88C5\u6865\u5C1A\u672A\u52A0\u8F7D\u3002");
13111
- const response = await installOfficialTool(id2);
13706
+ const response = await bridge({ tool: id2 });
13112
13707
  if (!mounted.current) return;
13113
13708
  const accepted = acceptedInstallJob(response);
13114
- if (!accepted.job) throw new Error(remoteErrorText(accepted.error, "\u5B89\u88C5\u4EFB\u52A1\u672A\u88AB\u63A5\u53D7\u3002"));
13709
+ if (!accepted.job) throw new Error(remoteErrorText(accepted.error, "\u4EFB\u52A1\u672A\u88AB\u63A5\u53D7\u3002"));
13115
13710
  rememberJobs((previous) => shouldApplyInstallStatus(previous[id2], accepted.job) ? { ...previous, [id2]: accepted.job } : previous);
13116
13711
  } catch (error) {
13117
13712
  if (!mounted.current) return;
13118
- const message = remoteErrorText(text5(error?.message), "\u5B89\u88C5\u542F\u52A8\u5931\u8D25\u3002");
13713
+ const message = remoteErrorText(text5(error?.message), "\u542F\u52A8\u5931\u8D25\u3002");
13119
13714
  rememberJobs((previous) => ({
13120
13715
  ...previous,
13121
13716
  [id2]: {
@@ -13131,6 +13726,29 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
13131
13726
  submitting.current.delete(id2);
13132
13727
  }
13133
13728
  };
13729
+ const install = (id2) => run(id2, "install");
13730
+ const repair = (id2) => run(id2, "repair");
13731
+ const uninstall = (id2) => run(id2, "uninstall");
13732
+ const setInstallMethod = async (toolId, methodId) => {
13733
+ const snapshot = settingsScope?.getSnapshot?.();
13734
+ if (!snapshot) return;
13735
+ let map = {};
13736
+ try {
13737
+ map = JSON.parse(String(snapshot.value?.toolInstallMethodsJson ?? "{}")) || {};
13738
+ } catch {
13739
+ map = {};
13740
+ }
13741
+ if (typeof map !== "object" || Array.isArray(map)) map = {};
13742
+ if (methodId) map[toolId] = methodId;
13743
+ else delete map[toolId];
13744
+ try {
13745
+ const accepted = await settingsScope.mutate([{ op: "set", path: ["toolInstallMethodsJson"], value: JSON.stringify(map) }], snapshot.revision);
13746
+ if (!accepted) throw new Error("\u5B89\u88C5\u65B9\u5F0F\u672A\u88AB\u4FDD\u5B58\uFF0C\u53EF\u80FD\u88AB\u5176\u4ED6\u9875\u9762\u4FEE\u6539\u3002");
13747
+ void refresh();
13748
+ } catch (error) {
13749
+ if (mounted.current) setRowErrors((previous) => ({ ...previous, [toolId]: text5(error?.message) || "\u5B89\u88C5\u65B9\u5F0F\u4FDD\u5B58\u5931\u8D25\u3002" }));
13750
+ }
13751
+ };
13134
13752
  const cancel = async (id2) => {
13135
13753
  if (typeof cancelOfficialToolInstall !== "function" || jobs[id2]?.status !== "running" || jobs[id2]?.cancelRequested) return;
13136
13754
  setRowErrors((previous) => ({ ...previous, [id2]: "" }));
@@ -13147,24 +13765,50 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
13147
13765
  const byId = Object.fromEntries(probeState.probes.map((probe) => [probe.id, probe]));
13148
13766
  const capabilitiesById = Object.fromEntries(probeState.capabilities.map((item) => [item.id, item]));
13149
13767
  const readinessById = Object.fromEntries(probeState.readiness.map((item) => [item.id, item]));
13150
- return /* @__PURE__ */ import_react5.default.createElement("section", { className: "mr-card", "aria-label": "\u5B98\u65B9\u5DE5\u5177" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-head" }, /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("h2", { className: "mr-card-title" }, "\u5B98\u65B9\u5DE5\u5177 \xB7 \u4F53\u68C0"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-card-copy" }, "\u68C0\u6D4B\u672C\u673A\u5B98\u65B9\u5DE5\u5177\u7684\u5B89\u88C5\u3001\u7248\u672C\u548C\u767B\u5F55\u72B6\u6001\uFF0C\u5E76\u4ECE\u56FA\u5B9A\u6CE8\u518C\u8868\u4E00\u952E\u5B89\u88C5\u6216\u66F4\u65B0\u5230\u5404\u5382\u5546\u6700\u65B0\u7248\uFF08\u65B0\u7248\u672C\u672A\u7ECF\u63D2\u4EF6\u6D4B\u8BD5\uFF09\u3002\u672A\u767B\u5F55\u7684\u5DE5\u5177\u70B9\u201C\u53BB\u767B\u5F55\u201D\u67E5\u770B\u767B\u5F55\u547D\u4EE4\u3002ZCode \u4F1A\u6253\u5F00\u5B98\u65B9\u5B89\u88C5\u7A97\u53E3\u4F9B\u4F60\u9009\u62E9\u76EE\u5F55\uFF1B\u5B8C\u6210\u540E\u91CD\u65B0\u4F53\u68C0\u3002")), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: probeState.status === "loading", onClick: () => {
13768
+ const installById = Object.fromEntries((probeState.install?.tools ?? []).map((item) => [item.id, item]));
13769
+ return /* @__PURE__ */ import_react5.default.createElement("section", { className: "mr-card", "aria-label": "\u5B98\u65B9\u5DE5\u5177" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-head" }, /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("h2", { className: "mr-card-title" }, "\u5B98\u65B9\u5DE5\u5177 \xB7 \u4F53\u68C0"), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-card-copy" }, "\u68C0\u6D4B\u672C\u673A\u5B98\u65B9\u5DE5\u5177\u7684\u5B89\u88C5\u3001\u7248\u672C\u548C\u767B\u5F55\u72B6\u6001\uFF0C\u5E76\u4ECE\u56FA\u5B9A\u6CE8\u518C\u8868\u4E00\u952E\u5B89\u88C5\u6216\u66F4\u65B0\u5230\u5404\u5382\u5546\u6700\u65B0\u7248\uFF08\u65B0\u7248\u672C\u672A\u7ECF\u63D2\u4EF6\u6D4B\u8BD5\uFF09\u3002\u53EF\u6309\u5DE5\u5177\u9009\u62E9 npm\u3001pnpm \u6216\u5382\u5546\u5B89\u88C5\u811A\u672C\u65B9\u5F0F\uFF0C\u5E76\u7EDF\u4E00\u8BBE\u7F6E\u5B89\u88C5\u76EE\u5F55\u4E0E\u4E0B\u8F7D\u6E90\uFF1B\u5378\u8F7D\u53EA\u5220\u9664\u7A0B\u5E8F\uFF0C\u914D\u7F6E\u4E0E\u767B\u5F55\u4FDD\u7559\u3002\u672A\u767B\u5F55\u7684\u5DE5\u5177\u70B9\u201C\u53BB\u767B\u5F55\u201D\u67E5\u770B\u767B\u5F55\u547D\u4EE4\u3002ZCode \u4F1A\u6253\u5F00\u5B98\u65B9\u5B89\u88C5\u7A97\u53E3\u4F9B\u4F60\u9009\u62E9\u76EE\u5F55\uFF1B\u5B8C\u6210\u540E\u91CD\u65B0\u4F53\u68C0\u3002")), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: probeState.status === "loading", onClick: () => {
13151
13770
  void refresh();
13152
- } }, "\u91CD\u65B0\u4F53\u68C0")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-body" }, probeState.status === "loading" && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-empty", role: "status" }, "\u6B63\u5728\u68C0\u6D4B\u672C\u673A\u5B98\u65B9\u5DE5\u5177\u2026"), probeState.error && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error", role: "alert" }, probeState.error), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tools", role: "list", "aria-label": "\u5B98\u65B9\u5DE5\u5177\u6CE8\u518C\u8868" }, OFFICIAL_TOOLS.map((tool) => {
13153
- const command = installCommandLine(tool);
13771
+ } }, "\u91CD\u65B0\u4F53\u68C0")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-body" }, /* @__PURE__ */ import_react5.default.createElement(InstallSettings, { settingsScope, install: probeState.install }), probeState.status === "loading" && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-empty", role: "status" }, "\u6B63\u5728\u68C0\u6D4B\u672C\u673A\u5B98\u65B9\u5DE5\u5177\u2026"), probeState.error && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error", role: "alert" }, probeState.error), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tools", role: "list", "aria-label": "\u5B98\u65B9\u5DE5\u5177\u6CE8\u518C\u8868" }, OFFICIAL_TOOLS.map((tool) => {
13772
+ const summary = installById[tool.id];
13773
+ const command = summary?.command ?? installCommandLine(tool);
13774
+ const methods = summary?.methods ?? installMethodsFor(tool).map((method) => ({ id: method.id, label: method.label }));
13154
13775
  const probe = byId[tool.id];
13155
13776
  const capability = capabilitiesById[tool.id];
13156
13777
  const readiness = readinessById[tool.id];
13157
13778
  const job = jobs[tool.id];
13158
13779
  const running = job?.status === "running";
13159
13780
  const action = toolInstallAction({ tool, probe, readiness, job, probeStatus: probeState.status, latestVersion: healthById[tool.id]?.latestVersion ?? null });
13160
- const verified = job?.status === "succeeded" && job.postInstallProbe?.installed === true;
13161
- const status = running ? job.cancelRequested ? "\u6B63\u5728\u53D6\u6D88\u5B89\u88C5\u2026" : "\u5B89\u88C5\u4E2D\u2026" : job?.status === "installer-opened" ? "\u5B98\u65B9\u5B89\u88C5\u5668\u5DF2\u6253\u5F00\uFF0C\u8BF7\u5B8C\u6210\u5B89\u88C5\u540E\u91CD\u65B0\u68C0\u6D4B" : job?.status === "cancelled" ? "\u5B89\u88C5\u5DF2\u53D6\u6D88\uFF0C\u8BF7\u91CD\u65B0\u68C0\u6D4B" : verified ? "\u5B89\u88C5\u6210\u529F\u5E76\u9A8C\u8BC1" : probeLabel(probe);
13162
- return /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool", role: "listitem", key: tool.id }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-info" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-name", title: tool.purpose }, tool.label), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-provider" }, tool.vendor, " \xB7 ", tool.id), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-status", role: "status" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: `mr-tool-dot ${running ? "running" : probe?.installed ? "installed" : "missing"}` }), status, probe?.installed && probe.version ? ` \xB7 ${probe.version}` : "", healthById[tool.id]?.latestVersion ? ` \xB7 \u6700\u65B0 ${healthById[tool.id].latestVersion}` : ""), /* @__PURE__ */ import_react5.default.createElement(ToolLoginLine, { entry: healthById[tool.id] }), probe?.installed && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, tool.headlessAdapter ? "\u5DF2\u53EF\u7531 model_router_execute \u4EE5\u65E0\u754C\u9762\u65B9\u5F0F\u8C03\u7528\u3002\u547D\u4EE4\u7F3A\u5931\u6216\u5931\u8D25\u65F6\u56DE\u9000\u6A21\u578B\u76EE\u5F55 API\u3002\u7B7E\u540D\u6C99\u7BB1\u5165\u53E3\u4E0D\u542F\u52A8\u6B64 CLI\u3002" : readiness?.ready ? `\u5B98\u65B9\u6267\u884C\u5165\u53E3\u5DF2\u6838\u9A8C\uFF0C\u53EF\u5728\u4F1A\u8BDD\u4E2D\u8C03\u7528 model_router_tool_run\uFF1B${capability?.modes?.includes("read-only") ? "\u652F\u6301\u53EA\u8BFB\u548C\u7ECF\u5BA1\u6279\u7684\u53EF\u7F16\u8F91\u4EFB\u52A1" : "\u4EC5\u652F\u6301\u7ECF\u5BA1\u6279\u7684\u53EF\u7F16\u8F91\u9694\u79BB\u5DE5\u4F5C\u533A\u4EFB\u52A1"}\uFF0C\u8D26\u53F7\u53CA\u6A21\u578B\u4ECD\u9700\u5B9E\u6D4B\u3002` : `\u5DF2\u5B89\u88C5\uFF0C\u4F46\u5F53\u524D\u4E0D\u53EF\u6258\u7BA1\u6267\u884C\uFF1A${readiness?.reason || capability?.reason || "\u6267\u884C\u5165\u53E3\u5C1A\u672A\u6838\u9A8C\u3002"}`), command ? /* @__PURE__ */ import_react5.default.createElement("code", { className: "mr-tool-command" }, command) : /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { margin: "6px 0 0" } }, tool.unsupportedReason), probe?.detail && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, probe.detail), (rowErrors[tool.id] || job?.error) && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error mr-tool-error", role: "alert" }, rowErrors[tool.id] || job.error), Array.isArray(job?.outputTail) && job.outputTail.length > 0 && /* @__PURE__ */ import_react5.default.createElement("details", { className: "mr-tool-log" }, /* @__PURE__ */ import_react5.default.createElement("summary", null, "\u5B89\u88C5\u65E5\u5FD7"), /* @__PURE__ */ import_react5.default.createElement("pre", null, job.outputTail.slice(-6).join("\n")))), command && /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-actions" }, /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-tool-button", type: "button", disabled: action.disabled, onClick: () => {
13781
+ const maintenance = toolMaintenanceActions({
13782
+ tool,
13783
+ probe,
13784
+ job,
13785
+ probeStatus: probeState.status,
13786
+ summary: { installable: !summary?.error, removable: !summary?.error }
13787
+ });
13788
+ const verified = job?.status === "succeeded" && (job.operation === "uninstall" ? job.postInstallProbe?.installed === false : job.postInstallProbe?.installed === true);
13789
+ const operation = job?.operation === "uninstall" ? "\u5378\u8F7D" : job?.operation === "repair" ? "\u4FEE\u590D" : "\u5B89\u88C5";
13790
+ const status = running ? job.cancelRequested ? `\u6B63\u5728\u53D6\u6D88${operation}\u2026` : `${operation}\u4E2D\u2026` : job?.status === "installer-opened" ? "\u5B98\u65B9\u5B89\u88C5\u5668\u5DF2\u6253\u5F00\uFF0C\u8BF7\u5B8C\u6210\u5B89\u88C5\u540E\u91CD\u65B0\u68C0\u6D4B" : job?.status === "cancelled" ? `${operation}\u5DF2\u53D6\u6D88\uFF0C\u8BF7\u91CD\u65B0\u68C0\u6D4B` : verified ? `${operation}\u6210\u529F\u5E76\u9A8C\u8BC1` : probeLabel(probe);
13791
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool", role: "listitem", key: tool.id }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-info" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-name", title: tool.purpose }, tool.label), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-provider" }, tool.vendor, " \xB7 ", tool.id), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-status", role: "status" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: `mr-tool-dot ${running ? "running" : probe?.installed ? "installed" : "missing"}` }), status, probe?.installed && probe.version ? ` \xB7 ${probe.version}` : "", healthById[tool.id]?.latestVersion ? ` \xB7 \u6700\u65B0 ${healthById[tool.id].latestVersion}` : ""), /* @__PURE__ */ import_react5.default.createElement(ToolLoginLine, { entry: healthById[tool.id] }), probe?.installed && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, tool.headlessAdapter ? "\u5DF2\u53EF\u7531 model_router_execute \u4EE5\u65E0\u754C\u9762\u65B9\u5F0F\u8C03\u7528\u3002\u547D\u4EE4\u7F3A\u5931\u6216\u5931\u8D25\u65F6\u56DE\u9000\u6A21\u578B\u76EE\u5F55 API\u3002\u7B7E\u540D\u6C99\u7BB1\u5165\u53E3\u4E0D\u542F\u52A8\u6B64 CLI\u3002" : readiness?.ready ? `\u5B98\u65B9\u6267\u884C\u5165\u53E3\u5DF2\u6838\u9A8C\uFF0C\u53EF\u5728\u4F1A\u8BDD\u4E2D\u8C03\u7528 model_router_tool_run\uFF1B${capability?.modes?.includes("read-only") ? "\u652F\u6301\u53EA\u8BFB\u548C\u7ECF\u5BA1\u6279\u7684\u53EF\u7F16\u8F91\u4EFB\u52A1" : "\u4EC5\u652F\u6301\u7ECF\u5BA1\u6279\u7684\u53EF\u7F16\u8F91\u9694\u79BB\u5DE5\u4F5C\u533A\u4EFB\u52A1"}\uFF0C\u8D26\u53F7\u53CA\u6A21\u578B\u4ECD\u9700\u5B9E\u6D4B\u3002` : `\u5DF2\u5B89\u88C5\uFF0C\u4F46\u5F53\u524D\u4E0D\u53EF\u6258\u7BA1\u6267\u884C\uFF1A${readiness?.reason || capability?.reason || "\u6267\u884C\u5165\u53E3\u5C1A\u672A\u6838\u9A8C\u3002"}`), command ? /* @__PURE__ */ import_react5.default.createElement("code", { className: "mr-tool-command" }, command) : /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { margin: "6px 0 0" } }, summary?.error ?? tool.unsupportedReason), (summary?.notices ?? []).map((notice) => /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail", key: notice }, notice)), probe?.detail && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, probe.detail), (rowErrors[tool.id] || job?.error) && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error mr-tool-error", role: "alert" }, rowErrors[tool.id] || job.error), Array.isArray(job?.outputTail) && job.outputTail.length > 0 && /* @__PURE__ */ import_react5.default.createElement("details", { className: "mr-tool-log" }, /* @__PURE__ */ import_react5.default.createElement("summary", null, "\u5B89\u88C5\u65E5\u5FD7"), /* @__PURE__ */ import_react5.default.createElement("pre", null, job.outputTail.slice(-6).join("\n")))), command && /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-actions" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-methods" }, /* @__PURE__ */ import_react5.default.createElement("label", null, /* @__PURE__ */ import_react5.default.createElement("span", null, "\u5B89\u88C5\u65B9\u5F0F"), /* @__PURE__ */ import_react5.default.createElement(
13792
+ "select",
13793
+ {
13794
+ className: "mr-mini-select",
13795
+ value: summary?.savedMethodId ?? summary?.methodId ?? "",
13796
+ disabled: running || methods.length === 0,
13797
+ onChange: (event) => {
13798
+ void setInstallMethod(tool.id, event.target.value);
13799
+ }
13800
+ },
13801
+ methods.map((method) => /* @__PURE__ */ import_react5.default.createElement("option", { key: method.id, value: method.id, disabled: Boolean(method.blocked) }, method.label, method.blocked ? `\uFF08${method.blocked}\uFF09` : ""))
13802
+ ))), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-tool-button", type: "button", disabled: action.disabled, onClick: () => {
13163
13803
  void install(tool.id);
13164
- } }, action.label), running && /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary mr-tool-button", type: "button", disabled: job.cancelRequested, onClick: () => {
13804
+ } }, action.label), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary mr-tool-button", type: "button", disabled: maintenance.repair.disabled, title: maintenance.repair.title, onClick: () => {
13805
+ void repair(tool.id);
13806
+ } }, maintenance.repair.label), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary mr-tool-button", type: "button", disabled: maintenance.uninstall.disabled, title: maintenance.uninstall.title, onClick: () => {
13807
+ void uninstall(tool.id);
13808
+ } }, maintenance.uninstall.label), running && /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary mr-tool-button", type: "button", disabled: job.cancelRequested, onClick: () => {
13165
13809
  void cancel(tool.id);
13166
- } }, job.cancelRequested ? "\u6B63\u5728\u53D6\u6D88\u2026" : "\u53D6\u6D88\u5B89\u88C5")));
13167
- })), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { marginTop: 12 } }, "\u5B89\u88C5\u7531 Host \u6309\u6CE8\u518C\u8868\u56FA\u5B9A\u6765\u6E90\u6267\u884C\uFF0C\u4E0D\u63A5\u53D7\u81EA\u5B9A\u4E49\u5305\u540D\uFF1B\u53EF\u70B9\u201C\u53D6\u6D88\u5B89\u88C5\u201D\u7EC8\u6B62\u4E0B\u8F7D\u4EFB\u52A1\uFF0C\u968F\u540E\u91CD\u65B0\u68C0\u6D4B\u5B9E\u9645\u7248\u672C\u3002ZCode \u5B89\u88C5\u5668\u542F\u52A8\u540E\u4ECD\u9700\u5728\u539F\u5382\u7A97\u53E3\u9009\u62E9\u76EE\u5F55\u5E76\u5B8C\u6210\u5B89\u88C5\u3002Agent \u4E5F\u53EF\u8C03\u7528 ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_install"), "\uFF0C\u6216\u5728\u4F1A\u8BDD\u4F7F\u7528 ", /* @__PURE__ */ import_react5.default.createElement("code", null, "/tools"), "\u3002")));
13810
+ } }, job.cancelRequested ? "\u6B63\u5728\u53D6\u6D88\u2026" : `\u53D6\u6D88${operation}`)));
13811
+ })), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { marginTop: 12 } }, "\u5B89\u88C5\u7531 Host \u6309\u6CE8\u518C\u8868\u56FA\u5B9A\u6765\u6E90\u6267\u884C\uFF0C\u4E0D\u63A5\u53D7\u81EA\u5B9A\u4E49\u5305\u540D\uFF1B\u5B89\u88C5\u76EE\u5F55\u4E0E\u4E0B\u8F7D\u6E90\u6765\u81EA\u4F60\u81EA\u5DF1\u586B\u5199\u7684\u8BBE\u7F6E\uFF0C\u547D\u4EE4\u4E0A\u65B9\u5B9E\u65F6\u663E\u793A\u5C06\u8981\u6267\u884C\u7684\u5185\u5BB9\u3002\u53EF\u70B9\u201C\u53D6\u6D88\u201D\u7EC8\u6B62\u4E0B\u8F7D\u4EFB\u52A1\uFF0C\u968F\u540E\u91CD\u65B0\u68C0\u6D4B\u5B9E\u9645\u7248\u672C\u3002\u4E00\u952E\u5378\u8F7D\u53EA\u5220\u9664\u7A0B\u5E8F\u672C\u8EAB\uFF08\u811A\u672C\u5B89\u88C5\u4F1A\u987A\u5E26\u6E05\u6389\u5B83\u5728 shell \u914D\u7F6E\u91CC\u5199\u7684 PATH \u8BB0\u5F55\uFF09\uFF0C\u914D\u7F6E\u3001\u767B\u5F55\u4FE1\u606F\u548C\u5386\u53F2\u8BB0\u5F55\u4FDD\u7559\u3002ZCode \u5B89\u88C5\u5668\u542F\u52A8\u540E\u4ECD\u9700\u5728\u539F\u5382\u7A97\u53E3\u9009\u62E9\u76EE\u5F55\u5E76\u5B8C\u6210\u5B89\u88C5\u3002Agent \u4E5F\u53EF\u8C03\u7528 ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_install"), " / ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_repair"), " / ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_uninstall"), "\uFF0C\u6216\u5728\u4F1A\u8BDD\u4F7F\u7528 ", /* @__PURE__ */ import_react5.default.createElement("code", null, "/tools"), "\u3002")));
13168
13812
  }
13169
13813
  var HEADLESS_TOOLS = /* @__PURE__ */ new Set(["claude-code", "codex", "gemini"]);
13170
13814
  var WORKSPACE_VIEWS = [
@@ -13291,7 +13935,7 @@ ${reasons.map((entry, index) => `${index + 1}. ${entry.text}`).join("\n")}
13291
13935
  }, []);
13292
13936
  return { health, ledger, boundaries, busy, refreshHealth, refreshLedger, refreshBoundaries, finishOnboarding, rate, rerun };
13293
13937
  }
13294
- function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, installOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
13938
+ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
13295
13939
  const workbench = useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries });
13296
13940
  const [catalogState, setCatalogState] = import_react5.default.useState({ status: "loading", catalog: null, error: "" });
13297
13941
  const [task, setTask] = import_react5.default.useState("");
@@ -13514,9 +14158,12 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, install
13514
14158
  {
13515
14159
  listOfficialTools,
13516
14160
  installOfficialTool,
14161
+ uninstallOfficialTool,
14162
+ repairOfficialTool,
13517
14163
  cancelOfficialToolInstall,
13518
14164
  officialToolInstallStatus,
13519
14165
  onProbes: handleToolProbes,
14166
+ settingsScope,
13520
14167
  health: workbench.health.report,
13521
14168
  onRefreshHealth: () => {
13522
14169
  void workbench.refreshHealth(true);
@@ -13567,6 +14214,26 @@ var toolIdCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#OfficialToolId`,
13567
14214
  }
13568
14215
  return value;
13569
14216
  });
14217
+ var MAX_METHOD_ID_CHARS = 40;
14218
+ var text6 = (value) => typeof value === "string" ? value.trim() : "";
14219
+ var toolActionCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#OfficialToolAction`, (value) => {
14220
+ const request = plainObject2(value, "tool action request");
14221
+ for (const key of Object.keys(request)) {
14222
+ if (key !== "tool" && key !== "method") throw new TypeError(`unknown field ${key} in tool action request`);
14223
+ }
14224
+ const tool = getOfficialTool(text6(request.tool));
14225
+ if (!tool) throw new TypeError("tool must name a fixed official tool");
14226
+ const method = request.method === void 0 || request.method === null || request.method === "" ? null : text6(request.method);
14227
+ if (method !== null) {
14228
+ if (method.length > MAX_METHOD_ID_CHARS || !/^[A-Za-z0-9-]+$/.test(method)) {
14229
+ throw new TypeError("method must be one of the tool's declared install methods");
14230
+ }
14231
+ if (!installMethodsFor(tool).some((item) => item.id === method)) {
14232
+ throw new TypeError(`${tool.label} does not offer the install method ${method}`);
14233
+ }
14234
+ }
14235
+ return method === null ? { tool: tool.id } : { tool: tool.id, method };
14236
+ });
13570
14237
  var listResultCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#OfficialToolList`, (value) => {
13571
14238
  const result = plainObject2(value, "tool list result");
13572
14239
  if (!Array.isArray(result.tools)) throw new TypeError("tool list result needs a tools array");
@@ -13680,7 +14347,9 @@ var toolIdParameter = Object.freeze({
13680
14347
  var jsonParameter = (name, codec) => Object.freeze({ name, wire: name, source: "json", codec });
13681
14348
  var OFFICIAL_TOOLS_REMOTE_DESCRIPTORS = Object.freeze([
13682
14349
  descriptor("list", [], listResultCodec),
13683
- descriptor("installTool", [toolIdParameter], installResultCodec),
14350
+ descriptor("installTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
14351
+ descriptor("uninstallTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
14352
+ descriptor("repairTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
13684
14353
  descriptor("cancel", [toolIdParameter], installResultCodec),
13685
14354
  descriptor("status", [toolIdParameter], statusResultCodec),
13686
14355
  // Workbench: onboarding health check, run ledger, ratings, step retry, security boundaries.
@@ -13744,8 +14413,8 @@ function boundedNumberField(field2, { minimum = 0, maximum = Number.MAX_SAFE_INT
13744
14413
  const numeric = (0, import_dsh_client_ui_primitives.settingsNumberField)(field2);
13745
14414
  return {
13746
14415
  ...numeric,
13747
- parse: (text6) => {
13748
- const write = numeric.parse(text6);
14416
+ parse: (text7) => {
14417
+ const write = numeric.parse(text7);
13749
14418
  if (write?.kind !== "set") return write;
13750
14419
  const value = write.value;
13751
14420
  if (typeof value !== "number" || value < minimum || value > maximum) return void 0;
@@ -13758,10 +14427,10 @@ function modelProfilesField() {
13758
14427
  const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("modelProfilesJson");
13759
14428
  return {
13760
14429
  ...field2,
13761
- parse: (text6) => {
14430
+ parse: (text7) => {
13762
14431
  try {
13763
- parseModelProfilesJson(text6);
13764
- return field2.parse(text6);
14432
+ parseModelProfilesJson(text7);
14433
+ return field2.parse(text7);
13765
14434
  } catch {
13766
14435
  return void 0;
13767
14436
  }
@@ -13772,15 +14441,79 @@ function quotaPatternsField() {
13772
14441
  const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("quotaPatternsJson");
13773
14442
  return {
13774
14443
  ...field2,
13775
- parse: (text6) => {
13776
- const raw = String(text6 ?? "").trim() || "{}";
14444
+ parse: (text7) => {
14445
+ const raw = String(text7 ?? "").trim() || "{}";
13777
14446
  try {
13778
14447
  const value = JSON.parse(raw);
13779
14448
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
13780
14449
  } catch {
13781
14450
  return void 0;
13782
14451
  }
13783
- return parseQuotaPatterns(raw).errors.length ? void 0 : field2.parse(text6);
14452
+ return parseQuotaPatterns(raw).errors.length ? void 0 : field2.parse(text7);
14453
+ }
14454
+ };
14455
+ }
14456
+ function installDirField() {
14457
+ const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("toolInstallDir");
14458
+ return {
14459
+ ...field2,
14460
+ parse: (text7) => {
14461
+ if (!String(text7 ?? "").trim()) return field2.parse(text7);
14462
+ try {
14463
+ expandInstallDir(text7);
14464
+ } catch {
14465
+ return void 0;
14466
+ }
14467
+ return field2.parse(text7);
14468
+ }
14469
+ };
14470
+ }
14471
+ function npmRegistryField() {
14472
+ const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("toolNpmRegistry");
14473
+ return {
14474
+ ...field2,
14475
+ parse: (text7) => {
14476
+ const raw = String(text7 ?? "").trim() || DEFAULT_NPM_REGISTRY;
14477
+ try {
14478
+ normalizeSourceUrl(raw, "npm \u6E90");
14479
+ } catch {
14480
+ return void 0;
14481
+ }
14482
+ return field2.parse(text7);
14483
+ }
14484
+ };
14485
+ }
14486
+ function scriptUrlsField() {
14487
+ const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("toolScriptUrlsJson");
14488
+ return {
14489
+ ...field2,
14490
+ parse: (text7) => {
14491
+ const raw = String(text7 ?? "").trim() || "{}";
14492
+ try {
14493
+ const parsed = JSON.parse(raw);
14494
+ parseInstallPreferences({ toolScriptUrlsJson: raw }, { tools: OFFICIAL_TOOLS });
14495
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
14496
+ } catch {
14497
+ return void 0;
14498
+ }
14499
+ return field2.parse(text7);
14500
+ }
14501
+ };
14502
+ }
14503
+ function installMethodsField() {
14504
+ const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("toolInstallMethodsJson");
14505
+ return {
14506
+ ...field2,
14507
+ parse: (text7) => {
14508
+ const raw = String(text7 ?? "").trim() || "{}";
14509
+ try {
14510
+ const parsed = JSON.parse(raw);
14511
+ parseInstallPreferences({ toolInstallMethodsJson: raw }, { tools: OFFICIAL_TOOLS });
14512
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
14513
+ } catch {
14514
+ return void 0;
14515
+ }
14516
+ return field2.parse(text7);
13784
14517
  }
13785
14518
  };
13786
14519
  }
@@ -13794,7 +14527,11 @@ var RouterSettingsCardController = class {
13794
14527
  boundedNumberField("reviewSampleRate", { minimum: 0, maximum: 1 }),
13795
14528
  boundedNumberField("subscriptionCooldownMinutes", { minimum: 1, maximum: 10080, integer: true }),
13796
14529
  modelProfilesField(),
13797
- quotaPatternsField()
14530
+ quotaPatternsField(),
14531
+ installDirField(),
14532
+ npmRegistryField(),
14533
+ installMethodsField(),
14534
+ scriptUrlsField()
13798
14535
  ]);
13799
14536
  this.store = this.form.bind(() => ({
13800
14537
  ...this.form.shell(),
@@ -13805,7 +14542,11 @@ var RouterSettingsCardController = class {
13805
14542
  reviewSampleRate: this.form.field("reviewSampleRate"),
13806
14543
  subscriptionCooldownMinutes: this.form.field("subscriptionCooldownMinutes"),
13807
14544
  modelProfilesJson: this.form.field("modelProfilesJson"),
13808
- quotaPatternsJson: this.form.field("quotaPatternsJson")
14545
+ quotaPatternsJson: this.form.field("quotaPatternsJson"),
14546
+ toolInstallDir: this.form.field("toolInstallDir"),
14547
+ toolNpmRegistry: this.form.field("toolNpmRegistry"),
14548
+ toolInstallMethodsJson: this.form.field("toolInstallMethodsJson"),
14549
+ toolScriptUrlsJson: this.form.field("toolScriptUrlsJson")
13809
14550
  }));
13810
14551
  }
13811
14552
  /** Supply the snapshot hook and staged form actions to the Plugins slot. */
@@ -13838,8 +14579,8 @@ function RouterSettingsCard(props) {
13838
14579
  overriddenLabel: FIELD_COPY.overridden,
13839
14580
  resetLabel: FIELD_COPY.reset,
13840
14581
  invalidLabel: FIELD_COPY.invalidNumber,
13841
- onEdit: (text6) => {
13842
- props.edit("budgetUsd", text6);
14582
+ onEdit: (text7) => {
14583
+ props.edit("budgetUsd", text7);
13843
14584
  },
13844
14585
  onReset: () => {
13845
14586
  props.resetField("budgetUsd");
@@ -13857,8 +14598,8 @@ function RouterSettingsCard(props) {
13857
14598
  overriddenLabel: FIELD_COPY.overridden,
13858
14599
  resetLabel: FIELD_COPY.reset,
13859
14600
  invalidLabel: FIELD_COPY.invalidNumber,
13860
- onEdit: (text6) => {
13861
- props.edit("maxConsultOutputChars", text6);
14601
+ onEdit: (text7) => {
14602
+ props.edit("maxConsultOutputChars", text7);
13862
14603
  },
13863
14604
  onReset: () => {
13864
14605
  props.resetField("maxConsultOutputChars");
@@ -13881,8 +14622,8 @@ function RouterSettingsCard(props) {
13881
14622
  overriddenLabel: FIELD_COPY.overridden,
13882
14623
  resetLabel: FIELD_COPY.reset,
13883
14624
  invalidLabel: FIELD_COPY.invalidNumber,
13884
- onEdit: (text6) => {
13885
- props.edit(key, text6);
14625
+ onEdit: (text7) => {
14626
+ props.edit(key, text7);
13886
14627
  },
13887
14628
  onReset: () => {
13888
14629
  props.resetField(key);
@@ -13929,7 +14670,82 @@ function RouterSettingsCard(props) {
13929
14670
  ), state.quotaPatternsJson.invalid && /* @__PURE__ */ import_react6.default.createElement("p", { style: styles.profileError, role: "alert" }, "\u9700\u8981 JSON \u5BF9\u8C61\uFF0C\u4E14\u6BCF\u6761\u89C4\u5219\u90FD\u662F\u6709\u6548\u7684\u6B63\u5219\u8868\u8FBE\u5F0F\u3002"), /* @__PURE__ */ import_react6.default.createElement("details", { style: styles.profileExample }, /* @__PURE__ */ import_react6.default.createElement("summary", null, "\u67E5\u770B\u89C4\u5219\u683C\u5F0F"), /* @__PURE__ */ import_react6.default.createElement("pre", null, `{
13930
14671
  "kimi-code": { "quota": ["\u989D\u5EA6\u5DF2\u7528\u5B8C"], "rateLimit": ["\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41"] },
13931
14672
  "my-glm-plan": { "quota": ["Usage limit reached for", "1308"] }
13932
- }`)), /* @__PURE__ */ import_react6.default.createElement("button", { type: "button", disabled, onClick: () => props.resetField("quotaPatternsJson") }, "\u6062\u590D\u9ED8\u8BA4\u89C4\u5219")), /* @__PURE__ */ import_react6.default.createElement("aside", { style: styles.notice, "aria-label": "\u6A21\u578B\u8DEF\u7531\u4F7F\u7528\u8BF4\u660E" }, /* @__PURE__ */ import_react6.default.createElement("div", { style: styles.titleLine }, /* @__PURE__ */ import_react6.default.createElement(import_dsh_client_ui_primitives.Tag, { tone: "info" }, "\u5B98\u65B9\u6A21\u578B\u914D\u7F6E")), /* @__PURE__ */ import_react6.default.createElement("p", { style: styles.noticeText }, "\u8BF7\u5728 DeepSeek Harness \u7684\u201C\u6A21\u578B\u201D\u9875\u9762\u914D\u7F6E DeepSeek\u3001OpenAI \u517C\u5BB9\u6216 Anthropic \u517C\u5BB9\u670D\u52A1\u3002\u6B64\u63D2\u4EF6\u8BFB\u53D6\u5B98\u65B9\u6A21\u578B\u76EE\u5F55\uFF0C\u4E0D\u4FDD\u5B58 API Key\uFF1B\u76EE\u5F55\u4E2D\u7684\u8DEF\u7EBF\u4ECD\u9700\u901A\u8FC7\u5B9E\u9645\u8C03\u7528\u9A8C\u8BC1\u8D26\u53F7\u548C\u7F51\u7EDC\u53EF\u7528\u6027\u3002"), /* @__PURE__ */ import_react6.default.createElement("p", { style: styles.noticeText }, "\u4F7F\u7528 ", /* @__PURE__ */ import_react6.default.createElement("code", null, "model_router_plan"), " \u83B7\u53D6\u53EF\u5BA1\u9605\u7684\u8DEF\u7531\u5EFA\u8BAE\uFF0C\u4F7F\u7528 ", /* @__PURE__ */ import_react6.default.createElement("code", null, "model_router_consult"), " \u54A8\u8BE2\u4E00\u4E2A\u5DF2\u914D\u7F6E\u6A21\u578B\u3002\u5EFA\u8BAE\u4E0D\u4F1A\u6539\u5199\u4E3B\u4F1A\u8BDD\u6A21\u578B\uFF1B\u591A\u4EBA\u5206\u5DE5\u7531\u5B98\u65B9 Agent Teams \u5DE5\u5177\u6267\u884C\u3002")));
14673
+ }`)), /* @__PURE__ */ import_react6.default.createElement("button", { type: "button", disabled, onClick: () => props.resetField("quotaPatternsJson") }, "\u6062\u590D\u9ED8\u8BA4\u89C4\u5219")), /* @__PURE__ */ import_react6.default.createElement("div", { style: styles.profileEditor }, /* @__PURE__ */ import_react6.default.createElement("span", { style: styles.profileLabel }, "\u5B98\u65B9\u5DE5\u5177\u7EDF\u4E00\u5B89\u88C5\u8BBE\u7F6E"), /* @__PURE__ */ import_react6.default.createElement("p", { style: styles.noticeText }, "\u5BF9\u6240\u6709\u5B98\u65B9\u5DE5\u5177\u7684\u4E00\u952E\u5B89\u88C5\u3001\u4FEE\u590D\u548C\u5378\u8F7D\u751F\u6548\u3002\u5DE5\u4F5C\u53F0\u7684\u201C\u5B98\u65B9\u5DE5\u5177\u201D\u9875\u4E5F\u80FD\u6539\u540C\u6837\u7684\u5B57\u6BB5\uFF0C\u5E76\u4F1A\u5B9E\u65F6\u663E\u793A\u6BCF\u4E2A\u5DE5\u5177\u5C06\u8981\u6267\u884C\u7684\u547D\u4EE4\u3002 \u5B89\u88C5\u547D\u4EE4\u59CB\u7EC8\u6765\u81EA\u56FA\u5B9A\u6CE8\u518C\u8868\uFF0C\u8FD9\u91CC\u53EA\u51B3\u5B9A\u76EE\u5F55\u4E0E\u4E0B\u8F7D\u6E90\uFF0C\u4E0D\u63A5\u53D7\u81EA\u5B9A\u4E49\u5305\u540D\u6216\u4EFB\u610F\u5730\u5740\u3002"), /* @__PURE__ */ import_react6.default.createElement(
14674
+ import_dsh_client_ui_primitives.SettingsValueField,
14675
+ {
14676
+ id: "model-router-tool-install-dir",
14677
+ label: "\u7EDF\u4E00\u5B89\u88C5\u76EE\u5F55",
14678
+ hint: "\u7559\u7A7A\u8868\u793A\u4F7F\u7528\u5404\u5382\u5546\u9ED8\u8BA4\u4F4D\u7F6E\u3002npm \u4E0E pnpm \u4F1A\u5E26\u4E0A --prefix\uFF1B\u652F\u6301\u76EE\u5F55\u7684\u5B98\u65B9\u811A\u672C\u4F1A\u628A\u8FD9\u4E2A\u8DEF\u5F84\u4F20\u7ED9\u5382\u5546\u81EA\u5DF1\u7684\u5B89\u88C5\u76EE\u5F55\u53D8\u91CF\uFF08\u4F8B\u5982 MCODE_INSTALL_DIR\uFF09\u3002\u5199\u6B7B\u8DEF\u5F84\u7684\u811A\u672C\u4F1A\u5728\u5DE5\u5177\u884C\u6807\u660E\u5B9E\u9645\u843D\u70B9\u3002\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u3002\u66F4\u65B0\u4F7F\u7528\u540C\u4E00\u8DEF\u5F84\u3002",
14679
+ disabled,
14680
+ ...state.toolInstallDir,
14681
+ overriddenLabel: FIELD_COPY.overridden,
14682
+ resetLabel: FIELD_COPY.reset,
14683
+ invalidLabel: "\u8BF7\u8F93\u5165\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u6216\u7559\u7A7A\u6062\u590D\u9ED8\u8BA4\u3002",
14684
+ onEdit: (value) => {
14685
+ props.edit("toolInstallDir", value);
14686
+ },
14687
+ onReset: () => {
14688
+ props.resetField("toolInstallDir");
14689
+ }
14690
+ }
14691
+ ), /* @__PURE__ */ import_react6.default.createElement(
14692
+ import_dsh_client_ui_primitives.SettingsValueField,
14693
+ {
14694
+ id: "model-router-tool-npm-registry",
14695
+ label: "npm \u6E90\u5730\u5740",
14696
+ hint: "\u6240\u6709\u5305\u7BA1\u7406\u5668\u5B89\u88C5\u65B9\u5F0F\u4ECE\u8BE5\u5730\u5740\u4E0B\u8F7D\uFF0C\u5FC5\u987B\u662F https \u94FE\u63A5\u3002\u56FD\u5185\u7F51\u7EDC\u53EF\u586B\u955C\u50CF\u6E90\uFF0C\u4F8B\u5982 https://registry.npmmirror.com/\u3002",
14697
+ disabled,
14698
+ ...state.toolNpmRegistry,
14699
+ overriddenLabel: FIELD_COPY.overridden,
14700
+ resetLabel: FIELD_COPY.reset,
14701
+ invalidLabel: "\u9700\u8981 https \u5F00\u5934\u7684\u5B8C\u6574\u5730\u5740\u3002",
14702
+ onEdit: (value) => {
14703
+ props.edit("toolNpmRegistry", value);
14704
+ },
14705
+ onReset: () => {
14706
+ props.resetField("toolNpmRegistry");
14707
+ }
14708
+ }
14709
+ ), /* @__PURE__ */ import_react6.default.createElement(
14710
+ import_dsh_client_ui_primitives.SettingsValueField,
14711
+ {
14712
+ id: "model-router-tool-install-methods",
14713
+ label: "\u9ED8\u8BA4\u5B89\u88C5\u65B9\u5F0F\uFF08JSON\uFF0C\u53EF\u9009\uFF09",
14714
+ hint: "\u6309\u5DE5\u5177 ID \u56FA\u5B9A\u4E00\u79CD\u5B89\u88C5\u65B9\u5F0F\uFF0C\u4F8B\u5982\u56FA\u5B9A Step Code \u5728 Windows \u4E0A\u7528 irm | iex\u3002\u7559\u7A7A\u8868\u793A\u4F7F\u7528\u8BE5\u5DE5\u5177\u7684\u9ED8\u8BA4\u65B9\u5F0F\u3002\u53EF\u9009\u503C\uFF1Anpm\u3001pnpm\u3001script-bash\uFF08curl | bash\uFF09\u3001script-powershell\uFF08irm | iex\uFF09\u3002",
14715
+ disabled,
14716
+ ...state.toolInstallMethodsJson,
14717
+ overriddenLabel: FIELD_COPY.overridden,
14718
+ resetLabel: FIELD_COPY.reset,
14719
+ invalidLabel: "\u9700\u8981 JSON \u5BF9\u8C61\uFF0C\u4E14\u6BCF\u4E2A\u952E\u90FD\u662F\u5B98\u65B9\u5DE5\u5177 ID\u3001\u6BCF\u4E2A\u503C\u90FD\u662F\u8BE5\u5DE5\u5177\u652F\u6301\u7684\u5B89\u88C5\u65B9\u5F0F\u3002",
14720
+ onEdit: (value) => {
14721
+ props.edit("toolInstallMethodsJson", value);
14722
+ },
14723
+ onReset: () => {
14724
+ props.resetField("toolInstallMethodsJson");
14725
+ }
14726
+ }
14727
+ ), /* @__PURE__ */ import_react6.default.createElement(
14728
+ import_dsh_client_ui_primitives.SettingsValueField,
14729
+ {
14730
+ id: "model-router-tool-script-urls",
14731
+ label: "\u5B89\u88C5\u811A\u672C\u6E90\u8986\u76D6\uFF08JSON\uFF0C\u53EF\u9009\uFF09",
14732
+ hint: "\u6309\u5DE5\u5177 ID \u6307\u5B9A\u5382\u5546\u5B89\u88C5\u811A\u672C\u7684\u955C\u50CF\u5730\u5740\uFF08\u5FC5\u987B\u662F https\uFF09\u3002\u63D2\u4EF6\u4F1A\u5148\u4E0B\u8F7D\u811A\u672C\u3001\u6838\u5BF9\u5B83\u4ECD\u7136\u662F\u8BE5\u5382\u5546\u7684\u5B98\u65B9\u5B89\u88C5\u811A\u672C\uFF0C\u518D\u6267\u884C\u672C\u5730\u526F\u672C\uFF1B\u955C\u50CF\u4E0D\u80FD\u66FF\u6362\u6210\u522B\u7684\u7A0B\u5E8F\u3002",
14733
+ disabled,
14734
+ ...state.toolScriptUrlsJson,
14735
+ overriddenLabel: FIELD_COPY.overridden,
14736
+ resetLabel: FIELD_COPY.reset,
14737
+ invalidLabel: "\u9700\u8981 JSON \u5BF9\u8C61\uFF0C\u952E\u662F\u5B98\u65B9\u5DE5\u5177 ID\uFF0C\u503C\u662F https \u94FE\u63A5\u3002",
14738
+ onEdit: (value) => {
14739
+ props.edit("toolScriptUrlsJson", value);
14740
+ },
14741
+ onReset: () => {
14742
+ props.resetField("toolScriptUrlsJson");
14743
+ }
14744
+ }
14745
+ ), /* @__PURE__ */ import_react6.default.createElement("details", { style: styles.profileExample }, /* @__PURE__ */ import_react6.default.createElement("summary", null, "\u67E5\u770B\u683C\u5F0F"), /* @__PURE__ */ import_react6.default.createElement("pre", null, `{
14746
+ "stepcode": "https://mirror.example.com/stepcode/install.ps1",
14747
+ "opencode": "https://mirror.example.com/opencode/install"
14748
+ }`), /* @__PURE__ */ import_react6.default.createElement("p", { style: styles.noticeText }, "\u5B89\u88C5\u811A\u672C\u65B9\u5F0F\uFF08curl / irm\uFF09\u53EF\u4EE5\u5728\u5DE5\u4F5C\u53F0\u7684\u201C\u5B98\u65B9\u5DE5\u5177\u201D\u9875\u5173\u95ED\uFF1B\u5173\u95ED\u540E\u6240\u6709\u5DE5\u5177\u53EA\u80FD\u901A\u8FC7 npm \u6216 pnpm \u5B89\u88C5\u3002"))), /* @__PURE__ */ import_react6.default.createElement("aside", { style: styles.notice, "aria-label": "\u6A21\u578B\u8DEF\u7531\u4F7F\u7528\u8BF4\u660E" }, /* @__PURE__ */ import_react6.default.createElement("div", { style: styles.titleLine }, /* @__PURE__ */ import_react6.default.createElement(import_dsh_client_ui_primitives.Tag, { tone: "info" }, "\u5B98\u65B9\u6A21\u578B\u914D\u7F6E")), /* @__PURE__ */ import_react6.default.createElement("p", { style: styles.noticeText }, "\u8BF7\u5728 DeepSeek Harness \u7684\u201C\u6A21\u578B\u201D\u9875\u9762\u914D\u7F6E DeepSeek\u3001OpenAI \u517C\u5BB9\u6216 Anthropic \u517C\u5BB9\u670D\u52A1\u3002\u6B64\u63D2\u4EF6\u8BFB\u53D6\u5B98\u65B9\u6A21\u578B\u76EE\u5F55\uFF0C\u4E0D\u4FDD\u5B58 API Key\uFF1B\u76EE\u5F55\u4E2D\u7684\u8DEF\u7EBF\u4ECD\u9700\u901A\u8FC7\u5B9E\u9645\u8C03\u7528\u9A8C\u8BC1\u8D26\u53F7\u548C\u7F51\u7EDC\u53EF\u7528\u6027\u3002"), /* @__PURE__ */ import_react6.default.createElement("p", { style: styles.noticeText }, "\u4F7F\u7528 ", /* @__PURE__ */ import_react6.default.createElement("code", null, "model_router_plan"), " \u83B7\u53D6\u53EF\u5BA1\u9605\u7684\u8DEF\u7531\u5EFA\u8BAE\uFF0C\u4F7F\u7528 ", /* @__PURE__ */ import_react6.default.createElement("code", null, "model_router_consult"), " \u54A8\u8BE2\u4E00\u4E2A\u5DF2\u914D\u7F6E\u6A21\u578B\u3002\u5EFA\u8BAE\u4E0D\u4F1A\u6539\u5199\u4E3B\u4F1A\u8BDD\u6A21\u578B\uFF1B\u591A\u4EBA\u5206\u5DE5\u7531\u5B98\u65B9 Agent Teams \u5DE5\u5177\u6267\u884C\u3002")));
13933
14749
  }
13934
14750
  function OpenRouterWorkspace({ subject, openPanel }) {
13935
14751
  if (subject?.kind !== "bundle" || subject.pkg?.name !== ROUTER_PACKAGE) return null;
@@ -13954,7 +14770,9 @@ function registerUi(ctx) {
13954
14770
  loadCatalog: () => ctx.remote.session.modelCatalog(),
13955
14771
  settingsScope,
13956
14772
  listOfficialTools: () => officialToolsRemote.list(),
13957
- installOfficialTool: (toolId) => officialToolsRemote.installTool(toolId),
14773
+ installOfficialTool: (request) => officialToolsRemote.installTool(request),
14774
+ uninstallOfficialTool: (request) => officialToolsRemote.uninstallTool(request),
14775
+ repairOfficialTool: (request) => officialToolsRemote.repairTool(request),
13958
14776
  cancelOfficialToolInstall: (toolId) => officialToolsRemote.cancel(toolId),
13959
14777
  officialToolInstallStatus: (toolId) => officialToolsRemote.status(toolId),
13960
14778
  toolHealth: (fresh) => officialToolsRemote.health(fresh),