@ljwei-stak/dsh-model-router 0.16.4 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,6 +46,40 @@ 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, { shell, scriptUrl, verify, installArg = [], env = [], platforms, supportsInstallDir = false }) {
66
+ return Object.freeze({
67
+ id: id2,
68
+ label,
69
+ kind: "script",
70
+ shell,
71
+ scriptUrl,
72
+ verify,
73
+ /** Passed after the staged script path; empty means the script takes no argument. */
74
+ installArgs: Object.freeze(installArg),
75
+ /** Environment the vendor script itself documents for this install. */
76
+ env: Object.freeze(env),
77
+ platforms: Object.freeze(platforms),
78
+ /** Whether the vendor script honours a caller-chosen directory at all. */
79
+ supportsInstallDir,
80
+ supportsRegistry: false
81
+ });
82
+ }
49
83
  var OFFICIAL_TOOLS = Object.freeze([
50
84
  Object.freeze({
51
85
  id: "kimi-code",
@@ -55,9 +89,14 @@ var OFFICIAL_TOOLS = Object.freeze([
55
89
  package: "@moonshot-ai/kimi-code",
56
90
  manager: "npm",
57
91
  installArgs: ["install", "-g", "@moonshot-ai/kimi-code@latest", "--registry=https://registry.npmjs.org/"],
92
+ uninstallArgs: ["uninstall", "-g", "@moonshot-ai/kimi-code"],
58
93
  probeExecutables: ["kimi"],
59
94
  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"]
95
+ providerHints: ["moonshot", "kimi"],
96
+ installMethods: Object.freeze([
97
+ packageManagerMethod("npm", "npm", "npm", "@moonshot-ai/kimi-code@latest"),
98
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@moonshot-ai/kimi-code@latest")
99
+ ])
61
100
  }),
62
101
  Object.freeze({
63
102
  id: "claude-code",
@@ -67,8 +106,13 @@ var OFFICIAL_TOOLS = Object.freeze([
67
106
  package: "@anthropic-ai/claude-code",
68
107
  manager: "npm",
69
108
  installArgs: ["install", "-g", "@anthropic-ai/claude-code@latest", "--registry=https://registry.npmjs.org/"],
109
+ uninstallArgs: ["uninstall", "-g", "@anthropic-ai/claude-code"],
70
110
  probeExecutables: ["claude"],
71
- providerHints: ["anthropic", "claude"]
111
+ providerHints: ["anthropic", "claude"],
112
+ installMethods: Object.freeze([
113
+ packageManagerMethod("npm", "npm", "npm", "@anthropic-ai/claude-code@latest"),
114
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@anthropic-ai/claude-code@latest")
115
+ ])
72
116
  }),
73
117
  Object.freeze({
74
118
  id: "codex",
@@ -76,11 +120,16 @@ var OFFICIAL_TOOLS = Object.freeze([
76
120
  vendor: "OpenAI",
77
121
  purpose: "OpenAI \u5B98\u65B9\u7F16\u7A0B CLI\uFF0C\u63D0\u4F9B codex \u547D\u4EE4\u3002",
78
122
  package: "@openai/codex",
79
- installArgs: ["install", "-g", "@openai/codex@latest", "--registry=https://registry.npmjs.org/"],
80
123
  manager: "npm",
124
+ installArgs: ["install", "-g", "@openai/codex@latest", "--registry=https://registry.npmjs.org/"],
125
+ uninstallArgs: ["uninstall", "-g", "@openai/codex"],
81
126
  probeExecutables: ["codex"],
82
127
  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"]
128
+ providerHints: ["openai", "gpt", "codex"],
129
+ installMethods: Object.freeze([
130
+ packageManagerMethod("npm", "npm", "npm", "@openai/codex@latest"),
131
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@openai/codex@latest")
132
+ ])
84
133
  }),
85
134
  Object.freeze({
86
135
  id: "minimax-code",
@@ -90,8 +139,13 @@ var OFFICIAL_TOOLS = Object.freeze([
90
139
  package: "@minimax-ai/code",
91
140
  manager: "npm",
92
141
  installArgs: ["install", "-g", "@minimax-ai/code@latest", "--registry=https://registry.npmjs.org/", "--ignore-scripts=false", "--include=optional", "--allow-scripts=@minimax-ai/code,better-sqlite3"],
142
+ uninstallArgs: ["uninstall", "-g", "@minimax-ai/code"],
93
143
  probeExecutables: ["mcode"],
94
- providerHints: ["minimax"]
144
+ providerHints: ["minimax"],
145
+ installMethods: Object.freeze([
146
+ packageManagerMethod("npm", "npm", "npm", "@minimax-ai/code@latest"),
147
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@minimax-ai/code@latest")
148
+ ])
95
149
  }),
96
150
  Object.freeze({
97
151
  id: "mimo-code",
@@ -101,8 +155,13 @@ var OFFICIAL_TOOLS = Object.freeze([
101
155
  package: "@mimo-ai/cli",
102
156
  manager: "npm",
103
157
  installArgs: ["install", "-g", "@mimo-ai/cli@latest", "--registry=https://registry.npmjs.org/"],
158
+ uninstallArgs: ["uninstall", "-g", "@mimo-ai/cli"],
104
159
  probeExecutables: ["mimo"],
105
- providerHints: ["mimo", "xiaomi"]
160
+ providerHints: ["mimo", "xiaomi"],
161
+ installMethods: Object.freeze([
162
+ packageManagerMethod("npm", "npm", "npm", "@mimo-ai/cli@latest"),
163
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@mimo-ai/cli@latest")
164
+ ])
106
165
  }),
107
166
  Object.freeze({
108
167
  id: "grok-build",
@@ -112,8 +171,13 @@ var OFFICIAL_TOOLS = Object.freeze([
112
171
  package: "@xai-official/grok",
113
172
  manager: "npm",
114
173
  installArgs: ["install", "-g", "@xai-official/grok@latest", "--registry=https://registry.npmjs.org/"],
174
+ uninstallArgs: ["uninstall", "-g", "@xai-official/grok"],
115
175
  probeExecutables: ["grok"],
116
- providerHints: ["xai", "grok"]
176
+ providerHints: ["xai", "grok"],
177
+ installMethods: Object.freeze([
178
+ packageManagerMethod("npm", "npm", "npm", "@xai-official/grok@latest"),
179
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@xai-official/grok@latest")
180
+ ])
117
181
  }),
118
182
  Object.freeze({
119
183
  id: "gemini",
@@ -123,11 +187,93 @@ var OFFICIAL_TOOLS = Object.freeze([
123
187
  package: "@google/gemini-cli",
124
188
  manager: "npm",
125
189
  installArgs: ["install", "-g", "@google/gemini-cli@latest", "--registry=https://registry.npmjs.org/"],
190
+ uninstallArgs: ["uninstall", "-g", "@google/gemini-cli"],
126
191
  probeExecutables: ["gemini"],
127
192
  providerHints: ["gemini", "google"],
128
193
  // Headless runs go through the task adapter. The signed sandbox runner does
129
194
  // not launch this CLI; a missing or failed process falls back to the API.
130
- headlessAdapter: true
195
+ headlessAdapter: true,
196
+ installMethods: Object.freeze([
197
+ packageManagerMethod("npm", "npm", "npm", "@google/gemini-cli@latest"),
198
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@google/gemini-cli@latest")
199
+ ])
200
+ }),
201
+ Object.freeze({
202
+ id: "opencode",
203
+ label: "OpenCode",
204
+ vendor: "anomalyco / SST",
205
+ 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",
206
+ package: "opencode-ai",
207
+ manager: "npm",
208
+ installArgs: ["install", "-g", "opencode-ai@latest", "--registry=https://registry.npmjs.org/"],
209
+ uninstallArgs: ["uninstall", "-g", "opencode-ai"],
210
+ probeExecutables: ["opencode"],
211
+ 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",
212
+ providerHints: ["opencode", "anomalyco"],
213
+ /** File an uninstall may delete, when the copy came from a script install. */
214
+ scriptBinaryNames: Object.freeze(["opencode", "opencode.exe"]),
215
+ /** Where the vendor script puts the binary when the user sets no directory. */
216
+ defaultScriptInstallDir: ".opencode/bin",
217
+ installMethods: Object.freeze([
218
+ packageManagerMethod("npm", "npm", "npm", "opencode-ai@latest"),
219
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "opencode-ai@latest"),
220
+ // The published script hardcodes $HOME/.opencode/bin, so it cannot honour
221
+ // a custom install directory; the npm/pnpm methods above can.
222
+ scriptMethod("script-bash", "curl | bash", {
223
+ shell: "bash",
224
+ scriptUrl: "https://opencode.ai/install",
225
+ platforms: ["linux", "darwin"],
226
+ // The published script hardcodes $HOME/.opencode/bin, so it cannot honour
227
+ // a custom install directory; the npm/pnpm methods above can.
228
+ supportsInstallDir: false,
229
+ verify: Object.freeze({
230
+ mustInclude: ["anomalyco/opencode", "opencode.ai"],
231
+ mustNotInclude: ["static-openapi.stepfun.com"]
232
+ })
233
+ })
234
+ ])
235
+ }),
236
+ Object.freeze({
237
+ id: "stepcode",
238
+ label: "Step Code",
239
+ vendor: "StepFun \u9636\u8DC3\u661F\u8FB0",
240
+ 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",
241
+ manager: "script-installer",
242
+ installArgs: [],
243
+ uninstallArgs: [],
244
+ probeExecutables: ["step"],
245
+ 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",
246
+ providerHints: ["stepfun", "stepcode"],
247
+ scriptBinaryNames: Object.freeze(["step", "step.exe"]),
248
+ defaultScriptInstallDir: ".stepcode/bin",
249
+ /** The vendor installer writes this marked block into the shell profile. */
250
+ pathProfileMarkers: Object.freeze(["# stepcode"]),
251
+ installMethods: Object.freeze([
252
+ scriptMethod("script-bash", "curl | bash", {
253
+ shell: "bash",
254
+ scriptUrl: "https://static-openapi.stepfun.com/stepcode/install.sh",
255
+ platforms: ["linux", "darwin"],
256
+ installArg: ["--install-dir"],
257
+ env: ["STEP_INSTALL_DIR", "STEP_RELEASE_BASE_URL"],
258
+ supportsInstallDir: true,
259
+ verify: Object.freeze({
260
+ mustInclude: ["static-openapi.stepfun.com", "stepcode installer"],
261
+ mustNotInclude: ["anomalyco/opencode"]
262
+ })
263
+ }),
264
+ scriptMethod("script-powershell", "irm | iex", {
265
+ shell: "powershell",
266
+ scriptUrl: "https://static-openapi.stepfun.com/stepcode/install.ps1",
267
+ platforms: ["win32"],
268
+ installArg: ["-InstallDir"],
269
+ env: ["STEP_INSTALL_DIR", "STEP_RELEASE_BASE_URL"],
270
+ supportsInstallDir: true,
271
+ verify: Object.freeze({
272
+ mustInclude: ["static-openapi.stepfun.com", "stepcode"],
273
+ mustNotInclude: ["anomalyco/opencode"]
274
+ })
275
+ })
276
+ ])
131
277
  }),
132
278
  Object.freeze({
133
279
  id: "zcode",
@@ -136,6 +282,7 @@ var OFFICIAL_TOOLS = Object.freeze([
136
282
  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
283
  manager: "signed-windows-installer",
138
284
  installArgs: [],
285
+ uninstallArgs: [],
139
286
  probeExecutables: [],
140
287
  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
288
  providerHints: ["zai", "z.ai", "zcode", "glm", "zhipu", "bigmodel"]
@@ -153,10 +300,30 @@ function toolForProvider(provider) {
153
300
  }
154
301
  return null;
155
302
  }
303
+ function installMethodsFor(tool) {
304
+ return Array.isArray(tool?.installMethods) ? tool.installMethods : [];
305
+ }
306
+ function installMethodFor(tool, methodId) {
307
+ const wanted = String(methodId ?? "").trim();
308
+ if (!wanted) return null;
309
+ return installMethodsFor(tool).find((method) => method.id === wanted) ?? null;
310
+ }
311
+ function defaultInstallMethod(tool) {
312
+ return installMethodsFor(tool)[0] ?? null;
313
+ }
156
314
  function installCommandLine(tool) {
157
315
  if (!tool || tool.unsupported) return null;
158
316
  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(" ")}`;
317
+ return installCommandLineFor(tool, defaultInstallMethod(tool));
318
+ }
319
+ function installCommandLineFor(tool, method) {
320
+ if (!tool || tool.unsupported) return null;
321
+ if (!method) return null;
322
+ if (method.kind === "script") {
323
+ const piped = method.shell === "powershell" ? `irm ${method.scriptUrl} | iex` : `curl -fsSL ${method.scriptUrl} | bash`;
324
+ return method.installArgs.length ? `${piped} ${method.installArgs[0]} <\u5B89\u88C5\u76EE\u5F55>` : piped;
325
+ }
326
+ return `${method.manager} ${method.installArgs.join(" ")}`;
160
327
  }
161
328
 
162
329
  // .dsh-plugin/shared/model-profiles.mjs
@@ -411,6 +578,112 @@ function toolBoundary(toolId, { sandboxed = false, platform = "unknown" } = {})
411
578
  };
412
579
  }
413
580
 
581
+ // .dsh-plugin/shared/tool-install-preferences.mjs
582
+ var MAX_INSTALL_DIR_CHARS = 4096;
583
+ var MAX_SOURCE_URL_CHARS = 2048;
584
+ var MAX_OVERRIDES = 64;
585
+ function isAbsolutePath(value) {
586
+ const raw = String(value ?? "");
587
+ if (!raw) return false;
588
+ if (raw.startsWith("/")) return true;
589
+ if (/^[A-Za-z]:[\\/]/.test(raw)) return true;
590
+ return /^\\\\[^\\]+\\[^\\]+/.test(raw);
591
+ }
592
+ function joinPath(base, ...segments) {
593
+ const separator = String(base ?? "").includes("\\") && !String(base ?? "").includes("/") ? "\\" : "/";
594
+ const parts = [String(base ?? "").replace(/[\\/]+$/, ""), ...segments.map((item) => String(item ?? "").replace(/^[\\/]+|[\\/]+$/g, ""))];
595
+ return parts.filter(Boolean).join(separator) || separator;
596
+ }
597
+ function expandInstallDir(value, { home = "", env = {} } = {}) {
598
+ const raw = String(value ?? "").trim();
599
+ if (!raw) return "";
600
+ if (raw.includes("\0")) throw new Error("\u5B89\u88C5\u76EE\u5F55\u5305\u542B\u65E0\u6548\u5B57\u7B26\u3002");
601
+ let expanded = raw;
602
+ const userHome = String(home ?? "");
603
+ if (expanded === "~") expanded = userHome;
604
+ else if (expanded.startsWith("~/") || expanded.startsWith("~\\")) expanded = joinPath(userHome, expanded.slice(2));
605
+ 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);
606
+ if (expanded.length > MAX_INSTALL_DIR_CHARS) throw new Error("\u5B89\u88C5\u76EE\u5F55\u8FC7\u957F\u3002");
607
+ if (!isAbsolutePath(expanded)) throw new Error("\u5B89\u88C5\u76EE\u5F55\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u3002");
608
+ const withoutTrailing = expanded.replace(/[\\/]+$/, "");
609
+ if (!withoutTrailing) throw new Error("\u5B89\u88C5\u76EE\u5F55\u4E0D\u80FD\u662F\u6587\u4EF6\u7CFB\u7EDF\u6839\u76EE\u5F55\u3002");
610
+ if (/^[A-Za-z]:$/.test(withoutTrailing)) throw new Error("\u5B89\u88C5\u76EE\u5F55\u4E0D\u80FD\u662F\u6587\u4EF6\u7CFB\u7EDF\u6839\u76EE\u5F55\u3002");
611
+ if (/^\\\\[^\\]+\\[^\\]+$/.test(withoutTrailing)) throw new Error("\u5B89\u88C5\u76EE\u5F55\u4E0D\u80FD\u662F\u7F51\u7EDC\u5171\u4EAB\u6839\u76EE\u5F55\u3002");
612
+ return expanded;
613
+ }
614
+ function normalizeSourceUrl(value, subject) {
615
+ const raw = String(value ?? "").trim();
616
+ if (!raw) return "";
617
+ if (raw.length > MAX_SOURCE_URL_CHARS) throw new Error(`${subject}\u5730\u5740\u8FC7\u957F\u3002`);
618
+ let url;
619
+ try {
620
+ url = new URL(raw);
621
+ } catch {
622
+ throw new Error(`${subject}\u4E0D\u662F\u6709\u6548\u7F51\u5740\u3002`);
623
+ }
624
+ if (url.protocol !== "https:") throw new Error(`${subject}\u5FC5\u987B\u4F7F\u7528 https\u3002`);
625
+ if (url.username || url.password) throw new Error(`${subject}\u4E0D\u80FD\u5305\u542B\u8D26\u53F7\u5BC6\u7801\u3002`);
626
+ return url.toString();
627
+ }
628
+ function parseJsonObject(value, subject) {
629
+ const raw = String(value ?? "").trim();
630
+ if (!raw) return {};
631
+ let parsed;
632
+ try {
633
+ parsed = JSON.parse(raw);
634
+ } catch {
635
+ throw new Error(`${subject}\u4E0D\u662F\u6709\u6548 JSON\u3002`);
636
+ }
637
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
638
+ throw new Error(`${subject}\u5FC5\u987B\u662F JSON \u5BF9\u8C61\u3002`);
639
+ }
640
+ const entries = Object.entries(parsed);
641
+ if (entries.length > MAX_OVERRIDES) throw new Error(`${subject}\u6761\u76EE\u8FC7\u591A\u3002`);
642
+ return Object.fromEntries(entries.filter(([, item]) => typeof item === "string" && item.trim()));
643
+ }
644
+ function valueOf(config, key, fallback) {
645
+ const value = config?.[key];
646
+ return value !== void 0 && typeof value?.get === "function" ? value.get() : value ?? fallback;
647
+ }
648
+ function keepKnownToolIds(raw, known, subject) {
649
+ for (const key of Object.keys(raw)) {
650
+ if (!known.has(key)) throw new Error(`${subject}\u4E2D\u7684 ${key} \u4E0D\u662F\u5B98\u65B9\u5DE5\u5177 ID\u3002`);
651
+ }
652
+ return raw;
653
+ }
654
+ function parseInstallPreferences(config = {}, { tools = [] } = {}) {
655
+ const known = new Set(tools.map((tool) => tool.id));
656
+ const installDir = expandInstallDir(valueOf(config, "toolInstallDir", ""));
657
+ const registry = normalizeSourceUrl(valueOf(config, "toolNpmRegistry", DEFAULT_NPM_REGISTRY), "npm \u6E90") || DEFAULT_NPM_REGISTRY;
658
+ const methodRaw = keepKnownToolIds(
659
+ parseJsonObject(valueOf(config, "toolInstallMethodsJson", "{}"), "\u5B89\u88C5\u65B9\u5F0F\u8986\u76D6"),
660
+ known,
661
+ "\u5B89\u88C5\u65B9\u5F0F\u8986\u76D6"
662
+ );
663
+ const scriptRaw = keepKnownToolIds(
664
+ parseJsonObject(valueOf(config, "toolScriptUrlsJson", "{}"), "\u5B89\u88C5\u811A\u672C\u6E90\u8986\u76D6"),
665
+ known,
666
+ "\u5B89\u88C5\u811A\u672C\u6E90\u8986\u76D6"
667
+ );
668
+ const methods = {};
669
+ const scriptUrls = {};
670
+ for (const [id2, methodId] of Object.entries(methodRaw)) methods[id2] = methodId.trim();
671
+ for (const [id2, url] of Object.entries(scriptRaw)) scriptUrls[id2] = normalizeSourceUrl(url, `${id2} \u5B89\u88C5\u811A\u672C\u6E90`);
672
+ for (const [id2, methodId] of Object.entries(methods)) {
673
+ if (!installMethodFor({ id: id2, installMethods: tools.find((tool) => tool.id === id2)?.installMethods }, methodId)) {
674
+ throw new Error(`${id2} \u4E0D\u652F\u6301\u5B89\u88C5\u65B9\u5F0F ${methodId}\u3002`);
675
+ }
676
+ }
677
+ return {
678
+ installDir,
679
+ registry,
680
+ methods,
681
+ scriptUrls,
682
+ /** Script execution stays a deliberate act; a config file alone must not run one. */
683
+ allowScriptInstall: valueOf(config, "toolAllowScriptInstall", true) !== false
684
+ };
685
+ }
686
+
414
687
  // .dsh-plugin/client/router-main.jsx
415
688
  var import_react5 = __toESM(require("react"), 1);
416
689
 
@@ -621,8 +894,8 @@ var OPENCODE_CATALOG_PROVIDERS = Object.freeze([
621
894
  "opencode-zen",
622
895
  "opencode-go-zen"
623
896
  ]);
624
- function classifyTask(text6) {
625
- const value = String(text6 ?? "");
897
+ function classifyTask(text7) {
898
+ const value = String(text7 ?? "");
626
899
  if (value.length < 80 && /翻译|解释|translate|explain/i.test(value)) return "general";
627
900
  return detectTaskTypes(value)[0] ?? "general";
628
901
  }
@@ -642,8 +915,8 @@ var TASK_TYPE_LABELS = Object.freeze({
642
915
  summarization: "\u6458\u8981\u4E0E\u6574\u7406",
643
916
  writing: "\u5199\u4F5C\u4E0E\u8868\u8FBE"
644
917
  });
645
- function detectTaskTypes(text6) {
646
- const value = String(text6 ?? "");
918
+ function detectTaskTypes(text7) {
919
+ const value = String(text7 ?? "");
647
920
  const ranked = TASK_TYPE_RULES.map(([type, pattern]) => ({
648
921
  type,
649
922
  signals: value.match(new RegExp(pattern.source, pattern.flags.includes("g") ? pattern.flags : `${pattern.flags}g`))?.length ?? 0
@@ -651,8 +924,8 @@ function detectTaskTypes(text6) {
651
924
  ranked.sort((left, right) => right.signals - left.signals || left.type.localeCompare(right.type));
652
925
  return ranked.map((item) => item.type);
653
926
  }
654
- function assessComplexity(text6) {
655
- const value = String(text6 ?? "");
927
+ function assessComplexity(text7) {
928
+ const value = String(text7 ?? "");
656
929
  const lengthScore = clamp(value.length / 2200);
657
930
  const requirementScore = clamp((value.match(/(?:^|\n)\s*(?:[-*]|\d+[.)]|[一二三四五六七八九十]+[、.])/g) ?? []).length / 8);
658
931
  const codeScore = /(代码|工程|架构|接口|实现|部署|测试|code|api|debug)/i.test(value) ? 0.22 : 0;
@@ -732,10 +1005,10 @@ function normalizedCacheRatios(cacheReadRatio = 0, cacheWriteRatio = 0) {
732
1005
  return { read, write };
733
1006
  }
734
1007
  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) {
1008
+ function estimateCost(model, text7, outputTokens = 900, pricingOverrides = {}, cacheReadRatio = 0, cacheWriteRatio = 0) {
736
1009
  const pricing = pricingFor(model, pricingOverrides);
737
1010
  if (pricing === null) return null;
738
- const inputTokens = Math.max(80, Math.ceil(String(text6 ?? "").length / 3.7));
1011
+ const inputTokens = Math.max(80, Math.ceil(String(text7 ?? "").length / 3.7));
739
1012
  const ratios = normalizedCacheRatios(cacheReadRatio, cacheWriteRatio);
740
1013
  const cacheReadTokens = Math.min(inputTokens, Math.max(0, Math.round(inputTokens * ratios.read)));
741
1014
  const cacheWriteTokens = Math.min(inputTokens - cacheReadTokens, Math.max(0, Math.round(inputTokens * ratios.write)));
@@ -747,8 +1020,8 @@ function modelMetadata(name) {
747
1020
  if (!key) return null;
748
1021
  return MODEL_CATALOG.find((model) => model.aliases.some((alias) => key === normalize(alias))) ?? null;
749
1022
  }
750
- function taskTokenBudget(text6, task, complexity, cacheReadRatio = 0, cacheWriteRatio = 0) {
751
- const inputTokens = Math.max(80, Math.ceil(String(text6 ?? "").length / 3.7));
1023
+ function taskTokenBudget(text7, task, complexity, cacheReadRatio = 0, cacheWriteRatio = 0) {
1024
+ const inputTokens = Math.max(80, Math.ceil(String(text7 ?? "").length / 3.7));
752
1025
  const multipliers = {
753
1026
  analysis: { input: 0.9, output: 0.55 },
754
1027
  execution: { input: 1.2, output: (task.difficulty ?? complexity) === "complex" ? 1.45 : 1 },
@@ -812,10 +1085,10 @@ function looksLikeRequirement(value) {
812
1085
  const item = requirementText(value);
813
1086
  return item.length >= 3 && !/[::]$/u.test(item) && !/(?:以下|下列|如下)(?:的)?(?:任务|需求|工作|事项|要求)/u.test(item) && REQUIREMENT_ACTION.test(item.slice(0, 32));
814
1087
  }
815
- function explicitRequirements(text6) {
1088
+ function explicitRequirements(text7) {
816
1089
  const visibleLines = [];
817
1090
  let fence = null;
818
- for (const line of String(text6 ?? "").split(/\r?\n/u)) {
1091
+ for (const line of String(text7 ?? "").split(/\r?\n/u)) {
819
1092
  const marker2 = /^\s*(`{3,}|~{3,})/u.exec(line);
820
1093
  if (fence) {
821
1094
  if (marker2 && marker2[1][0] === fence.char && marker2[1].length >= fence.length && !line.slice(marker2[0].length).trim()) fence = null;
@@ -888,16 +1161,16 @@ function explicitStepReferences(objective) {
888
1161
  }
889
1162
  return [...found].sort((a, b2) => a - b2);
890
1163
  }
891
- function taskPackages(taskType, text6, band) {
1164
+ function taskPackages(taskType, text7, band) {
892
1165
  if (band !== "complex") {
893
1166
  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
1167
  return [{ ...task, qualityFloor: taskQualityFloor(band, task) }];
895
1168
  }
896
- const value = String(text6 ?? "");
1169
+ const value = String(text7 ?? "");
897
1170
  const packages = [
898
1171
  { 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
1172
  ];
900
- const requirements = explicitRequirements(text6);
1173
+ const requirements = explicitRequirements(text7);
901
1174
  if (requirements.length >= 2) {
902
1175
  const groups = requirements.length > MAX_EXPLICIT_EXECUTION_PACKAGES ? [
903
1176
  ...requirements.slice(0, MAX_EXPLICIT_EXECUTION_PACKAGES - 1).map((item) => [item]),
@@ -924,7 +1197,7 @@ function taskPackages(taskType, text6, band) {
924
1197
  });
925
1198
  });
926
1199
  } else {
927
- const domains = [...new Set([...detectTaskTypes(text6), taskType].filter((type) => type !== "general"))];
1200
+ const domains = [...new Set([...detectTaskTypes(text7), taskType].filter((type) => type !== "general"))];
928
1201
  for (const type of domains.length > 0 ? domains : [taskType]) {
929
1202
  const objective = value.split(/[,,。;;]|最后|然后|接着|并且/u).map((item) => item.trim()).filter((item) => detectTaskTypes(item).includes(type)).join("\uFF1B") || value;
930
1203
  const difficulty = requirementDifficulty(objective, type);
@@ -1025,31 +1298,31 @@ function reasoningDecision(row, task) {
1025
1298
  multiplier: reasoningEffortMultiplier(chosen)
1026
1299
  };
1027
1300
  }
1028
- function candidateUtility(row, task, weights, cacheReadRatio = 0, cacheWriteRatio = 0, text6 = "", complexity = "balanced") {
1301
+ function candidateUtility(row, task, weights, cacheReadRatio = 0, cacheWriteRatio = 0, text7 = "", complexity = "balanced") {
1029
1302
  const quality = qualityForTask(row, task.type);
1030
1303
  const rawPreference = row.preferenceAdjustments?.[task.type];
1031
1304
  const preferenceAdjustment = Number.isFinite(rawPreference) ? clamp(rawPreference, -0.1, 0.1) : 0;
1032
1305
  const floor = Number(task.qualityFloor ?? taskQualityFloor("complex", task));
1033
1306
  const qualityGap = Math.max(0, floor - quality);
1034
1307
  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));
1308
+ const reference = taskCost({ pricing: COST_REFERENCE_PRICING, reasoningKnown: true, reasoningEfforts: [] }, task, text7, complexity, cacheReadRatio, cacheWriteRatio);
1309
+ const cost = row.pricing === null ? 0 : 1 / (1 + taskCost(row, task, text7, complexity, cacheReadRatio, cacheWriteRatio) / Math.max(reference, 1e-12));
1037
1310
  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
1311
  return { score, floor, qualityGap, preferenceAdjustment, ...reasoning };
1039
1312
  }
1040
- function taskCost(row, task, text6, complexity, cacheReadRatio = 0, cacheWriteRatio = 0) {
1313
+ function taskCost(row, task, text7, complexity, cacheReadRatio = 0, cacheWriteRatio = 0) {
1041
1314
  if (row?.pricing === null) return 0;
1042
1315
  const decision = row === null ? null : reasoningDecision(row, task);
1043
- const tokens = taskTokenBudget(text6, { ...task, reasoningEffort: decision?.reasoningEffort }, complexity, cacheReadRatio, cacheWriteRatio);
1316
+ const tokens = taskTokenBudget(text7, { ...task, reasoningEffort: decision?.reasoningEffort }, complexity, cacheReadRatio, cacheWriteRatio);
1044
1317
  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
1318
  }
1046
- function dominates(left, right, task, text6, complexity, cacheReadRatio, cacheWriteRatio) {
1319
+ function dominates(left, right, task, text7, complexity, cacheReadRatio, cacheWriteRatio) {
1047
1320
  if (left.pricing === null || right.pricing === null) return false;
1048
1321
  const leftReasoning = reasoningDecision(left, task);
1049
1322
  const rightReasoning = reasoningDecision(right, task);
1050
1323
  const leftValues = {
1051
1324
  quality: qualityForTask(left, task.type),
1052
- cost: taskCost(left, task, text6, complexity, cacheReadRatio, cacheWriteRatio),
1325
+ cost: taskCost(left, task, text7, complexity, cacheReadRatio, cacheWriteRatio),
1053
1326
  latency: clamp(left.latency * leftReasoning.multiplier.latency),
1054
1327
  specialty: specialtyForTask(left, task.type),
1055
1328
  reasoning: leftReasoning.reasoningFit,
@@ -1057,7 +1330,7 @@ function dominates(left, right, task, text6, complexity, cacheReadRatio, cacheWr
1057
1330
  };
1058
1331
  const rightValues = {
1059
1332
  quality: qualityForTask(right, task.type),
1060
- cost: taskCost(right, task, text6, complexity, cacheReadRatio, cacheWriteRatio),
1333
+ cost: taskCost(right, task, text7, complexity, cacheReadRatio, cacheWriteRatio),
1061
1334
  latency: clamp(right.latency * rightReasoning.multiplier.latency),
1062
1335
  specialty: specialtyForTask(right, task.type),
1063
1336
  reasoning: rightReasoning.reasoningFit,
@@ -1070,7 +1343,7 @@ function dominates(left, right, task, text6, complexity, cacheReadRatio, cacheWr
1070
1343
  function eligibleRowsForTask(rows, task) {
1071
1344
  return task.type === "vision" ? rows.filter((row) => row.inputModalities.length === 0 || row.inputModalities.includes("image")) : rows;
1072
1345
  }
1073
- function candidatePool(rows, task, weights, text6, complexity, cacheReadRatio, cacheWriteRatio, dependencySensitive = false) {
1346
+ function candidatePool(rows, task, weights, text7, complexity, cacheReadRatio, cacheWriteRatio, dependencySensitive = false) {
1074
1347
  const eligibleRows = eligibleRowsForTask(rows, task);
1075
1348
  const floor = Number(task.qualityFloor ?? 0);
1076
1349
  const feasible = eligibleRows.filter((row) => qualityForTask(row, task.type) >= floor);
@@ -1078,10 +1351,10 @@ function candidatePool(rows, task, weights, text6, complexity, cacheReadRatio, c
1078
1351
  const taskWeights = weightsForTask(weights, task);
1079
1352
  const scored = source.map((row) => ({
1080
1353
  row,
1081
- decision: candidateUtility(row, task, taskWeights, cacheReadRatio, cacheWriteRatio, text6, complexity),
1082
- cost: taskCost(row, task, text6, complexity, cacheReadRatio, cacheWriteRatio)
1354
+ decision: candidateUtility(row, task, taskWeights, cacheReadRatio, cacheWriteRatio, text7, complexity),
1355
+ cost: taskCost(row, task, text7, complexity, cacheReadRatio, cacheWriteRatio)
1083
1356
  }));
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)));
1357
+ 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
1358
  const essential = [
1086
1359
  scored.filter((item) => item.row.pricing !== null).sort((left, right) => left.cost - right.cost || compareRowsStable(left.row, right.row))[0],
1087
1360
  scored.slice().sort((left, right) => right.decision.score - left.decision.score || compareRowsStable(left.row, right.row))[0],
@@ -1099,9 +1372,9 @@ function candidatePool(rows, task, weights, text6, complexity, cacheReadRatio, c
1099
1372
  dependencySensitive
1100
1373
  };
1101
1374
  }
1102
- function solveAssignments({ rows, tasks, weights, text: text6, complexity, budget, cacheReadRatio, cacheWriteRatio, minimizeCost = false }) {
1375
+ function solveAssignments({ rows, tasks, weights, text: text7, complexity, budget, cacheReadRatio, cacheWriteRatio, minimizeCost = false }) {
1103
1376
  const dependencySensitive = tasks.some((task) => (task.dependsOn ?? []).length > 0);
1104
- const pools = tasks.map((task) => candidatePool(rows, task, weights, text6, complexity, cacheReadRatio, cacheWriteRatio, dependencySensitive));
1377
+ const pools = tasks.map((task) => candidatePool(rows, task, weights, text7, complexity, cacheReadRatio, cacheWriteRatio, dependencySensitive));
1105
1378
  const result = solveCandidateAssignments({ tasks, pools: pools.map((pool, index) => pool.options.map((option) => ({
1106
1379
  route: routeKey(option.row.provider, option.row.model),
1107
1380
  score: option.decision.score,
@@ -1125,15 +1398,15 @@ function solveAssignments({ rows, tasks, weights, text: text6, complexity, budge
1125
1398
  minimumFeasibleCost: result.minimumCostLowerBound
1126
1399
  };
1127
1400
  }
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 } = {}) {
1401
+ 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
1402
  const presetId = normalizeRoutingPreset(preset);
1130
- const firstLine = String(text6 ?? "").split(/\r?\n/u)[0].trim();
1403
+ const firstLine = String(text7 ?? "").split(/\r?\n/u)[0].trim();
1131
1404
  const transformOnly = /^(?:请|帮我)?(?:总结|概括|翻译|摘要|解释)(?:以下|下列|下面|这份|这些)/u.test(firstLine) && !/(?:执行|完成|实施|分配)/u.test(firstLine);
1132
- const assessed = assessComplexity(transformOnly ? firstLine : text6);
1133
- const requirements = explicitRequirements(text6);
1405
+ const assessed = assessComplexity(transformOnly ? firstLine : text7);
1406
+ const requirements = explicitRequirements(text7);
1134
1407
  const compound = shouldSplitRequirements(requirements);
1135
1408
  const complexity = compound && assessed.band !== "complex" ? { value: Math.max(0.66, assessed.value), band: "complex" } : assessed;
1136
- const taskType = classifyTask(transformOnly ? firstLine : text6);
1409
+ const taskType = classifyTask(transformOnly ? firstLine : text7);
1137
1410
  const weights = presetWeights(OBJECTIVE_WEIGHTS[complexity.band], presetId);
1138
1411
  const discovered = Array.isArray(available) ? available.map((entry) => {
1139
1412
  const rawEfforts = Array.isArray(entry.reasoningEfforts) ? entry.reasoningEfforts : [];
@@ -1212,10 +1485,10 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1212
1485
  costOut: pricingRow.output,
1213
1486
  cacheRead: pricingRow.cacheRead,
1214
1487
  cacheWrite: pricingRow.cacheWrite
1215
- }, text6, 900, {}, cacheReadRatio, cacheWriteRatio)
1488
+ }, text7, 900, {}, cacheReadRatio, cacheWriteRatio)
1216
1489
  });
1217
1490
  }
1218
- const taskNodes = taskPackages(taskType, text6, complexity.band).map((task) => presetId === DEFAULT_ROUTING_PRESET ? task : {
1491
+ const taskNodes = taskPackages(taskType, text7, complexity.band).map((task) => presetId === DEFAULT_ROUTING_PRESET ? task : {
1219
1492
  ...task,
1220
1493
  qualityFloor: presetFloor(task.qualityFloor, presetId),
1221
1494
  weights: presetWeights(weightsForTask(weights, task), presetId)
@@ -1226,7 +1499,7 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1226
1499
  rows,
1227
1500
  tasks: taskNodes,
1228
1501
  weights,
1229
- text: text6,
1502
+ text: text7,
1230
1503
  complexity: complexity.band,
1231
1504
  budget: Number.POSITIVE_INFINITY,
1232
1505
  cacheReadRatio,
@@ -1236,7 +1509,7 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1236
1509
  rows,
1237
1510
  tasks: taskNodes,
1238
1511
  weights,
1239
- text: text6,
1512
+ text: text7,
1240
1513
  complexity: complexity.band,
1241
1514
  budget,
1242
1515
  cacheReadRatio,
@@ -1246,7 +1519,7 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1246
1519
  rows,
1247
1520
  tasks: taskNodes,
1248
1521
  weights,
1249
- text: text6,
1522
+ text: text7,
1250
1523
  complexity: complexity.band,
1251
1524
  budget: Number.POSITIVE_INFINITY,
1252
1525
  cacheReadRatio,
@@ -1258,8 +1531,8 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1258
1531
  const usedRoutes = optimized?.usedRoutes ?? /* @__PURE__ */ new Set();
1259
1532
  const constraintRelaxed = (optimized?.relaxedCount ?? 0) > 0;
1260
1533
  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);
1534
+ row.score = candidateUtility(row, taskNodes[0] ?? { type: taskType, qualityFloor: QUALITY_FLOORS[complexity.band] }, weights, cacheReadRatio, cacheWriteRatio, text7, complexity.band).score;
1535
+ if (row.pricing !== null && taskNodes[0]) row.estimatedCost = taskCost(row, taskNodes[0], text7, complexity.band, cacheReadRatio, cacheWriteRatio);
1263
1536
  }
1264
1537
  rows.sort((left, right) => right.score - left.score || compareRowsStable(left, right));
1265
1538
  const selectedAssignment = assignments[0];
@@ -1285,8 +1558,8 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1285
1558
  dependsOn: [...task.dependsOn ?? []]
1286
1559
  }));
1287
1560
  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);
1561
+ const tokens = taskTokenBudget(text7, { ...task, reasoningEffort: decision?.reasoningEffort }, complexity.band, cacheReadRatio, cacheWriteRatio);
1562
+ const taskEstimate = estimatedCost ?? taskCost(row, task, text7, complexity.band, cacheReadRatio, cacheWriteRatio);
1290
1563
  return {
1291
1564
  stage: index + 1,
1292
1565
  purpose: task.purpose,
@@ -1309,12 +1582,12 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1309
1582
  };
1310
1583
  });
1311
1584
  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;
1585
+ const totalEstimate = pricingComplete ? assignments.reduce((sum, { task, row, estimatedCost }) => sum + (estimatedCost ?? taskCost(row, task, text7, complexity.band, cacheReadRatio, cacheWriteRatio)), 0) : null;
1313
1586
  const baselineRows = assignments.map(({ task }) => {
1314
1587
  const strongest = eligibleRowsForTask(rows, task).reduce((best, row) => qualityForTask(row, task.type) > (best === null ? -1 : qualityForTask(best, task.type)) ? row : best, null);
1315
1588
  return { task, strongest };
1316
1589
  });
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;
1590
+ 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
1591
  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
1592
  const budgetExceeded = Number(budgetUsd) > 0 && totalEstimate !== null ? totalEstimate > Number(budgetUsd) : null;
1320
1593
  const savings = baselineCost === null || totalEstimate === null || !qualityEvidenceComplete ? null : baselineCost <= 0 ? 0 : clamp((baselineCost - totalEstimate) / baselineCost);
@@ -1331,7 +1604,7 @@ function buildPlan({ text: text6 = "", available = [], mode = "collective", pric
1331
1604
  taskTypes: [...new Set(taskNodes.map((task) => task.type).filter((type) => type !== "reasoning"))],
1332
1605
  objectiveWeights: weights,
1333
1606
  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);
1607
+ 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
1608
  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
1609
  }),
1337
1610
  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 +2182,34 @@ function toolInstallAction({ tool, probe, readiness, job, probeStatus, latestVer
1909
2182
  disabled: !interactive || running || installed && (order === 1 || current)
1910
2183
  };
1911
2184
  }
1912
- function installClickRefusal({ tool, submitting = false, running = false } = {}) {
2185
+ function toolMaintenanceActions({ tool, probe, job, probeStatus, summary }) {
2186
+ const running = job?.status === "running";
2187
+ const installed = probe?.installed === true;
2188
+ const interactive = probeStatus === "ready" || probeStatus === "refreshing";
2189
+ const desktop = tool.manager === "signed-windows-installer";
2190
+ const busy = running || !interactive;
2191
+ return {
2192
+ repair: {
2193
+ label: "\u4E00\u952E\u4FEE\u590D",
2194
+ 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",
2195
+ disabled: busy || desktop || summary?.installable === false
2196
+ },
2197
+ uninstall: {
2198
+ label: "\u4E00\u952E\u5378\u8F7D",
2199
+ title: "\u53EA\u5220\u9664\u7A0B\u5E8F\u672C\u8EAB\uFF1B\u914D\u7F6E\u3001\u767B\u5F55\u4FE1\u606F\u548C\u5386\u53F2\u8BB0\u5F55\u4FDD\u7559",
2200
+ disabled: busy || desktop || !installed || summary?.removable === false
2201
+ }
2202
+ };
2203
+ }
2204
+ function installClickRefusal({ tool, submitting = false, running = false, operation = "install" } = {}) {
1913
2205
  if (!tool) return "\u672A\u77E5\u5B98\u65B9\u5DE5\u5177\uFF0C\u65E0\u6CD5\u5F00\u59CB\u5B89\u88C5\u3002";
1914
2206
  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";
2207
+ if (tool.manager === "signed-windows-installer" && operation !== "install") {
2208
+ 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`;
2209
+ }
2210
+ if (submitting || running) {
2211
+ 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";
2212
+ }
1916
2213
  return "";
1917
2214
  }
1918
2215
  function payloadError(payload, fallback) {
@@ -12154,7 +12451,7 @@ var xterm_default = `/**
12154
12451
  `;
12155
12452
 
12156
12453
  // .dsh-plugin/client/host-version.mjs
12157
- var ROUTER_CLIENT_VERSION = true ? "0.16.4" : "";
12454
+ var ROUTER_CLIENT_VERSION = true ? "0.17.0" : "";
12158
12455
  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
12456
  function isMissingRemoteMethod(message) {
12160
12457
  const value = String(message ?? "");
@@ -12752,6 +13049,16 @@ var router_main_default = `.mr-workspace {
12752
13049
  .mr-tool-log { margin-top: 7px; color: var(--mr-muted); font-size: 11px; }
12753
13050
  .mr-tool-log summary { cursor: pointer; }
12754
13051
  .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; }
13052
+ .mr-tool-methods { display: flex; align-items: center; gap: 6px; margin-right: 2px; }
13053
+ .mr-tool-methods label { display: flex; align-items: center; gap: 6px; color: var(--mr-muted); font-size: 11px; }
13054
+ .mr-install-settings { margin-bottom: 14px; padding: 13px 14px; border: 1px solid var(--mr-line); border-radius: 11px; background: var(--mr-card); }
13055
+ .mr-install-settings-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
13056
+ .mr-install-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
13057
+ .mr-install-field { display: flex; flex-direction: column; gap: 5px; font-size: 12px; color: var(--mr-muted); }
13058
+ .mr-install-field > span { font-weight: 600; color: var(--mr-ink); }
13059
+ .mr-install-field small { font-size: 11px; line-height: 1.6; }
13060
+ .mr-install-error { color: #d95360; }
13061
+ .mr-install-toggle-row { display: flex; align-items: center; gap: 7px; }
12755
13062
  .mr-channel-line { display: flex; align-items: center; gap: 8px; margin: 8px 0; flex-wrap: wrap; }
12756
13063
  .mr-pill-channel-ok { border-color: var(--dsw-alias-success, #2f9e63); color: var(--dsw-alias-success, #2f9e63); }
12757
13064
  .mr-pill-warn { border-color: var(--dsw-alias-warning, #c27c0e); color: var(--dsw-alias-warning, #c27c0e); }
@@ -12972,9 +13279,115 @@ function probeLabel(probe) {
12972
13279
  if (probe.status === "probe-failed") return "\u68C0\u6D4B\u5931\u8D25";
12973
13280
  return probe.detail || "\u672A\u5B89\u88C5";
12974
13281
  }
12975
- function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth }) {
13282
+ var INSTALL_SETTING_FIELDS = [
13283
+ {
13284
+ key: "toolInstallDir",
13285
+ label: "\u7EDF\u4E00\u5B89\u88C5\u76EE\u5F55",
13286
+ fallback: "",
13287
+ placeholder: "\u7559\u7A7A = \u5404\u5382\u5546\u9ED8\u8BA4\u76EE\u5F55",
13288
+ hint: "npm / pnpm \u65B9\u5F0F\u4F1A\u52A0 --prefix\uFF1B\u811A\u672C\u65B9\u5F0F\u770B\u5382\u5546\u662F\u5426\u652F\u6301\u76EE\u5F55\u53C2\u6570\u3002",
13289
+ validate: () => null
13290
+ },
13291
+ {
13292
+ key: "toolNpmRegistry",
13293
+ label: "npm \u6E90\u5730\u5740",
13294
+ fallback: "https://registry.npmjs.org/",
13295
+ placeholder: "https://registry.npmjs.org/",
13296
+ 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",
13297
+ 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/"
13298
+ },
13299
+ {
13300
+ key: "toolScriptUrlsJson",
13301
+ label: "\u5B89\u88C5\u811A\u672C\u6E90\u8986\u76D6\uFF08JSON\uFF0C\u53EF\u9009\uFF09",
13302
+ fallback: "{}",
13303
+ placeholder: '{ "stepcode": "https://mirror/stepcode/install.ps1" }',
13304
+ 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",
13305
+ validate: (raw) => {
13306
+ if (!raw.trim()) return null;
13307
+ let parsed;
13308
+ try {
13309
+ parsed = JSON.parse(raw);
13310
+ } catch {
13311
+ return "JSON \u683C\u5F0F\u65E0\u6548\u3002";
13312
+ }
13313
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return "\u9700\u8981 JSON \u5BF9\u8C61\u3002";
13314
+ for (const [id2, url] of Object.entries(parsed)) {
13315
+ if (!OFFICIAL_TOOLS.some((tool) => tool.id === id2)) return `${id2} \u4E0D\u662F\u5B98\u65B9\u5DE5\u5177 ID\u3002`;
13316
+ if (typeof url !== "string" || !/^https:\/\/\S+$/i.test(url.trim())) return `${id2} \u7684\u5730\u5740\u5FC5\u987B\u662F https \u94FE\u63A5\u3002`;
13317
+ }
13318
+ return null;
13319
+ }
13320
+ }
13321
+ ];
13322
+ function InstallSettings({ settingsScope, install }) {
13323
+ const [snapshot, setSnapshot] = import_react5.default.useState(() => settingsScope?.getSnapshot?.() ?? { value: {}, writable: false });
13324
+ const [drafts, setDrafts] = import_react5.default.useState({});
13325
+ const [saving, setSaving] = import_react5.default.useState(false);
13326
+ const [notice, setNotice] = import_react5.default.useState("");
13327
+ import_react5.default.useEffect(() => {
13328
+ if (typeof settingsScope?.subscribe !== "function") return void 0;
13329
+ const unsubscribe = settingsScope.subscribe(() => {
13330
+ setSnapshot(settingsScope.getSnapshot());
13331
+ setDrafts({});
13332
+ });
13333
+ setSnapshot(settingsScope.getSnapshot());
13334
+ return unsubscribe;
13335
+ }, [settingsScope]);
13336
+ if (!settingsScope) return null;
13337
+ const writable = snapshot.writable === true && !saving;
13338
+ const shown = (field2) => drafts[field2.key] ?? String(snapshot.value?.[field2.key] ?? field2.fallback);
13339
+ const errors = Object.fromEntries(INSTALL_SETTING_FIELDS.map((field2) => [field2.key, field2.validate(shown(field2))]));
13340
+ const apply2 = async (entries, successText) => {
13341
+ setSaving(true);
13342
+ setNotice("");
13343
+ try {
13344
+ const accepted = await settingsScope.mutate(entries, snapshot.revision);
13345
+ 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");
13346
+ setNotice(successText);
13347
+ } catch (error) {
13348
+ setNotice(text5(error?.message) || "\u5B89\u88C5\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25\u3002");
13349
+ } finally {
13350
+ setSaving(false);
13351
+ }
13352
+ };
13353
+ const commit = (field2) => {
13354
+ const saved = String(snapshot.value?.[field2.key] ?? field2.fallback);
13355
+ const raw = shown(field2);
13356
+ if (raw === saved || errors[field2.key]) return;
13357
+ return apply2([{ op: "set", path: [field2.key], value: raw }], `\u5DF2\u4FDD\u5B58${field2.label}\uFF0C\u4E0B\u4E00\u6B21\u5B89\u88C5\u8D77\u751F\u6548\u3002`);
13358
+ };
13359
+ 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(
13360
+ "input",
13361
+ {
13362
+ className: "mr-input",
13363
+ value: shown(field2),
13364
+ placeholder: field2.placeholder,
13365
+ disabled: !writable,
13366
+ "aria-invalid": Boolean(errors[field2.key]),
13367
+ onChange: (event) => setDrafts((previous) => ({ ...previous, [field2.key]: event.target.value })),
13368
+ onBlur: () => {
13369
+ void commit(field2);
13370
+ }
13371
+ }
13372
+ ), 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(
13373
+ "input",
13374
+ {
13375
+ type: "checkbox",
13376
+ checked: snapshot.value?.toolAllowScriptInstall !== false,
13377
+ disabled: !writable,
13378
+ onChange: (event) => {
13379
+ const allowed = event.target.checked;
13380
+ void apply2(
13381
+ [{ op: "set", path: ["toolAllowScriptInstall"], value: allowed }],
13382
+ 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"
13383
+ );
13384
+ }
13385
+ }
13386
+ ), /* @__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}` : ""));
13387
+ }
13388
+ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth, settingsScope }) {
12976
13389
  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: "" });
13390
+ const [probeState, setProbeState] = import_react5.default.useState({ status: "loading", probes: [], capabilities: [], readiness: [], install: null, error: "" });
12978
13391
  const [jobs, setJobs] = import_react5.default.useState({});
12979
13392
  const [rowErrors, setRowErrors] = import_react5.default.useState({});
12980
13393
  const mounted = import_react5.default.useRef(false);
@@ -13005,7 +13418,7 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
13005
13418
  const probes = Array.isArray(response.value?.tools) ? response.value.tools : [];
13006
13419
  const capabilities = Array.isArray(response.value?.executionCapabilities) ? response.value.executionCapabilities : [];
13007
13420
  const readiness = Array.isArray(response.value?.executionReadiness) ? response.value.executionReadiness : [];
13008
- setProbeState({ status: "ready", probes, capabilities, readiness, error: "" });
13421
+ setProbeState({ status: "ready", probes, capabilities, readiness, install: response.value?.install ?? null, error: "" });
13009
13422
  onProbes({ probes, capabilities, readiness, hostVersion: typeof response.value?.hostVersion === "string" ? response.value.hostVersion : null });
13010
13423
  } catch (error) {
13011
13424
  if (!mounted.current || current !== request.current) return;
@@ -13016,7 +13429,7 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
13016
13429
  kept = true;
13017
13430
  return { ...previous, status: "ready", error: message };
13018
13431
  }
13019
- return { status: "error", probes: [], capabilities: [], readiness: [], error: message };
13432
+ return { status: "error", probes: [], capabilities: [], readiness: [], install: null, error: message };
13020
13433
  });
13021
13434
  if (!kept) onProbes({ probes: [], capabilities: [], readiness: [] });
13022
13435
  }
@@ -13088,34 +13501,36 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
13088
13501
  clearInterval(timer);
13089
13502
  };
13090
13503
  }, [jobs, officialToolInstallStatus]);
13091
- const install = async (id2) => {
13504
+ const run = async (id2, operation) => {
13092
13505
  const tool = OFFICIAL_TOOLS.find((item) => item.id === id2);
13506
+ const bridge = { install: installOfficialTool, repair: repairOfficialTool, uninstall: uninstallOfficialTool }[operation];
13093
13507
  const refusal = installClickRefusal({
13094
13508
  tool,
13095
13509
  submitting: submitting.current.has(id2),
13096
- running: jobsRef.current[id2]?.status === "running"
13510
+ running: jobsRef.current[id2]?.status === "running",
13511
+ operation
13097
13512
  });
13098
13513
  if (refusal) {
13099
13514
  setRowErrors((previous) => ({ ...previous, [id2]: refusal }));
13100
13515
  return;
13101
13516
  }
13517
+ if (typeof bridge !== "function") throw new Error("\u5B98\u65B9\u5DE5\u5177\u5B89\u88C5\u6865\u5C1A\u672A\u52A0\u8F7D\u3002");
13102
13518
  submitting.current.add(id2);
13103
13519
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
13104
13520
  setRowErrors((previous) => ({ ...previous, [id2]: "" }));
13105
13521
  rememberJobs((previous) => ({
13106
13522
  ...previous,
13107
- [id2]: { tool: id2, status: "running", outputTail: [], startedAt, error: null }
13523
+ [id2]: { tool: id2, operation, status: "running", outputTail: [], startedAt, error: null }
13108
13524
  }));
13109
13525
  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);
13526
+ const response = await bridge({ tool: id2 });
13112
13527
  if (!mounted.current) return;
13113
13528
  const accepted = acceptedInstallJob(response);
13114
- if (!accepted.job) throw new Error(remoteErrorText(accepted.error, "\u5B89\u88C5\u4EFB\u52A1\u672A\u88AB\u63A5\u53D7\u3002"));
13529
+ if (!accepted.job) throw new Error(remoteErrorText(accepted.error, "\u4EFB\u52A1\u672A\u88AB\u63A5\u53D7\u3002"));
13115
13530
  rememberJobs((previous) => shouldApplyInstallStatus(previous[id2], accepted.job) ? { ...previous, [id2]: accepted.job } : previous);
13116
13531
  } catch (error) {
13117
13532
  if (!mounted.current) return;
13118
- const message = remoteErrorText(text5(error?.message), "\u5B89\u88C5\u542F\u52A8\u5931\u8D25\u3002");
13533
+ const message = remoteErrorText(text5(error?.message), "\u542F\u52A8\u5931\u8D25\u3002");
13119
13534
  rememberJobs((previous) => ({
13120
13535
  ...previous,
13121
13536
  [id2]: {
@@ -13131,6 +13546,29 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
13131
13546
  submitting.current.delete(id2);
13132
13547
  }
13133
13548
  };
13549
+ const install = (id2) => run(id2, "install");
13550
+ const repair = (id2) => run(id2, "repair");
13551
+ const uninstall = (id2) => run(id2, "uninstall");
13552
+ const setInstallMethod = async (toolId, methodId) => {
13553
+ const snapshot = settingsScope?.getSnapshot?.();
13554
+ if (!snapshot) return;
13555
+ let map = {};
13556
+ try {
13557
+ map = JSON.parse(String(snapshot.value?.toolInstallMethodsJson ?? "{}")) || {};
13558
+ } catch {
13559
+ map = {};
13560
+ }
13561
+ if (typeof map !== "object" || Array.isArray(map)) map = {};
13562
+ if (methodId) map[toolId] = methodId;
13563
+ else delete map[toolId];
13564
+ try {
13565
+ const accepted = await settingsScope.mutate([{ op: "set", path: ["toolInstallMethodsJson"], value: JSON.stringify(map) }], snapshot.revision);
13566
+ if (!accepted) throw new Error("\u5B89\u88C5\u65B9\u5F0F\u672A\u88AB\u4FDD\u5B58\uFF0C\u53EF\u80FD\u88AB\u5176\u4ED6\u9875\u9762\u4FEE\u6539\u3002");
13567
+ void refresh();
13568
+ } catch (error) {
13569
+ if (mounted.current) setRowErrors((previous) => ({ ...previous, [toolId]: text5(error?.message) || "\u5B89\u88C5\u65B9\u5F0F\u4FDD\u5B58\u5931\u8D25\u3002" }));
13570
+ }
13571
+ };
13134
13572
  const cancel = async (id2) => {
13135
13573
  if (typeof cancelOfficialToolInstall !== "function" || jobs[id2]?.status !== "running" || jobs[id2]?.cancelRequested) return;
13136
13574
  setRowErrors((previous) => ({ ...previous, [id2]: "" }));
@@ -13147,24 +13585,50 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, cancelOffic
13147
13585
  const byId = Object.fromEntries(probeState.probes.map((probe) => [probe.id, probe]));
13148
13586
  const capabilitiesById = Object.fromEntries(probeState.capabilities.map((item) => [item.id, item]));
13149
13587
  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: () => {
13588
+ const installById = Object.fromEntries((probeState.install?.tools ?? []).map((item) => [item.id, item]));
13589
+ 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
13590
  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);
13591
+ } }, "\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) => {
13592
+ const summary = installById[tool.id];
13593
+ const command = summary?.command ?? installCommandLine(tool);
13594
+ const methods = summary?.methods ?? installMethodsFor(tool).map((method) => ({ id: method.id, label: method.label }));
13154
13595
  const probe = byId[tool.id];
13155
13596
  const capability = capabilitiesById[tool.id];
13156
13597
  const readiness = readinessById[tool.id];
13157
13598
  const job = jobs[tool.id];
13158
13599
  const running = job?.status === "running";
13159
13600
  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: () => {
13601
+ const maintenance = toolMaintenanceActions({
13602
+ tool,
13603
+ probe,
13604
+ job,
13605
+ probeStatus: probeState.status,
13606
+ summary: { installable: !summary?.error, removable: !summary?.error }
13607
+ });
13608
+ const verified = job?.status === "succeeded" && (job.operation === "uninstall" ? job.postInstallProbe?.installed === false : job.postInstallProbe?.installed === true);
13609
+ const operation = job?.operation === "uninstall" ? "\u5378\u8F7D" : job?.operation === "repair" ? "\u4FEE\u590D" : "\u5B89\u88C5";
13610
+ 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);
13611
+ 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(
13612
+ "select",
13613
+ {
13614
+ className: "mr-mini-select",
13615
+ value: summary?.savedMethodId ?? summary?.methodId ?? "",
13616
+ disabled: running || methods.length === 0,
13617
+ onChange: (event) => {
13618
+ void setInstallMethod(tool.id, event.target.value);
13619
+ }
13620
+ },
13621
+ 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` : ""))
13622
+ ))), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-tool-button", type: "button", disabled: action.disabled, onClick: () => {
13163
13623
  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: () => {
13624
+ } }, 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: () => {
13625
+ void repair(tool.id);
13626
+ } }, 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: () => {
13627
+ void uninstall(tool.id);
13628
+ } }, 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
13629
  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")));
13630
+ } }, job.cancelRequested ? "\u6B63\u5728\u53D6\u6D88\u2026" : `\u53D6\u6D88${operation}`)));
13631
+ })), /* @__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
13632
  }
13169
13633
  var HEADLESS_TOOLS = /* @__PURE__ */ new Set(["claude-code", "codex", "gemini"]);
13170
13634
  var WORKSPACE_VIEWS = [
@@ -13291,7 +13755,7 @@ ${reasons.map((entry, index) => `${index + 1}. ${entry.text}`).join("\n")}
13291
13755
  }, []);
13292
13756
  return { health, ledger, boundaries, busy, refreshHealth, refreshLedger, refreshBoundaries, finishOnboarding, rate, rerun };
13293
13757
  }
13294
- function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, installOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
13758
+ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
13295
13759
  const workbench = useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries });
13296
13760
  const [catalogState, setCatalogState] = import_react5.default.useState({ status: "loading", catalog: null, error: "" });
13297
13761
  const [task, setTask] = import_react5.default.useState("");
@@ -13514,9 +13978,12 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, install
13514
13978
  {
13515
13979
  listOfficialTools,
13516
13980
  installOfficialTool,
13981
+ uninstallOfficialTool,
13982
+ repairOfficialTool,
13517
13983
  cancelOfficialToolInstall,
13518
13984
  officialToolInstallStatus,
13519
13985
  onProbes: handleToolProbes,
13986
+ settingsScope,
13520
13987
  health: workbench.health.report,
13521
13988
  onRefreshHealth: () => {
13522
13989
  void workbench.refreshHealth(true);
@@ -13567,6 +14034,26 @@ var toolIdCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#OfficialToolId`,
13567
14034
  }
13568
14035
  return value;
13569
14036
  });
14037
+ var MAX_METHOD_ID_CHARS = 40;
14038
+ var text6 = (value) => typeof value === "string" ? value.trim() : "";
14039
+ var toolActionCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#OfficialToolAction`, (value) => {
14040
+ const request = plainObject2(value, "tool action request");
14041
+ for (const key of Object.keys(request)) {
14042
+ if (key !== "tool" && key !== "method") throw new TypeError(`unknown field ${key} in tool action request`);
14043
+ }
14044
+ const tool = getOfficialTool(text6(request.tool));
14045
+ if (!tool) throw new TypeError("tool must name a fixed official tool");
14046
+ const method = request.method === void 0 || request.method === null || request.method === "" ? null : text6(request.method);
14047
+ if (method !== null) {
14048
+ if (method.length > MAX_METHOD_ID_CHARS || !/^[A-Za-z0-9-]+$/.test(method)) {
14049
+ throw new TypeError("method must be one of the tool's declared install methods");
14050
+ }
14051
+ if (!installMethodsFor(tool).some((item) => item.id === method)) {
14052
+ throw new TypeError(`${tool.label} does not offer the install method ${method}`);
14053
+ }
14054
+ }
14055
+ return method === null ? { tool: tool.id } : { tool: tool.id, method };
14056
+ });
13570
14057
  var listResultCodec = strictCodec(`${OFFICIAL_TOOLS_REMOTE_PACKAGE}#OfficialToolList`, (value) => {
13571
14058
  const result = plainObject2(value, "tool list result");
13572
14059
  if (!Array.isArray(result.tools)) throw new TypeError("tool list result needs a tools array");
@@ -13680,7 +14167,9 @@ var toolIdParameter = Object.freeze({
13680
14167
  var jsonParameter = (name, codec) => Object.freeze({ name, wire: name, source: "json", codec });
13681
14168
  var OFFICIAL_TOOLS_REMOTE_DESCRIPTORS = Object.freeze([
13682
14169
  descriptor("list", [], listResultCodec),
13683
- descriptor("installTool", [toolIdParameter], installResultCodec),
14170
+ descriptor("installTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
14171
+ descriptor("uninstallTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
14172
+ descriptor("repairTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
13684
14173
  descriptor("cancel", [toolIdParameter], installResultCodec),
13685
14174
  descriptor("status", [toolIdParameter], statusResultCodec),
13686
14175
  // Workbench: onboarding health check, run ledger, ratings, step retry, security boundaries.
@@ -13744,8 +14233,8 @@ function boundedNumberField(field2, { minimum = 0, maximum = Number.MAX_SAFE_INT
13744
14233
  const numeric = (0, import_dsh_client_ui_primitives.settingsNumberField)(field2);
13745
14234
  return {
13746
14235
  ...numeric,
13747
- parse: (text6) => {
13748
- const write = numeric.parse(text6);
14236
+ parse: (text7) => {
14237
+ const write = numeric.parse(text7);
13749
14238
  if (write?.kind !== "set") return write;
13750
14239
  const value = write.value;
13751
14240
  if (typeof value !== "number" || value < minimum || value > maximum) return void 0;
@@ -13758,10 +14247,10 @@ function modelProfilesField() {
13758
14247
  const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("modelProfilesJson");
13759
14248
  return {
13760
14249
  ...field2,
13761
- parse: (text6) => {
14250
+ parse: (text7) => {
13762
14251
  try {
13763
- parseModelProfilesJson(text6);
13764
- return field2.parse(text6);
14252
+ parseModelProfilesJson(text7);
14253
+ return field2.parse(text7);
13765
14254
  } catch {
13766
14255
  return void 0;
13767
14256
  }
@@ -13772,15 +14261,79 @@ function quotaPatternsField() {
13772
14261
  const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("quotaPatternsJson");
13773
14262
  return {
13774
14263
  ...field2,
13775
- parse: (text6) => {
13776
- const raw = String(text6 ?? "").trim() || "{}";
14264
+ parse: (text7) => {
14265
+ const raw = String(text7 ?? "").trim() || "{}";
13777
14266
  try {
13778
14267
  const value = JSON.parse(raw);
13779
14268
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
13780
14269
  } catch {
13781
14270
  return void 0;
13782
14271
  }
13783
- return parseQuotaPatterns(raw).errors.length ? void 0 : field2.parse(text6);
14272
+ return parseQuotaPatterns(raw).errors.length ? void 0 : field2.parse(text7);
14273
+ }
14274
+ };
14275
+ }
14276
+ function installDirField() {
14277
+ const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("toolInstallDir");
14278
+ return {
14279
+ ...field2,
14280
+ parse: (text7) => {
14281
+ if (!String(text7 ?? "").trim()) return field2.parse(text7);
14282
+ try {
14283
+ expandInstallDir(text7);
14284
+ } catch {
14285
+ return void 0;
14286
+ }
14287
+ return field2.parse(text7);
14288
+ }
14289
+ };
14290
+ }
14291
+ function npmRegistryField() {
14292
+ const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("toolNpmRegistry");
14293
+ return {
14294
+ ...field2,
14295
+ parse: (text7) => {
14296
+ const raw = String(text7 ?? "").trim() || DEFAULT_NPM_REGISTRY;
14297
+ try {
14298
+ normalizeSourceUrl(raw, "npm \u6E90");
14299
+ } catch {
14300
+ return void 0;
14301
+ }
14302
+ return field2.parse(text7);
14303
+ }
14304
+ };
14305
+ }
14306
+ function scriptUrlsField() {
14307
+ const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("toolScriptUrlsJson");
14308
+ return {
14309
+ ...field2,
14310
+ parse: (text7) => {
14311
+ const raw = String(text7 ?? "").trim() || "{}";
14312
+ try {
14313
+ const parsed = JSON.parse(raw);
14314
+ parseInstallPreferences({ toolScriptUrlsJson: raw }, { tools: OFFICIAL_TOOLS });
14315
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
14316
+ } catch {
14317
+ return void 0;
14318
+ }
14319
+ return field2.parse(text7);
14320
+ }
14321
+ };
14322
+ }
14323
+ function installMethodsField() {
14324
+ const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("toolInstallMethodsJson");
14325
+ return {
14326
+ ...field2,
14327
+ parse: (text7) => {
14328
+ const raw = String(text7 ?? "").trim() || "{}";
14329
+ try {
14330
+ const parsed = JSON.parse(raw);
14331
+ parseInstallPreferences({ toolInstallMethodsJson: raw }, { tools: OFFICIAL_TOOLS });
14332
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
14333
+ } catch {
14334
+ return void 0;
14335
+ }
14336
+ return field2.parse(text7);
13784
14337
  }
13785
14338
  };
13786
14339
  }
@@ -13794,7 +14347,11 @@ var RouterSettingsCardController = class {
13794
14347
  boundedNumberField("reviewSampleRate", { minimum: 0, maximum: 1 }),
13795
14348
  boundedNumberField("subscriptionCooldownMinutes", { minimum: 1, maximum: 10080, integer: true }),
13796
14349
  modelProfilesField(),
13797
- quotaPatternsField()
14350
+ quotaPatternsField(),
14351
+ installDirField(),
14352
+ npmRegistryField(),
14353
+ installMethodsField(),
14354
+ scriptUrlsField()
13798
14355
  ]);
13799
14356
  this.store = this.form.bind(() => ({
13800
14357
  ...this.form.shell(),
@@ -13805,7 +14362,11 @@ var RouterSettingsCardController = class {
13805
14362
  reviewSampleRate: this.form.field("reviewSampleRate"),
13806
14363
  subscriptionCooldownMinutes: this.form.field("subscriptionCooldownMinutes"),
13807
14364
  modelProfilesJson: this.form.field("modelProfilesJson"),
13808
- quotaPatternsJson: this.form.field("quotaPatternsJson")
14365
+ quotaPatternsJson: this.form.field("quotaPatternsJson"),
14366
+ toolInstallDir: this.form.field("toolInstallDir"),
14367
+ toolNpmRegistry: this.form.field("toolNpmRegistry"),
14368
+ toolInstallMethodsJson: this.form.field("toolInstallMethodsJson"),
14369
+ toolScriptUrlsJson: this.form.field("toolScriptUrlsJson")
13809
14370
  }));
13810
14371
  }
13811
14372
  /** Supply the snapshot hook and staged form actions to the Plugins slot. */
@@ -13838,8 +14399,8 @@ function RouterSettingsCard(props) {
13838
14399
  overriddenLabel: FIELD_COPY.overridden,
13839
14400
  resetLabel: FIELD_COPY.reset,
13840
14401
  invalidLabel: FIELD_COPY.invalidNumber,
13841
- onEdit: (text6) => {
13842
- props.edit("budgetUsd", text6);
14402
+ onEdit: (text7) => {
14403
+ props.edit("budgetUsd", text7);
13843
14404
  },
13844
14405
  onReset: () => {
13845
14406
  props.resetField("budgetUsd");
@@ -13857,8 +14418,8 @@ function RouterSettingsCard(props) {
13857
14418
  overriddenLabel: FIELD_COPY.overridden,
13858
14419
  resetLabel: FIELD_COPY.reset,
13859
14420
  invalidLabel: FIELD_COPY.invalidNumber,
13860
- onEdit: (text6) => {
13861
- props.edit("maxConsultOutputChars", text6);
14421
+ onEdit: (text7) => {
14422
+ props.edit("maxConsultOutputChars", text7);
13862
14423
  },
13863
14424
  onReset: () => {
13864
14425
  props.resetField("maxConsultOutputChars");
@@ -13881,8 +14442,8 @@ function RouterSettingsCard(props) {
13881
14442
  overriddenLabel: FIELD_COPY.overridden,
13882
14443
  resetLabel: FIELD_COPY.reset,
13883
14444
  invalidLabel: FIELD_COPY.invalidNumber,
13884
- onEdit: (text6) => {
13885
- props.edit(key, text6);
14445
+ onEdit: (text7) => {
14446
+ props.edit(key, text7);
13886
14447
  },
13887
14448
  onReset: () => {
13888
14449
  props.resetField(key);
@@ -13929,7 +14490,82 @@ function RouterSettingsCard(props) {
13929
14490
  ), 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
14491
  "kimi-code": { "quota": ["\u989D\u5EA6\u5DF2\u7528\u5B8C"], "rateLimit": ["\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41"] },
13931
14492
  "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")));
14493
+ }`)), /* @__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(
14494
+ import_dsh_client_ui_primitives.SettingsValueField,
14495
+ {
14496
+ id: "model-router-tool-install-dir",
14497
+ label: "\u7EDF\u4E00\u5B89\u88C5\u76EE\u5F55",
14498
+ hint: "\u7559\u7A7A\u8868\u793A\u4F7F\u7528\u5404\u5382\u5546\u9ED8\u8BA4\u4F4D\u7F6E\uFF08npm \u5168\u5C40\u76EE\u5F55 / \u5382\u5546\u811A\u672C\u76EE\u5F55\uFF09\u3002npm \u4E0E pnpm \u65B9\u5F0F\u4F1A\u5E26\u4E0A --prefix\uFF1B\u5382\u5546\u5B89\u88C5\u811A\u672C\u662F\u5426\u652F\u6301\u76EE\u5F55\u53C2\u6570\u53D6\u51B3\u4E8E\u8BE5\u5382\u5546\u3002\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u3002",
14499
+ disabled,
14500
+ ...state.toolInstallDir,
14501
+ overriddenLabel: FIELD_COPY.overridden,
14502
+ resetLabel: FIELD_COPY.reset,
14503
+ invalidLabel: "\u8BF7\u8F93\u5165\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u6216\u7559\u7A7A\u6062\u590D\u9ED8\u8BA4\u3002",
14504
+ onEdit: (value) => {
14505
+ props.edit("toolInstallDir", value);
14506
+ },
14507
+ onReset: () => {
14508
+ props.resetField("toolInstallDir");
14509
+ }
14510
+ }
14511
+ ), /* @__PURE__ */ import_react6.default.createElement(
14512
+ import_dsh_client_ui_primitives.SettingsValueField,
14513
+ {
14514
+ id: "model-router-tool-npm-registry",
14515
+ label: "npm \u6E90\u5730\u5740",
14516
+ 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",
14517
+ disabled,
14518
+ ...state.toolNpmRegistry,
14519
+ overriddenLabel: FIELD_COPY.overridden,
14520
+ resetLabel: FIELD_COPY.reset,
14521
+ invalidLabel: "\u9700\u8981 https \u5F00\u5934\u7684\u5B8C\u6574\u5730\u5740\u3002",
14522
+ onEdit: (value) => {
14523
+ props.edit("toolNpmRegistry", value);
14524
+ },
14525
+ onReset: () => {
14526
+ props.resetField("toolNpmRegistry");
14527
+ }
14528
+ }
14529
+ ), /* @__PURE__ */ import_react6.default.createElement(
14530
+ import_dsh_client_ui_primitives.SettingsValueField,
14531
+ {
14532
+ id: "model-router-tool-install-methods",
14533
+ label: "\u9ED8\u8BA4\u5B89\u88C5\u65B9\u5F0F\uFF08JSON\uFF0C\u53EF\u9009\uFF09",
14534
+ 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",
14535
+ disabled,
14536
+ ...state.toolInstallMethodsJson,
14537
+ overriddenLabel: FIELD_COPY.overridden,
14538
+ resetLabel: FIELD_COPY.reset,
14539
+ 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",
14540
+ onEdit: (value) => {
14541
+ props.edit("toolInstallMethodsJson", value);
14542
+ },
14543
+ onReset: () => {
14544
+ props.resetField("toolInstallMethodsJson");
14545
+ }
14546
+ }
14547
+ ), /* @__PURE__ */ import_react6.default.createElement(
14548
+ import_dsh_client_ui_primitives.SettingsValueField,
14549
+ {
14550
+ id: "model-router-tool-script-urls",
14551
+ label: "\u5B89\u88C5\u811A\u672C\u6E90\u8986\u76D6\uFF08JSON\uFF0C\u53EF\u9009\uFF09",
14552
+ 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",
14553
+ disabled,
14554
+ ...state.toolScriptUrlsJson,
14555
+ overriddenLabel: FIELD_COPY.overridden,
14556
+ resetLabel: FIELD_COPY.reset,
14557
+ invalidLabel: "\u9700\u8981 JSON \u5BF9\u8C61\uFF0C\u952E\u662F\u5B98\u65B9\u5DE5\u5177 ID\uFF0C\u503C\u662F https \u94FE\u63A5\u3002",
14558
+ onEdit: (value) => {
14559
+ props.edit("toolScriptUrlsJson", value);
14560
+ },
14561
+ onReset: () => {
14562
+ props.resetField("toolScriptUrlsJson");
14563
+ }
14564
+ }
14565
+ ), /* @__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, `{
14566
+ "stepcode": "https://mirror.example.com/stepcode/install.ps1",
14567
+ "opencode": "https://mirror.example.com/opencode/install"
14568
+ }`), /* @__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
14569
  }
13934
14570
  function OpenRouterWorkspace({ subject, openPanel }) {
13935
14571
  if (subject?.kind !== "bundle" || subject.pkg?.name !== ROUTER_PACKAGE) return null;
@@ -13954,7 +14590,9 @@ function registerUi(ctx) {
13954
14590
  loadCatalog: () => ctx.remote.session.modelCatalog(),
13955
14591
  settingsScope,
13956
14592
  listOfficialTools: () => officialToolsRemote.list(),
13957
- installOfficialTool: (toolId) => officialToolsRemote.installTool(toolId),
14593
+ installOfficialTool: (request) => officialToolsRemote.installTool(request),
14594
+ uninstallOfficialTool: (request) => officialToolsRemote.uninstallTool(request),
14595
+ repairOfficialTool: (request) => officialToolsRemote.repairTool(request),
13958
14596
  cancelOfficialToolInstall: (toolId) => officialToolsRemote.cancel(toolId),
13959
14597
  officialToolInstallStatus: (toolId) => officialToolsRemote.status(toolId),
13960
14598
  toolHealth: (fresh) => officialToolsRemote.health(fresh),