@ljwei-stak/dsh-model-router 0.17.0 → 0.18.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.
@@ -47,7 +47,7 @@ var import_react6 = __toESM(require("react"), 1);
47
47
 
48
48
  // .dsh-plugin/shared/official-tool-registry.mjs
49
49
  var DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org/";
50
- function packageManagerMethod(id2, label, manager, spec, { registryFlag = "--registry=" } = {}) {
50
+ function packageManagerMethod(id2, label, manager, spec, { registryFlag = "--registry=", command = "install", extraArgs = [] } = {}) {
51
51
  return Object.freeze({
52
52
  id: id2,
53
53
  label,
@@ -55,14 +55,31 @@ function packageManagerMethod(id2, label, manager, spec, { registryFlag = "--reg
55
55
  manager,
56
56
  spec,
57
57
  registryFlag,
58
- installArgs: Object.freeze(["install", "-g", spec]),
58
+ installArgs: Object.freeze([command, "-g", spec, ...extraArgs]),
59
59
  uninstallArgs: Object.freeze(["uninstall", "-g", spec.replace(/@latest$/, "")]),
60
- /** npm/pnpm both accept --prefix to place a global install off the default. */
60
+ /** The resolver binds npm prefix or pnpm global-dir/global-bin-dir. */
61
61
  supportsInstallDir: true,
62
62
  supportsRegistry: true
63
63
  });
64
64
  }
65
- function scriptMethod(id2, label, { shell, scriptUrl, verify, installArg = [], env = [], platforms, supportsInstallDir = false }) {
65
+ function scriptMethod(id2, label, {
66
+ shell,
67
+ scriptUrl,
68
+ verify,
69
+ installArg = [],
70
+ env = [],
71
+ platforms,
72
+ supportsInstallDir = false,
73
+ installDirEnv = "",
74
+ installDirSuffix = "",
75
+ defaultScriptInstallDir,
76
+ defaultScriptInstallDirEnv,
77
+ scriptBinaryNames,
78
+ registryEnv,
79
+ installDirSubdir,
80
+ uninstallUnsupportedReason,
81
+ scriptFallbackUrls
82
+ }) {
66
83
  return Object.freeze({
67
84
  id: id2,
68
85
  label,
@@ -77,7 +94,16 @@ function scriptMethod(id2, label, { shell, scriptUrl, verify, installArg = [], e
77
94
  platforms: Object.freeze(platforms),
78
95
  /** Whether the vendor script honours a caller-chosen directory at all. */
79
96
  supportsInstallDir,
80
- supportsRegistry: false
97
+ installDirEnv,
98
+ installDirSuffix,
99
+ ...installDirSubdir ? { installDirSubdir } : {},
100
+ ...uninstallUnsupportedReason ? { uninstallUnsupportedReason } : {},
101
+ ...defaultScriptInstallDir ? { defaultScriptInstallDir } : {},
102
+ ...defaultScriptInstallDirEnv ? { defaultScriptInstallDirEnv } : {},
103
+ ...scriptBinaryNames ? { scriptBinaryNames: Object.freeze(scriptBinaryNames) } : {},
104
+ ...scriptFallbackUrls ? { scriptFallbackUrls: Object.freeze(scriptFallbackUrls) } : {},
105
+ ...registryEnv ? { registryEnv } : {},
106
+ supportsRegistry: Boolean(registryEnv)
81
107
  });
82
108
  }
83
109
  var OFFICIAL_TOOLS = Object.freeze([
@@ -93,9 +119,33 @@ var OFFICIAL_TOOLS = Object.freeze([
93
119
  probeExecutables: ["kimi"],
94
120
  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",
95
121
  providerHints: ["moonshot", "kimi"],
122
+ scriptBinaryNames: Object.freeze(["kimi", "kimi.exe"]),
123
+ defaultScriptInstallDir: ".kimi-code/bin",
96
124
  installMethods: Object.freeze([
97
125
  packageManagerMethod("npm", "npm", "npm", "@moonshot-ai/kimi-code@latest"),
98
- packageManagerMethod("pnpm", "pnpm", "pnpm", "@moonshot-ai/kimi-code@latest")
126
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@moonshot-ai/kimi-code@latest"),
127
+ scriptMethod("script-bash", "curl | bash", {
128
+ shell: "bash",
129
+ scriptUrl: "https://code.kimi.com/kimi-code/install.sh",
130
+ platforms: ["linux", "darwin"],
131
+ supportsInstallDir: true,
132
+ installDirEnv: "KIMI_INSTALL_DIR",
133
+ installDirSuffix: "bin",
134
+ env: ["KIMI_INSTALL_DIR"],
135
+ scriptBinaryNames: ["kimi"],
136
+ verify: Object.freeze({ mustInclude: ["kimi-code", "code.kimi.com/kimi-code"] })
137
+ }),
138
+ scriptMethod("script-powershell", "irm | iex", {
139
+ shell: "powershell",
140
+ scriptUrl: "https://code.kimi.com/kimi-code/install.ps1",
141
+ platforms: ["win32"],
142
+ supportsInstallDir: true,
143
+ installDirEnv: "KIMI_INSTALL_DIR",
144
+ installDirSuffix: "bin",
145
+ env: ["KIMI_INSTALL_DIR"],
146
+ scriptBinaryNames: ["kimi.exe"],
147
+ verify: Object.freeze({ mustInclude: ["kimi-code", "code.kimi.com/kimi-code"] })
148
+ })
99
149
  ])
100
150
  }),
101
151
  Object.freeze({
@@ -109,9 +159,31 @@ var OFFICIAL_TOOLS = Object.freeze([
109
159
  uninstallArgs: ["uninstall", "-g", "@anthropic-ai/claude-code"],
110
160
  probeExecutables: ["claude"],
111
161
  providerHints: ["anthropic", "claude"],
162
+ scriptBinaryNames: Object.freeze(["claude", "claude.exe"]),
163
+ defaultScriptInstallDir: ".local/bin",
112
164
  installMethods: Object.freeze([
113
165
  packageManagerMethod("npm", "npm", "npm", "@anthropic-ai/claude-code@latest"),
114
- packageManagerMethod("pnpm", "pnpm", "pnpm", "@anthropic-ai/claude-code@latest")
166
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@anthropic-ai/claude-code@latest", {
167
+ command: "add",
168
+ extraArgs: ["--ignore-scripts=false", "--allow-build=@anthropic-ai/claude-code"]
169
+ }),
170
+ // The native installer has no directory argument. npm/pnpm support a prefix.
171
+ scriptMethod("script-bash", "curl | bash", {
172
+ shell: "bash",
173
+ scriptUrl: "https://claude.ai/install.sh",
174
+ scriptFallbackUrls: ["https://downloads.claude.ai/claude-code-releases/bootstrap.sh"],
175
+ platforms: ["linux", "darwin"],
176
+ scriptBinaryNames: ["claude"],
177
+ verify: Object.freeze({ mustInclude: ["downloads.claude.ai/claude-code-releases", "manifest.json"] })
178
+ }),
179
+ scriptMethod("script-powershell", "irm | iex", {
180
+ shell: "powershell",
181
+ scriptUrl: "https://claude.ai/install.ps1",
182
+ scriptFallbackUrls: ["https://downloads.claude.ai/claude-code-releases/bootstrap.ps1"],
183
+ platforms: ["win32"],
184
+ scriptBinaryNames: ["claude.exe"],
185
+ verify: Object.freeze({ mustInclude: ["downloads.claude.ai/claude-code-releases", "manifest.json"] })
186
+ })
115
187
  ])
116
188
  }),
117
189
  Object.freeze({
@@ -126,9 +198,36 @@ var OFFICIAL_TOOLS = Object.freeze([
126
198
  probeExecutables: ["codex"],
127
199
  probeNote: "Codex \u7248\u672C\u6A2A\u5E45\u7531\u9002\u914D\u5C42\u5BBD\u5339\u914D\uFF1B\u5B89\u88C5\u65F6\u53D6 npm \u6700\u65B0\u7248\u3002",
128
200
  providerHints: ["openai", "gpt", "codex"],
201
+ scriptBinaryNames: Object.freeze(["codex", "codex.exe"]),
202
+ defaultScriptInstallDir: ".local/bin",
129
203
  installMethods: Object.freeze([
130
204
  packageManagerMethod("npm", "npm", "npm", "@openai/codex@latest"),
131
- packageManagerMethod("pnpm", "pnpm", "pnpm", "@openai/codex@latest")
205
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@openai/codex@latest"),
206
+ scriptMethod("script-sh", "curl | sh", {
207
+ shell: "sh",
208
+ scriptUrl: "https://chatgpt.com/codex/install.sh",
209
+ platforms: ["linux", "darwin"],
210
+ supportsInstallDir: true,
211
+ installDirEnv: "CODEX_INSTALL_DIR",
212
+ env: ["CODEX_INSTALL_DIR"],
213
+ scriptBinaryNames: ["codex"],
214
+ verify: Object.freeze({ mustInclude: ["releases.openai.com/codex", "CODEX_INSTALL_DIR"] })
215
+ }),
216
+ scriptMethod("script-powershell", "irm | iex", {
217
+ shell: "powershell",
218
+ scriptUrl: "https://chatgpt.com/codex/install.ps1",
219
+ platforms: ["win32"],
220
+ supportsInstallDir: true,
221
+ installDirEnv: "CODEX_INSTALL_DIR",
222
+ env: ["CODEX_INSTALL_DIR"],
223
+ defaultScriptInstallDirEnv: "LOCALAPPDATA",
224
+ defaultScriptInstallDir: "Programs/OpenAI/Codex/bin",
225
+ installDirSubdir: "codex/bin",
226
+ uninstallUnsupportedReason: "Codex \u5B98\u65B9 Windows \u811A\u672C\u4F7F\u7528\u76EE\u5F55\u8054\u63A5\u548C\u7248\u672C\u5316\u8D44\u6E90\uFF1B\u8BF7\u6309\u5B98\u65B9\u5378\u8F7D\u8BF4\u660E\u79FB\u9664\uFF0C\u63D2\u4EF6\u4E0D\u4F1A\u5220\u9664\u8054\u63A5\u76EE\u6807\u3002",
227
+ // The Windows bin directory is a junction to the versioned payload.
228
+ scriptBinaryNames: ["codex.exe"],
229
+ verify: Object.freeze({ mustInclude: ["releases.openai.com/codex", "CODEX_INSTALL_DIR"] })
230
+ })
132
231
  ])
133
232
  }),
134
233
  Object.freeze({
@@ -142,9 +241,40 @@ var OFFICIAL_TOOLS = Object.freeze([
142
241
  uninstallArgs: ["uninstall", "-g", "@minimax-ai/code"],
143
242
  probeExecutables: ["mcode"],
144
243
  providerHints: ["minimax"],
244
+ defaultMethodsByPlatform: Object.freeze({ win32: "script-powershell" }),
245
+ scriptBinaryNames: Object.freeze(["mcode", "mcode-tools"]),
246
+ defaultScriptInstallDir: ".minimax-code/bin",
145
247
  installMethods: Object.freeze([
146
- packageManagerMethod("npm", "npm", "npm", "@minimax-ai/code@latest"),
147
- packageManagerMethod("pnpm", "pnpm", "pnpm", "@minimax-ai/code@latest")
248
+ packageManagerMethod("npm", "npm", "npm", "@minimax-ai/code@latest", {
249
+ extraArgs: ["--ignore-scripts=false", "--include=optional", "--allow-scripts=@minimax-ai/code,better-sqlite3"]
250
+ }),
251
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@minimax-ai/code@latest", {
252
+ command: "add",
253
+ extraArgs: ["--ignore-scripts=false", "--allow-build=@minimax-ai/code", "--allow-build=better-sqlite3"]
254
+ }),
255
+ scriptMethod("script-bash", "curl | bash", {
256
+ shell: "bash",
257
+ scriptUrl: "https://filecdn.minimax.chat/public/install.sh",
258
+ platforms: ["linux", "darwin"],
259
+ supportsInstallDir: true,
260
+ installDirEnv: "MCODE_INSTALL_DIR",
261
+ installDirSuffix: "bin",
262
+ registryEnv: "MCODE_NPM_REGISTRY",
263
+ env: ["MCODE_INSTALL_DIR", "MCODE_NPM_REGISTRY"],
264
+ verify: Object.freeze({ mustInclude: ["MiniMax Code CLI", "@minimax-ai/code"] })
265
+ }),
266
+ scriptMethod("script-powershell", "irm | iex", {
267
+ shell: "powershell",
268
+ scriptUrl: "https://filecdn.minimax.chat/public/install.ps1",
269
+ platforms: ["win32"],
270
+ supportsInstallDir: true,
271
+ installDirEnv: "MCODE_INSTALL_DIR",
272
+ registryEnv: "MCODE_NPM_REGISTRY",
273
+ env: ["MCODE_INSTALL_DIR", "MCODE_NPM_REGISTRY"],
274
+ defaultScriptInstallDir: ".minimax-code",
275
+ scriptBinaryNames: ["mcode.cmd", "mcode.ps1", "mcode-tools.cmd", "mcode-tools.ps1"],
276
+ verify: Object.freeze({ mustInclude: ["MiniMax Code CLI", "@minimax-ai/code"] })
277
+ })
148
278
  ])
149
279
  }),
150
280
  Object.freeze({
@@ -158,9 +288,28 @@ var OFFICIAL_TOOLS = Object.freeze([
158
288
  uninstallArgs: ["uninstall", "-g", "@mimo-ai/cli"],
159
289
  probeExecutables: ["mimo"],
160
290
  providerHints: ["mimo", "xiaomi"],
291
+ scriptBinaryNames: Object.freeze(["mimo", "mimo.exe"]),
292
+ defaultScriptInstallDir: ".mimocode/bin",
161
293
  installMethods: Object.freeze([
162
294
  packageManagerMethod("npm", "npm", "npm", "@mimo-ai/cli@latest"),
163
- packageManagerMethod("pnpm", "pnpm", "pnpm", "@mimo-ai/cli@latest")
295
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@mimo-ai/cli@latest"),
296
+ scriptMethod("script-bash", "curl | bash", {
297
+ shell: "bash",
298
+ scriptUrl: "https://mimo.xiaomi.com/install",
299
+ platforms: ["linux", "darwin"],
300
+ scriptBinaryNames: ["mimo"],
301
+ verify: Object.freeze({ mustInclude: ["mimo.xiaomi.com", ".mimocode"] })
302
+ }),
303
+ scriptMethod("script-powershell", "irm | iex", {
304
+ shell: "powershell",
305
+ scriptUrl: "https://mimo.xiaomi.com/install.ps1",
306
+ platforms: ["win32"],
307
+ supportsInstallDir: true,
308
+ installDirEnv: "MIMOCODE_INSTALL_DIR",
309
+ env: ["MIMOCODE_INSTALL_DIR"],
310
+ scriptBinaryNames: ["mimo.exe"],
311
+ verify: Object.freeze({ mustInclude: ["mimo.xiaomi.com", ".mimocode"] })
312
+ })
164
313
  ])
165
314
  }),
166
315
  Object.freeze({
@@ -174,9 +323,32 @@ var OFFICIAL_TOOLS = Object.freeze([
174
323
  uninstallArgs: ["uninstall", "-g", "@xai-official/grok"],
175
324
  probeExecutables: ["grok"],
176
325
  providerHints: ["xai", "grok"],
326
+ scriptBinaryNames: Object.freeze(["grok", "agent", "grok.exe", "agent.exe", "grove.exe", "grove-fsmonitor.exe", "grove-credential.exe"]),
327
+ defaultScriptInstallDir: ".grok/bin",
328
+ pathProfileMarkers: Object.freeze(["# >>> grok installer >>>", "# <<< grok installer <<<"]),
177
329
  installMethods: Object.freeze([
178
330
  packageManagerMethod("npm", "npm", "npm", "@xai-official/grok@latest"),
179
- packageManagerMethod("pnpm", "pnpm", "pnpm", "@xai-official/grok@latest")
331
+ packageManagerMethod("pnpm", "pnpm", "pnpm", "@xai-official/grok@latest"),
332
+ scriptMethod("script-bash", "curl | bash", {
333
+ shell: "bash",
334
+ scriptUrl: "https://x.ai/cli/install.sh",
335
+ platforms: ["linux", "darwin"],
336
+ supportsInstallDir: true,
337
+ installDirEnv: "GROK_BIN_DIR",
338
+ env: ["GROK_BIN_DIR"],
339
+ scriptBinaryNames: ["grok", "agent"],
340
+ verify: Object.freeze({ mustInclude: ["Grok CLI installer", "x.ai/cli"] })
341
+ }),
342
+ scriptMethod("script-powershell", "irm | iex", {
343
+ shell: "powershell",
344
+ scriptUrl: "https://x.ai/cli/install.ps1",
345
+ platforms: ["win32"],
346
+ supportsInstallDir: true,
347
+ installDirEnv: "GROK_BIN_DIR",
348
+ env: ["GROK_BIN_DIR"],
349
+ scriptBinaryNames: ["grok.exe", "agent.exe", "grove.exe", "grove-fsmonitor.exe", "grove-credential.exe"],
350
+ verify: Object.freeze({ mustInclude: ["Grok CLI installer", "x.ai/cli"] })
351
+ })
180
352
  ])
181
353
  }),
182
354
  Object.freeze({
@@ -306,7 +478,8 @@ function installMethodsFor(tool) {
306
478
  function installMethodFor(tool, methodId) {
307
479
  const wanted = String(methodId ?? "").trim();
308
480
  if (!wanted) return null;
309
- return installMethodsFor(tool).find((method) => method.id === wanted) ?? null;
481
+ const canonical = tool?.id === "codex" && wanted === "script-bash" ? "script-sh" : wanted;
482
+ return installMethodsFor(tool).find((method) => method.id === canonical) ?? null;
310
483
  }
311
484
  function defaultInstallMethod(tool) {
312
485
  return installMethodsFor(tool)[0] ?? null;
@@ -320,8 +493,8 @@ function installCommandLineFor(tool, method) {
320
493
  if (!tool || tool.unsupported) return null;
321
494
  if (!method) return null;
322
495
  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;
496
+ const piped = method.shell === "powershell" ? `irm ${method.scriptUrl} | iex` : `curl -fsSL ${method.scriptUrl} | ${method.shell}`;
497
+ return piped;
325
498
  }
326
499
  return `${method.manager} ${method.installArgs.join(" ")}`;
327
500
  }
@@ -651,9 +824,9 @@ function keepKnownToolIds(raw, known, subject) {
651
824
  }
652
825
  return raw;
653
826
  }
654
- function parseInstallPreferences(config = {}, { tools = [] } = {}) {
827
+ function parseInstallPreferences(config = {}, { tools = [], home = "", env = {} } = {}) {
655
828
  const known = new Set(tools.map((tool) => tool.id));
656
- const installDir = expandInstallDir(valueOf(config, "toolInstallDir", ""));
829
+ const installDir = expandInstallDir(valueOf(config, "toolInstallDir", ""), { home, env });
657
830
  const registry = normalizeSourceUrl(valueOf(config, "toolNpmRegistry", DEFAULT_NPM_REGISTRY), "npm \u6E90") || DEFAULT_NPM_REGISTRY;
658
831
  const methodRaw = keepKnownToolIds(
659
832
  parseJsonObject(valueOf(config, "toolInstallMethodsJson", "{}"), "\u5B89\u88C5\u65B9\u5F0F\u8986\u76D6"),
@@ -670,9 +843,11 @@ function parseInstallPreferences(config = {}, { tools = [] } = {}) {
670
843
  for (const [id2, methodId] of Object.entries(methodRaw)) methods[id2] = methodId.trim();
671
844
  for (const [id2, url] of Object.entries(scriptRaw)) scriptUrls[id2] = normalizeSourceUrl(url, `${id2} \u5B89\u88C5\u811A\u672C\u6E90`);
672
845
  for (const [id2, methodId] of Object.entries(methods)) {
673
- if (!installMethodFor({ id: id2, installMethods: tools.find((tool) => tool.id === id2)?.installMethods }, methodId)) {
846
+ const selected = installMethodFor({ id: id2, installMethods: tools.find((tool) => tool.id === id2)?.installMethods }, methodId);
847
+ if (!selected) {
674
848
  throw new Error(`${id2} \u4E0D\u652F\u6301\u5B89\u88C5\u65B9\u5F0F ${methodId}\u3002`);
675
849
  }
850
+ methods[id2] = selected.id;
676
851
  }
677
852
  return {
678
853
  installDir,
@@ -2167,6 +2342,23 @@ function compareReleaseVersions(left, right) {
2167
2342
  }
2168
2343
 
2169
2344
  // .dsh-plugin/client/tool-install-state.mjs
2345
+ var INSTALL_DEFAULTS = Object.freeze({
2346
+ toolInstallDir: "",
2347
+ toolNpmRegistry: DEFAULT_NPM_REGISTRY,
2348
+ toolScriptUrlsJson: "{}",
2349
+ toolAllowScriptInstall: true
2350
+ });
2351
+ function validateToolInstallSetting(key, value) {
2352
+ try {
2353
+ parseInstallPreferences({ [key]: value }, { tools: OFFICIAL_TOOLS });
2354
+ return "";
2355
+ } catch (error) {
2356
+ return error?.message || "\u5B89\u88C5\u8BBE\u7F6E\u65E0\u6548\u3002";
2357
+ }
2358
+ }
2359
+ function retainToolInstallDrafts(drafts, values = {}) {
2360
+ return Object.fromEntries(Object.entries(drafts).filter(([key, value]) => value !== (values[key] ?? INSTALL_DEFAULTS[key])));
2361
+ }
2170
2362
  function toolInstallAction({ tool, probe, readiness, job, probeStatus, latestVersion = null }) {
2171
2363
  const running = job?.status === "running";
2172
2364
  const installed = probe?.installed === true;
@@ -2201,7 +2393,7 @@ function toolMaintenanceActions({ tool, probe, job, probeStatus, summary }) {
2201
2393
  }
2202
2394
  };
2203
2395
  }
2204
- function installClickRefusal({ tool, submitting = false, running = false, operation = "install" } = {}) {
2396
+ function installClickRefusal({ tool, submitting = false, running = false, operation = "install", pendingSettings = false, installError = "" } = {}) {
2205
2397
  if (!tool) return "\u672A\u77E5\u5B98\u65B9\u5DE5\u5177\uFF0C\u65E0\u6CD5\u5F00\u59CB\u5B89\u88C5\u3002";
2206
2398
  if (tool.unsupported) return String(tool.unsupportedReason ?? "").trim() || "\u6B64\u5DE5\u5177\u6682\u4E0D\u652F\u6301\u4E00\u952E\u5B89\u88C5\u3002";
2207
2399
  if (tool.manager === "signed-windows-installer" && operation !== "install") {
@@ -2210,6 +2402,8 @@ function installClickRefusal({ tool, submitting = false, running = false, operat
2210
2402
  if (submitting || running) {
2211
2403
  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
2404
  }
2405
+ if (pendingSettings) return "\u8BF7\u5148\u4FDD\u5B58\u6216\u653E\u5F03\u5B89\u88C5\u8BBE\u7F6E\u66F4\u6539\uFF0C\u5E76\u7B49\u5F85\u5B89\u88C5\u547D\u4EE4\u5237\u65B0\u3002";
2406
+ if (installError) return installError;
2213
2407
  return "";
2214
2408
  }
2215
2409
  function payloadError(payload, fallback) {
@@ -12451,7 +12645,7 @@ var xterm_default = `/**
12451
12645
  `;
12452
12646
 
12453
12647
  // .dsh-plugin/client/host-version.mjs
12454
- var ROUTER_CLIENT_VERSION = true ? "0.17.0" : "";
12648
+ var ROUTER_CLIENT_VERSION = true ? "0.18.0" : "";
12455
12649
  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";
12456
12650
  function isMissingRemoteMethod(message) {
12457
12651
  const value = String(message ?? "");
@@ -13043,14 +13237,15 @@ var router_main_default = `.mr-workspace {
13043
13237
  .mr-tool-dot.running { background: #e9a640; }
13044
13238
  .mr-tool-command { display: block; margin-top: 6px; padding: 4px 8px; font-size: 12px; background: var(--dsw-alias-markdown-code-block); border-radius: 6px; overflow-wrap: anywhere; }
13045
13239
  .mr-tool-button { flex-shrink: 0; }
13046
- .mr-tool-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
13240
+ .mr-tool-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; flex: 0 1 420px; }
13047
13241
  .mr-tool-detail { margin: 7px 0 0; }
13048
13242
  .mr-tool-error { margin: 8px 0 0; padding: 9px 11px; }
13049
13243
  .mr-tool-log { margin-top: 7px; color: var(--mr-muted); font-size: 11px; }
13050
13244
  .mr-tool-log summary { cursor: pointer; }
13051
13245
  .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; }
13246
+ .mr-tool-methods { display: flex; align-items: center; gap: 6px; width: 100%; }
13247
+ .mr-tool-methods label { display: flex; align-items: center; justify-content: flex-end; gap: 6px; width: 100%; color: var(--mr-muted); font-size: 11px; }
13248
+ .mr-tool-methods .mr-mini-select { min-width: 0; max-width: calc(100% - 54px); }
13054
13249
  .mr-install-settings { margin-bottom: 14px; padding: 13px 14px; border: 1px solid var(--mr-line); border-radius: 11px; background: var(--mr-card); }
13055
13250
  .mr-install-settings-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
13056
13251
  .mr-install-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
@@ -13059,6 +13254,7 @@ var router_main_default = `.mr-workspace {
13059
13254
  .mr-install-field small { font-size: 11px; line-height: 1.6; }
13060
13255
  .mr-install-error { color: #d95360; }
13061
13256
  .mr-install-toggle-row { display: flex; align-items: center; gap: 7px; }
13257
+ .mr-install-save-row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
13062
13258
  .mr-channel-line { display: flex; align-items: center; gap: 8px; margin: 8px 0; flex-wrap: wrap; }
13063
13259
  .mr-pill-channel-ok { border-color: var(--dsw-alias-success, #2f9e63); color: var(--dsw-alias-success, #2f9e63); }
13064
13260
  .mr-pill-warn { border-color: var(--dsw-alias-warning, #c27c0e); color: var(--dsw-alias-warning, #c27c0e); }
@@ -13071,7 +13267,7 @@ var router_main_default = `.mr-workspace {
13071
13267
  .mr-profile-advanced summary { cursor: pointer; }
13072
13268
  .mr-profile-success { padding: 10px 12px; border-radius: 10px; background: #e5f5eb; color: #175f3a; font-size: 12px; }
13073
13269
  @media (prefers-color-scheme: dark) { .mr-profile-success { background: #193b2b; color: #b4f1cc; } }
13074
- @media (max-width: 560px) { .mr-tool { align-items: stretch; flex-direction: column; gap: 10px; } .mr-tool-button { align-self: flex-start; } }
13270
+ @media (max-width: 560px) { .mr-tool { align-items: stretch; flex-direction: column; gap: 10px; } .mr-tool-actions { flex-basis: auto; justify-content: flex-start; } .mr-tool-methods label { justify-content: flex-start; } .mr-tool-button { align-self: flex-start; } }
13075
13271
 
13076
13272
  /* \u5F00\u7BB1\u4F53\u68C0\u3001\u6210\u672C\u63A7\u5236\u3001\u6267\u884C\u8BB0\u5F55\u3001\u5B89\u5168\u8FB9\u754C */
13077
13273
  .mr-onboarding, .mr-cost { margin-bottom: 18px; }
@@ -13285,41 +13481,24 @@ var INSTALL_SETTING_FIELDS = [
13285
13481
  label: "\u7EDF\u4E00\u5B89\u88C5\u76EE\u5F55",
13286
13482
  fallback: "",
13287
13483
  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
13484
+ hint: "\u586B\u5199\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u4F8B\u5982 D:/AI/tools\u3002\u5404\u65B9\u5F0F\u7684\u5B9E\u9645\u76EE\u5F55\u4F1A\u663E\u793A\u5728\u5DE5\u5177\u547D\u4EE4\u65C1\uFF1B\u4E0D\u652F\u6301\u7EDF\u4E00\u76EE\u5F55\u7684\u65B9\u5F0F\u4F1A\u8BF4\u660E\u539F\u56E0\u3002"
13290
13485
  },
13291
13486
  {
13292
13487
  key: "toolNpmRegistry",
13293
13488
  label: "npm \u6E90\u5730\u5740",
13294
13489
  fallback: "https://registry.npmjs.org/",
13295
13490
  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/"
13491
+ hint: "\u5FC5\u987B\u4EE5 https:// \u5F00\u5934\u3002\u6240\u6709 npm / pnpm \u65B9\u5F0F\u5171\u7528\u6B64\u4E0B\u8F7D\u6E90\uFF0C\u4F8B\u5982 https://registry.npmmirror.com/\u3002"
13298
13492
  },
13299
13493
  {
13300
13494
  key: "toolScriptUrlsJson",
13301
13495
  label: "\u5B89\u88C5\u811A\u672C\u6E90\u8986\u76D6\uFF08JSON\uFF0C\u53EF\u9009\uFF09",
13302
13496
  fallback: "{}",
13303
13497
  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
- }
13498
+ hint: "\u6309\u5DE5\u5177 ID \u6307\u5B9A https \u955C\u50CF\u5730\u5740\u3002\u811A\u672C\u4E0B\u8F7D\u540E\u4ECD\u4F1A\u6838\u5BF9\u5382\u5546\u6807\u8BB0\uFF0C\u518D\u6267\u884C\u672C\u5730\u526F\u672C\u3002"
13320
13499
  }
13321
13500
  ];
13322
- function InstallSettings({ settingsScope, install }) {
13501
+ function InstallSettings({ settingsScope, install, onPendingChange }) {
13323
13502
  const [snapshot, setSnapshot] = import_react5.default.useState(() => settingsScope?.getSnapshot?.() ?? { value: {}, writable: false });
13324
13503
  const [drafts, setDrafts] = import_react5.default.useState({});
13325
13504
  const [saving, setSaving] = import_react5.default.useState(false);
@@ -13327,36 +13506,48 @@ function InstallSettings({ settingsScope, install }) {
13327
13506
  import_react5.default.useEffect(() => {
13328
13507
  if (typeof settingsScope?.subscribe !== "function") return void 0;
13329
13508
  const unsubscribe = settingsScope.subscribe(() => {
13330
- setSnapshot(settingsScope.getSnapshot());
13331
- setDrafts({});
13509
+ const next = settingsScope.getSnapshot();
13510
+ setSnapshot(next);
13511
+ setDrafts((previous) => retainToolInstallDrafts(previous, next.value));
13332
13512
  });
13333
13513
  setSnapshot(settingsScope.getSnapshot());
13334
13514
  return unsubscribe;
13335
13515
  }, [settingsScope]);
13516
+ const dirty = Object.keys(drafts).length > 0;
13517
+ import_react5.default.useEffect(() => {
13518
+ onPendingChange?.(saving || dirty);
13519
+ }, [saving, dirty, onPendingChange]);
13336
13520
  if (!settingsScope) return null;
13337
13521
  const writable = snapshot.writable === true && !saving;
13338
13522
  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) => {
13523
+ const errors = Object.fromEntries(INSTALL_SETTING_FIELDS.map((field2) => [field2.key, validateToolInstallSetting(field2.key, shown(field2))]));
13524
+ const invalid = Object.values(errors).some(Boolean);
13525
+ const edit = (key, value) => {
13526
+ const next = retainToolInstallDrafts({ ...drafts, [key]: value }, snapshot.value);
13527
+ onPendingChange?.(saving || Object.keys(next).length > 0);
13528
+ setNotice("");
13529
+ setDrafts(next);
13530
+ };
13531
+ const apply2 = async () => {
13532
+ if (!writable || !dirty || invalid) return;
13341
13533
  setSaving(true);
13534
+ onPendingChange?.(true);
13342
13535
  setNotice("");
13343
13536
  try {
13537
+ const entries = Object.entries(drafts).map(([key, value]) => ({ op: "set", path: [key], value }));
13344
13538
  const accepted = await settingsScope.mutate(entries, snapshot.revision);
13345
13539
  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);
13540
+ const next = settingsScope.getSnapshot();
13541
+ setSnapshot(next);
13542
+ setDrafts((previous) => retainToolInstallDrafts(previous, next.value));
13543
+ setNotice("\u5B89\u88C5\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002");
13347
13544
  } catch (error) {
13348
13545
  setNotice(text5(error?.message) || "\u5B89\u88C5\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25\u3002");
13349
13546
  } finally {
13350
13547
  setSaving(false);
13351
13548
  }
13352
13549
  };
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(
13550
+ 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" }, "\u4FDD\u5B58\u540E\u7528\u4E8E\u540E\u7EED\u5B89\u88C5\u4E0E\u66F4\u65B0\u3002\u6BCF\u4E2A\u5DE5\u5177\u4E0B\u65B9\u663E\u793A\u5B9E\u9645\u65B9\u5F0F\u548C\u76EE\u5F55\uFF0C\u63D2\u4EF6\u8BBE\u7F6E\u9875\u91CC\u4E5F\u80FD\u4FEE\u6539\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
13551
  "input",
13361
13552
  {
13362
13553
  className: "mr-input",
@@ -13364,37 +13555,47 @@ function InstallSettings({ settingsScope, install }) {
13364
13555
  placeholder: field2.placeholder,
13365
13556
  disabled: !writable,
13366
13557
  "aria-invalid": Boolean(errors[field2.key]),
13367
- onChange: (event) => setDrafts((previous) => ({ ...previous, [field2.key]: event.target.value })),
13368
- onBlur: () => {
13369
- void commit(field2);
13370
- }
13558
+ onChange: (event) => edit(field2.key, event.target.value)
13371
13559
  }
13372
13560
  ), 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
13561
  "input",
13374
13562
  {
13375
13563
  type: "checkbox",
13376
- checked: snapshot.value?.toolAllowScriptInstall !== false,
13564
+ checked: (drafts.toolAllowScriptInstall ?? snapshot.value?.toolAllowScriptInstall) !== false,
13377
13565
  disabled: !writable,
13378
13566
  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
- );
13567
+ edit("toolAllowScriptInstall", event.target.checked);
13384
13568
  }
13385
13569
  }
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}` : ""));
13570
+ ), /* @__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("div", { className: "mr-install-save-row" }, /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: !writable || !dirty || invalid, onClick: () => {
13571
+ void apply2();
13572
+ } }, saving ? "\u4FDD\u5B58\u4E2D\u2026" : "\u4FDD\u5B58\u5B89\u88C5\u8BBE\u7F6E"), dirty && /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: saving, onClick: () => {
13573
+ setDrafts({});
13574
+ setNotice("");
13575
+ onPendingChange?.(false);
13576
+ } }, "\u653E\u5F03\u66F4\u6539"), dirty && /* @__PURE__ */ import_react5.default.createElement("span", { className: "mr-caption" }, invalid ? "\u8BF7\u4FEE\u6B63\u8F93\u5165\u540E\u4FDD\u5B58\u3002" : "\u66F4\u6539\u5C1A\u672A\u4FDD\u5B58\uFF0C\u5B89\u88C5\u4E0E\u66F4\u65B0\u6682\u4E0D\u53EF\u7528\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
13577
  }
13388
13578
  function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth, settingsScope }) {
13389
13579
  const healthById = Object.fromEntries((health?.tools ?? []).map((item) => [item.id, item]));
13390
13580
  const [probeState, setProbeState] = import_react5.default.useState({ status: "loading", probes: [], capabilities: [], readiness: [], install: null, error: "" });
13391
13581
  const [jobs, setJobs] = import_react5.default.useState({});
13392
13582
  const [rowErrors, setRowErrors] = import_react5.default.useState({});
13583
+ const [settingsPending, setSettingsPending] = import_react5.default.useState(false);
13584
+ const [methodSaving, setMethodSaving] = import_react5.default.useState(null);
13585
+ const [previewPending, setPreviewPending] = import_react5.default.useState(false);
13586
+ const [settingsWritable, setSettingsWritable] = import_react5.default.useState(() => settingsScope?.getSnapshot?.()?.writable === true);
13587
+ const settingsPendingRef = import_react5.default.useRef(false);
13588
+ const methodSavingRef = import_react5.default.useRef(null);
13589
+ const previewPendingRef = import_react5.default.useRef(false);
13393
13590
  const mounted = import_react5.default.useRef(false);
13394
13591
  const request = import_react5.default.useRef(0);
13395
13592
  const submitting = import_react5.default.useRef(/* @__PURE__ */ new Set());
13396
13593
  const polling = import_react5.default.useRef(/* @__PURE__ */ new Set());
13397
13594
  const jobsRef = import_react5.default.useRef({});
13595
+ const handleSettingsPending = import_react5.default.useCallback((value) => {
13596
+ settingsPendingRef.current = value;
13597
+ setSettingsPending(value);
13598
+ }, []);
13398
13599
  const rememberJobs = (updater) => {
13399
13600
  setJobs((previous) => {
13400
13601
  const next = typeof updater === "function" ? updater(previous) : updater;
@@ -13419,6 +13620,8 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13419
13620
  const capabilities = Array.isArray(response.value?.executionCapabilities) ? response.value.executionCapabilities : [];
13420
13621
  const readiness = Array.isArray(response.value?.executionReadiness) ? response.value.executionReadiness : [];
13421
13622
  setProbeState({ status: "ready", probes, capabilities, readiness, install: response.value?.install ?? null, error: "" });
13623
+ previewPendingRef.current = false;
13624
+ setPreviewPending(false);
13422
13625
  onProbes({ probes, capabilities, readiness, hostVersion: typeof response.value?.hostVersion === "string" ? response.value.hostVersion : null });
13423
13626
  } catch (error) {
13424
13627
  if (!mounted.current || current !== request.current) return;
@@ -13463,6 +13666,29 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13463
13666
  request.current += 1;
13464
13667
  };
13465
13668
  }, []);
13669
+ const refreshRef = import_react5.default.useRef(refresh);
13670
+ refreshRef.current = refresh;
13671
+ import_react5.default.useEffect(() => {
13672
+ if (typeof settingsScope?.subscribe !== "function") return void 0;
13673
+ const signature2 = (snapshot) => JSON.stringify([
13674
+ snapshot?.value?.toolInstallDir,
13675
+ snapshot?.value?.toolNpmRegistry,
13676
+ snapshot?.value?.toolInstallMethodsJson,
13677
+ snapshot?.value?.toolScriptUrlsJson,
13678
+ snapshot?.value?.toolAllowScriptInstall
13679
+ ]);
13680
+ let previous = signature2(settingsScope.getSnapshot());
13681
+ return settingsScope.subscribe(() => {
13682
+ const snapshot = settingsScope.getSnapshot();
13683
+ setSettingsWritable(snapshot.writable === true);
13684
+ const next = signature2(snapshot);
13685
+ if (next === previous) return;
13686
+ previous = next;
13687
+ previewPendingRef.current = true;
13688
+ setPreviewPending(true);
13689
+ void refreshRef.current();
13690
+ });
13691
+ }, [settingsScope]);
13466
13692
  import_react5.default.useEffect(() => {
13467
13693
  const active = Object.values(jobs).filter((job) => job?.status === "running").map((job) => job.tool);
13468
13694
  if (active.length === 0 || typeof officialToolInstallStatus !== "function") return void 0;
@@ -13503,18 +13729,24 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13503
13729
  }, [jobs, officialToolInstallStatus]);
13504
13730
  const run = async (id2, operation) => {
13505
13731
  const tool = OFFICIAL_TOOLS.find((item) => item.id === id2);
13732
+ const summary = (probeState.install?.tools ?? []).find((item) => item.id === id2);
13506
13733
  const bridge = { install: installOfficialTool, repair: repairOfficialTool, uninstall: uninstallOfficialTool }[operation];
13507
13734
  const refusal = installClickRefusal({
13508
13735
  tool,
13509
13736
  submitting: submitting.current.has(id2),
13510
13737
  running: jobsRef.current[id2]?.status === "running",
13511
- operation
13738
+ operation,
13739
+ pendingSettings: settingsPendingRef.current || Boolean(methodSavingRef.current) || previewPendingRef.current,
13740
+ installError: operation === "uninstall" ? summary?.uninstallError ?? (summary?.uninstallCommand ? "" : summary?.error ?? "") : summary?.installError ?? summary?.error ?? ""
13512
13741
  });
13513
13742
  if (refusal) {
13514
13743
  setRowErrors((previous) => ({ ...previous, [id2]: refusal }));
13515
13744
  return;
13516
13745
  }
13517
- if (typeof bridge !== "function") throw new Error("\u5B98\u65B9\u5DE5\u5177\u5B89\u88C5\u6865\u5C1A\u672A\u52A0\u8F7D\u3002");
13746
+ if (typeof bridge !== "function") {
13747
+ setRowErrors((previous) => ({ ...previous, [id2]: "\u5B98\u65B9\u5DE5\u5177\u5B89\u88C5\u6865\u5C1A\u672A\u52A0\u8F7D\u3002" }));
13748
+ return;
13749
+ }
13518
13750
  submitting.current.add(id2);
13519
13751
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
13520
13752
  setRowErrors((previous) => ({ ...previous, [id2]: "" }));
@@ -13551,7 +13783,10 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13551
13783
  const uninstall = (id2) => run(id2, "uninstall");
13552
13784
  const setInstallMethod = async (toolId, methodId) => {
13553
13785
  const snapshot = settingsScope?.getSnapshot?.();
13554
- if (!snapshot) return;
13786
+ if (!snapshot?.writable || methodSavingRef.current || settingsPendingRef.current) return;
13787
+ methodSavingRef.current = toolId;
13788
+ setMethodSaving(toolId);
13789
+ setRowErrors((previous) => ({ ...previous, [toolId]: "" }));
13555
13790
  let map = {};
13556
13791
  try {
13557
13792
  map = JSON.parse(String(snapshot.value?.toolInstallMethodsJson ?? "{}")) || {};
@@ -13564,9 +13799,14 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13564
13799
  try {
13565
13800
  const accepted = await settingsScope.mutate([{ op: "set", path: ["toolInstallMethodsJson"], value: JSON.stringify(map) }], snapshot.revision);
13566
13801
  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();
13802
+ previewPendingRef.current = true;
13803
+ setPreviewPending(true);
13804
+ await refresh();
13568
13805
  } catch (error) {
13569
13806
  if (mounted.current) setRowErrors((previous) => ({ ...previous, [toolId]: text5(error?.message) || "\u5B89\u88C5\u65B9\u5F0F\u4FDD\u5B58\u5931\u8D25\u3002" }));
13807
+ } finally {
13808
+ methodSavingRef.current = null;
13809
+ if (mounted.current) setMethodSaving(null);
13570
13810
  }
13571
13811
  };
13572
13812
  const cancel = async (id2) => {
@@ -13588,10 +13828,12 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13588
13828
  const installById = Object.fromEntries((probeState.install?.tools ?? []).map((item) => [item.id, item]));
13589
13829
  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: () => {
13590
13830
  void refresh();
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) => {
13831
+ } }, "\u91CD\u65B0\u4F53\u68C0")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-body" }, /* @__PURE__ */ import_react5.default.createElement(InstallSettings, { settingsScope, install: probeState.install, onPendingChange: handleSettingsPending }), previewPending && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", role: "status" }, "\u5B89\u88C5\u8BBE\u7F6E\u5DF2\u53D8\u66F4\uFF0C\u6B63\u5728\u5237\u65B0\u5B9E\u9645\u5B89\u88C5\u547D\u4EE4\uFF1B\u5237\u65B0\u5B8C\u6210\u540E\u53EF\u7EE7\u7EED\u5B89\u88C5\u4E0E\u66F4\u65B0\u3002"), 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
13832
  const summary = installById[tool.id];
13593
- const command = summary?.command ?? installCommandLine(tool);
13833
+ const command = summary ? summary.command : installCommandLine(tool);
13834
+ const updateCommand = summary?.updateCommand ?? command;
13594
13835
  const methods = summary?.methods ?? installMethodsFor(tool).map((method) => ({ id: method.id, label: method.label }));
13836
+ const settingsBusy = settingsPending || Boolean(methodSaving) || previewPending;
13595
13837
  const probe = byId[tool.id];
13596
13838
  const capability = capabilitiesById[tool.id];
13597
13839
  const readiness = readinessById[tool.id];
@@ -13603,32 +13845,36 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13603
13845
  probe,
13604
13846
  job,
13605
13847
  probeStatus: probeState.status,
13606
- summary: { installable: !summary?.error, removable: !summary?.error }
13848
+ summary: {
13849
+ installable: !(summary?.installError ?? summary?.error),
13850
+ removable: !(summary?.uninstallError ?? (summary?.uninstallCommand ? "" : summary?.error))
13851
+ }
13607
13852
  });
13608
13853
  const verified = job?.status === "succeeded" && (job.operation === "uninstall" ? job.postInstallProbe?.installed === false : job.postInstallProbe?.installed === true);
13609
13854
  const operation = job?.operation === "uninstall" ? "\u5378\u8F7D" : job?.operation === "repair" ? "\u4FEE\u590D" : "\u5B89\u88C5";
13610
13855
  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(
13856
+ 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(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, "\u5B89\u88C5\u65B9\u5F0F\uFF1A", methods.find((method) => method.id === summary?.methodId)?.label ?? tool.manager, " \xB7 \u5B9E\u9645\u5B89\u88C5\u76EE\u5F55\uFF1A", summary?.installDir || "\u5404\u5382\u5546\u9ED8\u8BA4\u76EE\u5F55"), /* @__PURE__ */ import_react5.default.createElement("code", { className: "mr-tool-command", "aria-label": `${tool.label} \u5B89\u88C5\u547D\u4EE4` }, command), updateCommand !== command && /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, "\u66F4\u65B0\u6CBF\u7528\u539F\u5B89\u88C5\u4F4D\u7F6E\uFF1A", summary?.updateInstallDir || "\u5404\u5382\u5546\u9ED8\u8BA4\u76EE\u5F55"), /* @__PURE__ */ import_react5.default.createElement("code", { className: "mr-tool-command", "aria-label": `${tool.label} \u66F4\u65B0\u547D\u4EE4` }, updateCommand))) : /* @__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 || methods.length > 0) && /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-actions" }, methods.length > 0 && /* @__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
13857
  "select",
13613
13858
  {
13614
13859
  className: "mr-mini-select",
13615
- value: summary?.savedMethodId ?? summary?.methodId ?? "",
13616
- disabled: running || methods.length === 0,
13860
+ value: summary?.savedMethodId ?? "",
13861
+ disabled: running || settingsBusy || !settingsWritable,
13617
13862
  onChange: (event) => {
13618
13863
  void setInstallMethod(tool.id, event.target.value);
13619
13864
  }
13620
13865
  },
13866
+ /* @__PURE__ */ import_react5.default.createElement("option", { value: "" }, "\u81EA\u52A8\u9009\u62E9"),
13621
13867
  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: () => {
13868
+ ))), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-tool-button", type: "button", disabled: action.disabled || settingsBusy || !command || Boolean(summary?.error), title: summary?.error || (settingsBusy ? "\u8BF7\u5148\u4FDD\u5B58\u6216\u653E\u5F03\u5B89\u88C5\u8BBE\u7F6E\u66F4\u6539\uFF0C\u5E76\u7B49\u5F85\u547D\u4EE4\u5237\u65B0\u3002" : void 0), onClick: () => {
13623
13869
  void install(tool.id);
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: () => {
13870
+ } }, methodSaving === tool.id ? "\u4FDD\u5B58\u65B9\u5F0F\u4E2D\u2026" : action.label), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary mr-tool-button", type: "button", disabled: maintenance.repair.disabled || settingsBusy, title: maintenance.repair.title, onClick: () => {
13625
13871
  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: () => {
13872
+ } }, maintenance.repair.label), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary mr-tool-button", type: "button", disabled: maintenance.uninstall.disabled || settingsBusy, title: maintenance.uninstall.title, onClick: () => {
13627
13873
  void uninstall(tool.id);
13628
13874
  } }, 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: () => {
13629
13875
  void cancel(tool.id);
13630
13876
  } }, 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")));
13877
+ })), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { marginTop: 12 } }, "\u5B89\u88C5\u76EE\u5F55\u4E0E\u4E0B\u8F7D\u6E90\u4F7F\u7528\u5DF2\u4FDD\u5B58\u8BBE\u7F6E\uFF0C\u547D\u4EE4\u4E0A\u65B9\u663E\u793A\u5B9E\u9645\u6267\u884C\u65B9\u5F0F\u3002\u53EF\u70B9\u201C\u53D6\u6D88\u201D\u7EC8\u6B62\u4E0B\u8F7D\u4EFB\u52A1\uFF0C\u968F\u540E\u91CD\u65B0\u68C0\u6D4B\u7248\u672C\u3002\u5378\u8F7D\u4FDD\u7559\u914D\u7F6E\u3001\u767B\u5F55\u4FE1\u606F\u548C\u5386\u53F2\u8BB0\u5F55\uFF1B\u811A\u672C\u65B9\u5F0F\u53EA\u79FB\u9664\u767B\u8BB0\u7684\u547D\u4EE4\u5165\u53E3\uFF0C\u53EF\u80FD\u4FDD\u7559\u5382\u5546\u7248\u672C\u76EE\u5F55\u3002\u4E0D\u652F\u6301\u5B89\u5168\u5378\u8F7D\u7684\u65B9\u5F0F\u4F1A\u8BF4\u660E\u539F\u56E0\u3002ZCode \u9700\u5728\u5B98\u65B9\u5B89\u88C5\u7A97\u53E3\u5B8C\u6210\u5B89\u88C5\u3002\u4F1A\u8BDD\u4E0E Agent \u4E5F\u53EF\u4F7F\u7528 ", /* @__PURE__ */ import_react5.default.createElement("code", null, "/tools"), "\u3001", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_install"), "\u3001", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_repair"), " \u548C ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_uninstall"), "\u3002")));
13632
13878
  }
13633
13879
  var HEADLESS_TOOLS = /* @__PURE__ */ new Set(["claude-code", "codex", "gemini"]);
13634
13880
  var WORKSPACE_VIEWS = [
@@ -14490,12 +14736,12 @@ function RouterSettingsCard(props) {
14490
14736
  ), 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, `{
14491
14737
  "kimi-code": { "quota": ["\u989D\u5EA6\u5DF2\u7528\u5B8C"], "rateLimit": ["\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41"] },
14492
14738
  "my-glm-plan": { "quota": ["Usage limit reached for", "1308"] }
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(
14739
+ }`)), /* @__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 }, "\u7528\u4E8E\u5B98\u65B9\u5DE5\u5177\u7684\u5B89\u88C5\u4E0E\u66F4\u65B0\u3002\u5DE5\u4F5C\u53F0\u7684\u201C\u5B98\u65B9\u5DE5\u5177\u201D\u9875\u4E5F\u80FD\u4FDD\u5B58\u540C\u6837\u7684\u5B57\u6BB5\uFF0C\u5E76\u4F1A\u663E\u793A\u6BCF\u4E2A\u5DE5\u5177\u7684\u5B9E\u9645\u5B89\u88C5\u65B9\u5F0F\u3001\u76EE\u5F55\u4E0E\u6267\u884C\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
14740
  import_dsh_client_ui_primitives.SettingsValueField,
14495
14741
  {
14496
14742
  id: "model-router-tool-install-dir",
14497
14743
  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",
14744
+ hint: "\u586B\u5199\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u7559\u7A7A\u4F7F\u7528\u5382\u5546\u9ED8\u8BA4\u4F4D\u7F6E\u3002npm / pnpm \u4F7F\u7528\u8BE5\u76EE\u5F55\u5B89\u88C5\uFF1B\u5382\u5546\u811A\u672C\u4E0D\u652F\u6301\u81EA\u5B9A\u4E49\u76EE\u5F55\u65F6\uFF0C\u5B98\u65B9\u5DE5\u5177\u9875\u4F1A\u663E\u793A\u5B9E\u9645\u76EE\u5F55\u4E0E\u539F\u56E0\u3002\u66F4\u65B0\u4F7F\u7528\u540C\u4E00\u5B89\u88C5\u65B9\u5F0F\u3001\u76EE\u5F55\u4E0E\u4E0B\u8F7D\u6E90\u3002",
14499
14745
  disabled,
14500
14746
  ...state.toolInstallDir,
14501
14747
  overriddenLabel: FIELD_COPY.overridden,
@@ -14531,7 +14777,7 @@ function RouterSettingsCard(props) {
14531
14777
  {
14532
14778
  id: "model-router-tool-install-methods",
14533
14779
  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",
14780
+ 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\u81EA\u52A8\u9009\u62E9\u9002\u5408\u672C\u673A\u7684\u65B9\u5F0F\u3002\u53EF\u9009\u503C\u4EE5\u5DE5\u5177\u4E0B\u62C9\u6846\u4E3A\u51C6\uFF1Anpm\u3001pnpm\u3001script-bash\uFF08curl | bash\uFF09\u3001script-sh\uFF08curl | sh\uFF09\u3001script-powershell\uFF08irm | iex\uFF09\u3002",
14535
14781
  disabled,
14536
14782
  ...state.toolInstallMethodsJson,
14537
14783
  overriddenLabel: FIELD_COPY.overridden,