@dsh-sup/dsh-core-linux-x64 0.1.6-BETA.13 → 0.1.6-BETA.15

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.
Files changed (23) hide show
  1. package/core.cjs +2461 -1416
  2. package/package.json +1 -1
  3. package/ui-react/assets/{InstancesPage-DM0dMQHh.js → InstancesPage-Ce6M33Ba.js} +1 -1
  4. package/ui-react/assets/{LanPage-B5oThF8O.js → LanPage-wpx7g9la.js} +2 -2
  5. package/ui-react/assets/OverviewPage-81_tl-DM.js +2 -0
  6. package/ui-react/assets/{PluginsPage-C1llxTDk.js → PluginsPage-BiGqLhAM.js} +1 -1
  7. package/ui-react/assets/RouterPage-BGuTrkVi.js +1 -0
  8. package/ui-react/assets/{SettingsPage-BZG-EvR3.js → SettingsPage-Dtzd41KX.js} +1 -1
  9. package/ui-react/assets/{TasksPage-haZMZbBh.js → TasksPage-B1eWMGNq.js} +1 -1
  10. package/ui-react/assets/openExternal-CCkI5Gcp.js +1 -0
  11. package/ui-react/assets/{refresh-cw-B-VAKpDt.js → refresh-cw-DL1X7yMA.js} +1 -1
  12. package/ui-react/assets/{rocket-CLAmwatt.js → rocket-USfrjyOj.js} +1 -1
  13. package/ui-react/assets/{shield-check-CMRwmPpk.js → shield-check-T6r2Ozdx.js} +1 -1
  14. package/ui-react/assets/{supervisor-BCZW2z-5.js → supervisor-B8Hi4QZf.js} +3 -3
  15. package/ui-react/assets/supervisor-C1zJELaq.css +2 -0
  16. package/ui-react/assets/{supervisor-BbSavtNN.js → supervisor-DsqHrafU.js} +1 -1
  17. package/ui-react/assets/{useSupervisorAction-q0YJmhui.js → useSupervisorAction-bKiRxMdz.js} +1 -1
  18. package/ui-react/assets/{widgets-BbRkZm-Z.js → widgets-CKp8QAOl.js} +1 -1
  19. package/ui-react/supervisor.html +3 -3
  20. package/ui-react/assets/OverviewPage-DH8PtnPI.js +0 -2
  21. package/ui-react/assets/RouterPage-CUKMV2Q9.js +0 -1
  22. package/ui-react/assets/openExternal-CzqErnSy.js +0 -1
  23. package/ui-react/assets/supervisor-YWIsBeZU.css +0 -2
package/core.cjs CHANGED
@@ -833,7 +833,11 @@ var require_config = __commonJS({
833
833
  // 不配置 = LAN 受 RFC1918 白名单约束,FRP 暴露仍强制 remoteToken。
834
834
  apiAccessKey: null,
835
835
  // 关闭窗口行为(系统级):'hide' = 隐藏至托盘、服务常驻(默认);'exit' = 退出管家并停全部服务链。
836
- closeAction: "hide"
836
+ closeAction: "hide",
837
+ // 外部打开的浏览器偏好(系统级,本机配置):存环境表单候选清单里的 id,null = 未选。
838
+ // 与「系统默认浏览器」分两个字段是刻意的:前者是用户在本产品里的选择,后者是操作系统的事实;
839
+ // 系统说不出默认时按候选次序分发并如实标注,用户可在这里定一次覆盖它(见 platform/os/environment.js)。
840
+ externalBrowser: null
837
841
  };
838
842
  function buildDefaults2(ext) {
839
843
  const pending = normalizeExtension(ext).defaults.slice();
@@ -947,7 +951,7 @@ var require_version = __commonJS({
947
951
  var fs2 = require("node:fs");
948
952
  var path2 = require("node:path");
949
953
  function guardVersion() {
950
- if (true) return String("0.1.6-BETA.13");
954
+ if (true) return String("0.1.6-BETA.15");
951
955
  try {
952
956
  return JSON.parse(fs2.readFileSync(path2.join(__dirname, "..", "..", "package.json"), "utf8")).version || "unknown";
953
957
  } catch {
@@ -1080,6 +1084,7 @@ var require_fs = __commonJS({
1080
1084
  "src/platform/util/fs.js"(exports2, module2) {
1081
1085
  "use strict";
1082
1086
  var fs2 = require("node:fs");
1087
+ var os2 = require("node:os");
1083
1088
  var path2 = require("node:path");
1084
1089
  function dirSizeBytes(root) {
1085
1090
  let total = 0;
@@ -1140,7 +1145,26 @@ var require_fs = __commonJS({
1140
1145
  throw e;
1141
1146
  }
1142
1147
  }
1143
- module2.exports = { dirSizeBytes, writeAtomic };
1148
+ function removeTreeDeferred(dir, ms) {
1149
+ if (!dir) return null;
1150
+ const t = setTimeout(() => {
1151
+ try {
1152
+ fs2.rmSync(dir, { recursive: true, force: true });
1153
+ } catch {
1154
+ }
1155
+ }, ms === void 0 ? 6e4 : ms);
1156
+ if (t.unref) t.unref();
1157
+ return t;
1158
+ }
1159
+ function allocTempDir(prefix) {
1160
+ const dir = fs2.mkdtempSync(path2.join(os2.tmpdir(), prefix || "dsh-"));
1161
+ try {
1162
+ fs2.chmodSync(dir, 448);
1163
+ } catch {
1164
+ }
1165
+ return dir;
1166
+ }
1167
+ module2.exports = { dirSizeBytes, writeAtomic, removeTreeDeferred, allocTempDir };
1144
1168
  }
1145
1169
  });
1146
1170
 
@@ -4831,1218 +4855,2020 @@ var require_capability_profile = __commonJS({
4831
4855
  }
4832
4856
  });
4833
4857
 
4834
- // src/platform/os/process.js
4835
- var require_process = __commonJS({
4836
- "src/platform/os/process.js"(exports2, module2) {
4858
+ // src/platform/util/redact.js
4859
+ var require_redact = __commonJS({
4860
+ "src/platform/util/redact.js"(exports2, module2) {
4837
4861
  "use strict";
4838
- var ex2 = require_exec();
4839
- var isWindows = process.platform === "win32";
4840
- function signalProcess(pid, sig) {
4841
- if (!Number.isInteger(pid) || pid <= 0) return;
4842
- if (isWindows) {
4843
- try {
4844
- process.kill(pid, sig);
4845
- } catch {
4846
- }
4847
- return;
4862
+ function maskProxyServer(server) {
4863
+ return server ? String(server).replace(/(\/\/)?([^\s/@:]+):([^\s/@]*)@/, "$1$2:***@") : null;
4864
+ }
4865
+ function maskProxySecrets(text) {
4866
+ return String(text == null ? "" : text).replace(/(\/\/)?([^\s/@:]+):([^\s/@]*)@/g, "$1$2:***@");
4867
+ }
4868
+ module2.exports = { maskProxyServer, maskProxySecrets };
4869
+ }
4870
+ });
4871
+
4872
+ // src/platform/os/registry.js
4873
+ var require_registry = __commonJS({
4874
+ "src/platform/os/registry.js"(exports2, module2) {
4875
+ "use strict";
4876
+ function regValueOf(out) {
4877
+ const m = String(out || "").match(/REG_(?:EXPAND_SZ|SZ)\s+(.*)/);
4878
+ if (!m) return null;
4879
+ return m[1].trim() || null;
4880
+ }
4881
+ function regDwordOf(out) {
4882
+ const m = String(out || "").match(/REG_DWORD\s+(0x[0-9a-fA-F]+|\d+)\b/);
4883
+ if (!m) return null;
4884
+ const n = Number.parseInt(m[1], m[1].startsWith("0x") ? 16 : 10);
4885
+ return Number.isNaN(n) ? null : n;
4886
+ }
4887
+ function expandEnvVars(value, env) {
4888
+ const e = env || process.env;
4889
+ return String(value).replace(/%([^%]+)%/g, (all, name) => e[name] === void 0 ? all : e[name]);
4890
+ }
4891
+ function safeRegKeyPart(s) {
4892
+ return typeof s === "string" && s.length > 0 && s.length <= 200 && /^[\x20-\x7e]+$/.test(s) && !/[;|&`$()<>^"'\r\n]/.test(s);
4893
+ }
4894
+ var REG_HIVE_ALIAS = { HKLM: "HKEY_LOCAL_MACHINE", HKCU: "HKEY_CURRENT_USER", HKCR: "HKEY_CLASSES_ROOT", HKU: "HKEY_USERS", HKCC: "HKEY_CURRENT_CONFIG" };
4895
+ function regKeyFull(key) {
4896
+ const s = String(key || "");
4897
+ const i = s.indexOf("\\");
4898
+ const root = i < 0 ? s : s.slice(0, i);
4899
+ return (REG_HIVE_ALIAS[root.toUpperCase()] || root) + (i < 0 ? "" : s.slice(i));
4900
+ }
4901
+ function regValue(runner, note, key, name) {
4902
+ const args = name ? ["query", key, "/v", name] : ["query", key, "/ve"];
4903
+ const out = runner("reg.exe", args);
4904
+ const v = regValueOf(out);
4905
+ note(name ? key + " /v " + name : key, v ? "ok" : "empty");
4906
+ return v;
4907
+ }
4908
+ function regSubkeys(runner, note, key) {
4909
+ const out = runner("reg.exe", ["query", key]);
4910
+ if (!out) {
4911
+ note(key, "unreadable");
4912
+ return [];
4848
4913
  }
4849
- try {
4850
- process.kill(-pid, sig);
4851
- } catch {
4852
- try {
4853
- process.kill(pid, sig);
4854
- } catch {
4855
- }
4914
+ const prefix = regKeyFull(key) + "\\";
4915
+ const names = [];
4916
+ for (const line of String(out).split(/\r?\n/)) {
4917
+ const l = line.trim();
4918
+ if (!/^HKEY_/i.test(l) || l.length <= prefix.length) continue;
4919
+ if (l.slice(0, prefix.length).toUpperCase() !== prefix.toUpperCase()) continue;
4920
+ const rest = l.slice(prefix.length);
4921
+ if (rest && !rest.includes("\\") && safeRegKeyPart(rest)) names.push(rest);
4856
4922
  }
4923
+ note(key, names.length ? names.length + " \u9879" : "\u65E0\u5B50\u952E");
4924
+ return names;
4857
4925
  }
4858
- function killTree(pid, sig, cb, opts) {
4859
- if (!Number.isInteger(pid) || pid <= 0) {
4860
- if (cb) cb(new Error("invalid pid"));
4861
- return;
4862
- }
4863
- if (isWindows) {
4864
- ex2.runAsync("taskkill", ["/PID", String(pid), "/T", "/F"], { timeoutMs: 1e4 }).then((r) => {
4865
- if (cb) cb(r.ok ? null : new Error(r.error || "taskkill failed"));
4866
- });
4867
- return;
4926
+ function regValueTargets(runner, note, key) {
4927
+ const out = runner("reg.exe", ["query", key]);
4928
+ if (!out) {
4929
+ note(key, "unreadable");
4930
+ return [];
4868
4931
  }
4869
- if (opts && opts.ownGroup === true) {
4870
- signalProcess(pid, sig || "SIGTERM");
4871
- } else {
4872
- try {
4873
- process.kill(pid, sig || "SIGTERM");
4874
- } catch {
4875
- }
4932
+ const paths = [];
4933
+ for (const line of String(out).split(/\r?\n/)) {
4934
+ const m = line.trim().match(/REG_SZ\s+(.*)/);
4935
+ if (!m) continue;
4936
+ const rel = m[1].trim();
4937
+ if (!safeRegKeyPart(rel)) continue;
4938
+ const root = key.slice(0, key.indexOf("\\"));
4939
+ paths.push(root + "\\" + rel);
4876
4940
  }
4877
- if (cb) process.nextTick(cb, null);
4941
+ note(key, paths.length ? paths.length + " \u4E2A\u80FD\u529B\u8DEF\u5F84" : "\u65E0\u503C");
4942
+ return paths;
4878
4943
  }
4879
- module2.exports = { signalProcess, killTree };
4944
+ module2.exports = {
4945
+ regValueOf,
4946
+ regDwordOf,
4947
+ expandEnvVars,
4948
+ safeRegKeyPart,
4949
+ REG_HIVE_ALIAS,
4950
+ regKeyFull,
4951
+ regValue,
4952
+ regSubkeys,
4953
+ regValueTargets
4954
+ };
4880
4955
  }
4881
4956
  });
4882
4957
 
4883
- // src/platform/os/file-protect.js
4884
- var require_file_protect = __commonJS({
4885
- "src/platform/os/file-protect.js"(exports2, module2) {
4958
+ // src/platform/os/browser-inventory.js
4959
+ var require_browser_inventory = __commonJS({
4960
+ "src/platform/os/browser-inventory.js"(exports2, module2) {
4886
4961
  "use strict";
4887
4962
  var fs2 = require("node:fs");
4888
- var os2 = require("node:os");
4889
4963
  var path2 = require("node:path");
4890
- var ex2 = require_exec();
4891
- var IS_WINDOWS2 = process.platform === "win32";
4892
- var _icacls = null;
4893
- function hasIcacls(platform) {
4894
- if ((platform || process.platform) !== "win32") return false;
4895
- if (_icacls !== null) return _icacls;
4896
- _icacls = ex2.runOut("icacls", ["/?"], { timeoutMs: 3e3 }) !== null;
4897
- return _icacls;
4964
+ var os2 = require("node:os");
4965
+ var exec = require_exec();
4966
+ var { isExecutableFile } = require_exec_path();
4967
+ var registry = require_registry();
4968
+ var { expandEnvVars, safeRegKeyPart, regValue, regSubkeys, regValueTargets } = registry;
4969
+ var PROBE_TIMEOUT_MS = 1500;
4970
+ function engineOf(bin) {
4971
+ const base = String(bin || "").toLowerCase().split(/[\\/]/).pop();
4972
+ if (/(chrome|chromium|msedge|edge|brave|vivaldi|opera|thorium)/.test(base)) return "chromium";
4973
+ if (/(firefox|librewolf|waterfox)/.test(base)) return "firefox";
4974
+ return "other";
4898
4975
  }
4899
- function currentUser() {
4900
- return process.env.USERNAME || process.env.USER || (() => {
4901
- try {
4902
- return os2.userInfo().username;
4903
- } catch {
4904
- return null;
4976
+ function tokenizeExec(line) {
4977
+ const toks = [];
4978
+ let cur = "";
4979
+ let q = null;
4980
+ let esc = false;
4981
+ let has = false;
4982
+ for (const ch of String(line)) {
4983
+ if (esc) {
4984
+ cur += ch;
4985
+ esc = false;
4986
+ continue;
4905
4987
  }
4906
- })();
4907
- }
4908
- function protectFile(file) {
4909
- if (!IS_WINDOWS2) {
4910
- try {
4911
- fs2.chmodSync(file, 384);
4912
- return { ok: true, mode: "posix-0600" };
4913
- } catch (e) {
4914
- return { ok: false, mode: "posix-0600", reason: e.message };
4988
+ if (ch === "\\") {
4989
+ esc = true;
4990
+ has = true;
4991
+ continue;
4915
4992
  }
4916
- }
4917
- if (!hasIcacls()) return { ok: false, mode: "none", reason: "icacls \u4E0D\u53EF\u7528" };
4918
- const user = currentUser();
4919
- if (!user) return { ok: false, mode: "icacls", reason: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u7528\u6237" };
4920
- const r = ex2.runDetail("icacls", [file, "/inheritance:r", "/grant:r", user + ":F"], { stdio: "ignore", timeoutMs: 5e3 });
4921
- return r.ok ? { ok: true, mode: "icacls-file" } : { ok: false, mode: "icacls-file", reason: r.error || "\u9000\u51FA\u7801 " + r.code };
4922
- }
4923
- function protectDir(dir) {
4924
- if (!IS_WINDOWS2) {
4925
- try {
4926
- fs2.chmodSync(dir, 448);
4927
- return { ok: true, mode: "posix-0700" };
4928
- } catch (e) {
4929
- return { ok: false, mode: "posix-0700", reason: e.message };
4993
+ if (q) {
4994
+ if (ch === q) q = null;
4995
+ else cur += ch;
4996
+ continue;
4930
4997
  }
4931
- }
4932
- if (!hasIcacls()) return { ok: false, mode: "none", reason: "icacls \u4E0D\u53EF\u7528" };
4933
- const user = currentUser();
4934
- if (!user) return { ok: false, mode: "icacls", reason: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u7528\u6237" };
4935
- const r = ex2.runDetail("icacls", [dir, "/inheritance:r", "/grant:r", user + ":(OI)(CI)F"], { stdio: "ignore", timeoutMs: 1e4 });
4936
- return r.ok ? { ok: true, mode: "icacls-dir" } : { ok: false, mode: "icacls-dir", reason: r.error || "\u9000\u51FA\u7801 " + r.code };
4937
- }
4938
- function ensurePrivateDir(dir) {
4939
- try {
4940
- fs2.mkdirSync(dir, { recursive: true });
4941
- } catch (e) {
4942
- return { ok: false, mode: "mkdir", reason: e.message };
4943
- }
4944
- return protectDir(dir);
4945
- }
4946
- function writePrivate(file, data) {
4947
- try {
4948
- const dir = path2.dirname(file);
4949
- try {
4950
- fs2.mkdirSync(dir, { recursive: true });
4951
- } catch {
4998
+ if (ch === "'" || ch === '"') {
4999
+ q = ch;
5000
+ has = true;
5001
+ continue;
4952
5002
  }
4953
- const tmp = file + ".tmp" + process.pid;
4954
- fs2.writeFileSync(tmp, data, { mode: 384 });
4955
- const p1 = protectFile(tmp);
4956
- if (p1 && p1.ok === false) {
4957
- try {
4958
- fs2.rmSync(tmp, { force: true });
4959
- } catch {
5003
+ if (/\s/.test(ch)) {
5004
+ if (has) {
5005
+ toks.push(cur);
5006
+ cur = "";
5007
+ has = false;
4960
5008
  }
4961
- return { ok: false, reason: "protect(tmp): " + (p1.reason || p1.mode), mode: p1.mode };
5009
+ continue;
4962
5010
  }
4963
- fs2.renameSync(tmp, file);
4964
- const p2 = protectFile(file);
4965
- if (p2 && p2.ok === false) return { ok: false, reason: "protect(file): " + (p2.reason || p2.mode), mode: p2.mode };
4966
- return { ok: true };
4967
- } catch (e) {
4968
- return { ok: false, reason: e.message };
5011
+ cur += ch;
5012
+ has = true;
4969
5013
  }
5014
+ if (has) toks.push(cur);
5015
+ return toks;
4970
5016
  }
4971
- module2.exports = { protectFile, protectDir, ensurePrivateDir, writePrivate, hasIcacls, currentUser, IS_WINDOWS: IS_WINDOWS2 };
4972
- }
4973
- });
4974
-
4975
- // src/platform/util/input.js
4976
- var require_input = __commonJS({
4977
- "src/platform/util/input.js"(exports2, module2) {
4978
- "use strict";
4979
- var PKG_NAME_RE = /^(@[a-zA-Z0-9._-]+\/)?[a-zA-Z0-9._-]+$/;
4980
- var ARGV_UNSAFE_RE = /[\s;|&<>`'"$(){}\\*?~#]/;
4981
- var WIN_ABS_PATH_RE = /^[A-Za-z]:\\[^;|&<>`'"$*?~#\s]*$/;
4982
- var UNIT_NAME_RE = /^[A-Za-z0-9:@._-]{1,128}$/;
4983
- var UNSAFE_LEDGER_KEYS = ["__proto__", "prototype", "constructor"];
4984
- var LEDGER_UNSAFE_RE = /[\s\u0000-\u001f\u007f]/;
4985
- function argvViolation(arg) {
4986
- const s = String(arg == null ? "" : arg);
4987
- if (!s) return "\u7A7A\u7684 argv \u9879";
4988
- if (ARGV_UNSAFE_RE.test(s) && !WIN_ABS_PATH_RE.test(s)) return "argv \u9879\u542B\u7981\u7528\u5B57\u7B26\uFF08\u7A7A\u767D/shell \u5143\u5B57\u7B26\uFF09: " + s.slice(0, 80);
4989
- return null;
4990
- }
4991
- var CONTROL_RE = /[\u0000-\u001f\u007f]/;
4992
- function isAbsoluteForm(p) {
4993
- const s = String(p == null ? "" : p);
4994
- return s.charCodeAt(0) === 47 || /^[A-Za-z]:[\\/]/.test(s) || s.charCodeAt(0) === 92 && s.charCodeAt(1) === 92;
4995
- }
4996
- function prefixViolation(prefix) {
4997
- const s = String(prefix == null ? "" : prefix);
4998
- if (!s.trim()) return "\u7A7A\u7684\u5B89\u88C5\u524D\u7F00";
4999
- if (CONTROL_RE.test(s)) return "\u5B89\u88C5\u524D\u7F00\u542B\u63A7\u5236\u7B26: " + s.slice(0, 80);
5000
- if (s.length > 4096) return "\u5B89\u88C5\u524D\u7F00\u8D85\u957F: " + s.slice(0, 60) + "...";
5001
- if (s[0] === "-") return "\u5B89\u88C5\u524D\u7F00\u4EE5 - \u5F00\u5934\uFF08\u4F1A\u88AB\u5F53\u4F5C npm \u9009\u9879\uFF09: " + s.slice(0, 80);
5002
- if (!isAbsoluteForm(s)) return "\u5B89\u88C5\u524D\u7F00\u987B\u4E3A\u7EDD\u5BF9\u8DEF\u5F84\uFF08/ \u5F00\u5934\u3001win \u76D8\u7B26\u6216 UNC\uFF09: " + s.slice(0, 80);
5003
- return null;
5004
- }
5005
- function pkgNameViolation(pkg) {
5006
- const s = String(pkg == null ? "" : pkg);
5007
- return PKG_NAME_RE.test(s) ? null : "\u975E\u6CD5\u5305\u540D\uFF08\u5B57\u7B26\u96C6\u767D\u540D\u5355\u4E0D\u901A\u8FC7\uFF09: " + s.slice(0, 80);
5008
- }
5009
- function unitNameViolation(unit) {
5010
- const s = String(unit == null ? "" : unit);
5011
- if (!UNIT_NAME_RE.test(s)) return "\u975E\u6CD5\u5355\u5143\u540D\uFF08\u5B57\u7B26\u96C6/\u957F\u5EA6\u767D\u540D\u5355\u4E0D\u901A\u8FC7\uFF09: " + s.slice(0, 80);
5012
- const dot = s.indexOf(".");
5013
- if (dot >= 0 && !/\.service$/.test(s)) return "\u5355\u5143\u540D\u540E\u7F00\u4E0D\u53D7\u652F\u6301\uFF08\u4EC5\u5141\u8BB8 .service \u6216\u65E0\u540E\u7F00\uFF09: " + s.slice(0, 80);
5014
- return null;
5015
- }
5016
- function ledgerKey(raw, opts) {
5017
- const o = opts || {};
5018
- const max = typeof o.max === "number" ? o.max : 128;
5019
- const empty = o.empty == null ? "unknown" : o.empty;
5020
- const unsafe = o.unsafe == null ? "(other)" : o.unsafe;
5021
- if (typeof raw !== "string" || !raw) return empty;
5022
- const s = raw.length > max ? raw.slice(0, max) : raw;
5023
- if (UNSAFE_LEDGER_KEYS.indexOf(s) >= 0 || LEDGER_UNSAFE_RE.test(s)) return unsafe;
5024
- return s;
5025
- }
5026
- module2.exports = {
5027
- PKG_NAME_RE,
5028
- ARGV_UNSAFE_RE,
5029
- WIN_ABS_PATH_RE,
5030
- UNIT_NAME_RE,
5031
- UNSAFE_LEDGER_KEYS,
5032
- LEDGER_UNSAFE_KEYS_RE: LEDGER_UNSAFE_RE,
5033
- argvViolation,
5034
- pkgNameViolation,
5035
- unitNameViolation,
5036
- ledgerKey,
5037
- CONTROL_RE,
5038
- isAbsoluteForm,
5039
- prefixViolation
5040
- };
5041
- }
5042
- });
5043
-
5044
- // src/platform/os/spawn.js
5045
- var require_spawn = __commonJS({
5046
- "src/platform/os/spawn.js"(exports2, module2) {
5047
- "use strict";
5048
- var { spawn } = require("node:child_process");
5049
- function detached(cmd2, args, opts) {
5050
- const o = opts || {};
5051
- return spawn(cmd2, args, Object.assign({}, o, {
5052
- detached: true,
5053
- windowsHide: true,
5054
- // 默认 ignore;显式 undefined 也回落 ignore,避免 stdio:undefined 的歧义。
5055
- stdio: o.stdio === void 0 ? "ignore" : o.stdio
5056
- }));
5057
- }
5058
- function piped(cmd2, args, opts) {
5059
- const o = opts || {};
5060
- return spawn(cmd2, args, Object.assign({}, o, {
5061
- stdio: ["ignore", "pipe", "pipe"],
5062
- windowsHide: true,
5063
- detached: o.detached === true
5064
- }));
5065
- }
5066
- function detachedIgnored(cmd2, args, opts) {
5067
- const o = opts || {};
5068
- return spawn(cmd2, args, Object.assign({}, o, {
5069
- detached: true,
5070
- windowsHide: true,
5071
- stdio: "ignore"
5072
- }));
5073
- }
5074
- module2.exports = { detached, piped, detachedIgnored };
5075
- }
5076
- });
5077
-
5078
- // src/platform/os/portable.js
5079
- var require_portable = __commonJS({
5080
- "src/platform/os/portable.js"(exports2, module2) {
5081
- "use strict";
5082
- var fs2 = require("node:fs");
5083
- var pidlookup = require_pidlookup();
5084
- var spawner = require_spawn();
5085
- var procOS = require_process();
5086
- var STOP_WAIT_CAP_MS = 3e3;
5087
- var NAP_MS = 150;
5088
- function nap(ms) {
5089
- try {
5090
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
5091
- } catch {
5092
- }
5093
- }
5094
- function readPidFile(pidFile) {
5095
- if (!pidFile) return null;
5096
- try {
5097
- const n = parseInt(String(fs2.readFileSync(pidFile, "utf8")).trim(), 10);
5098
- return Number.isInteger(n) && n > 0 ? n : null;
5099
- } catch {
5100
- return null;
5017
+ function parseExecLine(line) {
5018
+ let toks = tokenizeExec(line).filter((t) => t !== "%%" && !/^%[a-zA-Z]$/.test(t));
5019
+ if (toks[0] === "env") {
5020
+ let i = 1;
5021
+ while (i < toks.length && /=/.test(toks[i])) i++;
5022
+ toks = toks.slice(i);
5101
5023
  }
5024
+ if (!toks.length) return null;
5025
+ return { bin: toks[0], baseArgs: toks.slice(1) };
5102
5026
  }
5103
- function matchesAnchors(pid, anchors) {
5104
- if (!pid || !Array.isArray(anchors) || !anchors.length) return false;
5105
- const cmd2 = pidlookup.readCmdline(pid);
5106
- if (!cmd2) return false;
5107
- return anchors.some((a) => a && cmd2.indexOf(String(a)) !== -1);
5027
+ function exeFromCmdLine(cmdLine) {
5028
+ const s = String(cmdLine || "");
5029
+ let m = s.match(/^\s*"([^"]+\.exe)"/i);
5030
+ if (m) return m[1];
5031
+ m = s.match(/^\s*(.*?\.exe)/i);
5032
+ return m ? m[1] : null;
5108
5033
  }
5109
- var portOf = (o) => {
5110
- const p = Number(o && o.port);
5111
- return Number.isInteger(p) && p > 0 ? p : 0;
5112
- };
5113
- var pidFileOf = (o) => o && o.pidFile || null;
5114
- var anchorsOf = (o) => Array.isArray(o && o.anchors) ? o.anchors : [];
5115
- function findOurs(o) {
5116
- const anchors = anchorsOf(o);
5117
- const fp = readPidFile(pidFileOf(o));
5118
- if (fp !== null && pidlookup.isAlive(fp) && matchesAnchors(fp, anchors)) return { pid: fp, ownGroup: true };
5119
- const port = portOf(o);
5120
- if (port) {
5121
- const q = pidlookup.findListeningPid(port);
5122
- if (q !== null && q !== fp && matchesAnchors(q, anchors)) return { pid: q, ownGroup: false };
5034
+ var WIN_APP_PATHS = ["msedge.exe", "chrome.exe", "firefox.exe", "brave.exe", "opera.exe", "vivaldi.exe", "chromium.exe", "thorium.exe", "librewolf.exe"];
5035
+ function winExeOfProgId(runner, note, progId, env) {
5036
+ if (!safeRegKeyPart(progId)) return null;
5037
+ for (const root of ["HKCU\\Software\\Classes", "HKLM\\Software\\Classes"]) {
5038
+ const cmd2 = regValue(runner, note, root + "\\" + progId + "\\shell\\open\\command");
5039
+ const exe = cmd2 ? exeFromCmdLine(expandEnvVars(cmd2, env)) : null;
5040
+ if (exe) return exe;
5123
5041
  }
5124
5042
  return null;
5125
5043
  }
5126
- function removePidFile(o) {
5127
- const f = pidFileOf(o);
5128
- if (f) {
5129
- try {
5130
- fs2.unlinkSync(f);
5131
- } catch {
5044
+ function probeWin(d) {
5045
+ const { runOut, env, canExec } = d;
5046
+ const notes = [];
5047
+ const note = (source, detail) => {
5048
+ notes.push({ source, detail });
5049
+ };
5050
+ const found = /* @__PURE__ */ new Map();
5051
+ const keyOf = (exe) => String(exe || "").toLowerCase().replace(/[\\/]/g, "\\");
5052
+ const push = (exe, how) => {
5053
+ const full = expandEnvVars(exe, env).trim();
5054
+ if (!/\.exe$/i.test(full)) {
5055
+ note(how, "\u6765\u6E90\u7ED9\u51FA\u7684\u4E0D\u662F exe \u8DEF\u5F84: " + full);
5056
+ return null;
5132
5057
  }
5133
- }
5134
- }
5135
- var portable = {
5136
- kind: "portable",
5137
- supportsUnits: false,
5138
- supportsTransient: true,
5139
- // 无操作 = 成功(与 systemd 的动词返回形态一致,调用方无需按 provider 分支)。
5140
- daemonReload() {
5141
- return true;
5142
- },
5143
- resetFailed() {
5144
- return true;
5145
- },
5146
- /** 拉起:spawn(detached)——POSIX 自成进程组(kill(-pid) 语义的前提)、win32 windowsHide +
5147
- * CREATE_NEW_PROCESS_GROUP(NO-CONSOLE-WINDOW 纪律收口在 platform/os/spawn.js)。
5148
- * 调用方传来的 props(cgroup 语义)在此如实忽略:本档无内核强制可施加。
5149
- * @param {{cmd:string[], env?:object, workingDir?:string, pidFile?:string, port?:number, anchors?:string[]}} o */
5150
- startTransient(o) {
5151
- const opts = o || {};
5152
- const cmd2 = opts.cmd || [];
5153
- if (!cmd2.length) throw new Error("portable \u62C9\u8D77\u62D2\u7EDD\uFF1A\u7A7A\u547D\u4EE4");
5154
- const child = spawner.detached(cmd2[0], cmd2.slice(1), {
5155
- cwd: opts.workingDir || void 0,
5156
- env: Object.assign({}, process.env, opts.env || {})
5157
- });
5158
- child.on("error", () => {
5159
- });
5160
- if (!child.pid) {
5161
- try {
5162
- child.kill();
5163
- } catch {
5164
- }
5165
- throw new Error("portable \u62C9\u8D77\u5931\u8D25\uFF1Aspawn \u672A\u4EA7\u751F\u8FDB\u7A0B");
5058
+ const k = keyOf(full);
5059
+ const hit = found.get(k);
5060
+ if (hit) {
5061
+ if (!hit.sources.includes(how)) hit.sources.push(how);
5062
+ return hit;
5166
5063
  }
5167
- try {
5168
- child.unref();
5169
- } catch {
5064
+ if (!canExec(full)) {
5065
+ note(how, "\u6307\u5411\u7684\u6587\u4EF6\u4E0D\u53EF\u6267\u884C: " + full);
5066
+ return null;
5170
5067
  }
5171
- if (opts.pidFile) {
5172
- try {
5173
- fs2.writeFileSync(opts.pidFile, String(child.pid));
5174
- } catch {
5175
- }
5068
+ const item = { id: k, name: path2.basename(full).replace(/\.exe$/i, ""), engine: engineOf(full), bin: full, source: how, sources: [how] };
5069
+ found.set(k, item);
5070
+ note(how, item.name);
5071
+ return item;
5072
+ };
5073
+ const progIds = /* @__PURE__ */ new Map();
5074
+ const addProgId = (pid, how) => {
5075
+ const s = String(pid || "").trim();
5076
+ if (!safeRegKeyPart(s)) return;
5077
+ if (progIds.get(s) === "userchoice") return;
5078
+ progIds.set(s, how);
5079
+ };
5080
+ let defaultId = null;
5081
+ let defaultSource = null;
5082
+ const DEF_RANK = { userchoice: 1, "scheme-association": 2, "only-installed": 9 };
5083
+ let defRank = 99;
5084
+ const setDefault = (item, how) => {
5085
+ const r = DEF_RANK[how] === void 0 ? 99 : DEF_RANK[how];
5086
+ if (item && r < defRank) {
5087
+ defRank = r;
5088
+ defaultId = item.id;
5089
+ defaultSource = how;
5176
5090
  }
5177
- return true;
5178
- },
5179
- /** 停止(有界同步复核):终止我们的进程树,确认「端口锚点与 run.pid 锚点双双消失」才 true。
5180
- * 无可杀对象(含 run.pid 为陈旧/被 PID 复用)-> 清文件后如实 true(幂等)。 */
5181
- stopUnit(unit, o) {
5182
- const opts = o || {};
5183
- const ours = findOurs(opts);
5184
- if (!ours) {
5185
- removePidFile(opts);
5186
- return true;
5187
- }
5188
- try {
5189
- procOS.killTree(ours.pid, "SIGTERM", void 0, { ownGroup: ours.ownGroup });
5190
- } catch {
5191
- }
5192
- const budget = Math.max(0, Math.min(typeof opts.timeoutMs === "number" ? opts.timeoutMs : STOP_WAIT_CAP_MS, STOP_WAIT_CAP_MS));
5193
- const t0 = Date.now();
5194
- for (; ; ) {
5195
- if (!findOurs(opts)) {
5196
- removePidFile(opts);
5197
- return true;
5198
- }
5199
- if (Date.now() - t0 >= budget) break;
5200
- nap(NAP_MS);
5091
+ };
5092
+ const cmdToExe = (raw) => raw ? exeFromCmdLine(expandEnvVars(raw, env)) : null;
5093
+ const uc = regValue(runOut, note, "HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice", "ProgId");
5094
+ if (uc) addProgId(uc, "userchoice");
5095
+ for (const root of ["HKCU\\Software\\Classes", "HKLM\\Software\\Classes"]) {
5096
+ const pid = regValue(runOut, note, root + "\\https");
5097
+ if (pid) addProgId(pid, "scheme-association");
5098
+ const exe = cmdToExe(regValue(runOut, note, root + "\\https\\shell\\open\\command"));
5099
+ if (exe) setDefault(push(exe, "scheme-association"), "scheme-association");
5100
+ }
5101
+ for (const name of regSubkeys(runOut, note, "HKLM\\SOFTWARE\\Clients\\StartMenuInternet")) {
5102
+ addProgId(name, "startmenu-catalog");
5103
+ const exe = cmdToExe(regValue(runOut, note, "HKLM\\SOFTWARE\\Clients\\StartMenuInternet\\" + name + "\\shell\\open\\command")) || winExeOfProgId(runOut, note, name, env);
5104
+ if (exe) push(exe, "startmenu-catalog");
5105
+ }
5106
+ for (const root of ["HKLM\\SOFTWARE", "HKCU\\SOFTWARE"]) {
5107
+ for (const capPath of regValueTargets(runOut, note, root + "\\RegisteredApplications")) {
5108
+ const https = regValue(runOut, note, capPath + "\\UrlAssociations\\https");
5109
+ const sid = regValue(runOut, note, capPath, "StartMenuInternet");
5110
+ const pid = https || sid;
5111
+ if (pid) addProgId(pid, "registered-apps");
5201
5112
  }
5202
- try {
5203
- procOS.killTree(ours.pid, "SIGKILL", void 0, { ownGroup: ours.ownGroup });
5204
- } catch {
5113
+ }
5114
+ for (const exe of WIN_APP_PATHS) {
5115
+ for (const root of ["HKCU", "HKLM"]) {
5116
+ const v = regValue(runOut, note, root + "\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + exe);
5117
+ if (v && push(v, "app-paths")) break;
5205
5118
  }
5206
- nap(NAP_MS);
5207
- if (!findOurs(opts)) {
5208
- removePidFile(opts);
5209
- return true;
5119
+ }
5120
+ for (const [pid, how] of progIds) {
5121
+ const exe = winExeOfProgId(runOut, note, pid, env);
5122
+ if (!exe) continue;
5123
+ const item = push(exe, how);
5124
+ if (how === "userchoice") setDefault(item, "userchoice");
5125
+ }
5126
+ if (found.size === 1) setDefault([...found.values()][0], "only-installed");
5127
+ return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5128
+ }
5129
+ var MAC_JXA_LIST = [
5130
+ "ObjC.import('Foundation');",
5131
+ "var ws=$.NSWorkspace.sharedWorkspace;",
5132
+ "var u=$.NSURL.URLWithString('https://dsh.local/probe');",
5133
+ "var out=[];var def='';",
5134
+ "try{var d=ws.URLForApplicationToOpenURL(u); if(!d.isNil()) def=ObjC.unwrap(d.path)||'';}catch(e){}",
5135
+ "var list=null;",
5136
+ "try{list=ws.urlsForApplicationsToOpenURL(u);}catch(e){}",
5137
+ "if(list===null||typeof list.count!=='number'){if(def){out.push(def+'\\t\\t1');}}else{",
5138
+ "var n=Number(list.count);",
5139
+ "for(var i=0;i<n;i++){",
5140
+ "var p=ObjC.unwrap(list.objectAtIndex(i).path)||''; if(!p) continue;",
5141
+ "var exe='';var bid='';",
5142
+ "var b=$.NSBundle.bundleWithPath(p);",
5143
+ "if(!b.isNil()){exe=ObjC.unwrap(b.executablePath)||'';bid=ObjC.unwrap(b.bundleIdentifier)||'';}",
5144
+ "out.push((exe||p)+'\\t'+bid+'\\t'+(p&&def&&p===def?1:0));}",
5145
+ "}",
5146
+ // 末句必须是裸表达式:osascript 取的是最后一条**语句**的值,if/else 语句不产结果,
5147
+ // 写在分支里会让整段查询输出空字符串(表现为「系统未报任何可用应用」)。
5148
+ "out.join('\\n');"
5149
+ ].join("");
5150
+ function probeMac(d) {
5151
+ const { runOut, canExec } = d;
5152
+ const notes = [];
5153
+ const note = (source, detail) => {
5154
+ notes.push({ source, detail });
5155
+ };
5156
+ const out = runOut("osascript", ["-l", "JavaScript", "-e", MAC_JXA_LIST]);
5157
+ if (!out || !out.trim()) {
5158
+ note("launchservices", "\u67E5\u8BE2\u65E0\u8F93\u51FA\uFF08\u65E7\u7CFB\u7EDF\u6216\u88AB\u6743\u9650\u62E6\u622A\uFF09");
5159
+ return { browsers: [], defaultId: null, defaultSource: null, probed: notes };
5160
+ }
5161
+ const keyOf = (exe) => String(exe || "").toLowerCase();
5162
+ const found = /* @__PURE__ */ new Map();
5163
+ let defaultId = null;
5164
+ let defaultSource = null;
5165
+ for (const line of out.trim().split("\n")) {
5166
+ const parts = line.split(" ");
5167
+ const bin = (parts[0] || "").trim();
5168
+ if (!bin) continue;
5169
+ if (!canExec(bin)) {
5170
+ note("launchservices", "\u4E0D\u53EF\u6267\u884C: " + bin);
5171
+ continue;
5210
5172
  }
5211
- return false;
5212
- },
5213
- /** 活跃判定(三态):port 与 pidFile 都缺 = 无从查询 -> null,删除保护路径不得把它当「已停止」。
5214
- * anchors 为空时降级为「端口有监听即活跃」(monitor.probeInstance 同口径,该调用方本就以此判在线);
5215
- * 杀进程(stopUnit)仍要求锚点命中 —— 判定可宽,动手必严。
5216
- * 存活 pid 的 cmdline 读不到 = 分不清「他人复用」与「查询失败」-> null。 */
5217
- isUnitActive(unit, o) {
5218
- const opts = o || {};
5219
- if (findOurs(opts)) return true;
5220
- const port = portOf(opts);
5221
- const pidFile = pidFileOf(opts);
5222
- const anchors = anchorsOf(opts);
5223
- if (!port && !pidFile) return null;
5224
- if (!anchors.length) {
5225
- const fp2 = readPidFile(pidFile);
5226
- if (fp2 !== null && pidlookup.isAlive(fp2)) return true;
5227
- if (port) {
5228
- const q = pidlookup.findListeningPid(port);
5229
- if (q !== null) return true;
5230
- if (fp2 !== null) return false;
5231
- return null;
5173
+ const k = keyOf(bin);
5174
+ if (!found.has(k)) found.set(k, { id: k, name: path2.basename(bin), engine: engineOf(bin), bin, source: "launchservices", sources: ["launchservices"], isDefault: false });
5175
+ if (parts[2] === "1") {
5176
+ found.get(k).isDefault = true;
5177
+ if (!defaultId) {
5178
+ defaultId = k;
5179
+ defaultSource = "launchservices";
5232
5180
  }
5233
- return fp2 !== null ? false : null;
5234
5181
  }
5235
- const fp = readPidFile(pidFile);
5236
- if (fp !== null && pidlookup.isAlive(fp) && !pidlookup.readCmdline(fp)) return null;
5237
- return false;
5238
- },
5239
- // portable 无 systemd transient 单元文件;run.pid 的清理归 cleanTransient。
5240
- transientUnitFile() {
5241
- return null;
5242
- },
5243
- /** 清理同名残留:先停掉仍活着的旧进程(重启自愈路径:旧进程未监听端口时端口探测拦不住),再清 run.pid。 */
5244
- cleanTransient(unit, o) {
5245
- const opts = o || {};
5246
- const errors = [];
5247
- if (this.stopUnit(unit, Object.assign({}, opts, { timeoutMs: 1500 })) === false) errors.push("stop-unconfirmed");
5248
- removePidFile(opts);
5249
- return { ok: errors.length === 0, errors };
5250
- },
5251
- /** 返回值只表示「是否改动了内核强制」;governor 不因 false 走降级分支 —— 违规处置链本就平台无关。 */
5252
- setLimits() {
5253
- return false;
5254
5182
  }
5255
- };
5256
- module2.exports = { portable, findOurs, matchesAnchors, _test: { readPidFile, matchesAnchors, findOurs } };
5257
- }
5258
- });
5259
-
5260
- // src/platform/os/service.js
5261
- var require_service = __commonJS({
5262
- "src/platform/os/service.js"(exports2, module2) {
5263
- "use strict";
5264
- var fs2 = require("node:fs");
5265
- var os2 = require("node:os");
5266
- var path2 = require("node:path");
5267
- var exec = require_exec();
5268
- var input = require_input();
5269
- var execPath = require_exec_path();
5270
- var { portable } = require_portable();
5271
- var CapabilityError = class extends Error {
5272
- constructor(msg) {
5273
- super(msg);
5274
- this.name = "CapabilityError";
5275
- this.code = "CAPABILITY_UNSUPPORTED";
5183
+ note("launchservices", found.size ? found.size + " \u4E2A\u53EF\u6253\u5F00 https \u7684\u5E94\u7528" : "\u7CFB\u7EDF\u672A\u62A5\u4EFB\u4F55\u53EF\u7528\u5E94\u7528");
5184
+ if (!defaultId && found.size === 1) {
5185
+ defaultId = [...found.keys()][0];
5186
+ defaultSource = "only-installed";
5276
5187
  }
5277
- };
5278
- var PLATFORM = process.platform;
5279
- function run(cmd2, args, opts) {
5280
- return exec.run(cmd2, args, opts || {});
5188
+ return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5281
5189
  }
5282
- var UNIT_NAME_RE = input.UNIT_NAME_RE;
5283
- function unitNameViolation(unit) {
5284
- return input.unitNameViolation(unit);
5190
+ var PJ = path2.posix.join;
5191
+ function desktopDirs(env, home) {
5192
+ const dataHome = env.XDG_DATA_HOME || PJ(home, ".local", "share");
5193
+ const dataDirs = String(env.XDG_DATA_DIRS || "/usr/local/share:/usr/share").split(":").filter(Boolean);
5194
+ const dirs = [[dataHome, "xdg"], ...dataDirs.map((p) => [p, "xdg"])];
5195
+ dirs.push([PJ(home, ".local", "share"), "xdg"]);
5196
+ dirs.push(["/var/lib/flatpak/exports/share", "flatpak"]);
5197
+ dirs.push([PJ(home, ".local", "share", "flatpak", "exports", "share"), "flatpak"]);
5198
+ dirs.push(["/var/lib/snapd/desktop", "snap"]);
5199
+ const seen = [];
5200
+ return dirs.map(([root, kind]) => ({ dir: PJ(root, "applications"), kind })).filter((x) => {
5201
+ if (seen.includes(x.dir)) return false;
5202
+ seen.push(x.dir);
5203
+ return true;
5204
+ });
5285
5205
  }
5286
- var systemd = {
5287
- kind: "systemd",
5288
- supportsUnits: true,
5289
- supportsTransient: true,
5290
- daemonReload() {
5291
- try {
5292
- return run("systemctl", ["--user", "daemon-reload"], { timeoutMs: 15e3 }) !== null;
5293
- } catch {
5294
- return false;
5295
- }
5296
- },
5297
- stopUnit(unit, opts) {
5298
- if (unitNameViolation(unit)) return false;
5299
- const o = opts || {};
5300
- try {
5301
- return run("systemctl", ["--user", "stop", unit], { timeoutMs: o.timeoutMs || 15e3 }) !== null;
5302
- } catch {
5303
- return false;
5304
- }
5305
- },
5306
- // run() 失败返回 null 而不抛,try/catch 包裹会恒返回 true;如实回传成败。
5307
- resetFailed(unit) {
5308
- if (unitNameViolation(unit)) return false;
5309
- return run("systemctl", ["--user", "reset-failed", unit], { timeoutMs: 1e4 }) !== null;
5310
- },
5311
- /** 单元活跃判定(三态):确认 active -> true;确认不活跃 -> false;查询未完成 -> null(未知)。
5312
- * run() 失败只返回 null,「查询未完成」不可折成 false:instance/ops.js 删除数据目录前
5313
- * 按「!== false 才放行删除」消费本函数,折假即超时窗口内误删沙箱数据。 */
5314
- isUnitActive(unit) {
5315
- if (!unit) return true;
5316
- if (unitNameViolation(unit)) return false;
5317
- try {
5318
- const r = exec.runDetail("systemctl", ["--user", "is-active", unit], { timeoutMs: 8e3 });
5319
- if (r.timedOut) return null;
5320
- const state = String(r.stdout || "").trim();
5321
- if (state === "active") return true;
5322
- if (state || r.code !== null) return false;
5323
- return null;
5324
- } catch {
5325
- return null;
5326
- }
5327
- },
5328
- transientUnitFile(unit) {
5329
- if (unitNameViolation(unit)) return null;
5330
- let uid = 0;
5331
- try {
5332
- uid = os2.userInfo().uid;
5333
- } catch {
5206
+ function browserFromDesktop(text, kind, file) {
5207
+ let inMain = false;
5208
+ const got = {};
5209
+ for (const line of String(text).split(/\r?\n/)) {
5210
+ if (/^\[Desktop Entry\]\s*$/.test(line)) {
5211
+ inMain = true;
5212
+ continue;
5334
5213
  }
5335
- const rt = process.env.XDG_RUNTIME_DIR || "/run/user/" + uid;
5336
- return path2.join(rt, "systemd", "transient", unit + ".service");
5337
- },
5338
- /** 清理 stale transient 单元:stop/reset-failed/删单元文件/daemon-reload。
5339
- * 必须 reload:删除文件后 systemd 仍缓存该单元为 loaded,否则 systemd-run 拒绝重建同名单元。
5340
- * 返回 {ok, errors}:stop/reset-failed 对「从未加载的单元」非零退出属正常,不计入 ok;
5341
- * 硬失败只有删单元文件与 daemon-reload(run() 失败返回 null 不抛,成败须如实回传)。 */
5342
- cleanTransient(unit) {
5343
- const errors = [];
5344
- const bad = unitNameViolation(unit);
5345
- if (bad) return { ok: false, errors: [bad] };
5346
- exec.runDetail("systemctl", ["--user", "stop", unit + ".service"], { timeoutMs: 1e4 });
5347
- exec.runDetail("systemctl", ["--user", "reset-failed", unit + ".service"], { timeoutMs: 1e4 });
5348
- let unlinkOk = true;
5349
- try {
5350
- const f = this.transientUnitFile(unit);
5351
- if (f) fs2.unlinkSync(f);
5352
- } catch {
5353
- unlinkOk = false;
5354
- errors.push("unlink " + unit + ".service");
5355
- }
5356
- if (!exec.runDetail("systemctl", ["--user", "daemon-reload"], { timeoutMs: 15e3 }).ok) errors.push("daemon-reload");
5357
- return { ok: unlinkOk && errors.length === 0, errors };
5358
- },
5359
- /** 以 transient 单元启动(独立 cgroup;每实例隔离)。
5360
- * @param {{unit:string, cmd:string[], env?:object, props?:string[], workingDir?:string, description?:string, timeoutMs?:number}} o
5361
- * props 为 systemd 属性(如 KillMode=process、MemoryMax=8G),平台层只拼装成 --property,不解释语义。 */
5362
- startTransient(o) {
5363
- const opts = o || {};
5364
- const bad = unitNameViolation(opts.unit);
5365
- if (bad) throw new Error("systemd-run \u62D2\u7EDD: " + bad);
5366
- const args = ["--user", "--unit=" + opts.unit];
5367
- if (opts.description) args.push("--description=" + opts.description);
5368
- for (const p of opts.props || []) args.push("--property=" + p);
5369
- for (const [k, v] of Object.entries(opts.env || {})) args.push("--setenv=" + k + "=" + v);
5370
- if (opts.workingDir) args.push("--working-directory=" + opts.workingDir);
5371
- args.push("--", ...opts.cmd || []);
5372
- const r = exec.runDetail("systemd-run", args, { timeoutMs: opts.timeoutMs || 2e4 });
5373
- if (!r.ok) throw new Error("systemd-run \u5931\u8D25: " + (r.error || "unknown") + (r.stderr ? " | " + String(r.stderr).trim() : ""));
5374
- return true;
5375
- },
5376
- /** 运行期改限额(W3 动态化):systemctl --user set-property 立即生效,无需重启单元。
5377
- * 必带 --runtime:transient 单元本不落盘,不带它会把 drop-in 写进用户配置目录,陈旧下限永久黏住。
5378
- * 值只来自 sandbox.unitProps 同源的 alloc(平台层翻译语义、不决定数额)。 */
5379
- setLimits(unit, alloc) {
5380
- if (unitNameViolation(unit)) return false;
5381
- const a = alloc || {};
5382
- const props = [];
5383
- if (a.memoryMax) props.push("MemoryMax=" + a.memoryMax);
5384
- if (a.memoryHigh) props.push("MemoryHigh=" + a.memoryHigh);
5385
- if (a.cpuQuota) props.push("CPUQuota=" + a.cpuQuota);
5386
- if (!props.length) return false;
5387
- return exec.runDetail(
5388
- "systemctl",
5389
- ["--user", "set-property", "--runtime", unit].concat(props),
5390
- { timeoutMs: 1e4 }
5391
- ).ok;
5214
+ if (/^\[/.test(line)) break;
5215
+ if (!inMain) continue;
5216
+ const m = line.match(/^(Exec|Name|TryExec|Categories)=(.*)$/);
5217
+ if (!m) continue;
5218
+ if (m[1] === "Name" && got.Name) continue;
5219
+ got[m[1]] = m[2].trim();
5392
5220
  }
5393
- };
5394
- function makeUnsupported(kind, label) {
5221
+ if (!got.Exec) return null;
5222
+ const categories = String(got.Categories || "");
5223
+ const parsed = parseExecLine(got.Exec);
5224
+ if (!parsed) return null;
5225
+ const engine = engineOf(parsed.bin);
5226
+ const claimsBrowser = /(^|;)WebBrowser(;|$)/.test(categories);
5227
+ if (!claimsBrowser && engine === "other") return null;
5228
+ if (engine === "other") return null;
5395
5229
  return {
5396
- kind,
5397
- supportsUnits: false,
5398
- supportsTransient: false,
5399
- daemonReload() {
5400
- return false;
5401
- },
5402
- stopUnit() {
5403
- throw new CapabilityError(label + "\uFF1A\u4E0D\u652F\u6301\u4EE5\u7528\u6237\u5355\u5143\u65B9\u5F0F\u7BA1\u7406\u88AB\u7BA1\u5B9E\u4F8B");
5404
- },
5405
- resetFailed() {
5406
- return false;
5407
- },
5408
- isUnitActive(unit) {
5409
- return unit ? false : true;
5410
- },
5411
- transientUnitFile() {
5412
- return null;
5413
- },
5414
- // 无 transient 单元可清 = 成功;返回形态与 systemd 一致,调用方无需分支。
5415
- cleanTransient() {
5416
- return { ok: true, errors: [] };
5417
- },
5418
- startTransient() {
5419
- throw new CapabilityError(label + "\uFF1A\u4E0D\u652F\u6301\u62C9\u8D77\u5B9E\u4F8B\u8231\uFF08portable \u6863\u672A\u542F\u7528\uFF09");
5420
- },
5421
- setLimits() {
5422
- return false;
5423
- }
5230
+ // id 恒为归一后的可执行文件路径:defaultId 靠它在清单里定位条目,三平台同一契约
5231
+ id: parsed.bin.toLowerCase(),
5232
+ name: got.Name || path2.basename(file, ".desktop"),
5233
+ engine,
5234
+ bin: parsed.bin,
5235
+ baseArgs: parsed.baseArgs,
5236
+ source: kind,
5237
+ desktopFile: file
5424
5238
  };
5425
5239
  }
5426
- var NONE = makeUnsupported("none", "\u5F53\u524D\u5E73\u53F0\u65E0\u670D\u52A1\u7BA1\u7406\u5668");
5427
- var _systemdRun = null;
5428
- function hasSystemdRun() {
5429
- if (_systemdRun === null) {
5430
- _systemdRun = !!execPath.resolveExecutable("systemd-run") || exec.runOut("systemd-run", ["--version"], { timeoutMs: 3e3 }) !== null;
5431
- }
5432
- return _systemdRun;
5433
- }
5434
- function current() {
5435
- if (PLATFORM === "linux") return hasSystemdRun() ? systemd : portable;
5436
- if (PLATFORM === "darwin" || PLATFORM === "win32") return portable;
5437
- return NONE;
5438
- }
5439
- module2.exports = { current, CapabilityError, kind: () => current().kind, PLATFORM, UNIT_NAME_RE, unitNameViolation, _testProviders: { systemd, portable, NONE } };
5440
- }
5441
- });
5442
-
5443
- // src/platform/os/notify.js
5444
- var require_notify = __commonJS({
5445
- "src/platform/os/notify.js"(exports2, module2) {
5446
- "use strict";
5447
- var spawnOS = require_spawn();
5448
- function appleScriptString(s) {
5449
- return JSON.stringify(String(s));
5450
- }
5451
- function powerShellString(s) {
5452
- return "'" + String(s).replace(/'/g, "''") + "'";
5453
- }
5454
- function notifyCommand(platform, title, body) {
5455
- const pl = platform || process.platform;
5456
- const t = String(title == null ? "" : title);
5457
- const b = String(body == null ? "" : body);
5458
- if (pl === "linux") {
5459
- return { cmd: "notify-send", args: ["-a", "dsh-supervisor", t, b] };
5460
- }
5461
- if (pl === "darwin") {
5462
- const script = "display notification " + appleScriptString(b) + " with title " + appleScriptString(t);
5463
- return { cmd: "osascript", args: ["-e", script] };
5464
- }
5465
- if (pl === "win32") {
5466
- const ps = [
5467
- '[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null',
5468
- '[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null',
5469
- "$n = New-Object System.Windows.Forms.NotifyIcon",
5470
- "$n.Icon = [System.Drawing.SystemIcons]::Information",
5471
- "$n.Visible = $true",
5472
- "$n.ShowBalloonTip(4000, " + powerShellString(t) + ", " + powerShellString(b) + ", [System.Windows.Forms.ToolTipIcon]::None)",
5473
- "Start-Sleep -Milliseconds 4200",
5474
- "$n.Dispose(); $n.Visible = $false"
5475
- ].join("; ");
5476
- return { cmd: "powershell", args: ["-NoProfile", "-NonInteractive", "-Command", ps] };
5240
+ function resolveLinuxBin(bin, canExec, env) {
5241
+ const b = String(bin || "").trim();
5242
+ if (!b) return null;
5243
+ if (b.startsWith("/")) return canExec(b) ? b : null;
5244
+ if (b.includes("/")) return null;
5245
+ for (const dir of String(env.PATH || "").split(":")) {
5246
+ if (!dir) continue;
5247
+ const p = PJ(dir.replace(/\/+$/, ""), b);
5248
+ if (canExec(p)) return p;
5477
5249
  }
5478
5250
  return null;
5479
5251
  }
5480
- function notify(title, body, onError) {
5481
- try {
5482
- const plan = notifyCommand(process.platform, title, body);
5483
- if (!plan) return false;
5484
- const c = spawnOS.detachedIgnored(plan.cmd, plan.args);
5485
- c.on("error", () => {
5486
- if (onError) onError();
5487
- });
5488
- c.unref();
5489
- return true;
5490
- } catch {
5491
- if (onError) onError();
5492
- return false;
5493
- }
5494
- }
5495
- module2.exports = { notify, notifyCommand, appleScriptString, powerShellString };
5496
- }
5497
- });
5498
-
5499
- // src/platform/os/browser-inventory.js
5500
- var require_browser_inventory = __commonJS({
5501
- "src/platform/os/browser-inventory.js"(exports2, module2) {
5502
- "use strict";
5503
- var fs2 = require("node:fs");
5504
- var path2 = require("node:path");
5505
- var os2 = require("node:os");
5506
- var exec = require_exec();
5507
- var { isExecutableFile } = require_exec_path();
5508
- var PROBE_TIMEOUT_MS = 1500;
5509
- function engineOf(bin) {
5510
- const base = String(bin || "").toLowerCase().split(/[\\/]/).pop();
5511
- if (/(chrome|chromium|msedge|edge|brave|vivaldi|opera|thorium)/.test(base)) return "chromium";
5512
- if (/(firefox|librewolf|waterfox)/.test(base)) return "firefox";
5513
- return "other";
5514
- }
5515
- function tokenizeExec(line) {
5516
- const toks = [];
5517
- let cur = "";
5518
- let q = null;
5519
- let esc = false;
5520
- let has = false;
5521
- for (const ch of String(line)) {
5522
- if (esc) {
5523
- cur += ch;
5524
- esc = false;
5525
- continue;
5526
- }
5527
- if (ch === "\\") {
5528
- esc = true;
5529
- has = true;
5530
- continue;
5252
+ function probeLinux(d) {
5253
+ const { runOut, readFile, exists, listDir, canExec, env, home } = d;
5254
+ const notes = [];
5255
+ const note = (source, detail) => {
5256
+ notes.push({ source, detail });
5257
+ };
5258
+ const found = /* @__PURE__ */ new Map();
5259
+ const usable = (item) => {
5260
+ const resolved = resolveLinuxBin(item.bin, canExec, env);
5261
+ if (!resolved) {
5262
+ note(item.source, "\u4E0D\u53EF\u6267\u884C: " + item.bin);
5263
+ return null;
5531
5264
  }
5532
- if (q) {
5533
- if (ch === q) q = null;
5534
- else cur += ch;
5535
- continue;
5265
+ const k = resolved.toLowerCase();
5266
+ const hit = found.get(k);
5267
+ if (hit) {
5268
+ if (!hit.sources.includes(item.source)) hit.sources.push(item.source);
5269
+ return hit;
5536
5270
  }
5537
- if (ch === "'" || ch === '"') {
5538
- q = ch;
5539
- has = true;
5271
+ const v = Object.assign({}, item, { id: k, bin: resolved, sources: [item.source] });
5272
+ found.set(k, v);
5273
+ return v;
5274
+ };
5275
+ for (const { dir, kind } of desktopDirs(env, home)) {
5276
+ let names = [];
5277
+ try {
5278
+ names = listDir(dir).filter((f) => f.endsWith(".desktop"));
5279
+ } catch {
5540
5280
  continue;
5541
5281
  }
5542
- if (/\s/.test(ch)) {
5543
- if (has) {
5544
- toks.push(cur);
5545
- cur = "";
5546
- has = false;
5547
- }
5548
- continue;
5282
+ for (const f of names) {
5283
+ let text = null;
5284
+ try {
5285
+ text = readFile(PJ(dir, f));
5286
+ } catch {
5287
+ continue;
5288
+ }
5289
+ const item = browserFromDesktop(text, kind, f);
5290
+ if (item) usable(item);
5549
5291
  }
5550
- cur += ch;
5551
- has = true;
5552
5292
  }
5553
- if (has) toks.push(cur);
5554
- return toks;
5293
+ let defaultId = null;
5294
+ let defaultSource = null;
5295
+ const fromMime = linuxDefaultFromMimeApps(readFile, exists, env, home, note);
5296
+ if (fromMime) {
5297
+ const item = usable(fromMime);
5298
+ if (item) {
5299
+ defaultId = item.id;
5300
+ defaultSource = "mimeapps";
5301
+ }
5302
+ }
5303
+ const id = runOut("xdg-settings", ["get", "default-web-browser"]);
5304
+ const desktopId = id && String(id).trim();
5305
+ if (!defaultId && desktopId && /^[A-Za-z0-9._-]+\.desktop$/.test(desktopId)) {
5306
+ for (const { dir } of desktopDirs(env, home)) {
5307
+ const p = PJ(dir, desktopId);
5308
+ let text = null;
5309
+ try {
5310
+ if (exists(p)) text = readFile(p);
5311
+ } catch {
5312
+ continue;
5313
+ }
5314
+ if (text === null) continue;
5315
+ const item = browserFromDesktop(text, "xdg-settings", desktopId);
5316
+ const used = item && usable(item);
5317
+ if (used) {
5318
+ defaultId = used.id;
5319
+ defaultSource = "xdg-settings";
5320
+ break;
5321
+ }
5322
+ }
5323
+ }
5324
+ if (!desktopId) note("xdg-settings", "\u65E0\u8F93\u51FA");
5325
+ else if (defaultSource === "xdg-settings") note("xdg-settings", "\u9ED8\u8BA4=" + defaultId);
5326
+ else note("xdg-settings", "\u62A5\u51FA " + desktopId + (defaultId ? "\uFF08\u9ED8\u8BA4\u5DF2\u7531 " + defaultSource + " \u5B9A\u51FA\uFF09" : " \u4F46\u4E0D\u53EF\u7528"));
5327
+ if (!defaultId && found.size === 1) {
5328
+ defaultId = [...found.keys()][0];
5329
+ defaultSource = "only-installed";
5330
+ }
5331
+ return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5555
5332
  }
5556
- function parseExecLine(line) {
5557
- let toks = tokenizeExec(line).filter((t) => t !== "%%" && !/^%[a-zA-Z]$/.test(t));
5558
- if (toks[0] === "env") {
5559
- let i = 1;
5560
- while (i < toks.length && /=/.test(toks[i])) i++;
5561
- toks = toks.slice(i);
5333
+ function linuxDefaultFromMimeApps(readFile, exists, env, home, note) {
5334
+ const cfgHome = env.XDG_CONFIG_HOME || PJ(home, ".config");
5335
+ const cfgDirs = String(env.XDG_CONFIG_DIRS || "/etc/xdg").split(":").filter(Boolean);
5336
+ const dataDirs = String(env.XDG_DATA_DIRS || "/usr/local/share:/usr/share").split(":").filter(Boolean);
5337
+ const files = [
5338
+ PJ(cfgHome, "mimeapps.list"),
5339
+ ...cfgDirs.map((d) => PJ(d, "mimeapps.list")),
5340
+ ...dataDirs.map((d) => PJ(d, "applications", "mimeapps.list"))
5341
+ ];
5342
+ for (const f of files) {
5343
+ if (!exists(f)) continue;
5344
+ let text = null;
5345
+ try {
5346
+ text = readFile(f);
5347
+ } catch {
5348
+ continue;
5349
+ }
5350
+ const id = mimeAppsDefault(text);
5351
+ if (!id) continue;
5352
+ note("mimeapps", path2.basename(f) + " -> " + id);
5353
+ for (const { dir, kind } of desktopDirs(env, home)) {
5354
+ const p = PJ(dir, id);
5355
+ if (!exists(p)) continue;
5356
+ let dt = null;
5357
+ try {
5358
+ dt = readFile(p);
5359
+ } catch {
5360
+ continue;
5361
+ }
5362
+ const item = browserFromDesktop(dt, kind, id);
5363
+ if (item) return item;
5364
+ }
5365
+ return null;
5562
5366
  }
5563
- if (!toks.length) return null;
5564
- return { bin: toks[0], baseArgs: toks.slice(1) };
5367
+ note("mimeapps", "\u65E0\u9ED8\u8BA4\u9879");
5368
+ return null;
5565
5369
  }
5566
- function regValueOf(out) {
5567
- const m = String(out || "").match(/REG_(?:EXPAND_SZ|SZ)\s+(.*)/);
5568
- if (!m) return null;
5569
- return m[1].trim() || null;
5370
+ function mimeAppsDefault(text) {
5371
+ let inDefault = false;
5372
+ for (const line of String(text || "").split(/\r?\n/)) {
5373
+ if (/^\s*\[/.test(line)) {
5374
+ inDefault = /^\s*\[Default Applications\]\s*$/.test(line);
5375
+ continue;
5376
+ }
5377
+ if (!inDefault) continue;
5378
+ const m = line.match(/^\s*x-scheme-handler\/https?\s*=\s*(.*)$/i);
5379
+ if (!m) continue;
5380
+ const ids = m[1].split(";").map((s) => s.trim()).filter((s) => /^[A-Za-z0-9._-]+\.desktop$/.test(s));
5381
+ if (ids.length) return ids[0];
5382
+ }
5383
+ return null;
5570
5384
  }
5571
- function expandEnvVars(value, env) {
5572
- const e = env || process.env;
5573
- return String(value).replace(/%([^%]+)%/g, (all, name) => e[name] === void 0 ? all : e[name]);
5385
+ function probe(platform, d) {
5386
+ const pl = platform || process.platform;
5387
+ const dd = Object.assign({}, d, { canExec: d.canExec || ((p) => isExecutableFile(p, pl)) });
5388
+ if (pl === "win32") return probeWin(dd);
5389
+ if (pl === "darwin") return probeMac(dd);
5390
+ return probeLinux(dd);
5574
5391
  }
5575
- function exeFromCmdLine(cmdLine) {
5576
- const s = String(cmdLine || "");
5577
- let m = s.match(/^\s*"([^"]+\.exe)"/i);
5578
- if (m) return m[1];
5579
- m = s.match(/^\s*(.*?\.exe)/i);
5580
- return m ? m[1] : null;
5392
+ var CACHE = /* @__PURE__ */ Object.create(null);
5393
+ function inventory(platform, o) {
5394
+ const ov = o || {};
5395
+ const pl = platform || process.platform;
5396
+ const now = ov.now || (() => Date.now());
5397
+ const ttl = ov.ttlMs === void 0 ? 6e4 : ov.ttlMs;
5398
+ const cached = CACHE[pl];
5399
+ if (!ov.force && cached && now() - cached.at < ttl) return Object.assign({ cached: true }, cached.value);
5400
+ const runOut = ov.runOut || ((bin, args) => exec.runOut(bin, args, { timeoutMs: PROBE_TIMEOUT_MS }));
5401
+ const readFile = ov.readFile || ((p) => fs2.readFileSync(p, "utf8"));
5402
+ const exists = ov.exists || ((p) => fs2.existsSync(p));
5403
+ const listDir = ov.listDir || ((p) => fs2.readdirSync(p));
5404
+ let value;
5405
+ try {
5406
+ value = probe(pl, { runOut, readFile, exists, listDir, canExec: ov.canExec, env: ov.env || process.env, home: ov.home || os2.homedir() });
5407
+ } catch (e) {
5408
+ value = { browsers: [], defaultId: null, defaultSource: null, probed: [{ source: "probe-error", detail: String(e && e.message || e) }] };
5409
+ }
5410
+ value.at = now();
5411
+ value.platform = pl;
5412
+ value.browsers.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
5413
+ CACHE[pl] = { at: now(), value };
5414
+ return Object.assign({ cached: false }, value);
5581
5415
  }
5582
- function safeRegKeyPart(s) {
5583
- return typeof s === "string" && s.length > 0 && s.length <= 200 && /^[\x20-\x7e]+$/.test(s) && !/[;|&`$()<>^"'\r\n]/.test(s);
5416
+ function invalidate(platform) {
5417
+ if (platform) delete CACHE[platform];
5418
+ else for (const k of Object.keys(CACHE)) delete CACHE[k];
5584
5419
  }
5585
- function regValue(runner, note, key, name) {
5586
- const args = name ? ["query", key, "/v", name] : ["query", key, "/ve"];
5587
- const out = runner("reg.exe", args);
5588
- const v = regValueOf(out);
5589
- note(name ? key + " /v " + name : key, v ? "ok" : "empty");
5590
- return v;
5420
+ module2.exports = {
5421
+ inventory,
5422
+ invalidate,
5423
+ probe,
5424
+ probeWin,
5425
+ probeMac,
5426
+ probeLinux,
5427
+ engineOf,
5428
+ tokenizeExec,
5429
+ parseExecLine,
5430
+ exeFromCmdLine,
5431
+ // 注册表原语住在 ./registry.js(单一实现),此处原样转出:既有门禁与消费方的取用口不变。
5432
+ regValueOf: registry.regValueOf,
5433
+ expandEnvVars,
5434
+ safeRegKeyPart,
5435
+ regKeyFull: registry.regKeyFull,
5436
+ regValue,
5437
+ regSubkeys,
5438
+ regValueTargets,
5439
+ browserFromDesktop,
5440
+ desktopDirs,
5441
+ resolveLinuxBin,
5442
+ mimeAppsDefault,
5443
+ linuxDefaultFromMimeApps,
5444
+ WIN_APP_PATHS,
5445
+ PROBE_TIMEOUT_MS
5446
+ };
5447
+ }
5448
+ });
5449
+
5450
+ // src/platform/os/egress.js
5451
+ var require_egress = __commonJS({
5452
+ "src/platform/os/egress.js"(exports2, module2) {
5453
+ "use strict";
5454
+ var tls = require("node:tls");
5455
+ var dns = require("node:dns");
5456
+ var exec = require_exec();
5457
+ var registry = require_registry();
5458
+ var PROBE_TIMEOUT_MS = 2500;
5459
+ var READ_TTL_MS = 6e4;
5460
+ function hostOf(url) {
5461
+ try {
5462
+ return new URL(String(url)).hostname.toLowerCase() || null;
5463
+ } catch {
5464
+ return null;
5465
+ }
5591
5466
  }
5592
- var REG_HIVE_ALIAS = { HKLM: "HKEY_LOCAL_MACHINE", HKCU: "HKEY_CURRENT_USER", HKCR: "HKEY_CLASSES_ROOT", HKU: "HKEY_USERS", HKCC: "HKEY_CURRENT_CONFIG" };
5593
- function regKeyFull(key) {
5594
- const s = String(key || "");
5595
- const i = s.indexOf("\\");
5596
- const root = i < 0 ? s : s.slice(0, i);
5597
- return (REG_HIVE_ALIAS[root.toUpperCase()] || root) + (i < 0 ? "" : s.slice(i));
5467
+ function proxyWin(runner, note) {
5468
+ const KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
5469
+ const read = (args) => Promise.resolve(runner("reg.exe", args));
5470
+ return Promise.all([
5471
+ read(["query", KEY, "/v", "ProxyEnable"]),
5472
+ read(["query", KEY, "/v", "ProxyServer"]),
5473
+ read(["query", KEY, "/v", "AutoConfigURL"])
5474
+ ]).then(([en, sv, pc]) => {
5475
+ const on = registry.regDwordOf(en);
5476
+ const server = registry.regValueOf(sv);
5477
+ const pac = registry.regValueOf(pc);
5478
+ const state = on === 1 ? "on" : on === 0 ? pac ? "on" : "off" : "unknown";
5479
+ note("registry:Internet Settings", "ProxyEnable=" + (on === null ? "unreadable" : on) + " server=" + (server || "-") + " pac=" + (pac ? "set" : "-"));
5480
+ return { state, server: server || null, pac: pac || null, source: "registry:Internet Settings" };
5481
+ });
5598
5482
  }
5599
- function regSubkeys(runner, note, key) {
5600
- const out = runner("reg.exe", ["query", key]);
5601
- if (!out) {
5602
- note(key, "unreadable");
5603
- return [];
5604
- }
5605
- const prefix = regKeyFull(key) + "\\";
5606
- const names = [];
5607
- for (const line of String(out).split(/\r?\n/)) {
5608
- const l = line.trim();
5609
- if (!/^HKEY_/i.test(l) || l.length <= prefix.length) continue;
5610
- if (l.slice(0, prefix.length).toUpperCase() !== prefix.toUpperCase()) continue;
5611
- const rest = l.slice(prefix.length);
5612
- if (rest && !rest.includes("\\") && safeRegKeyPart(rest)) names.push(rest);
5613
- }
5614
- note(key, names.length ? names.length + " \u9879" : "\u65E0\u5B50\u952E");
5615
- return names;
5483
+ function proxyMac(runOut, note) {
5484
+ return Promise.resolve(runOut("scutil", ["--proxy"])).then((out) => {
5485
+ if (!out) {
5486
+ note("scutil --proxy", "unreadable");
5487
+ return { state: "unknown", server: null, pac: null, source: "scutil" };
5488
+ }
5489
+ const kv = {};
5490
+ for (const line of String(out).split(/\r?\n/)) {
5491
+ const m = line.match(/^\s*([A-Za-z]+)\s*:\s*(.+?)\s*$/);
5492
+ if (m) kv[m[1]] = m[2];
5493
+ }
5494
+ const flagOn = (k) => kv[k] === "1";
5495
+ const anyManual = kv.HTTPEnable !== void 0 || kv.HTTPSEnable !== void 0 || kv.SOCKSEnable !== void 0 || kv.ProxyAutoConfigEnable !== void 0;
5496
+ const state = flagOn("HTTPEnable") || flagOn("HTTPSEnable") || flagOn("SOCKSEnable") || flagOn("ProxyAutoConfigEnable") ? "on" : anyManual ? "off" : "unknown";
5497
+ note("scutil --proxy", "state=" + state);
5498
+ return {
5499
+ state,
5500
+ server: kv.HTTPProxy || kv.HTTPSProxy || kv.SOCKSProxy || null,
5501
+ pac: kv.ProxyAutoConfigURLString || kv.ProxyAutoConfigURL || null,
5502
+ source: "scutil"
5503
+ };
5504
+ });
5616
5505
  }
5617
- var WIN_APP_PATHS = ["msedge.exe", "chrome.exe", "firefox.exe", "brave.exe", "opera.exe", "vivaldi.exe", "chromium.exe", "thorium.exe", "librewolf.exe"];
5618
- function winExeOfProgId(runner, note, progId, env) {
5619
- if (!safeRegKeyPart(progId)) return null;
5620
- for (const root of ["HKCU\\Software\\Classes", "HKLM\\Software\\Classes"]) {
5621
- const cmd2 = regValue(runner, note, root + "\\" + progId + "\\shell\\open\\command");
5622
- const exe = cmd2 ? exeFromCmdLine(expandEnvVars(cmd2, env)) : null;
5623
- if (exe) return exe;
5624
- }
5625
- return null;
5506
+ function proxyLinux(env, note) {
5507
+ const pick = (k) => typeof env[k] === "string" && env[k].trim() ? env[k].trim() : null;
5508
+ const server = pick("https_proxy") || pick("HTTPS_PROXY") || pick("http_proxy") || pick("HTTP_PROXY") || pick("ALL_PROXY") || pick("all_proxy");
5509
+ const noProxy = pick("no_proxy") || pick("NO_PROXY");
5510
+ const state = server ? "on" : "unknown";
5511
+ note("env proxy", "state=" + state);
5512
+ return { state, server, pac: pick("PAC_FILE") || null, noProxy, source: "env" };
5626
5513
  }
5627
- function probeWin(d) {
5628
- const { runOut, env, canExec } = d;
5514
+ function proxyUnknown(reason) {
5515
+ return { state: "unknown", server: null, pac: null, source: reason };
5516
+ }
5517
+ var _proxyCache = /* @__PURE__ */ new Map();
5518
+ function proxy(o) {
5519
+ const ov = o || {};
5520
+ const pl = ov.platform || process.platform;
5521
+ const now = typeof ov.now === "function" ? ov.now : Date.now;
5522
+ const ttl = ov.ttlMs === void 0 ? READ_TTL_MS : ov.ttlMs;
5523
+ const hit = _proxyCache.get(pl);
5524
+ if (!ov.force && ttl > 0 && hit && now() - hit.value.at < ttl) {
5525
+ return Promise.resolve(Object.assign({}, hit.value, { cached: true }));
5526
+ }
5629
5527
  const notes = [];
5630
5528
  const note = (source, detail) => {
5631
5529
  notes.push({ source, detail });
5632
5530
  };
5633
- const found = /* @__PURE__ */ new Map();
5634
- const keyOf = (exe) => String(exe || "").toLowerCase().replace(/[\\/]/g, "\\");
5635
- const push = (exe, how) => {
5636
- const full = expandEnvVars(exe, env).trim();
5637
- if (!/\.exe$/i.test(full)) {
5638
- note(how, "\u6765\u6E90\u7ED9\u51FA\u7684\u4E0D\u662F exe \u8DEF\u5F84: " + full);
5639
- return null;
5640
- }
5641
- const k = keyOf(full);
5642
- const hit = found.get(k);
5643
- if (hit) {
5644
- if (!hit.sources.includes(how)) hit.sources.push(how);
5645
- return hit;
5646
- }
5647
- if (!canExec(full)) {
5648
- note(how, "\u6307\u5411\u7684\u6587\u4EF6\u4E0D\u53EF\u6267\u884C: " + full);
5649
- return null;
5650
- }
5651
- const item = { id: k, name: path2.basename(full).replace(/\.exe$/i, ""), engine: engineOf(full), bin: full, source: how, sources: [how] };
5652
- found.set(k, item);
5653
- note(how, item.name);
5654
- return item;
5531
+ const runOut = ov.runOut || ((bin, args) => exec.runOutAsync(bin, args, { timeoutMs: PROBE_TIMEOUT_MS }));
5532
+ const out = Promise.resolve().then(() => {
5533
+ if (pl === "win32") return proxyWin(runOut, note);
5534
+ if (pl === "darwin") return proxyMac(runOut, note);
5535
+ if (pl === "linux") return proxyLinux(ov.env || process.env, note);
5536
+ return proxyUnknown("unsupported-platform");
5537
+ }).catch((e) => {
5538
+ note(pl + " proxy probe", "error: " + String(e && (e.code || e.message) || e));
5539
+ return proxyUnknown("probe-failed");
5540
+ }).then((v) => {
5541
+ const value = Object.assign({ platform: pl, at: now(), probed: notes }, v);
5542
+ _proxyCache.set(pl, { value });
5543
+ return value;
5544
+ });
5545
+ return out;
5546
+ }
5547
+ function proxyRead() {
5548
+ const hit = _proxyCache.get(process.platform);
5549
+ return hit ? hit.value : null;
5550
+ }
5551
+ function reachWith(deps, host2, port, timeoutMs) {
5552
+ const d = deps || {};
5553
+ const ms = timeoutMs || PROBE_TIMEOUT_MS;
5554
+ const lookup = d.lookup || ((h) => new Promise((resolve, reject) => dns.lookup(h, (err, addr) => err ? reject(err) : resolve(addr))));
5555
+ const connect = d.connect || ((opts) => new Promise((resolve, reject) => {
5556
+ let settled = false;
5557
+ const sock = tls.connect(opts);
5558
+ const fail = (e) => {
5559
+ if (settled) return;
5560
+ settled = true;
5561
+ try {
5562
+ sock.destroy();
5563
+ } catch {
5564
+ }
5565
+ reject(e);
5566
+ };
5567
+ sock.once("secureConnect", () => {
5568
+ if (settled) return;
5569
+ settled = true;
5570
+ try {
5571
+ sock.destroy();
5572
+ } catch {
5573
+ }
5574
+ resolve(true);
5575
+ });
5576
+ sock.once("error", fail);
5577
+ sock.setTimeout(ms, () => fail(Object.assign(new Error("tls-timeout"), { code: "ETIMEDOUT" })));
5578
+ }));
5579
+ const started = Date.now();
5580
+ const verdict = (ok, stage, detail) => ({ ok, stage, host: host2, port, detail, at: started });
5581
+ return lookup(host2).then((addr) => connect({ host: addr, port, servername: host2, timeout: ms })).then(() => verdict(true, "tls", "TLS \u63E1\u624B\u5B8C\u6210")).catch((e) => {
5582
+ const code = String(e && (e.code || e.message) || e);
5583
+ if (/ENOTFOUND/i.test(code)) return verdict(false, "dns", code);
5584
+ if (/ECONNREFUSED/i.test(code)) return verdict(false, "tcp", code);
5585
+ if (/EPROTO|handshake|ALPR|CERT|UNEXPECTED/i.test(code)) return verdict(false, "tls", code);
5586
+ if (/EAI_AGAIN|getaddrinfo|ESERVFAIL|ENODATA/i.test(code)) return verdict(null, "dns", code);
5587
+ if (/ETIMEDOUT|ECONNRESET|EHOSTUNREACH|ENETUNREACH|timeout/i.test(code)) return verdict(null, "tcp", code);
5588
+ return verdict(null, "tcp", code);
5589
+ });
5590
+ }
5591
+ var _reachCache = /* @__PURE__ */ new Map();
5592
+ function reach(host2, o) {
5593
+ const ov = o || {};
5594
+ const h = String(host2 || "").toLowerCase();
5595
+ if (!h) return Promise.resolve({ ok: null, stage: "not-attempted", host: host2 || null, port: null, detail: "\u6CA1\u6709\u53EF\u5224\u5B9A\u7684\u4E3B\u673A", at: Date.now() });
5596
+ const ttl = ov.ttlMs === void 0 ? READ_TTL_MS : ov.ttlMs;
5597
+ const hit = _reachCache.get(h);
5598
+ if (!ov.force && ttl > 0 && hit && Date.now() - hit.value.at < ttl) {
5599
+ return Promise.resolve(Object.assign({}, hit.value, { cached: true }));
5600
+ }
5601
+ const port = ov.port || 443;
5602
+ return reachWith(ov, h, port, ov.timeoutMs).then((v) => {
5603
+ _reachCache.set(h, { value: v });
5604
+ return v;
5605
+ });
5606
+ }
5607
+ function reachRead(host2) {
5608
+ const h = String(host2 || "").toLowerCase();
5609
+ const hit = _reachCache.get(h);
5610
+ return hit ? hit.value : null;
5611
+ }
5612
+ function hosts() {
5613
+ return [..._reachCache.keys()];
5614
+ }
5615
+ function invalidate(host2) {
5616
+ if (host2 === void 0 || host2 === null) {
5617
+ const n = _reachCache.size + _proxyCache.size;
5618
+ _reachCache.clear();
5619
+ _proxyCache.clear();
5620
+ return n;
5621
+ }
5622
+ return _reachCache.delete(String(host2).toLowerCase()) ? 1 : 0;
5623
+ }
5624
+ module2.exports = {
5625
+ PROBE_TIMEOUT_MS,
5626
+ READ_TTL_MS,
5627
+ hostOf,
5628
+ proxy,
5629
+ proxyRead,
5630
+ proxyWin,
5631
+ proxyMac,
5632
+ proxyLinux,
5633
+ reach,
5634
+ reachRead,
5635
+ reachWith,
5636
+ hosts,
5637
+ invalidate
5638
+ };
5639
+ }
5640
+ });
5641
+
5642
+ // src/platform/contract/shell-report.js
5643
+ var require_shell_report = __commonJS({
5644
+ "src/platform/contract/shell-report.js"(exports2, module2) {
5645
+ "use strict";
5646
+ var fs2 = require("node:fs");
5647
+ var path2 = require("node:path");
5648
+ var stateRoot2 = require_state_root();
5649
+ var { maskProxyServer, maskProxySecrets } = require_redact();
5650
+ var SUPPORTED_SCHEMA = 1;
5651
+ var FILE_NAME = "shell-report.json";
5652
+ var MAX_BYTES = 256 * 1024;
5653
+ var MAX_ROWS = 64;
5654
+ function file() {
5655
+ return path2.join(stateRoot2.supervisorDir(), FILE_NAME);
5656
+ }
5657
+ var str = (v) => typeof v === "string" && v.trim() ? maskProxySecrets(v.trim()) : null;
5658
+ var raw = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
5659
+ var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
5660
+ var tri = (v) => v === true || v === false ? v : null;
5661
+ var list = (v) => Array.isArray(v) ? v : [];
5662
+ function nodeView(n) {
5663
+ const o = n && typeof n === "object" ? n : null;
5664
+ if (!o) return null;
5665
+ return { path: str(o.path), binDir: str(o.binDir), version: raw(o.version), min: raw(o.min), ok: tri(o.ok) };
5666
+ }
5667
+ function npmView(n) {
5668
+ const o = n && typeof n === "object" ? n : null;
5669
+ if (!o) return null;
5670
+ return { path: str(o.path), args: list(o.args).map(String), version: raw(o.version), ok: tri(o.ok) };
5671
+ }
5672
+ function prefixView(n) {
5673
+ const o = n && typeof n === "object" ? n : null;
5674
+ if (!o) return null;
5675
+ return { dir: str(o.dir), writable: tri(o.writable), why: str(o.why) };
5676
+ }
5677
+ function registryView(n) {
5678
+ const o = n && typeof n === "object" ? n : null;
5679
+ if (!o) return null;
5680
+ const rows = list(o.probes).slice(0, MAX_ROWS).map((p) => ({
5681
+ url: p && typeof p === "object" ? maskProxyServer(raw(p.url)) : null,
5682
+ ok: p && typeof p === "object" ? tri(p.ok) : null,
5683
+ latencyMs: p && typeof p === "object" ? num(p.latencyMs) : null
5684
+ }));
5685
+ return { best: maskProxyServer(raw(o.best)), latencyMs: num(o.latencyMs), probes: rows, probesTotal: list(o.probes).length };
5686
+ }
5687
+ function recordsView(recs) {
5688
+ const all = list(recs);
5689
+ const rows = all.slice(0, MAX_ROWS).map((r) => {
5690
+ const o = r && typeof r === "object" ? r : {};
5691
+ return {
5692
+ probe: raw(o.probe) || "unknown",
5693
+ source: str(o.source) || "",
5694
+ target: str(o.target) || "",
5695
+ ms: num(o.ms),
5696
+ ok: tri(o.ok),
5697
+ note: str(o.note) || ""
5698
+ };
5699
+ });
5700
+ return { rows, truncated: all.length > MAX_ROWS ? all.length - MAX_ROWS : 0 };
5701
+ }
5702
+ function bodyOf(j) {
5703
+ const recs = recordsView(j.records);
5704
+ const body = {
5705
+ writtenBy: raw(j.writtenBy),
5706
+ schema: num(j.schema),
5707
+ node: nodeView(j.node),
5708
+ npm: npmView(j.npm),
5709
+ prefix: prefixView(j.prefix),
5710
+ registry: registryView(j.registry),
5711
+ records: recs.rows,
5712
+ droppedRecords: recs.truncated
5655
5713
  };
5656
- const progIds = /* @__PURE__ */ new Map();
5657
- const addProgId = (pid, how) => {
5658
- const s = String(pid || "").trim();
5659
- if (!safeRegKeyPart(s)) return;
5660
- if (progIds.get(s) === "userchoice") return;
5661
- progIds.set(s, how);
5714
+ const seen = [body.node, body.npm, body.prefix, body.registry].some((x) => !!x) || body.records.length > 0;
5715
+ return seen ? body : null;
5716
+ }
5717
+ function envelope(p, at, ageMs, reason, body) {
5718
+ return Object.assign({ available: reason === "ok", path: p, at, ageMs, reason }, body || {
5719
+ writtenBy: null,
5720
+ schema: null,
5721
+ node: null,
5722
+ npm: null,
5723
+ prefix: null,
5724
+ registry: null,
5725
+ records: [],
5726
+ droppedRecords: 0
5727
+ });
5728
+ }
5729
+ function read(o) {
5730
+ const now = o && typeof o.now === "function" ? o.now : Date.now;
5731
+ const p = file();
5732
+ let text;
5733
+ try {
5734
+ const st = fs2.statSync(p);
5735
+ if (st.size > MAX_BYTES) return envelope(p, null, null, "unreadable-or-schema-mismatch", null);
5736
+ text = fs2.readFileSync(p, "utf8");
5737
+ } catch (e) {
5738
+ if (e && e.code === "ENOENT") return envelope(p, null, null, "never-written", null);
5739
+ return envelope(p, null, null, "unreadable-or-schema-mismatch", null);
5740
+ }
5741
+ let j;
5742
+ try {
5743
+ j = JSON.parse(text);
5744
+ } catch {
5745
+ return envelope(p, null, null, "unreadable-or-schema-mismatch", null);
5746
+ }
5747
+ if (!j || typeof j !== "object" || Array.isArray(j) || num(j.schema) !== SUPPORTED_SCHEMA) {
5748
+ return envelope(p, null, null, "unreadable-or-schema-mismatch", null);
5749
+ }
5750
+ const at = num(j.at);
5751
+ if (at === null) return envelope(p, null, null, "unreadable-or-schema-mismatch", null);
5752
+ const ageMs = Math.max(0, now() - at);
5753
+ const body = bodyOf(j);
5754
+ if (!body) return envelope(p, at, null, "unreadable-or-schema-mismatch", null);
5755
+ return envelope(p, at, ageMs, "ok", body);
5756
+ }
5757
+ module2.exports = { SUPPORTED_SCHEMA, FILE_NAME, MAX_BYTES, MAX_ROWS, file, read };
5758
+ }
5759
+ });
5760
+
5761
+ // src/platform/os/environment.js
5762
+ var require_environment = __commonJS({
5763
+ "src/platform/os/environment.js"(exports2, module2) {
5764
+ "use strict";
5765
+ var fs2 = require("node:fs");
5766
+ var os2 = require("node:os");
5767
+ var path2 = require("node:path");
5768
+ var stateRoot2 = require_state_root();
5769
+ var { writeAtomic } = require_fs();
5770
+ var { maskProxyServer, maskProxySecrets } = require_redact();
5771
+ var desktop = require_desktop();
5772
+ var detector = require_browser_inventory();
5773
+ var egress = require_egress();
5774
+ var shellReport = require_shell_report();
5775
+ var { engineOf } = detector;
5776
+ var SCHEMA = 2;
5777
+ var FILE_NAME = "environment.json";
5778
+ var FORM_TTL_MS = 6e4;
5779
+ var SECTION_TTL_MS = 6e4;
5780
+ var SHELL_REPORT_TTL_MS = 1e4;
5781
+ var _sources = {};
5782
+ function bind(sources) {
5783
+ _sources = Object.assign({}, _sources, sources || {});
5784
+ return _sources;
5785
+ }
5786
+ function preferenceId() {
5787
+ const f = _sources.preference;
5788
+ if (typeof f !== "function") return null;
5789
+ const v = f();
5790
+ return typeof v === "string" && v.trim() ? v.trim() : null;
5791
+ }
5792
+ function capabilities() {
5793
+ const f = _sources.capabilities;
5794
+ if (typeof f !== "function") return null;
5795
+ return f() || null;
5796
+ }
5797
+ function identity() {
5798
+ let user = null;
5799
+ try {
5800
+ user = os2.userInfo().username;
5801
+ } catch {
5802
+ user = process.env.USER || process.env.USERNAME || null;
5803
+ }
5804
+ return {
5805
+ platform: process.platform,
5806
+ arch: process.arch,
5807
+ hostname: os2.hostname(),
5808
+ user: user || null,
5809
+ home: os2.homedir(),
5810
+ node: process.version
5662
5811
  };
5663
- let defaultId = null;
5664
- let defaultSource = null;
5665
- const DEF_RANK = { userchoice: 1, "scheme-association": 2, "only-installed": 9 };
5666
- let defRank = 99;
5667
- const setDefault = (item, how) => {
5668
- const r = DEF_RANK[how] === void 0 ? 99 : DEF_RANK[how];
5669
- if (item && r < defRank) {
5670
- defRank = r;
5671
- defaultId = item.id;
5672
- defaultSource = how;
5673
- }
5812
+ }
5813
+ function paths() {
5814
+ return { root: stateRoot2.root(), supervisor: stateRoot2.supervisorDir(), shell: stateRoot2.shellDir() };
5815
+ }
5816
+ function normalizeInventory(inv, platform) {
5817
+ const list = inv && Array.isArray(inv.browsers) ? inv.browsers : [];
5818
+ const defaultId = inv && inv.defaultId ? inv.defaultId : null;
5819
+ return {
5820
+ platform: inv && inv.platform || platform || process.platform,
5821
+ cached: !!(inv && inv.cached),
5822
+ at: inv && inv.at !== void 0 ? inv.at : null,
5823
+ defaultId,
5824
+ defaultSource: inv && inv.defaultSource || null,
5825
+ browsers: list.map((b) => ({
5826
+ id: b.id,
5827
+ name: b.name,
5828
+ bin: b.bin,
5829
+ engine: b.engine || engineOf(b.bin),
5830
+ baseArgs: b.baseArgs || [],
5831
+ sources: b.sources && b.sources.length ? b.sources : [b.source || "unknown"],
5832
+ isDefault: !!defaultId && b.id === defaultId
5833
+ })),
5834
+ probed: inv && inv.probed || []
5674
5835
  };
5675
- const cmdToExe = (raw) => raw ? exeFromCmdLine(expandEnvVars(raw, env)) : null;
5676
- const uc = regValue(runOut, note, "HKCU\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice", "ProgId");
5677
- if (uc) addProgId(uc, "userchoice");
5678
- for (const root of ["HKCU\\Software\\Classes", "HKLM\\Software\\Classes"]) {
5679
- const pid = regValue(runOut, note, root + "\\https");
5680
- if (pid) addProgId(pid, "scheme-association");
5681
- const exe = cmdToExe(regValue(runOut, note, root + "\\https\\shell\\open\\command"));
5682
- if (exe) setDefault(push(exe, "scheme-association"), "scheme-association");
5836
+ }
5837
+ function browsers(o) {
5838
+ const ov = o || {};
5839
+ const pl = ov.platform || process.platform;
5840
+ if (ov.inventory) return normalizeInventory(ov.inventory, pl);
5841
+ const resolve = typeof ov.resolveInventory === "function" ? ov.resolveInventory : ((platform, deps) => detector.inventory(platform, deps));
5842
+ return normalizeInventory(resolve(pl, ov.resolveDeps || {}), pl);
5843
+ }
5844
+ var ENGINE_RANK = { chromium: 0, firefox: 1, other: 2 };
5845
+ function rankCandidates(list) {
5846
+ const rankOf = (b) => {
5847
+ const r = ENGINE_RANK[b && b.engine];
5848
+ return r === void 0 ? ENGINE_RANK.other : r;
5849
+ };
5850
+ return (Array.isArray(list) ? list : []).slice().sort((a, b) => {
5851
+ if (rankOf(a) !== rankOf(b)) return rankOf(a) - rankOf(b);
5852
+ const ia = String(a && a.id || "");
5853
+ const ib = String(b && b.id || "");
5854
+ return ia < ib ? -1 : ia > ib ? 1 : 0;
5855
+ });
5856
+ }
5857
+ function pickLauncher(platform, inv, preference) {
5858
+ const list = inv && Array.isArray(inv.browsers) ? inv.browsers : [];
5859
+ const byId = /* @__PURE__ */ new Map();
5860
+ for (const b of list) byId.set(b.id, b);
5861
+ const wanted = preference === void 0 ? preferenceId() : typeof preference === "string" && preference ? preference : null;
5862
+ const stale = wanted !== null && !byId.has(wanted);
5863
+ const out = (browser, how) => ({ browser, how, wanted, stale });
5864
+ if (wanted !== null && byId.has(wanted)) return out(byId.get(wanted), "user-preference");
5865
+ const sys = inv && inv.defaultId ? byId.get(inv.defaultId) : null;
5866
+ if (sys) return out(sys, inv.defaultSource || "default");
5867
+ if (list.length === 1) return out(list[0], "only-installed");
5868
+ if (list.length > 1) return out(rankCandidates(list)[0], "candidate-rank");
5869
+ return out(null, "none-found");
5870
+ }
5871
+ var _sections = /* @__PURE__ */ new Map();
5872
+ var _reads = /* @__PURE__ */ new Map();
5873
+ var SECTION_ORDER = ["runtime", "shell", "dsh", "browsers", "session", "egress", "capabilities", "preference", "pick", "startup"];
5874
+ var SYNC_DIMS = ["browsers", "session", "capabilities", "preference", "pick"];
5875
+ function registerSection(id, def) {
5876
+ if (!id || !def || typeof def.probe !== "function") throw new Error("registerSection(" + id + ") \u9700\u8981 probe \u51FD\u6570");
5877
+ if (SYNC_DIMS.includes(id)) throw new Error("\u7EF4\u5EA6 " + id + " \u7531\u8868\u5355\u6BCF\u62CD\u81EA\u88C5\uFF0C\u4E0D\u63A5\u6CE8\u518C");
5878
+ const d = { id, label: def.label || id, probe: def.probe, ttlMs: def.ttlMs === void 0 ? SECTION_TTL_MS : def.ttlMs, source: def.source || "registered" };
5879
+ _sections.set(id, d);
5880
+ _reads.delete(id);
5881
+ return d;
5882
+ }
5883
+ function unregisterSection(id) {
5884
+ _sections.delete(id);
5885
+ _reads.delete(id);
5886
+ }
5887
+ function section(id) {
5888
+ return _reads.get(id) || null;
5889
+ }
5890
+ function sectionData(id) {
5891
+ const r = _reads.get(id);
5892
+ return r ? r.data || null : null;
5893
+ }
5894
+ function proxyDataOf(p) {
5895
+ return p ? { state: p.state, server: maskProxyServer(p.server), pac: p.pac || null, source: p.source, cached: p.cached === true } : null;
5896
+ }
5897
+ function egressData(proxyReading) {
5898
+ const targets = {};
5899
+ for (const h of egress.hosts()) {
5900
+ const r = egress.reachRead(h);
5901
+ if (r) targets[h] = { ok: r.ok === null ? null : r.ok === true, stage: r.stage, detail: r.detail, at: r.at };
5902
+ }
5903
+ const p = proxyReading || egress.proxyRead();
5904
+ const probed = [];
5905
+ if (p) {
5906
+ for (const row of p.probed || []) probed.push({ source: row.source, detail: maskProxySecrets(String(row.detail || "")) });
5907
+ probed.push({ source: p.source, detail: "proxy=" + p.state + (p.server ? " (" + maskProxyServer(p.server) + ")" : "") + (p.cached ? " \u590D\u7528" : "") });
5908
+ } else {
5909
+ probed.push({ source: "egress", detail: "\u7CFB\u7EDF\u4EE3\u7406\u672A\u8BFB\uFF08\u672C\u7EF4\u5EA6\u5C1A\u672A\u5237\u65B0\uFF09" });
5683
5910
  }
5684
- for (const name of regSubkeys(runOut, note, "HKLM\\SOFTWARE\\Clients\\StartMenuInternet")) {
5685
- addProgId(name, "startmenu-catalog");
5686
- const exe = cmdToExe(regValue(runOut, note, "HKLM\\SOFTWARE\\Clients\\StartMenuInternet\\" + name + "\\shell\\open\\command")) || winExeOfProgId(runOut, note, name, env);
5687
- if (exe) push(exe, "startmenu-catalog");
5911
+ for (const h of Object.keys(targets)) {
5912
+ const t = targets[h];
5913
+ probed.push({ source: "reach:" + h, detail: (t.ok === null ? "unknown" : t.ok ? "ok" : "no-route") + "@" + t.stage + " " + t.detail });
5688
5914
  }
5689
- for (const root of ["HKLM\\SOFTWARE", "HKCU\\SOFTWARE"]) {
5690
- for (const capPath of regValueTargets(runOut, note, root + "\\RegisteredApplications")) {
5691
- const https = regValue(runOut, note, capPath + "\\UrlAssociations\\https");
5692
- const sid = regValue(runOut, note, capPath, "StartMenuInternet");
5693
- const pid = https || sid;
5694
- if (pid) addProgId(pid, "registered-apps");
5695
- }
5915
+ return {
5916
+ at: p ? p.at : null,
5917
+ proxy: proxyDataOf(p),
5918
+ targets,
5919
+ probed
5920
+ };
5921
+ }
5922
+ registerSection("egress", {
5923
+ label: "\u51FA\u7F51\u6761\u4EF6",
5924
+ source: "self",
5925
+ probe: (o) => {
5926
+ const ov = o || {};
5927
+ const force = ov.force === true;
5928
+ const hosts = Array.isArray(ov.hosts) ? ov.hosts : egress.hosts();
5929
+ const reachOpts = { force, timeoutMs: ov.timeoutMs, lookup: ov.lookup, connect: ov.connect, ttlMs: ov.ttlMs };
5930
+ return Promise.all([egress.proxy(Object.assign({}, ov, { force }))].concat(hosts.map((h) => egress.reach(h, reachOpts)))).then(([p]) => egressData(p));
5696
5931
  }
5697
- for (const exe of WIN_APP_PATHS) {
5698
- for (const root of ["HKCU", "HKLM"]) {
5699
- const v = regValue(runOut, note, root + "\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" + exe);
5700
- if (v && push(v, "app-paths")) break;
5932
+ });
5933
+ registerSection("shell", {
5934
+ label: "\u684C\u9762\u58F3\u6240\u89C1\uFF08Node/npm/\u955C\u50CF\u6E90/\u5168\u5C40\u524D\u7F00\uFF09",
5935
+ source: "shell",
5936
+ ttlMs: SHELL_REPORT_TTL_MS,
5937
+ probe: () => shellReport.read()
5938
+ });
5939
+ async function probeSection(id, o) {
5940
+ const def = _sections.get(id);
5941
+ if (!def) return null;
5942
+ const ov = o || {};
5943
+ const now = typeof ov.now === "function" ? ov.now : Date.now;
5944
+ try {
5945
+ const data = await def.probe(ov);
5946
+ _reads.set(id, { at: now(), source: def.source, label: def.label, state: data ? "ok" : "empty", data: data || null, error: null });
5947
+ } catch (e) {
5948
+ _reads.set(id, { at: now(), source: def.source, label: def.label, state: "error", data: null, error: String(e && (e.code || e.message) || e) });
5949
+ }
5950
+ return _reads.get(id);
5951
+ }
5952
+ function staleOf(id, now, force) {
5953
+ if (force) return true;
5954
+ const def = _sections.get(id);
5955
+ const read = _reads.get(id);
5956
+ if (!def || !read) return true;
5957
+ return now - read.at >= def.ttlMs;
5958
+ }
5959
+ async function refresh(o) {
5960
+ const ov = o || {};
5961
+ const now = typeof ov.now === "function" ? ov.now : Date.now;
5962
+ const ids = Array.isArray(ov.only) && ov.only.length ? ov.only : [..._sections.keys()];
5963
+ await Promise.all(ids.filter((id) => _sections.has(id) && staleOf(id, now(), ov.force)).map((id) => probeSection(id, ov)));
5964
+ return form(Object.assign({}, ov, { force: true }));
5965
+ }
5966
+ async function checkEgress(url, o) {
5967
+ const ov = o || {};
5968
+ const host2 = egress.hostOf(url);
5969
+ let data = ov.egress === void 0 ? null : ov.egress;
5970
+ if (!data) {
5971
+ try {
5972
+ const hosts = new Set(egress.hosts());
5973
+ if (host2) {
5974
+ await egress.reach(host2, ov);
5975
+ hosts.add(host2);
5976
+ }
5977
+ const read = await probeSection("egress", Object.assign({}, ov, { hosts: [...hosts] }));
5978
+ data = read && read.data || sectionData("egress");
5979
+ } catch {
5980
+ data = sectionData("egress");
5701
5981
  }
5702
5982
  }
5703
- for (const [pid, how] of progIds) {
5704
- const exe = winExeOfProgId(runOut, note, pid, env);
5705
- if (!exe) continue;
5706
- const item = push(exe, how);
5707
- if (how === "userchoice") setDefault(item, "userchoice");
5983
+ const v = coldProfileViable(data, host2);
5984
+ return Object.assign({}, v, {
5985
+ host: host2,
5986
+ proxy: data && data.proxy ? data.proxy.state : "unknown",
5987
+ at: data ? data.at || null : null
5988
+ });
5989
+ }
5990
+ function coldProfileViable(eg, host2) {
5991
+ if (!eg) return { viable: null, basis: "egress-unprobed", detail: "\u51FA\u7F51\u6761\u4EF6\u5C1A\u672A\u63A2\u6D4B\uFF0C\u9694\u79BB\u7A97\u53E3\u6309\u539F\u6863\u6253\u5F00" };
5992
+ const t = host2 && eg.targets ? eg.targets[host2] : null;
5993
+ const reached = t ? t.ok : null;
5994
+ const proxyState = eg.proxy ? eg.proxy.state : "unknown";
5995
+ if (reached === true) {
5996
+ return { viable: true, basis: "target-reachable", detail: host2 + " \u76F4\u8FDE\u53EF\u8FBE\uFF08" + t.stage + "\uFF09" };
5708
5997
  }
5709
- if (found.size === 1) setDefault([...found.values()][0], "only-installed");
5710
- return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5998
+ if (reached === null) {
5999
+ return { viable: null, basis: "egress-undetermined", detail: host2 + " \u7684\u901A\u8DEF\u5224\u5B9A\u6CA1\u6709\u7ED9\u51FA\u7B54\u6848\uFF08" + (t ? t.stage + " " + t.detail : "\u65E0\u8BFB\u6570") + "\uFF09" };
6000
+ }
6001
+ if (proxyState === "on") {
6002
+ return { viable: true, basis: "cold-profile-inherits-proxy", detail: host2 + " \u76F4\u8FDE\u4E0D\u901A\uFF08" + t.stage + "\uFF09\uFF0C\u7CFB\u7EDF\u7EA7\u4EE3\u7406\u5728\u7528\uFF0C\u51B7\u6863\u6848\u540C\u4E00\u6761\u8DEF" };
6003
+ }
6004
+ if (proxyState === "unknown") {
6005
+ return { viable: null, basis: "proxy-unreadable", detail: host2 + " \u76F4\u8FDE\u4E0D\u901A\uFF08" + t.stage + "\uFF09\uFF0C\u4F46\u4EE3\u7406\u8BFB\u6570\u53D6\u4E0D\u5230\uFF0C\u6309\u5224\u4E0D\u51FA\u5904\u7406" };
6006
+ }
6007
+ return {
6008
+ viable: false,
6009
+ basis: "cold-profile-blocked",
6010
+ detail: host2 + " \u76F4\u8FDE\u4E0D\u901A\uFF08" + t.stage + " " + t.detail + "\uFF09\u4E14\u7CFB\u7EDF\u6CA1\u6709\u5728\u7528\u4EE3\u7406\uFF0C\u51B7\u6863\u6848\u7A97\u53E3\u5FC5\u7136\u7A7A\u767D"
6011
+ };
5711
6012
  }
5712
- function regValueTargets(runOut, note, key) {
5713
- const out = runOut("reg.exe", ["query", key]);
5714
- if (!out) {
5715
- note(key, "unreadable");
5716
- return [];
6013
+ function snapshotPath() {
6014
+ return path2.join(stateRoot2.supervisorDir(), FILE_NAME);
6015
+ }
6016
+ function readSnapshot() {
6017
+ try {
6018
+ const doc = JSON.parse(fs2.readFileSync(snapshotPath(), "utf8"));
6019
+ return doc && doc.schema === SCHEMA ? doc : null;
6020
+ } catch {
6021
+ return null;
5717
6022
  }
5718
- const paths = [];
5719
- for (const line of String(out).split(/\r?\n/)) {
5720
- const m = line.trim().match(/REG_SZ\s+(.*)/);
5721
- if (!m) continue;
5722
- const rel = m[1].trim();
5723
- if (!safeRegKeyPart(rel)) continue;
5724
- const root = key.slice(0, key.indexOf("\\"));
5725
- paths.push(root + "\\" + rel);
6023
+ }
6024
+ function lastSnapshot(o) {
6025
+ const now = o && typeof o.now === "function" ? o.now : Date.now;
6026
+ const p = snapshotPath();
6027
+ const doc = readSnapshot();
6028
+ if (doc) {
6029
+ const at = typeof doc.at === "number" ? doc.at : null;
6030
+ return { available: true, path: p, at, ageMs: at === null ? null : Math.max(0, now() - at), reason: "ok", data: doc };
5726
6031
  }
5727
- note(key, paths.length ? paths.length + " \u4E2A\u80FD\u529B\u8DEF\u5F84" : "\u65E0\u503C");
5728
- return paths;
6032
+ return {
6033
+ available: false,
6034
+ path: p,
6035
+ at: null,
6036
+ ageMs: null,
6037
+ reason: fs2.existsSync(p) ? "unreadable-or-schema-mismatch" : "never-written",
6038
+ data: null
6039
+ };
5729
6040
  }
5730
- var MAC_JXA_LIST = [
5731
- "ObjC.import('Foundation');",
5732
- "var ws=$.NSWorkspace.sharedWorkspace;",
5733
- "var u=$.NSURL.URLWithString('https://dsh.local/probe');",
5734
- "var out=[];var def='';",
5735
- "try{var d=ws.URLForApplicationToOpenURL(u); if(!d.isNil()) def=ObjC.unwrap(d.path)||'';}catch(e){}",
5736
- "var list=null;",
5737
- "try{list=ws.urlsForApplicationsToOpenURL(u);}catch(e){}",
5738
- "if(list===null||typeof list.count!=='number'){if(def){out.push(def+'\\t\\t1');}}else{",
5739
- "var n=Number(list.count);",
5740
- "for(var i=0;i<n;i++){",
5741
- "var p=ObjC.unwrap(list.objectAtIndex(i).path)||''; if(!p) continue;",
5742
- "var exe='';var bid='';",
5743
- "var b=$.NSBundle.bundleWithPath(p);",
5744
- "if(!b.isNil()){exe=ObjC.unwrap(b.executablePath)||'';bid=ObjC.unwrap(b.bundleIdentifier)||'';}",
5745
- "out.push((exe||p)+'\\t'+bid+'\\t'+(p&&def&&p===def?1:0));}",
5746
- "}",
5747
- // 末句必须是裸表达式:osascript 取的是最后一条**语句**的值,if/else 语句不产结果,
5748
- // 写在分支里会让整段查询输出空字符串(表现为「系统未报任何可用应用」)。
5749
- "out.join('\\n');"
5750
- ].join("");
5751
- function probeMac(d) {
5752
- const { runOut, canExec } = d;
5753
- const notes = [];
5754
- const note = (source, detail) => {
5755
- notes.push({ source, detail });
6041
+ var _formCache = null;
6042
+ function form(o) {
6043
+ const ov = o || {};
6044
+ const now = typeof ov.now === "function" ? ov.now : (() => Date.now());
6045
+ const stamp = now();
6046
+ const ttl = ov.ttlMs === void 0 ? FORM_TTL_MS : ov.ttlMs;
6047
+ if (!ov.force && _formCache && stamp - _formCache.at < ttl) {
6048
+ return Object.assign({}, _formCache.value, { cached: true });
6049
+ }
6050
+ const inv = browsers(ov);
6051
+ const caps = capabilities();
6052
+ const session = desktop.describe();
6053
+ const prefId = preferenceId();
6054
+ const prefEntry = prefId ? inv.browsers.find((b) => b.id === prefId) || null : null;
6055
+ const preference = {
6056
+ id: prefId,
6057
+ configured: !!prefId,
6058
+ matched: !!prefEntry,
6059
+ browser: prefEntry ? { id: prefEntry.id, name: prefEntry.name, engine: prefEntry.engine } : null,
6060
+ reason: !prefId ? "not-set" : prefEntry ? "matched" : "stale"
5756
6061
  };
5757
- const out = runOut("osascript", ["-l", "JavaScript", "-e", MAC_JXA_LIST]);
5758
- if (!out || !out.trim()) {
5759
- note("launchservices", "\u67E5\u8BE2\u65E0\u8F93\u51FA\uFF08\u65E7\u7CFB\u7EDF\u6216\u88AB\u6743\u9650\u62E6\u622A\uFF09");
5760
- return { browsers: [], defaultId: null, defaultSource: null, probed: notes };
6062
+ const pick = pickLauncher(inv.platform, inv, prefId);
6063
+ const probed = inv.probed.map((p) => ({ section: "browsers", source: p.source, detail: p.detail }));
6064
+ probed.push({ section: "session", source: "desktop", detail: session.reason + (session.available ? "\uFF08\u53EF\u7528\uFF09" : "\uFF08\u4E0D\u53EF\u7528\uFF09") });
6065
+ probed.push({
6066
+ section: "capabilities",
6067
+ source: "profile",
6068
+ detail: caps ? "openBrowser=" + (caps.openBrowser === true) : "\u672A\u7ED1\u5B9A\u80FD\u529B\u77E9\u9635\uFF08\u7531 platform/os/index.js \u88C5\u914D\u671F\u6CE8\u5165\uFF09"
6069
+ });
6070
+ probed.push({
6071
+ section: "preference",
6072
+ source: "config",
6073
+ detail: preference.reason === "not-set" ? "\u672A\u8BBE\u7F6E\uFF0C\u6309\u7CFB\u7EDF\u9ED8\u8BA4\u6216\u5019\u9009\u6B21\u5E8F\u5206\u53D1" : preference.reason === "matched" ? "\u5DF2\u9009 " + preference.browser.name : "\u504F\u597D\u6240\u6307\u5DF2\u4E0D\u5728\u5019\u9009\u6E05\u5355: " + prefId
6074
+ });
6075
+ probed.push({
6076
+ section: "pick",
6077
+ source: "form",
6078
+ detail: pick.how + (pick.browser ? "\uFF08" + pick.browser.name + "\uFF09" : "\uFF08\u65E0\u5019\u9009\uFF09") + (pick.stale ? "\uFF0C\u504F\u597D\u5DF2\u5931\u6548\u9700\u91CD\u9009" : "")
6079
+ });
6080
+ const sections = {};
6081
+ const syncDims = {
6082
+ browsers: { at: stamp, state: inv.browsers.length ? "ok" : "empty", count: inv.browsers.length, defaultSource: inv.defaultSource },
6083
+ session: { at: stamp, state: session.available ? "ok" : "missing", reason: session.reason },
6084
+ capabilities: { at: stamp, state: caps ? "ok" : "unbound", openBrowser: caps ? caps.openBrowser === true : null },
6085
+ preference: { at: stamp, state: preference.reason === "matched" ? "ok" : preference.configured ? "stale" : "unset", id: preference.id },
6086
+ pick: { at: stamp, state: pick.browser ? "ok" : "empty", how: pick.how, id: pick.browser ? pick.browser.id : null }
6087
+ };
6088
+ const dimOf = (id) => {
6089
+ const def = _sections.get(id);
6090
+ const read = _reads.get(id);
6091
+ if (read && !syncDims[id]) {
6092
+ return { label: def ? def.label : id, at: read.at, source: read.source, state: read.state, data: read.data, error: read.error || null };
6093
+ }
6094
+ const s = syncDims[id];
6095
+ if (s) return Object.assign({ label: id, source: "self" }, s);
6096
+ return { label: def ? def.label : id, at: null, source: def ? def.source : "registered", state: "pending", data: null, error: null };
6097
+ };
6098
+ const ids = SECTION_ORDER.concat([..._sections.keys()].filter((id) => !SECTION_ORDER.includes(id)));
6099
+ for (const id of ids) {
6100
+ if (sections[id]) continue;
6101
+ const entry = dimOf(id);
6102
+ sections[id] = entry;
6103
+ if (entry.state === "pending") {
6104
+ probed.push({ section: id, source: "form", detail: "\u672A\u5237\u65B0\uFF08\u7B49\u542F\u52A8\u88C5\u914D\u6216\u9762\u677F force \u5237\u65B0\u8865\u62CD\uFF09" });
6105
+ } else if (entry.state === "error") {
6106
+ probed.push({ section: id, source: "probe", detail: id + " \u63A2\u6D4B\u5931\u8D25: " + entry.error });
6107
+ }
6108
+ }
6109
+ const egressRows = ((sections.egress || {}).data || {}).probed || [];
6110
+ for (const row of egressRows) probed.push({ section: "egress", source: row.source, detail: row.detail });
6111
+ const sh = (sections.shell || {}).data;
6112
+ if (sh) {
6113
+ probed.push({
6114
+ section: "shell",
6115
+ source: "shell-report:" + (sh.reason || "?"),
6116
+ detail: sh.available === true ? "\u58F3\u5DF2\u4E0A\u62A5\uFF08\u5199\u5165\u8005 " + (sh.writtenBy || "\u672A\u7F72\u540D") + "\uFF0Cschema " + sh.schema + "\uFF0C\u8DDD\u4ECA " + (sh.ageMs === null ? "\u672A\u8BFB\u51FA" : sh.ageMs) + " \u6BEB\u79D2\uFF0C\u660E\u7EC6 " + (sh.records || []).length + " \u6761" + (sh.droppedRecords ? "\uFF0C\u622A\u65AD " + sh.droppedRecords + " \u6761" : "") + "\uFF09" : sh.reason === "never-written" ? "\u8FD9\u53F0\u673A\u5668\u7684\u58F3\u8FD8\u6CA1\u62A5\u8FC7\uFF08\u5185\u6838\u72EC\u7ACB\u8DD1\u6216\u8001\u7248\u672C\u58F3\u65F6\u662F\u5E38\u6001\uFF0C\u4E0D\u662F\u6545\u969C\uFF09" : "\u58F3\u62A5\u7684\u6587\u4EF6\u8BFB\u4E0D\u51FA\u6216\u7248\u672C\u4E0D\u7B26\uFF0C\u5F97\u67E5\uFF1A" + (sh.path || "?")
6117
+ });
5761
6118
  }
5762
- const keyOf = (exe) => String(exe || "").toLowerCase();
5763
- const found = /* @__PURE__ */ new Map();
5764
- let defaultId = null;
5765
- let defaultSource = null;
5766
- for (const line of out.trim().split("\n")) {
5767
- const parts = line.split(" ");
5768
- const bin = (parts[0] || "").trim();
5769
- if (!bin) continue;
5770
- if (!canExec(bin)) {
5771
- note("launchservices", "\u4E0D\u53EF\u6267\u884C: " + bin);
5772
- continue;
5773
- }
5774
- const k = keyOf(bin);
5775
- if (!found.has(k)) found.set(k, { id: k, name: path2.basename(bin), engine: engineOf(bin), bin, source: "launchservices", sources: ["launchservices"], isDefault: false });
5776
- if (parts[2] === "1") {
5777
- found.get(k).isDefault = true;
5778
- if (!defaultId) {
5779
- defaultId = k;
5780
- defaultSource = "launchservices";
5781
- }
6119
+ const value = {
6120
+ schema: SCHEMA,
6121
+ at: stamp,
6122
+ platform: inv.platform,
6123
+ identity: identity(),
6124
+ paths: paths(),
6125
+ session,
6126
+ capabilities: caps,
6127
+ preference,
6128
+ default: inv.defaultId ? { id: inv.defaultId, source: inv.defaultSource } : null,
6129
+ browsers: inv.browsers,
6130
+ pick: { how: pick.how, id: pick.browser ? pick.browser.id : null, name: pick.browser ? pick.browser.name : null, wanted: pick.wanted, stale: pick.stale },
6131
+ sections,
6132
+ probed
6133
+ };
6134
+ const snapshot = { path: snapshotPath(), written: false, error: null };
6135
+ if (ov.persist === true) {
6136
+ try {
6137
+ writeAtomic(snapshot.path, JSON.stringify(value, null, 2) + "\n", { mode: 384 });
6138
+ snapshot.written = true;
6139
+ } catch (e) {
6140
+ snapshot.error = String(e && e.message || e);
5782
6141
  }
6142
+ probed.push({
6143
+ section: "snapshot",
6144
+ source: FILE_NAME,
6145
+ detail: snapshot.written ? "\u5DF2\u843D\u76D8 " + snapshot.path : "\u843D\u76D8\u5931\u8D25: " + snapshot.error
6146
+ });
5783
6147
  }
5784
- note("launchservices", found.size ? found.size + " \u4E2A\u53EF\u6253\u5F00 https \u7684\u5E94\u7528" : "\u7CFB\u7EDF\u672A\u62A5\u4EFB\u4F55\u53EF\u7528\u5E94\u7528");
5785
- if (!defaultId && found.size === 1) {
5786
- defaultId = [...found.keys()][0];
5787
- defaultSource = "only-installed";
6148
+ value.snapshot = snapshot;
6149
+ _formCache = { at: stamp, value };
6150
+ return Object.assign({ cached: false }, value);
6151
+ }
6152
+ function invalidate(platform, only) {
6153
+ _formCache = null;
6154
+ const list = only === void 0 ? null : Array.isArray(only) ? only : [only];
6155
+ if (!list || list.includes("egress")) egress.invalidate();
6156
+ for (const id of list || [..._reads.keys()]) {
6157
+ const r = _reads.get(id);
6158
+ if (r) r.at = 0;
5788
6159
  }
5789
- return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
6160
+ return detector.invalidate(platform);
5790
6161
  }
5791
- var PJ = path2.posix.join;
5792
- function desktopDirs(env, home) {
5793
- const dataHome = env.XDG_DATA_HOME || PJ(home, ".local", "share");
5794
- const dataDirs = String(env.XDG_DATA_DIRS || "/usr/local/share:/usr/share").split(":").filter(Boolean);
5795
- const dirs = [[dataHome, "xdg"], ...dataDirs.map((p) => [p, "xdg"])];
5796
- dirs.push([PJ(home, ".local", "share"), "xdg"]);
5797
- dirs.push(["/var/lib/flatpak/exports/share", "flatpak"]);
5798
- dirs.push([PJ(home, ".local", "share", "flatpak", "exports", "share"), "flatpak"]);
5799
- dirs.push(["/var/lib/snapd/desktop", "snap"]);
5800
- const seen = [];
5801
- return dirs.map(([root, kind]) => ({ dir: PJ(root, "applications"), kind })).filter((x) => {
5802
- if (seen.includes(x.dir)) return false;
5803
- seen.push(x.dir);
5804
- return true;
5805
- });
6162
+ function checkPreference(value, form2) {
6163
+ const candidates = (form2 && form2.browsers || []).map((b) => ({
6164
+ id: b.id,
6165
+ name: b.name,
6166
+ engine: b.engine,
6167
+ isDefault: b.isDefault === true
6168
+ }));
6169
+ const id = typeof value === "string" ? value.trim() : "";
6170
+ if (!id) return { ok: true, id: null, browser: null, candidates };
6171
+ const hit = candidates.find((c) => c.id === id);
6172
+ if (!hit) {
6173
+ return { ok: false, id: null, browser: null, candidates, error: "\u8BE5\u6D4F\u89C8\u5668\u4E0D\u5728\u672C\u673A\u5019\u9009\u6E05\u5355\u91CC\uFF08\u53EF\u80FD\u5DF2\u5378\u8F7D\u6216\u8DEF\u5F84\u5931\u6548\uFF09\uFF0C\u8BF7\u5148\u5237\u65B0\u73AF\u5883\u8868\u5355" };
6174
+ }
6175
+ return { ok: true, id: hit.id, browser: hit, candidates };
5806
6176
  }
5807
- function browserFromDesktop(text, kind, file) {
5808
- let inMain = false;
5809
- const got = {};
5810
- for (const line of String(text).split(/\r?\n/)) {
5811
- if (/^\[Desktop Entry\]\s*$/.test(line)) {
5812
- inMain = true;
5813
- continue;
6177
+ module2.exports = {
6178
+ SCHEMA,
6179
+ FILE_NAME,
6180
+ FORM_TTL_MS,
6181
+ SECTION_TTL_MS,
6182
+ SECTION_ORDER,
6183
+ SYNC_DIMS,
6184
+ bind,
6185
+ preferenceId,
6186
+ capabilities,
6187
+ identity,
6188
+ paths,
6189
+ browsers,
6190
+ normalizeInventory,
6191
+ rankCandidates,
6192
+ pickLauncher,
6193
+ checkPreference,
6194
+ registerSection,
6195
+ unregisterSection,
6196
+ section,
6197
+ sectionData,
6198
+ refresh,
6199
+ coldProfileViable,
6200
+ checkEgress,
6201
+ form,
6202
+ snapshotPath,
6203
+ readSnapshot,
6204
+ lastSnapshot,
6205
+ invalidate
6206
+ };
6207
+ }
6208
+ });
6209
+
6210
+ // src/platform/os/process.js
6211
+ var require_process = __commonJS({
6212
+ "src/platform/os/process.js"(exports2, module2) {
6213
+ "use strict";
6214
+ var ex2 = require_exec();
6215
+ var isWindows = process.platform === "win32";
6216
+ function signalProcess(pid, sig) {
6217
+ if (!Number.isInteger(pid) || pid <= 0) return;
6218
+ if (isWindows) {
6219
+ try {
6220
+ process.kill(pid, sig);
6221
+ } catch {
6222
+ }
6223
+ return;
6224
+ }
6225
+ try {
6226
+ process.kill(-pid, sig);
6227
+ } catch {
6228
+ try {
6229
+ process.kill(pid, sig);
6230
+ } catch {
5814
6231
  }
5815
- if (/^\[/.test(line)) break;
5816
- if (!inMain) continue;
5817
- const m = line.match(/^(Exec|Name|TryExec|Categories)=(.*)$/);
5818
- if (!m) continue;
5819
- if (m[1] === "Name" && got.Name) continue;
5820
- got[m[1]] = m[2].trim();
5821
6232
  }
5822
- if (!got.Exec) return null;
5823
- const categories = String(got.Categories || "");
5824
- const parsed = parseExecLine(got.Exec);
5825
- if (!parsed) return null;
5826
- const engine = engineOf(parsed.bin);
5827
- const claimsBrowser = /(^|;)WebBrowser(;|$)/.test(categories);
5828
- if (!claimsBrowser && engine === "other") return null;
5829
- if (engine === "other") return null;
5830
- return {
5831
- // id 恒为归一后的可执行文件路径:defaultId 靠它在清单里定位条目,三平台同一契约
5832
- id: parsed.bin.toLowerCase(),
5833
- name: got.Name || path2.basename(file, ".desktop"),
5834
- engine,
5835
- bin: parsed.bin,
5836
- baseArgs: parsed.baseArgs,
5837
- source: kind,
5838
- desktopFile: file
5839
- };
5840
6233
  }
5841
- function resolveLinuxBin(bin, canExec, env) {
5842
- const b = String(bin || "").trim();
5843
- if (!b) return null;
5844
- if (b.startsWith("/")) return canExec(b) ? b : null;
5845
- if (b.includes("/")) return null;
5846
- for (const dir of String(env.PATH || "").split(":")) {
5847
- if (!dir) continue;
5848
- const p = PJ(dir.replace(/\/+$/, ""), b);
5849
- if (canExec(p)) return p;
6234
+ function killTree(pid, sig, cb, opts) {
6235
+ if (!Number.isInteger(pid) || pid <= 0) {
6236
+ if (cb) cb(new Error("invalid pid"));
6237
+ return;
5850
6238
  }
5851
- return null;
6239
+ if (isWindows) {
6240
+ ex2.runAsync("taskkill", ["/PID", String(pid), "/T", "/F"], { timeoutMs: 1e4 }).then((r) => {
6241
+ if (cb) cb(r.ok ? null : new Error(r.error || "taskkill failed"));
6242
+ });
6243
+ return;
6244
+ }
6245
+ if (opts && opts.ownGroup === true) {
6246
+ signalProcess(pid, sig || "SIGTERM");
6247
+ } else {
6248
+ try {
6249
+ process.kill(pid, sig || "SIGTERM");
6250
+ } catch {
6251
+ }
6252
+ }
6253
+ if (cb) process.nextTick(cb, null);
5852
6254
  }
5853
- function probeLinux(d) {
5854
- const { runOut, readFile, exists, listDir, canExec, env, home } = d;
5855
- const notes = [];
5856
- const note = (source, detail) => {
5857
- notes.push({ source, detail });
5858
- };
5859
- const found = /* @__PURE__ */ new Map();
5860
- const usable = (item) => {
5861
- const resolved = resolveLinuxBin(item.bin, canExec, env);
5862
- if (!resolved) {
5863
- note(item.source, "\u4E0D\u53EF\u6267\u884C: " + item.bin);
6255
+ module2.exports = { signalProcess, killTree };
6256
+ }
6257
+ });
6258
+
6259
+ // src/platform/os/file-protect.js
6260
+ var require_file_protect = __commonJS({
6261
+ "src/platform/os/file-protect.js"(exports2, module2) {
6262
+ "use strict";
6263
+ var fs2 = require("node:fs");
6264
+ var os2 = require("node:os");
6265
+ var path2 = require("node:path");
6266
+ var ex2 = require_exec();
6267
+ var IS_WINDOWS2 = process.platform === "win32";
6268
+ var _icacls = null;
6269
+ function hasIcacls(platform) {
6270
+ if ((platform || process.platform) !== "win32") return false;
6271
+ if (_icacls !== null) return _icacls;
6272
+ _icacls = ex2.runOut("icacls", ["/?"], { timeoutMs: 3e3 }) !== null;
6273
+ return _icacls;
6274
+ }
6275
+ function currentUser() {
6276
+ return process.env.USERNAME || process.env.USER || (() => {
6277
+ try {
6278
+ return os2.userInfo().username;
6279
+ } catch {
5864
6280
  return null;
5865
6281
  }
5866
- const k = resolved.toLowerCase();
5867
- const hit = found.get(k);
5868
- if (hit) {
5869
- if (!hit.sources.includes(item.source)) hit.sources.push(item.source);
5870
- return hit;
6282
+ })();
6283
+ }
6284
+ function protectFile(file) {
6285
+ if (!IS_WINDOWS2) {
6286
+ try {
6287
+ fs2.chmodSync(file, 384);
6288
+ return { ok: true, mode: "posix-0600" };
6289
+ } catch (e) {
6290
+ return { ok: false, mode: "posix-0600", reason: e.message };
5871
6291
  }
5872
- const v = Object.assign({}, item, { id: k, bin: resolved, sources: [item.source] });
5873
- found.set(k, v);
5874
- return v;
5875
- };
5876
- for (const { dir, kind } of desktopDirs(env, home)) {
5877
- let names = [];
6292
+ }
6293
+ if (!hasIcacls()) return { ok: false, mode: "none", reason: "icacls \u4E0D\u53EF\u7528" };
6294
+ const user = currentUser();
6295
+ if (!user) return { ok: false, mode: "icacls", reason: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u7528\u6237" };
6296
+ const r = ex2.runDetail("icacls", [file, "/inheritance:r", "/grant:r", user + ":F"], { stdio: "ignore", timeoutMs: 5e3 });
6297
+ return r.ok ? { ok: true, mode: "icacls-file" } : { ok: false, mode: "icacls-file", reason: r.error || "\u9000\u51FA\u7801 " + r.code };
6298
+ }
6299
+ function protectDir(dir) {
6300
+ if (!IS_WINDOWS2) {
6301
+ try {
6302
+ fs2.chmodSync(dir, 448);
6303
+ return { ok: true, mode: "posix-0700" };
6304
+ } catch (e) {
6305
+ return { ok: false, mode: "posix-0700", reason: e.message };
6306
+ }
6307
+ }
6308
+ if (!hasIcacls()) return { ok: false, mode: "none", reason: "icacls \u4E0D\u53EF\u7528" };
6309
+ const user = currentUser();
6310
+ if (!user) return { ok: false, mode: "icacls", reason: "\u65E0\u6CD5\u786E\u5B9A\u5F53\u524D\u7528\u6237" };
6311
+ const r = ex2.runDetail("icacls", [dir, "/inheritance:r", "/grant:r", user + ":(OI)(CI)F"], { stdio: "ignore", timeoutMs: 1e4 });
6312
+ return r.ok ? { ok: true, mode: "icacls-dir" } : { ok: false, mode: "icacls-dir", reason: r.error || "\u9000\u51FA\u7801 " + r.code };
6313
+ }
6314
+ function ensurePrivateDir(dir) {
6315
+ try {
6316
+ fs2.mkdirSync(dir, { recursive: true });
6317
+ } catch (e) {
6318
+ return { ok: false, mode: "mkdir", reason: e.message };
6319
+ }
6320
+ return protectDir(dir);
6321
+ }
6322
+ function writePrivate(file, data) {
6323
+ try {
6324
+ const dir = path2.dirname(file);
6325
+ try {
6326
+ fs2.mkdirSync(dir, { recursive: true });
6327
+ } catch {
6328
+ }
6329
+ const tmp = file + ".tmp" + process.pid;
6330
+ fs2.writeFileSync(tmp, data, { mode: 384 });
6331
+ const p1 = protectFile(tmp);
6332
+ if (p1 && p1.ok === false) {
6333
+ try {
6334
+ fs2.rmSync(tmp, { force: true });
6335
+ } catch {
6336
+ }
6337
+ return { ok: false, reason: "protect(tmp): " + (p1.reason || p1.mode), mode: p1.mode };
6338
+ }
6339
+ fs2.renameSync(tmp, file);
6340
+ const p2 = protectFile(file);
6341
+ if (p2 && p2.ok === false) return { ok: false, reason: "protect(file): " + (p2.reason || p2.mode), mode: p2.mode };
6342
+ return { ok: true };
6343
+ } catch (e) {
6344
+ return { ok: false, reason: e.message };
6345
+ }
6346
+ }
6347
+ module2.exports = { protectFile, protectDir, ensurePrivateDir, writePrivate, hasIcacls, currentUser, IS_WINDOWS: IS_WINDOWS2 };
6348
+ }
6349
+ });
6350
+
6351
+ // src/platform/util/input.js
6352
+ var require_input = __commonJS({
6353
+ "src/platform/util/input.js"(exports2, module2) {
6354
+ "use strict";
6355
+ var PKG_NAME_RE = /^(@[a-zA-Z0-9._-]+\/)?[a-zA-Z0-9._-]+$/;
6356
+ var ARGV_UNSAFE_RE = /[\s;|&<>`'"$(){}\\*?~#]/;
6357
+ var WIN_ABS_PATH_RE = /^[A-Za-z]:\\[^;|&<>`'"$*?~#\s]*$/;
6358
+ var UNIT_NAME_RE = /^[A-Za-z0-9:@._-]{1,128}$/;
6359
+ var UNSAFE_LEDGER_KEYS = ["__proto__", "prototype", "constructor"];
6360
+ var LEDGER_UNSAFE_RE = /[\s\u0000-\u001f\u007f]/;
6361
+ function argvViolation(arg) {
6362
+ const s = String(arg == null ? "" : arg);
6363
+ if (!s) return "\u7A7A\u7684 argv \u9879";
6364
+ if (ARGV_UNSAFE_RE.test(s) && !WIN_ABS_PATH_RE.test(s)) return "argv \u9879\u542B\u7981\u7528\u5B57\u7B26\uFF08\u7A7A\u767D/shell \u5143\u5B57\u7B26\uFF09: " + s.slice(0, 80);
6365
+ return null;
6366
+ }
6367
+ var CONTROL_RE = /[\u0000-\u001f\u007f]/;
6368
+ function isAbsoluteForm(p) {
6369
+ const s = String(p == null ? "" : p);
6370
+ return s.charCodeAt(0) === 47 || /^[A-Za-z]:[\\/]/.test(s) || s.charCodeAt(0) === 92 && s.charCodeAt(1) === 92;
6371
+ }
6372
+ function prefixViolation(prefix) {
6373
+ const s = String(prefix == null ? "" : prefix);
6374
+ if (!s.trim()) return "\u7A7A\u7684\u5B89\u88C5\u524D\u7F00";
6375
+ if (CONTROL_RE.test(s)) return "\u5B89\u88C5\u524D\u7F00\u542B\u63A7\u5236\u7B26: " + s.slice(0, 80);
6376
+ if (s.length > 4096) return "\u5B89\u88C5\u524D\u7F00\u8D85\u957F: " + s.slice(0, 60) + "...";
6377
+ if (s[0] === "-") return "\u5B89\u88C5\u524D\u7F00\u4EE5 - \u5F00\u5934\uFF08\u4F1A\u88AB\u5F53\u4F5C npm \u9009\u9879\uFF09: " + s.slice(0, 80);
6378
+ if (!isAbsoluteForm(s)) return "\u5B89\u88C5\u524D\u7F00\u987B\u4E3A\u7EDD\u5BF9\u8DEF\u5F84\uFF08/ \u5F00\u5934\u3001win \u76D8\u7B26\u6216 UNC\uFF09: " + s.slice(0, 80);
6379
+ return null;
6380
+ }
6381
+ function pkgNameViolation(pkg) {
6382
+ const s = String(pkg == null ? "" : pkg);
6383
+ return PKG_NAME_RE.test(s) ? null : "\u975E\u6CD5\u5305\u540D\uFF08\u5B57\u7B26\u96C6\u767D\u540D\u5355\u4E0D\u901A\u8FC7\uFF09: " + s.slice(0, 80);
6384
+ }
6385
+ function unitNameViolation(unit) {
6386
+ const s = String(unit == null ? "" : unit);
6387
+ if (!UNIT_NAME_RE.test(s)) return "\u975E\u6CD5\u5355\u5143\u540D\uFF08\u5B57\u7B26\u96C6/\u957F\u5EA6\u767D\u540D\u5355\u4E0D\u901A\u8FC7\uFF09: " + s.slice(0, 80);
6388
+ const dot = s.indexOf(".");
6389
+ if (dot >= 0 && !/\.service$/.test(s)) return "\u5355\u5143\u540D\u540E\u7F00\u4E0D\u53D7\u652F\u6301\uFF08\u4EC5\u5141\u8BB8 .service \u6216\u65E0\u540E\u7F00\uFF09: " + s.slice(0, 80);
6390
+ return null;
6391
+ }
6392
+ function ledgerKey(raw, opts) {
6393
+ const o = opts || {};
6394
+ const max = typeof o.max === "number" ? o.max : 128;
6395
+ const empty = o.empty == null ? "unknown" : o.empty;
6396
+ const unsafe = o.unsafe == null ? "(other)" : o.unsafe;
6397
+ if (typeof raw !== "string" || !raw) return empty;
6398
+ const s = raw.length > max ? raw.slice(0, max) : raw;
6399
+ if (UNSAFE_LEDGER_KEYS.indexOf(s) >= 0 || LEDGER_UNSAFE_RE.test(s)) return unsafe;
6400
+ return s;
6401
+ }
6402
+ module2.exports = {
6403
+ PKG_NAME_RE,
6404
+ ARGV_UNSAFE_RE,
6405
+ WIN_ABS_PATH_RE,
6406
+ UNIT_NAME_RE,
6407
+ UNSAFE_LEDGER_KEYS,
6408
+ LEDGER_UNSAFE_KEYS_RE: LEDGER_UNSAFE_RE,
6409
+ argvViolation,
6410
+ pkgNameViolation,
6411
+ unitNameViolation,
6412
+ ledgerKey,
6413
+ CONTROL_RE,
6414
+ isAbsoluteForm,
6415
+ prefixViolation
6416
+ };
6417
+ }
6418
+ });
6419
+
6420
+ // src/platform/os/spawn.js
6421
+ var require_spawn = __commonJS({
6422
+ "src/platform/os/spawn.js"(exports2, module2) {
6423
+ "use strict";
6424
+ var { spawn } = require("node:child_process");
6425
+ function detached(cmd2, args, opts) {
6426
+ const o = opts || {};
6427
+ return spawn(cmd2, args, Object.assign({}, o, {
6428
+ detached: true,
6429
+ windowsHide: true,
6430
+ // 默认 ignore;显式 undefined 也回落 ignore,避免 stdio:undefined 的歧义。
6431
+ stdio: o.stdio === void 0 ? "ignore" : o.stdio
6432
+ }));
6433
+ }
6434
+ function piped(cmd2, args, opts) {
6435
+ const o = opts || {};
6436
+ return spawn(cmd2, args, Object.assign({}, o, {
6437
+ stdio: ["ignore", "pipe", "pipe"],
6438
+ windowsHide: true,
6439
+ detached: o.detached === true
6440
+ }));
6441
+ }
6442
+ function detachedIgnored(cmd2, args, opts) {
6443
+ const o = opts || {};
6444
+ return spawn(cmd2, args, Object.assign({}, o, {
6445
+ detached: true,
6446
+ windowsHide: true,
6447
+ stdio: "ignore"
6448
+ }));
6449
+ }
6450
+ module2.exports = { detached, piped, detachedIgnored };
6451
+ }
6452
+ });
6453
+
6454
+ // src/platform/os/portable.js
6455
+ var require_portable = __commonJS({
6456
+ "src/platform/os/portable.js"(exports2, module2) {
6457
+ "use strict";
6458
+ var fs2 = require("node:fs");
6459
+ var pidlookup = require_pidlookup();
6460
+ var spawner = require_spawn();
6461
+ var procOS = require_process();
6462
+ var STOP_WAIT_CAP_MS = 3e3;
6463
+ var NAP_MS = 150;
6464
+ function nap(ms) {
6465
+ try {
6466
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
6467
+ } catch {
6468
+ }
6469
+ }
6470
+ function readPidFile(pidFile) {
6471
+ if (!pidFile) return null;
6472
+ try {
6473
+ const n = parseInt(String(fs2.readFileSync(pidFile, "utf8")).trim(), 10);
6474
+ return Number.isInteger(n) && n > 0 ? n : null;
6475
+ } catch {
6476
+ return null;
6477
+ }
6478
+ }
6479
+ function matchesAnchors(pid, anchors) {
6480
+ if (!pid || !Array.isArray(anchors) || !anchors.length) return false;
6481
+ const cmd2 = pidlookup.readCmdline(pid);
6482
+ if (!cmd2) return false;
6483
+ return anchors.some((a) => a && cmd2.indexOf(String(a)) !== -1);
6484
+ }
6485
+ var portOf = (o) => {
6486
+ const p = Number(o && o.port);
6487
+ return Number.isInteger(p) && p > 0 ? p : 0;
6488
+ };
6489
+ var pidFileOf = (o) => o && o.pidFile || null;
6490
+ var anchorsOf = (o) => Array.isArray(o && o.anchors) ? o.anchors : [];
6491
+ function findOurs(o) {
6492
+ const anchors = anchorsOf(o);
6493
+ const fp = readPidFile(pidFileOf(o));
6494
+ if (fp !== null && pidlookup.isAlive(fp) && matchesAnchors(fp, anchors)) return { pid: fp, ownGroup: true };
6495
+ const port = portOf(o);
6496
+ if (port) {
6497
+ const q = pidlookup.findListeningPid(port);
6498
+ if (q !== null && q !== fp && matchesAnchors(q, anchors)) return { pid: q, ownGroup: false };
6499
+ }
6500
+ return null;
6501
+ }
6502
+ function removePidFile(o) {
6503
+ const f = pidFileOf(o);
6504
+ if (f) {
6505
+ try {
6506
+ fs2.unlinkSync(f);
6507
+ } catch {
6508
+ }
6509
+ }
6510
+ }
6511
+ var portable = {
6512
+ kind: "portable",
6513
+ supportsUnits: false,
6514
+ supportsTransient: true,
6515
+ // 无操作 = 成功(与 systemd 的动词返回形态一致,调用方无需按 provider 分支)。
6516
+ daemonReload() {
6517
+ return true;
6518
+ },
6519
+ resetFailed() {
6520
+ return true;
6521
+ },
6522
+ /** 拉起:spawn(detached)——POSIX 自成进程组(kill(-pid) 语义的前提)、win32 windowsHide +
6523
+ * CREATE_NEW_PROCESS_GROUP(NO-CONSOLE-WINDOW 纪律收口在 platform/os/spawn.js)。
6524
+ * 调用方传来的 props(cgroup 语义)在此如实忽略:本档无内核强制可施加。
6525
+ * @param {{cmd:string[], env?:object, workingDir?:string, pidFile?:string, port?:number, anchors?:string[]}} o */
6526
+ startTransient(o) {
6527
+ const opts = o || {};
6528
+ const cmd2 = opts.cmd || [];
6529
+ if (!cmd2.length) throw new Error("portable \u62C9\u8D77\u62D2\u7EDD\uFF1A\u7A7A\u547D\u4EE4");
6530
+ const child = spawner.detached(cmd2[0], cmd2.slice(1), {
6531
+ cwd: opts.workingDir || void 0,
6532
+ env: Object.assign({}, process.env, opts.env || {})
6533
+ });
6534
+ child.on("error", () => {
6535
+ });
6536
+ if (!child.pid) {
6537
+ try {
6538
+ child.kill();
6539
+ } catch {
6540
+ }
6541
+ throw new Error("portable \u62C9\u8D77\u5931\u8D25\uFF1Aspawn \u672A\u4EA7\u751F\u8FDB\u7A0B");
6542
+ }
6543
+ try {
6544
+ child.unref();
6545
+ } catch {
6546
+ }
6547
+ if (opts.pidFile) {
6548
+ try {
6549
+ fs2.writeFileSync(opts.pidFile, String(child.pid));
6550
+ } catch {
6551
+ }
6552
+ }
6553
+ return true;
6554
+ },
6555
+ /** 停止(有界同步复核):终止我们的进程树,确认「端口锚点与 run.pid 锚点双双消失」才 true。
6556
+ * 无可杀对象(含 run.pid 为陈旧/被 PID 复用)-> 清文件后如实 true(幂等)。 */
6557
+ stopUnit(unit, o) {
6558
+ const opts = o || {};
6559
+ const ours = findOurs(opts);
6560
+ if (!ours) {
6561
+ removePidFile(opts);
6562
+ return true;
6563
+ }
6564
+ try {
6565
+ procOS.killTree(ours.pid, "SIGTERM", void 0, { ownGroup: ours.ownGroup });
6566
+ } catch {
6567
+ }
6568
+ const budget = Math.max(0, Math.min(typeof opts.timeoutMs === "number" ? opts.timeoutMs : STOP_WAIT_CAP_MS, STOP_WAIT_CAP_MS));
6569
+ const t0 = Date.now();
6570
+ for (; ; ) {
6571
+ if (!findOurs(opts)) {
6572
+ removePidFile(opts);
6573
+ return true;
6574
+ }
6575
+ if (Date.now() - t0 >= budget) break;
6576
+ nap(NAP_MS);
6577
+ }
6578
+ try {
6579
+ procOS.killTree(ours.pid, "SIGKILL", void 0, { ownGroup: ours.ownGroup });
6580
+ } catch {
6581
+ }
6582
+ nap(NAP_MS);
6583
+ if (!findOurs(opts)) {
6584
+ removePidFile(opts);
6585
+ return true;
6586
+ }
6587
+ return false;
6588
+ },
6589
+ /** 活跃判定(三态):port 与 pidFile 都缺 = 无从查询 -> null,删除保护路径不得把它当「已停止」。
6590
+ * anchors 为空时降级为「端口有监听即活跃」(monitor.probeInstance 同口径,该调用方本就以此判在线);
6591
+ * 杀进程(stopUnit)仍要求锚点命中 —— 判定可宽,动手必严。
6592
+ * 存活 pid 的 cmdline 读不到 = 分不清「他人复用」与「查询失败」-> null。 */
6593
+ isUnitActive(unit, o) {
6594
+ const opts = o || {};
6595
+ if (findOurs(opts)) return true;
6596
+ const port = portOf(opts);
6597
+ const pidFile = pidFileOf(opts);
6598
+ const anchors = anchorsOf(opts);
6599
+ if (!port && !pidFile) return null;
6600
+ if (!anchors.length) {
6601
+ const fp2 = readPidFile(pidFile);
6602
+ if (fp2 !== null && pidlookup.isAlive(fp2)) return true;
6603
+ if (port) {
6604
+ const q = pidlookup.findListeningPid(port);
6605
+ if (q !== null) return true;
6606
+ if (fp2 !== null) return false;
6607
+ return null;
6608
+ }
6609
+ return fp2 !== null ? false : null;
6610
+ }
6611
+ const fp = readPidFile(pidFile);
6612
+ if (fp !== null && pidlookup.isAlive(fp) && !pidlookup.readCmdline(fp)) return null;
6613
+ return false;
6614
+ },
6615
+ // portable 无 systemd transient 单元文件;run.pid 的清理归 cleanTransient。
6616
+ transientUnitFile() {
6617
+ return null;
6618
+ },
6619
+ /** 清理同名残留:先停掉仍活着的旧进程(重启自愈路径:旧进程未监听端口时端口探测拦不住),再清 run.pid。 */
6620
+ cleanTransient(unit, o) {
6621
+ const opts = o || {};
6622
+ const errors = [];
6623
+ if (this.stopUnit(unit, Object.assign({}, opts, { timeoutMs: 1500 })) === false) errors.push("stop-unconfirmed");
6624
+ removePidFile(opts);
6625
+ return { ok: errors.length === 0, errors };
6626
+ },
6627
+ /** 返回值只表示「是否改动了内核强制」;governor 不因 false 走降级分支 —— 违规处置链本就平台无关。 */
6628
+ setLimits() {
6629
+ return false;
6630
+ }
6631
+ };
6632
+ module2.exports = { portable, findOurs, matchesAnchors, _test: { readPidFile, matchesAnchors, findOurs } };
6633
+ }
6634
+ });
6635
+
6636
+ // src/platform/os/service.js
6637
+ var require_service = __commonJS({
6638
+ "src/platform/os/service.js"(exports2, module2) {
6639
+ "use strict";
6640
+ var fs2 = require("node:fs");
6641
+ var os2 = require("node:os");
6642
+ var path2 = require("node:path");
6643
+ var exec = require_exec();
6644
+ var input = require_input();
6645
+ var execPath = require_exec_path();
6646
+ var { portable } = require_portable();
6647
+ var CapabilityError = class extends Error {
6648
+ constructor(msg) {
6649
+ super(msg);
6650
+ this.name = "CapabilityError";
6651
+ this.code = "CAPABILITY_UNSUPPORTED";
6652
+ }
6653
+ };
6654
+ var PLATFORM = process.platform;
6655
+ function run(cmd2, args, opts) {
6656
+ return exec.run(cmd2, args, opts || {});
6657
+ }
6658
+ var UNIT_NAME_RE = input.UNIT_NAME_RE;
6659
+ function unitNameViolation(unit) {
6660
+ return input.unitNameViolation(unit);
6661
+ }
6662
+ var systemd = {
6663
+ kind: "systemd",
6664
+ supportsUnits: true,
6665
+ supportsTransient: true,
6666
+ daemonReload() {
5878
6667
  try {
5879
- names = listDir(dir).filter((f) => f.endsWith(".desktop"));
6668
+ return run("systemctl", ["--user", "daemon-reload"], { timeoutMs: 15e3 }) !== null;
5880
6669
  } catch {
5881
- continue;
5882
- }
5883
- for (const f of names) {
5884
- let text = null;
5885
- try {
5886
- text = readFile(PJ(dir, f));
5887
- } catch {
5888
- continue;
5889
- }
5890
- const item = browserFromDesktop(text, kind, f);
5891
- if (item) usable(item);
6670
+ return false;
5892
6671
  }
5893
- }
5894
- let defaultId = null;
5895
- let defaultSource = null;
5896
- const fromMime = linuxDefaultFromMimeApps(readFile, exists, env, home, note);
5897
- if (fromMime) {
5898
- const item = usable(fromMime);
5899
- if (item) {
5900
- defaultId = item.id;
5901
- defaultSource = "mimeapps";
6672
+ },
6673
+ stopUnit(unit, opts) {
6674
+ if (unitNameViolation(unit)) return false;
6675
+ const o = opts || {};
6676
+ try {
6677
+ return run("systemctl", ["--user", "stop", unit], { timeoutMs: o.timeoutMs || 15e3 }) !== null;
6678
+ } catch {
6679
+ return false;
5902
6680
  }
5903
- }
5904
- const id = runOut("xdg-settings", ["get", "default-web-browser"]);
5905
- const desktopId = id && String(id).trim();
5906
- if (!defaultId && desktopId && /^[A-Za-z0-9._-]+\.desktop$/.test(desktopId)) {
5907
- for (const { dir } of desktopDirs(env, home)) {
5908
- const p = PJ(dir, desktopId);
5909
- let text = null;
5910
- try {
5911
- if (exists(p)) text = readFile(p);
5912
- } catch {
5913
- continue;
5914
- }
5915
- if (text === null) continue;
5916
- const item = browserFromDesktop(text, "xdg-settings", desktopId);
5917
- const used = item && usable(item);
5918
- if (used) {
5919
- defaultId = used.id;
5920
- defaultSource = "xdg-settings";
5921
- break;
5922
- }
6681
+ },
6682
+ // run() 失败返回 null 而不抛,try/catch 包裹会恒返回 true;如实回传成败。
6683
+ resetFailed(unit) {
6684
+ if (unitNameViolation(unit)) return false;
6685
+ return run("systemctl", ["--user", "reset-failed", unit], { timeoutMs: 1e4 }) !== null;
6686
+ },
6687
+ /** 单元活跃判定(三态):确认 active -> true;确认不活跃 -> false;查询未完成 -> null(未知)。
6688
+ * run() 失败只返回 null,「查询未完成」不可折成 false:instance/ops.js 删除数据目录前
6689
+ * 按「!== false 才放行删除」消费本函数,折假即超时窗口内误删沙箱数据。 */
6690
+ isUnitActive(unit) {
6691
+ if (!unit) return true;
6692
+ if (unitNameViolation(unit)) return false;
6693
+ try {
6694
+ const r = exec.runDetail("systemctl", ["--user", "is-active", unit], { timeoutMs: 8e3 });
6695
+ if (r.timedOut) return null;
6696
+ const state = String(r.stdout || "").trim();
6697
+ if (state === "active") return true;
6698
+ if (state || r.code !== null) return false;
6699
+ return null;
6700
+ } catch {
6701
+ return null;
5923
6702
  }
5924
- }
5925
- if (!desktopId) note("xdg-settings", "\u65E0\u8F93\u51FA");
5926
- else if (defaultSource === "xdg-settings") note("xdg-settings", "\u9ED8\u8BA4=" + defaultId);
5927
- else note("xdg-settings", "\u62A5\u51FA " + desktopId + (defaultId ? "\uFF08\u9ED8\u8BA4\u5DF2\u7531 " + defaultSource + " \u5B9A\u51FA\uFF09" : " \u4F46\u4E0D\u53EF\u7528"));
5928
- if (!defaultId && found.size === 1) {
5929
- defaultId = [...found.keys()][0];
5930
- defaultSource = "only-installed";
5931
- }
5932
- return { browsers: [...found.values()], defaultId, defaultSource, probed: notes };
5933
- }
5934
- function linuxDefaultFromMimeApps(readFile, exists, env, home, note) {
5935
- const cfgHome = env.XDG_CONFIG_HOME || PJ(home, ".config");
5936
- const cfgDirs = String(env.XDG_CONFIG_DIRS || "/etc/xdg").split(":").filter(Boolean);
5937
- const dataDirs = String(env.XDG_DATA_DIRS || "/usr/local/share:/usr/share").split(":").filter(Boolean);
5938
- const files = [
5939
- PJ(cfgHome, "mimeapps.list"),
5940
- ...cfgDirs.map((d) => PJ(d, "mimeapps.list")),
5941
- ...dataDirs.map((d) => PJ(d, "applications", "mimeapps.list"))
5942
- ];
5943
- for (const f of files) {
5944
- if (!exists(f)) continue;
5945
- let text = null;
6703
+ },
6704
+ transientUnitFile(unit) {
6705
+ if (unitNameViolation(unit)) return null;
6706
+ let uid = 0;
5946
6707
  try {
5947
- text = readFile(f);
6708
+ uid = os2.userInfo().uid;
5948
6709
  } catch {
5949
- continue;
5950
6710
  }
5951
- const id = mimeAppsDefault(text);
5952
- if (!id) continue;
5953
- note("mimeapps", path2.basename(f) + " -> " + id);
5954
- for (const { dir, kind } of desktopDirs(env, home)) {
5955
- const p = PJ(dir, id);
5956
- if (!exists(p)) continue;
5957
- let dt = null;
5958
- try {
5959
- dt = readFile(p);
5960
- } catch {
5961
- continue;
5962
- }
5963
- const item = browserFromDesktop(dt, kind, id);
5964
- if (item) return item;
6711
+ const rt = process.env.XDG_RUNTIME_DIR || "/run/user/" + uid;
6712
+ return path2.join(rt, "systemd", "transient", unit + ".service");
6713
+ },
6714
+ /** 清理 stale transient 单元:stop/reset-failed/删单元文件/daemon-reload。
6715
+ * 必须 reload:删除文件后 systemd 仍缓存该单元为 loaded,否则 systemd-run 拒绝重建同名单元。
6716
+ * 返回 {ok, errors}:stop/reset-failed 对「从未加载的单元」非零退出属正常,不计入 ok;
6717
+ * 硬失败只有删单元文件与 daemon-reload(run() 失败返回 null 不抛,成败须如实回传)。 */
6718
+ cleanTransient(unit) {
6719
+ const errors = [];
6720
+ const bad = unitNameViolation(unit);
6721
+ if (bad) return { ok: false, errors: [bad] };
6722
+ exec.runDetail("systemctl", ["--user", "stop", unit + ".service"], { timeoutMs: 1e4 });
6723
+ exec.runDetail("systemctl", ["--user", "reset-failed", unit + ".service"], { timeoutMs: 1e4 });
6724
+ let unlinkOk = true;
6725
+ try {
6726
+ const f = this.transientUnitFile(unit);
6727
+ if (f) fs2.unlinkSync(f);
6728
+ } catch {
6729
+ unlinkOk = false;
6730
+ errors.push("unlink " + unit + ".service");
5965
6731
  }
5966
- return null;
6732
+ if (!exec.runDetail("systemctl", ["--user", "daemon-reload"], { timeoutMs: 15e3 }).ok) errors.push("daemon-reload");
6733
+ return { ok: unlinkOk && errors.length === 0, errors };
6734
+ },
6735
+ /** 以 transient 单元启动(独立 cgroup;每实例隔离)。
6736
+ * @param {{unit:string, cmd:string[], env?:object, props?:string[], workingDir?:string, description?:string, timeoutMs?:number}} o
6737
+ * props 为 systemd 属性(如 KillMode=process、MemoryMax=8G),平台层只拼装成 --property,不解释语义。 */
6738
+ startTransient(o) {
6739
+ const opts = o || {};
6740
+ const bad = unitNameViolation(opts.unit);
6741
+ if (bad) throw new Error("systemd-run \u62D2\u7EDD: " + bad);
6742
+ const args = ["--user", "--unit=" + opts.unit];
6743
+ if (opts.description) args.push("--description=" + opts.description);
6744
+ for (const p of opts.props || []) args.push("--property=" + p);
6745
+ for (const [k, v] of Object.entries(opts.env || {})) args.push("--setenv=" + k + "=" + v);
6746
+ if (opts.workingDir) args.push("--working-directory=" + opts.workingDir);
6747
+ args.push("--", ...opts.cmd || []);
6748
+ const r = exec.runDetail("systemd-run", args, { timeoutMs: opts.timeoutMs || 2e4 });
6749
+ if (!r.ok) throw new Error("systemd-run \u5931\u8D25: " + (r.error || "unknown") + (r.stderr ? " | " + String(r.stderr).trim() : ""));
6750
+ return true;
6751
+ },
6752
+ /** 运行期改限额(W3 动态化):systemctl --user set-property 立即生效,无需重启单元。
6753
+ * 必带 --runtime:transient 单元本不落盘,不带它会把 drop-in 写进用户配置目录,陈旧下限永久黏住。
6754
+ * 值只来自 sandbox.unitProps 同源的 alloc(平台层翻译语义、不决定数额)。 */
6755
+ setLimits(unit, alloc) {
6756
+ if (unitNameViolation(unit)) return false;
6757
+ const a = alloc || {};
6758
+ const props = [];
6759
+ if (a.memoryMax) props.push("MemoryMax=" + a.memoryMax);
6760
+ if (a.memoryHigh) props.push("MemoryHigh=" + a.memoryHigh);
6761
+ if (a.cpuQuota) props.push("CPUQuota=" + a.cpuQuota);
6762
+ if (!props.length) return false;
6763
+ return exec.runDetail(
6764
+ "systemctl",
6765
+ ["--user", "set-property", "--runtime", unit].concat(props),
6766
+ { timeoutMs: 1e4 }
6767
+ ).ok;
5967
6768
  }
5968
- note("mimeapps", "\u65E0\u9ED8\u8BA4\u9879");
5969
- return null;
5970
- }
5971
- function mimeAppsDefault(text) {
5972
- let inDefault = false;
5973
- for (const line of String(text || "").split(/\r?\n/)) {
5974
- if (/^\s*\[/.test(line)) {
5975
- inDefault = /^\s*\[Default Applications\]\s*$/.test(line);
5976
- continue;
6769
+ };
6770
+ function makeUnsupported(kind, label) {
6771
+ return {
6772
+ kind,
6773
+ supportsUnits: false,
6774
+ supportsTransient: false,
6775
+ daemonReload() {
6776
+ return false;
6777
+ },
6778
+ stopUnit() {
6779
+ throw new CapabilityError(label + "\uFF1A\u4E0D\u652F\u6301\u4EE5\u7528\u6237\u5355\u5143\u65B9\u5F0F\u7BA1\u7406\u88AB\u7BA1\u5B9E\u4F8B");
6780
+ },
6781
+ resetFailed() {
6782
+ return false;
6783
+ },
6784
+ isUnitActive(unit) {
6785
+ return unit ? false : true;
6786
+ },
6787
+ transientUnitFile() {
6788
+ return null;
6789
+ },
6790
+ // 无 transient 单元可清 = 成功;返回形态与 systemd 一致,调用方无需分支。
6791
+ cleanTransient() {
6792
+ return { ok: true, errors: [] };
6793
+ },
6794
+ startTransient() {
6795
+ throw new CapabilityError(label + "\uFF1A\u4E0D\u652F\u6301\u62C9\u8D77\u5B9E\u4F8B\u8231\uFF08portable \u6863\u672A\u542F\u7528\uFF09");
6796
+ },
6797
+ setLimits() {
6798
+ return false;
5977
6799
  }
5978
- if (!inDefault) continue;
5979
- const m = line.match(/^\s*x-scheme-handler\/https?\s*=\s*(.*)$/i);
5980
- if (!m) continue;
5981
- const ids = m[1].split(";").map((s) => s.trim()).filter((s) => /^[A-Za-z0-9._-]+\.desktop$/.test(s));
5982
- if (ids.length) return ids[0];
6800
+ };
6801
+ }
6802
+ var NONE = makeUnsupported("none", "\u5F53\u524D\u5E73\u53F0\u65E0\u670D\u52A1\u7BA1\u7406\u5668");
6803
+ var _systemdRun = null;
6804
+ function hasSystemdRun() {
6805
+ if (_systemdRun === null) {
6806
+ _systemdRun = !!execPath.resolveExecutable("systemd-run") || exec.runOut("systemd-run", ["--version"], { timeoutMs: 3e3 }) !== null;
5983
6807
  }
5984
- return null;
6808
+ return _systemdRun;
6809
+ }
6810
+ function current() {
6811
+ if (PLATFORM === "linux") return hasSystemdRun() ? systemd : portable;
6812
+ if (PLATFORM === "darwin" || PLATFORM === "win32") return portable;
6813
+ return NONE;
6814
+ }
6815
+ module2.exports = { current, CapabilityError, kind: () => current().kind, PLATFORM, UNIT_NAME_RE, unitNameViolation, _testProviders: { systemd, portable, NONE } };
6816
+ }
6817
+ });
6818
+
6819
+ // src/platform/os/notify.js
6820
+ var require_notify = __commonJS({
6821
+ "src/platform/os/notify.js"(exports2, module2) {
6822
+ "use strict";
6823
+ var spawnOS = require_spawn();
6824
+ function appleScriptString(s) {
6825
+ return JSON.stringify(String(s));
6826
+ }
6827
+ function powerShellString(s) {
6828
+ return "'" + String(s).replace(/'/g, "''") + "'";
5985
6829
  }
5986
- function probe(platform, d) {
6830
+ function notifyCommand(platform, title, body) {
5987
6831
  const pl = platform || process.platform;
5988
- const dd = Object.assign({}, d, { canExec: d.canExec || ((p) => isExecutableFile(p, pl)) });
5989
- if (pl === "win32") return probeWin(dd);
5990
- if (pl === "darwin") return probeMac(dd);
5991
- return probeLinux(dd);
6832
+ const t = String(title == null ? "" : title);
6833
+ const b = String(body == null ? "" : body);
6834
+ if (pl === "linux") {
6835
+ return { cmd: "notify-send", args: ["-a", "dsh-supervisor", t, b] };
6836
+ }
6837
+ if (pl === "darwin") {
6838
+ const script = "display notification " + appleScriptString(b) + " with title " + appleScriptString(t);
6839
+ return { cmd: "osascript", args: ["-e", script] };
6840
+ }
6841
+ if (pl === "win32") {
6842
+ const ps = [
6843
+ '[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null',
6844
+ '[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null',
6845
+ "$n = New-Object System.Windows.Forms.NotifyIcon",
6846
+ "$n.Icon = [System.Drawing.SystemIcons]::Information",
6847
+ "$n.Visible = $true",
6848
+ "$n.ShowBalloonTip(4000, " + powerShellString(t) + ", " + powerShellString(b) + ", [System.Windows.Forms.ToolTipIcon]::None)",
6849
+ "Start-Sleep -Milliseconds 4200",
6850
+ "$n.Dispose(); $n.Visible = $false"
6851
+ ].join("; ");
6852
+ return { cmd: "powershell", args: ["-NoProfile", "-NonInteractive", "-Command", ps] };
6853
+ }
6854
+ return null;
5992
6855
  }
5993
- var CACHE = /* @__PURE__ */ Object.create(null);
5994
- function inventory(platform, o) {
5995
- const ov = o || {};
5996
- const pl = platform || process.platform;
5997
- const now = ov.now || (() => Date.now());
5998
- const ttl = ov.ttlMs === void 0 ? 6e4 : ov.ttlMs;
5999
- const cached = CACHE[pl];
6000
- if (!ov.force && cached && now() - cached.at < ttl) return Object.assign({ cached: true }, cached.value);
6001
- const runOut = ov.runOut || ((bin, args) => exec.runOut(bin, args, { timeoutMs: PROBE_TIMEOUT_MS }));
6002
- const readFile = ov.readFile || ((p) => fs2.readFileSync(p, "utf8"));
6003
- const exists = ov.exists || ((p) => fs2.existsSync(p));
6004
- const listDir = ov.listDir || ((p) => fs2.readdirSync(p));
6005
- let value;
6856
+ function notify(title, body, onError) {
6006
6857
  try {
6007
- value = probe(pl, { runOut, readFile, exists, listDir, canExec: ov.canExec, env: ov.env || process.env, home: ov.home || os2.homedir() });
6008
- } catch (e) {
6009
- value = { browsers: [], defaultId: null, defaultSource: null, probed: [{ source: "probe-error", detail: String(e && e.message || e) }] };
6858
+ const plan = notifyCommand(process.platform, title, body);
6859
+ if (!plan) return false;
6860
+ const c = spawnOS.detachedIgnored(plan.cmd, plan.args);
6861
+ c.on("error", () => {
6862
+ if (onError) onError();
6863
+ });
6864
+ c.unref();
6865
+ return true;
6866
+ } catch {
6867
+ if (onError) onError();
6868
+ return false;
6010
6869
  }
6011
- value.at = now();
6012
- value.platform = pl;
6013
- value.browsers.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
6014
- CACHE[pl] = { at: now(), value };
6015
- return Object.assign({ cached: false }, value);
6016
- }
6017
- function invalidate(platform) {
6018
- if (platform) delete CACHE[platform];
6019
- else for (const k of Object.keys(CACHE)) delete CACHE[k];
6020
6870
  }
6021
- module2.exports = {
6022
- inventory,
6023
- invalidate,
6024
- probe,
6025
- probeWin,
6026
- probeMac,
6027
- probeLinux,
6028
- engineOf,
6029
- tokenizeExec,
6030
- parseExecLine,
6031
- regValueOf,
6032
- expandEnvVars,
6033
- exeFromCmdLine,
6034
- regSubkeys,
6035
- regKeyFull,
6036
- regValueTargets,
6037
- safeRegKeyPart,
6038
- browserFromDesktop,
6039
- desktopDirs,
6040
- resolveLinuxBin,
6041
- mimeAppsDefault,
6042
- linuxDefaultFromMimeApps,
6043
- WIN_APP_PATHS,
6044
- PROBE_TIMEOUT_MS
6045
- };
6871
+ module2.exports = { notify, notifyCommand, appleScriptString, powerShellString };
6046
6872
  }
6047
6873
  });
6048
6874
 
@@ -6055,8 +6881,10 @@ var require_browser = __commonJS({
6055
6881
  var desktop = require_desktop();
6056
6882
  var CAPABILITY_PROFILES = require_capability_profile();
6057
6883
  var { resolveExecutable, isExecutableFile } = require_exec_path();
6884
+ var environment = require_environment();
6058
6885
  var detector = require_browser_inventory();
6059
6886
  var { engineOf } = detector;
6887
+ var { allocTempDir, removeTreeDeferred } = require_fs();
6060
6888
  function isSafeHttpUrl(url) {
6061
6889
  try {
6062
6890
  const u = new URL(String(url));
@@ -6092,7 +6920,7 @@ var require_browser = __commonJS({
6092
6920
  var HANDEDOFF_TEXT = "\u5DF2\u628A\u5730\u5740\u4EA4\u7ED9\u7CFB\u7EDF\uFF0C\u4F46\u8FD9\u6B21\u542F\u52A8\u62FF\u4E0D\u5230\u7A97\u53E3\u662F\u5426\u51FA\u73B0\u7684\u8BC1\u636E";
6093
6921
  var FAILURE_TEXT = {
6094
6922
  "unsafe-url": "\u5730\u5740\u4E0D\u662F http(s) \u7EDD\u5BF9 URL\uFF0C\u5DF2\u62D2\u7EDD\u4EA4\u7ED9\u6D4F\u89C8\u5668",
6095
- "no-launcher": "\u672A\u80FD\u786E\u5B9A\u8BE5\u7528\u54EA\u4E2A\u6D4F\u89C8\u5668\u6253\u5F00\uFF08\u7CFB\u7EDF\u91CC\u8BFB\u4E0D\u5230\u9ED8\u8BA4\u6D4F\u89C8\u5668\u8BBE\u7F6E\uFF09\uFF0C\u8BF7\u5728\u7CFB\u7EDF\u8BBE\u7F6E\u91CC\u6307\u5B9A\u9ED8\u8BA4\u6D4F\u89C8\u5668\u6216\u624B\u52A8\u6253\u5F00\u8BE5\u5730\u5740",
6923
+ "no-launcher": "\u672C\u673A\u672A\u63A2\u5230\u53EF\u542F\u52A8\u7684\u6D4F\u89C8\u5668\uFF08\u6216\u6240\u9009\u6D4F\u89C8\u5668\u5DF2\u4E0D\u53EF\u6267\u884C\uFF09\uFF0C\u8BF7\u5728\u9762\u677F\u7684\u73AF\u5883\u8868\u5355\u91CC\u786E\u8BA4\uFF0C\u6216\u624B\u52A8\u6253\u5F00\u8BE5\u5730\u5740",
6096
6924
  "spawn-failed": "\u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25\uFF08\u7CFB\u7EDF\u62D2\u7EDD\u4E86\u8BE5\u547D\u4EE4\uFF09",
6097
6925
  "exit-nonzero": "\u7CFB\u7EDF\u62D2\u7EDD\u4E86\u8FD9\u4E2A\u5730\u5740\uFF08\u542F\u52A8\u547D\u4EE4\u975E 0 \u9000\u51FA\uFF09\uFF0C\u7A97\u53E3\u672A\u51FA\u73B0",
6098
6926
  "killed-by-signal": "\u6D4F\u89C8\u5668\u542F\u52A8\u547D\u4EE4\u88AB\u7CFB\u7EDF\u7EC8\u6B62\uFF0C\u7A97\u53E3\u672A\u51FA\u73B0",
@@ -6100,17 +6928,10 @@ var require_browser = __commonJS({
6100
6928
  "unsupported-platform": "\u5F53\u524D\u5E73\u53F0\u4E0D\u5728\u4EA7\u54C1\u652F\u6301\u7684\u684C\u9762\u5E73\u53F0\u5185\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00\u8BE5\u5730\u5740"
6101
6929
  };
6102
6930
  function ownsItsWindow(via, platform) {
6931
+ if (via === "isolated") return true;
6103
6932
  if (via !== "dispatcher") return false;
6104
6933
  return platform !== "win32";
6105
6934
  }
6106
- function pickLauncher(platform, inv) {
6107
- const list = inv && Array.isArray(inv.browsers) ? inv.browsers : [];
6108
- const id = inv && inv.defaultId;
6109
- const hit = id ? list.find((b) => b.id === id) : null;
6110
- if (hit) return { browser: hit, how: inv && inv.defaultSource || "default" };
6111
- if (list.length === 1) return { browser: list[0], how: "only-installed" };
6112
- return { browser: null, how: list.length ? "no-default" : "none-found" };
6113
- }
6114
6935
  function formOfBin(pl, b) {
6115
6936
  const engine = b && b.bin ? engineOf(b.bin) : "other";
6116
6937
  return { engine, direct: !!b && (engine !== "other" || pl === "win32") };
@@ -6118,30 +6939,118 @@ var require_browser = __commonJS({
6118
6939
  function openPlan(platform, url, opts) {
6119
6940
  const o = opts || {};
6120
6941
  const pl = platform || process.platform;
6121
- const picked = o.pick || pickLauncher(pl, o.inventory);
6942
+ const picked = o.pick || environment.pickLauncher(pl, o.inventory, o.preference);
6122
6943
  const b = picked.browser;
6123
6944
  const form = formOfBin(pl, b);
6124
6945
  if (form.direct) {
6125
6946
  return {
6126
6947
  bin: b.bin,
6948
+ args: (b.baseArgs || []).concat([url]),
6127
6949
  engine: form.engine,
6128
6950
  via: "browser",
6951
+ isolated: false,
6952
+ watch: false,
6953
+ envKind: "sys",
6129
6954
  pick: picked.how,
6130
- baseArgs: (b.baseArgs || []).concat([url]),
6955
+ stale: picked.stale === true,
6131
6956
  exitIsEvidence: ownsItsWindow("browser", pl)
6132
6957
  };
6133
6958
  }
6134
6959
  const c = openCommand(pl, url);
6135
- if (!c) return { bin: null, engine: form.engine, via: "none", pick: picked.how, baseArgs: [], exitIsEvidence: false };
6960
+ if (!c) return {
6961
+ bin: null,
6962
+ args: [url],
6963
+ engine: form.engine,
6964
+ via: "none",
6965
+ isolated: false,
6966
+ watch: false,
6967
+ envKind: "sys",
6968
+ pick: picked.how,
6969
+ stale: picked.stale === true,
6970
+ exitIsEvidence: false
6971
+ };
6136
6972
  return {
6137
6973
  bin: c.cmd,
6974
+ args: c.args,
6138
6975
  engine: "other",
6139
6976
  via: "dispatcher",
6977
+ isolated: false,
6978
+ watch: false,
6979
+ envKind: "sys",
6140
6980
  pick: picked.how,
6141
- baseArgs: c.args,
6981
+ stale: picked.stale === true,
6142
6982
  exitIsEvidence: ownsItsWindow("dispatcher", pl)
6143
6983
  };
6144
6984
  }
6985
+ function isolatedPlan(platform, url, opts) {
6986
+ const o = opts || {};
6987
+ const pl = platform || process.platform;
6988
+ const picked = o.pick || environment.pickLauncher(pl, o.inventory, o.preference);
6989
+ const b = picked.browser;
6990
+ const form = formOfBin(pl, b);
6991
+ const baseArgs = b && b.baseArgs || [];
6992
+ const common = { pick: picked.how, stale: picked.stale === true, engine: form.engine };
6993
+ const plain = (bin, label) => Object.assign(common, {
6994
+ bin,
6995
+ args: baseArgs.concat([url]),
6996
+ via: "browser",
6997
+ isolated: false,
6998
+ watch: false,
6999
+ envKind: "sys",
7000
+ label,
7001
+ exitIsEvidence: ownsItsWindow("browser", pl)
7002
+ });
7003
+ if (form.direct && form.engine === "chromium" && o.profileDir) {
7004
+ const args = ["--incognito", "--user-data-dir=" + o.profileDir];
7005
+ if (Array.isArray(o.size) && o.size.length === 2) args.push("--window-size=" + o.size[0] + "," + o.size[1]);
7006
+ if (o.lang) args.push("--lang=" + o.lang);
7007
+ args.push("--no-first-run", "--no-default-browser-check", "--disable-session-crashed-bubble");
7008
+ return Object.assign(common, {
7009
+ bin: b.bin,
7010
+ args: [...baseArgs, ...args, url],
7011
+ via: "isolated",
7012
+ isolated: true,
7013
+ watch: true,
7014
+ envKind: "anti",
7015
+ label: "chromium",
7016
+ exitIsEvidence: ownsItsWindow("isolated", pl)
7017
+ });
7018
+ }
7019
+ if (form.direct && form.engine === "firefox" && o.profileDir) {
7020
+ return Object.assign(common, {
7021
+ bin: b.bin,
7022
+ args: [...baseArgs, "--no-remote", "--profile", o.profileDir, "-private-window", url],
7023
+ via: "isolated",
7024
+ isolated: true,
7025
+ watch: true,
7026
+ envKind: "anti",
7027
+ label: "firefox",
7028
+ exitIsEvidence: ownsItsWindow("isolated", pl)
7029
+ });
7030
+ }
7031
+ if (form.direct) return plain(b.bin, path2.basename(b.bin));
7032
+ const c = openCommand(pl, url);
7033
+ if (!c) return Object.assign(common, {
7034
+ bin: null,
7035
+ args: [url],
7036
+ via: "none",
7037
+ isolated: false,
7038
+ watch: false,
7039
+ envKind: "sys",
7040
+ label: null,
7041
+ exitIsEvidence: false
7042
+ });
7043
+ return Object.assign(common, {
7044
+ bin: c.cmd,
7045
+ args: c.args,
7046
+ via: "dispatcher",
7047
+ isolated: false,
7048
+ watch: false,
7049
+ envKind: "sys",
7050
+ label: c.cmd,
7051
+ exitIsEvidence: ownsItsWindow("dispatcher", pl)
7052
+ });
7053
+ }
6145
7054
  function observeSpawn(child, windowMs, setTimeoutFn) {
6146
7055
  return new Promise((resolve) => {
6147
7056
  let settled = false;
@@ -6159,105 +7068,32 @@ var require_browser = __commonJS({
6159
7068
  });
6160
7069
  }
6161
7070
  var OPEN_OBSERVE_MS = 1500;
6162
- function launchDiagnostics(inv, plan, how) {
7071
+ var LOGIN_TZ_POOL = ["Asia/Shanghai", "Asia/Seoul", "Asia/Tokyo", "Asia/Singapore", "Europe/Berlin", "Europe/London", "Europe/Paris", "America/New_York", "America/Los_Angeles", "Australia/Sydney"];
7072
+ var LOGIN_LANG_POOL = ["zh-CN", "en-US", "en-GB", "ja-JP", "ko-KR", "de-DE", "fr-FR", "zh-TW"];
7073
+ var LOGIN_SIZE_POOL = [[1280, 800], [1366, 768], [1440, 900], [1536, 864], [1600, 900], [1680, 1050], [1920, 1080], [1024, 768], [1152, 864], [1280, 720]];
7074
+ function loginEnv(rand) {
7075
+ const pick = (arr) => arr[Math.floor((typeof rand === "function" ? rand() : Math.random()) * arr.length)];
7076
+ const size = pick(LOGIN_SIZE_POOL);
7077
+ const lang = pick(LOGIN_LANG_POOL);
7078
+ const tz = pick(LOGIN_TZ_POOL);
7079
+ const sysEnv = Object.assign({}, process.env, desktop.sessionEnv());
7080
+ return { size, lang, sysEnv, antiEnv: Object.assign({}, sysEnv, { TZ: tz, LANG: lang }) };
7081
+ }
7082
+ function launchDiagnostics(inv, plan, picked) {
6163
7083
  const found = (inv && inv.browsers || []).map((b) => ({
6164
7084
  name: b.name,
6165
7085
  engine: b.engine || engineOf(b.bin),
6166
7086
  via: (b.sources && b.sources.length ? b.sources : [b.source || "unknown"]).join("+")
6167
7087
  }));
7088
+ const pk = picked || {};
6168
7089
  return {
6169
- platform: inv && inv.platform || null,
6170
- pick: how || plan && plan.pick || "none",
6171
- bin: plan && plan.bin || null,
7090
+ pick: pk.how || plan && plan.pick || "none",
6172
7091
  default: inv && inv.defaultId ? { id: inv.defaultId, source: inv.defaultSource || null } : null,
7092
+ preference: pk.wanted ? { id: pk.wanted, matched: pk.stale !== true } : null,
6173
7093
  found,
6174
7094
  probed: inv && inv.probed || []
6175
7095
  };
6176
7096
  }
6177
- async function openBrowser(url, o) {
6178
- const opts = o || {};
6179
- const pl = opts.platform || process.platform;
6180
- if (!isSafeHttpUrl(url)) {
6181
- return outcome({ ok: false, reason: "unsafe-url", url: String(url || ""), evidence: null });
6182
- }
6183
- if (!SUPPORTED_OPEN_PLATFORMS.includes(pl)) {
6184
- return outcome({ ok: false, reason: "unsupported-platform", url, evidence: { platform: pl } });
6185
- }
6186
- const observeMs = opts.observeMs === void 0 ? OPEN_OBSERVE_MS : opts.observeMs;
6187
- const spawnWith = typeof opts.spawn === "function" ? opts.spawn : _spawnDetachedIgnored;
6188
- const avail = typeof opts.binAvailable === "function" ? opts.binAvailable : binAvailable;
6189
- const observe = typeof opts.observe === "function" ? opts.observe : observeSpawn;
6190
- const resolveInv = typeof opts.resolveInventory === "function" ? opts.resolveInventory : ((platform, deps) => detector.inventory(platform, deps));
6191
- const inv = "inventory" in opts ? opts.inventory : resolveInv(pl, opts.resolveDeps || {});
6192
- const plan = openPlan(pl, url, { inventory: inv, pick: opts.pick });
6193
- const diagnostics = launchDiagnostics(inv, plan, plan.pick);
6194
- const evidence = {
6195
- bin: plan.bin,
6196
- engine: plan.engine,
6197
- via: plan.via,
6198
- ownsWindow: plan.exitIsEvidence === true,
6199
- exitCode: null,
6200
- exitSignal: null,
6201
- error: null,
6202
- diagnostics
6203
- };
6204
- const desktopAvailable = typeof opts.desktopAvailable === "function" ? opts.desktopAvailable : desktop.sessionAvailable;
6205
- if (pl === "linux" && !desktopAvailable()) {
6206
- return outcome({ ok: false, reason: "no-desktop-session", url, evidence });
6207
- }
6208
- if (!plan.bin) {
6209
- return outcome({ ok: false, reason: "no-launcher", url, evidence });
6210
- }
6211
- if (!avail(plan.bin)) {
6212
- return outcome({ ok: false, reason: "no-launcher", url, evidence });
6213
- }
6214
- let child;
6215
- try {
6216
- child = spawnWith(plan.bin, plan.baseArgs);
6217
- } catch (e) {
6218
- evidence.error = e && e.code || String(e);
6219
- return outcome({ ok: false, reason: "spawn-failed", url, evidence });
6220
- }
6221
- if (!child) return outcome({ ok: false, reason: "spawn-failed", url, evidence });
6222
- const seen = await observe(child, observeMs, opts.setTimeout);
6223
- evidence.error = seen.stage === "error" ? String(seen.code) : null;
6224
- evidence.exitCode = seen.code === void 0 ? null : seen.code;
6225
- evidence.exitSignal = seen.signal === void 0 ? null : seen.signal;
6226
- if (seen.stage === "error") return outcome({ ok: false, reason: "spawn-failed", url, evidence });
6227
- const exitDecides = seen.stage === "exit" && plan.exitIsEvidence === true;
6228
- if (exitDecides && (seen.code !== 0 || seen.signal)) {
6229
- return outcome({
6230
- ok: false,
6231
- reason: seen.signal ? "killed-by-signal" : "exit-nonzero",
6232
- url,
6233
- evidence,
6234
- error: FAILURE_TEXT[seen.signal ? "killed-by-signal" : "exit-nonzero"] + "\uFF08" + (seen.signal || seen.code) + "\uFF09"
6235
- });
6236
- }
6237
- return outcome({ ok: true, confirmed: exitDecides, handedOff: !exitDecides, url, evidence });
6238
- }
6239
- function listBrowsers(o) {
6240
- const ov = o || {};
6241
- const inv = detector.inventory(ov.platform, { force: ov.force === true });
6242
- return {
6243
- ok: true,
6244
- platform: inv.platform,
6245
- cached: inv.cached === true,
6246
- default: inv.defaultId ? { id: inv.defaultId, source: inv.defaultSource || null } : null,
6247
- browsers: (inv.browsers || []).map((b) => ({
6248
- id: b.id,
6249
- name: b.name,
6250
- engine: b.engine || engineOf(b.bin),
6251
- bin: b.bin,
6252
- sources: b.sources && b.sources.length ? b.sources : [b.source],
6253
- isDefault: b.id === inv.defaultId
6254
- })),
6255
- probed: inv.probed || []
6256
- };
6257
- }
6258
- function invalidateBrowsers(platform) {
6259
- return detector.invalidate(platform);
6260
- }
6261
7097
  function _spawnDetachedIgnored(bin, args) {
6262
7098
  try {
6263
7099
  const p = spawnOS.detachedIgnored(bin, args);
@@ -6287,86 +7123,120 @@ var require_browser = __commonJS({
6287
7123
  child.unref();
6288
7124
  return child;
6289
7125
  }
6290
- function isolatedPlan(platform, url, opts) {
6291
- const o = opts || {};
6292
- const pl = platform || process.platform;
6293
- const picked = o.pick || pickLauncher(pl, o.inventory);
6294
- const b = picked.browser;
6295
- const form = formOfBin(pl, b);
6296
- const baseArgs = b && b.baseArgs || [];
6297
- if (form.direct && form.engine === "chromium" && o.profileDir) {
6298
- const args = ["--incognito", "--user-data-dir=" + o.profileDir];
6299
- if (Array.isArray(o.size) && o.size.length === 2) args.push("--window-size=" + o.size[0] + "," + o.size[1]);
6300
- if (o.lang) args.push("--lang=" + o.lang);
6301
- args.push("--no-first-run", "--no-default-browser-check", "--disable-session-crashed-bubble");
6302
- return { bin: b.bin, args: [...baseArgs, ...args, url], isolated: true, watch: true, envKind: "anti", label: "chromium", pick: picked.how };
6303
- }
6304
- if (form.direct && form.engine === "firefox" && o.profileDir) {
6305
- return {
6306
- bin: b.bin,
6307
- args: [...baseArgs, "--no-remote", "--profile", o.profileDir, "-private-window", url],
6308
- isolated: true,
6309
- watch: true,
6310
- envKind: "anti",
6311
- label: "firefox",
6312
- pick: picked.how
6313
- };
6314
- }
6315
- if (form.direct) {
6316
- return { bin: b.bin, args: baseArgs.concat([url]), isolated: false, watch: false, envKind: "sys", label: path2.basename(b.bin), pick: picked.how };
6317
- }
6318
- const c = openCommand(pl, url);
6319
- if (!c) return { bin: null, args: [url], isolated: false, watch: false, envKind: "sys", label: null, pick: picked.how };
6320
- return { bin: c.cmd, args: c.args, isolated: false, watch: false, envKind: "sys", label: c.cmd, pick: picked.how };
6321
- }
6322
7126
  function binAvailable(bin) {
6323
7127
  if (!bin) return false;
6324
7128
  if (bin.includes("/") || bin.includes("\\") || /^[A-Za-z]:[\\/]/.test(bin)) return isExecutableFile(bin);
6325
7129
  return resolveExecutable(bin) !== null;
6326
7130
  }
6327
- function launchIsolated(url, o) {
7131
+ async function openBrowser(url, o) {
6328
7132
  const opts = o || {};
6329
- if (!isSafeHttpUrl(url)) return Object.assign(outcome({ ok: false, reason: "unsafe-url", url: String(url || "") }), { bin: null, isolated: false });
6330
- const antiEnv = opts.antiEnv || opts.sysEnv || process.env;
6331
- const sysEnv = opts.sysEnv || process.env;
6332
- const onExit = opts.onExit;
7133
+ const pl = opts.platform || process.platform;
7134
+ const intent = opts.intent === "isolated-login" ? "isolated-login" : "plain";
7135
+ if (!isSafeHttpUrl(url)) {
7136
+ return outcome({ ok: false, reason: "unsafe-url", url: String(url || ""), evidence: null });
7137
+ }
7138
+ if (!SUPPORTED_OPEN_PLATFORMS.includes(pl)) {
7139
+ return outcome({ ok: false, reason: "unsupported-platform", url, evidence: { platform: pl } });
7140
+ }
7141
+ const observeMs = opts.observeMs === void 0 ? OPEN_OBSERVE_MS : opts.observeMs;
7142
+ const spawnWith = typeof opts.spawn === "function" ? opts.spawn : intent === "isolated-login" ? _spawnDetached : _spawnDetachedIgnored;
6333
7143
  const avail = typeof opts.binAvailable === "function" ? opts.binAvailable : binAvailable;
6334
- const spawnWith = typeof opts.spawn === "function" ? opts.spawn : _spawnDetached;
6335
- const fail = (reason, extra) => Object.assign(outcome(Object.assign({ ok: false, reason, url }, extra)), { bin: null, isolated: false });
7144
+ const observe = typeof opts.observe === "function" ? opts.observe : observeSpawn;
7145
+ const resolveInv = typeof opts.resolveInventory === "function" ? opts.resolveInventory : ((platform, deps) => environment.browsers(Object.assign({ platform }, deps)));
7146
+ const inv = "inventory" in opts ? opts.inventory : resolveInv(pl, opts.resolveDeps || {});
7147
+ const pref = "preference" in opts ? opts.preference : void 0;
7148
+ const login = intent === "isolated-login" ? loginEnv(opts.rand) : null;
7149
+ const picked = opts.pick || environment.pickLauncher(pl, inv, pref);
7150
+ const form = formOfBin(pl, picked.browser);
7151
+ const canIsolate = intent === "isolated-login" && form.direct && (form.engine === "chromium" || form.engine === "firefox");
7152
+ const cold = canIsolate ? await environment.checkEgress(url, opts) : null;
7153
+ const isolate = canIsolate && (!cold || cold.viable !== false);
7154
+ let profile = null;
7155
+ if (isolate) {
7156
+ const alloc = typeof opts.allocProfile === "function" ? opts.allocProfile : (() => allocTempDir("dsh-login-"));
7157
+ try {
7158
+ profile = opts.profileDir || alloc();
7159
+ } catch {
7160
+ profile = null;
7161
+ }
7162
+ }
7163
+ const plan = intent === "isolated-login" ? isolatedPlan(pl, url, { inventory: inv, pick: picked, profileDir: profile, size: login.size, lang: login.lang }) : openPlan(pl, url, { inventory: inv, pick: picked });
7164
+ const diagnostics = launchDiagnostics(inv, plan, picked);
7165
+ const evidence = {
7166
+ bin: plan.bin,
7167
+ engine: plan.engine,
7168
+ via: plan.via,
7169
+ ownsWindow: plan.exitIsEvidence === true,
7170
+ isolated: plan.isolated === true,
7171
+ profile: plan.isolated ? profile : null,
7172
+ watch: plan.watch === true,
7173
+ exitCode: null,
7174
+ exitSignal: null,
7175
+ error: null,
7176
+ diagnostics,
7177
+ egress: cold
7178
+ };
7179
+ const downgraded = cold && cold.viable === false;
7180
+ const downgradeText = downgraded ? "\u672C\u673A\u76F4\u8FDE " + cold.host + " \u4E0D\u901A\u4E14\u6CA1\u6709\u5728\u7528\u7CFB\u7EDF\u4EE3\u7406\uFF0C\u9694\u79BB\u7A97\u53E3\u4F1A\u662F\u7A7A\u767D\u9875\uFF1B\u5DF2\u5728\u73B0\u6709\u6D4F\u89C8\u5668\u7A97\u53E3\u6253\u5F00\u8BE5\u5730\u5740\uFF08\u767B\u5F55\u5B8C\u6210\u540E\u8BF7\u624B\u52A8\u6E05\u7406\u8D26\u53F7\uFF0C\u6216\u5148\u5728\u7CFB\u7EDF\u91CC\u914D\u597D\u4EE3\u7406\uFF09" : null;
7181
+ const deferredRemove = typeof opts.rmTree === "function" ? opts.rmTree : removeTreeDeferred;
7182
+ const fail = (reason, patch) => {
7183
+ if (profile) deferredRemove(profile, 0);
7184
+ return outcome(Object.assign({ ok: false, reason, url, evidence }, patch));
7185
+ };
7186
+ const desktopAvailable = typeof opts.desktopAvailable === "function" ? opts.desktopAvailable : desktop.sessionAvailable;
7187
+ if (pl === "linux" && !desktopAvailable()) {
7188
+ return fail("no-desktop-session");
7189
+ }
7190
+ if (!plan.bin) return fail("no-launcher");
7191
+ if (!avail(plan.bin)) return fail("no-launcher");
7192
+ if (isolate && !profile) {
7193
+ evidence.error = "\u4E34\u65F6 profile \u76EE\u5F55\u5206\u914D\u5931\u8D25";
7194
+ return fail("spawn-failed");
7195
+ }
7196
+ let child;
6336
7197
  try {
6337
- const resolveInv = typeof opts.resolveInventory === "function" ? opts.resolveInventory : ((platform, deps) => detector.inventory(platform, deps));
6338
- const inv = "inventory" in opts ? opts.inventory : resolveInv(process.platform, opts.resolveDeps || {});
6339
- const plan = isolatedPlan(process.platform, url, {
6340
- inventory: inv,
6341
- pick: opts.pick,
6342
- profileDir: opts.profileDir,
6343
- size: opts.size,
6344
- lang: opts.lang
7198
+ child = intent === "isolated-login" ? spawnWith(plan.bin, plan.args, plan.envKind === "anti" ? login.antiEnv : login.sysEnv, plan.watch ? opts.onExit : void 0) : spawnWith(plan.bin, plan.args);
7199
+ } catch (e) {
7200
+ evidence.error = e && e.code || String(e);
7201
+ return fail("spawn-failed");
7202
+ }
7203
+ if (!child) return fail("spawn-failed");
7204
+ if (evidence.profile) deferredRemove(evidence.profile, opts.profileMs === void 0 ? 30 * 60 * 1e3 : opts.profileMs);
7205
+ const seen = await observe(child, observeMs, opts.setTimeout);
7206
+ evidence.error = seen.stage === "error" ? String(seen.code) : null;
7207
+ evidence.exitCode = seen.code === void 0 ? null : seen.code;
7208
+ evidence.exitSignal = seen.signal === void 0 ? null : seen.signal;
7209
+ if (seen.stage === "error") return outcome({ ok: false, reason: "spawn-failed", url, evidence });
7210
+ const exitDecides = seen.stage === "exit" && plan.exitIsEvidence === true;
7211
+ if (exitDecides && (seen.code !== 0 || seen.signal)) {
7212
+ return outcome({
7213
+ ok: false,
7214
+ reason: seen.signal ? "killed-by-signal" : "exit-nonzero",
7215
+ url,
7216
+ evidence,
7217
+ error: FAILURE_TEXT[seen.signal ? "killed-by-signal" : "exit-nonzero"] + "\uFF08" + (seen.signal || seen.code) + "\uFF09"
6345
7218
  });
6346
- const evidence = { bin: plan.bin, engine: plan.label, via: plan.isolated ? "browser" : "dispatcher", diagnostics: launchDiagnostics(inv, plan, plan.pick) };
6347
- if (!avail(plan.bin)) return fail("no-launcher", { evidence });
6348
- const env = plan.envKind === "anti" ? antiEnv : sysEnv;
6349
- const p = spawnWith(plan.bin, plan.args, env, plan.watch ? onExit : void 0);
6350
- if (!p) return fail("spawn-failed", { evidence });
6351
- return Object.assign(outcome({ ok: true, confirmed: false, handedOff: true, url, evidence }), { bin: plan.label, isolated: plan.isolated });
6352
- } catch (e) {
6353
- return fail("spawn-failed", { error: FAILURE_TEXT["spawn-failed"] + "\uFF08" + (e && e.code || e) + "\uFF09" });
6354
7219
  }
7220
+ return outcome({
7221
+ ok: true,
7222
+ confirmed: exitDecides,
7223
+ handedOff: !exitDecides,
7224
+ url,
7225
+ evidence,
7226
+ message: downgradeText || void 0
7227
+ });
6355
7228
  }
6356
7229
  module2.exports = {
6357
7230
  openBrowser,
6358
- launchIsolated,
6359
7231
  openCommand,
6360
7232
  openPlan,
6361
7233
  isolatedPlan,
6362
- pickLauncher,
6363
7234
  ownsItsWindow,
6364
7235
  observeSpawn,
6365
7236
  isSafeHttpUrl,
6366
7237
  binAvailable,
6367
- listBrowsers,
6368
- invalidateBrowsers,
6369
7238
  launchDiagnostics,
7239
+ loginEnv,
6370
7240
  // 探测层的解析原语经此转出(唯一实现处在 ./browser-inventory.js):门禁与消费方按同一出口取用,
6371
7241
  // 不在别处再写第二份平台解析。
6372
7242
  engineOf,
@@ -6723,6 +7593,7 @@ var require_os = __commonJS({
6723
7593
  var execPath = require_exec_path();
6724
7594
  var desktop = require_desktop();
6725
7595
  var CAPABILITY_PROFILES = require_capability_profile();
7596
+ var environment = require_environment();
6726
7597
  var PLATFORM = process.platform;
6727
7598
  var ARCH = process.arch;
6728
7599
  var isLinux = PLATFORM === "linux";
@@ -6777,6 +7648,7 @@ var require_os = __commonJS({
6777
7648
  }
6778
7649
  return p;
6779
7650
  }
7651
+ environment.bind({ capabilities });
6780
7652
  module2.exports = {
6781
7653
  PLATFORM,
6782
7654
  ARCH,
@@ -6800,6 +7672,8 @@ var require_os = __commonJS({
6800
7672
  // 导出模块对象会抛 platform.notify is not a function,把升级终态误判成失败。
6801
7673
  notify: require_notify().notify,
6802
7674
  browser: require_browser(),
7675
+ environment,
7676
+ // 环境表单(外部打开链路的事实底座 + 选路依据)
6803
7677
  desktop,
6804
7678
  // 图形会话可用性(Linux 需实测 socket)
6805
7679
  autostart: require_autostart()
@@ -6818,6 +7692,7 @@ var require_bootstrap = __commonJS({
6818
7692
  var { createShellWatchdog } = require_watchdog();
6819
7693
  var pidlook = require_pidlookup();
6820
7694
  var platform = require_os();
7695
+ var ENV_FORM_STARTUP_DELAY_MS = 3e3;
6821
7696
  function _bootstrap(host2) {
6822
7697
  try {
6823
7698
  host2.events.append("guard_started", {
@@ -6833,6 +7708,15 @@ var require_bootstrap = __commonJS({
6833
7708
  } catch {
6834
7709
  }
6835
7710
  host2.logger.info("guard started v" + host2.guardVersion + " pid=" + process.pid);
7711
+ host2._startupFacts = {
7712
+ bootAt: Date.now(),
7713
+ envDelayMs: ENV_FORM_STARTUP_DELAY_MS,
7714
+ routerAutostart: host2.config.routerAutostart === true,
7715
+ routerMode: null,
7716
+ updateCheck: null,
7717
+ shellWatchdog: null,
7718
+ lastRefresh: null
7719
+ };
6836
7720
  host2.tick();
6837
7721
  host2._timer = host2.managedObjects ? null : setInterval(() => host2.tick(), host2.config.probeIntervalMs);
6838
7722
  host2._lastHeartbeatAt = Date.now();
@@ -6888,6 +7772,7 @@ var require_bootstrap = __commonJS({
6888
7772
  }
6889
7773
  if (host2._routerDaemonActive()) host2._writeRouterDaemonLock();
6890
7774
  const rt = host2._ensureRouterRuntime(true);
7775
+ host2._startupFacts.routerMode = rt.mode;
6891
7776
  if (rt.mode === "daemon") {
6892
7777
  host2._disableRouterPersist();
6893
7778
  if (rt.spawned) {
@@ -6928,13 +7813,20 @@ var require_bootstrap = __commonJS({
6928
7813
  rlc._monitoring = false;
6929
7814
  }
6930
7815
  }
7816
+ const checkDelayMs = host2.config.initialCheckDelayMs || 2e4;
7817
+ const checkIntervalMs = host2.config.updateCheckIntervalMs || 36e5;
7818
+ host2._startupFacts.updateCheck = {
7819
+ enabled: host2.config.updateCheckEnabled !== false,
7820
+ initialDelayMs: checkDelayMs,
7821
+ intervalMs: checkIntervalMs
7822
+ };
6931
7823
  if (host2.config.updateCheckEnabled !== false) {
6932
7824
  host2._initialCheckTimer = setTimeout(() => {
6933
7825
  host2.nativeManager.checkUpdate();
6934
- }, host2.config.initialCheckDelayMs || 2e4);
7826
+ }, checkDelayMs);
6935
7827
  host2._upgradeTimer = setInterval(() => {
6936
7828
  host2.nativeManager.checkUpdate();
6937
- }, host2.config.updateCheckIntervalMs || 36e5);
7829
+ }, checkIntervalMs);
6938
7830
  }
6939
7831
  try {
6940
7832
  host2._startShellWatchdog();
@@ -6943,6 +7835,50 @@ var require_bootstrap = __commonJS({
6943
7835
  host2.logger.warn("[shell-watchdog] \u542F\u52A8\u5F02\u5E38\uFF08\u4E0D\u5F71\u54CD\u5B88\u536B\u4E3B\u5FAA\u73AF\uFF09: " + (e && e.message || e));
6944
7836
  }
6945
7837
  }
7838
+ host2._startupFacts.shellWatchdog = !!host2.shellWatchdog;
7839
+ const envTimer = setTimeout(() => {
7840
+ try {
7841
+ host2._refreshEnvironmentForm();
7842
+ } catch (e) {
7843
+ host2.logger.warn && host2.logger.warn("[environment] \u542F\u52A8\u5237\u65B0\u5F02\u5E38\uFF08\u4E0D\u5F71\u54CD\u5B88\u536B\u4E3B\u5FAA\u73AF\uFF09: " + (e && e.message || e));
7844
+ }
7845
+ }, ENV_FORM_STARTUP_DELAY_MS);
7846
+ if (envTimer && envTimer.unref) envTimer.unref();
7847
+ }
7848
+ function _refreshEnvironmentForm(host2) {
7849
+ const startedAt = Date.now();
7850
+ return platform.environment.refresh({ persist: true }).then((f) => {
7851
+ const secs = f && f.sections || {};
7852
+ const pending = Object.keys(secs).filter((id) => secs[id] && secs[id].state === "pending");
7853
+ const failed = Object.keys(secs).filter((id) => secs[id] && secs[id].state === "error");
7854
+ const snap = f && f.snapshot ? f.snapshot : {};
7855
+ if (host2._startupFacts) {
7856
+ const states = {};
7857
+ for (const id of Object.keys(secs)) states[id] = secs[id] ? secs[id].state : "missing";
7858
+ host2._startupFacts.lastRefresh = {
7859
+ at: f && f.at || null,
7860
+ tookMs: Date.now() - startedAt,
7861
+ dims: states,
7862
+ browsers: f && f.browsers && f.browsers.length || 0,
7863
+ pick: f && f.pick && f.pick.how || null,
7864
+ snapshotWritten: snap.written === true
7865
+ };
7866
+ }
7867
+ if (snap.written === false && snap.error) {
7868
+ host2.logger.warn && host2.logger.warn("[environment] \u5FEB\u7167\u672A\u843D\u76D8: " + snap.error);
7869
+ }
7870
+ if (failed.length) {
7871
+ host2.logger.warn && host2.logger.warn("[environment] \u7EF4\u5EA6\u63A2\u6D4B\u5931\u8D25: " + failed.map((id) => id + "(" + secs[id].error + ")").join("; "));
7872
+ }
7873
+ if (pending.length) {
7874
+ host2.logger.warn && host2.logger.warn("[environment] \u7EF4\u5EA6\u65E0\u63A2\u9488\uFF0C\u672C\u673A\u8BE5\u9879\u4E0D\u53EF\u5224: " + pending.join(","));
7875
+ }
7876
+ host2.logger.info && host2.logger.info("[environment] \u5DF2\u5237\u65B0\uFF1A" + Object.keys(secs).length + " \u4E2A\u7EF4\u5EA6\uFF0C\u6D4F\u89C8\u5668\u5019\u9009 " + (f && f.browsers && f.browsers.length || 0) + " \u4E2A\uFF0C\u5206\u53D1\u4F9D\u636E " + (f && f.pick && f.pick.how || "?"));
7877
+ return f;
7878
+ }).catch((e) => {
7879
+ host2.logger.warn && host2.logger.warn("[environment] \u5237\u65B0\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u5B88\u536B\u4E3B\u5FAA\u73AF\uFF09: " + (e && e.message || e));
7880
+ return null;
7881
+ });
6946
7882
  }
6947
7883
  function _startShellWatchdog(host2) {
6948
7884
  if (host2.config.shellWatchdog === false) {
@@ -7012,7 +7948,7 @@ var require_bootstrap = __commonJS({
7012
7948
  }
7013
7949
  }
7014
7950
  }
7015
- module2.exports = { _bootstrap, _startShellWatchdog, _registerFixedPorts, _bindNativeDshCommand };
7951
+ module2.exports = { _bootstrap, _startShellWatchdog, _registerFixedPorts, _bindNativeDshCommand, _refreshEnvironmentForm };
7016
7952
  }
7017
7953
  });
7018
7954
 
@@ -7542,7 +8478,7 @@ var require_release = __commonJS({
7542
8478
  });
7543
8479
 
7544
8480
  // src/platform/contract/registry.js
7545
- var require_registry = __commonJS({
8481
+ var require_registry2 = __commonJS({
7546
8482
  "src/platform/contract/registry.js"(exports2, module2) {
7547
8483
  "use strict";
7548
8484
  var fs2 = require("node:fs");
@@ -7658,7 +8594,7 @@ var require_registry_config = __commonJS({
7658
8594
  "use strict";
7659
8595
  var fs2 = require("node:fs");
7660
8596
  var path2 = require("node:path");
7661
- var registryContract = require_registry();
8597
+ var registryContract = require_registry2();
7662
8598
  var { writeAtomic } = require_fs();
7663
8599
  var policies = require_policies();
7664
8600
  var CONTRACT_TTL_MS = 60 * 1e3;
@@ -7816,7 +8752,7 @@ var require_matrix = __commonJS({
7816
8752
  });
7817
8753
 
7818
8754
  // src/platform/distribution/registry.js
7819
- var require_registry2 = __commonJS({
8755
+ var require_registry3 = __commonJS({
7820
8756
  "src/platform/distribution/registry.js"(exports2, module2) {
7821
8757
  "use strict";
7822
8758
  var matrix = require_matrix();
@@ -8037,7 +8973,7 @@ var require_version_check = __commonJS({
8037
8973
  var { VERSION_RE } = require_version2();
8038
8974
  var input = require_input();
8039
8975
  var policies = require_policies();
8040
- var registry = require_registry2();
8976
+ var registry = require_registry3();
8041
8977
  var ref = require_registry_ref();
8042
8978
  var release = require_release();
8043
8979
  var PKG_NAME_RE = input.PKG_NAME_RE;
@@ -8404,7 +9340,7 @@ var require_distribution = __commonJS({
8404
9340
  var policies = require_policies();
8405
9341
  var release = require_release();
8406
9342
  var registryConfig = require_registry_config();
8407
- var registry = require_registry2();
9343
+ var registry = require_registry3();
8408
9344
  var versionCheck = require_version_check();
8409
9345
  var install = require_install();
8410
9346
  var { semverCompare, VERSION_RE } = require_version2();
@@ -12918,6 +13854,60 @@ var require_access = __commonJS({
12918
13854
  }
12919
13855
  });
12920
13856
 
13857
+ // src/app/settings/browser.js
13858
+ var require_browser2 = __commonJS({
13859
+ "src/app/settings/browser.js"(exports2, module2) {
13860
+ "use strict";
13861
+ var platform = require_os();
13862
+ var { verifyPersisted } = require_access();
13863
+ module2.exports = {
13864
+ methods: {
13865
+ /** 当前偏好 + 候选清单 + 这一拍的分发依据(面板据此渲染选择器并说明「现在实际会用谁」)。 */
13866
+ externalBrowserStatus() {
13867
+ try {
13868
+ const form = platform.environment.form();
13869
+ const v = platform.environment.checkPreference(platform.environment.preferenceId(), form);
13870
+ return {
13871
+ ok: true,
13872
+ configured: !!form.preference && form.preference.configured === true,
13873
+ value: form.preference && form.preference.id || null,
13874
+ // stale=true:偏好所指已不在候选清单(被卸载/路径失效),本次分发已回落,需用户重选。
13875
+ stale: !!(form.pick && form.pick.stale),
13876
+ browser: v.browser,
13877
+ candidates: v.candidates,
13878
+ pick: form.pick || null,
13879
+ platform: form.platform
13880
+ };
13881
+ } catch (e) {
13882
+ return { ok: false, error: e.message };
13883
+ }
13884
+ },
13885
+ /** 设置/清除浏览器偏好(空串=清除,回到「按系统默认或候选次序分发」)。 */
13886
+ setExternalBrowser(id) {
13887
+ try {
13888
+ const form = platform.environment.form();
13889
+ const v = platform.environment.checkPreference(id, form);
13890
+ if (!v.ok) return { ok: false, error: v.error };
13891
+ const cfg = this.config || {};
13892
+ cfg.externalBrowser = v.id;
13893
+ const patch = { externalBrowser: v.id };
13894
+ if (this.configPath) {
13895
+ this.state.persistConfigPatch(patch);
13896
+ const persistError = verifyPersisted(this.configPath, patch);
13897
+ if (persistError) return { ok: false, error: persistError };
13898
+ }
13899
+ platform.environment.invalidate();
13900
+ if (this.events) this.events.append("external_browser_changed", { id: v.id, name: v.browser ? v.browser.name : null });
13901
+ return { ok: true, configured: !!v.id, value: v.id, browser: v.browser };
13902
+ } catch (e) {
13903
+ return { ok: false, error: e.message };
13904
+ }
13905
+ }
13906
+ }
13907
+ };
13908
+ }
13909
+ });
13910
+
12921
13911
  // src/platform/os/netinfo.js
12922
13912
  var require_netinfo = __commonJS({
12923
13913
  "src/platform/os/netinfo.js"(exports2, module2) {
@@ -13135,6 +14125,7 @@ var require_facets = __commonJS({
13135
14125
  { name: "settings/node-lts", mod: require_node_lts() },
13136
14126
  { name: "settings/versions", mod: require_versions() },
13137
14127
  { name: "settings/access", mod: require_access() },
14128
+ { name: "settings/browser", mod: require_browser2() },
13138
14129
  { name: "settings/lan-panel", mod: require_lan_panel() }
13139
14130
  ];
13140
14131
  function installMethods(host2, methods) {
@@ -14016,6 +15007,77 @@ var require_token = __commonJS({
14016
15007
  }
14017
15008
  });
14018
15009
 
15010
+ // src/app/native/npm.js
15011
+ var require_npm = __commonJS({
15012
+ "src/app/native/npm.js"(exports2, module2) {
15013
+ "use strict";
15014
+ var ex2 = require_exec();
15015
+ var runtimeContract = require_runtime();
15016
+ function npmLaunch(host2) {
15017
+ const h = host2 || {};
15018
+ if (h._npmBin) {
15019
+ return { program: h._npmBin, args: Array.isArray(h._npmBinArgs) ? h._npmBinArgs.slice() : [] };
15020
+ }
15021
+ const l = runtimeContract.npmLauncher();
15022
+ return { program: l.program, args: l.args };
15023
+ }
15024
+ async function resolveNpmRoot(host2, opts) {
15025
+ if (host2.npmRoot) return host2.npmRoot;
15026
+ const l = npmLaunch(host2);
15027
+ const r = await ex2.runOutAsync(l.program, l.args.concat(["root", "-g"]), opts || void 0);
15028
+ return r ? r.trim() : null;
15029
+ }
15030
+ async function checkEnvironment(host2) {
15031
+ const errors = [];
15032
+ const l = npmLaunch(host2);
15033
+ const [nv, npmv, npmRoot] = await Promise.all([
15034
+ ex2.runOutAsync("node", ["--version"]),
15035
+ ex2.runOutAsync(l.program, l.args.concat(["--version"])),
15036
+ resolveNpmRoot(host2)
15037
+ ]);
15038
+ if (!nv || !nv.trim()) errors.push("node \u672A\u5B89\u88C5\u6216\u4E0D\u53EF\u6267\u884C");
15039
+ if (!npmv || !npmv.trim()) errors.push("npm \u672A\u5B89\u88C5\u6216\u4E0D\u53EF\u6267\u884C");
15040
+ return { ok: errors.length === 0, errors, npmRoot };
15041
+ }
15042
+ async function latestVersion(host2) {
15043
+ if (!host2.dist || !host2.config.packageName) throw new Error("\u5206\u53D1\u670D\u52A1\u672A\u521D\u59CB\u5316\uFF0C\u65E0\u6CD5\u67E5\u8BE2\u6700\u65B0\u7248\u672C");
15044
+ const channel = host2.config.releaseChannel || "npm";
15045
+ return host2.dist.fetchVersionInfo(host2.config.packageName, channel);
15046
+ }
15047
+ async function selectRegistry(host2) {
15048
+ if (!host2.dist) return null;
15049
+ try {
15050
+ return await host2.dist.registryOrigin(true);
15051
+ } catch {
15052
+ return null;
15053
+ }
15054
+ }
15055
+ function runNpm(host2, opts) {
15056
+ const a = opts && opts.action || "install";
15057
+ const verb = a === "uninstall" ? "\u5378\u8F7D" : "\u5B89\u88C5";
15058
+ if (!host2.dist) return Promise.resolve({ ok: false, error: "dist \u5206\u53D1\u670D\u52A1\u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5" + verb, output: [] });
15059
+ const tpl = a === "install" ? host2.config.installCommandTemplate : null;
15060
+ return host2.dist.runNpmInstall({
15061
+ action: a,
15062
+ pkg: host2.config.packageName || "@deepseek-ai/dsh",
15063
+ version: opts && opts.version,
15064
+ // 前缀单源:装与卸必须落在同一个全局前缀,否则「卸载成功」而包还在原地。
15065
+ prefix: host2.npmRoot || null,
15066
+ registry: opts && opts.registry,
15067
+ // 只在显式注入过 npm 形态时接管启动形态;生产留空,由执行器按运行期契约解析一次。
15068
+ launcher: host2._npmBin ? npmLaunch(host2) : null,
15069
+ commandTemplate: Array.isArray(tpl) && tpl.length ? tpl : null,
15070
+ timeoutMs: a === "uninstall" ? host2.config.uninstallTimeoutMs : host2.config.upgradeTimeoutMs,
15071
+ onLine: (l) => {
15072
+ host2._appendUpgradeLog(l);
15073
+ if (host2.installing) host2._appendInstallLog(l);
15074
+ }
15075
+ });
15076
+ }
15077
+ module2.exports = { npmLaunch, resolveNpmRoot, checkEnvironment, latestVersion, selectRegistry, runNpm };
15078
+ }
15079
+ });
15080
+
14019
15081
  // src/domains/shell/journal.js
14020
15082
  var require_journal = __commonJS({
14021
15083
  "src/domains/shell/journal.js"(exports2, module2) {
@@ -14668,6 +15730,9 @@ var require_core5 = __commonJS({
14668
15730
  var { extension: domainConfigExtension2 } = require_domain_config();
14669
15731
  var logcore = require_logcore();
14670
15732
  var { DshTokenService } = require_token();
15733
+ var { EnvCatalog } = require_env_catalog();
15734
+ var npm = require_npm();
15735
+ var FORM_NPM_ROOT_TIMEOUT_MS = 4e3;
14671
15736
  var { DistributionManager } = require_distribution();
14672
15737
  var shellDomain = require_shell();
14673
15738
  var { TaskRegistry } = require_tasks();
@@ -14677,6 +15742,69 @@ var require_core5 = __commonJS({
14677
15742
  function composeCore(host2, rawConfig, configPath) {
14678
15743
  host2.config = normalize(rawConfig, domainConfigExtension2());
14679
15744
  host2.configPath = typeof configPath === "string" ? configPath : null;
15745
+ platform.environment.bind({ preference: () => host2.config && host2.config.externalBrowser || null });
15746
+ platform.environment.registerSection("runtime", {
15747
+ label: "\u8FD0\u884C\u65F6\uFF08Node/npm/git/\u955C\u50CF\u6E90/\u5168\u5C40\u524D\u7F00\uFF09",
15748
+ probe: async () => {
15749
+ const cat = await new EnvCatalog(host2.config).probeAsync();
15750
+ const m = host2.nativeManager;
15751
+ const [reg, prefix] = await Promise.all([
15752
+ m && m.dist && typeof m.dist.registryInfo === "function" ? Promise.resolve(m.dist.registryInfo()).catch(() => null) : Promise.resolve(null),
15753
+ // 前缀实测就是安装/卸载用的那同一个解析口,只把超时收到表单预算内(默认 15s 会吃掉面板刷新)。
15754
+ m ? npm.resolveNpmRoot(m, { timeoutMs: FORM_NPM_ROOT_TIMEOUT_MS }).catch(() => null) : Promise.resolve(null)
15755
+ ]);
15756
+ return {
15757
+ node: cat.node,
15758
+ npm: cat.npm,
15759
+ git: cat.git,
15760
+ registry: reg ? {
15761
+ origin: reg.origin || null,
15762
+ mode: reg.mode || null,
15763
+ source: reg.source || null,
15764
+ manualOrigin: reg.manualOrigin || null,
15765
+ candidates: (reg.registries || []).map((r) => ({
15766
+ base: r.base,
15767
+ reachable: r.reachable === void 0 ? null : r.reachable,
15768
+ latencyMs: r.latencyMs === void 0 ? null : r.latencyMs,
15769
+ error: r.error || null
15770
+ }))
15771
+ } : null,
15772
+ // 全局前缀只来自 `npm root -g` 的实测,拿不到就是 null:装/卸落在哪个前缀是排障第一问,
15773
+ // 猜一个只会指错地方(壳侧的同名读数在 E4 上报接通后进这一维)。
15774
+ prefix: prefix || null
15775
+ };
15776
+ }
15777
+ });
15778
+ platform.environment.registerSection("dsh", {
15779
+ label: "DSH \u672C\u4F53\u4E0E\u5185\u6838\u66F4\u65B0",
15780
+ probe: async () => {
15781
+ const cat = new EnvCatalog(host2.config);
15782
+ const d = typeof host2.dshenvStatus === "function" ? host2.dshenvStatus() : null;
15783
+ if (!d) return null;
15784
+ return {
15785
+ dsh: cat.dshEntry(d.binOk, d.installed, d.bin),
15786
+ selfUpdate: cat.selfUpdateEntry(),
15787
+ managed: d.managed === true,
15788
+ phase: d.phase || null
15789
+ };
15790
+ }
15791
+ });
15792
+ platform.environment.registerSection("startup", {
15793
+ label: "\u542F\u52A8\u65E2\u6210\u4E8B\u5B9E\uFF08\u5B88\u536B\u8FD9\u4E00\u62CD\u8DD1\u8FC7\u4EC0\u4E48\uFF09",
15794
+ probe: () => {
15795
+ const s = host2._startupFacts;
15796
+ if (!s) return null;
15797
+ return {
15798
+ bootAt: s.bootAt,
15799
+ envDelayMs: s.envDelayMs,
15800
+ routerAutostart: s.routerAutostart,
15801
+ routerMode: s.routerMode,
15802
+ updateCheck: s.updateCheck,
15803
+ shellWatchdog: s.shellWatchdog,
15804
+ lastRefresh: s.lastRefresh
15805
+ };
15806
+ }
15807
+ });
14680
15808
  host2._fileProtectStatus = null;
14681
15809
  try {
14682
15810
  const fp = platform.fileProtect;
@@ -18211,58 +19339,13 @@ var require_forward_core = __commonJS({
18211
19339
  }
18212
19340
  });
18213
19341
 
18214
- // src/domains/router/ops/browser.js
18215
- var require_browser2 = __commonJS({
18216
- "src/domains/router/ops/browser.js"(exports2, module2) {
18217
- "use strict";
18218
- var platform = require_os();
18219
- var desktop = require_desktop();
18220
- var fs2 = require("node:fs");
18221
- var os2 = require("node:os");
18222
- var path2 = require("node:path");
18223
- var crypto = require("node:crypto");
18224
- function graphicalEnv() {
18225
- return desktop.sessionEnv();
18226
- }
18227
- function openInBrowser(url, onExit) {
18228
- try {
18229
- const tmpProfile = path2.join(os2.tmpdir(), "dsh-oauth-" + crypto.randomBytes(8).toString("hex"));
18230
- const sysEnv = Object.assign({}, process.env, graphicalEnv());
18231
- const TZ_POOL = ["Asia/Shanghai", "Asia/Seoul", "Asia/Tokyo", "Asia/Singapore", "Europe/Berlin", "Europe/London", "Europe/Paris", "America/New_York", "America/Los_Angeles", "Australia/Sydney"];
18232
- const LANG_POOL = ["zh-CN", "en-US", "en-GB", "ja-JP", "ko-KR", "de-DE", "fr-FR", "zh-TW"];
18233
- const SIZE_POOL = [[1280, 800], [1366, 768], [1440, 900], [1536, 864], [1600, 900], [1680, 1050], [1920, 1080], [1024, 768], [1152, 864], [1280, 720]];
18234
- const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
18235
- const size = pick(SIZE_POOL);
18236
- const lang = pick(LANG_POOL);
18237
- const tz = pick(TZ_POOL);
18238
- const antiEnv = Object.assign({}, sysEnv, { TZ: tz, LANG: lang });
18239
- const r = platform.browser.launchIsolated(url, { profileDir: tmpProfile, size, lang, antiEnv, sysEnv, onExit });
18240
- if (!r || !r.ok) return { profile: null, result: r || { ok: false, reason: "spawn-failed" } };
18241
- if (r.isolated) {
18242
- const t30 = setTimeout(() => {
18243
- try {
18244
- fs2.rmSync(tmpProfile, { recursive: true, force: true });
18245
- } catch {
18246
- }
18247
- }, 30 * 60 * 1e3);
18248
- if (t30.unref) t30.unref();
18249
- }
18250
- return { profile: tmpProfile, result: r };
18251
- } catch (e) {
18252
- return { profile: null, result: { ok: false, reason: "spawn-failed", error: "\u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25\uFF1A" + (e && e.message || e) } };
18253
- }
18254
- }
18255
- module2.exports = { graphicalEnv, openInBrowser };
18256
- }
18257
- });
18258
-
18259
19342
  // src/domains/router/ops/oauth.js
18260
19343
  var require_oauth = __commonJS({
18261
19344
  "src/domains/router/ops/oauth.js"(exports2, module2) {
18262
19345
  "use strict";
18263
- var fs2 = require("node:fs");
18264
19346
  var crypto = require("node:crypto");
18265
19347
  var { createServer } = require("node:http");
19348
+ var { removeTreeDeferred } = require_fs();
18266
19349
  function createOAuthOps(deps) {
18267
19350
  const d = deps || {};
18268
19351
  const ports = d.ports;
@@ -18427,20 +19510,21 @@ var require_oauth = __commonJS({
18427
19510
  promise.catch(() => {
18428
19511
  });
18429
19512
  st._ccLoginPromise = promise;
18430
- const opened = openInBrowser(authUrl, () => {
19513
+ const opened = await openInBrowser(authUrl, () => {
18431
19514
  if (st._ccLoginRound !== roundId) return;
18432
19515
  if (st._ccLoginReject) {
18433
- const r = st._ccLoginReject;
19516
+ const r2 = st._ccLoginReject;
18434
19517
  st._ccLoginReject = null;
18435
19518
  st._ccLoginResolve = null;
18436
19519
  try {
18437
- r(new Error("\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED\uFF0C\u767B\u5F55\u5DF2\u53D6\u6D88"));
19520
+ r2(new Error("\u6D4F\u89C8\u5668\u5DF2\u5173\u95ED\uFF0C\u767B\u5F55\u5DF2\u53D6\u6D88"));
18438
19521
  } catch {
18439
19522
  }
18440
19523
  }
18441
19524
  });
18442
- const evidence = opened.result || {};
18443
- if (!opened.profile) {
19525
+ const r = opened || { ok: false, reason: "spawn-failed" };
19526
+ const ev = r.evidence || {};
19527
+ if (!r.ok) {
18444
19528
  st._ccLoginPromise = null;
18445
19529
  st._ccLoginResolve = null;
18446
19530
  st._ccLoginReject = null;
@@ -18460,13 +19544,13 @@ var require_oauth = __commonJS({
18460
19544
  opened: false,
18461
19545
  confirmed: false,
18462
19546
  handedOff: false,
18463
- reason: evidence.reason || "no-launcher",
18464
- evidence: evidence.evidence || null,
18465
- error: (evidence.error || "\u65E0\u6CD5\u8C03\u8D77\u7CFB\u7EDF\u6D4F\u89C8\u5668") + "\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00\u4E0B\u65B9\u5730\u5740\u5B8C\u6210\u6388\u6743"
19547
+ reason: r.reason || "no-launcher",
19548
+ evidence: r.evidence || null,
19549
+ error: (r.error || "\u65E0\u6CD5\u8C03\u8D77\u7CFB\u7EDF\u6D4F\u89C8\u5668") + "\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00\u4E0B\u65B9\u5730\u5740\u5B8C\u6210\u6388\u6743"
18466
19550
  };
18467
19551
  }
18468
- st._ccLogin = { state, port, server, tmpProfile: opened.profile };
18469
- return Object.assign({}, evidence, {
19552
+ st._ccLogin = { state, port, server, tmpProfile: ev.profile || null };
19553
+ return Object.assign({}, r, {
18470
19554
  ok: true,
18471
19555
  authUrl,
18472
19556
  url: authUrl,
@@ -18474,8 +19558,13 @@ var require_oauth = __commonJS({
18474
19558
  port,
18475
19559
  waitMs: 18e4,
18476
19560
  opened: true,
18477
- // 非隔离引擎(Safari/snap 包装器)由平台层降级:账号隔离不成立,换账号只能靠超时重发或手动窗口。
18478
- isolated: evidence.isolated === true
19561
+ // 非隔离引擎(Safari/打包器包装)由平台层降级:账号隔离不成立,换账号只能靠超时重发或手动窗口。
19562
+ isolated: ev.isolated === true,
19563
+ // 为什么隔离成立/不成立必须一起交出:面板上一句「未隔离」既没说原因也没法处置。
19564
+ // basis 来自环境表单的出网条件维度(cold-profile-blocked 即冷档案注定空白),
19565
+ // 与「引擎不支持隔离」是两种不同原因,分开才谈得上分开修(配代理 vs 换浏览器)。
19566
+ isolatedBasis: ev.egress ? ev.egress.basis : ev.isolated === true ? "isolated" : "engine-not-isolatable",
19567
+ isolatedDetail: ev.egress ? ev.egress.detail || null : null
18479
19568
  });
18480
19569
  }
18481
19570
  async function commandcodeLoginWait(timeoutMs) {
@@ -18521,15 +19610,7 @@ var require_oauth = __commonJS({
18521
19610
  st._ccLoginResolve = st._ccLoginReject = null;
18522
19611
  return { ok: false, error: e.message };
18523
19612
  } finally {
18524
- if (tmpProfile) {
18525
- const t60 = setTimeout(() => {
18526
- try {
18527
- fs2.rmSync(tmpProfile, { recursive: true, force: true });
18528
- } catch {
18529
- }
18530
- }, 60 * 1e3);
18531
- if (t60.unref) t60.unref();
18532
- }
19613
+ if (tmpProfile) removeTreeDeferred(tmpProfile, 60 * 1e3);
18533
19614
  }
18534
19615
  }
18535
19616
  return { commandcodeLoginStart, commandcodeLoginWait };
@@ -18983,8 +20064,8 @@ var require_router_ops = __commonJS({
18983
20064
  "use strict";
18984
20065
  require_port_segments();
18985
20066
  var ports = require_ports().shared;
20067
+ var platform = require_os();
18986
20068
  var { maskKey } = require_base();
18987
- var { openInBrowser } = require_browser2();
18988
20069
  var { createOAuthOps } = require_oauth();
18989
20070
  var { createAppsRegistryOps } = require_apps_registry();
18990
20071
  var { createQuotaSyncOps } = require_quotasync();
@@ -18992,6 +20073,7 @@ var require_router_ops = __commonJS({
18992
20073
  function createAuxCore(deps) {
18993
20074
  const d = deps || {};
18994
20075
  const getProviders = d.getProviders || (() => []);
20076
+ const openInBrowser = (url, onExit) => platform.browser.openBrowser(url, { intent: "isolated-login", onExit });
18995
20077
  const oauth = createOAuthOps({ ports, openInBrowser });
18996
20078
  const apps = createAppsRegistryOps({
18997
20079
  getProviders,
@@ -23843,7 +24925,7 @@ var require_heartbeat = __commonJS({
23843
24925
  });
23844
24926
 
23845
24927
  // src/app/control/registry.js
23846
- var require_registry3 = __commonJS({
24928
+ var require_registry4 = __commonJS({
23847
24929
  "src/app/control/registry.js"(exports2, module2) {
23848
24930
  "use strict";
23849
24931
  var fs2 = require("node:fs");
@@ -24414,77 +25496,6 @@ var require_manifest = __commonJS({
24414
25496
  }
24415
25497
  });
24416
25498
 
24417
- // src/app/native/npm.js
24418
- var require_npm = __commonJS({
24419
- "src/app/native/npm.js"(exports2, module2) {
24420
- "use strict";
24421
- var ex2 = require_exec();
24422
- var runtimeContract = require_runtime();
24423
- function npmLaunch(host2) {
24424
- const h = host2 || {};
24425
- if (h._npmBin) {
24426
- return { program: h._npmBin, args: Array.isArray(h._npmBinArgs) ? h._npmBinArgs.slice() : [] };
24427
- }
24428
- const l = runtimeContract.npmLauncher();
24429
- return { program: l.program, args: l.args };
24430
- }
24431
- async function resolveNpmRoot(host2) {
24432
- if (host2.npmRoot) return host2.npmRoot;
24433
- const l = npmLaunch(host2);
24434
- const r = await ex2.runOutAsync(l.program, l.args.concat(["root", "-g"]));
24435
- return r ? r.trim() : null;
24436
- }
24437
- async function checkEnvironment(host2) {
24438
- const errors = [];
24439
- const l = npmLaunch(host2);
24440
- const [nv, npmv, npmRoot] = await Promise.all([
24441
- ex2.runOutAsync("node", ["--version"]),
24442
- ex2.runOutAsync(l.program, l.args.concat(["--version"])),
24443
- resolveNpmRoot(host2)
24444
- ]);
24445
- if (!nv || !nv.trim()) errors.push("node \u672A\u5B89\u88C5\u6216\u4E0D\u53EF\u6267\u884C");
24446
- if (!npmv || !npmv.trim()) errors.push("npm \u672A\u5B89\u88C5\u6216\u4E0D\u53EF\u6267\u884C");
24447
- return { ok: errors.length === 0, errors, npmRoot };
24448
- }
24449
- async function latestVersion(host2) {
24450
- if (!host2.dist || !host2.config.packageName) throw new Error("\u5206\u53D1\u670D\u52A1\u672A\u521D\u59CB\u5316\uFF0C\u65E0\u6CD5\u67E5\u8BE2\u6700\u65B0\u7248\u672C");
24451
- const channel = host2.config.releaseChannel || "npm";
24452
- return host2.dist.fetchVersionInfo(host2.config.packageName, channel);
24453
- }
24454
- async function selectRegistry(host2) {
24455
- if (!host2.dist) return null;
24456
- try {
24457
- return await host2.dist.registryOrigin(true);
24458
- } catch {
24459
- return null;
24460
- }
24461
- }
24462
- function runNpm(host2, opts) {
24463
- const a = opts && opts.action || "install";
24464
- const verb = a === "uninstall" ? "\u5378\u8F7D" : "\u5B89\u88C5";
24465
- if (!host2.dist) return Promise.resolve({ ok: false, error: "dist \u5206\u53D1\u670D\u52A1\u4E0D\u53EF\u7528\uFF0C\u65E0\u6CD5" + verb, output: [] });
24466
- const tpl = a === "install" ? host2.config.installCommandTemplate : null;
24467
- return host2.dist.runNpmInstall({
24468
- action: a,
24469
- pkg: host2.config.packageName || "@deepseek-ai/dsh",
24470
- version: opts && opts.version,
24471
- // 前缀单源:装与卸必须落在同一个全局前缀,否则「卸载成功」而包还在原地。
24472
- prefix: host2.npmRoot || null,
24473
- registry: opts && opts.registry,
24474
- // 只在显式注入过 npm 形态时接管启动形态;生产留空,由执行器按运行期契约解析一次。
24475
- launcher: host2._npmBin ? npmLaunch(host2) : null,
24476
- commandTemplate: Array.isArray(tpl) && tpl.length ? tpl : null,
24477
- timeoutMs: a === "uninstall" ? host2.config.uninstallTimeoutMs : host2.config.upgradeTimeoutMs,
24478
- onLine: (l) => {
24479
- host2._appendUpgradeLog(l);
24480
- if (host2.installing) host2._appendInstallLog(l);
24481
- }
24482
- });
24483
- }
24484
- module2.exports = { npmLaunch, resolveNpmRoot, checkEnvironment, latestVersion, selectRegistry, runNpm };
24485
- }
24486
- });
24487
-
24488
25499
  // src/app/native/ops.js
24489
25500
  var require_ops4 = __commonJS({
24490
25501
  "src/app/native/ops.js"(exports2, module2) {
@@ -25129,7 +26140,7 @@ var require_installer = __commonJS({
25129
26140
  var require_entry = __commonJS({
25130
26141
  "src/app/control/entry.js"(exports2, module2) {
25131
26142
  "use strict";
25132
- var { PHASES } = require_registry3();
26143
+ var { PHASES } = require_registry4();
25133
26144
  var ManagedLifecycle = class {
25134
26145
  constructor(opts) {
25135
26146
  this.id = opts.id || "lc-" + Math.random().toString(36).slice(2, 8);
@@ -25383,7 +26394,7 @@ var require_domains = __commonJS({
25383
26394
  var { InstanceManager } = require_instance();
25384
26395
  var { PluginMarket } = require_market();
25385
26396
  var { PluginManager } = require_plugin();
25386
- var { ManagedRegistry } = require_registry3();
26397
+ var { ManagedRegistry } = require_registry4();
25387
26398
  var { guardVersion } = require_version();
25388
26399
  var { Lifecycle } = require_lifecycle2();
25389
26400
  var { Health } = require_health();
@@ -25510,7 +26521,7 @@ var require_adapters = __commonJS({
25510
26521
  "src/app/control/adapters.js"(exports2, module2) {
25511
26522
  "use strict";
25512
26523
  var { ManagedLifecycle } = require_entry();
25513
- var { kindMeta } = require_registry3();
26524
+ var { kindMeta } = require_registry4();
25514
26525
  function capsOf(objectKind) {
25515
26526
  const m = kindMeta(objectKind);
25516
26527
  if (!m) return { startable: true, guardable: true };
@@ -26009,7 +27020,7 @@ var require_guard = __commonJS({
26009
27020
  var fs2 = require("node:fs");
26010
27021
  var path2 = require("node:path");
26011
27022
  function owns(pathname) {
26012
- return pathname === "/changelog" || pathname.startsWith("/guard/") || pathname === "/autostart" || pathname.startsWith("/settings/") || pathname.startsWith("/self-update/") || pathname === "/env/dsh" || pathname === "/env/status" || pathname === "/env/node-lts" || pathname === "/env/open-url" || pathname === "/env/browsers" || pathname === "/ports" || pathname === "/shutdown";
27023
+ return pathname === "/changelog" || pathname.startsWith("/guard/") || pathname === "/autostart" || pathname.startsWith("/settings/") || pathname.startsWith("/self-update/") || pathname === "/env/dsh" || pathname === "/env/status" || pathname === "/env/node-lts" || pathname === "/env/open-url" || pathname === "/env/environment" || pathname === "/env/environment/last" || pathname === "/ports" || pathname === "/shutdown";
26013
27024
  }
26014
27025
  function fetchDshChangelog(res, sup) {
26015
27026
  const v = sup && sup.nativeManager ? sup.nativeManager.versionInfo() : {};
@@ -26021,7 +27032,7 @@ var require_guard = __commonJS({
26021
27032
  return res.end(md);
26022
27033
  }
26023
27034
  function handle(ctx) {
26024
- const { sup, req, res, pathname, identity, send, collectBody, originAllowed, browser } = ctx;
27035
+ const { sup, req, res, pathname, identity, send, collectBody, originAllowed, browser, environment } = ctx;
26025
27036
  if (req.method === "GET" && pathname === "/changelog") {
26026
27037
  return fetchDshChangelog(res, sup);
26027
27038
  }
@@ -26127,6 +27138,31 @@ var require_guard = __commonJS({
26127
27138
  });
26128
27139
  return;
26129
27140
  }
27141
+ if (req.method === "GET" && pathname === "/settings/external-browser") {
27142
+ return send(200, sup.externalBrowserStatus());
27143
+ }
27144
+ if (req.method === "POST" && pathname === "/settings/external-browser") {
27145
+ if (!originAllowed(req, sup.config.apiPort)) {
27146
+ req.resume();
27147
+ return send(403, {});
27148
+ }
27149
+ collectBody(req, res, 2048, (body) => {
27150
+ let id = null;
27151
+ let given = false;
27152
+ try {
27153
+ const j = body ? JSON.parse(body) : {};
27154
+ if (typeof j.id === "string") {
27155
+ id = j.id;
27156
+ given = true;
27157
+ }
27158
+ } catch {
27159
+ }
27160
+ if (!given) return send(400, { ok: false, error: '\u9700\u8981 {"id":"<\u5019\u9009 id>"}\uFF08\u7A7A\u4E32\u6E05\u9664\u504F\u597D\uFF09' });
27161
+ const r = sup.setExternalBrowser(id);
27162
+ return send(r.ok ? 200 : 500, r);
27163
+ });
27164
+ return;
27165
+ }
26130
27166
  if (req.method === "POST" && pathname === "/shutdown") {
26131
27167
  if (!originAllowed(req, sup.config.apiPort)) {
26132
27168
  req.resume();
@@ -26185,13 +27221,20 @@ var require_guard = __commonJS({
26185
27221
  if (req.method === "GET" && pathname === "/env/node-lts") {
26186
27222
  return sup.nodeLtsStatus().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
26187
27223
  }
26188
- if (req.method === "GET" && pathname === "/env/browsers") {
27224
+ if (req.method === "GET" && pathname === "/env/environment") {
27225
+ if (!originAllowed(req, sup.config.apiPort)) {
27226
+ req.resume();
27227
+ return send(403, { ok: false, error: "cross-origin request rejected" });
27228
+ }
27229
+ const force = /[?&]force=1/.test(String(req.url || ""));
27230
+ return Promise.resolve(force ? environment.refresh({ persist: true, force: true }) : environment.form()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: "\u73AF\u5883\u8868\u5355\u88C5\u914D\u5931\u8D25\uFF1A" + (e && e.message || e) }));
27231
+ }
27232
+ if (req.method === "GET" && pathname === "/env/environment/last") {
26189
27233
  if (!originAllowed(req, sup.config.apiPort)) {
26190
27234
  req.resume();
26191
27235
  return send(403, { ok: false, error: "cross-origin request rejected" });
26192
27236
  }
26193
- const force = /[?&]force=1/.test(req.url || "");
26194
- return Promise.resolve(browser.listBrowsers({ force })).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: "\u6D4F\u89C8\u5668\u63A2\u6D4B\u5931\u8D25\uFF1A" + (e && e.message || e) }));
27237
+ return send(200, environment.lastSnapshot());
26195
27238
  }
26196
27239
  if (req.method === "POST" && pathname === "/env/open-url") {
26197
27240
  if (!identity.loopback) {
@@ -27328,6 +28371,7 @@ var require_server = __commonJS({
27328
28371
  var { identify } = require_identity3();
27329
28372
  var { originAllowed, requestHasAccessKey, isShellOrigin } = require_security();
27330
28373
  var browserExit = require_browser();
28374
+ var environmentExit = require_environment();
27331
28375
  function safeFail(res, err, where) {
27332
28376
  try {
27333
28377
  const body = JSON.stringify({ ok: false, error: err && err.message || String(err) });
@@ -27347,6 +28391,7 @@ var require_server = __commonJS({
27347
28391
  }
27348
28392
  function createServer(sup, deps) {
27349
28393
  const browser = deps && deps.browser || browserExit;
28394
+ const environment = deps && deps.environment || environmentExit;
27350
28395
  return http2.createServer((req, res) => {
27351
28396
  const shellOrigin = (() => {
27352
28397
  const o = req.headers.origin;
@@ -27414,7 +28459,7 @@ var require_server = __commonJS({
27414
28459
  } catch (e) {
27415
28460
  return send(400, { error: "bad request encoding" });
27416
28461
  }
27417
- const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed, tokOf, browser };
28462
+ const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed, tokOf, browser, environment };
27418
28463
  for (const d of API_DOMAINS) {
27419
28464
  if (d.owns(pathname)) {
27420
28465
  try {