@dsh-sup/dsh-core-linux-x64 0.1.6-BETA.10 → 0.1.6-BETA.11

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 (31) hide show
  1. package/core.cjs +384 -133
  2. package/package.json +1 -1
  3. package/ui-react/assets/InstancesPage--LhDhPDs.js +3 -0
  4. package/ui-react/assets/LanPage-D7soTx9V.js +4 -0
  5. package/ui-react/assets/OverviewPage-DxCKB6BV.js +2 -0
  6. package/ui-react/assets/PluginsPage-mISvN5b8.js +1 -0
  7. package/ui-react/assets/RouterPage-vhAx87Fo.js +1 -0
  8. package/ui-react/assets/SettingsPage-DgA5JCBo.js +1 -0
  9. package/ui-react/assets/{TasksPage-BsXiGMar.js → TasksPage-CGdHy0sy.js} +1 -1
  10. package/ui-react/assets/openExternal-CBH3SnvT.js +1 -0
  11. package/ui-react/assets/{refresh-cw-uOv8eoMN.js → refresh-cw-B-VAKpDt.js} +1 -1
  12. package/ui-react/assets/{rocket-BYxAkKOI.js → rocket-CLAmwatt.js} +1 -1
  13. package/ui-react/assets/{shield-check-DskEWpz7.js → shield-check-CMRwmPpk.js} +1 -1
  14. package/ui-react/assets/supervisor-B5Bq0rnN.css +2 -0
  15. package/ui-react/assets/supervisor-BbSavtNN.js +41 -0
  16. package/ui-react/assets/supervisor-C_6Efj6Z.js +11 -0
  17. package/ui-react/assets/useSupervisorAction-q0YJmhui.js +1 -0
  18. package/ui-react/assets/widgets-BbRkZm-Z.js +1 -0
  19. package/ui-react/supervisor.html +3 -3
  20. package/ui-react/assets/InstancesPage-Bv6gx6Pj.js +0 -3
  21. package/ui-react/assets/LanPage-BLdWpYNX.js +0 -4
  22. package/ui-react/assets/OverviewPage-GLglcmt5.js +0 -2
  23. package/ui-react/assets/PluginsPage-D0w7WSyx.js +0 -5
  24. package/ui-react/assets/RouterPage-CXHKQf2P.js +0 -1
  25. package/ui-react/assets/SettingsPage-OuiVCipQ.js +0 -5
  26. package/ui-react/assets/external-link-CnOCgHhe.js +0 -1
  27. package/ui-react/assets/supervisor-BnUm3AVQ.js +0 -41
  28. package/ui-react/assets/supervisor-DJuPMCY8.css +0 -2
  29. package/ui-react/assets/supervisor-xQMSEUga.js +0 -11
  30. package/ui-react/assets/useSupervisorAction-CSk4q5cT.js +0 -1
  31. package/ui-react/assets/widgets-CcAMVR4H.js +0 -1
package/core.cjs CHANGED
@@ -947,7 +947,7 @@ var require_version = __commonJS({
947
947
  var fs2 = require("node:fs");
948
948
  var path2 = require("node:path");
949
949
  function guardVersion() {
950
- if (true) return String("0.1.6-BETA.10");
950
+ if (true) return String("0.1.6-BETA.11");
951
951
  try {
952
952
  return JSON.parse(fs2.readFileSync(path2.join(__dirname, "..", "..", "package.json"), "utf8")).version || "unknown";
953
953
  } catch {
@@ -4634,6 +4634,58 @@ var require_watchdog = __commonJS({
4634
4634
  }
4635
4635
  });
4636
4636
 
4637
+ // src/platform/os/desktop.js
4638
+ var require_desktop = __commonJS({
4639
+ "src/platform/os/desktop.js"(exports2, module2) {
4640
+ "use strict";
4641
+ var fs2 = require("node:fs");
4642
+ var PLATFORM = process.platform;
4643
+ function hasX11Socket() {
4644
+ try {
4645
+ return fs2.readdirSync("/tmp/.X11-unix").some((f) => /^X\d+$/.test(f));
4646
+ } catch {
4647
+ return false;
4648
+ }
4649
+ }
4650
+ function hasWaylandSocket() {
4651
+ const uid = typeof process.getuid === "function" ? process.getuid() : 0;
4652
+ const rt = process.env.XDG_RUNTIME_DIR || "/run/user/" + uid;
4653
+ try {
4654
+ return fs2.readdirSync(rt).some((f) => /^wayland-\d+$/.test(f));
4655
+ } catch {
4656
+ return false;
4657
+ }
4658
+ }
4659
+ function sessionAvailable() {
4660
+ if (PLATFORM === "linux") {
4661
+ if (process.env.DISPLAY || process.env.WAYLAND_DISPLAY) return true;
4662
+ return hasX11Socket() || hasWaylandSocket();
4663
+ }
4664
+ return PLATFORM === "darwin" || PLATFORM === "win32";
4665
+ }
4666
+ function describe() {
4667
+ if (PLATFORM === "linux") {
4668
+ const byEnv = !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
4669
+ const byX = hasX11Socket();
4670
+ const byWl = hasWaylandSocket();
4671
+ return {
4672
+ platform: PLATFORM,
4673
+ available: byEnv || byX || byWl,
4674
+ reason: byEnv ? "env(DISPLAY/WAYLAND_DISPLAY)" : byX ? "x11-socket" : byWl ? "wayland-socket" : "none",
4675
+ display: process.env.DISPLAY || null,
4676
+ waylandDisplay: process.env.WAYLAND_DISPLAY || null
4677
+ };
4678
+ }
4679
+ return {
4680
+ platform: PLATFORM,
4681
+ available: sessionAvailable(),
4682
+ reason: "session-scoped-by-launcher"
4683
+ };
4684
+ }
4685
+ module2.exports = { sessionAvailable, describe, PLATFORM };
4686
+ }
4687
+ });
4688
+
4637
4689
  // src/platform/os/capability-profile.js
4638
4690
  var require_capability_profile = __commonJS({
4639
4691
  "src/platform/os/capability-profile.js"(exports2, module2) {
@@ -4651,6 +4703,8 @@ var require_capability_profile = __commonJS({
4651
4703
  autostart: true,
4652
4704
  // 期望 systemctl(实测覆写)
4653
4705
  frpExpose: true,
4706
+ openBrowser: true,
4707
+ // xdg-open / 解析到的默认浏览器;无图形会话时实测覆写成 false
4654
4708
  hostService: "systemd",
4655
4709
  guardAutostart: true,
4656
4710
  // systemd --user enable + linger
@@ -4674,6 +4728,8 @@ var require_capability_profile = __commonJS({
4674
4728
  autostart: true,
4675
4729
  // launchctl/LaunchAgent 恒在
4676
4730
  frpExpose: true,
4731
+ openBrowser: true,
4732
+ // LaunchServices 解析 + open 直启
4677
4733
  hostService: "launchd",
4678
4734
  guardAutostart: true,
4679
4735
  // LaunchAgent RunAtLoad + KeepAlive
@@ -4699,6 +4755,9 @@ var require_capability_profile = __commonJS({
4699
4755
  autostart: true,
4700
4756
  // 期望 schtasks(实测覆写)
4701
4757
  frpExpose: true,
4758
+ // 可打开,但只有注册表解析到默认浏览器时才有退出证据;退到 explorer.exe 调度器即恒返 0,
4759
+ // 每次调用的证据档位由 openBrowser 的 confirmed/handedOff 如实回报,不在此处宣称。
4760
+ openBrowser: true,
4702
4761
  hostService: "windows-service",
4703
4762
  guardAutostart: true,
4704
4763
  // schtasks DSH-Supervisor(ONLOGON,由壳建立)
@@ -4717,6 +4776,7 @@ var require_capability_profile = __commonJS({
4717
4776
  desktopNotify: false,
4718
4777
  autostart: false,
4719
4778
  frpExpose: false,
4779
+ openBrowser: false,
4720
4780
  hostService: "none",
4721
4781
  guardAutostart: false,
4722
4782
  guardSelfHeal: false,
@@ -5400,6 +5460,8 @@ var require_browser = __commonJS({
5400
5460
  var path2 = require("node:path");
5401
5461
  var os2 = require("node:os");
5402
5462
  var spawnOS = require_spawn();
5463
+ var desktop = require_desktop();
5464
+ var CAPABILITY_PROFILES = require_capability_profile();
5403
5465
  var { resolveExecutable, isExecutableFile } = require_exec_path();
5404
5466
  var exec = require_exec();
5405
5467
  function isSafeHttpUrl(url) {
@@ -5416,17 +5478,122 @@ var require_browser = __commonJS({
5416
5478
  if (pl === "win32") return { cmd: "explorer.exe", args: [url] };
5417
5479
  return { cmd: "xdg-open", args: [url] };
5418
5480
  }
5419
- function open(url) {
5420
- if (!isSafeHttpUrl(url)) return false;
5481
+ var SUPPORTED_OPEN_PLATFORMS = Object.keys(CAPABILITY_PROFILES).filter((k) => CAPABILITY_PROFILES[k].openBrowser === true);
5482
+ function outcome(p) {
5483
+ const ok = !!p.ok;
5484
+ const confirmed = ok && p.confirmed === true;
5485
+ const handedOff = ok && !confirmed;
5486
+ const reason = ok ? null : p.reason || "failed";
5487
+ return {
5488
+ ok,
5489
+ confirmed,
5490
+ handedOff,
5491
+ reason,
5492
+ error: ok ? null : p.error || FAILURE_TEXT[reason] || "\u6253\u5F00\u5931\u8D25\uFF08" + reason + "\uFF09",
5493
+ message: ok ? confirmed ? CONFIRMED_TEXT : p.message || HANDEDOFF_TEXT : null,
5494
+ url: p.url === void 0 ? null : p.url,
5495
+ evidence: p.evidence || null
5496
+ };
5497
+ }
5498
+ var CONFIRMED_TEXT = "\u5DF2\u5728\u7CFB\u7EDF\u6D4F\u89C8\u5668\u6253\u5F00";
5499
+ var HANDEDOFF_TEXT = "\u5DF2\u628A\u5730\u5740\u4EA4\u7ED9\u7CFB\u7EDF\uFF0C\u4F46\u65E0\u6CD5\u786E\u8BA4\u6D4F\u89C8\u5668\u7A97\u53E3\u662F\u5426\u51FA\u73B0";
5500
+ var FAILURE_TEXT = {
5501
+ "unsafe-url": "\u5730\u5740\u4E0D\u662F http(s) \u7EDD\u5BF9 URL\uFF0C\u5DF2\u62D2\u7EDD\u4EA4\u7ED9\u6D4F\u89C8\u5668",
5502
+ "no-launcher": "\u672A\u627E\u5230\u53EF\u7528\u7684\u6D4F\u89C8\u5668\u542F\u52A8\u547D\u4EE4\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00\u8BE5\u5730\u5740",
5503
+ "spawn-failed": "\u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25\uFF08\u7CFB\u7EDF\u62D2\u7EDD\u4E86\u8BE5\u547D\u4EE4\uFF09",
5504
+ "exit-nonzero": "\u6D4F\u89C8\u5668\u542F\u52A8\u547D\u4EE4\u4EE5\u975E 0 \u9000\u51FA\uFF0C\u7A97\u53E3\u672A\u51FA\u73B0",
5505
+ "killed-by-signal": "\u6D4F\u89C8\u5668\u542F\u52A8\u547D\u4EE4\u88AB\u7CFB\u7EDF\u7EC8\u6B62",
5506
+ "no-desktop-session": "\u5F53\u524D\u6CA1\u6709\u56FE\u5F62\u4F1A\u8BDD\uFF0C\u65E0\u6CD5\u8C03\u8D77\u6D4F\u89C8\u5668",
5507
+ "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"
5508
+ };
5509
+ function openPlan(platform, url, opts) {
5510
+ const o = opts || {};
5511
+ const pl = platform || process.platform;
5512
+ const db = o.defaultBrowser;
5513
+ const engine = db && db.bin ? engineOf(db.bin) : "other";
5514
+ if (engine === "chromium" || engine === "firefox") {
5515
+ return { bin: db.bin, engine, via: "browser", baseArgs: (db.baseArgs || []).concat([url]), trustExit: true };
5516
+ }
5517
+ const c = openCommand(pl, url);
5518
+ return { bin: c.cmd, engine: "other", via: "dispatcher", baseArgs: c.args, trustExit: pl !== "win32" };
5519
+ }
5520
+ function observeSpawn(child, windowMs, setTimeoutFn) {
5521
+ return new Promise((resolve) => {
5522
+ let settled = false;
5523
+ let timer;
5524
+ const done = (r) => {
5525
+ if (settled) return;
5526
+ settled = true;
5527
+ clearTimeout(timer);
5528
+ resolve(r);
5529
+ };
5530
+ timer = (setTimeoutFn || setTimeout)(() => done({ stage: "alive" }), windowMs);
5531
+ if (timer && timer.unref) timer.unref();
5532
+ child.on("error", (e) => done({ stage: "error", code: e && e.code || String(e) }));
5533
+ child.on("exit", (code, signal) => done({ stage: "exit", code, signal }));
5534
+ });
5535
+ }
5536
+ var OPEN_OBSERVE_MS = 1500;
5537
+ async function openBrowser(url, o) {
5538
+ const opts = o || {};
5539
+ const pl = opts.platform || process.platform;
5540
+ if (!isSafeHttpUrl(url)) {
5541
+ return outcome({ ok: false, reason: "unsafe-url", url: String(url || ""), evidence: null });
5542
+ }
5543
+ if (!SUPPORTED_OPEN_PLATFORMS.includes(pl)) {
5544
+ return outcome({ ok: false, reason: "unsupported-platform", url, evidence: { platform: pl } });
5545
+ }
5546
+ const observeMs = opts.observeMs === void 0 ? OPEN_OBSERVE_MS : opts.observeMs;
5547
+ const spawnWith = typeof opts.spawn === "function" ? opts.spawn : _spawnDetachedIgnored;
5548
+ const avail = typeof opts.binAvailable === "function" ? opts.binAvailable : binAvailable;
5549
+ const observe = typeof opts.observe === "function" ? opts.observe : observeSpawn;
5550
+ let db;
5551
+ if ("defaultBrowser" in opts) db = opts.defaultBrowser;
5552
+ else if (typeof opts.resolveDefault === "function") db = opts.resolveDefault(pl, opts.resolveDeps || {});
5553
+ else db = resolveDefaultBrowser(pl, opts.resolveDeps || {});
5554
+ const plan = openPlan(pl, url, { defaultBrowser: db });
5555
+ const evidence = { bin: plan.bin, engine: plan.engine, via: plan.via, exitCode: null, exitSignal: null, error: null };
5556
+ const desktopAvailable = typeof opts.desktopAvailable === "function" ? opts.desktopAvailable : desktop.sessionAvailable;
5557
+ if (pl === "linux" && !desktopAvailable()) {
5558
+ return outcome({ ok: false, reason: "no-desktop-session", url, evidence });
5559
+ }
5560
+ if (!avail(plan.bin)) {
5561
+ return outcome({ ok: false, reason: "no-launcher", url, evidence });
5562
+ }
5563
+ let child;
5564
+ try {
5565
+ child = spawnWith(plan.bin, plan.baseArgs);
5566
+ } catch (e) {
5567
+ evidence.error = e && e.code || String(e);
5568
+ return outcome({ ok: false, reason: "spawn-failed", url, evidence });
5569
+ }
5570
+ if (!child) return outcome({ ok: false, reason: "spawn-failed", url, evidence });
5571
+ const seen = await observe(child, observeMs, opts.setTimeout);
5572
+ evidence.error = seen.stage === "error" ? String(seen.code) : null;
5573
+ evidence.exitCode = seen.code === void 0 ? null : seen.code;
5574
+ evidence.exitSignal = seen.signal === void 0 ? null : seen.signal;
5575
+ if (seen.stage === "error") return outcome({ ok: false, reason: "spawn-failed", url, evidence });
5576
+ if (seen.stage === "exit" && (seen.code !== 0 || seen.signal)) {
5577
+ return outcome({
5578
+ ok: false,
5579
+ reason: seen.signal ? "killed-by-signal" : "exit-nonzero",
5580
+ url,
5581
+ evidence,
5582
+ error: FAILURE_TEXT[seen.signal ? "killed-by-signal" : "exit-nonzero"] + "\uFF08" + (seen.signal || seen.code) + "\uFF09"
5583
+ });
5584
+ }
5585
+ const exitEvidence = seen.stage === "exit" && plan.trustExit;
5586
+ return outcome({ ok: true, confirmed: exitEvidence, handedOff: !exitEvidence, url, evidence });
5587
+ }
5588
+ function _spawnDetachedIgnored(bin, args) {
5421
5589
  try {
5422
- const c = openCommand(process.platform, url);
5423
- const p = spawnOS.detachedIgnored(c.cmd, c.args);
5590
+ const p = spawnOS.detachedIgnored(bin, args);
5424
5591
  p.on("error", () => {
5425
5592
  });
5426
5593
  p.unref();
5427
- return true;
5594
+ return p;
5428
5595
  } catch {
5429
- return false;
5596
+ return null;
5430
5597
  }
5431
5598
  }
5432
5599
  function _spawnDetached(bin, args, env, onExit) {
@@ -5546,8 +5713,8 @@ var require_browser = __commonJS({
5546
5713
  }
5547
5714
  function resolveDefaultLinux(runOut, readFile, exists, env, home) {
5548
5715
  const id = runOut("xdg-settings", ["get", "default-web-browser"]);
5549
- const desktop = id && id.trim();
5550
- if (!desktop || !/^[A-Za-z0-9._-]+\.desktop$/.test(desktop)) return null;
5716
+ const desktop2 = id && id.trim();
5717
+ if (!desktop2 || !/^[A-Za-z0-9._-]+\.desktop$/.test(desktop2)) return null;
5551
5718
  const dirs = [
5552
5719
  env.XDG_DATA_HOME || path2.join(home, ".local", "share"),
5553
5720
  "/usr/local/share",
@@ -5555,7 +5722,7 @@ var require_browser = __commonJS({
5555
5722
  "/var/lib/snapd/desktop"
5556
5723
  ].map((d) => path2.join(d, "applications"));
5557
5724
  for (const dir of dirs) {
5558
- const p = path2.join(dir, desktop);
5725
+ const p = path2.join(dir, desktop2);
5559
5726
  let text = null;
5560
5727
  try {
5561
5728
  if (exists(p)) text = readFile(p);
@@ -5627,12 +5794,13 @@ var require_browser = __commonJS({
5627
5794
  }
5628
5795
  function launchIsolated(url, o) {
5629
5796
  const opts = o || {};
5630
- if (!isSafeHttpUrl(url)) return { ok: false, bin: null, isolated: false };
5797
+ if (!isSafeHttpUrl(url)) return Object.assign(outcome({ ok: false, reason: "unsafe-url", url: String(url || "") }), { bin: null, isolated: false });
5631
5798
  const antiEnv = opts.antiEnv || opts.sysEnv || process.env;
5632
5799
  const sysEnv = opts.sysEnv || process.env;
5633
5800
  const onExit = opts.onExit;
5634
5801
  const avail = typeof opts.binAvailable === "function" ? opts.binAvailable : binAvailable;
5635
5802
  const spawnWith = typeof opts.spawn === "function" ? opts.spawn : _spawnDetached;
5803
+ const fail = (reason, extra) => Object.assign(outcome(Object.assign({ ok: false, reason, url }, extra)), { bin: null, isolated: false });
5636
5804
  try {
5637
5805
  let db;
5638
5806
  if ("defaultBrowser" in opts) db = opts.defaultBrowser;
@@ -5644,19 +5812,22 @@ var require_browser = __commonJS({
5644
5812
  size: opts.size,
5645
5813
  lang: opts.lang
5646
5814
  });
5647
- if (!avail(plan.bin)) return { ok: false, bin: null, isolated: false };
5815
+ if (!avail(plan.bin)) return fail("no-launcher", { evidence: { bin: plan.bin, engine: plan.label, via: plan.isolated ? "browser" : "dispatcher" } });
5648
5816
  const env = plan.envKind === "anti" ? antiEnv : sysEnv;
5649
5817
  const p = spawnWith(plan.bin, plan.args, env, plan.watch ? onExit : void 0);
5650
- return { ok: !!p, bin: p ? plan.label : null, isolated: plan.isolated };
5651
- } catch {
5652
- return { ok: false, bin: null, isolated: false };
5818
+ if (!p) return fail("spawn-failed");
5819
+ return Object.assign(outcome({ ok: true, confirmed: false, handedOff: true, url }), { bin: plan.label, isolated: plan.isolated });
5820
+ } catch (e) {
5821
+ return fail("spawn-failed", { error: FAILURE_TEXT["spawn-failed"] + "\uFF08" + (e && e.code || e) + "\uFF09" });
5653
5822
  }
5654
5823
  }
5655
5824
  module2.exports = {
5656
- open,
5825
+ openBrowser,
5657
5826
  launchIsolated,
5658
5827
  openCommand,
5828
+ openPlan,
5659
5829
  isolatedPlan,
5830
+ observeSpawn,
5660
5831
  isSafeHttpUrl,
5661
5832
  resolveDefaultBrowser,
5662
5833
  resolveDefaultWin,
@@ -5672,58 +5843,6 @@ var require_browser = __commonJS({
5672
5843
  }
5673
5844
  });
5674
5845
 
5675
- // src/platform/os/desktop.js
5676
- var require_desktop = __commonJS({
5677
- "src/platform/os/desktop.js"(exports2, module2) {
5678
- "use strict";
5679
- var fs2 = require("node:fs");
5680
- var PLATFORM = process.platform;
5681
- function hasX11Socket() {
5682
- try {
5683
- return fs2.readdirSync("/tmp/.X11-unix").some((f) => /^X\d+$/.test(f));
5684
- } catch {
5685
- return false;
5686
- }
5687
- }
5688
- function hasWaylandSocket() {
5689
- const uid = typeof process.getuid === "function" ? process.getuid() : 0;
5690
- const rt = process.env.XDG_RUNTIME_DIR || "/run/user/" + uid;
5691
- try {
5692
- return fs2.readdirSync(rt).some((f) => /^wayland-\d+$/.test(f));
5693
- } catch {
5694
- return false;
5695
- }
5696
- }
5697
- function sessionAvailable() {
5698
- if (PLATFORM === "linux") {
5699
- if (process.env.DISPLAY || process.env.WAYLAND_DISPLAY) return true;
5700
- return hasX11Socket() || hasWaylandSocket();
5701
- }
5702
- return PLATFORM === "darwin" || PLATFORM === "win32";
5703
- }
5704
- function describe() {
5705
- if (PLATFORM === "linux") {
5706
- const byEnv = !!(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
5707
- const byX = hasX11Socket();
5708
- const byWl = hasWaylandSocket();
5709
- return {
5710
- platform: PLATFORM,
5711
- available: byEnv || byX || byWl,
5712
- reason: byEnv ? "env(DISPLAY/WAYLAND_DISPLAY)" : byX ? "x11-socket" : byWl ? "wayland-socket" : "none",
5713
- display: process.env.DISPLAY || null,
5714
- waylandDisplay: process.env.WAYLAND_DISPLAY || null
5715
- };
5716
- }
5717
- return {
5718
- platform: PLATFORM,
5719
- available: sessionAvailable(),
5720
- reason: "session-scoped-by-launcher"
5721
- };
5722
- }
5723
- module2.exports = { sessionAvailable, describe, PLATFORM };
5724
- }
5725
- });
5726
-
5727
5846
  // src/platform/os/autostart/win32.js
5728
5847
  var require_win32 = __commonJS({
5729
5848
  "src/platform/os/autostart/win32.js"(exports2, module2) {
@@ -6070,6 +6189,7 @@ var require_os = __commonJS({
6070
6189
  var path2 = require("node:path");
6071
6190
  var ex2 = require_exec();
6072
6191
  var execPath = require_exec_path();
6192
+ var desktop = require_desktop();
6073
6193
  var CAPABILITY_PROFILES = require_capability_profile();
6074
6194
  var PLATFORM = process.platform;
6075
6195
  var ARCH = process.arch;
@@ -6115,6 +6235,7 @@ var require_os = __commonJS({
6115
6235
  p.sandboxEnforcement = hasTool("systemd-run") ? "cgroup" : "supervise";
6116
6236
  p.desktopNotify = hasTool("notify-send");
6117
6237
  p.autostart = hasTool("systemctl");
6238
+ p.openBrowser = desktop.sessionAvailable();
6118
6239
  } else if (pl === "darwin") {
6119
6240
  p.desktopNotify = hasTool("osascript");
6120
6241
  } else if (pl === "win32") {
@@ -6147,7 +6268,7 @@ var require_os = __commonJS({
6147
6268
  // 导出模块对象会抛 platform.notify is not a function,把升级终态误判成失败。
6148
6269
  notify: require_notify().notify,
6149
6270
  browser: require_browser(),
6150
- desktop: require_desktop(),
6271
+ desktop,
6151
6272
  // 图形会话可用性(Linux 需实测 socket)
6152
6273
  autostart: require_autostart()
6153
6274
  };
@@ -10916,6 +11037,7 @@ var require_lan = __commonJS({
10916
11037
  module2.exports = { methods: {
10917
11038
  // 远程控制委托:全部转发给 LanManager;daemon 监督模式经 43108 ctl 委托(异步),本地模式走 LanManager(同步)。
10918
11039
  // 令牌收敛:listLan 输出必须剔除 token/dshToken —— /lan-access 允许 LAN/私网 Host 访问,直出会把 DSH 会话令牌泄漏给局域网,权威仍在 DshTokenService。
11040
+ // 远程访问令牌的明文也不在此下发:它是实例意图字段,只随 /instances 的回环分支交给本机面板(api/domains/instances.js#decorate)。
10919
11041
  // remote(projectRemoteView 产物)为后端单一推导视图 {mode,ready,accessUrl,reasons},不含机密,整段放行给 UI。返回形如 {items,addresses}。
10920
11042
  listLan() {
10921
11043
  const d = depsOf(this);
@@ -10928,7 +11050,7 @@ var require_lan = __commonJS({
10928
11050
  dshPort: it.dshPort,
10929
11051
  wanPort: it.wanPort,
10930
11052
  running: !!it.running,
10931
- // 令牌状态(布尔,不泄明文):wan 模式的安全闸要求已设令牌,UI 据此引导。
11053
+ // 令牌状态(布尔,不泄明文):UI 据此引导,明文的查看/修改走 /instances 的回环分支。
10932
11054
  tokenSet: !!String(it.token || "").trim(),
10933
11055
  remote: it.remote || null
10934
11056
  };
@@ -11097,7 +11219,7 @@ var require_main = __commonJS({
11097
11219
  guardian: m.guardian,
11098
11220
  remoteMode: m.remoteMode,
11099
11221
  remoteToken: m.remoteToken,
11100
- // 进程内消费(LanManager mainOf);API 边界由 api/domains/instances.js 剔除
11222
+ // 进程内消费(LanManager mainOf);API 边界按回环判据决定去留(api/domains/instances.js#decorate)
11101
11223
  unitName: null,
11102
11224
  // systemd 托管已废弃:main 由守卫 spawn/观测
11103
11225
  // 实时运行态:native 条目缺 state 会导致远程控制页误判「实例已停止」
@@ -11463,6 +11585,9 @@ if (typeof crypto.randomUUID !== 'function') {
11463
11585
  }
11464
11586
  return { ok: true };
11465
11587
  }
11588
+ function generateRemoteToken() {
11589
+ return crypto.randomBytes(12).toString("base64url");
11590
+ }
11466
11591
  function normalizeRemoteMode(v) {
11467
11592
  return v === "lan" || v === "wan" ? v : "off";
11468
11593
  }
@@ -11472,9 +11597,9 @@ if (typeof crypto.randomUUID !== 'function') {
11472
11597
  if (mode === "off") return { mode, ready: false, accessUrl: null, reasons: [] };
11473
11598
  const reasons = [];
11474
11599
  if (!x.relayListening) reasons.push("\u8FDC\u7A0B\u670D\u52A1\u672A\u5C31\u7EEA\uFF08relay \u672A\u76D1\u542C\uFF09");
11475
- if (!x.tokenSet) reasons.push("\u672A\u8BBE\u8BBF\u95EE\u4EE4\u724C");
11476
11600
  else if (!x.cookieReady) reasons.push("\u6B63\u5728\u6CE8\u5165 DSH \u4F1A\u8BDD\u2026");
11477
11601
  if (mode === "wan") {
11602
+ if (!x.tokenSet) reasons.push("\u672A\u8BBE\u8BBF\u95EE\u4EE4\u724C");
11478
11603
  if (!String(x.serverAddr || "").trim()) reasons.push("\u672A\u914D\u7F6E frps \u670D\u52A1\u5668\u5730\u5740");
11479
11604
  if (!x.frpcRunning) reasons.push("\u516C\u7F51\u96A7\u9053\u672A\u5EFA\u7ACB\uFF08frpc \u672A\u8FD0\u884C\uFF09");
11480
11605
  }
@@ -11498,6 +11623,7 @@ if (typeof crypto.randomUUID !== 'function') {
11498
11623
  buildFrpcToml,
11499
11624
  normalizeFrpSettings,
11500
11625
  normalizeRemoteMode,
11626
+ generateRemoteToken,
11501
11627
  validateFrpServerSettings,
11502
11628
  validateWanAccess,
11503
11629
  projectRemoteView
@@ -11509,7 +11635,7 @@ if (typeof crypto.randomUUID !== 'function') {
11509
11635
  var require_lan2 = __commonJS({
11510
11636
  "src/app/domain-actions/lan.js"(exports2, module2) {
11511
11637
  "use strict";
11512
- var { validateWanAccess } = require_core4();
11638
+ var { validateWanAccess, generateRemoteToken } = require_core4();
11513
11639
  var { remoteTokenStrength } = require_credential();
11514
11640
  function lanModule(deps) {
11515
11641
  const lm = typeof deps.getLifecycleManager === "function" ? deps.getLifecycleManager() : null;
@@ -11563,26 +11689,49 @@ var require_lan2 = __commonJS({
11563
11689
  }
11564
11690
  return {
11565
11691
  /** 远程控制模式唯一写入口(off|lan|wan)。mode 必须显式给出——缺省归 'off' 会让
11566
- * 漏字段的请求静默关闭远程控制。wan 前置闸:必须先有合规访问令牌。 */
11692
+ * 漏字段的请求静默关闭远程控制。
11693
+ * 开启(lan|wan)时若该实例压根没有访问令牌,就在这里分配一个并随模式一次落盘:
11694
+ * 「开启远程控制」是用户唯一的开远程动作,把「先去别处设令牌」留在流程里,产出的是开关已开、
11695
+ * 屏幕无二维码、用户也不知凭据为何的半截状态。已有令牌(含过弱的)一律不覆盖——静默改写
11696
+ * 用户自设凭据是另一类事故,弱令牌交给 wan 闸显式拒绝并把用户引到令牌框。
11697
+ * wan 前置闸按**补齐后**的令牌复判:分配在复判之前完成、落盘在复判之后发生,被拒仍零写入。 */
11567
11698
  setRemoteMode(id, mode) {
11568
11699
  if (mode !== "off" && mode !== "lan" && mode !== "wan") {
11569
11700
  return { ok: false, error: "mode \u5FC5\u987B\u663E\u5F0F\u7ED9\u51FA\uFF08off|lan|wan\uFF09" };
11570
11701
  }
11571
11702
  const target = resolveTarget(id);
11572
11703
  if (!target) return { ok: false, error: "\u5B9E\u4F8B\u4E0D\u5B58\u5728" };
11704
+ const allocate = mode !== "off" && !String(target.remoteToken || "").trim();
11705
+ const nextToken = allocate ? generateRemoteToken() : target.remoteToken;
11573
11706
  if (mode === "wan") {
11574
- const v = validateWanAccess({ remoteToken: target.remoteToken });
11707
+ const v = validateWanAccess({ remoteToken: nextToken });
11575
11708
  if (!v.ok) return { ok: false, error: v.error };
11576
11709
  }
11577
11710
  if (target.kind === "main") {
11578
- if (target.mode !== mode) applyMainIntent({ remoteMode: mode }, { id: "main", name: "\u539F\u751F DSH", mode });
11579
- return { ok: true };
11711
+ const patch = { remoteMode: mode };
11712
+ if (allocate) patch.remoteToken = nextToken;
11713
+ if (allocate || target.mode !== mode) {
11714
+ applyMainIntent(patch, { id: "main", name: "\u539F\u751F DSH", mode });
11715
+ if (allocate) {
11716
+ const events = g.getEvents();
11717
+ if (events) {
11718
+ try {
11719
+ events.append("dsh_remote_token_changed", { id: "main", tokenSet: true, autoAllocated: true });
11720
+ } catch {
11721
+ }
11722
+ }
11723
+ }
11724
+ }
11725
+ return { ok: true, tokenAutoAllocated: allocate };
11580
11726
  }
11581
- return g.getInstances().updateInstance(id, { remoteMode: mode });
11727
+ const r = allocate ? g.getInstances().updateInstance(id, { remoteMode: mode, remoteToken: nextToken }) : g.getInstances().updateInstance(id, { remoteMode: mode });
11728
+ if (r && r.ok !== false) r.tokenAutoAllocated = allocate;
11729
+ return r;
11582
11730
  },
11583
- /** 访问令牌唯一写入口。token 必须是字符串:空串=显式清除;缺字段/非字符串=请求方缺陷,
11584
- * 拒绝而非当作清除(漏 token 字段清掉访问凭据是事故,不是语义)。lan 模式可无令牌,
11585
- * wan 模式的守门由执行边界闸兜住。 */
11731
+ /** 访问令牌唯一显式写入口。token 必须是字符串:空串=显式清除;缺字段/非字符串=请求方缺陷,
11732
+ * 拒绝而非当作清除(漏 token 字段清掉访问凭据是事故,不是语义)。
11733
+ * lan 模式清除后仍然可用(relay 空令牌放行),但下一次写入远程控制模式会由 setRemoteMode 重新分配;
11734
+ * wan 模式的守门由执行边界闸兜住(令牌清空后 syncFrpc 复判不过闸即停隧道)。 */
11586
11735
  setRemoteToken(id, token) {
11587
11736
  if (typeof token !== "string") {
11588
11737
  return { ok: false, error: "token \u5FC5\u987B\u663E\u5F0F\u7ED9\u51FA\uFF08\u7A7A\u4E32=\u6E05\u9664\uFF09" };
@@ -17587,7 +17736,7 @@ var require_browser2 = __commonJS({
17587
17736
  const tz = pick(TZ_POOL);
17588
17737
  const antiEnv = Object.assign({}, sysEnv, { TZ: tz, LANG: lang });
17589
17738
  const r = platform.browser.launchIsolated(url, { profileDir: tmpProfile, size, lang, antiEnv, sysEnv, onExit });
17590
- if (!r || !r.ok) return null;
17739
+ if (!r || !r.ok) return { profile: null, result: r || { ok: false, reason: "spawn-failed" } };
17591
17740
  if (r.isolated) {
17592
17741
  const t30 = setTimeout(() => {
17593
17742
  try {
@@ -17597,9 +17746,9 @@ var require_browser2 = __commonJS({
17597
17746
  }, 30 * 60 * 1e3);
17598
17747
  if (t30.unref) t30.unref();
17599
17748
  }
17600
- return tmpProfile;
17601
- } catch {
17602
- return null;
17749
+ return { profile: tmpProfile, result: r };
17750
+ } catch (e) {
17751
+ return { profile: null, result: { ok: false, reason: "spawn-failed", error: "\u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25\uFF1A" + (e && e.message || e) } };
17603
17752
  }
17604
17753
  }
17605
17754
  module2.exports = { graphicalEnv, openInBrowser };
@@ -17777,7 +17926,7 @@ var require_oauth = __commonJS({
17777
17926
  promise.catch(() => {
17778
17927
  });
17779
17928
  st._ccLoginPromise = promise;
17780
- const tmpProfile = openInBrowser(authUrl, () => {
17929
+ const opened = openInBrowser(authUrl, () => {
17781
17930
  if (st._ccLoginRound !== roundId) return;
17782
17931
  if (st._ccLoginReject) {
17783
17932
  const r = st._ccLoginReject;
@@ -17789,7 +17938,8 @@ var require_oauth = __commonJS({
17789
17938
  }
17790
17939
  }
17791
17940
  });
17792
- if (!tmpProfile) {
17941
+ const evidence = opened.result || {};
17942
+ if (!opened.profile) {
17793
17943
  st._ccLoginPromise = null;
17794
17944
  st._ccLoginResolve = null;
17795
17945
  st._ccLoginReject = null;
@@ -17802,10 +17952,29 @@ var require_oauth = __commonJS({
17802
17952
  } catch {
17803
17953
  }
17804
17954
  st._ccLogin = null;
17805
- return { ok: false, error: "\u65E0\u6CD5\u8C03\u8D77\u6D4F\u89C8\u5668\uFF08\u672A\u627E\u5230\u53EF\u7528\u6D4F\u89C8\u5668\uFF09\uFF0C\u8BF7\u624B\u52A8\u6253\u5F00: " + authUrl };
17955
+ return {
17956
+ ok: false,
17957
+ authUrl,
17958
+ url: authUrl,
17959
+ opened: false,
17960
+ confirmed: false,
17961
+ handedOff: false,
17962
+ reason: evidence.reason || "no-launcher",
17963
+ 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"
17964
+ };
17806
17965
  }
17807
- st._ccLogin = { state, port, server, tmpProfile };
17808
- return { ok: true, authUrl, state, port, waitMs: 18e4 };
17966
+ st._ccLogin = { state, port, server, tmpProfile: opened.profile };
17967
+ return Object.assign({}, evidence, {
17968
+ ok: true,
17969
+ authUrl,
17970
+ url: authUrl,
17971
+ state,
17972
+ port,
17973
+ waitMs: 18e4,
17974
+ opened: true,
17975
+ // 非隔离引擎(Safari/snap 包装器)由平台层降级:账号隔离不成立,换账号只能靠超时重发或手动窗口。
17976
+ isolated: evidence.isolated === true
17977
+ });
17809
17978
  }
17810
17979
  async function commandcodeLoginWait(timeoutMs) {
17811
17980
  const p = st._ccLoginPromise;
@@ -21545,6 +21714,9 @@ var require_market = __commonJS({
21545
21714
  this._cache = null;
21546
21715
  this._ts = 0;
21547
21716
  this._inFlight = null;
21717
+ this._lastError = null;
21718
+ this._nextAllowAt = 0;
21719
+ this.retryBackoffMs = opts.retryBackoffMs || 6e4;
21548
21720
  this.buildBudgetMs = opts.buildBudgetMs || 24e4;
21549
21721
  this.loadFromDisk();
21550
21722
  }
@@ -21558,38 +21730,51 @@ var require_market = __commonJS({
21558
21730
  saveToDisk() {
21559
21731
  marketCache.saveIndex(this.cacheDir, this.indexFile, this._cache, this.logger);
21560
21732
  }
21561
- /** 读取插件列表(带 TTL 缓存 + 并发去重)。 */
21562
- async getIndex(force = false) {
21563
- if (this._cache && !force) {
21564
- this._refreshIfStale();
21565
- return this._cache;
21566
- }
21567
- if (this._inFlight) return this._inFlight;
21568
- return this._startBuild();
21733
+ /** 索引快照,耗时上限必须是「读内存」:构建(默认预算 4 分钟)交 _requestBuild 在后台跑。
21734
+ * 挂到请求上等构建,面板 15s 计时会先放弃而服务端仍在跑 —— 用户见失败、结论却是好的。
21735
+ * 无缓存且上次构建刚失败时按 retryBackoffMs 退避,免得每次读都点燃一轮 4 分钟构建;force 不受退避约束。 */
21736
+ getIndex(force = false) {
21737
+ const need = !this._cache || force || Date.now() - this._ts >= this.ttl;
21738
+ if (need && (force || Date.now() >= this._nextAllowAt)) this._requestBuild();
21739
+ return Promise.resolve(this._view());
21740
+ }
21741
+ /** 快照视图(无缓存也返回合法空索引,绝不为取数据而触网)。 */
21742
+ _view() {
21743
+ const c = this._cache;
21744
+ return {
21745
+ ok: true,
21746
+ indexedAt: c ? c.indexedAt : 0,
21747
+ sources: c ? c.sources : { npm: 0, github: 0, community: 0 },
21748
+ total: c ? c.total : 0,
21749
+ plugins: c ? c.plugins : [],
21750
+ building: !!this._inFlight,
21751
+ error: this._lastError
21752
+ };
21569
21753
  }
21570
- /** 启动一次构建并登记为并发去重引用,返回原始 promise。两条不变量必须同时成立:
21571
- * (a) 无消费者的后台刷新失败不得成为进程级 unhandledRejection —— 靠给 raw 挂 no-op handler「标记已处理」实现,而不是把 promise 消化掉;
21572
- * (b) 被消费者取走时仍须如实失败 —— raw 的 reject 语义不变(api/domains/plugins.js 的 GET /plugins/market 据此回答 500)。
21573
- * 若改成「消化后存回 _inFlight」,force 请求会取到它并在失败时 resolve 成 undefined -> 200 + 空体。 */
21574
- _startBuild() {
21754
+ /** 启动一次构建(无消费者 await)并登记为在飞引用;两条不变量:
21755
+ * (a) 无人等待的失败不得成为进程级 unhandledRejection —— 给 raw 挂 no-op handler「标记已处理」;
21756
+ * (b) 去重:已有在飞构建就直接复用,force 连点不得叠加并发构建(M-i)。
21757
+ * 失败原因记入 _lastError 并由快照如实上报 —— 取不到不能显示成「没有插件」。 */
21758
+ _requestBuild() {
21759
+ if (this._inFlight) return this._inFlight;
21575
21760
  const raw = this.buildIndex();
21576
21761
  raw.catch(() => {
21577
21762
  });
21578
21763
  this._inFlight = raw;
21579
- raw.then(() => {
21580
- }, () => {
21581
- }).then(() => {
21764
+ raw.then(
21765
+ () => {
21766
+ this._lastError = null;
21767
+ },
21768
+ (e) => {
21769
+ this._lastError = e && e.message || String(e);
21770
+ if (!this._cache) this._nextAllowAt = Date.now() + this.retryBackoffMs;
21771
+ this.logger.warn && this.logger.warn("market: \u7D22\u5F15\u6784\u5EFA\u5931\u8D25\uFF08\u6CBF\u7528\u65E7\u7F13\u5B58\uFF09: " + this._lastError);
21772
+ }
21773
+ ).then(() => {
21582
21774
  if (this._inFlight === raw) this._inFlight = null;
21583
21775
  });
21584
21776
  return raw;
21585
21777
  }
21586
- _refreshIfStale() {
21587
- if (Date.now() - this._ts < this.ttl) return;
21588
- if (this._inFlight) return;
21589
- this._startBuild().catch((e) => {
21590
- this.logger.warn && this.logger.warn("market: \u540E\u53F0\u5237\u65B0\u7D22\u5F15\u5931\u8D25\uFF08\u6CBF\u7528\u65E7\u7F13\u5B58\uFF09: " + (e && e.message || e));
21591
- });
21592
- }
21593
21778
  async buildIndex() {
21594
21779
  const start = Date.now();
21595
21780
  const bctx = { deadline: start + this.buildBudgetMs, truncated: /* @__PURE__ */ new Set() };
@@ -22753,22 +22938,57 @@ var require_updater = __commonJS({
22753
22938
  if (latest !== null) ctx._updCache[name] = { latest, at: Date.now() };
22754
22939
  return { latest, error };
22755
22940
  }
22756
- async function checkUpdates(ctx, force) {
22941
+ function checkUpdates(ctx, force) {
22942
+ if (!ctx._updSnapshot || force) _requestCheck(ctx, force);
22943
+ const s = ctx._updSnapshot;
22944
+ return Promise.resolve({
22945
+ ok: true,
22946
+ checkedAt: s ? s.checkedAt : 0,
22947
+ plugins: s ? s.plugins : [],
22948
+ refreshing: !!ctx._updInFlight,
22949
+ error: s ? s.error : null
22950
+ });
22951
+ }
22952
+ function _requestCheck(ctx, force) {
22953
+ if (ctx._updInFlight) return ctx._updInFlight;
22954
+ const raw = refreshUpdates(ctx, force);
22955
+ raw.catch(() => {
22956
+ });
22957
+ ctx._updInFlight = raw;
22958
+ raw.catch((e) => {
22959
+ const msg = e && e.message || String(e);
22960
+ if (ctx.logger && ctx.logger.warn) ctx.logger.warn("plugin check-updates \u5931\u8D25: " + msg);
22961
+ if (!ctx._updSnapshot) ctx._updSnapshot = { checkedAt: 0, plugins: [], error: msg };
22962
+ }).then(() => {
22963
+ if (ctx._updInFlight === raw) ctx._updInFlight = null;
22964
+ });
22965
+ return raw;
22966
+ }
22967
+ async function refreshUpdates(ctx, force) {
22757
22968
  const targets = [ctx._nativeTarget(), ...ctx._allSandboxTargets()];
22758
22969
  const meta = /* @__PURE__ */ new Map();
22759
22970
  for (const t of targets) {
22760
22971
  for (const p of ctx.installedOn(t)) {
22761
22972
  if (meta.has(p.name)) continue;
22762
- const st = specType(p.source);
22763
- let latest = null;
22764
- if (st === "npm" && ctx.dist) latest = (await latestCached(ctx, p.name, force)).latest;
22765
- meta.set(p.name, { specType: st, latest });
22973
+ meta.set(p.name, { specType: specType(p.source), latest: null, error: null });
22766
22974
  }
22767
22975
  }
22976
+ const npmNames = [...meta.entries()].filter(([, m]) => m.specType === "npm" && ctx.dist).map(([name]) => name);
22977
+ const errors = [];
22978
+ for (let i = 0; i < npmNames.length; i += 6) {
22979
+ const slice = npmNames.slice(i, i + 6);
22980
+ await Promise.all(slice.map(async (name) => {
22981
+ const r = await latestCached(ctx, name, force);
22982
+ const m = meta.get(name);
22983
+ m.latest = r.latest;
22984
+ m.error = r.error;
22985
+ if (r.error) errors.push(name + ": " + r.error);
22986
+ }));
22987
+ }
22768
22988
  const rows = [];
22769
22989
  for (const t of targets) {
22770
22990
  for (const p of ctx.installedOn(t)) {
22771
- const m = meta.get(p.name) || { specType: "npm", latest: null };
22991
+ const m = meta.get(p.name) || { specType: "npm", latest: null, error: null };
22772
22992
  const updateAvailable = m.specType === "npm" && isUpdateAvailable(m.latest, p.version);
22773
22993
  rows.push({ name: p.name, target: t.id, targetName: t.name, installed: p.version || null, latest: m.latest || null, updateAvailable, specType: m.specType, bundle: p.bundle });
22774
22994
  }
@@ -22777,13 +22997,15 @@ var require_updater = __commonJS({
22777
22997
  for (const row of rows) {
22778
22998
  let rec = byName.get(row.name);
22779
22999
  if (!rec) {
22780
- byName.set(row.name, { name: row.name, specType: row.specType, targets: [] });
23000
+ byName.set(row.name, { name: row.name, specType: row.specType, error: meta.get(row.name) ? meta.get(row.name).error : null, targets: [] });
22781
23001
  rec = byName.get(row.name);
22782
23002
  }
22783
23003
  rec.targets.push({ id: row.target, name: row.targetName, installed: row.installed, latest: row.latest, updateAvailable: row.updateAvailable });
22784
23004
  }
22785
- const plugins = [...byName.values()].map((x) => ({ name: x.name, specType: x.specType, updateAvailable: x.targets.some((t) => t.updateAvailable), targets: x.targets })).sort((a, b) => a.name.localeCompare(b.name));
22786
- return { ok: true, checkedAt: Date.now(), plugins };
23005
+ const plugins = [...byName.values()].map((x) => ({ name: x.name, specType: x.specType, error: x.error, updateAvailable: x.targets.some((t) => t.updateAvailable), targets: x.targets })).sort((a, b) => a.name.localeCompare(b.name));
23006
+ const snap = { checkedAt: Date.now(), plugins, error: errors.length ? errors.join("; ") : null };
23007
+ ctx._updSnapshot = snap;
23008
+ return snap;
22787
23009
  }
22788
23010
  async function update(ctx, name, targetStr) {
22789
23011
  if (!name) return { ok: false, error: "missing plugin name" };
@@ -22885,6 +23107,8 @@ var require_plugin = __commonJS({
22885
23107
  this.tasks = opts.tasks || null;
22886
23108
  this._updCache = {};
22887
23109
  this._updTTL = 6 * 3600 * 1e3;
23110
+ this._updSnapshot = null;
23111
+ this._updInFlight = null;
22888
23112
  this.jobs = createJobs({ tasks: this.tasks });
22889
23113
  this.layers = createLayers({ overlayFile: this.overlayFile, logger: this.logger });
22890
23114
  this.store = new store.PluginStore({ getInventory: () => this.inventory() });
@@ -25283,7 +25507,7 @@ var require_guard = __commonJS({
25283
25507
  var fs2 = require("node:fs");
25284
25508
  var path2 = require("node:path");
25285
25509
  function owns(pathname) {
25286
- 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 === "/ports" || pathname === "/shutdown";
25510
+ 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 === "/ports" || pathname === "/shutdown";
25287
25511
  }
25288
25512
  function fetchDshChangelog(res, sup) {
25289
25513
  const v = sup && sup.nativeManager ? sup.nativeManager.versionInfo() : {};
@@ -25295,7 +25519,7 @@ var require_guard = __commonJS({
25295
25519
  return res.end(md);
25296
25520
  }
25297
25521
  function handle(ctx) {
25298
- const { sup, req, res, pathname, send, collectBody, originAllowed } = ctx;
25522
+ const { sup, req, res, pathname, identity, send, collectBody, originAllowed, browser } = ctx;
25299
25523
  if (req.method === "GET" && pathname === "/changelog") {
25300
25524
  return fetchDshChangelog(res, sup);
25301
25525
  }
@@ -25459,6 +25683,27 @@ var require_guard = __commonJS({
25459
25683
  if (req.method === "GET" && pathname === "/env/node-lts") {
25460
25684
  return sup.nodeLtsStatus().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
25461
25685
  }
25686
+ if (req.method === "POST" && pathname === "/env/open-url") {
25687
+ if (!identity.loopback) {
25688
+ req.resume();
25689
+ return send(403, { ok: false, error: "\u4EC5\u5185\u6838\u6240\u5728\u673A\u5668\u53EF\u8BF7\u5185\u6838\u8C03\u8D77\u6D4F\u89C8\u5668\uFF0C\u8BF7\u590D\u5236\u6216\u81EA\u884C\u6253\u5F00\u8BE5\u5730\u5740" });
25690
+ }
25691
+ if (!originAllowed(req, sup.config.apiPort)) {
25692
+ req.resume();
25693
+ return send(403, { ok: false, error: "cross-origin request rejected" });
25694
+ }
25695
+ collectBody(req, res, 4096, (body) => {
25696
+ let url = null;
25697
+ try {
25698
+ const j = body ? JSON.parse(body) : {};
25699
+ if (typeof j.url === "string") url = j.url;
25700
+ } catch {
25701
+ }
25702
+ if (!url) return send(400, { ok: false, error: '\u9700\u8981 {"url":"http(s)://\u2026"}' });
25703
+ return Promise.resolve(browser.openBrowser(url)).then((r) => send(r.ok ? 200 : 500, r)).catch((e) => send(500, { ok: false, reason: "spawn-failed", error: "\u6253\u5F00\u6D4F\u89C8\u5668\u5931\u8D25\uFF1A" + (e && e.message || e), url }));
25704
+ });
25705
+ return;
25706
+ }
25462
25707
  if (req.method === "GET" && pathname === "/ports") {
25463
25708
  return Promise.resolve(sup.listPorts()).then((r) => send(200, r)).catch((e) => send(500, { error: e && e.message }));
25464
25709
  }
@@ -25953,7 +26198,6 @@ var require_exchange = __commonJS({
25953
26198
  var require_instances = __commonJS({
25954
26199
  "src/api/domains/instances.js"(exports2, module2) {
25955
26200
  "use strict";
25956
- var platform = require_os();
25957
26201
  var execPath = require_exec_path();
25958
26202
  var crypto = require("node:crypto");
25959
26203
  var { bootstrapDshCookie } = require_exchange();
@@ -26055,17 +26299,18 @@ var require_instances = __commonJS({
26055
26299
  return sharedErr + "\uFF1B" + FORMS + "\uFF1B\u9700\u8981\u5176\u5B83\u53EF\u6267\u884C\u8BF7\u8D70\u63D2\u4EF6\u5B89\u88C5\u901A\u9053";
26056
26300
  }
26057
26301
  function handle(ctx) {
26058
- const { sup, req, res, pathname, identity, send, collectBody, originAllowed, tokOf } = ctx;
26302
+ const { sup, req, res, pathname, identity, send, collectBody, originAllowed, tokOf, browser } = ctx;
26059
26303
  function openInSystemBrowser(url) {
26060
- return platform.browser.open(url);
26304
+ return browser.openBrowser(url);
26061
26305
  }
26062
26306
  if (req.method === "GET" && pathname === "/open") return handleOpen(ctx);
26063
26307
  if (req.method === "GET" && pathname === "/instances") {
26064
26308
  const decorate = (it) => {
26065
26309
  const tok = tokOf(it.id);
26066
26310
  const out = Object.assign({}, it);
26067
- delete out.remoteToken;
26068
26311
  const loopback = identity.loopback;
26312
+ out.tokenSet = !!String(it.remoteToken || "").trim();
26313
+ if (!loopback) delete out.remoteToken;
26069
26314
  out.authUrl = tok && loopback ? "http://127.0.0.1:" + it.port + "/?token=" + encodeURIComponent(tok) : "http://127.0.0.1:" + it.port + "/";
26070
26315
  out.tokenPresent = loopback && !!tok;
26071
26316
  return out;
@@ -26121,9 +26366,13 @@ var require_instances = __commonJS({
26121
26366
  if (!(Number(it.port) > 0)) return send(400, { ok: false, error: "\u975E\u6CD5\u7AEF\u53E3" });
26122
26367
  const code = issueOpenWebCode(j.id);
26123
26368
  const url = "http://127.0.0.1:" + sup.config.apiPort + "/open?code=" + code;
26124
- const ok = openInSystemBrowser(url);
26125
- if (!ok) dropOpenWebCode(code);
26126
- return send(ok ? 200 : 500, { ok, url });
26369
+ return Promise.resolve(openInSystemBrowser(url)).then((r) => {
26370
+ if (!r.ok) dropOpenWebCode(code);
26371
+ return send(r.ok ? 200 : 500, r);
26372
+ }).catch((e) => {
26373
+ dropOpenWebCode(code);
26374
+ return send(500, { ok: false, reason: "spawn-failed", error: "\u6253\u5F00\u6D4F\u89C8\u5668\u5931\u8D25\uFF1A" + (e && e.message || e), url });
26375
+ });
26127
26376
  } catch (e) {
26128
26377
  return send(500, { ok: false, error: e.message });
26129
26378
  }
@@ -26568,6 +26817,7 @@ var require_server = __commonJS({
26568
26817
  var { serveStatic } = require_static();
26569
26818
  var { identify } = require_identity3();
26570
26819
  var { originAllowed, requestHasAccessKey, isShellOrigin } = require_security();
26820
+ var browserExit = require_browser();
26571
26821
  function safeFail(res, err, where) {
26572
26822
  try {
26573
26823
  const body = JSON.stringify({ ok: false, error: err && err.message || String(err) });
@@ -26585,7 +26835,8 @@ var require_server = __commonJS({
26585
26835
  } catch {
26586
26836
  }
26587
26837
  }
26588
- function createServer(sup) {
26838
+ function createServer(sup, deps) {
26839
+ const browser = deps && deps.browser || browserExit;
26589
26840
  return http2.createServer((req, res) => {
26590
26841
  const shellOrigin = (() => {
26591
26842
  const o = req.headers.origin;
@@ -26653,7 +26904,7 @@ var require_server = __commonJS({
26653
26904
  } catch (e) {
26654
26905
  return send(400, { error: "bad request encoding" });
26655
26906
  }
26656
- const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed, tokOf };
26907
+ const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed, tokOf, browser };
26657
26908
  for (const d of API_DOMAINS) {
26658
26909
  if (d.owns(pathname)) {
26659
26910
  try {