@ljwei-stak/dsh-model-router 0.17.1 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,9 +55,9 @@ 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
  });
@@ -69,8 +69,16 @@ function scriptMethod(id2, label, {
69
69
  installArg = [],
70
70
  env = [],
71
71
  platforms,
72
+ supportsInstallDir = false,
72
73
  installDirEnv = "",
73
- defaultInstallDir = ""
74
+ installDirSuffix = "",
75
+ defaultScriptInstallDir,
76
+ defaultScriptInstallDirEnv,
77
+ scriptBinaryNames,
78
+ registryEnv,
79
+ installDirSubdir,
80
+ uninstallUnsupportedReason,
81
+ scriptFallbackUrls
74
82
  }) {
75
83
  return Object.freeze({
76
84
  id: id2,
@@ -83,14 +91,19 @@ function scriptMethod(id2, label, {
83
91
  installArgs: Object.freeze(installArg),
84
92
  /** Environment the vendor script itself documents for this install. */
85
93
  env: Object.freeze(env),
86
- /** When set, the configured directory is this environment variable, not an argv flag. */
87
- installDirEnv,
88
- /** Home-relative directory the vendor uses when the user sets none. Slashes only. */
89
- defaultInstallDir,
90
94
  platforms: Object.freeze(platforms),
91
95
  /** Whether the vendor script honours a caller-chosen directory at all. */
92
- supportsInstallDir: Boolean(installDirEnv) || installArg.length > 0,
93
- supportsRegistry: false
96
+ supportsInstallDir,
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)
94
107
  });
95
108
  }
96
109
  var OFFICIAL_TOOLS = Object.freeze([
@@ -107,8 +120,7 @@ var OFFICIAL_TOOLS = Object.freeze([
107
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",
108
121
  providerHints: ["moonshot", "kimi"],
109
122
  scriptBinaryNames: Object.freeze(["kimi", "kimi.exe"]),
110
- scriptBinaryRelatives: Object.freeze(["bin/kimi", "bin/kimi.exe"]),
111
- defaultScriptInstallDir: ".kimi-code",
123
+ defaultScriptInstallDir: ".kimi-code/bin",
112
124
  installMethods: Object.freeze([
113
125
  packageManagerMethod("npm", "npm", "npm", "@moonshot-ai/kimi-code@latest"),
114
126
  packageManagerMethod("pnpm", "pnpm", "pnpm", "@moonshot-ai/kimi-code@latest"),
@@ -116,25 +128,23 @@ var OFFICIAL_TOOLS = Object.freeze([
116
128
  shell: "bash",
117
129
  scriptUrl: "https://code.kimi.com/kimi-code/install.sh",
118
130
  platforms: ["linux", "darwin"],
131
+ supportsInstallDir: true,
119
132
  installDirEnv: "KIMI_INSTALL_DIR",
120
- defaultInstallDir: ".kimi-code",
133
+ installDirSuffix: "bin",
121
134
  env: ["KIMI_INSTALL_DIR"],
122
- verify: Object.freeze({
123
- mustInclude: ["code.kimi.com/kimi-code", "KIMI_INSTALL_DIR"],
124
- mustNotInclude: ["downloads.claude.ai/claude-code-releases", "@minimax-ai/code"]
125
- })
135
+ scriptBinaryNames: ["kimi"],
136
+ verify: Object.freeze({ mustInclude: ["kimi-code", "code.kimi.com/kimi-code"] })
126
137
  }),
127
138
  scriptMethod("script-powershell", "irm | iex", {
128
139
  shell: "powershell",
129
140
  scriptUrl: "https://code.kimi.com/kimi-code/install.ps1",
130
141
  platforms: ["win32"],
142
+ supportsInstallDir: true,
131
143
  installDirEnv: "KIMI_INSTALL_DIR",
132
- defaultInstallDir: ".kimi-code",
144
+ installDirSuffix: "bin",
133
145
  env: ["KIMI_INSTALL_DIR"],
134
- verify: Object.freeze({
135
- mustInclude: ["code.kimi.com/kimi-code", "KIMI_INSTALL_DIR"],
136
- mustNotInclude: ["downloads.claude.ai/claude-code-releases", "@minimax-ai/code"]
137
- })
146
+ scriptBinaryNames: ["kimi.exe"],
147
+ verify: Object.freeze({ mustInclude: ["kimi-code", "code.kimi.com/kimi-code"] })
138
148
  })
139
149
  ])
140
150
  }),
@@ -149,31 +159,30 @@ var OFFICIAL_TOOLS = Object.freeze([
149
159
  uninstallArgs: ["uninstall", "-g", "@anthropic-ai/claude-code"],
150
160
  probeExecutables: ["claude"],
151
161
  providerHints: ["anthropic", "claude"],
152
- // The native installer always lands in ~/.local/bin; it has no directory flag.
153
162
  scriptBinaryNames: Object.freeze(["claude", "claude.exe"]),
154
163
  defaultScriptInstallDir: ".local/bin",
155
164
  installMethods: Object.freeze([
156
165
  packageManagerMethod("npm", "npm", "npm", "@anthropic-ai/claude-code@latest"),
157
- 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.
158
171
  scriptMethod("script-bash", "curl | bash", {
159
172
  shell: "bash",
160
173
  scriptUrl: "https://claude.ai/install.sh",
174
+ scriptFallbackUrls: ["https://downloads.claude.ai/claude-code-releases/bootstrap.sh"],
161
175
  platforms: ["linux", "darwin"],
162
- defaultInstallDir: ".local/bin",
163
- verify: Object.freeze({
164
- mustInclude: ["downloads.claude.ai/claude-code-releases", "claude.ai/install.sh"],
165
- mustNotInclude: ["@minimax-ai/code", "releases.openai.com/codex"]
166
- })
176
+ scriptBinaryNames: ["claude"],
177
+ verify: Object.freeze({ mustInclude: ["downloads.claude.ai/claude-code-releases", "manifest.json"] })
167
178
  }),
168
179
  scriptMethod("script-powershell", "irm | iex", {
169
180
  shell: "powershell",
170
181
  scriptUrl: "https://claude.ai/install.ps1",
182
+ scriptFallbackUrls: ["https://downloads.claude.ai/claude-code-releases/bootstrap.ps1"],
171
183
  platforms: ["win32"],
172
- defaultInstallDir: ".local/bin",
173
- verify: Object.freeze({
174
- mustInclude: ["downloads.claude.ai/claude-code-releases"],
175
- mustNotInclude: ["@minimax-ai/code", "releases.openai.com/codex"]
176
- })
184
+ scriptBinaryNames: ["claude.exe"],
185
+ verify: Object.freeze({ mustInclude: ["downloads.claude.ai/claude-code-releases", "manifest.json"] })
177
186
  })
178
187
  ])
179
188
  }),
@@ -190,33 +199,34 @@ var OFFICIAL_TOOLS = Object.freeze([
190
199
  probeNote: "Codex \u7248\u672C\u6A2A\u5E45\u7531\u9002\u914D\u5C42\u5BBD\u5339\u914D\uFF1B\u5B89\u88C5\u65F6\u53D6 npm \u6700\u65B0\u7248\u3002",
191
200
  providerHints: ["openai", "gpt", "codex"],
192
201
  scriptBinaryNames: Object.freeze(["codex", "codex.exe"]),
202
+ defaultScriptInstallDir: ".local/bin",
193
203
  installMethods: Object.freeze([
194
204
  packageManagerMethod("npm", "npm", "npm", "@openai/codex@latest"),
195
205
  packageManagerMethod("pnpm", "pnpm", "pnpm", "@openai/codex@latest"),
196
- scriptMethod("script-bash", "curl | bash", {
197
- shell: "bash",
206
+ scriptMethod("script-sh", "curl | sh", {
207
+ shell: "sh",
198
208
  scriptUrl: "https://chatgpt.com/codex/install.sh",
199
209
  platforms: ["linux", "darwin"],
210
+ supportsInstallDir: true,
200
211
  installDirEnv: "CODEX_INSTALL_DIR",
201
- defaultInstallDir: ".local/bin",
202
212
  env: ["CODEX_INSTALL_DIR"],
203
- verify: Object.freeze({
204
- mustInclude: ["releases.openai.com/codex", "CODEX_INSTALL_DIR"],
205
- mustNotInclude: ["downloads.claude.ai/claude-code-releases", "@minimax-ai/code"]
206
- })
213
+ scriptBinaryNames: ["codex"],
214
+ verify: Object.freeze({ mustInclude: ["releases.openai.com/codex", "CODEX_INSTALL_DIR"] })
207
215
  }),
208
216
  scriptMethod("script-powershell", "irm | iex", {
209
217
  shell: "powershell",
210
218
  scriptUrl: "https://chatgpt.com/codex/install.ps1",
211
219
  platforms: ["win32"],
220
+ supportsInstallDir: true,
212
221
  installDirEnv: "CODEX_INSTALL_DIR",
213
- // %LOCALAPPDATA%\Programs\OpenAI\Codex\bin under a normal user profile.
214
- defaultInstallDir: "AppData/Local/Programs/OpenAI/Codex/bin",
215
222
  env: ["CODEX_INSTALL_DIR"],
216
- verify: Object.freeze({
217
- mustInclude: ["releases.openai.com/codex", "CODEX_INSTALL_DIR"],
218
- mustNotInclude: ["downloads.claude.ai/claude-code-releases", "@minimax-ai/code"]
219
- })
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"] })
220
230
  })
221
231
  ])
222
232
  }),
@@ -231,50 +241,39 @@ var OFFICIAL_TOOLS = Object.freeze([
231
241
  uninstallArgs: ["uninstall", "-g", "@minimax-ai/code"],
232
242
  probeExecutables: ["mcode"],
233
243
  providerHints: ["minimax"],
234
- scriptBinaryNames: Object.freeze([
235
- "mcode",
236
- "mcode.exe",
237
- "mcode.cmd",
238
- "mcode.ps1",
239
- "mcode-tools",
240
- "mcode-tools.cmd",
241
- "mcode-tools.ps1"
242
- ]),
243
- scriptBinaryRelatives: Object.freeze([
244
- "mcode.cmd",
245
- "mcode.ps1",
246
- "mcode-tools.cmd",
247
- "mcode-tools.ps1",
248
- "bin/mcode",
249
- "bin/mcode-tools"
250
- ]),
251
- defaultScriptInstallDir: ".minimax-code",
244
+ defaultMethodsByPlatform: Object.freeze({ win32: "script-powershell" }),
245
+ scriptBinaryNames: Object.freeze(["mcode", "mcode-tools"]),
246
+ defaultScriptInstallDir: ".minimax-code/bin",
252
247
  installMethods: Object.freeze([
253
- packageManagerMethod("npm", "npm", "npm", "@minimax-ai/code@latest"),
254
- 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
255
  scriptMethod("script-bash", "curl | bash", {
256
256
  shell: "bash",
257
257
  scriptUrl: "https://filecdn.minimax.chat/public/install.sh",
258
258
  platforms: ["linux", "darwin"],
259
+ supportsInstallDir: true,
259
260
  installDirEnv: "MCODE_INSTALL_DIR",
260
- defaultInstallDir: ".minimax-code",
261
+ installDirSuffix: "bin",
262
+ registryEnv: "MCODE_NPM_REGISTRY",
261
263
  env: ["MCODE_INSTALL_DIR", "MCODE_NPM_REGISTRY"],
262
- verify: Object.freeze({
263
- mustInclude: ["@minimax-ai/code", "MCODE_INSTALL_DIR", "MiniMax Code"],
264
- mustNotInclude: ["downloads.claude.ai/claude-code-releases", "releases.openai.com/codex"]
265
- })
264
+ verify: Object.freeze({ mustInclude: ["MiniMax Code CLI", "@minimax-ai/code"] })
266
265
  }),
267
266
  scriptMethod("script-powershell", "irm | iex", {
268
267
  shell: "powershell",
269
268
  scriptUrl: "https://filecdn.minimax.chat/public/install.ps1",
270
269
  platforms: ["win32"],
270
+ supportsInstallDir: true,
271
271
  installDirEnv: "MCODE_INSTALL_DIR",
272
- defaultInstallDir: ".minimax-code",
272
+ registryEnv: "MCODE_NPM_REGISTRY",
273
273
  env: ["MCODE_INSTALL_DIR", "MCODE_NPM_REGISTRY"],
274
- verify: Object.freeze({
275
- mustInclude: ["@minimax-ai/code", "MCODE_INSTALL_DIR", "MiniMax Code"],
276
- mustNotInclude: ["downloads.claude.ai/claude-code-releases", "releases.openai.com/codex"]
277
- })
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"] })
278
277
  })
279
278
  ])
280
279
  }),
@@ -294,28 +293,22 @@ var OFFICIAL_TOOLS = Object.freeze([
294
293
  installMethods: Object.freeze([
295
294
  packageManagerMethod("npm", "npm", "npm", "@mimo-ai/cli@latest"),
296
295
  packageManagerMethod("pnpm", "pnpm", "pnpm", "@mimo-ai/cli@latest"),
297
- // The Unix script hardcodes ~/.mimocode/bin and ignores MIMOCODE_INSTALL_DIR.
298
296
  scriptMethod("script-bash", "curl | bash", {
299
297
  shell: "bash",
300
298
  scriptUrl: "https://mimo.xiaomi.com/install",
301
299
  platforms: ["linux", "darwin"],
302
- defaultInstallDir: ".mimocode/bin",
303
- verify: Object.freeze({
304
- mustInclude: ["MiMoCode", "mimo.xiaomi.com"],
305
- mustNotInclude: ["@minimax-ai/code", "downloads.claude.ai/claude-code-releases"]
306
- })
300
+ scriptBinaryNames: ["mimo"],
301
+ verify: Object.freeze({ mustInclude: ["mimo.xiaomi.com", ".mimocode"] })
307
302
  }),
308
303
  scriptMethod("script-powershell", "irm | iex", {
309
304
  shell: "powershell",
310
305
  scriptUrl: "https://mimo.xiaomi.com/install.ps1",
311
306
  platforms: ["win32"],
307
+ supportsInstallDir: true,
312
308
  installDirEnv: "MIMOCODE_INSTALL_DIR",
313
- defaultInstallDir: ".mimocode/bin",
314
309
  env: ["MIMOCODE_INSTALL_DIR"],
315
- verify: Object.freeze({
316
- mustInclude: ["MiMoCode", "mimo.xiaomi.com", "MIMOCODE_INSTALL_DIR"],
317
- mustNotInclude: ["@minimax-ai/code", "downloads.claude.ai/claude-code-releases"]
318
- })
310
+ scriptBinaryNames: ["mimo.exe"],
311
+ verify: Object.freeze({ mustInclude: ["mimo.xiaomi.com", ".mimocode"] })
319
312
  })
320
313
  ])
321
314
  }),
@@ -330,8 +323,9 @@ var OFFICIAL_TOOLS = Object.freeze([
330
323
  uninstallArgs: ["uninstall", "-g", "@xai-official/grok"],
331
324
  probeExecutables: ["grok"],
332
325
  providerHints: ["xai", "grok"],
333
- scriptBinaryNames: Object.freeze(["grok", "grok.exe", "agent", "agent.exe"]),
326
+ scriptBinaryNames: Object.freeze(["grok", "agent", "grok.exe", "agent.exe", "grove.exe", "grove-fsmonitor.exe", "grove-credential.exe"]),
334
327
  defaultScriptInstallDir: ".grok/bin",
328
+ pathProfileMarkers: Object.freeze(["# >>> grok installer >>>", "# <<< grok installer <<<"]),
335
329
  installMethods: Object.freeze([
336
330
  packageManagerMethod("npm", "npm", "npm", "@xai-official/grok@latest"),
337
331
  packageManagerMethod("pnpm", "pnpm", "pnpm", "@xai-official/grok@latest"),
@@ -339,25 +333,21 @@ var OFFICIAL_TOOLS = Object.freeze([
339
333
  shell: "bash",
340
334
  scriptUrl: "https://x.ai/cli/install.sh",
341
335
  platforms: ["linux", "darwin"],
336
+ supportsInstallDir: true,
342
337
  installDirEnv: "GROK_BIN_DIR",
343
- defaultInstallDir: ".grok/bin",
344
338
  env: ["GROK_BIN_DIR"],
345
- verify: Object.freeze({
346
- mustInclude: ["x.ai/cli", "GROK_BIN_DIR"],
347
- mustNotInclude: ["@minimax-ai/code", "downloads.claude.ai/claude-code-releases"]
348
- })
339
+ scriptBinaryNames: ["grok", "agent"],
340
+ verify: Object.freeze({ mustInclude: ["Grok CLI installer", "x.ai/cli"] })
349
341
  }),
350
342
  scriptMethod("script-powershell", "irm | iex", {
351
343
  shell: "powershell",
352
344
  scriptUrl: "https://x.ai/cli/install.ps1",
353
345
  platforms: ["win32"],
346
+ supportsInstallDir: true,
354
347
  installDirEnv: "GROK_BIN_DIR",
355
- defaultInstallDir: ".grok/bin",
356
348
  env: ["GROK_BIN_DIR"],
357
- verify: Object.freeze({
358
- mustInclude: ["x.ai/cli", "GROK_BIN_DIR"],
359
- mustNotInclude: ["@minimax-ai/code", "downloads.claude.ai/claude-code-releases"]
360
- })
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"] })
361
351
  })
362
352
  ])
363
353
  }),
@@ -405,7 +395,9 @@ var OFFICIAL_TOOLS = Object.freeze([
405
395
  shell: "bash",
406
396
  scriptUrl: "https://opencode.ai/install",
407
397
  platforms: ["linux", "darwin"],
408
- defaultInstallDir: ".opencode/bin",
398
+ // The published script hardcodes $HOME/.opencode/bin, so it cannot honour
399
+ // a custom install directory; the npm/pnpm methods above can.
400
+ supportsInstallDir: false,
409
401
  verify: Object.freeze({
410
402
  mustInclude: ["anomalyco/opencode", "opencode.ai"],
411
403
  mustNotInclude: ["static-openapi.stepfun.com"]
@@ -435,7 +427,7 @@ var OFFICIAL_TOOLS = Object.freeze([
435
427
  platforms: ["linux", "darwin"],
436
428
  installArg: ["--install-dir"],
437
429
  env: ["STEP_INSTALL_DIR", "STEP_RELEASE_BASE_URL"],
438
- defaultInstallDir: ".stepcode/bin",
430
+ supportsInstallDir: true,
439
431
  verify: Object.freeze({
440
432
  mustInclude: ["static-openapi.stepfun.com", "stepcode installer"],
441
433
  mustNotInclude: ["anomalyco/opencode"]
@@ -447,7 +439,7 @@ var OFFICIAL_TOOLS = Object.freeze([
447
439
  platforms: ["win32"],
448
440
  installArg: ["-InstallDir"],
449
441
  env: ["STEP_INSTALL_DIR", "STEP_RELEASE_BASE_URL"],
450
- defaultInstallDir: ".stepcode/bin",
442
+ supportsInstallDir: true,
451
443
  verify: Object.freeze({
452
444
  mustInclude: ["static-openapi.stepfun.com", "stepcode"],
453
445
  mustNotInclude: ["anomalyco/opencode"]
@@ -486,7 +478,8 @@ function installMethodsFor(tool) {
486
478
  function installMethodFor(tool, methodId) {
487
479
  const wanted = String(methodId ?? "").trim();
488
480
  if (!wanted) return null;
489
- 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;
490
483
  }
491
484
  function defaultInstallMethod(tool) {
492
485
  return installMethodsFor(tool)[0] ?? null;
@@ -500,8 +493,8 @@ function installCommandLineFor(tool, method) {
500
493
  if (!tool || tool.unsupported) return null;
501
494
  if (!method) return null;
502
495
  if (method.kind === "script") {
503
- const piped = method.shell === "powershell" ? `irm ${method.scriptUrl} | iex` : `curl -fsSL ${method.scriptUrl} | bash`;
504
- return method.installArgs.length ? `${piped} ${method.installArgs[0]} <\u5B89\u88C5\u76EE\u5F55>` : piped;
496
+ const piped = method.shell === "powershell" ? `irm ${method.scriptUrl} | iex` : `curl -fsSL ${method.scriptUrl} | ${method.shell}`;
497
+ return piped;
505
498
  }
506
499
  return `${method.manager} ${method.installArgs.join(" ")}`;
507
500
  }
@@ -831,9 +824,9 @@ function keepKnownToolIds(raw, known, subject) {
831
824
  }
832
825
  return raw;
833
826
  }
834
- function parseInstallPreferences(config = {}, { tools = [] } = {}) {
827
+ function parseInstallPreferences(config = {}, { tools = [], home = "", env = {} } = {}) {
835
828
  const known = new Set(tools.map((tool) => tool.id));
836
- const installDir = expandInstallDir(valueOf(config, "toolInstallDir", ""));
829
+ const installDir = expandInstallDir(valueOf(config, "toolInstallDir", ""), { home, env });
837
830
  const registry = normalizeSourceUrl(valueOf(config, "toolNpmRegistry", DEFAULT_NPM_REGISTRY), "npm \u6E90") || DEFAULT_NPM_REGISTRY;
838
831
  const methodRaw = keepKnownToolIds(
839
832
  parseJsonObject(valueOf(config, "toolInstallMethodsJson", "{}"), "\u5B89\u88C5\u65B9\u5F0F\u8986\u76D6"),
@@ -850,9 +843,11 @@ function parseInstallPreferences(config = {}, { tools = [] } = {}) {
850
843
  for (const [id2, methodId] of Object.entries(methodRaw)) methods[id2] = methodId.trim();
851
844
  for (const [id2, url] of Object.entries(scriptRaw)) scriptUrls[id2] = normalizeSourceUrl(url, `${id2} \u5B89\u88C5\u811A\u672C\u6E90`);
852
845
  for (const [id2, methodId] of Object.entries(methods)) {
853
- 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) {
854
848
  throw new Error(`${id2} \u4E0D\u652F\u6301\u5B89\u88C5\u65B9\u5F0F ${methodId}\u3002`);
855
849
  }
850
+ methods[id2] = selected.id;
856
851
  }
857
852
  return {
858
853
  installDir,
@@ -2347,6 +2342,82 @@ function compareReleaseVersions(left, right) {
2347
2342
  }
2348
2343
 
2349
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 toolInstallSettingsSignature(value = {}) {
2352
+ try {
2353
+ const preferences = parseInstallPreferences({ ...value, toolInstallDir: "" }, { tools: OFFICIAL_TOOLS });
2354
+ const sorted = (map) => Object.entries(map).sort(([left], [right]) => left.localeCompare(right));
2355
+ return JSON.stringify([
2356
+ String(value.toolInstallDir ?? "").trim(),
2357
+ preferences.registry,
2358
+ sorted(preferences.methods),
2359
+ sorted(preferences.scriptUrls),
2360
+ preferences.allowScriptInstall
2361
+ ]);
2362
+ } catch {
2363
+ return JSON.stringify([
2364
+ "invalid",
2365
+ value.toolInstallDir,
2366
+ value.toolNpmRegistry,
2367
+ value.toolInstallMethodsJson,
2368
+ value.toolScriptUrlsJson,
2369
+ value.toolAllowScriptInstall
2370
+ ]);
2371
+ }
2372
+ }
2373
+ function installSummaryMatchesSettings(summary, value) {
2374
+ if (!summary) return false;
2375
+ return toolInstallSettingsSignature(value) === toolInstallSettingsSignature({
2376
+ toolInstallDir: summary.requestedInstallDir ?? summary.installDir ?? "",
2377
+ toolNpmRegistry: summary.registry || DEFAULT_NPM_REGISTRY,
2378
+ toolInstallMethodsJson: JSON.stringify(summary.methods ?? {}),
2379
+ toolScriptUrlsJson: JSON.stringify(summary.scriptUrls ?? {}),
2380
+ toolAllowScriptInstall: summary.allowScriptInstall !== false
2381
+ });
2382
+ }
2383
+ function savedToolInstallMethods(value = {}) {
2384
+ try {
2385
+ return parseInstallPreferences({ toolInstallMethodsJson: value.toolInstallMethodsJson }, { tools: OFFICIAL_TOOLS }).methods;
2386
+ } catch {
2387
+ return {};
2388
+ }
2389
+ }
2390
+ async function mutateToolInstallSettings(scope, operations, revision, { timeoutMs = 15e3, onSettled } = {}) {
2391
+ let timer;
2392
+ try {
2393
+ const writing = Promise.resolve().then(() => scope.mutate(operations, revision)).then((accepted) => {
2394
+ onSettled?.({ accepted });
2395
+ return accepted;
2396
+ }, (error) => {
2397
+ onSettled?.({ accepted: false, error });
2398
+ throw error;
2399
+ });
2400
+ return await Promise.race([
2401
+ writing,
2402
+ new Promise((_2, reject) => {
2403
+ timer = setTimeout(() => reject(new Error("\u5B89\u88C5\u8BBE\u7F6E\u4FDD\u5B58\u672A\u6536\u5230\u786E\u8BA4\uFF0C\u8BF7\u5728\u8FDE\u63A5\u6062\u590D\u540E\u91CD\u8BD5\uFF1B\u8FDF\u5230\u7ED3\u679C\u4F1A\u6309\u5B9E\u9645\u5DF2\u4FDD\u5B58\u8BBE\u7F6E\u663E\u793A\u3002")), timeoutMs);
2404
+ })
2405
+ ]);
2406
+ } finally {
2407
+ clearTimeout(timer);
2408
+ }
2409
+ }
2410
+ function validateToolInstallSetting(key, value) {
2411
+ try {
2412
+ parseInstallPreferences({ [key]: value }, { tools: OFFICIAL_TOOLS });
2413
+ return "";
2414
+ } catch (error) {
2415
+ return error?.message || "\u5B89\u88C5\u8BBE\u7F6E\u65E0\u6548\u3002";
2416
+ }
2417
+ }
2418
+ function retainToolInstallDrafts(drafts, values = {}) {
2419
+ return Object.fromEntries(Object.entries(drafts).filter(([key, value]) => value !== (values[key] ?? INSTALL_DEFAULTS[key])));
2420
+ }
2350
2421
  function toolInstallAction({ tool, probe, readiness, job, probeStatus, latestVersion = null }) {
2351
2422
  const running = job?.status === "running";
2352
2423
  const installed = probe?.installed === true;
@@ -2381,7 +2452,7 @@ function toolMaintenanceActions({ tool, probe, job, probeStatus, summary }) {
2381
2452
  }
2382
2453
  };
2383
2454
  }
2384
- function installClickRefusal({ tool, submitting = false, running = false, operation = "install" } = {}) {
2455
+ function installClickRefusal({ tool, submitting = false, running = false, operation = "install", pendingSettings = false, installError = "" } = {}) {
2385
2456
  if (!tool) return "\u672A\u77E5\u5B98\u65B9\u5DE5\u5177\uFF0C\u65E0\u6CD5\u5F00\u59CB\u5B89\u88C5\u3002";
2386
2457
  if (tool.unsupported) return String(tool.unsupportedReason ?? "").trim() || "\u6B64\u5DE5\u5177\u6682\u4E0D\u652F\u6301\u4E00\u952E\u5B89\u88C5\u3002";
2387
2458
  if (tool.manager === "signed-windows-installer" && operation !== "install") {
@@ -2390,6 +2461,8 @@ function installClickRefusal({ tool, submitting = false, running = false, operat
2390
2461
  if (submitting || running) {
2391
2462
  return operation === "uninstall" ? "\u8BE5\u5DE5\u5177\u6B63\u5728\u5378\u8F7D\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u3002" : operation === "repair" ? "\u8BE5\u5DE5\u5177\u6B63\u5728\u4FEE\u590D\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u3002" : "\u8BE5\u5DE5\u5177\u6B63\u5728\u5B89\u88C5\uFF0C\u8BF7\u7B49\u5F85\u5F53\u524D\u4EFB\u52A1\u7ED3\u675F\u3002";
2392
2463
  }
2464
+ 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";
2465
+ if (installError) return installError;
2393
2466
  return "";
2394
2467
  }
2395
2468
  function payloadError(payload, fallback) {
@@ -3204,7 +3277,7 @@ function LaunchPreview({ value, changed, running, confirmRef, onConfirm, onCance
3204
3277
  // .dsh-plugin/client/cli-terminal.jsx
3205
3278
  var import_react4 = __toESM(require("react"), 1);
3206
3279
 
3207
- // node_modules/@xterm/xterm/lib/xterm.mjs
3280
+ // node_modules/.pnpm/@xterm+xterm@6.0.0/node_modules/@xterm/xterm/lib/xterm.mjs
3208
3281
  var zs = Object.defineProperty;
3209
3282
  var Rl = Object.getOwnPropertyDescriptor;
3210
3283
  var Ll = (s15, t) => {
@@ -12318,7 +12391,7 @@ var Dl = class extends D {
12318
12391
  }
12319
12392
  };
12320
12393
 
12321
- // node_modules/@xterm/addon-fit/lib/addon-fit.mjs
12394
+ // node_modules/.pnpm/@xterm+addon-fit@0.11.0/node_modules/@xterm/addon-fit/lib/addon-fit.mjs
12322
12395
  var h = 2;
12323
12396
  var _ = 1;
12324
12397
  var o = class {
@@ -12342,7 +12415,7 @@ var o = class {
12342
12415
  }
12343
12416
  };
12344
12417
 
12345
- // node_modules/@xterm/xterm/css/xterm.css
12418
+ // node_modules/.pnpm/@xterm+xterm@6.0.0/node_modules/@xterm/xterm/css/xterm.css
12346
12419
  var xterm_default = `/**
12347
12420
  * Copyright (c) 2014 The xterm.js authors. All rights reserved.
12348
12421
  * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
@@ -12631,7 +12704,7 @@ var xterm_default = `/**
12631
12704
  `;
12632
12705
 
12633
12706
  // .dsh-plugin/client/host-version.mjs
12634
- var ROUTER_CLIENT_VERSION = true ? "0.17.1" : "";
12707
+ var ROUTER_CLIENT_VERSION = true ? "0.18.1" : "";
12635
12708
  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";
12636
12709
  function isMissingRemoteMethod(message) {
12637
12710
  const value = String(message ?? "");
@@ -13223,14 +13296,15 @@ var router_main_default = `.mr-workspace {
13223
13296
  .mr-tool-dot.running { background: #e9a640; }
13224
13297
  .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; }
13225
13298
  .mr-tool-button { flex-shrink: 0; }
13226
- .mr-tool-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
13299
+ .mr-tool-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; flex: 0 1 420px; }
13227
13300
  .mr-tool-detail { margin: 7px 0 0; }
13228
13301
  .mr-tool-error { margin: 8px 0 0; padding: 9px 11px; }
13229
13302
  .mr-tool-log { margin-top: 7px; color: var(--mr-muted); font-size: 11px; }
13230
13303
  .mr-tool-log summary { cursor: pointer; }
13231
13304
  .mr-tool-log pre { max-height: 140px; overflow: auto; padding: 8px; border-radius: 7px; background: var(--dsw-alias-markdown-code-block); font-size: 10px; white-space: pre-wrap; overflow-wrap: anywhere; }
13232
- .mr-tool-methods { display: flex; align-items: center; gap: 6px; margin-right: 2px; }
13233
- .mr-tool-methods label { display: flex; align-items: center; gap: 6px; color: var(--mr-muted); font-size: 11px; }
13305
+ .mr-tool-methods { display: flex; align-items: center; gap: 6px; width: 100%; }
13306
+ .mr-tool-methods label { display: flex; align-items: center; justify-content: flex-end; gap: 6px; width: 100%; color: var(--mr-muted); font-size: 11px; }
13307
+ .mr-tool-methods .mr-mini-select { min-width: 0; max-width: calc(100% - 54px); }
13234
13308
  .mr-install-settings { margin-bottom: 14px; padding: 13px 14px; border: 1px solid var(--mr-line); border-radius: 11px; background: var(--mr-card); }
13235
13309
  .mr-install-settings-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
13236
13310
  .mr-install-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); }
@@ -13239,6 +13313,7 @@ var router_main_default = `.mr-workspace {
13239
13313
  .mr-install-field small { font-size: 11px; line-height: 1.6; }
13240
13314
  .mr-install-error { color: #d95360; }
13241
13315
  .mr-install-toggle-row { display: flex; align-items: center; gap: 7px; }
13316
+ .mr-install-save-row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
13242
13317
  .mr-channel-line { display: flex; align-items: center; gap: 8px; margin: 8px 0; flex-wrap: wrap; }
13243
13318
  .mr-pill-channel-ok { border-color: var(--dsw-alias-success, #2f9e63); color: var(--dsw-alias-success, #2f9e63); }
13244
13319
  .mr-pill-warn { border-color: var(--dsw-alias-warning, #c27c0e); color: var(--dsw-alias-warning, #c27c0e); }
@@ -13251,7 +13326,7 @@ var router_main_default = `.mr-workspace {
13251
13326
  .mr-profile-advanced summary { cursor: pointer; }
13252
13327
  .mr-profile-success { padding: 10px 12px; border-radius: 10px; background: #e5f5eb; color: #175f3a; font-size: 12px; }
13253
13328
  @media (prefers-color-scheme: dark) { .mr-profile-success { background: #193b2b; color: #b4f1cc; } }
13254
- @media (max-width: 560px) { .mr-tool { align-items: stretch; flex-direction: column; gap: 10px; } .mr-tool-button { align-self: flex-start; } }
13329
+ @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; } }
13255
13330
 
13256
13331
  /* \u5F00\u7BB1\u4F53\u68C0\u3001\u6210\u672C\u63A7\u5236\u3001\u6267\u884C\u8BB0\u5F55\u3001\u5B89\u5168\u8FB9\u754C */
13257
13332
  .mr-onboarding, .mr-cost { margin-bottom: 18px; }
@@ -13465,41 +13540,24 @@ var INSTALL_SETTING_FIELDS = [
13465
13540
  label: "\u7EDF\u4E00\u5B89\u88C5\u76EE\u5F55",
13466
13541
  fallback: "",
13467
13542
  placeholder: "\u7559\u7A7A = \u5404\u5382\u5546\u9ED8\u8BA4\u76EE\u5F55",
13468
- hint: "npm / pnpm \u4F1A\u52A0 --prefix\u3002\u652F\u6301\u76EE\u5F55\u7684\u5B98\u65B9\u811A\u672C\u628A\u8FD9\u4E2A\u8DEF\u5F84\u4F20\u7ED9\u5382\u5546\u81EA\u5DF1\u7684\u5B89\u88C5\u76EE\u5F55\u53D8\u91CF\uFF1B\u5199\u6B7B\u8DEF\u5F84\u7684\u811A\u672C\u4F1A\u6807\u660E\u5B9E\u9645\u843D\u70B9\u3002\u66F4\u65B0\u4E5F\u4F7F\u7528\u8FD9\u91CC\uFF0C\u4E0D\u4F1A\u6539\u53BB\u522B\u7684\u9ED8\u8BA4\u4F4D\u7F6E\u3002",
13469
- validate: () => null
13543
+ 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"
13470
13544
  },
13471
13545
  {
13472
13546
  key: "toolNpmRegistry",
13473
13547
  label: "npm \u6E90\u5730\u5740",
13474
13548
  fallback: "https://registry.npmjs.org/",
13475
13549
  placeholder: "https://registry.npmjs.org/",
13476
- hint: "\u5FC5\u987B\u4EE5 https:// \u5F00\u5934\u3002\u6362\u6210\u56FD\u5185\u955C\u50CF\u540E\uFF0C\u6240\u6709\u5305\u7BA1\u7406\u5668\u65B9\u5F0F\u90FD\u4ECE\u8FD9\u91CC\u4E0B\u8F7D\u3002",
13477
- validate: (raw) => /^https:\/\/\S+$/i.test(raw.trim()) ? null : "\u9700\u8981 https \u5F00\u5934\u7684\u5B8C\u6574\u5730\u5740\uFF0C\u4F8B\u5982 https://registry.npmmirror.com/"
13550
+ 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"
13478
13551
  },
13479
13552
  {
13480
13553
  key: "toolScriptUrlsJson",
13481
13554
  label: "\u5B89\u88C5\u811A\u672C\u6E90\u8986\u76D6\uFF08JSON\uFF0C\u53EF\u9009\uFF09",
13482
13555
  fallback: "{}",
13483
13556
  placeholder: '{ "stepcode": "https://mirror/stepcode/install.ps1" }',
13484
- hint: "\u6309\u5DE5\u5177 ID \u6307\u5B9A\u5382\u5546\u5B89\u88C5\u811A\u672C\u7684\u955C\u50CF\u5730\u5740\u3002\u811A\u672C\u4ECD\u4F1A\u4E0B\u8F7D\u540E\u6821\u9A8C\u5382\u5546\u6807\u8BB0\u518D\u6267\u884C\uFF0C\u955C\u50CF\u4E0D\u80FD\u6362\u6210\u522B\u7684\u7A0B\u5E8F\u3002",
13485
- validate: (raw) => {
13486
- if (!raw.trim()) return null;
13487
- let parsed;
13488
- try {
13489
- parsed = JSON.parse(raw);
13490
- } catch {
13491
- return "JSON \u683C\u5F0F\u65E0\u6548\u3002";
13492
- }
13493
- if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return "\u9700\u8981 JSON \u5BF9\u8C61\u3002";
13494
- for (const [id2, url] of Object.entries(parsed)) {
13495
- if (!OFFICIAL_TOOLS.some((tool) => tool.id === id2)) return `${id2} \u4E0D\u662F\u5B98\u65B9\u5DE5\u5177 ID\u3002`;
13496
- if (typeof url !== "string" || !/^https:\/\/\S+$/i.test(url.trim())) return `${id2} \u7684\u5730\u5740\u5FC5\u987B\u662F https \u94FE\u63A5\u3002`;
13497
- }
13498
- return null;
13499
- }
13557
+ 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"
13500
13558
  }
13501
13559
  ];
13502
- function InstallSettings({ settingsScope, install }) {
13560
+ function InstallSettings({ settingsScope, install, onPendingChange, onMutationStart }) {
13503
13561
  const [snapshot, setSnapshot] = import_react5.default.useState(() => settingsScope?.getSnapshot?.() ?? { value: {}, writable: false });
13504
13562
  const [drafts, setDrafts] = import_react5.default.useState({});
13505
13563
  const [saving, setSaving] = import_react5.default.useState(false);
@@ -13507,36 +13565,53 @@ function InstallSettings({ settingsScope, install }) {
13507
13565
  import_react5.default.useEffect(() => {
13508
13566
  if (typeof settingsScope?.subscribe !== "function") return void 0;
13509
13567
  const unsubscribe = settingsScope.subscribe(() => {
13510
- setSnapshot(settingsScope.getSnapshot());
13511
- setDrafts({});
13568
+ const next = settingsScope.getSnapshot();
13569
+ setSnapshot(next);
13570
+ setDrafts((previous) => retainToolInstallDrafts(previous, next.value));
13512
13571
  });
13513
13572
  setSnapshot(settingsScope.getSnapshot());
13514
13573
  return unsubscribe;
13515
13574
  }, [settingsScope]);
13575
+ const dirty = Object.keys(drafts).length > 0;
13576
+ import_react5.default.useEffect(() => {
13577
+ onPendingChange?.(saving || dirty);
13578
+ }, [saving, dirty, onPendingChange]);
13516
13579
  if (!settingsScope) return null;
13517
13580
  const writable = snapshot.writable === true && !saving;
13518
13581
  const shown = (field2) => drafts[field2.key] ?? String(snapshot.value?.[field2.key] ?? field2.fallback);
13519
- const errors = Object.fromEntries(INSTALL_SETTING_FIELDS.map((field2) => [field2.key, field2.validate(shown(field2))]));
13520
- const apply2 = async (entries, successText) => {
13582
+ const errors = Object.fromEntries(INSTALL_SETTING_FIELDS.map((field2) => [field2.key, validateToolInstallSetting(field2.key, shown(field2))]));
13583
+ const invalid = Object.values(errors).some(Boolean);
13584
+ const edit = (key, value) => {
13585
+ const next = retainToolInstallDrafts({ ...drafts, [key]: value }, snapshot.value);
13586
+ onPendingChange?.(saving || Object.keys(next).length > 0);
13587
+ setNotice("");
13588
+ setDrafts(next);
13589
+ };
13590
+ const apply2 = async () => {
13591
+ if (!writable || !dirty || invalid) return;
13521
13592
  setSaving(true);
13593
+ onPendingChange?.(true);
13522
13594
  setNotice("");
13523
13595
  try {
13524
- const accepted = await settingsScope.mutate(entries, snapshot.revision);
13596
+ const entries = Object.entries(drafts).map(([key, value]) => ({ op: "set", path: [key], value }));
13597
+ const finishMutation = onMutationStart?.(entries);
13598
+ const onSettled = (outcome) => {
13599
+ const current = finishMutation?.(outcome);
13600
+ if (current && outcome.accepted) setNotice("\u5B89\u88C5\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002");
13601
+ };
13602
+ const accepted = await mutateToolInstallSettings(settingsScope, entries, snapshot.revision, { onSettled });
13525
13603
  if (!accepted) throw new Error("\u8BBE\u7F6E\u672A\u88AB\u4FDD\u5B58\uFF0C\u53EF\u80FD\u88AB\u5176\u4ED6\u9875\u9762\u4FEE\u6539\uFF1B\u8F93\u5165\u5DF2\u4FDD\u7559\uFF0C\u8BF7\u91CD\u65B0\u6838\u5BF9\u3002");
13526
- setNotice(successText);
13604
+ const next = settingsScope.getSnapshot();
13605
+ setSnapshot(next);
13606
+ setDrafts((previous) => retainToolInstallDrafts(previous, next.value));
13607
+ setNotice("\u5B89\u88C5\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002");
13527
13608
  } catch (error) {
13528
13609
  setNotice(text5(error?.message) || "\u5B89\u88C5\u8BBE\u7F6E\u4FDD\u5B58\u5931\u8D25\u3002");
13529
13610
  } finally {
13530
13611
  setSaving(false);
13531
13612
  }
13532
13613
  };
13533
- const commit = (field2) => {
13534
- const saved = String(snapshot.value?.[field2.key] ?? field2.fallback);
13535
- const raw = shown(field2);
13536
- if (raw === saved || errors[field2.key]) return;
13537
- return apply2([{ op: "set", path: [field2.key], value: raw }], `\u5DF2\u4FDD\u5B58${field2.label}\uFF0C\u4E0B\u4E00\u6B21\u5B89\u88C5\u8D77\u751F\u6548\u3002`);
13538
- };
13539
- return /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-install-settings", "aria-label": "\u7EDF\u4E00\u5B89\u88C5\u8BBE\u7F6E" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-install-settings-head" }, /* @__PURE__ */ import_react5.default.createElement("strong", null, "\u7EDF\u4E00\u5B89\u88C5\u8BBE\u7F6E"), /* @__PURE__ */ import_react5.default.createElement("span", { className: "mr-caption" }, "\u5BF9\u6240\u6709\u5B98\u65B9\u5DE5\u5177\u751F\u6548\uFF0C\u6539\u52A8\u540E\u4E0B\u4E00\u6B21\u4E00\u952E\u5B89\u88C5\u3001\u4FEE\u590D\u6216\u5378\u8F7D\u7ACB\u5373\u4F7F\u7528\u3002\u63D2\u4EF6\u8BBE\u7F6E\u9875\u91CC\u6709\u540C\u6837\u7684\u5B57\u6BB5\u3002")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-install-grid" }, INSTALL_SETTING_FIELDS.map((field2) => /* @__PURE__ */ import_react5.default.createElement("label", { className: "mr-install-field", key: field2.key }, /* @__PURE__ */ import_react5.default.createElement("span", null, field2.label), /* @__PURE__ */ import_react5.default.createElement(
13614
+ 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(
13540
13615
  "input",
13541
13616
  {
13542
13617
  className: "mr-input",
@@ -13544,37 +13619,52 @@ function InstallSettings({ settingsScope, install }) {
13544
13619
  placeholder: field2.placeholder,
13545
13620
  disabled: !writable,
13546
13621
  "aria-invalid": Boolean(errors[field2.key]),
13547
- onChange: (event) => setDrafts((previous) => ({ ...previous, [field2.key]: event.target.value })),
13548
- onBlur: () => {
13549
- void commit(field2);
13550
- }
13622
+ onChange: (event) => edit(field2.key, event.target.value)
13551
13623
  }
13552
13624
  ), errors[field2.key] ? /* @__PURE__ */ import_react5.default.createElement("small", { className: "mr-install-error", role: "alert" }, errors[field2.key]) : /* @__PURE__ */ import_react5.default.createElement("small", null, field2.hint))), /* @__PURE__ */ import_react5.default.createElement("label", { className: "mr-install-field" }, /* @__PURE__ */ import_react5.default.createElement("span", null, "\u5B89\u88C5\u811A\u672C\u65B9\u5F0F"), /* @__PURE__ */ import_react5.default.createElement("span", { className: "mr-install-toggle-row" }, /* @__PURE__ */ import_react5.default.createElement(
13553
13625
  "input",
13554
13626
  {
13555
13627
  type: "checkbox",
13556
- checked: snapshot.value?.toolAllowScriptInstall !== false,
13628
+ checked: (drafts.toolAllowScriptInstall ?? snapshot.value?.toolAllowScriptInstall) !== false,
13557
13629
  disabled: !writable,
13558
13630
  onChange: (event) => {
13559
- const allowed = event.target.checked;
13560
- void apply2(
13561
- [{ op: "set", path: ["toolAllowScriptInstall"], value: allowed }],
13562
- allowed ? "\u5DF2\u5141\u8BB8\u811A\u672C\u5B89\u88C5\u65B9\u5F0F\uFF08curl / irm\uFF09\u3002" : "\u5DF2\u5173\u95ED\u811A\u672C\u5B89\u88C5\u65B9\u5F0F\uFF0C\u53EA\u80FD\u4F7F\u7528 npm / pnpm\u3002"
13563
- );
13631
+ edit("toolAllowScriptInstall", event.target.checked);
13564
13632
  }
13565
13633
  }
13566
- ), /* @__PURE__ */ import_react5.default.createElement("span", null, "\u5141\u8BB8 curl / irm \u5B89\u88C5\u811A\u672C")), /* @__PURE__ */ import_react5.default.createElement("small", null, "\u5173\u95ED\u540E\u53EA\u80FD\u4F7F\u7528 npm / pnpm \u65B9\u5F0F\uFF1B\u811A\u672C\u65B9\u5F0F\u9700\u8981\u5148\u4E0B\u8F7D\u5E76\u6821\u9A8C\u5382\u5546\u6807\u8BB0\u518D\u6267\u884C\u3002"))), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { marginTop: 8 }, role: notice ? "status" : void 0 }, "\u5F53\u524D\u751F\u6548\uFF1A\u5B89\u88C5\u76EE\u5F55 ", install?.installDir || "\uFF08\u5404\u5382\u5546\u9ED8\u8BA4\uFF09", " \xB7 npm \u6E90 ", install?.registry || "\uFF08\u9ED8\u8BA4\uFF09", notice ? ` \xB7 ${notice}` : ""));
13634
+ ), /* @__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: () => {
13635
+ void apply2();
13636
+ } }, 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: () => {
13637
+ setDrafts({});
13638
+ setNotice("");
13639
+ onPendingChange?.(false);
13640
+ } }, "\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}` : ""));
13567
13641
  }
13568
- function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth, settingsScope }) {
13642
+ function OfficialToolsCard({ listOfficialTools, loadOfficialInstallSettings, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth, settingsScope }) {
13569
13643
  const healthById = Object.fromEntries((health?.tools ?? []).map((item) => [item.id, item]));
13570
13644
  const [probeState, setProbeState] = import_react5.default.useState({ status: "loading", probes: [], capabilities: [], readiness: [], install: null, error: "" });
13571
13645
  const [jobs, setJobs] = import_react5.default.useState({});
13572
13646
  const [rowErrors, setRowErrors] = import_react5.default.useState({});
13647
+ const [settingsPending, setSettingsPending] = import_react5.default.useState(false);
13648
+ const [methodSaving, setMethodSaving] = import_react5.default.useState(null);
13649
+ const [previewPending, setPreviewPending] = import_react5.default.useState(false);
13650
+ const [settingsSnapshot, setSettingsSnapshot] = import_react5.default.useState(() => settingsScope?.getSnapshot?.() ?? { value: {}, writable: false });
13651
+ const [previewError, setPreviewError] = import_react5.default.useState("");
13652
+ const [probeSettingsStale, setProbeSettingsStale] = import_react5.default.useState(false);
13653
+ const settingsPendingRef = import_react5.default.useRef(false);
13654
+ const methodSavingRef = import_react5.default.useRef(null);
13655
+ const previewPendingRef = import_react5.default.useRef(false);
13573
13656
  const mounted = import_react5.default.useRef(false);
13574
13657
  const request = import_react5.default.useRef(0);
13658
+ const previewRequest = import_react5.default.useRef(0);
13659
+ const mutationGeneration = import_react5.default.useRef(0);
13660
+ const mutationPendingRef = import_react5.default.useRef(null);
13575
13661
  const submitting = import_react5.default.useRef(/* @__PURE__ */ new Set());
13576
13662
  const polling = import_react5.default.useRef(/* @__PURE__ */ new Set());
13577
13663
  const jobsRef = import_react5.default.useRef({});
13664
+ const handleSettingsPending = import_react5.default.useCallback((value) => {
13665
+ settingsPendingRef.current = value;
13666
+ setSettingsPending(value);
13667
+ }, []);
13578
13668
  const rememberJobs = (updater) => {
13579
13669
  setJobs((previous) => {
13580
13670
  const next = typeof updater === "function" ? updater(previous) : updater;
@@ -13585,27 +13675,46 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13585
13675
  const refresh = async () => {
13586
13676
  onRefreshHealth?.();
13587
13677
  const current = ++request.current;
13678
+ const settingsSignature = toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value);
13588
13679
  setProbeState((previous) => ({
13589
13680
  ...previous,
13590
- status: previous.probes.length > 0 ? "refreshing" : "loading",
13681
+ status: previous.probes.length > 0 || previous.install ? "refreshing" : "loading",
13591
13682
  error: ""
13592
13683
  }));
13593
13684
  try {
13594
13685
  if (typeof listOfficialTools !== "function") throw new Error("\u5B98\u65B9\u5DE5\u5177\u5B89\u88C5\u6865\u5C1A\u672A\u52A0\u8F7D\u3002");
13595
13686
  const response = await listOfficialTools();
13596
13687
  if (!mounted.current || current !== request.current) return;
13688
+ if (settingsSignature !== toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value)) return;
13597
13689
  if (!response?.ok) throw new Error(remoteError(response, "\u65E0\u6CD5\u68C0\u6D4B\u5B98\u65B9\u5DE5\u5177\u3002"));
13598
13690
  const probes = Array.isArray(response.value?.tools) ? response.value.tools : [];
13599
13691
  const capabilities = Array.isArray(response.value?.executionCapabilities) ? response.value.executionCapabilities : [];
13600
13692
  const readiness = Array.isArray(response.value?.executionReadiness) ? response.value.executionReadiness : [];
13601
- setProbeState({ status: "ready", probes, capabilities, readiness, install: response.value?.install ?? null, error: "" });
13693
+ const install2 = response.value?.install;
13694
+ const matches = settingsSignature === toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value) && installSummaryMatchesSettings(install2, settingsScope?.getSnapshot?.()?.value);
13695
+ if (!matches) return;
13696
+ setProbeState((previous) => ({
13697
+ status: "ready",
13698
+ probes,
13699
+ capabilities,
13700
+ readiness,
13701
+ install: matches ? install2 : previous.install,
13702
+ error: ""
13703
+ }));
13704
+ setProbeSettingsStale(false);
13705
+ if (matches && !mutationPendingRef.current) {
13706
+ previewPendingRef.current = false;
13707
+ setPreviewPending(false);
13708
+ setPreviewError("");
13709
+ }
13602
13710
  onProbes({ probes, capabilities, readiness, hostVersion: typeof response.value?.hostVersion === "string" ? response.value.hostVersion : null });
13603
13711
  } catch (error) {
13604
13712
  if (!mounted.current || current !== request.current) return;
13713
+ if (settingsSignature !== toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value)) return;
13605
13714
  const message = remoteErrorText(text5(error?.message), "\u65E0\u6CD5\u68C0\u6D4B\u5B98\u65B9\u5DE5\u5177\u3002");
13606
13715
  let kept = false;
13607
13716
  setProbeState((previous) => {
13608
- if (previous.probes.length > 0) {
13717
+ if (previous.probes.length > 0 || previous.install) {
13609
13718
  kept = true;
13610
13719
  return { ...previous, status: "ready", error: message };
13611
13720
  }
@@ -13614,9 +13723,38 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13614
13723
  if (!kept) onProbes({ probes: [], capabilities: [], readiness: [] });
13615
13724
  }
13616
13725
  };
13726
+ const refreshInstallSettings = async () => {
13727
+ const current = ++previewRequest.current;
13728
+ const snapshot = settingsScope?.getSnapshot?.();
13729
+ const signature2 = toolInstallSettingsSignature(snapshot?.value);
13730
+ previewPendingRef.current = true;
13731
+ setPreviewPending(true);
13732
+ setPreviewError("");
13733
+ try {
13734
+ if (typeof loadOfficialInstallSettings !== "function") throw new Error("\u5B89\u88C5\u8BBE\u7F6E\u6865\u5C1A\u672A\u52A0\u8F7D\uFF0C\u8BF7\u66F4\u65B0 Host \u540E\u91CD\u65B0\u6253\u5F00\u5DE5\u4F5C\u53F0\u3002");
13735
+ const response = await loadOfficialInstallSettings();
13736
+ if (!mounted.current || current !== previewRequest.current || signature2 !== toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value)) return;
13737
+ if (!response?.ok) throw new Error(remoteError(response, "\u65E0\u6CD5\u8BFB\u53D6\u5B89\u88C5\u8BBE\u7F6E\u3002"));
13738
+ if (!installSummaryMatchesSettings(response.value?.install, settingsScope?.getSnapshot?.()?.value)) {
13739
+ throw new Error("\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u4F46 Host \u5B89\u88C5\u914D\u7F6E\u5C1A\u672A\u540C\u6B65\uFF1B\u8BF7\u5237\u65B0\u5B89\u88C5\u547D\u4EE4\u540E\u518D\u5B89\u88C5\u3002");
13740
+ }
13741
+ setProbeState((previous) => ({
13742
+ ...previous,
13743
+ status: previous.status === "loading" || previous.status === "error" ? "ready" : previous.status,
13744
+ install: response.value.install
13745
+ }));
13746
+ if (!mutationPendingRef.current) {
13747
+ previewPendingRef.current = false;
13748
+ setPreviewPending(false);
13749
+ }
13750
+ } catch (error) {
13751
+ if (mounted.current && current === previewRequest.current) setPreviewError(text5(error?.message) || "\u5B89\u88C5\u547D\u4EE4\u5237\u65B0\u5931\u8D25\u3002");
13752
+ }
13753
+ };
13617
13754
  import_react5.default.useEffect(() => {
13618
13755
  mounted.current = true;
13619
13756
  void refresh();
13757
+ void refreshInstallSettings();
13620
13758
  if (typeof officialToolInstallStatus === "function") {
13621
13759
  void Promise.all(OFFICIAL_TOOLS.map(async (tool) => {
13622
13760
  try {
@@ -13641,8 +13779,42 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13641
13779
  return () => {
13642
13780
  mounted.current = false;
13643
13781
  request.current += 1;
13782
+ previewRequest.current += 1;
13783
+ };
13784
+ }, []);
13785
+ const refreshInstallSettingsRef = import_react5.default.useRef(refreshInstallSettings);
13786
+ refreshInstallSettingsRef.current = refreshInstallSettings;
13787
+ const beginSettingsMutation = import_react5.default.useCallback((operations) => {
13788
+ const generation = ++mutationGeneration.current;
13789
+ mutationPendingRef.current = { generation, operations };
13790
+ previewPendingRef.current = true;
13791
+ setPreviewPending(true);
13792
+ return () => {
13793
+ if (!mounted.current || mutationPendingRef.current?.generation !== generation) return false;
13794
+ mutationPendingRef.current = null;
13795
+ void refreshInstallSettingsRef.current();
13796
+ return true;
13644
13797
  };
13645
13798
  }, []);
13799
+ import_react5.default.useEffect(() => {
13800
+ if (typeof settingsScope?.subscribe !== "function") return void 0;
13801
+ const signature2 = (snapshot) => toolInstallSettingsSignature(snapshot?.value);
13802
+ let previous = signature2(settingsScope.getSnapshot());
13803
+ return settingsScope.subscribe(() => {
13804
+ const snapshot = settingsScope.getSnapshot();
13805
+ setSettingsSnapshot(snapshot);
13806
+ const next = signature2(snapshot);
13807
+ if (next === previous) return;
13808
+ previous = next;
13809
+ request.current += 1;
13810
+ setProbeSettingsStale(true);
13811
+ setProbeState((previous2) => ({ ...previous2, status: "ready", probes: [], readiness: [], error: "" }));
13812
+ onProbes({ probes: [], capabilities: [], readiness: [] });
13813
+ previewPendingRef.current = true;
13814
+ setPreviewPending(true);
13815
+ void refreshInstallSettingsRef.current();
13816
+ });
13817
+ }, [settingsScope]);
13646
13818
  import_react5.default.useEffect(() => {
13647
13819
  const active = Object.values(jobs).filter((job) => job?.status === "running").map((job) => job.tool);
13648
13820
  if (active.length === 0 || typeof officialToolInstallStatus !== "function") return void 0;
@@ -13683,18 +13855,24 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13683
13855
  }, [jobs, officialToolInstallStatus]);
13684
13856
  const run = async (id2, operation) => {
13685
13857
  const tool = OFFICIAL_TOOLS.find((item) => item.id === id2);
13858
+ const summary = (probeState.install?.tools ?? []).find((item) => item.id === id2);
13686
13859
  const bridge = { install: installOfficialTool, repair: repairOfficialTool, uninstall: uninstallOfficialTool }[operation];
13687
13860
  const refusal = installClickRefusal({
13688
13861
  tool,
13689
13862
  submitting: submitting.current.has(id2),
13690
13863
  running: jobsRef.current[id2]?.status === "running",
13691
- operation
13864
+ operation,
13865
+ pendingSettings: settingsPendingRef.current || Boolean(methodSavingRef.current) || previewPendingRef.current,
13866
+ installError: operation === "uninstall" ? summary?.uninstallError ?? (summary?.uninstallCommand ? "" : summary?.error ?? "") : summary?.installError ?? summary?.error ?? ""
13692
13867
  });
13693
13868
  if (refusal) {
13694
13869
  setRowErrors((previous) => ({ ...previous, [id2]: refusal }));
13695
13870
  return;
13696
13871
  }
13697
- if (typeof bridge !== "function") throw new Error("\u5B98\u65B9\u5DE5\u5177\u5B89\u88C5\u6865\u5C1A\u672A\u52A0\u8F7D\u3002");
13872
+ if (typeof bridge !== "function") {
13873
+ setRowErrors((previous) => ({ ...previous, [id2]: "\u5B98\u65B9\u5DE5\u5177\u5B89\u88C5\u6865\u5C1A\u672A\u52A0\u8F7D\u3002" }));
13874
+ return;
13875
+ }
13698
13876
  submitting.current.add(id2);
13699
13877
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
13700
13878
  setRowErrors((previous) => ({ ...previous, [id2]: "" }));
@@ -13731,7 +13909,10 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13731
13909
  const uninstall = (id2) => run(id2, "uninstall");
13732
13910
  const setInstallMethod = async (toolId, methodId) => {
13733
13911
  const snapshot = settingsScope?.getSnapshot?.();
13734
- if (!snapshot) return;
13912
+ if (!snapshot?.writable || methodSavingRef.current || settingsPendingRef.current) return;
13913
+ methodSavingRef.current = toolId;
13914
+ setMethodSaving(toolId);
13915
+ setRowErrors((previous) => ({ ...previous, [toolId]: "" }));
13735
13916
  let map = {};
13736
13917
  try {
13737
13918
  map = JSON.parse(String(snapshot.value?.toolInstallMethodsJson ?? "{}")) || {};
@@ -13742,11 +13923,19 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13742
13923
  if (methodId) map[toolId] = methodId;
13743
13924
  else delete map[toolId];
13744
13925
  try {
13745
- const accepted = await settingsScope.mutate([{ op: "set", path: ["toolInstallMethodsJson"], value: JSON.stringify(map) }], snapshot.revision);
13926
+ const operations = [{ op: "set", path: ["toolInstallMethodsJson"], value: JSON.stringify(map) }];
13927
+ const finishMutation = beginSettingsMutation(operations);
13928
+ const onSettled = (outcome) => {
13929
+ if (finishMutation(outcome) && outcome.accepted) setRowErrors((previous) => ({ ...previous, [toolId]: "" }));
13930
+ };
13931
+ const accepted = await mutateToolInstallSettings(settingsScope, operations, snapshot.revision, { onSettled });
13746
13932
  if (!accepted) throw new Error("\u5B89\u88C5\u65B9\u5F0F\u672A\u88AB\u4FDD\u5B58\uFF0C\u53EF\u80FD\u88AB\u5176\u4ED6\u9875\u9762\u4FEE\u6539\u3002");
13747
- void refresh();
13933
+ setSettingsSnapshot(settingsScope.getSnapshot());
13748
13934
  } catch (error) {
13749
13935
  if (mounted.current) setRowErrors((previous) => ({ ...previous, [toolId]: text5(error?.message) || "\u5B89\u88C5\u65B9\u5F0F\u4FDD\u5B58\u5931\u8D25\u3002" }));
13936
+ } finally {
13937
+ methodSavingRef.current = null;
13938
+ if (mounted.current) setMethodSaving(null);
13750
13939
  }
13751
13940
  };
13752
13941
  const cancel = async (id2) => {
@@ -13766,12 +13955,19 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13766
13955
  const capabilitiesById = Object.fromEntries(probeState.capabilities.map((item) => [item.id, item]));
13767
13956
  const readinessById = Object.fromEntries(probeState.readiness.map((item) => [item.id, item]));
13768
13957
  const installById = Object.fromEntries((probeState.install?.tools ?? []).map((item) => [item.id, item]));
13958
+ const savedMethods = savedToolInstallMethods(settingsSnapshot.value);
13769
13959
  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: () => {
13770
13960
  void refresh();
13771
- } }, "\u91CD\u65B0\u4F53\u68C0")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-card-body" }, /* @__PURE__ */ import_react5.default.createElement(InstallSettings, { settingsScope, install: probeState.install }), probeState.status === "loading" && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-empty", role: "status" }, "\u6B63\u5728\u68C0\u6D4B\u672C\u673A\u5B98\u65B9\u5DE5\u5177\u2026"), probeState.error && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error", role: "alert" }, probeState.error), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tools", role: "list", "aria-label": "\u5B98\u65B9\u5DE5\u5177\u6CE8\u518C\u8868" }, OFFICIAL_TOOLS.map((tool) => {
13961
+ } }, "\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, onMutationStart: beginSettingsMutation }), 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\u4ECD\u53EF\u5207\u6362\u65B9\u5F0F\uFF0C\u5237\u65B0\u5B8C\u6210\u540E\u53EF\u5B89\u88C5\u4E0E\u66F4\u65B0\u3002", !previewError && /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", onClick: () => {
13962
+ void refreshInstallSettings();
13963
+ } }, "\u5237\u65B0\u5B89\u88C5\u547D\u4EE4")), previewError && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error", role: "alert" }, previewError, " ", /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", onClick: () => {
13964
+ void refreshInstallSettings();
13965
+ } }, "\u5237\u65B0\u5B89\u88C5\u547D\u4EE4")), probeSettingsStale && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", role: "status" }, "\u5B89\u88C5\u8BBE\u7F6E\u5DF2\u66F4\u6539\uFF0C\u65E7\u76EE\u6807\u7684\u7248\u672C\u4E0E\u5B89\u88C5\u72B6\u6001\u5DF2\u8FC7\u671F\u3002\u53EF\u5B89\u88C5\u6240\u9009\u76EE\u6807\uFF1B\u5378\u8F7D\u524D\u8BF7\u91CD\u65B0\u4F53\u68C0\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) => {
13772
13966
  const summary = installById[tool.id];
13773
- const command = summary?.command ?? installCommandLine(tool);
13967
+ const command = summary ? summary.command : installCommandLine(tool);
13968
+ const updateCommand = summary?.updateCommand ?? command;
13774
13969
  const methods = summary?.methods ?? installMethodsFor(tool).map((method) => ({ id: method.id, label: method.label }));
13970
+ const settingsBusy = settingsPending || Boolean(methodSaving) || previewPending;
13775
13971
  const probe = byId[tool.id];
13776
13972
  const capability = capabilitiesById[tool.id];
13777
13973
  const readiness = readinessById[tool.id];
@@ -13783,32 +13979,36 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13783
13979
  probe,
13784
13980
  job,
13785
13981
  probeStatus: probeState.status,
13786
- summary: { installable: !summary?.error, removable: !summary?.error }
13982
+ summary: {
13983
+ installable: !(summary?.installError ?? summary?.error),
13984
+ removable: !(summary?.uninstallError ?? (summary?.uninstallCommand ? "" : summary?.error))
13985
+ }
13787
13986
  });
13788
- const verified = job?.status === "succeeded" && (job.operation === "uninstall" ? job.postInstallProbe?.installed === false : job.postInstallProbe?.installed === true);
13987
+ const verified = Boolean(probe) && job?.status === "succeeded" && (job.operation === "uninstall" ? job.postInstallProbe?.installed === false : job.postInstallProbe?.installed === true);
13789
13988
  const operation = job?.operation === "uninstall" ? "\u5378\u8F7D" : job?.operation === "repair" ? "\u4FEE\u590D" : "\u5B89\u88C5";
13790
- const status = running ? job.cancelRequested ? `\u6B63\u5728\u53D6\u6D88${operation}\u2026` : `${operation}\u4E2D\u2026` : job?.status === "installer-opened" ? "\u5B98\u65B9\u5B89\u88C5\u5668\u5DF2\u6253\u5F00\uFF0C\u8BF7\u5B8C\u6210\u5B89\u88C5\u540E\u91CD\u65B0\u68C0\u6D4B" : job?.status === "cancelled" ? `${operation}\u5DF2\u53D6\u6D88\uFF0C\u8BF7\u91CD\u65B0\u68C0\u6D4B` : verified ? `${operation}\u6210\u529F\u5E76\u9A8C\u8BC1` : probeLabel(probe);
13791
- return /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool", role: "listitem", key: tool.id }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-info" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-name", title: tool.purpose }, tool.label), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-route-provider" }, tool.vendor, " \xB7 ", tool.id), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-status", role: "status" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: `mr-tool-dot ${running ? "running" : probe?.installed ? "installed" : "missing"}` }), status, probe?.installed && probe.version ? ` \xB7 ${probe.version}` : "", healthById[tool.id]?.latestVersion ? ` \xB7 \u6700\u65B0 ${healthById[tool.id].latestVersion}` : ""), /* @__PURE__ */ import_react5.default.createElement(ToolLoginLine, { entry: healthById[tool.id] }), probe?.installed && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, tool.headlessAdapter ? "\u5DF2\u53EF\u7531 model_router_execute \u4EE5\u65E0\u754C\u9762\u65B9\u5F0F\u8C03\u7528\u3002\u547D\u4EE4\u7F3A\u5931\u6216\u5931\u8D25\u65F6\u56DE\u9000\u6A21\u578B\u76EE\u5F55 API\u3002\u7B7E\u540D\u6C99\u7BB1\u5165\u53E3\u4E0D\u542F\u52A8\u6B64 CLI\u3002" : readiness?.ready ? `\u5B98\u65B9\u6267\u884C\u5165\u53E3\u5DF2\u6838\u9A8C\uFF0C\u53EF\u5728\u4F1A\u8BDD\u4E2D\u8C03\u7528 model_router_tool_run\uFF1B${capability?.modes?.includes("read-only") ? "\u652F\u6301\u53EA\u8BFB\u548C\u7ECF\u5BA1\u6279\u7684\u53EF\u7F16\u8F91\u4EFB\u52A1" : "\u4EC5\u652F\u6301\u7ECF\u5BA1\u6279\u7684\u53EF\u7F16\u8F91\u9694\u79BB\u5DE5\u4F5C\u533A\u4EFB\u52A1"}\uFF0C\u8D26\u53F7\u53CA\u6A21\u578B\u4ECD\u9700\u5B9E\u6D4B\u3002` : `\u5DF2\u5B89\u88C5\uFF0C\u4F46\u5F53\u524D\u4E0D\u53EF\u6258\u7BA1\u6267\u884C\uFF1A${readiness?.reason || capability?.reason || "\u6267\u884C\u5165\u53E3\u5C1A\u672A\u6838\u9A8C\u3002"}`), command ? /* @__PURE__ */ import_react5.default.createElement("code", { className: "mr-tool-command" }, command) : /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { margin: "6px 0 0" } }, summary?.error ?? tool.unsupportedReason), (summary?.notices ?? []).map((notice) => /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail", key: notice }, notice)), probe?.detail && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption mr-tool-detail" }, probe.detail), (rowErrors[tool.id] || job?.error) && /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-error mr-tool-error", role: "alert" }, rowErrors[tool.id] || job.error), Array.isArray(job?.outputTail) && job.outputTail.length > 0 && /* @__PURE__ */ import_react5.default.createElement("details", { className: "mr-tool-log" }, /* @__PURE__ */ import_react5.default.createElement("summary", null, "\u5B89\u88C5\u65E5\u5FD7"), /* @__PURE__ */ import_react5.default.createElement("pre", null, job.outputTail.slice(-6).join("\n")))), command && /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-actions" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-tool-methods" }, /* @__PURE__ */ import_react5.default.createElement("label", null, /* @__PURE__ */ import_react5.default.createElement("span", null, "\u5B89\u88C5\u65B9\u5F0F"), /* @__PURE__ */ import_react5.default.createElement(
13989
+ 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` : probe ? probeLabel(probe) : "\u6240\u9009\u5B89\u88C5\u76EE\u6807\u5C1A\u672A\u4F53\u68C0";
13990
+ 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(
13792
13991
  "select",
13793
13992
  {
13794
13993
  className: "mr-mini-select",
13795
- value: summary?.savedMethodId ?? summary?.methodId ?? "",
13796
- disabled: running || methods.length === 0,
13994
+ value: savedMethods[tool.id] ?? "",
13995
+ disabled: running || settingsPending || Boolean(methodSaving) || settingsSnapshot.writable !== true,
13797
13996
  onChange: (event) => {
13798
13997
  void setInstallMethod(tool.id, event.target.value);
13799
13998
  }
13800
13999
  },
14000
+ /* @__PURE__ */ import_react5.default.createElement("option", { value: "" }, "\u81EA\u52A8\u9009\u62E9"),
13801
14001
  methods.map((method) => /* @__PURE__ */ import_react5.default.createElement("option", { key: method.id, value: method.id, disabled: Boolean(method.blocked) }, method.label, method.blocked ? `\uFF08${method.blocked}\uFF09` : ""))
13802
- ))), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-tool-button", type: "button", disabled: action.disabled, onClick: () => {
14002
+ ))), /* @__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: () => {
13803
14003
  void install(tool.id);
13804
- } }, action.label), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary mr-tool-button", type: "button", disabled: maintenance.repair.disabled, title: maintenance.repair.title, onClick: () => {
14004
+ } }, 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: () => {
13805
14005
  void repair(tool.id);
13806
- } }, maintenance.repair.label), /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary mr-tool-button", type: "button", disabled: maintenance.uninstall.disabled, title: maintenance.uninstall.title, onClick: () => {
14006
+ } }, 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: () => {
13807
14007
  void uninstall(tool.id);
13808
14008
  } }, 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: () => {
13809
14009
  void cancel(tool.id);
13810
14010
  } }, job.cancelRequested ? "\u6B63\u5728\u53D6\u6D88\u2026" : `\u53D6\u6D88${operation}`)));
13811
- })), /* @__PURE__ */ import_react5.default.createElement("p", { className: "mr-caption", style: { marginTop: 12 } }, "\u5B89\u88C5\u7531 Host \u6309\u6CE8\u518C\u8868\u56FA\u5B9A\u6765\u6E90\u6267\u884C\uFF0C\u4E0D\u63A5\u53D7\u81EA\u5B9A\u4E49\u5305\u540D\uFF1B\u5B89\u88C5\u76EE\u5F55\u4E0E\u4E0B\u8F7D\u6E90\u6765\u81EA\u4F60\u81EA\u5DF1\u586B\u5199\u7684\u8BBE\u7F6E\uFF0C\u547D\u4EE4\u4E0A\u65B9\u5B9E\u65F6\u663E\u793A\u5C06\u8981\u6267\u884C\u7684\u5185\u5BB9\u3002\u53EF\u70B9\u201C\u53D6\u6D88\u201D\u7EC8\u6B62\u4E0B\u8F7D\u4EFB\u52A1\uFF0C\u968F\u540E\u91CD\u65B0\u68C0\u6D4B\u5B9E\u9645\u7248\u672C\u3002\u4E00\u952E\u5378\u8F7D\u53EA\u5220\u9664\u7A0B\u5E8F\u672C\u8EAB\uFF08\u811A\u672C\u5B89\u88C5\u4F1A\u987A\u5E26\u6E05\u6389\u5B83\u5728 shell \u914D\u7F6E\u91CC\u5199\u7684 PATH \u8BB0\u5F55\uFF09\uFF0C\u914D\u7F6E\u3001\u767B\u5F55\u4FE1\u606F\u548C\u5386\u53F2\u8BB0\u5F55\u4FDD\u7559\u3002ZCode \u5B89\u88C5\u5668\u542F\u52A8\u540E\u4ECD\u9700\u5728\u539F\u5382\u7A97\u53E3\u9009\u62E9\u76EE\u5F55\u5E76\u5B8C\u6210\u5B89\u88C5\u3002Agent \u4E5F\u53EF\u8C03\u7528 ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_install"), " / ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_repair"), " / ", /* @__PURE__ */ import_react5.default.createElement("code", null, "model_router_tool_uninstall"), "\uFF0C\u6216\u5728\u4F1A\u8BDD\u4F7F\u7528 ", /* @__PURE__ */ import_react5.default.createElement("code", null, "/tools"), "\u3002")));
14011
+ })), /* @__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")));
13812
14012
  }
13813
14013
  var HEADLESS_TOOLS = /* @__PURE__ */ new Set(["claude-code", "codex", "gemini"]);
13814
14014
  var WORKSPACE_VIEWS = [
@@ -13935,7 +14135,7 @@ ${reasons.map((entry, index) => `${index + 1}. ${entry.text}`).join("\n")}
13935
14135
  }, []);
13936
14136
  return { health, ledger, boundaries, busy, refreshHealth, refreshLedger, refreshBoundaries, finishOnboarding, rate, rerun };
13937
14137
  }
13938
- function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
14138
+ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, loadOfficialInstallSettings, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
13939
14139
  const workbench = useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries });
13940
14140
  const [catalogState, setCatalogState] = import_react5.default.useState({ status: "loading", catalog: null, error: "" });
13941
14141
  const [task, setTask] = import_react5.default.useState("");
@@ -14157,6 +14357,7 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, install
14157
14357
  OfficialToolsCard,
14158
14358
  {
14159
14359
  listOfficialTools,
14360
+ loadOfficialInstallSettings,
14160
14361
  installOfficialTool,
14161
14362
  uninstallOfficialTool,
14162
14363
  repairOfficialTool,
@@ -14347,6 +14548,7 @@ var toolIdParameter = Object.freeze({
14347
14548
  var jsonParameter = (name, codec) => Object.freeze({ name, wire: name, source: "json", codec });
14348
14549
  var OFFICIAL_TOOLS_REMOTE_DESCRIPTORS = Object.freeze([
14349
14550
  descriptor("list", [], listResultCodec),
14551
+ descriptor("installSettings", [], anyObjectCodec("OfficialInstallSettings")),
14350
14552
  descriptor("installTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
14351
14553
  descriptor("uninstallTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
14352
14554
  descriptor("repairTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
@@ -14670,12 +14872,12 @@ function RouterSettingsCard(props) {
14670
14872
  ), 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, `{
14671
14873
  "kimi-code": { "quota": ["\u989D\u5EA6\u5DF2\u7528\u5B8C"], "rateLimit": ["\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41"] },
14672
14874
  "my-glm-plan": { "quota": ["Usage limit reached for", "1308"] }
14673
- }`)), /* @__PURE__ */ import_react6.default.createElement("button", { type: "button", disabled, onClick: () => props.resetField("quotaPatternsJson") }, "\u6062\u590D\u9ED8\u8BA4\u89C4\u5219")), /* @__PURE__ */ import_react6.default.createElement("div", { style: styles.profileEditor }, /* @__PURE__ */ import_react6.default.createElement("span", { style: styles.profileLabel }, "\u5B98\u65B9\u5DE5\u5177\u7EDF\u4E00\u5B89\u88C5\u8BBE\u7F6E"), /* @__PURE__ */ import_react6.default.createElement("p", { style: styles.noticeText }, "\u5BF9\u6240\u6709\u5B98\u65B9\u5DE5\u5177\u7684\u4E00\u952E\u5B89\u88C5\u3001\u4FEE\u590D\u548C\u5378\u8F7D\u751F\u6548\u3002\u5DE5\u4F5C\u53F0\u7684\u201C\u5B98\u65B9\u5DE5\u5177\u201D\u9875\u4E5F\u80FD\u6539\u540C\u6837\u7684\u5B57\u6BB5\uFF0C\u5E76\u4F1A\u5B9E\u65F6\u663E\u793A\u6BCF\u4E2A\u5DE5\u5177\u5C06\u8981\u6267\u884C\u7684\u547D\u4EE4\u3002 \u5B89\u88C5\u547D\u4EE4\u59CB\u7EC8\u6765\u81EA\u56FA\u5B9A\u6CE8\u518C\u8868\uFF0C\u8FD9\u91CC\u53EA\u51B3\u5B9A\u76EE\u5F55\u4E0E\u4E0B\u8F7D\u6E90\uFF0C\u4E0D\u63A5\u53D7\u81EA\u5B9A\u4E49\u5305\u540D\u6216\u4EFB\u610F\u5730\u5740\u3002"), /* @__PURE__ */ import_react6.default.createElement(
14875
+ }`)), /* @__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(
14674
14876
  import_dsh_client_ui_primitives.SettingsValueField,
14675
14877
  {
14676
14878
  id: "model-router-tool-install-dir",
14677
14879
  label: "\u7EDF\u4E00\u5B89\u88C5\u76EE\u5F55",
14678
- hint: "\u7559\u7A7A\u8868\u793A\u4F7F\u7528\u5404\u5382\u5546\u9ED8\u8BA4\u4F4D\u7F6E\u3002npm \u4E0E pnpm \u4F1A\u5E26\u4E0A --prefix\uFF1B\u652F\u6301\u76EE\u5F55\u7684\u5B98\u65B9\u811A\u672C\u4F1A\u628A\u8FD9\u4E2A\u8DEF\u5F84\u4F20\u7ED9\u5382\u5546\u81EA\u5DF1\u7684\u5B89\u88C5\u76EE\u5F55\u53D8\u91CF\uFF08\u4F8B\u5982 MCODE_INSTALL_DIR\uFF09\u3002\u5199\u6B7B\u8DEF\u5F84\u7684\u811A\u672C\u4F1A\u5728\u5DE5\u5177\u884C\u6807\u660E\u5B9E\u9645\u843D\u70B9\u3002\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u3002\u66F4\u65B0\u4F7F\u7528\u540C\u4E00\u8DEF\u5F84\u3002",
14880
+ 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",
14679
14881
  disabled,
14680
14882
  ...state.toolInstallDir,
14681
14883
  overriddenLabel: FIELD_COPY.overridden,
@@ -14711,7 +14913,7 @@ function RouterSettingsCard(props) {
14711
14913
  {
14712
14914
  id: "model-router-tool-install-methods",
14713
14915
  label: "\u9ED8\u8BA4\u5B89\u88C5\u65B9\u5F0F\uFF08JSON\uFF0C\u53EF\u9009\uFF09",
14714
- hint: "\u6309\u5DE5\u5177 ID \u56FA\u5B9A\u4E00\u79CD\u5B89\u88C5\u65B9\u5F0F\uFF0C\u4F8B\u5982\u56FA\u5B9A Step Code \u5728 Windows \u4E0A\u7528 irm | iex\u3002\u7559\u7A7A\u8868\u793A\u4F7F\u7528\u8BE5\u5DE5\u5177\u7684\u9ED8\u8BA4\u65B9\u5F0F\u3002\u53EF\u9009\u503C\uFF1Anpm\u3001pnpm\u3001script-bash\uFF08curl | bash\uFF09\u3001script-powershell\uFF08irm | iex\uFF09\u3002",
14916
+ 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",
14715
14917
  disabled,
14716
14918
  ...state.toolInstallMethodsJson,
14717
14919
  overriddenLabel: FIELD_COPY.overridden,
@@ -14770,6 +14972,7 @@ function registerUi(ctx) {
14770
14972
  loadCatalog: () => ctx.remote.session.modelCatalog(),
14771
14973
  settingsScope,
14772
14974
  listOfficialTools: () => officialToolsRemote.list(),
14975
+ loadOfficialInstallSettings: () => officialToolsRemote.installSettings(),
14773
14976
  installOfficialTool: (request) => officialToolsRemote.installTool(request),
14774
14977
  uninstallOfficialTool: (request) => officialToolsRemote.uninstallTool(request),
14775
14978
  repairOfficialTool: (request) => officialToolsRemote.repairTool(request),