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

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 +389 -133
  2. package/package.json +1 -1
  3. package/ui-react/assets/InstancesPage-DadqCqud.js +3 -0
  4. package/ui-react/assets/LanPage-DWzQ-VLz.js +4 -0
  5. package/ui-react/assets/OverviewPage-D9y-GeR2.js +2 -0
  6. package/ui-react/assets/PluginsPage-BwLIhj7E.js +1 -0
  7. package/ui-react/assets/RouterPage-BeMIxdSL.js +1 -0
  8. package/ui-react/assets/SettingsPage-R7lkiQwq.js +1 -0
  9. package/ui-react/assets/{TasksPage-BsXiGMar.js → TasksPage-A3SFro8F.js} +1 -1
  10. package/ui-react/assets/openExternal-DOkxrLUq.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-BbSavtNN.js +41 -0
  15. package/ui-react/assets/supervisor-Btn_MIEc.js +11 -0
  16. package/ui-react/assets/supervisor-YWIsBeZU.css +2 -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.12");
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
+ // 可打开,但 win32 的两种启动形态(explorer.exe 调度器 / 裸 URL 直启解析到的浏览器)退出码都不携带
4759
+ // 窗口是否出现的信息,故每次调用最多到 handedOff(见 browser.js#ownsItsWindow)。能力位只声明「能交出去」。
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,126 @@ 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\u8FD9\u6B21\u542F\u52A8\u62FF\u4E0D\u5230\u7A97\u53E3\u662F\u5426\u51FA\u73B0\u7684\u8BC1\u636E";
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": "\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",
5505
+ "killed-by-signal": "\u6D4F\u89C8\u5668\u542F\u52A8\u547D\u4EE4\u88AB\u7CFB\u7EDF\u7EC8\u6B62\uFF0C\u7A97\u53E3\u672A\u51FA\u73B0",
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 ownsItsWindow(via, platform) {
5510
+ if (via !== "dispatcher") return false;
5511
+ return platform !== "win32";
5512
+ }
5513
+ function openPlan(platform, url, opts) {
5514
+ const o = opts || {};
5515
+ const pl = platform || process.platform;
5516
+ const db = o.defaultBrowser;
5517
+ const engine = db && db.bin ? engineOf(db.bin) : "other";
5518
+ if (engine === "chromium" || engine === "firefox") {
5519
+ return { bin: db.bin, engine, via: "browser", baseArgs: (db.baseArgs || []).concat([url]), exitIsEvidence: ownsItsWindow("browser", pl) };
5520
+ }
5521
+ const c = openCommand(pl, url);
5522
+ return { bin: c.cmd, engine: "other", via: "dispatcher", baseArgs: c.args, exitIsEvidence: ownsItsWindow("dispatcher", pl) };
5523
+ }
5524
+ function observeSpawn(child, windowMs, setTimeoutFn) {
5525
+ return new Promise((resolve) => {
5526
+ let settled = false;
5527
+ let timer;
5528
+ const done = (r) => {
5529
+ if (settled) return;
5530
+ settled = true;
5531
+ clearTimeout(timer);
5532
+ resolve(r);
5533
+ };
5534
+ timer = (setTimeoutFn || setTimeout)(() => done({ stage: "alive" }), windowMs);
5535
+ if (timer && timer.unref) timer.unref();
5536
+ child.on("error", (e) => done({ stage: "error", code: e && e.code || String(e) }));
5537
+ child.on("exit", (code, signal) => done({ stage: "exit", code, signal }));
5538
+ });
5539
+ }
5540
+ var OPEN_OBSERVE_MS = 1500;
5541
+ async function openBrowser(url, o) {
5542
+ const opts = o || {};
5543
+ const pl = opts.platform || process.platform;
5544
+ if (!isSafeHttpUrl(url)) {
5545
+ return outcome({ ok: false, reason: "unsafe-url", url: String(url || ""), evidence: null });
5546
+ }
5547
+ if (!SUPPORTED_OPEN_PLATFORMS.includes(pl)) {
5548
+ return outcome({ ok: false, reason: "unsupported-platform", url, evidence: { platform: pl } });
5549
+ }
5550
+ const observeMs = opts.observeMs === void 0 ? OPEN_OBSERVE_MS : opts.observeMs;
5551
+ const spawnWith = typeof opts.spawn === "function" ? opts.spawn : _spawnDetachedIgnored;
5552
+ const avail = typeof opts.binAvailable === "function" ? opts.binAvailable : binAvailable;
5553
+ const observe = typeof opts.observe === "function" ? opts.observe : observeSpawn;
5554
+ let db;
5555
+ if ("defaultBrowser" in opts) db = opts.defaultBrowser;
5556
+ else if (typeof opts.resolveDefault === "function") db = opts.resolveDefault(pl, opts.resolveDeps || {});
5557
+ else db = resolveDefaultBrowser(pl, opts.resolveDeps || {});
5558
+ const plan = openPlan(pl, url, { defaultBrowser: db });
5559
+ const evidence = { bin: plan.bin, engine: plan.engine, via: plan.via, ownsWindow: plan.exitIsEvidence === true, exitCode: null, exitSignal: null, error: null };
5560
+ const desktopAvailable = typeof opts.desktopAvailable === "function" ? opts.desktopAvailable : desktop.sessionAvailable;
5561
+ if (pl === "linux" && !desktopAvailable()) {
5562
+ return outcome({ ok: false, reason: "no-desktop-session", url, evidence });
5563
+ }
5564
+ if (!avail(plan.bin)) {
5565
+ return outcome({ ok: false, reason: "no-launcher", url, evidence });
5566
+ }
5567
+ let child;
5568
+ try {
5569
+ child = spawnWith(plan.bin, plan.baseArgs);
5570
+ } catch (e) {
5571
+ evidence.error = e && e.code || String(e);
5572
+ return outcome({ ok: false, reason: "spawn-failed", url, evidence });
5573
+ }
5574
+ if (!child) return outcome({ ok: false, reason: "spawn-failed", url, evidence });
5575
+ const seen = await observe(child, observeMs, opts.setTimeout);
5576
+ evidence.error = seen.stage === "error" ? String(seen.code) : null;
5577
+ evidence.exitCode = seen.code === void 0 ? null : seen.code;
5578
+ evidence.exitSignal = seen.signal === void 0 ? null : seen.signal;
5579
+ if (seen.stage === "error") return outcome({ ok: false, reason: "spawn-failed", url, evidence });
5580
+ const exitDecides = seen.stage === "exit" && plan.exitIsEvidence === true;
5581
+ if (exitDecides && (seen.code !== 0 || seen.signal)) {
5582
+ return outcome({
5583
+ ok: false,
5584
+ reason: seen.signal ? "killed-by-signal" : "exit-nonzero",
5585
+ url,
5586
+ evidence,
5587
+ error: FAILURE_TEXT[seen.signal ? "killed-by-signal" : "exit-nonzero"] + "\uFF08" + (seen.signal || seen.code) + "\uFF09"
5588
+ });
5589
+ }
5590
+ return outcome({ ok: true, confirmed: exitDecides, handedOff: !exitDecides, url, evidence });
5591
+ }
5592
+ function _spawnDetachedIgnored(bin, args) {
5421
5593
  try {
5422
- const c = openCommand(process.platform, url);
5423
- const p = spawnOS.detachedIgnored(c.cmd, c.args);
5594
+ const p = spawnOS.detachedIgnored(bin, args);
5424
5595
  p.on("error", () => {
5425
5596
  });
5426
5597
  p.unref();
5427
- return true;
5598
+ return p;
5428
5599
  } catch {
5429
- return false;
5600
+ return null;
5430
5601
  }
5431
5602
  }
5432
5603
  function _spawnDetached(bin, args, env, onExit) {
@@ -5546,8 +5717,8 @@ var require_browser = __commonJS({
5546
5717
  }
5547
5718
  function resolveDefaultLinux(runOut, readFile, exists, env, home) {
5548
5719
  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;
5720
+ const desktop2 = id && id.trim();
5721
+ if (!desktop2 || !/^[A-Za-z0-9._-]+\.desktop$/.test(desktop2)) return null;
5551
5722
  const dirs = [
5552
5723
  env.XDG_DATA_HOME || path2.join(home, ".local", "share"),
5553
5724
  "/usr/local/share",
@@ -5555,7 +5726,7 @@ var require_browser = __commonJS({
5555
5726
  "/var/lib/snapd/desktop"
5556
5727
  ].map((d) => path2.join(d, "applications"));
5557
5728
  for (const dir of dirs) {
5558
- const p = path2.join(dir, desktop);
5729
+ const p = path2.join(dir, desktop2);
5559
5730
  let text = null;
5560
5731
  try {
5561
5732
  if (exists(p)) text = readFile(p);
@@ -5627,12 +5798,13 @@ var require_browser = __commonJS({
5627
5798
  }
5628
5799
  function launchIsolated(url, o) {
5629
5800
  const opts = o || {};
5630
- if (!isSafeHttpUrl(url)) return { ok: false, bin: null, isolated: false };
5801
+ if (!isSafeHttpUrl(url)) return Object.assign(outcome({ ok: false, reason: "unsafe-url", url: String(url || "") }), { bin: null, isolated: false });
5631
5802
  const antiEnv = opts.antiEnv || opts.sysEnv || process.env;
5632
5803
  const sysEnv = opts.sysEnv || process.env;
5633
5804
  const onExit = opts.onExit;
5634
5805
  const avail = typeof opts.binAvailable === "function" ? opts.binAvailable : binAvailable;
5635
5806
  const spawnWith = typeof opts.spawn === "function" ? opts.spawn : _spawnDetached;
5807
+ const fail = (reason, extra) => Object.assign(outcome(Object.assign({ ok: false, reason, url }, extra)), { bin: null, isolated: false });
5636
5808
  try {
5637
5809
  let db;
5638
5810
  if ("defaultBrowser" in opts) db = opts.defaultBrowser;
@@ -5644,19 +5816,23 @@ var require_browser = __commonJS({
5644
5816
  size: opts.size,
5645
5817
  lang: opts.lang
5646
5818
  });
5647
- if (!avail(plan.bin)) return { ok: false, bin: null, isolated: false };
5819
+ if (!avail(plan.bin)) return fail("no-launcher", { evidence: { bin: plan.bin, engine: plan.label, via: plan.isolated ? "browser" : "dispatcher" } });
5648
5820
  const env = plan.envKind === "anti" ? antiEnv : sysEnv;
5649
5821
  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 };
5822
+ if (!p) return fail("spawn-failed");
5823
+ return Object.assign(outcome({ ok: true, confirmed: false, handedOff: true, url }), { bin: plan.label, isolated: plan.isolated });
5824
+ } catch (e) {
5825
+ return fail("spawn-failed", { error: FAILURE_TEXT["spawn-failed"] + "\uFF08" + (e && e.code || e) + "\uFF09" });
5653
5826
  }
5654
5827
  }
5655
5828
  module2.exports = {
5656
- open,
5829
+ openBrowser,
5657
5830
  launchIsolated,
5658
5831
  openCommand,
5832
+ openPlan,
5833
+ ownsItsWindow,
5659
5834
  isolatedPlan,
5835
+ observeSpawn,
5660
5836
  isSafeHttpUrl,
5661
5837
  resolveDefaultBrowser,
5662
5838
  resolveDefaultWin,
@@ -5672,58 +5848,6 @@ var require_browser = __commonJS({
5672
5848
  }
5673
5849
  });
5674
5850
 
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
5851
  // src/platform/os/autostart/win32.js
5728
5852
  var require_win32 = __commonJS({
5729
5853
  "src/platform/os/autostart/win32.js"(exports2, module2) {
@@ -6070,6 +6194,7 @@ var require_os = __commonJS({
6070
6194
  var path2 = require("node:path");
6071
6195
  var ex2 = require_exec();
6072
6196
  var execPath = require_exec_path();
6197
+ var desktop = require_desktop();
6073
6198
  var CAPABILITY_PROFILES = require_capability_profile();
6074
6199
  var PLATFORM = process.platform;
6075
6200
  var ARCH = process.arch;
@@ -6115,6 +6240,7 @@ var require_os = __commonJS({
6115
6240
  p.sandboxEnforcement = hasTool("systemd-run") ? "cgroup" : "supervise";
6116
6241
  p.desktopNotify = hasTool("notify-send");
6117
6242
  p.autostart = hasTool("systemctl");
6243
+ p.openBrowser = desktop.sessionAvailable();
6118
6244
  } else if (pl === "darwin") {
6119
6245
  p.desktopNotify = hasTool("osascript");
6120
6246
  } else if (pl === "win32") {
@@ -6147,7 +6273,7 @@ var require_os = __commonJS({
6147
6273
  // 导出模块对象会抛 platform.notify is not a function,把升级终态误判成失败。
6148
6274
  notify: require_notify().notify,
6149
6275
  browser: require_browser(),
6150
- desktop: require_desktop(),
6276
+ desktop,
6151
6277
  // 图形会话可用性(Linux 需实测 socket)
6152
6278
  autostart: require_autostart()
6153
6279
  };
@@ -10916,6 +11042,7 @@ var require_lan = __commonJS({
10916
11042
  module2.exports = { methods: {
10917
11043
  // 远程控制委托:全部转发给 LanManager;daemon 监督模式经 43108 ctl 委托(异步),本地模式走 LanManager(同步)。
10918
11044
  // 令牌收敛:listLan 输出必须剔除 token/dshToken —— /lan-access 允许 LAN/私网 Host 访问,直出会把 DSH 会话令牌泄漏给局域网,权威仍在 DshTokenService。
11045
+ // 远程访问令牌的明文也不在此下发:它是实例意图字段,只随 /instances 的回环分支交给本机面板(api/domains/instances.js#decorate)。
10919
11046
  // remote(projectRemoteView 产物)为后端单一推导视图 {mode,ready,accessUrl,reasons},不含机密,整段放行给 UI。返回形如 {items,addresses}。
10920
11047
  listLan() {
10921
11048
  const d = depsOf(this);
@@ -10928,7 +11055,7 @@ var require_lan = __commonJS({
10928
11055
  dshPort: it.dshPort,
10929
11056
  wanPort: it.wanPort,
10930
11057
  running: !!it.running,
10931
- // 令牌状态(布尔,不泄明文):wan 模式的安全闸要求已设令牌,UI 据此引导。
11058
+ // 令牌状态(布尔,不泄明文):UI 据此引导,明文的查看/修改走 /instances 的回环分支。
10932
11059
  tokenSet: !!String(it.token || "").trim(),
10933
11060
  remote: it.remote || null
10934
11061
  };
@@ -11097,7 +11224,7 @@ var require_main = __commonJS({
11097
11224
  guardian: m.guardian,
11098
11225
  remoteMode: m.remoteMode,
11099
11226
  remoteToken: m.remoteToken,
11100
- // 进程内消费(LanManager mainOf);API 边界由 api/domains/instances.js 剔除
11227
+ // 进程内消费(LanManager mainOf);API 边界按回环判据决定去留(api/domains/instances.js#decorate)
11101
11228
  unitName: null,
11102
11229
  // systemd 托管已废弃:main 由守卫 spawn/观测
11103
11230
  // 实时运行态:native 条目缺 state 会导致远程控制页误判「实例已停止」
@@ -11463,6 +11590,9 @@ if (typeof crypto.randomUUID !== 'function') {
11463
11590
  }
11464
11591
  return { ok: true };
11465
11592
  }
11593
+ function generateRemoteToken() {
11594
+ return crypto.randomBytes(12).toString("base64url");
11595
+ }
11466
11596
  function normalizeRemoteMode(v) {
11467
11597
  return v === "lan" || v === "wan" ? v : "off";
11468
11598
  }
@@ -11472,9 +11602,9 @@ if (typeof crypto.randomUUID !== 'function') {
11472
11602
  if (mode === "off") return { mode, ready: false, accessUrl: null, reasons: [] };
11473
11603
  const reasons = [];
11474
11604
  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
11605
  else if (!x.cookieReady) reasons.push("\u6B63\u5728\u6CE8\u5165 DSH \u4F1A\u8BDD\u2026");
11477
11606
  if (mode === "wan") {
11607
+ if (!x.tokenSet) reasons.push("\u672A\u8BBE\u8BBF\u95EE\u4EE4\u724C");
11478
11608
  if (!String(x.serverAddr || "").trim()) reasons.push("\u672A\u914D\u7F6E frps \u670D\u52A1\u5668\u5730\u5740");
11479
11609
  if (!x.frpcRunning) reasons.push("\u516C\u7F51\u96A7\u9053\u672A\u5EFA\u7ACB\uFF08frpc \u672A\u8FD0\u884C\uFF09");
11480
11610
  }
@@ -11498,6 +11628,7 @@ if (typeof crypto.randomUUID !== 'function') {
11498
11628
  buildFrpcToml,
11499
11629
  normalizeFrpSettings,
11500
11630
  normalizeRemoteMode,
11631
+ generateRemoteToken,
11501
11632
  validateFrpServerSettings,
11502
11633
  validateWanAccess,
11503
11634
  projectRemoteView
@@ -11509,7 +11640,7 @@ if (typeof crypto.randomUUID !== 'function') {
11509
11640
  var require_lan2 = __commonJS({
11510
11641
  "src/app/domain-actions/lan.js"(exports2, module2) {
11511
11642
  "use strict";
11512
- var { validateWanAccess } = require_core4();
11643
+ var { validateWanAccess, generateRemoteToken } = require_core4();
11513
11644
  var { remoteTokenStrength } = require_credential();
11514
11645
  function lanModule(deps) {
11515
11646
  const lm = typeof deps.getLifecycleManager === "function" ? deps.getLifecycleManager() : null;
@@ -11563,26 +11694,49 @@ var require_lan2 = __commonJS({
11563
11694
  }
11564
11695
  return {
11565
11696
  /** 远程控制模式唯一写入口(off|lan|wan)。mode 必须显式给出——缺省归 'off' 会让
11566
- * 漏字段的请求静默关闭远程控制。wan 前置闸:必须先有合规访问令牌。 */
11697
+ * 漏字段的请求静默关闭远程控制。
11698
+ * 开启(lan|wan)时若该实例压根没有访问令牌,就在这里分配一个并随模式一次落盘:
11699
+ * 「开启远程控制」是用户唯一的开远程动作,把「先去别处设令牌」留在流程里,产出的是开关已开、
11700
+ * 屏幕无二维码、用户也不知凭据为何的半截状态。已有令牌(含过弱的)一律不覆盖——静默改写
11701
+ * 用户自设凭据是另一类事故,弱令牌交给 wan 闸显式拒绝并把用户引到令牌框。
11702
+ * wan 前置闸按**补齐后**的令牌复判:分配在复判之前完成、落盘在复判之后发生,被拒仍零写入。 */
11567
11703
  setRemoteMode(id, mode) {
11568
11704
  if (mode !== "off" && mode !== "lan" && mode !== "wan") {
11569
11705
  return { ok: false, error: "mode \u5FC5\u987B\u663E\u5F0F\u7ED9\u51FA\uFF08off|lan|wan\uFF09" };
11570
11706
  }
11571
11707
  const target = resolveTarget(id);
11572
11708
  if (!target) return { ok: false, error: "\u5B9E\u4F8B\u4E0D\u5B58\u5728" };
11709
+ const allocate = mode !== "off" && !String(target.remoteToken || "").trim();
11710
+ const nextToken = allocate ? generateRemoteToken() : target.remoteToken;
11573
11711
  if (mode === "wan") {
11574
- const v = validateWanAccess({ remoteToken: target.remoteToken });
11712
+ const v = validateWanAccess({ remoteToken: nextToken });
11575
11713
  if (!v.ok) return { ok: false, error: v.error };
11576
11714
  }
11577
11715
  if (target.kind === "main") {
11578
- if (target.mode !== mode) applyMainIntent({ remoteMode: mode }, { id: "main", name: "\u539F\u751F DSH", mode });
11579
- return { ok: true };
11716
+ const patch = { remoteMode: mode };
11717
+ if (allocate) patch.remoteToken = nextToken;
11718
+ if (allocate || target.mode !== mode) {
11719
+ applyMainIntent(patch, { id: "main", name: "\u539F\u751F DSH", mode });
11720
+ if (allocate) {
11721
+ const events = g.getEvents();
11722
+ if (events) {
11723
+ try {
11724
+ events.append("dsh_remote_token_changed", { id: "main", tokenSet: true, autoAllocated: true });
11725
+ } catch {
11726
+ }
11727
+ }
11728
+ }
11729
+ }
11730
+ return { ok: true, tokenAutoAllocated: allocate };
11580
11731
  }
11581
- return g.getInstances().updateInstance(id, { remoteMode: mode });
11732
+ const r = allocate ? g.getInstances().updateInstance(id, { remoteMode: mode, remoteToken: nextToken }) : g.getInstances().updateInstance(id, { remoteMode: mode });
11733
+ if (r && r.ok !== false) r.tokenAutoAllocated = allocate;
11734
+ return r;
11582
11735
  },
11583
- /** 访问令牌唯一写入口。token 必须是字符串:空串=显式清除;缺字段/非字符串=请求方缺陷,
11584
- * 拒绝而非当作清除(漏 token 字段清掉访问凭据是事故,不是语义)。lan 模式可无令牌,
11585
- * wan 模式的守门由执行边界闸兜住。 */
11736
+ /** 访问令牌唯一显式写入口。token 必须是字符串:空串=显式清除;缺字段/非字符串=请求方缺陷,
11737
+ * 拒绝而非当作清除(漏 token 字段清掉访问凭据是事故,不是语义)。
11738
+ * lan 模式清除后仍然可用(relay 空令牌放行),但下一次写入远程控制模式会由 setRemoteMode 重新分配;
11739
+ * wan 模式的守门由执行边界闸兜住(令牌清空后 syncFrpc 复判不过闸即停隧道)。 */
11586
11740
  setRemoteToken(id, token) {
11587
11741
  if (typeof token !== "string") {
11588
11742
  return { ok: false, error: "token \u5FC5\u987B\u663E\u5F0F\u7ED9\u51FA\uFF08\u7A7A\u4E32=\u6E05\u9664\uFF09" };
@@ -17587,7 +17741,7 @@ var require_browser2 = __commonJS({
17587
17741
  const tz = pick(TZ_POOL);
17588
17742
  const antiEnv = Object.assign({}, sysEnv, { TZ: tz, LANG: lang });
17589
17743
  const r = platform.browser.launchIsolated(url, { profileDir: tmpProfile, size, lang, antiEnv, sysEnv, onExit });
17590
- if (!r || !r.ok) return null;
17744
+ if (!r || !r.ok) return { profile: null, result: r || { ok: false, reason: "spawn-failed" } };
17591
17745
  if (r.isolated) {
17592
17746
  const t30 = setTimeout(() => {
17593
17747
  try {
@@ -17597,9 +17751,9 @@ var require_browser2 = __commonJS({
17597
17751
  }, 30 * 60 * 1e3);
17598
17752
  if (t30.unref) t30.unref();
17599
17753
  }
17600
- return tmpProfile;
17601
- } catch {
17602
- return null;
17754
+ return { profile: tmpProfile, result: r };
17755
+ } catch (e) {
17756
+ return { profile: null, result: { ok: false, reason: "spawn-failed", error: "\u6D4F\u89C8\u5668\u542F\u52A8\u5931\u8D25\uFF1A" + (e && e.message || e) } };
17603
17757
  }
17604
17758
  }
17605
17759
  module2.exports = { graphicalEnv, openInBrowser };
@@ -17777,7 +17931,7 @@ var require_oauth = __commonJS({
17777
17931
  promise.catch(() => {
17778
17932
  });
17779
17933
  st._ccLoginPromise = promise;
17780
- const tmpProfile = openInBrowser(authUrl, () => {
17934
+ const opened = openInBrowser(authUrl, () => {
17781
17935
  if (st._ccLoginRound !== roundId) return;
17782
17936
  if (st._ccLoginReject) {
17783
17937
  const r = st._ccLoginReject;
@@ -17789,7 +17943,8 @@ var require_oauth = __commonJS({
17789
17943
  }
17790
17944
  }
17791
17945
  });
17792
- if (!tmpProfile) {
17946
+ const evidence = opened.result || {};
17947
+ if (!opened.profile) {
17793
17948
  st._ccLoginPromise = null;
17794
17949
  st._ccLoginResolve = null;
17795
17950
  st._ccLoginReject = null;
@@ -17802,10 +17957,29 @@ var require_oauth = __commonJS({
17802
17957
  } catch {
17803
17958
  }
17804
17959
  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 };
17960
+ return {
17961
+ ok: false,
17962
+ authUrl,
17963
+ url: authUrl,
17964
+ opened: false,
17965
+ confirmed: false,
17966
+ handedOff: false,
17967
+ reason: evidence.reason || "no-launcher",
17968
+ 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"
17969
+ };
17806
17970
  }
17807
- st._ccLogin = { state, port, server, tmpProfile };
17808
- return { ok: true, authUrl, state, port, waitMs: 18e4 };
17971
+ st._ccLogin = { state, port, server, tmpProfile: opened.profile };
17972
+ return Object.assign({}, evidence, {
17973
+ ok: true,
17974
+ authUrl,
17975
+ url: authUrl,
17976
+ state,
17977
+ port,
17978
+ waitMs: 18e4,
17979
+ opened: true,
17980
+ // 非隔离引擎(Safari/snap 包装器)由平台层降级:账号隔离不成立,换账号只能靠超时重发或手动窗口。
17981
+ isolated: evidence.isolated === true
17982
+ });
17809
17983
  }
17810
17984
  async function commandcodeLoginWait(timeoutMs) {
17811
17985
  const p = st._ccLoginPromise;
@@ -21545,6 +21719,9 @@ var require_market = __commonJS({
21545
21719
  this._cache = null;
21546
21720
  this._ts = 0;
21547
21721
  this._inFlight = null;
21722
+ this._lastError = null;
21723
+ this._nextAllowAt = 0;
21724
+ this.retryBackoffMs = opts.retryBackoffMs || 6e4;
21548
21725
  this.buildBudgetMs = opts.buildBudgetMs || 24e4;
21549
21726
  this.loadFromDisk();
21550
21727
  }
@@ -21558,38 +21735,51 @@ var require_market = __commonJS({
21558
21735
  saveToDisk() {
21559
21736
  marketCache.saveIndex(this.cacheDir, this.indexFile, this._cache, this.logger);
21560
21737
  }
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();
21738
+ /** 索引快照,耗时上限必须是「读内存」:构建(默认预算 4 分钟)交 _requestBuild 在后台跑。
21739
+ * 挂到请求上等构建,面板 15s 计时会先放弃而服务端仍在跑 —— 用户见失败、结论却是好的。
21740
+ * 无缓存且上次构建刚失败时按 retryBackoffMs 退避,免得每次读都点燃一轮 4 分钟构建;force 不受退避约束。 */
21741
+ getIndex(force = false) {
21742
+ const need = !this._cache || force || Date.now() - this._ts >= this.ttl;
21743
+ if (need && (force || Date.now() >= this._nextAllowAt)) this._requestBuild();
21744
+ return Promise.resolve(this._view());
21745
+ }
21746
+ /** 快照视图(无缓存也返回合法空索引,绝不为取数据而触网)。 */
21747
+ _view() {
21748
+ const c = this._cache;
21749
+ return {
21750
+ ok: true,
21751
+ indexedAt: c ? c.indexedAt : 0,
21752
+ sources: c ? c.sources : { npm: 0, github: 0, community: 0 },
21753
+ total: c ? c.total : 0,
21754
+ plugins: c ? c.plugins : [],
21755
+ building: !!this._inFlight,
21756
+ error: this._lastError
21757
+ };
21569
21758
  }
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() {
21759
+ /** 启动一次构建(无消费者 await)并登记为在飞引用;两条不变量:
21760
+ * (a) 无人等待的失败不得成为进程级 unhandledRejection —— 给 raw 挂 no-op handler「标记已处理」;
21761
+ * (b) 去重:已有在飞构建就直接复用,force 连点不得叠加并发构建(M-i)。
21762
+ * 失败原因记入 _lastError 并由快照如实上报 —— 取不到不能显示成「没有插件」。 */
21763
+ _requestBuild() {
21764
+ if (this._inFlight) return this._inFlight;
21575
21765
  const raw = this.buildIndex();
21576
21766
  raw.catch(() => {
21577
21767
  });
21578
21768
  this._inFlight = raw;
21579
- raw.then(() => {
21580
- }, () => {
21581
- }).then(() => {
21769
+ raw.then(
21770
+ () => {
21771
+ this._lastError = null;
21772
+ },
21773
+ (e) => {
21774
+ this._lastError = e && e.message || String(e);
21775
+ if (!this._cache) this._nextAllowAt = Date.now() + this.retryBackoffMs;
21776
+ this.logger.warn && this.logger.warn("market: \u7D22\u5F15\u6784\u5EFA\u5931\u8D25\uFF08\u6CBF\u7528\u65E7\u7F13\u5B58\uFF09: " + this._lastError);
21777
+ }
21778
+ ).then(() => {
21582
21779
  if (this._inFlight === raw) this._inFlight = null;
21583
21780
  });
21584
21781
  return raw;
21585
21782
  }
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
21783
  async buildIndex() {
21594
21784
  const start = Date.now();
21595
21785
  const bctx = { deadline: start + this.buildBudgetMs, truncated: /* @__PURE__ */ new Set() };
@@ -22753,22 +22943,57 @@ var require_updater = __commonJS({
22753
22943
  if (latest !== null) ctx._updCache[name] = { latest, at: Date.now() };
22754
22944
  return { latest, error };
22755
22945
  }
22756
- async function checkUpdates(ctx, force) {
22946
+ function checkUpdates(ctx, force) {
22947
+ if (!ctx._updSnapshot || force) _requestCheck(ctx, force);
22948
+ const s = ctx._updSnapshot;
22949
+ return Promise.resolve({
22950
+ ok: true,
22951
+ checkedAt: s ? s.checkedAt : 0,
22952
+ plugins: s ? s.plugins : [],
22953
+ refreshing: !!ctx._updInFlight,
22954
+ error: s ? s.error : null
22955
+ });
22956
+ }
22957
+ function _requestCheck(ctx, force) {
22958
+ if (ctx._updInFlight) return ctx._updInFlight;
22959
+ const raw = refreshUpdates(ctx, force);
22960
+ raw.catch(() => {
22961
+ });
22962
+ ctx._updInFlight = raw;
22963
+ raw.catch((e) => {
22964
+ const msg = e && e.message || String(e);
22965
+ if (ctx.logger && ctx.logger.warn) ctx.logger.warn("plugin check-updates \u5931\u8D25: " + msg);
22966
+ if (!ctx._updSnapshot) ctx._updSnapshot = { checkedAt: 0, plugins: [], error: msg };
22967
+ }).then(() => {
22968
+ if (ctx._updInFlight === raw) ctx._updInFlight = null;
22969
+ });
22970
+ return raw;
22971
+ }
22972
+ async function refreshUpdates(ctx, force) {
22757
22973
  const targets = [ctx._nativeTarget(), ...ctx._allSandboxTargets()];
22758
22974
  const meta = /* @__PURE__ */ new Map();
22759
22975
  for (const t of targets) {
22760
22976
  for (const p of ctx.installedOn(t)) {
22761
22977
  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 });
22978
+ meta.set(p.name, { specType: specType(p.source), latest: null, error: null });
22766
22979
  }
22767
22980
  }
22981
+ const npmNames = [...meta.entries()].filter(([, m]) => m.specType === "npm" && ctx.dist).map(([name]) => name);
22982
+ const errors = [];
22983
+ for (let i = 0; i < npmNames.length; i += 6) {
22984
+ const slice = npmNames.slice(i, i + 6);
22985
+ await Promise.all(slice.map(async (name) => {
22986
+ const r = await latestCached(ctx, name, force);
22987
+ const m = meta.get(name);
22988
+ m.latest = r.latest;
22989
+ m.error = r.error;
22990
+ if (r.error) errors.push(name + ": " + r.error);
22991
+ }));
22992
+ }
22768
22993
  const rows = [];
22769
22994
  for (const t of targets) {
22770
22995
  for (const p of ctx.installedOn(t)) {
22771
- const m = meta.get(p.name) || { specType: "npm", latest: null };
22996
+ const m = meta.get(p.name) || { specType: "npm", latest: null, error: null };
22772
22997
  const updateAvailable = m.specType === "npm" && isUpdateAvailable(m.latest, p.version);
22773
22998
  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
22999
  }
@@ -22777,13 +23002,15 @@ var require_updater = __commonJS({
22777
23002
  for (const row of rows) {
22778
23003
  let rec = byName.get(row.name);
22779
23004
  if (!rec) {
22780
- byName.set(row.name, { name: row.name, specType: row.specType, targets: [] });
23005
+ byName.set(row.name, { name: row.name, specType: row.specType, error: meta.get(row.name) ? meta.get(row.name).error : null, targets: [] });
22781
23006
  rec = byName.get(row.name);
22782
23007
  }
22783
23008
  rec.targets.push({ id: row.target, name: row.targetName, installed: row.installed, latest: row.latest, updateAvailable: row.updateAvailable });
22784
23009
  }
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 };
23010
+ 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));
23011
+ const snap = { checkedAt: Date.now(), plugins, error: errors.length ? errors.join("; ") : null };
23012
+ ctx._updSnapshot = snap;
23013
+ return snap;
22787
23014
  }
22788
23015
  async function update(ctx, name, targetStr) {
22789
23016
  if (!name) return { ok: false, error: "missing plugin name" };
@@ -22885,6 +23112,8 @@ var require_plugin = __commonJS({
22885
23112
  this.tasks = opts.tasks || null;
22886
23113
  this._updCache = {};
22887
23114
  this._updTTL = 6 * 3600 * 1e3;
23115
+ this._updSnapshot = null;
23116
+ this._updInFlight = null;
22888
23117
  this.jobs = createJobs({ tasks: this.tasks });
22889
23118
  this.layers = createLayers({ overlayFile: this.overlayFile, logger: this.logger });
22890
23119
  this.store = new store.PluginStore({ getInventory: () => this.inventory() });
@@ -25283,7 +25512,7 @@ var require_guard = __commonJS({
25283
25512
  var fs2 = require("node:fs");
25284
25513
  var path2 = require("node:path");
25285
25514
  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";
25515
+ 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
25516
  }
25288
25517
  function fetchDshChangelog(res, sup) {
25289
25518
  const v = sup && sup.nativeManager ? sup.nativeManager.versionInfo() : {};
@@ -25295,7 +25524,7 @@ var require_guard = __commonJS({
25295
25524
  return res.end(md);
25296
25525
  }
25297
25526
  function handle(ctx) {
25298
- const { sup, req, res, pathname, send, collectBody, originAllowed } = ctx;
25527
+ const { sup, req, res, pathname, identity, send, collectBody, originAllowed, browser } = ctx;
25299
25528
  if (req.method === "GET" && pathname === "/changelog") {
25300
25529
  return fetchDshChangelog(res, sup);
25301
25530
  }
@@ -25459,6 +25688,27 @@ var require_guard = __commonJS({
25459
25688
  if (req.method === "GET" && pathname === "/env/node-lts") {
25460
25689
  return sup.nodeLtsStatus().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
25461
25690
  }
25691
+ if (req.method === "POST" && pathname === "/env/open-url") {
25692
+ if (!identity.loopback) {
25693
+ req.resume();
25694
+ 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" });
25695
+ }
25696
+ if (!originAllowed(req, sup.config.apiPort)) {
25697
+ req.resume();
25698
+ return send(403, { ok: false, error: "cross-origin request rejected" });
25699
+ }
25700
+ collectBody(req, res, 4096, (body) => {
25701
+ let url = null;
25702
+ try {
25703
+ const j = body ? JSON.parse(body) : {};
25704
+ if (typeof j.url === "string") url = j.url;
25705
+ } catch {
25706
+ }
25707
+ if (!url) return send(400, { ok: false, error: '\u9700\u8981 {"url":"http(s)://\u2026"}' });
25708
+ 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 }));
25709
+ });
25710
+ return;
25711
+ }
25462
25712
  if (req.method === "GET" && pathname === "/ports") {
25463
25713
  return Promise.resolve(sup.listPorts()).then((r) => send(200, r)).catch((e) => send(500, { error: e && e.message }));
25464
25714
  }
@@ -25953,7 +26203,6 @@ var require_exchange = __commonJS({
25953
26203
  var require_instances = __commonJS({
25954
26204
  "src/api/domains/instances.js"(exports2, module2) {
25955
26205
  "use strict";
25956
- var platform = require_os();
25957
26206
  var execPath = require_exec_path();
25958
26207
  var crypto = require("node:crypto");
25959
26208
  var { bootstrapDshCookie } = require_exchange();
@@ -26055,17 +26304,18 @@ var require_instances = __commonJS({
26055
26304
  return sharedErr + "\uFF1B" + FORMS + "\uFF1B\u9700\u8981\u5176\u5B83\u53EF\u6267\u884C\u8BF7\u8D70\u63D2\u4EF6\u5B89\u88C5\u901A\u9053";
26056
26305
  }
26057
26306
  function handle(ctx) {
26058
- const { sup, req, res, pathname, identity, send, collectBody, originAllowed, tokOf } = ctx;
26307
+ const { sup, req, res, pathname, identity, send, collectBody, originAllowed, tokOf, browser } = ctx;
26059
26308
  function openInSystemBrowser(url) {
26060
- return platform.browser.open(url);
26309
+ return browser.openBrowser(url);
26061
26310
  }
26062
26311
  if (req.method === "GET" && pathname === "/open") return handleOpen(ctx);
26063
26312
  if (req.method === "GET" && pathname === "/instances") {
26064
26313
  const decorate = (it) => {
26065
26314
  const tok = tokOf(it.id);
26066
26315
  const out = Object.assign({}, it);
26067
- delete out.remoteToken;
26068
26316
  const loopback = identity.loopback;
26317
+ out.tokenSet = !!String(it.remoteToken || "").trim();
26318
+ if (!loopback) delete out.remoteToken;
26069
26319
  out.authUrl = tok && loopback ? "http://127.0.0.1:" + it.port + "/?token=" + encodeURIComponent(tok) : "http://127.0.0.1:" + it.port + "/";
26070
26320
  out.tokenPresent = loopback && !!tok;
26071
26321
  return out;
@@ -26121,9 +26371,13 @@ var require_instances = __commonJS({
26121
26371
  if (!(Number(it.port) > 0)) return send(400, { ok: false, error: "\u975E\u6CD5\u7AEF\u53E3" });
26122
26372
  const code = issueOpenWebCode(j.id);
26123
26373
  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 });
26374
+ return Promise.resolve(openInSystemBrowser(url)).then((r) => {
26375
+ if (!r.ok) dropOpenWebCode(code);
26376
+ return send(r.ok ? 200 : 500, r);
26377
+ }).catch((e) => {
26378
+ dropOpenWebCode(code);
26379
+ return send(500, { ok: false, reason: "spawn-failed", error: "\u6253\u5F00\u6D4F\u89C8\u5668\u5931\u8D25\uFF1A" + (e && e.message || e), url });
26380
+ });
26127
26381
  } catch (e) {
26128
26382
  return send(500, { ok: false, error: e.message });
26129
26383
  }
@@ -26568,6 +26822,7 @@ var require_server = __commonJS({
26568
26822
  var { serveStatic } = require_static();
26569
26823
  var { identify } = require_identity3();
26570
26824
  var { originAllowed, requestHasAccessKey, isShellOrigin } = require_security();
26825
+ var browserExit = require_browser();
26571
26826
  function safeFail(res, err, where) {
26572
26827
  try {
26573
26828
  const body = JSON.stringify({ ok: false, error: err && err.message || String(err) });
@@ -26585,7 +26840,8 @@ var require_server = __commonJS({
26585
26840
  } catch {
26586
26841
  }
26587
26842
  }
26588
- function createServer(sup) {
26843
+ function createServer(sup, deps) {
26844
+ const browser = deps && deps.browser || browserExit;
26589
26845
  return http2.createServer((req, res) => {
26590
26846
  const shellOrigin = (() => {
26591
26847
  const o = req.headers.origin;
@@ -26653,7 +26909,7 @@ var require_server = __commonJS({
26653
26909
  } catch (e) {
26654
26910
  return send(400, { error: "bad request encoding" });
26655
26911
  }
26656
- const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed, tokOf };
26912
+ const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed, tokOf, browser };
26657
26913
  for (const d of API_DOMAINS) {
26658
26914
  if (d.owns(pathname)) {
26659
26915
  try {