@ljwei-stak/dsh-model-router 0.18.0 → 0.18.2

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.
@@ -77,6 +77,8 @@ function scriptMethod(id2, label, {
77
77
  scriptBinaryNames,
78
78
  registryEnv,
79
79
  installDirSubdir,
80
+ legacySharedInstallDir = false,
81
+ sharedDefaultBin = false,
80
82
  uninstallUnsupportedReason,
81
83
  scriptFallbackUrls
82
84
  }) {
@@ -97,6 +99,8 @@ function scriptMethod(id2, label, {
97
99
  installDirEnv,
98
100
  installDirSuffix,
99
101
  ...installDirSubdir ? { installDirSubdir } : {},
102
+ ...legacySharedInstallDir ? { legacySharedInstallDir: true } : {},
103
+ ...sharedDefaultBin ? { sharedDefaultBin: true } : {},
100
104
  ...uninstallUnsupportedReason ? { uninstallUnsupportedReason } : {},
101
105
  ...defaultScriptInstallDir ? { defaultScriptInstallDir } : {},
102
106
  ...defaultScriptInstallDirEnv ? { defaultScriptInstallDirEnv } : {},
@@ -131,6 +135,8 @@ var OFFICIAL_TOOLS = Object.freeze([
131
135
  supportsInstallDir: true,
132
136
  installDirEnv: "KIMI_INSTALL_DIR",
133
137
  installDirSuffix: "bin",
138
+ installDirSubdir: "kimi-code",
139
+ legacySharedInstallDir: true,
134
140
  env: ["KIMI_INSTALL_DIR"],
135
141
  scriptBinaryNames: ["kimi"],
136
142
  verify: Object.freeze({ mustInclude: ["kimi-code", "code.kimi.com/kimi-code"] })
@@ -142,6 +148,8 @@ var OFFICIAL_TOOLS = Object.freeze([
142
148
  supportsInstallDir: true,
143
149
  installDirEnv: "KIMI_INSTALL_DIR",
144
150
  installDirSuffix: "bin",
151
+ installDirSubdir: "kimi-code",
152
+ legacySharedInstallDir: true,
145
153
  env: ["KIMI_INSTALL_DIR"],
146
154
  scriptBinaryNames: ["kimi.exe"],
147
155
  verify: Object.freeze({ mustInclude: ["kimi-code", "code.kimi.com/kimi-code"] })
@@ -174,6 +182,7 @@ var OFFICIAL_TOOLS = Object.freeze([
174
182
  scriptFallbackUrls: ["https://downloads.claude.ai/claude-code-releases/bootstrap.sh"],
175
183
  platforms: ["linux", "darwin"],
176
184
  scriptBinaryNames: ["claude"],
185
+ sharedDefaultBin: true,
177
186
  verify: Object.freeze({ mustInclude: ["downloads.claude.ai/claude-code-releases", "manifest.json"] })
178
187
  }),
179
188
  scriptMethod("script-powershell", "irm | iex", {
@@ -182,6 +191,7 @@ var OFFICIAL_TOOLS = Object.freeze([
182
191
  scriptFallbackUrls: ["https://downloads.claude.ai/claude-code-releases/bootstrap.ps1"],
183
192
  platforms: ["win32"],
184
193
  scriptBinaryNames: ["claude.exe"],
194
+ sharedDefaultBin: true,
185
195
  verify: Object.freeze({ mustInclude: ["downloads.claude.ai/claude-code-releases", "manifest.json"] })
186
196
  })
187
197
  ])
@@ -210,6 +220,9 @@ var OFFICIAL_TOOLS = Object.freeze([
210
220
  supportsInstallDir: true,
211
221
  installDirEnv: "CODEX_INSTALL_DIR",
212
222
  env: ["CODEX_INSTALL_DIR"],
223
+ installDirSubdir: "codex/bin",
224
+ legacySharedInstallDir: true,
225
+ sharedDefaultBin: true,
213
226
  scriptBinaryNames: ["codex"],
214
227
  verify: Object.freeze({ mustInclude: ["releases.openai.com/codex", "CODEX_INSTALL_DIR"] })
215
228
  }),
@@ -259,6 +272,8 @@ var OFFICIAL_TOOLS = Object.freeze([
259
272
  supportsInstallDir: true,
260
273
  installDirEnv: "MCODE_INSTALL_DIR",
261
274
  installDirSuffix: "bin",
275
+ installDirSubdir: "minimax-code",
276
+ legacySharedInstallDir: true,
262
277
  registryEnv: "MCODE_NPM_REGISTRY",
263
278
  env: ["MCODE_INSTALL_DIR", "MCODE_NPM_REGISTRY"],
264
279
  verify: Object.freeze({ mustInclude: ["MiniMax Code CLI", "@minimax-ai/code"] })
@@ -269,6 +284,8 @@ var OFFICIAL_TOOLS = Object.freeze([
269
284
  platforms: ["win32"],
270
285
  supportsInstallDir: true,
271
286
  installDirEnv: "MCODE_INSTALL_DIR",
287
+ installDirSubdir: "minimax-code",
288
+ legacySharedInstallDir: true,
272
289
  registryEnv: "MCODE_NPM_REGISTRY",
273
290
  env: ["MCODE_INSTALL_DIR", "MCODE_NPM_REGISTRY"],
274
291
  defaultScriptInstallDir: ".minimax-code",
@@ -307,6 +324,8 @@ var OFFICIAL_TOOLS = Object.freeze([
307
324
  supportsInstallDir: true,
308
325
  installDirEnv: "MIMOCODE_INSTALL_DIR",
309
326
  env: ["MIMOCODE_INSTALL_DIR"],
327
+ installDirSubdir: "mimo-code",
328
+ legacySharedInstallDir: true,
310
329
  scriptBinaryNames: ["mimo.exe"],
311
330
  verify: Object.freeze({ mustInclude: ["mimo.xiaomi.com", ".mimocode"] })
312
331
  })
@@ -336,6 +355,8 @@ var OFFICIAL_TOOLS = Object.freeze([
336
355
  supportsInstallDir: true,
337
356
  installDirEnv: "GROK_BIN_DIR",
338
357
  env: ["GROK_BIN_DIR"],
358
+ installDirSubdir: "grok-build",
359
+ legacySharedInstallDir: true,
339
360
  scriptBinaryNames: ["grok", "agent"],
340
361
  verify: Object.freeze({ mustInclude: ["Grok CLI installer", "x.ai/cli"] })
341
362
  }),
@@ -346,6 +367,8 @@ var OFFICIAL_TOOLS = Object.freeze([
346
367
  supportsInstallDir: true,
347
368
  installDirEnv: "GROK_BIN_DIR",
348
369
  env: ["GROK_BIN_DIR"],
370
+ installDirSubdir: "grok-build",
371
+ legacySharedInstallDir: true,
349
372
  scriptBinaryNames: ["grok.exe", "agent.exe", "grove.exe", "grove-fsmonitor.exe", "grove-credential.exe"],
350
373
  verify: Object.freeze({ mustInclude: ["Grok CLI installer", "x.ai/cli"] })
351
374
  })
@@ -428,6 +451,10 @@ var OFFICIAL_TOOLS = Object.freeze([
428
451
  installArg: ["--install-dir"],
429
452
  env: ["STEP_INSTALL_DIR", "STEP_RELEASE_BASE_URL"],
430
453
  supportsInstallDir: true,
454
+ // The installer replaces its runtime directories, including the entire
455
+ // node_modules tree on Windows. It must own a dedicated directory.
456
+ installDirSubdir: "stepcode",
457
+ legacySharedInstallDir: true,
431
458
  verify: Object.freeze({
432
459
  mustInclude: ["static-openapi.stepfun.com", "stepcode installer"],
433
460
  mustNotInclude: ["anomalyco/opencode"]
@@ -440,6 +467,8 @@ var OFFICIAL_TOOLS = Object.freeze([
440
467
  installArg: ["-InstallDir"],
441
468
  env: ["STEP_INSTALL_DIR", "STEP_RELEASE_BASE_URL"],
442
469
  supportsInstallDir: true,
470
+ installDirSubdir: "stepcode",
471
+ legacySharedInstallDir: true,
443
472
  verify: Object.freeze({
444
473
  mustInclude: ["static-openapi.stepfun.com", "stepcode"],
445
474
  mustNotInclude: ["anomalyco/opencode"]
@@ -2348,6 +2377,65 @@ var INSTALL_DEFAULTS = Object.freeze({
2348
2377
  toolScriptUrlsJson: "{}",
2349
2378
  toolAllowScriptInstall: true
2350
2379
  });
2380
+ function toolInstallSettingsSignature(value = {}) {
2381
+ try {
2382
+ const preferences = parseInstallPreferences({ ...value, toolInstallDir: "" }, { tools: OFFICIAL_TOOLS });
2383
+ const sorted = (map) => Object.entries(map).sort(([left], [right]) => left.localeCompare(right));
2384
+ return JSON.stringify([
2385
+ String(value.toolInstallDir ?? "").trim(),
2386
+ preferences.registry,
2387
+ sorted(preferences.methods),
2388
+ sorted(preferences.scriptUrls),
2389
+ preferences.allowScriptInstall
2390
+ ]);
2391
+ } catch {
2392
+ return JSON.stringify([
2393
+ "invalid",
2394
+ value.toolInstallDir,
2395
+ value.toolNpmRegistry,
2396
+ value.toolInstallMethodsJson,
2397
+ value.toolScriptUrlsJson,
2398
+ value.toolAllowScriptInstall
2399
+ ]);
2400
+ }
2401
+ }
2402
+ function installSummaryMatchesSettings(summary, value) {
2403
+ if (!summary) return false;
2404
+ return toolInstallSettingsSignature(value) === toolInstallSettingsSignature({
2405
+ toolInstallDir: summary.requestedInstallDir ?? summary.installDir ?? "",
2406
+ toolNpmRegistry: summary.registry || DEFAULT_NPM_REGISTRY,
2407
+ toolInstallMethodsJson: JSON.stringify(summary.methods ?? {}),
2408
+ toolScriptUrlsJson: JSON.stringify(summary.scriptUrls ?? {}),
2409
+ toolAllowScriptInstall: summary.allowScriptInstall !== false
2410
+ });
2411
+ }
2412
+ function savedToolInstallMethods(value = {}) {
2413
+ try {
2414
+ return parseInstallPreferences({ toolInstallMethodsJson: value.toolInstallMethodsJson }, { tools: OFFICIAL_TOOLS }).methods;
2415
+ } catch {
2416
+ return {};
2417
+ }
2418
+ }
2419
+ async function mutateToolInstallSettings(scope, operations, revision, { timeoutMs = 15e3, onSettled } = {}) {
2420
+ let timer;
2421
+ try {
2422
+ const writing = Promise.resolve().then(() => scope.mutate(operations, revision)).then((accepted) => {
2423
+ onSettled?.({ accepted });
2424
+ return accepted;
2425
+ }, (error) => {
2426
+ onSettled?.({ accepted: false, error });
2427
+ throw error;
2428
+ });
2429
+ return await Promise.race([
2430
+ writing,
2431
+ new Promise((_2, reject) => {
2432
+ 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);
2433
+ })
2434
+ ]);
2435
+ } finally {
2436
+ clearTimeout(timer);
2437
+ }
2438
+ }
2351
2439
  function validateToolInstallSetting(key, value) {
2352
2440
  try {
2353
2441
  parseInstallPreferences({ [key]: value }, { tools: OFFICIAL_TOOLS });
@@ -12645,7 +12733,7 @@ var xterm_default = `/**
12645
12733
  `;
12646
12734
 
12647
12735
  // .dsh-plugin/client/host-version.mjs
12648
- var ROUTER_CLIENT_VERSION = true ? "0.18.0" : "";
12736
+ var ROUTER_CLIENT_VERSION = true ? "0.18.2" : "";
12649
12737
  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";
12650
12738
  function isMissingRemoteMethod(message) {
12651
12739
  const value = String(message ?? "");
@@ -13478,10 +13566,10 @@ function probeLabel(probe) {
13478
13566
  var INSTALL_SETTING_FIELDS = [
13479
13567
  {
13480
13568
  key: "toolInstallDir",
13481
- label: "\u7EDF\u4E00\u5B89\u88C5\u76EE\u5F55",
13569
+ label: "\u7EDF\u4E00\u5B89\u88C5\u6839\u76EE\u5F55",
13482
13570
  fallback: "",
13483
13571
  placeholder: "\u7559\u7A7A = \u5404\u5382\u5546\u9ED8\u8BA4\u76EE\u5F55",
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"
13572
+ hint: "\u4F8B\u5982 D:/AI/tools\u3002npm / pnpm \u4F7F\u7528\u8BE5\u6839\uFF1B\u5382\u5546\u811A\u672C\u4F7F\u7528\u5DE5\u5177\u4E13\u5C5E\u5B50\u76EE\u5F55\uFF0C\u5B9E\u9645\u5730\u5740\u89C1\u5DE5\u5177\u884C\u3002\u56FA\u5B9A\u5382\u5546\u5730\u5740\u7684\u65B9\u5F0F\u4F1A\u8BF4\u660E\u539F\u56E0\u3002"
13485
13573
  },
13486
13574
  {
13487
13575
  key: "toolNpmRegistry",
@@ -13498,7 +13586,7 @@ var INSTALL_SETTING_FIELDS = [
13498
13586
  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"
13499
13587
  }
13500
13588
  ];
13501
- function InstallSettings({ settingsScope, install, onPendingChange }) {
13589
+ function InstallSettings({ settingsScope, install, onPendingChange, onMutationStart }) {
13502
13590
  const [snapshot, setSnapshot] = import_react5.default.useState(() => settingsScope?.getSnapshot?.() ?? { value: {}, writable: false });
13503
13591
  const [drafts, setDrafts] = import_react5.default.useState({});
13504
13592
  const [saving, setSaving] = import_react5.default.useState(false);
@@ -13535,7 +13623,12 @@ function InstallSettings({ settingsScope, install, onPendingChange }) {
13535
13623
  setNotice("");
13536
13624
  try {
13537
13625
  const entries = Object.entries(drafts).map(([key, value]) => ({ op: "set", path: [key], value }));
13538
- const accepted = await settingsScope.mutate(entries, snapshot.revision);
13626
+ const finishMutation = onMutationStart?.(entries);
13627
+ const onSettled = (outcome) => {
13628
+ const current = finishMutation?.(outcome);
13629
+ if (current && outcome.accepted) setNotice("\u5B89\u88C5\u8BBE\u7F6E\u5DF2\u4FDD\u5B58\u3002");
13630
+ };
13631
+ const accepted = await mutateToolInstallSettings(settingsScope, entries, snapshot.revision, { onSettled });
13539
13632
  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");
13540
13633
  const next = settingsScope.getSnapshot();
13541
13634
  setSnapshot(next);
@@ -13575,7 +13668,7 @@ function InstallSettings({ settingsScope, install, onPendingChange }) {
13575
13668
  onPendingChange?.(false);
13576
13669
  } }, "\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}` : ""));
13577
13670
  }
13578
- function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth, settingsScope }) {
13671
+ function OfficialToolsCard({ listOfficialTools, loadOfficialInstallSettings, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth, settingsScope }) {
13579
13672
  const healthById = Object.fromEntries((health?.tools ?? []).map((item) => [item.id, item]));
13580
13673
  const [probeState, setProbeState] = import_react5.default.useState({ status: "loading", probes: [], capabilities: [], readiness: [], install: null, error: "" });
13581
13674
  const [jobs, setJobs] = import_react5.default.useState({});
@@ -13583,12 +13676,17 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13583
13676
  const [settingsPending, setSettingsPending] = import_react5.default.useState(false);
13584
13677
  const [methodSaving, setMethodSaving] = import_react5.default.useState(null);
13585
13678
  const [previewPending, setPreviewPending] = import_react5.default.useState(false);
13586
- const [settingsWritable, setSettingsWritable] = import_react5.default.useState(() => settingsScope?.getSnapshot?.()?.writable === true);
13679
+ const [settingsSnapshot, setSettingsSnapshot] = import_react5.default.useState(() => settingsScope?.getSnapshot?.() ?? { value: {}, writable: false });
13680
+ const [previewError, setPreviewError] = import_react5.default.useState("");
13681
+ const [probeSettingsStale, setProbeSettingsStale] = import_react5.default.useState(false);
13587
13682
  const settingsPendingRef = import_react5.default.useRef(false);
13588
13683
  const methodSavingRef = import_react5.default.useRef(null);
13589
13684
  const previewPendingRef = import_react5.default.useRef(false);
13590
13685
  const mounted = import_react5.default.useRef(false);
13591
13686
  const request = import_react5.default.useRef(0);
13687
+ const previewRequest = import_react5.default.useRef(0);
13688
+ const mutationGeneration = import_react5.default.useRef(0);
13689
+ const mutationPendingRef = import_react5.default.useRef(null);
13592
13690
  const submitting = import_react5.default.useRef(/* @__PURE__ */ new Set());
13593
13691
  const polling = import_react5.default.useRef(/* @__PURE__ */ new Set());
13594
13692
  const jobsRef = import_react5.default.useRef({});
@@ -13606,29 +13704,46 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13606
13704
  const refresh = async () => {
13607
13705
  onRefreshHealth?.();
13608
13706
  const current = ++request.current;
13707
+ const settingsSignature = toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value);
13609
13708
  setProbeState((previous) => ({
13610
13709
  ...previous,
13611
- status: previous.probes.length > 0 ? "refreshing" : "loading",
13710
+ status: previous.probes.length > 0 || previous.install ? "refreshing" : "loading",
13612
13711
  error: ""
13613
13712
  }));
13614
13713
  try {
13615
13714
  if (typeof listOfficialTools !== "function") throw new Error("\u5B98\u65B9\u5DE5\u5177\u5B89\u88C5\u6865\u5C1A\u672A\u52A0\u8F7D\u3002");
13616
13715
  const response = await listOfficialTools();
13617
13716
  if (!mounted.current || current !== request.current) return;
13717
+ if (settingsSignature !== toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value)) return;
13618
13718
  if (!response?.ok) throw new Error(remoteError(response, "\u65E0\u6CD5\u68C0\u6D4B\u5B98\u65B9\u5DE5\u5177\u3002"));
13619
13719
  const probes = Array.isArray(response.value?.tools) ? response.value.tools : [];
13620
13720
  const capabilities = Array.isArray(response.value?.executionCapabilities) ? response.value.executionCapabilities : [];
13621
13721
  const readiness = Array.isArray(response.value?.executionReadiness) ? response.value.executionReadiness : [];
13622
- setProbeState({ status: "ready", probes, capabilities, readiness, install: response.value?.install ?? null, error: "" });
13623
- previewPendingRef.current = false;
13624
- setPreviewPending(false);
13722
+ const install2 = response.value?.install;
13723
+ const matches = settingsSignature === toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value) && installSummaryMatchesSettings(install2, settingsScope?.getSnapshot?.()?.value);
13724
+ if (!matches) return;
13725
+ setProbeState((previous) => ({
13726
+ status: "ready",
13727
+ probes,
13728
+ capabilities,
13729
+ readiness,
13730
+ install: matches ? install2 : previous.install,
13731
+ error: ""
13732
+ }));
13733
+ setProbeSettingsStale(false);
13734
+ if (matches && !mutationPendingRef.current) {
13735
+ previewPendingRef.current = false;
13736
+ setPreviewPending(false);
13737
+ setPreviewError("");
13738
+ }
13625
13739
  onProbes({ probes, capabilities, readiness, hostVersion: typeof response.value?.hostVersion === "string" ? response.value.hostVersion : null });
13626
13740
  } catch (error) {
13627
13741
  if (!mounted.current || current !== request.current) return;
13742
+ if (settingsSignature !== toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value)) return;
13628
13743
  const message = remoteErrorText(text5(error?.message), "\u65E0\u6CD5\u68C0\u6D4B\u5B98\u65B9\u5DE5\u5177\u3002");
13629
13744
  let kept = false;
13630
13745
  setProbeState((previous) => {
13631
- if (previous.probes.length > 0) {
13746
+ if (previous.probes.length > 0 || previous.install) {
13632
13747
  kept = true;
13633
13748
  return { ...previous, status: "ready", error: message };
13634
13749
  }
@@ -13637,9 +13752,38 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13637
13752
  if (!kept) onProbes({ probes: [], capabilities: [], readiness: [] });
13638
13753
  }
13639
13754
  };
13755
+ const refreshInstallSettings = async () => {
13756
+ const current = ++previewRequest.current;
13757
+ const snapshot = settingsScope?.getSnapshot?.();
13758
+ const signature2 = toolInstallSettingsSignature(snapshot?.value);
13759
+ previewPendingRef.current = true;
13760
+ setPreviewPending(true);
13761
+ setPreviewError("");
13762
+ try {
13763
+ 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");
13764
+ const response = await loadOfficialInstallSettings();
13765
+ if (!mounted.current || current !== previewRequest.current || signature2 !== toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value)) return;
13766
+ if (!response?.ok) throw new Error(remoteError(response, "\u65E0\u6CD5\u8BFB\u53D6\u5B89\u88C5\u8BBE\u7F6E\u3002"));
13767
+ if (!installSummaryMatchesSettings(response.value?.install, settingsScope?.getSnapshot?.()?.value)) {
13768
+ 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");
13769
+ }
13770
+ setProbeState((previous) => ({
13771
+ ...previous,
13772
+ status: previous.status === "loading" || previous.status === "error" ? "ready" : previous.status,
13773
+ install: response.value.install
13774
+ }));
13775
+ if (!mutationPendingRef.current) {
13776
+ previewPendingRef.current = false;
13777
+ setPreviewPending(false);
13778
+ }
13779
+ } catch (error) {
13780
+ if (mounted.current && current === previewRequest.current) setPreviewError(text5(error?.message) || "\u5B89\u88C5\u547D\u4EE4\u5237\u65B0\u5931\u8D25\u3002");
13781
+ }
13782
+ };
13640
13783
  import_react5.default.useEffect(() => {
13641
13784
  mounted.current = true;
13642
13785
  void refresh();
13786
+ void refreshInstallSettings();
13643
13787
  if (typeof officialToolInstallStatus === "function") {
13644
13788
  void Promise.all(OFFICIAL_TOOLS.map(async (tool) => {
13645
13789
  try {
@@ -13664,29 +13808,40 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13664
13808
  return () => {
13665
13809
  mounted.current = false;
13666
13810
  request.current += 1;
13811
+ previewRequest.current += 1;
13812
+ };
13813
+ }, []);
13814
+ const refreshInstallSettingsRef = import_react5.default.useRef(refreshInstallSettings);
13815
+ refreshInstallSettingsRef.current = refreshInstallSettings;
13816
+ const beginSettingsMutation = import_react5.default.useCallback((operations) => {
13817
+ const generation = ++mutationGeneration.current;
13818
+ mutationPendingRef.current = { generation, operations };
13819
+ previewPendingRef.current = true;
13820
+ setPreviewPending(true);
13821
+ return () => {
13822
+ if (!mounted.current || mutationPendingRef.current?.generation !== generation) return false;
13823
+ mutationPendingRef.current = null;
13824
+ void refreshInstallSettingsRef.current();
13825
+ return true;
13667
13826
  };
13668
13827
  }, []);
13669
- const refreshRef = import_react5.default.useRef(refresh);
13670
- refreshRef.current = refresh;
13671
13828
  import_react5.default.useEffect(() => {
13672
13829
  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
- ]);
13830
+ const signature2 = (snapshot) => toolInstallSettingsSignature(snapshot?.value);
13680
13831
  let previous = signature2(settingsScope.getSnapshot());
13681
13832
  return settingsScope.subscribe(() => {
13682
13833
  const snapshot = settingsScope.getSnapshot();
13683
- setSettingsWritable(snapshot.writable === true);
13834
+ setSettingsSnapshot(snapshot);
13684
13835
  const next = signature2(snapshot);
13685
13836
  if (next === previous) return;
13686
13837
  previous = next;
13838
+ request.current += 1;
13839
+ setProbeSettingsStale(true);
13840
+ setProbeState((previous2) => ({ ...previous2, status: "ready", probes: [], readiness: [], error: "" }));
13841
+ onProbes({ probes: [], capabilities: [], readiness: [] });
13687
13842
  previewPendingRef.current = true;
13688
13843
  setPreviewPending(true);
13689
- void refreshRef.current();
13844
+ void refreshInstallSettingsRef.current();
13690
13845
  });
13691
13846
  }, [settingsScope]);
13692
13847
  import_react5.default.useEffect(() => {
@@ -13797,11 +13952,14 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13797
13952
  if (methodId) map[toolId] = methodId;
13798
13953
  else delete map[toolId];
13799
13954
  try {
13800
- const accepted = await settingsScope.mutate([{ op: "set", path: ["toolInstallMethodsJson"], value: JSON.stringify(map) }], snapshot.revision);
13955
+ const operations = [{ op: "set", path: ["toolInstallMethodsJson"], value: JSON.stringify(map) }];
13956
+ const finishMutation = beginSettingsMutation(operations);
13957
+ const onSettled = (outcome) => {
13958
+ if (finishMutation(outcome) && outcome.accepted) setRowErrors((previous) => ({ ...previous, [toolId]: "" }));
13959
+ };
13960
+ const accepted = await mutateToolInstallSettings(settingsScope, operations, snapshot.revision, { onSettled });
13801
13961
  if (!accepted) throw new Error("\u5B89\u88C5\u65B9\u5F0F\u672A\u88AB\u4FDD\u5B58\uFF0C\u53EF\u80FD\u88AB\u5176\u4ED6\u9875\u9762\u4FEE\u6539\u3002");
13802
- previewPendingRef.current = true;
13803
- setPreviewPending(true);
13804
- await refresh();
13962
+ setSettingsSnapshot(settingsScope.getSnapshot());
13805
13963
  } catch (error) {
13806
13964
  if (mounted.current) setRowErrors((previous) => ({ ...previous, [toolId]: text5(error?.message) || "\u5B89\u88C5\u65B9\u5F0F\u4FDD\u5B58\u5931\u8D25\u3002" }));
13807
13965
  } finally {
@@ -13826,9 +13984,14 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13826
13984
  const capabilitiesById = Object.fromEntries(probeState.capabilities.map((item) => [item.id, item]));
13827
13985
  const readinessById = Object.fromEntries(probeState.readiness.map((item) => [item.id, item]));
13828
13986
  const installById = Object.fromEntries((probeState.install?.tools ?? []).map((item) => [item.id, item]));
13987
+ const savedMethods = savedToolInstallMethods(settingsSnapshot.value);
13829
13988
  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: () => {
13830
13989
  void refresh();
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) => {
13990
+ } }, "\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: () => {
13991
+ void refreshInstallSettings();
13992
+ } }, "\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: () => {
13993
+ void refreshInstallSettings();
13994
+ } }, "\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) => {
13832
13995
  const summary = installById[tool.id];
13833
13996
  const command = summary ? summary.command : installCommandLine(tool);
13834
13997
  const updateCommand = summary?.updateCommand ?? command;
@@ -13850,15 +14013,15 @@ function OfficialToolsCard({ listOfficialTools, installOfficialTool, uninstallOf
13850
14013
  removable: !(summary?.uninstallError ?? (summary?.uninstallCommand ? "" : summary?.error))
13851
14014
  }
13852
14015
  });
13853
- const verified = job?.status === "succeeded" && (job.operation === "uninstall" ? job.postInstallProbe?.installed === false : job.postInstallProbe?.installed === true);
14016
+ const verified = Boolean(probe) && job?.status === "succeeded" && (job.operation === "uninstall" ? job.postInstallProbe?.installed === false : job.postInstallProbe?.installed === true);
13854
14017
  const operation = job?.operation === "uninstall" ? "\u5378\u8F7D" : job?.operation === "repair" ? "\u4FEE\u590D" : "\u5B89\u88C5";
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);
14018
+ 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";
13856
14019
  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(
13857
14020
  "select",
13858
14021
  {
13859
14022
  className: "mr-mini-select",
13860
- value: summary?.savedMethodId ?? "",
13861
- disabled: running || settingsBusy || !settingsWritable,
14023
+ value: savedMethods[tool.id] ?? "",
14024
+ disabled: running || settingsPending || Boolean(methodSaving) || settingsSnapshot.writable !== true,
13862
14025
  onChange: (event) => {
13863
14026
  void setInstallMethod(tool.id, event.target.value);
13864
14027
  }
@@ -14001,7 +14164,7 @@ ${reasons.map((entry, index) => `${index + 1}. ${entry.text}`).join("\n")}
14001
14164
  }, []);
14002
14165
  return { health, ledger, boundaries, busy, refreshHealth, refreshLedger, refreshBoundaries, finishOnboarding, rate, rerun };
14003
14166
  }
14004
- function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
14167
+ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, loadOfficialInstallSettings, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries, previewRun, startRun, terminalApi }) {
14005
14168
  const workbench = useWorkbenchData({ toolHealth, completeOnboarding, loadLedger, rateResult, rerunStep, loadBoundaries });
14006
14169
  const [catalogState, setCatalogState] = import_react5.default.useState({ status: "loading", catalog: null, error: "" });
14007
14170
  const [task, setTask] = import_react5.default.useState("");
@@ -14223,6 +14386,7 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, install
14223
14386
  OfficialToolsCard,
14224
14387
  {
14225
14388
  listOfficialTools,
14389
+ loadOfficialInstallSettings,
14226
14390
  installOfficialTool,
14227
14391
  uninstallOfficialTool,
14228
14392
  repairOfficialTool,
@@ -14413,6 +14577,7 @@ var toolIdParameter = Object.freeze({
14413
14577
  var jsonParameter = (name, codec) => Object.freeze({ name, wire: name, source: "json", codec });
14414
14578
  var OFFICIAL_TOOLS_REMOTE_DESCRIPTORS = Object.freeze([
14415
14579
  descriptor("list", [], listResultCodec),
14580
+ descriptor("installSettings", [], anyObjectCodec("OfficialInstallSettings")),
14416
14581
  descriptor("installTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
14417
14582
  descriptor("uninstallTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
14418
14583
  descriptor("repairTool", [jsonParameter("request", toolActionCodec)], installResultCodec),
@@ -14740,8 +14905,8 @@ function RouterSettingsCard(props) {
14740
14905
  import_dsh_client_ui_primitives.SettingsValueField,
14741
14906
  {
14742
14907
  id: "model-router-tool-install-dir",
14743
- label: "\u7EDF\u4E00\u5B89\u88C5\u76EE\u5F55",
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",
14908
+ label: "\u7EDF\u4E00\u5B89\u88C5\u6839\u76EE\u5F55",
14909
+ hint: "\u586B\u5199\u7EDD\u5BF9\u8DEF\u5F84\uFF0C\u7559\u7A7A\u4F7F\u7528\u5382\u5546\u9ED8\u8BA4\u4F4D\u7F6E\u3002npm / pnpm \u5171\u7528\u8BE5\u6839\uFF1B\u53EF\u914D\u7F6E\u5382\u5546\u811A\u672C\u4F7F\u7528\u5DE5\u5177\u4E13\u5C5E\u5B50\u76EE\u5F55\uFF0C\u5B9E\u9645\u4F4D\u7F6E\u89C1\u5B98\u65B9\u5DE5\u5177\u9875\u3002\u66F4\u65B0\u590D\u7528\u6240\u9009\u65B9\u5F0F\u3001\u76EE\u5F55\u4E0E\u4E0B\u8F7D\u6E90\u3002",
14745
14910
  disabled,
14746
14911
  ...state.toolInstallDir,
14747
14912
  overriddenLabel: FIELD_COPY.overridden,
@@ -14836,6 +15001,7 @@ function registerUi(ctx) {
14836
15001
  loadCatalog: () => ctx.remote.session.modelCatalog(),
14837
15002
  settingsScope,
14838
15003
  listOfficialTools: () => officialToolsRemote.list(),
15004
+ loadOfficialInstallSettings: () => officialToolsRemote.installSettings(),
14839
15005
  installOfficialTool: (request) => officialToolsRemote.installTool(request),
14840
15006
  uninstallOfficialTool: (request) => officialToolsRemote.uninstallTool(request),
14841
15007
  repairOfficialTool: (request) => officialToolsRemote.repairTool(request),
@@ -1434,11 +1434,25 @@ export function routerRemoteServices(ctx, config, { terminals = null } = {}) {
1434
1434
  /** Register model-facing tools and the human /router command. */
1435
1435
  export function apply(ctx, config = {}) {
1436
1436
  // Install addresses (directory, npm source, per-tool method and script source)
1437
- // live in volatile config, so re-apply picks up whatever the user last saved.
1438
- // A malformed value is reported here rather than at the first click.
1437
+ // are volatile refs: Loader changes them without re-applying the plugin.
1438
+ // Read them after the owning fiber's live update, before a saved view is sent.
1439
1439
  let installPreferenceError = null
1440
- try { setInstallPreferences(parseInstallPreferences(config, { tools: OFFICIAL_TOOLS, ...currentInstallOptions() })) }
1441
- catch (error) { installPreferenceError = errorText(error) }
1440
+ const syncInstallPreferences = () => {
1441
+ try {
1442
+ const next = parseInstallPreferences(config, { tools: OFFICIAL_TOOLS, ...currentInstallOptions() })
1443
+ setInstallPreferences(next)
1444
+ installPreferenceError = null
1445
+ } catch (error) { installPreferenceError = errorText(error) }
1446
+ }
1447
+ const installFields = new Set(['toolInstallDir', 'toolNpmRegistry', 'toolInstallMethodsJson', 'toolScriptUrlsJson', 'toolAllowScriptInstall'])
1448
+ syncInstallPreferences()
1449
+ // Loader scopes this event to the fiber whose refs were committed.
1450
+ ctx.on('loader/volatile-update', paths => {
1451
+ if (Array.isArray(paths) && paths.some(path => installFields.has(path?.[0]))) syncInstallPreferences()
1452
+ })
1453
+ const assertInstallPreferences = () => {
1454
+ if (installPreferenceError) throw new Error(`官方工具安装设置无效:${installPreferenceError}`)
1455
+ }
1442
1456
  // The official Host injects typert; direct lightweight uses of apply may
1443
1457
  // supply only the model/command services and do not expose the Desktop RPC.
1444
1458
  if (ctx.typert) {
@@ -1449,7 +1463,11 @@ export function apply(ctx, config = {}) {
1449
1463
  process.once('exit', onExit)
1450
1464
  return () => { process.off('exit', onExit); terminals.disposeAll('dispose') }
1451
1465
  }, 'model-router-galgame: interactive terminals')
1452
- registerOfficialToolsRemote(ctx, routerRemoteServices(ctx, config, { terminals }))
1466
+ registerOfficialToolsRemote(ctx, {
1467
+ ...routerRemoteServices(ctx, config, { terminals }),
1468
+ installSettingsError: () => installPreferenceError,
1469
+ installRequestedDirectory: () => valueOf(config, 'toolInstallDir', ''),
1470
+ })
1453
1471
  }
1454
1472
  ctx.on('tools/pre-execute', async (exec, next) => {
1455
1473
  const decision = await next()
@@ -1498,7 +1516,7 @@ export function apply(ctx, config = {}) {
1498
1516
  if (reasons.length) return combinedAsk(reasons)
1499
1517
  return decision
1500
1518
  })
1501
- registerOfficialToolModels(ctx, config)
1519
+ registerOfficialToolModels(ctx, config, assertInstallPreferences)
1502
1520
  ctx.tools.register(defineTool({
1503
1521
  name: 'model_router_routes',
1504
1522
  description: 'List provider/model routes registered in the official DeepSeek Harness model directory. Credential and network availability are not verified. No API keys or endpoints are returned.',
@@ -1611,6 +1629,7 @@ export function apply(ctx, config = {}) {
1611
1629
  const toolId = matched[2]
1612
1630
  const method = matched[3]
1613
1631
  try {
1632
+ assertInstallPreferences()
1614
1633
  const job = action === 'uninstall' ? startUninstall(toolId, { method })
1615
1634
  : action === 'repair' ? startRepair(toolId, { method })
1616
1635
  : startInstall(toolId, { method })
@@ -1649,7 +1668,7 @@ async function waitForInstall(toolId, signal) {
1649
1668
  }
1650
1669
 
1651
1670
  /** Register the model-facing official-tool probes and installer. */
1652
- function registerOfficialToolModels(ctx, config) {
1671
+ function registerOfficialToolModels(ctx, config, assertInstallPreferences) {
1653
1672
  ctx.tools.register(defineTool({
1654
1673
  name: 'model_router_tools',
1655
1674
  description: 'Probe the fixed registry of official model tools (Kimi Code, Claude Code, Codex, MiniMax Code, MiMo Code, Grok Build, Gemini CLI, OpenCode, Step Code, ZCode) and report which are installed with their versions, plus each tool\'s available install methods and the effective install directory and npm source. Never reads credentials.',
@@ -1680,6 +1699,7 @@ function registerOfficialToolModels(ctx, config) {
1680
1699
  },
1681
1700
  output: JSON_OUTPUT,
1682
1701
  async execute(args, exec) {
1702
+ assertInstallPreferences()
1683
1703
  const job = startInstall(args.tool, { method: text(args.method) || undefined })
1684
1704
  const settled = await waitForInstall(job.tool, exec.signal)
1685
1705
  const tool = getOfficialTool(args.tool)
@@ -1704,6 +1724,7 @@ function registerOfficialToolModels(ctx, config) {
1704
1724
  },
1705
1725
  output: JSON_OUTPUT,
1706
1726
  async execute(args, exec) {
1727
+ assertInstallPreferences()
1707
1728
  const job = startRepair(args.tool, { method: text(args.method) || undefined })
1708
1729
  const settled = await waitForInstall(job.tool, exec.signal)
1709
1730
  const tool = getOfficialTool(args.tool)
@@ -1726,6 +1747,7 @@ function registerOfficialToolModels(ctx, config) {
1726
1747
  },
1727
1748
  output: JSON_OUTPUT,
1728
1749
  async execute(args, exec) {
1750
+ assertInstallPreferences()
1729
1751
  const job = startUninstall(args.tool, { method: text(args.method) || undefined })
1730
1752
  const settled = await waitForInstall(job.tool, exec.signal)
1731
1753
  const tool = getOfficialTool(args.tool)