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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -480,12 +480,12 @@ var OFFICIAL_TOOLS = Object.freeze([
480
480
  id: "zcode",
481
481
  label: "ZCode",
482
482
  vendor: "Z.ai",
483
- purpose: "\u667A\u8C31\u5B98\u65B9 ZCode \u684C\u9762\u7248\uFF0C\u5185\u542B GLM \u7F16\u7A0B\u4EE3\u7406\u3002Windows \u5B89\u88C5\u5668\u53EF\u9009\u62E9 D \u76D8\u76EE\u5F55\u3002",
483
+ purpose: "\u667A\u8C31\u5B98\u65B9 ZCode \u684C\u9762\u7248\uFF0C\u5185\u542B\u53EF\u72EC\u7ACB\u8FD0\u884C\u7684 GLM CLI/TUI\u3002\u53EF\u5355\u72EC\u914D\u7F6E\u684C\u9762\u76EE\u5F55\uFF0C\u5185\u7F6E CLI \u968F\u684C\u9762\u7248\u66F4\u65B0\u3002",
484
484
  manager: "signed-windows-installer",
485
485
  installArgs: [],
486
486
  uninstallArgs: [],
487
487
  probeExecutables: [],
488
- probeNote: "\u68C0\u6D4B\u7ECF\u8FC7\u6709\u6548\u7B7E\u540D\u7684 ZCode.exe \u548C\u540C\u76EE\u5F55 GLM \u8D44\u6E90\uFF1B\u684C\u9762\u5B89\u88C5\u5668\u9700\u4EBA\u5DE5\u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E\u3002",
488
+ probeNote: "\u68C0\u6D4B\u7ECF\u8FC7\u6709\u6548\u7B7E\u540D\u7684 ZCode.exe \u548C\u540C\u76EE\u5F55 GLM CLI/TUI \u8D44\u6E90\uFF1B\u684C\u9762\u5B89\u88C5\u5668\u9700\u4EBA\u5DE5\u9009\u62E9\u5B89\u88C5\u4F4D\u7F6E\u3002",
489
489
  providerHints: ["zai", "z.ai", "zcode", "glm", "zhipu", "bigmodel"]
490
490
  })
491
491
  ]);
@@ -813,6 +813,25 @@ function expandInstallDir(value, { home = "", env = {} } = {}) {
813
813
  if (/^\\\\[^\\]+\\[^\\]+$/.test(withoutTrailing)) throw new Error("\u5B89\u88C5\u76EE\u5F55\u4E0D\u80FD\u662F\u7F51\u7EDC\u5171\u4EAB\u6839\u76EE\u5F55\u3002");
814
814
  return expanded;
815
815
  }
816
+ function normalizeZCodeInstallDir(value, options = {}) {
817
+ const input = String(value ?? "");
818
+ if (/[\x00-\x1f]/.test(input)) throw new Error("ZCode \u5B89\u88C5\u4F4D\u7F6E\u5305\u542B\u65E0\u6548\u5B57\u7B26\u3002");
819
+ const raw = input.trim().replace(/^"(.*)"$/, "$1");
820
+ if (!raw) return "";
821
+ let path;
822
+ try {
823
+ path = expandInstallDir(raw, options);
824
+ } catch (error) {
825
+ throw new Error(`ZCode \u5B89\u88C5\u4F4D\u7F6E\u65E0\u6548\uFF1A${error.message}`);
826
+ }
827
+ if (!/^[A-Za-z]:[\\/]/.test(path)) {
828
+ throw new Error("ZCode \u5B89\u88C5\u4F4D\u7F6E\u5FC5\u987B\u662F Windows \u672C\u5730\u7EDD\u5BF9\u76EE\u5F55\u6216 ZCode.exe \u8DEF\u5F84\u3002");
829
+ }
830
+ if (/\.exe[\\/]*$/i.test(path) && !/[\\/]ZCode\.exe[\\/]*$/i.test(path)) {
831
+ throw new Error("ZCode \u7A0B\u5E8F\u8DEF\u5F84\u5FC5\u987B\u6307\u5411\u5B98\u65B9 ZCode.exe\u3002");
832
+ }
833
+ return path.replace(/[\\/]+$/, "");
834
+ }
816
835
  function normalizeSourceUrl(value, subject) {
817
836
  const raw = String(value ?? "").trim();
818
837
  if (!raw) return "";
@@ -856,6 +875,7 @@ function keepKnownToolIds(raw, known, subject) {
856
875
  function parseInstallPreferences(config = {}, { tools = [], home = "", env = {} } = {}) {
857
876
  const known = new Set(tools.map((tool) => tool.id));
858
877
  const installDir = expandInstallDir(valueOf(config, "toolInstallDir", ""), { home, env });
878
+ const zcodeInstallDir = normalizeZCodeInstallDir(valueOf(config, "zcodeInstallDir", ""), { home, env });
859
879
  const registry = normalizeSourceUrl(valueOf(config, "toolNpmRegistry", DEFAULT_NPM_REGISTRY), "npm \u6E90") || DEFAULT_NPM_REGISTRY;
860
880
  const methodRaw = keepKnownToolIds(
861
881
  parseJsonObject(valueOf(config, "toolInstallMethodsJson", "{}"), "\u5B89\u88C5\u65B9\u5F0F\u8986\u76D6"),
@@ -880,6 +900,7 @@ function parseInstallPreferences(config = {}, { tools = [], home = "", env = {}
880
900
  }
881
901
  return {
882
902
  installDir,
903
+ zcodeInstallDir,
883
904
  registry,
884
905
  methods,
885
906
  scriptUrls,
@@ -2375,18 +2396,20 @@ var INSTALL_DEFAULTS = Object.freeze({
2375
2396
  toolInstallDir: "",
2376
2397
  toolNpmRegistry: DEFAULT_NPM_REGISTRY,
2377
2398
  toolScriptUrlsJson: "{}",
2378
- toolAllowScriptInstall: true
2399
+ toolAllowScriptInstall: true,
2400
+ zcodeInstallDir: ""
2379
2401
  });
2380
2402
  function toolInstallSettingsSignature(value = {}) {
2381
2403
  try {
2382
- const preferences = parseInstallPreferences({ ...value, toolInstallDir: "" }, { tools: OFFICIAL_TOOLS });
2404
+ const preferences = parseInstallPreferences({ ...value, toolInstallDir: "", zcodeInstallDir: "" }, { tools: OFFICIAL_TOOLS });
2383
2405
  const sorted = (map) => Object.entries(map).sort(([left], [right]) => left.localeCompare(right));
2384
2406
  return JSON.stringify([
2385
2407
  String(value.toolInstallDir ?? "").trim(),
2386
2408
  preferences.registry,
2387
2409
  sorted(preferences.methods),
2388
2410
  sorted(preferences.scriptUrls),
2389
- preferences.allowScriptInstall
2411
+ preferences.allowScriptInstall,
2412
+ String(value.zcodeInstallDir ?? "").trim()
2390
2413
  ]);
2391
2414
  } catch {
2392
2415
  return JSON.stringify([
@@ -2395,7 +2418,8 @@ function toolInstallSettingsSignature(value = {}) {
2395
2418
  value.toolNpmRegistry,
2396
2419
  value.toolInstallMethodsJson,
2397
2420
  value.toolScriptUrlsJson,
2398
- value.toolAllowScriptInstall
2421
+ value.toolAllowScriptInstall,
2422
+ value.zcodeInstallDir
2399
2423
  ]);
2400
2424
  }
2401
2425
  }
@@ -2406,7 +2430,8 @@ function installSummaryMatchesSettings(summary, value) {
2406
2430
  toolNpmRegistry: summary.registry || DEFAULT_NPM_REGISTRY,
2407
2431
  toolInstallMethodsJson: JSON.stringify(summary.methods ?? {}),
2408
2432
  toolScriptUrlsJson: JSON.stringify(summary.scriptUrls ?? {}),
2409
- toolAllowScriptInstall: summary.allowScriptInstall !== false
2433
+ toolAllowScriptInstall: summary.allowScriptInstall !== false,
2434
+ zcodeInstallDir: summary.requestedZCodeInstallDir ?? summary.zcodeInstallDir ?? ""
2410
2435
  });
2411
2436
  }
2412
2437
  function savedToolInstallMethods(value = {}) {
@@ -2452,6 +2477,10 @@ function toolInstallAction({ tool, probe, readiness, job, probeStatus, latestVer
2452
2477
  const installed = probe?.installed === true;
2453
2478
  const order = installed && latestVersion ? compareReleaseVersions(probe.version, latestVersion) : null;
2454
2479
  const desktop = tool.manager === "signed-windows-installer";
2480
+ if (desktop) return {
2481
+ label: running ? "\u684C\u9762\u5B89\u88C5\u5904\u7406\u4E2D\u2026" : installed ? "\u68C0\u67E5\u5E76\u66F4\u65B0\u684C\u9762\u7248" : job?.status === "failed" ? "\u91CD\u8BD5\u684C\u9762\u5B89\u88C5" : "\u5B89\u88C5 ZCode \u684C\u9762\u7248",
2482
+ disabled: !(probeStatus === "ready" || probeStatus === "refreshing") || running
2483
+ };
2455
2484
  const noRunner = desktop || tool.headlessAdapter === true;
2456
2485
  const repair = installed && !noRunner && readiness?.ready === false && order !== 0;
2457
2486
  const label = running ? "\u5B89\u88C5\u4E2D\u2026" : !installed ? job?.status === "failed" ? "\u91CD\u8BD5\u5B89\u88C5" : desktop ? "\u4E0B\u8F7D\u6700\u65B0\u5B89\u88C5\u5668" : "\u4E00\u952E\u5B89\u88C5\u6700\u65B0\u7248" : order === -1 ? `\u66F4\u65B0\u5230\u6700\u65B0\u7248 ${latestVersion}` : order === 1 ? "\u5DF2\u9AD8\u4E8E\u6700\u65B0\u6B63\u5F0F\u7248" : repair ? "\u4FEE\u590D\u5B98\u65B9\u6267\u884C\u5165\u53E3" : order === 0 ? "\u5DF2\u662F\u6700\u65B0\u7248\u672C" : "\u5B89\u88C5\u6700\u65B0\u7248";
@@ -2470,9 +2499,9 @@ function toolMaintenanceActions({ tool, probe, job, probeStatus, summary }) {
2470
2499
  const busy = running || !interactive;
2471
2500
  return {
2472
2501
  repair: {
2473
- label: "\u4E00\u952E\u4FEE\u590D",
2474
- title: "\u7528\u540C\u4E00\u6761\u56FA\u5B9A\u5B98\u65B9\u5B89\u88C5\u547D\u4EE4\u91CD\u65B0\u5B89\u88C5\u4E00\u904D\uFF0C\u4FEE\u590D\u635F\u574F\u6216\u4E0D\u5B8C\u6574\u7684\u5B89\u88C5",
2475
- disabled: busy || desktop || summary?.installable === false
2502
+ label: desktop ? "\u91CD\u65B0\u5B89\u88C5\u684C\u9762\u7248" : "\u4E00\u952E\u4FEE\u590D",
2503
+ title: desktop ? "\u6253\u5F00\u5DF2\u6838\u9A8C\u7684\u5B98\u65B9\u684C\u9762\u5B89\u88C5\u5668\uFF1B\u5B8C\u6210\u5B89\u88C5\u540E\u91CD\u65B0\u4F53\u68C0\uFF0C\u5237\u65B0\u5185\u7F6E TUI" : "\u7528\u540C\u4E00\u6761\u56FA\u5B9A\u5B98\u65B9\u5B89\u88C5\u547D\u4EE4\u91CD\u65B0\u5B89\u88C5\u4E00\u904D\uFF0C\u4FEE\u590D\u635F\u574F\u6216\u4E0D\u5B8C\u6574\u7684\u5B89\u88C5",
2504
+ disabled: busy || summary?.installable === false
2476
2505
  },
2477
2506
  uninstall: {
2478
2507
  label: "\u4E00\u952E\u5378\u8F7D",
@@ -2484,7 +2513,7 @@ function toolMaintenanceActions({ tool, probe, job, probeStatus, summary }) {
2484
2513
  function installClickRefusal({ tool, submitting = false, running = false, operation = "install", pendingSettings = false, installError = "" } = {}) {
2485
2514
  if (!tool) return "\u672A\u77E5\u5B98\u65B9\u5DE5\u5177\uFF0C\u65E0\u6CD5\u5F00\u59CB\u5B89\u88C5\u3002";
2486
2515
  if (tool.unsupported) return String(tool.unsupportedReason ?? "").trim() || "\u6B64\u5DE5\u5177\u6682\u4E0D\u652F\u6301\u4E00\u952E\u5B89\u88C5\u3002";
2487
- if (tool.manager === "signed-windows-installer" && operation !== "install") {
2516
+ if (tool.manager === "signed-windows-installer" && operation === "uninstall") {
2488
2517
  return `${tool.label} \u7531\u5B98\u65B9\u7B7E\u540D\u684C\u9762\u5B89\u88C5\u5668\u5B89\u88C5\uFF0C\u8BF7\u5728\u7CFB\u7EDF\u201C\u5E94\u7528\u201D\u4E2D\u5378\u8F7D\u5B83\u3002`;
2489
2518
  }
2490
2519
  if (submitting || running) {
@@ -12733,7 +12762,7 @@ var xterm_default = `/**
12733
12762
  `;
12734
12763
 
12735
12764
  // .dsh-plugin/client/host-version.mjs
12736
- var ROUTER_CLIENT_VERSION = true ? "0.18.2" : "";
12765
+ var ROUTER_CLIENT_VERSION = true ? "0.19.0" : "";
12737
12766
  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";
12738
12767
  function isMissingRemoteMethod(message) {
12739
12768
  const value = String(message ?? "");
@@ -12751,7 +12780,7 @@ function staleHostNotice({ hostVersion, clientVersion = ROUTER_CLIENT_VERSION, l
12751
12780
  }
12752
12781
 
12753
12782
  // .dsh-plugin/shared/cli-terminal-protocol.mjs
12754
- var TERMINAL_TOOL_IDS = Object.freeze(["codex", "claude-code", "kimi-code", "minimax-code", "mimo-code", "grok-build", "gemini"]);
12783
+ var TERMINAL_TOOL_IDS = Object.freeze(OFFICIAL_TOOLS.filter((tool) => !tool.unsupported && (tool.probeExecutables?.length || tool.manager === "signed-windows-installer")).map((tool) => tool.id));
12755
12784
  var TERMINAL_SHELL_ID = "shell";
12756
12785
  var TERMINAL_TARGETS = Object.freeze([TERMINAL_SHELL_ID, ...TERMINAL_TOOL_IDS]);
12757
12786
  var TERMINAL_MODES = Object.freeze(["interactive", "login"]);
@@ -12781,8 +12810,14 @@ var TERMINAL_LOGIN_ARGS = Object.freeze({
12781
12810
  "minimax-code": Object.freeze(["login"]),
12782
12811
  "mimo-code": Object.freeze(["auth", "login"]),
12783
12812
  "grok-build": Object.freeze(["login"]),
12784
- gemini: Object.freeze([])
12813
+ gemini: Object.freeze([]),
12814
+ opencode: Object.freeze(["auth", "login"]),
12815
+ stepcode: Object.freeze(["login"]),
12816
+ zcode: Object.freeze([])
12785
12817
  });
12818
+ function terminalCanLogin(target) {
12819
+ return Boolean(TERMINAL_LOGIN_ARGS[target]?.length);
12820
+ }
12786
12821
  var SESSION_ID = /^term-[a-z0-9]{6,40}$/;
12787
12822
  function plainObject(value, subject) {
12788
12823
  if (value === null || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${subject} must be an object`);
@@ -12808,7 +12843,7 @@ function terminalTargetLabel(target) {
12808
12843
  function terminalCommandPreview(target, mode = "interactive", platform = "win32") {
12809
12844
  if (target === TERMINAL_SHELL_ID) return platform === "win32" ? "powershell.exe -NoLogo" : "$SHELL -l";
12810
12845
  const tool = getOfficialTool(target);
12811
- const executable = tool?.probeExecutables?.[0] ?? String(target);
12846
+ const executable = target === "zcode" ? "ZCode \u684C\u9762\u7248\u5185\u7F6E TUI" : tool?.probeExecutables?.[0] ?? String(target);
12812
12847
  const args = mode === "login" ? TERMINAL_LOGIN_ARGS[target] ?? [] : [];
12813
12848
  return [executable, ...args].join(" ");
12814
12849
  }
@@ -12817,7 +12852,7 @@ function parseTerminalStart(value) {
12817
12852
  if (!TERMINAL_TARGETS.includes(request.target)) throw new TypeError("target must be the shell or a fixed official CLI");
12818
12853
  const mode = request.mode === void 0 ? "interactive" : request.mode;
12819
12854
  if (!TERMINAL_MODES.includes(mode)) throw new TypeError("mode must be interactive or login");
12820
- if (mode === "login" && request.target === TERMINAL_SHELL_ID) throw new TypeError("the shell has no login mode");
12855
+ if (mode === "login" && !terminalCanLogin(request.target)) throw new TypeError("this target has no login mode");
12821
12856
  if (!isAbsoluteDirectoryText(request.cwd)) throw new TypeError("cwd must be an absolute directory path");
12822
12857
  if (request.confirmed !== true) throw new TypeError("starting a terminal requires the user's confirmation");
12823
12858
  return {
@@ -12867,7 +12902,11 @@ function terminalTargets(healthTools = [], platform = "win32") {
12867
12902
  const installed = new Set((Array.isArray(healthTools) ? healthTools : []).filter((item) => item?.installed).map((item) => item.id));
12868
12903
  return [
12869
12904
  { id: TERMINAL_SHELL_ID, label: platform === "win32" ? "PowerShell\uFF08\u7CFB\u7EDF\u7EC8\u7AEF\uFF09" : "Shell\uFF08\u7CFB\u7EDF\u7EC8\u7AEF\uFF09", canLogin: false },
12870
- ...TERMINAL_TOOL_IDS.filter((id2) => installed.has(id2)).map((id2) => ({ id: id2, label: terminalTargetLabel(id2), canLogin: id2 !== "gemini" }))
12905
+ ...TERMINAL_TOOL_IDS.filter((id2) => installed.has(id2)).map((id2) => ({
12906
+ id: id2,
12907
+ label: id2 === "zcode" ? `${terminalTargetLabel(id2)}\uFF08\u684C\u9762\u7248\u5185\u7F6E TUI\uFF09` : terminalTargetLabel(id2),
12908
+ canLogin: terminalCanLogin(id2)
12909
+ }))
12871
12910
  ];
12872
12911
  }
12873
12912
  function startProblem({ target, cwd, targets }) {
@@ -12927,6 +12966,26 @@ async function loadTerminalInfo(api) {
12927
12966
  if (!info || typeof info !== "object" || !["pty", "pipe"].includes(info.backend)) throw new Error("\u7EC8\u7AEF\u72B6\u6001\u683C\u5F0F\u65E0\u6548\u3002");
12928
12967
  return info;
12929
12968
  }
12969
+ var TerminalInfoRefresher = class {
12970
+ constructor() {
12971
+ this.generation = 0;
12972
+ }
12973
+ invalidate() {
12974
+ this.generation += 1;
12975
+ }
12976
+ async load(api) {
12977
+ const generation = ++this.generation;
12978
+ try {
12979
+ const value = await loadTerminalInfo(api);
12980
+ return generation === this.generation ? { status: "ready", value, error: "" } : null;
12981
+ } catch (error) {
12982
+ return generation === this.generation ? { status: "error", value: null, error: remoteErrorText(text3(error?.message), "\u65E0\u6CD5\u8BFB\u53D6\u7EC8\u7AEF\u72B6\u6001\u3002") } : null;
12983
+ }
12984
+ }
12985
+ };
12986
+ function terminalInfoTools(info, healthTools = []) {
12987
+ return Array.isArray(info?.tools) ? info.tools : Array.isArray(healthTools) ? healthTools : [];
12988
+ }
12930
12989
  async function startTerminal(api, request) {
12931
12990
  const session = unwrap(await api.terminalStart({ ...request, confirmed: true }), "\u7EC8\u7AEF\u542F\u52A8\u5931\u8D25\u3002");
12932
12991
  if (!isTerminalSessionId(session?.sessionId)) throw new Error("\u7EC8\u7AEF\u542F\u52A8\u7ED3\u679C\u7F3A\u5C11\u6709\u6548\u7684\u4F1A\u8BDD ID\u3002");
@@ -13120,7 +13179,7 @@ function TerminalPane({ api, session, active, onEnded }) {
13120
13179
  }, [session.sessionId]);
13121
13180
  return /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-term-pane", style: { display: active ? "block" : "none" } }, session.limitation && /* @__PURE__ */ import_react4.default.createElement("p", { className: "mr-caption mr-term-warning" }, session.limitation, session.ptyError ? `\uFF08${session.ptyError}\uFF09` : ""), error && /* @__PURE__ */ import_react4.default.createElement("p", { className: "mr-caption mr-term-warning", role: "alert" }, error), /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-term-screen", ref: host }));
13122
13181
  }
13123
- function CliTerminalCard({ api, health }) {
13182
+ function CliTerminalCard({ api, health, settingsSignature = "" }) {
13124
13183
  const [info, setInfo] = import_react4.default.useState({ status: "loading", value: null, error: "" });
13125
13184
  const [target, setTarget] = import_react4.default.useState("shell");
13126
13185
  const [mode, setMode] = import_react4.default.useState("interactive");
@@ -13130,29 +13189,38 @@ function CliTerminalCard({ api, health }) {
13130
13189
  const [startError, setStartError] = import_react4.default.useState("");
13131
13190
  const [sessions, setSessions] = import_react4.default.useState([]);
13132
13191
  const [activeId, setActiveId] = import_react4.default.useState("");
13192
+ const [tools, setTools] = import_react4.default.useState(health?.tools ?? []);
13133
13193
  const mounted = import_react4.default.useRef(true);
13194
+ const refresher = import_react4.default.useRef(null);
13195
+ if (!refresher.current) refresher.current = new TerminalInfoRefresher();
13196
+ const healthTools = import_react4.default.useRef(health?.tools);
13197
+ healthTools.current = health?.tools;
13198
+ const settingsScope = import_react4.default.useRef({ tools: health?.tools, signature: settingsSignature });
13134
13199
  const loadInfo = import_react4.default.useCallback(async () => {
13135
- try {
13136
- const value = await loadTerminalInfo(api);
13137
- if (!mounted.current) return;
13138
- setInfo({ status: "ready", value, error: "" });
13139
- } catch (error) {
13140
- if (mounted.current) setInfo({ status: "error", value: null, error: errorText(error, "\u65E0\u6CD5\u8BFB\u53D6\u7EC8\u7AEF\u72B6\u6001\u3002") });
13141
- }
13200
+ const result = await refresher.current.load(api);
13201
+ if (!mounted.current || !result) return;
13202
+ setInfo(result);
13203
+ if (result.status === "ready") setTools(terminalInfoTools(result.value, healthTools.current));
13142
13204
  }, [api]);
13143
13205
  import_react4.default.useEffect(() => {
13144
13206
  mounted.current = true;
13145
13207
  void loadInfo();
13146
13208
  return () => {
13147
13209
  mounted.current = false;
13210
+ refresher.current.invalidate();
13148
13211
  };
13149
13212
  }, [loadInfo]);
13213
+ import_react4.default.useEffect(() => {
13214
+ if (settingsScope.current.tools === health?.tools && settingsScope.current.signature === settingsSignature) return;
13215
+ settingsScope.current = { tools: health?.tools, signature: settingsSignature };
13216
+ void loadInfo();
13217
+ }, [health?.tools, settingsSignature, loadInfo]);
13150
13218
  const platform = info.value?.platform ?? clientPlatform();
13151
- const targets = terminalTargets(health?.tools ?? [], platform);
13219
+ const targets = terminalTargets(tools, platform);
13152
13220
  const selected = targets.find((item) => item.id === target) ?? targets[0];
13153
13221
  import_react4.default.useEffect(() => {
13154
13222
  if (!targets.some((item) => item.id === target)) setTarget("shell");
13155
- }, [targets.length]);
13223
+ }, [tools, target]);
13156
13224
  import_react4.default.useEffect(() => {
13157
13225
  if (!selected?.canLogin && mode === "login") setMode("interactive");
13158
13226
  }, [selected?.id]);
@@ -13196,7 +13264,7 @@ function CliTerminalCard({ api, health }) {
13196
13264
  };
13197
13265
  const active = sessions.find((item) => item.sessionId === activeId);
13198
13266
  const history = info.value?.history ?? [];
13199
- return /* @__PURE__ */ import_react4.default.createElement("section", { className: "mr-card", "aria-label": "\u5B98\u65B9\u5DE5\u5177\u7EC8\u7AEF" }, /* @__PURE__ */ import_react4.default.createElement("style", null, xterm_default), /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-card-head" }, /* @__PURE__ */ import_react4.default.createElement("div", null, /* @__PURE__ */ import_react4.default.createElement("h2", { className: "mr-card-title" }, "\u5B98\u65B9\u5DE5\u5177\u7EC8\u7AEF"), /* @__PURE__ */ import_react4.default.createElement("p", { className: "mr-card-copy" }, "\u5728\u5DE5\u4F5C\u53F0\u91CC\u4EA4\u4E92\u5F0F\u8FD0\u884C PowerShell \u6216\u5DF2\u5B89\u88C5\u7684\u5B98\u65B9 CLI\uFF08\u591A\u8F6E\u5BF9\u8BDD\u3001\u5B9E\u65F6\u8F93\u51FA\uFF0C\u53EF\u7528\u4E8E ", /* @__PURE__ */ import_react4.default.createElement("code", null, "codex login"), " \u7B49\u767B\u5F55\uFF09\u3002\u7EC8\u7AEF\u4E0D\u7ECF\u8FC7 Harness \u6C99\u7BB1\uFF0C\u4F7F\u7528\u4F60\u81EA\u5DF1\u7684 CLI \u767B\u5F55\u548C\u73AF\u5883\u53D8\u91CF\u3002")), /* @__PURE__ */ import_react4.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", onClick: () => {
13267
+ return /* @__PURE__ */ import_react4.default.createElement("section", { className: "mr-card", "aria-label": "\u5B98\u65B9\u5DE5\u5177\u7EC8\u7AEF" }, /* @__PURE__ */ import_react4.default.createElement("style", null, xterm_default), /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-card-head" }, /* @__PURE__ */ import_react4.default.createElement("div", null, /* @__PURE__ */ import_react4.default.createElement("h2", { className: "mr-card-title" }, "\u5B98\u65B9\u5DE5\u5177\u7EC8\u7AEF"), /* @__PURE__ */ import_react4.default.createElement("p", { className: "mr-card-copy" }, "\u5728\u5DE5\u4F5C\u53F0\u91CC\u4EA4\u4E92\u5F0F\u8FD0\u884C PowerShell\u3001\u5DF2\u5B89\u88C5\u7684\u5B98\u65B9 CLI \u6216 ZCode \u684C\u9762\u7248\u5185\u7F6E TUI\uFF08\u591A\u8F6E\u5BF9\u8BDD\u3001\u5B9E\u65F6\u8F93\u51FA\uFF0C\u53EF\u7528\u4E8E ", /* @__PURE__ */ import_react4.default.createElement("code", null, "codex login"), " \u7B49\u767B\u5F55\uFF09\u3002\u542F\u52A8\u5165\u53E3\u4E0E\u5B98\u65B9\u5DE5\u5177\u7684\u5B89\u88C5\u65B9\u5F0F\u548C\u76EE\u5F55\u540C\u6B65\u3002\u7EC8\u7AEF\u4E0D\u7ECF\u8FC7 Harness \u6C99\u7BB1\uFF0C\u4F7F\u7528\u4F60\u81EA\u5DF1\u7684\u767B\u5F55\u548C\u73AF\u5883\u53D8\u91CF\u3002")), /* @__PURE__ */ import_react4.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", onClick: () => {
13200
13268
  void loadInfo();
13201
13269
  } }, "\u5237\u65B0")), /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-card-body" }, info.status === "error" && /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-error", role: "alert" }, info.error), info.value && /* @__PURE__ */ import_react4.default.createElement("p", { className: "mr-caption" }, info.value.backend === "pty" ? "\u7EC8\u7AEF\u7EC4\u4EF6\uFF1A\u4F2A\u7EC8\u7AEF\uFF08PTY\uFF09\uFF0C\u652F\u6301\u5168\u5C4F\u754C\u9762\u3001\u989C\u8272\u548C\u7A97\u53E3\u5927\u5C0F\u540C\u6B65\u3002" : `\u7EC8\u7AEF\u7EC4\u4EF6\uFF1A\u7BA1\u9053\u6A21\u5F0F\u3002${info.value.limitation ?? ""}${info.value.ptyError ? `\uFF08PTY \u52A0\u8F7D\u5931\u8D25\uFF1A${info.value.ptyError}\uFF09` : ""}`), /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-controls" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-control-group" }, /* @__PURE__ */ import_react4.default.createElement("label", { className: "mr-control-label", htmlFor: "mr-term-target" }, "\u542F\u52A8"), /* @__PURE__ */ import_react4.default.createElement("select", { className: "mr-input", id: "mr-term-target", value: selected?.id ?? "shell", onChange: (event) => {
13202
13270
  setTarget(event.target.value);
@@ -13220,7 +13288,7 @@ function CliTerminalCard({ api, health }) {
13220
13288
  setConfirming(false);
13221
13289
  }
13222
13290
  }
13223
- ))), targets.length === 1 && /* @__PURE__ */ import_react4.default.createElement("p", { className: "mr-caption" }, "\u4F53\u68C0\u5C1A\u672A\u53D1\u73B0\u5DF2\u5B89\u88C5\u7684\u5B98\u65B9 CLI\uFF1B\u5B89\u88C5\u540E\u70B9\u201C\u5B98\u65B9\u5DE5\u5177 \xB7 \u4F53\u68C0\u201D\u7684\u201C\u91CD\u65B0\u4F53\u68C0\u201D\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5BF9\u5E94\u9009\u9879\u3002"), !confirming && /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-actions" }, /* @__PURE__ */ import_react4.default.createElement("button", { className: "mr-button", type: "button", disabled: Boolean(problem) || live >= maxSessions || info.status !== "ready", onClick: () => {
13291
+ ))), targets.length === 1 && /* @__PURE__ */ import_react4.default.createElement("p", { className: "mr-caption" }, "\u5C1A\u672A\u53D1\u73B0\u5DF2\u5B89\u88C5\u7684\u5B98\u65B9 CLI\uFF1B\u5B89\u88C5\u6216\u914D\u7F6E\u76EE\u5F55\u540E\u70B9\u672C\u5361\u7247\u201C\u5237\u65B0\u201D\u6216\u201C\u5B98\u65B9\u5DE5\u5177 \xB7 \u4F53\u68C0\u201D\u7684\u201C\u91CD\u65B0\u4F53\u68C0\u201D\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u5BF9\u5E94\u9009\u9879\u3002"), !confirming && /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-actions" }, /* @__PURE__ */ import_react4.default.createElement("button", { className: "mr-button", type: "button", disabled: Boolean(problem) || live >= maxSessions || info.status !== "ready", onClick: () => {
13224
13292
  setStartError("");
13225
13293
  setConfirming(true);
13226
13294
  } }, "\u5F00\u59CB"), /* @__PURE__ */ import_react4.default.createElement("span", { className: "mr-caption" }, problem || (live >= maxSessions ? `\u6700\u591A\u540C\u65F6\u8FD0\u884C ${maxSessions} \u4E2A\u4F1A\u8BDD\u3002` : `\u5C06\u8FD0\u884C\uFF1A${details.command}`))), confirming && /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-term-confirm", role: "dialog", "aria-label": "\u786E\u8BA4\u542F\u52A8\u7EC8\u7AEF" }, /* @__PURE__ */ import_react4.default.createElement("strong", null, details.title), /* @__PURE__ */ import_react4.default.createElement("p", { className: "mr-caption" }, "\u547D\u4EE4\uFF1A", /* @__PURE__ */ import_react4.default.createElement("code", null, details.command), /* @__PURE__ */ import_react4.default.createElement("br", null), "\u5DE5\u4F5C\u76EE\u5F55\uFF1A", /* @__PURE__ */ import_react4.default.createElement("code", null, details.cwd)), /* @__PURE__ */ import_react4.default.createElement("ul", null, details.points.map((point) => /* @__PURE__ */ import_react4.default.createElement("li", { key: point }, point))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "mr-actions" }, /* @__PURE__ */ import_react4.default.createElement("button", { className: "mr-button", type: "button", disabled: starting, onClick: () => {
@@ -13586,7 +13654,14 @@ var INSTALL_SETTING_FIELDS = [
13586
13654
  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"
13587
13655
  }
13588
13656
  ];
13589
- function InstallSettings({ settingsScope, install, onPendingChange, onMutationStart }) {
13657
+ var ZCODE_SETTING_FIELDS = [{
13658
+ key: "zcodeInstallDir",
13659
+ label: "ZCode \u684C\u9762\u5B89\u88C5\u76EE\u5F55",
13660
+ fallback: "",
13661
+ placeholder: "\u4F8B\u5982 D:/Program Files/ZCode\uFF0C\u7559\u7A7A\u81EA\u52A8\u68C0\u6D4B",
13662
+ hint: "\u5DF2\u5B89\u88C5\u684C\u9762\u7248\u5373\u53EF\u4F7F\u7528\u5185\u7F6E TUI\u3002\u4FDD\u5B58\u76EE\u5F55\u540E\u91CD\u65B0\u6838\u9A8C\u684C\u9762\u7A0B\u5E8F\u548C\u5185\u7F6E CLI\uFF1B\u65E0\u9700\u72EC\u7ACB CLI \u5B89\u88C5\u5305\u3002"
13663
+ }];
13664
+ function InstallSettings({ settingsScope, install, onPendingChange, onMutationStart, zcode = false }) {
13590
13665
  const [snapshot, setSnapshot] = import_react5.default.useState(() => settingsScope?.getSnapshot?.() ?? { value: {}, writable: false });
13591
13666
  const [drafts, setDrafts] = import_react5.default.useState({});
13592
13667
  const [saving, setSaving] = import_react5.default.useState(false);
@@ -13608,7 +13683,8 @@ function InstallSettings({ settingsScope, install, onPendingChange, onMutationSt
13608
13683
  if (!settingsScope) return null;
13609
13684
  const writable = snapshot.writable === true && !saving;
13610
13685
  const shown = (field2) => drafts[field2.key] ?? String(snapshot.value?.[field2.key] ?? field2.fallback);
13611
- const errors = Object.fromEntries(INSTALL_SETTING_FIELDS.map((field2) => [field2.key, validateToolInstallSetting(field2.key, shown(field2))]));
13686
+ const fields = zcode ? ZCODE_SETTING_FIELDS : INSTALL_SETTING_FIELDS;
13687
+ const errors = Object.fromEntries(fields.map((field2) => [field2.key, validateToolInstallSetting(field2.key, shown(field2))]));
13612
13688
  const invalid = Object.values(errors).some(Boolean);
13613
13689
  const edit = (key, value) => {
13614
13690
  const next = retainToolInstallDrafts({ ...drafts, [key]: value }, snapshot.value);
@@ -13640,7 +13716,7 @@ function InstallSettings({ settingsScope, install, onPendingChange, onMutationSt
13640
13716
  setSaving(false);
13641
13717
  }
13642
13718
  };
13643
- 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(
13719
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-install-settings", "aria-label": zcode ? "ZCode TUI \u72EC\u7ACB\u914D\u7F6E" : "\u7EDF\u4E00\u5B89\u88C5\u8BBE\u7F6E" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-install-settings-head" }, /* @__PURE__ */ import_react5.default.createElement("strong", null, zcode ? "ZCode TUI \u72EC\u7ACB\u914D\u7F6E" : "\u7EDF\u4E00\u5B89\u88C5\u8BBE\u7F6E"), /* @__PURE__ */ import_react5.default.createElement("span", { className: "mr-caption" }, zcode ? "\u5185\u7F6E CLI \u968F\u684C\u9762\u7248\u66F4\u65B0\u3002\u4FDD\u5B58\u540E\u5728\u5B98\u65B9\u5DE5\u5177\u7EC8\u7AEF\u9009\u62E9 ZCode TUI\uFF0C\u81EA\u884C\u914D\u7F6E\u767B\u5F55\u3001\u6A21\u578B\u4E0E\u63D2\u4EF6\u3002" : "\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" }, 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(
13644
13720
  "input",
13645
13721
  {
13646
13722
  className: "mr-input",
@@ -13650,7 +13726,7 @@ function InstallSettings({ settingsScope, install, onPendingChange, onMutationSt
13650
13726
  "aria-invalid": Boolean(errors[field2.key]),
13651
13727
  onChange: (event) => edit(field2.key, event.target.value)
13652
13728
  }
13653
- ), 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(
13729
+ ), 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))), !zcode && /* @__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(
13654
13730
  "input",
13655
13731
  {
13656
13732
  type: "checkbox",
@@ -13662,11 +13738,11 @@ function InstallSettings({ settingsScope, install, onPendingChange, onMutationSt
13662
13738
  }
13663
13739
  ), /* @__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: () => {
13664
13740
  void apply2();
13665
- } }, 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: () => {
13741
+ } }, saving ? "\u4FDD\u5B58\u4E2D\u2026" : zcode ? "\u4FDD\u5B58\u5E76\u5237\u65B0 TUI \u5165\u53E3" : "\u4FDD\u5B58\u5B89\u88C5\u8BBE\u7F6E"), dirty && /* @__PURE__ */ import_react5.default.createElement("button", { className: "mr-button mr-button-secondary", type: "button", disabled: saving, onClick: () => {
13666
13742
  setDrafts({});
13667
13743
  setNotice("");
13668
13744
  onPendingChange?.(false);
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}` : ""));
13745
+ } }, "\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 }, zcode ? `\u5F53\u524D ZCode \u76EE\u5F55\uFF1A${install?.zcodeInstallDir || "\uFF08\u81EA\u52A8\u68C0\u6D4B\uFF09"}` : `\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}` : ""));
13670
13746
  }
13671
13747
  function OfficialToolsCard({ listOfficialTools, loadOfficialInstallSettings, installOfficialTool, uninstallOfficialTool, repairOfficialTool, cancelOfficialToolInstall, officialToolInstallStatus, onProbes, health, onRefreshHealth, settingsScope }) {
13672
13748
  const healthById = Object.fromEntries((health?.tools ?? []).map((item) => [item.id, item]));
@@ -13690,9 +13766,18 @@ function OfficialToolsCard({ listOfficialTools, loadOfficialInstallSettings, ins
13690
13766
  const submitting = import_react5.default.useRef(/* @__PURE__ */ new Set());
13691
13767
  const polling = import_react5.default.useRef(/* @__PURE__ */ new Set());
13692
13768
  const jobsRef = import_react5.default.useRef({});
13769
+ const settingsPendingBySection = import_react5.default.useRef({ shared: false, zcode: false });
13693
13770
  const handleSettingsPending = import_react5.default.useCallback((value) => {
13694
- settingsPendingRef.current = value;
13695
- setSettingsPending(value);
13771
+ settingsPendingBySection.current.shared = value;
13772
+ const pending = Object.values(settingsPendingBySection.current).some(Boolean);
13773
+ settingsPendingRef.current = pending;
13774
+ setSettingsPending(pending);
13775
+ }, []);
13776
+ const handleZCodeSettingsPending = import_react5.default.useCallback((value) => {
13777
+ settingsPendingBySection.current.zcode = value;
13778
+ const pending = Object.values(settingsPendingBySection.current).some(Boolean);
13779
+ settingsPendingRef.current = pending;
13780
+ setSettingsPending(pending);
13696
13781
  }, []);
13697
13782
  const rememberJobs = (updater) => {
13698
13783
  setJobs((previous) => {
@@ -13813,6 +13898,8 @@ function OfficialToolsCard({ listOfficialTools, loadOfficialInstallSettings, ins
13813
13898
  }, []);
13814
13899
  const refreshInstallSettingsRef = import_react5.default.useRef(refreshInstallSettings);
13815
13900
  refreshInstallSettingsRef.current = refreshInstallSettings;
13901
+ const refreshProbesRef = import_react5.default.useRef(refresh);
13902
+ refreshProbesRef.current = refresh;
13816
13903
  const beginSettingsMutation = import_react5.default.useCallback((operations) => {
13817
13904
  const generation = ++mutationGeneration.current;
13818
13905
  mutationPendingRef.current = { generation, operations };
@@ -13829,12 +13916,15 @@ function OfficialToolsCard({ listOfficialTools, loadOfficialInstallSettings, ins
13829
13916
  if (typeof settingsScope?.subscribe !== "function") return void 0;
13830
13917
  const signature2 = (snapshot) => toolInstallSettingsSignature(snapshot?.value);
13831
13918
  let previous = signature2(settingsScope.getSnapshot());
13919
+ let previousZCode = settingsScope.getSnapshot()?.value?.zcodeInstallDir ?? "";
13832
13920
  return settingsScope.subscribe(() => {
13833
13921
  const snapshot = settingsScope.getSnapshot();
13834
13922
  setSettingsSnapshot(snapshot);
13835
13923
  const next = signature2(snapshot);
13836
13924
  if (next === previous) return;
13837
13925
  previous = next;
13926
+ const zcodeChanged = previousZCode !== (snapshot?.value?.zcodeInstallDir ?? "");
13927
+ previousZCode = snapshot?.value?.zcodeInstallDir ?? "";
13838
13928
  request.current += 1;
13839
13929
  setProbeSettingsStale(true);
13840
13930
  setProbeState((previous2) => ({ ...previous2, status: "ready", probes: [], readiness: [], error: "" }));
@@ -13842,6 +13932,7 @@ function OfficialToolsCard({ listOfficialTools, loadOfficialInstallSettings, ins
13842
13932
  previewPendingRef.current = true;
13843
13933
  setPreviewPending(true);
13844
13934
  void refreshInstallSettingsRef.current();
13935
+ if (zcodeChanged) void refreshProbesRef.current();
13845
13936
  });
13846
13937
  }, [settingsScope]);
13847
13938
  import_react5.default.useEffect(() => {
@@ -14016,7 +14107,16 @@ function OfficialToolsCard({ listOfficialTools, loadOfficialInstallSettings, ins
14016
14107
  const verified = Boolean(probe) && job?.status === "succeeded" && (job.operation === "uninstall" ? job.postInstallProbe?.installed === false : job.postInstallProbe?.installed === true);
14017
14108
  const operation = job?.operation === "uninstall" ? "\u5378\u8F7D" : job?.operation === "repair" ? "\u4FEE\u590D" : "\u5B89\u88C5";
14018
14109
  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";
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(
14110
+ 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 ${tool.id === "zcode" ? "\u684C\u9762\u7248 " : ""}${probe.version}` : "", healthById[tool.id]?.latestVersion ? ` \xB7 \u6700\u65B0 ${healthById[tool.id].latestVersion}` : ""), /* @__PURE__ */ import_react5.default.createElement(ToolLoginLine, { entry: healthById[tool.id] }), tool.id === "zcode" && /* @__PURE__ */ import_react5.default.createElement(
14111
+ InstallSettings,
14112
+ {
14113
+ zcode: true,
14114
+ settingsScope,
14115
+ install: probeState.install,
14116
+ onPendingChange: handleZCodeSettingsPending,
14117
+ onMutationStart: beginSettingsMutation
14118
+ }
14119
+ ), 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 === "signed-windows-installer" ? "\u5B98\u65B9\u684C\u9762\u5B89\u88C5\u5668" : tool.manager), " \xB7 \u5B9E\u9645\u5B89\u88C5\u76EE\u5F55\uFF1A", probe?.installDir || 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(
14020
14120
  "select",
14021
14121
  {
14022
14122
  className: "mr-mini-select",
@@ -14399,7 +14499,14 @@ function RouterMainPage({ loadCatalog, settingsScope, listOfficialTools, loadOff
14399
14499
  void workbench.refreshHealth(true);
14400
14500
  }
14401
14501
  }
14402
- ), terminalApi && /* @__PURE__ */ import_react5.default.createElement(CliTerminalCard, { api: terminalApi, health: workbench.health.report })), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-view mr-stack", role: "tabpanel", id: "mr-panel-controls", "aria-labelledby": "mr-tab-controls", hidden: view !== "controls" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-view-heading" }, /* @__PURE__ */ import_react5.default.createElement("h2", null, "\u9884\u7B97\u4E0E\u5B89\u5168"), /* @__PURE__ */ import_react5.default.createElement("p", null, "\u8BBE\u7F6E\u6210\u672C\u4E0E\u8D28\u91CF\u7B56\u7565\uFF0C\u6838\u5BF9\u8BA2\u9605\u8BA1\u8D39\u548C\u6267\u884C\u8FB9\u754C\u3002")), /* @__PURE__ */ import_react5.default.createElement(
14502
+ ), terminalApi && /* @__PURE__ */ import_react5.default.createElement(
14503
+ CliTerminalCard,
14504
+ {
14505
+ api: terminalApi,
14506
+ health: workbench.health.report,
14507
+ settingsSignature: toolInstallSettingsSignature(settingsScope?.getSnapshot?.()?.value)
14508
+ }
14509
+ )), /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-view mr-stack", role: "tabpanel", id: "mr-panel-controls", "aria-labelledby": "mr-tab-controls", hidden: view !== "controls" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "mr-view-heading" }, /* @__PURE__ */ import_react5.default.createElement("h2", null, "\u9884\u7B97\u4E0E\u5B89\u5168"), /* @__PURE__ */ import_react5.default.createElement("p", null, "\u8BBE\u7F6E\u6210\u672C\u4E0E\u8D28\u91CF\u7B56\u7565\uFF0C\u6838\u5BF9\u8BA2\u9605\u8BA1\u8D39\u548C\u6267\u884C\u8FB9\u754C\u3002")), /* @__PURE__ */ import_react5.default.createElement(
14403
14510
  CostControlCard,
14404
14511
  {
14405
14512
  ledger: workbench.ledger.value,
@@ -14699,6 +14806,17 @@ function installDirField() {
14699
14806
  }
14700
14807
  };
14701
14808
  }
14809
+ function zcodeInstallDirField() {
14810
+ const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("zcodeInstallDir");
14811
+ return { ...field2, parse: (text7) => {
14812
+ try {
14813
+ parseInstallPreferences({ zcodeInstallDir: text7 }, { tools: OFFICIAL_TOOLS });
14814
+ } catch {
14815
+ return void 0;
14816
+ }
14817
+ return field2.parse(text7);
14818
+ } };
14819
+ }
14702
14820
  function npmRegistryField() {
14703
14821
  const field2 = (0, import_dsh_client_ui_primitives.settingsTextField)("toolNpmRegistry");
14704
14822
  return {
@@ -14760,6 +14878,7 @@ var RouterSettingsCardController = class {
14760
14878
  modelProfilesField(),
14761
14879
  quotaPatternsField(),
14762
14880
  installDirField(),
14881
+ zcodeInstallDirField(),
14763
14882
  npmRegistryField(),
14764
14883
  installMethodsField(),
14765
14884
  scriptUrlsField()
@@ -14775,6 +14894,7 @@ var RouterSettingsCardController = class {
14775
14894
  modelProfilesJson: this.form.field("modelProfilesJson"),
14776
14895
  quotaPatternsJson: this.form.field("quotaPatternsJson"),
14777
14896
  toolInstallDir: this.form.field("toolInstallDir"),
14897
+ zcodeInstallDir: this.form.field("zcodeInstallDir"),
14778
14898
  toolNpmRegistry: this.form.field("toolNpmRegistry"),
14779
14899
  toolInstallMethodsJson: this.form.field("toolInstallMethodsJson"),
14780
14900
  toolScriptUrlsJson: this.form.field("toolScriptUrlsJson")
@@ -14937,6 +15057,24 @@ function RouterSettingsCard(props) {
14937
15057
  props.resetField("toolNpmRegistry");
14938
15058
  }
14939
15059
  }
15060
+ ), /* @__PURE__ */ import_react6.default.createElement(
15061
+ import_dsh_client_ui_primitives.SettingsValueField,
15062
+ {
15063
+ id: "model-router-zcode-install-dir",
15064
+ label: "ZCode \u684C\u9762\u76EE\u5F55\u4E0E TUI",
15065
+ hint: "Windows \u5DF2\u5B89\u88C5 ZCode \u684C\u9762\u7248\u5373\u53EF\u4F7F\u7528\u5185\u7F6E TUI\u3002\u586B\u5199\u5176\u72EC\u7ACB\u76EE\u5F55\uFF08\u4F8B\u5982 D:/Program Files/ZCode\uFF09\uFF0C\u7559\u7A7A\u81EA\u52A8\u68C0\u6D4B\u3002\u4FDD\u5B58\u540E\u4F53\u68C0\u4E0E\u5B98\u65B9\u5DE5\u5177\u7EC8\u7AEF\u5171\u7528\u6B64\u5165\u53E3\uFF1B\u5185\u7F6E CLI \u968F\u684C\u9762\u7248\u66F4\u65B0\uFF0C\u767B\u5F55\u4E0E\u6A21\u578B\u5728 TUI \u4E2D\u914D\u7F6E\u3002",
15066
+ disabled,
15067
+ ...state.zcodeInstallDir,
15068
+ overriddenLabel: FIELD_COPY.overridden,
15069
+ resetLabel: FIELD_COPY.reset,
15070
+ invalidLabel: "\u8BF7\u8F93\u5165 Windows \u672C\u5730\u7EDD\u5BF9\u684C\u9762\u76EE\u5F55\uFF0C\u6216\u7559\u7A7A\u81EA\u52A8\u68C0\u6D4B\u3002",
15071
+ onEdit: (value) => {
15072
+ props.edit("zcodeInstallDir", value);
15073
+ },
15074
+ onReset: () => {
15075
+ props.resetField("zcodeInstallDir");
15076
+ }
15077
+ }
14940
15078
  ), /* @__PURE__ */ import_react6.default.createElement(
14941
15079
  import_dsh_client_ui_primitives.SettingsValueField,
14942
15080
  {
@@ -95,6 +95,7 @@ export const Config = z.object({
95
95
  toolInstallMethodsJson: z.string().max(8_000).default('{}').volatile(),
96
96
  toolScriptUrlsJson: z.string().max(8_000).default('{}').volatile(),
97
97
  toolAllowScriptInstall: z.boolean().default(true).volatile(),
98
+ zcodeInstallDir: z.string().max(4_096).default('').volatile(),
98
99
  })
99
100
 
100
101
  const JSON_OUTPUT = {
@@ -1387,6 +1388,8 @@ export async function startWorkbenchRun(ctx, config, request = {}, options = {})
1387
1388
  export function createRouterTerminals(options = {}) {
1388
1389
  return createTerminalManager({
1389
1390
  prepare: () => ensureNpmPrefixOnPath(),
1391
+ preferences: () => installPreferences(),
1392
+ launchOptions: () => currentInstallOptions(),
1390
1393
  onSessionEnd: record => { void routerStateStore().appendTerminalSession(record).catch(() => {}) },
1391
1394
  ...options,
1392
1395
  })
@@ -1397,11 +1400,14 @@ async function terminalHistory(limit = 20) {
1397
1400
  return (saved.terminalSessions ?? []).slice(-limit).reverse()
1398
1401
  }
1399
1402
 
1400
- function terminalServices(terminals) {
1403
+ function terminalServices(terminals, assertPreferences = () => {}) {
1401
1404
  if (!terminals) return {}
1402
1405
  return {
1403
- terminalInfo: async () => ({ ...terminals.info(), history: await terminalHistory() }),
1404
- terminalStart: async request => terminals.start(parseTerminalStart(request)),
1406
+ terminalInfo: async () => {
1407
+ const tools = await probeAllTools({ fresh: true })
1408
+ return { ...terminals.info(), tools, toolsCheckedAt: Date.now(), history: await terminalHistory() }
1409
+ },
1410
+ terminalStart: async request => { assertPreferences(); return terminals.start(parseTerminalStart(request)) },
1405
1411
  terminalRead: async request => terminals.read(parseTerminalRead(request)),
1406
1412
  terminalWrite: async request => terminals.write(parseTerminalWrite(request)),
1407
1413
  terminalResize: async request => terminals.resize(parseTerminalResize(request)),
@@ -1409,9 +1415,9 @@ function terminalServices(terminals) {
1409
1415
  }
1410
1416
  }
1411
1417
 
1412
- export function routerRemoteServices(ctx, config, { terminals = null } = {}) {
1418
+ export function routerRemoteServices(ctx, config, { terminals = null, assertPreferences } = {}) {
1413
1419
  return {
1414
- ...terminalServices(terminals),
1420
+ ...terminalServices(terminals, assertPreferences),
1415
1421
  health: async fresh => {
1416
1422
  const report = await toolHealthReport({ fresh: fresh === true })
1417
1423
  const saved = await savedState()
@@ -1444,7 +1450,7 @@ export function apply(ctx, config = {}) {
1444
1450
  installPreferenceError = null
1445
1451
  } catch (error) { installPreferenceError = errorText(error) }
1446
1452
  }
1447
- const installFields = new Set(['toolInstallDir', 'toolNpmRegistry', 'toolInstallMethodsJson', 'toolScriptUrlsJson', 'toolAllowScriptInstall'])
1453
+ const installFields = new Set(['toolInstallDir', 'toolNpmRegistry', 'toolInstallMethodsJson', 'toolScriptUrlsJson', 'toolAllowScriptInstall', 'zcodeInstallDir'])
1448
1454
  syncInstallPreferences()
1449
1455
  // Loader scopes this event to the fiber whose refs were committed.
1450
1456
  ctx.on('loader/volatile-update', paths => {
@@ -1464,9 +1470,10 @@ export function apply(ctx, config = {}) {
1464
1470
  return () => { process.off('exit', onExit); terminals.disposeAll('dispose') }
1465
1471
  }, 'model-router-galgame: interactive terminals')
1466
1472
  registerOfficialToolsRemote(ctx, {
1467
- ...routerRemoteServices(ctx, config, { terminals }),
1473
+ ...routerRemoteServices(ctx, config, { terminals, assertPreferences: assertInstallPreferences }),
1468
1474
  installSettingsError: () => installPreferenceError,
1469
1475
  installRequestedDirectory: () => valueOf(config, 'toolInstallDir', ''),
1476
+ zcodeRequestedDirectory: () => valueOf(config, 'zcodeInstallDir', ''),
1470
1477
  })
1471
1478
  }
1472
1479
  ctx.on('tools/pre-execute', async (exec, next) => {