@lob-ox/core-darwin-x64 0.0.7 → 0.0.10

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.
package/core.cjs CHANGED
@@ -306,6 +306,78 @@ var require_exec_path = __commonJS({
306
306
  function dshJsIn(prefix) {
307
307
  return path2.join(prefix, "node_modules", ...DSH_PKG, "lib", "bin.js");
308
308
  }
309
+ function shimJsTarget(hit) {
310
+ if (!/\.(cmd|bat)$/i.test(hit)) return null;
311
+ let txt = null;
312
+ try {
313
+ txt = fs2.readFileSync(hit, "utf8");
314
+ } catch {
315
+ return null;
316
+ }
317
+ const re = /"([^"]+\.(?:js|cjs|mjs))"|(\S+\.(?:js|cjs|mjs))(?=\s|$)/gi;
318
+ let m;
319
+ while (m = re.exec(txt)) {
320
+ const cand = m[1] || m[2];
321
+ try {
322
+ const rp = fs2.realpathSync(cand);
323
+ if (fs2.statSync(rp).isFile()) return rp;
324
+ } catch {
325
+ }
326
+ }
327
+ return null;
328
+ }
329
+ function harnessHome(env) {
330
+ const e = env || process.env;
331
+ const h = e.HOME || e.USERPROFILE || os2.homedir();
332
+ return path2.join(h, ".dsh-app");
333
+ }
334
+ function dshJsFromLive(pl, env) {
335
+ const pidlookup = require_pidlookup();
336
+ const isFile = (p) => {
337
+ try {
338
+ return fs2.statSync(p).isFile();
339
+ } catch {
340
+ return false;
341
+ }
342
+ };
343
+ let port = null;
344
+ try {
345
+ const cfg = env && env.DSH_CONFIG && typeof env.DSH_CONFIG === "object" ? env.DSH_CONFIG : null;
346
+ if (cfg && Number.isInteger(cfg.targetPort) && cfg.targetPort > 0) port = cfg.targetPort;
347
+ else if (cfg && cfg.healthUrl) {
348
+ const u = new URL(cfg.healthUrl);
349
+ if (u.port) port = Number(u.port);
350
+ }
351
+ } catch {
352
+ }
353
+ if (!port) return null;
354
+ let pid = null;
355
+ try {
356
+ pid = pidlookup.findListeningPid(port);
357
+ } catch {
358
+ return null;
359
+ }
360
+ if (!pid) return null;
361
+ let cmd2 = null;
362
+ try {
363
+ cmd2 = pidlookup.readCmdline(pid);
364
+ } catch {
365
+ return null;
366
+ }
367
+ if (!cmd2) return null;
368
+ for (const part of cmd2.split(/\s+/).filter(Boolean)) {
369
+ try {
370
+ const rp = fs2.realpathSync(part);
371
+ if (isFile(rp) && /\.(js|cjs|mjs)$/i.test(rp)) {
372
+ const js = dshJsIn(path2.dirname(rp));
373
+ if (isFile(js)) return asJs(js, rp);
374
+ if (isFile(rp)) return asJs(rp, rp);
375
+ }
376
+ } catch {
377
+ }
378
+ }
379
+ return null;
380
+ }
309
381
  function resolveDsh(opts) {
310
382
  const o = opts || {};
311
383
  const pl = o.platform || process.platform;
@@ -317,35 +389,43 @@ var require_exec_path = __commonJS({
317
389
  return false;
318
390
  }
319
391
  };
320
- const asJs = (bin, launcher) => ({ runtime: process.execPath, bin, isJs: true, launcher: launcher || null });
392
+ const asJs2 = (bin, launcher) => ({ runtime: process.execPath, bin, isJs: true, launcher: launcher || null });
321
393
  if (env.DSH_BIN) {
322
394
  try {
323
395
  const r = fs2.realpathSync(env.DSH_BIN);
324
- if (isFile(r)) return asJs(r, env.DSH_BIN);
396
+ if (isFile(r)) return asJs2(r, env.DSH_BIN);
325
397
  } catch {
326
398
  }
327
399
  }
328
400
  const hit = resolveExecutable("dsh", { platform: pl, env });
329
401
  if (hit) {
402
+ const shim = shimJsTarget(hit);
403
+ if (shim) return asJs2(shim, hit);
330
404
  try {
331
405
  const real = fs2.realpathSync(hit);
332
- if (isFile(real) && /\.(js|cjs|mjs)$/i.test(real)) return asJs(real, hit);
406
+ if (isFile(real) && /\.(js|cjs|mjs)$/i.test(real)) return asJs2(real, hit);
333
407
  } catch {
334
408
  }
335
409
  const js = dshJsIn(path2.dirname(hit));
336
- if (isFile(js)) return asJs(js, hit);
337
- if (isFile(hit) && !/\.(cmd|bat|exe)$/i.test(hit)) return asJs(hit, hit);
410
+ if (isFile(js)) return asJs2(js, hit);
411
+ if (isFile(hit) && !/\.(cmd|bat|exe)$/i.test(hit)) return asJs2(hit, hit);
338
412
  return { runtime: null, bin: hit, isJs: false, launcher: hit };
339
413
  }
340
414
  if (o.npmRoot) {
341
415
  const js = dshJsIn(o.npmRoot);
342
- if (isFile(js)) return asJs(js, null);
416
+ if (isFile(js)) return asJs2(js, null);
343
417
  }
344
418
  const gRoot = globalInstallRoot(pl, void 0, env);
345
419
  {
346
420
  const js = dshJsIn(gRoot);
347
- if (isFile(js)) return asJs(js, null);
421
+ if (isFile(js)) return asJs2(js, null);
422
+ }
423
+ {
424
+ const js = dshJsIn(harnessHome(env));
425
+ if (isFile(js)) return asJs2(js, null);
348
426
  }
427
+ const live = dshJsFromLive(pl, env);
428
+ if (live) return live;
349
429
  return null;
350
430
  }
351
431
  function knownDshEntries(opts) {
@@ -918,8 +998,6 @@ var require_brand = __commonJS({
918
998
  var CLI_SHIM_NAMES = ["lobox", "lobox.cmd", "lobox.ps1"];
919
999
  var GUI_BIN_NAMES = ["lobox-shell", "lobox-shell.exe"];
920
1000
  var PROC_MATCH_GUARD = SHARED.proc.matchGuard;
921
- var PROC_MATCH_GUI = "lobox-shell";
922
- var PROC_MATCH_GUI_RE = "lobox-shell(\\.exe)?";
923
1001
  var BRIDGE_MSG_KERNEL_UPDATE_REQUEST = SHARED.bridge.msgKernelUpdateRequest;
924
1002
  var BRIDGE_MSG_KERNEL_UPDATE_RESULT = SHARED.bridge.msgKernelUpdateResult;
925
1003
  var BRIDGE_MSG_KERNEL_UPDATE_PROGRESS = SHARED.bridge.msgKernelUpdateProgress;
@@ -936,9 +1014,7 @@ var require_brand = __commonJS({
936
1014
  var EVENT_HARNESS_REMOTE_TOKEN_CHANGED = "harness_remote_token_changed";
937
1015
  var EVENT_LAN_HARNESS_TOKEN_UPDATED = "lan_harness_token_updated";
938
1016
  var EVENT_UPGRADE_STOPPING_HARNESS = "upgrade_stopping_harness";
939
- var EVENT_SHELL_UPDATE_PENDING = "shell_update_pending";
940
1017
  var EVENT_SHELL_UPDATE_CHECKED = "shell_update_checked";
941
- var EVENT_SHELL_RESTART_REQUESTED = "shell_restart_requested";
942
1018
  var SYSTEMD_TEMPLATE_ASIDE_SUFFIX = ".disabled-by-lobox-";
943
1019
  function corePackageName(tag) {
944
1020
  return NPM_SCOPE + "/" + CORE_PKG_PREFIX + tag;
@@ -1040,8 +1116,6 @@ var require_brand = __commonJS({
1040
1116
  CLI_SHIM_NAMES,
1041
1117
  GUI_BIN_NAMES,
1042
1118
  PROC_MATCH_GUARD,
1043
- PROC_MATCH_GUI,
1044
- PROC_MATCH_GUI_RE,
1045
1119
  BRIDGE_MSG_KERNEL_UPDATE_REQUEST,
1046
1120
  BRIDGE_MSG_KERNEL_UPDATE_RESULT,
1047
1121
  BRIDGE_MSG_KERNEL_UPDATE_PROGRESS,
@@ -1058,9 +1132,7 @@ var require_brand = __commonJS({
1058
1132
  EVENT_HARNESS_REMOTE_TOKEN_CHANGED,
1059
1133
  EVENT_LAN_HARNESS_TOKEN_UPDATED,
1060
1134
  EVENT_UPGRADE_STOPPING_HARNESS,
1061
- EVENT_SHELL_UPDATE_PENDING,
1062
1135
  EVENT_SHELL_UPDATE_CHECKED,
1063
- EVENT_SHELL_RESTART_REQUESTED,
1064
1136
  SYSTEMD_TEMPLATE_ASIDE_SUFFIX,
1065
1137
  corePackageName,
1066
1138
  stateRoot: stateRoot2,
@@ -1232,14 +1304,11 @@ var require_config = __commonJS({
1232
1304
  };
1233
1305
  }
1234
1306
  var BASE_DEFAULTS = {
1235
- // 守卫监控节拍(进程存活只由 child 的 exit/close 事件与平台 pidlookup 判定,见 app/main/*)。
1236
1307
  tickIntervalMs: 5e3,
1237
- // 启动窗口(秒):spawn 后 startsecs 内退出 ⇒ 记一次「启动失败」;活过 startsecs 后退出 ⇒ 正常重启,不计失败。
1238
1308
  startsecs: 10,
1239
- // 唯一一条限流:startupFailWindowMs 内「启动失败」达 startupFailBurst 次 ⇒ phase=FAILED,停止自动重启,等人工重试。
1240
- // 不变量:startupFailWindowMs > startupFailBurst × startsecs×1000(60s > 5×10s);同一规则按域参数化,实例域见 domains/instance/state-machine.js。
1241
1309
  startupFailWindowMs: 6e4,
1242
1310
  startupFailBurst: 5,
1311
+ restart: { windowMs: 6e4, burst: 5, baseDelayMs: 2e3, maxDelayMs: 6e4 },
1243
1312
  stopGraceMs: 1e4,
1244
1313
  portReleaseWaitMs: 1e4,
1245
1314
  apiHost: "127.0.0.1",
@@ -1267,7 +1336,6 @@ var require_config = __commonJS({
1267
1336
  initialCheckDelayMs: 2e4,
1268
1337
  upgradeTimeoutMs: 6e5,
1269
1338
  installCommandTemplate: ["npm", "install", "-g", "{pkg}@{version}"],
1270
- // apiAccessKey:非回环须带 Authorization: Bearer <key> 或 ?access_key=<key>,回环豁免;不配置则 LAN 受 RFC1918 约束、FRP 仍强制 remoteToken。
1271
1339
  apiAccessKey: null,
1272
1340
  closeAction: "hide",
1273
1341
  externalBrowser: null
@@ -1403,7 +1471,7 @@ var require_version = __commonJS({
1403
1471
  var fs2 = require("node:fs");
1404
1472
  var path2 = require("node:path");
1405
1473
  function guardVersion() {
1406
- if (true) return String("0.0.7");
1474
+ if (true) return String("0.0.10");
1407
1475
  try {
1408
1476
  return JSON.parse(fs2.readFileSync(path2.join(__dirname, "..", "..", "package.json"), "utf8")).version || "unknown";
1409
1477
  } catch {
@@ -1742,28 +1810,38 @@ var require_field_tables = __commonJS({
1742
1810
  var require_phase = __commonJS({
1743
1811
  "src/app/state/phase.js"(exports2, module2) {
1744
1812
  "use strict";
1813
+ var ENTRY_TO_LEGACY = {
1814
+ stopped: "STOPPED",
1815
+ installing: "INSTALLING",
1816
+ starting: "STARTING",
1817
+ running: "RUNNING",
1818
+ draining: "STOPPING",
1819
+ failed: "FAILED"
1820
+ };
1821
+ var LEGACY_TO_ENTRY = {
1822
+ STOPPED: "stopped",
1823
+ INSTALLING: "installing",
1824
+ STARTING: "starting",
1825
+ RUNNING: "running",
1826
+ STOPPING: "draining",
1827
+ FAILED: "failed",
1828
+ OBSERVED: "stopped",
1829
+ RESTARTING: "starting",
1830
+ BACKOFF: "failed"
1831
+ };
1832
+ function entryToLegacyPhase(ph) {
1833
+ return ENTRY_TO_LEGACY[ph] || "STOPPED";
1834
+ }
1745
1835
  function legacyToEntryPhase(ph) {
1746
- return {
1747
- STOPPED: "stopped",
1748
- STARTING: "starting",
1749
- RUNNING: "running",
1750
- FAILED: "failed",
1751
- OBSERVED: "stopped",
1752
- RESTARTING: "starting",
1753
- BACKOFF: "failed"
1754
- }[ph] || "stopped";
1836
+ return LEGACY_TO_ENTRY[ph] || "stopped";
1755
1837
  }
1756
- function entryToLegacyPhase(ph) {
1757
- return {
1758
- stopped: "STOPPED",
1759
- starting: "STARTING",
1760
- running: "RUNNING",
1761
- failed: "FAILED",
1762
- restarting: "STARTING",
1763
- backoff: "FAILED"
1764
- }[ph] || "STOPPED";
1838
+ function isKnownEntry(ph) {
1839
+ return Object.prototype.hasOwnProperty.call(ENTRY_TO_LEGACY, ph);
1765
1840
  }
1766
- module2.exports = { legacyToEntryPhase, entryToLegacyPhase };
1841
+ function isKnownLegacy(ph) {
1842
+ return Object.prototype.hasOwnProperty.call(LEGACY_TO_ENTRY, ph);
1843
+ }
1844
+ module2.exports = { entryToLegacyPhase, legacyToEntryPhase, isKnownEntry, isKnownLegacy };
1767
1845
  }
1768
1846
  });
1769
1847
 
@@ -2210,7 +2288,6 @@ var require_decide = __commonJS({
2210
2288
  return {
2211
2289
  phase,
2212
2290
  desired: d.state().desired(),
2213
- // 端口视角(不是存活判据):只喂孤儿接管/占用分支。
2214
2291
  portUp: d.portUp() === true,
2215
2292
  childAlive: childAlive(d.mChild()),
2216
2293
  adoptedAlive: adoptedAlive(d.mAdoptPid()),
@@ -2222,7 +2299,6 @@ var require_decide = __commonJS({
2222
2299
  manualRestart: d.manualRestart() === true,
2223
2300
  spawnBlocked: !!(d.mSpawnBlockedUntil() && now < d.mSpawnBlockedUntil()),
2224
2301
  startDeadlinePassed: startDeadlinePassed(startDeadline, now),
2225
- // STARTING 且没有存活进程/没记启动窗口 = 已判定重启,等端口释放落点。
2226
2302
  restartInFlight: phase === "STARTING" && startDeadline === null,
2227
2303
  restartDue: d.mRestartAt() === null || now >= d.mRestartAt(),
2228
2304
  crashHalted: d.crashHalted() === true,
@@ -4058,13 +4134,6 @@ var require_alloc = __commonJS({
4058
4134
  this._releaseAlloc();
4059
4135
  }
4060
4136
  }
4061
- /**
4062
- * Lease I1:把"检查"与"置位"放在**同一个同步块**内。
4063
- *
4064
- * 修前:`while (r._allocLock) { await sleep(10); } r._allocLock = true;`
4065
- * —— 检查与置位之间隔着 await,两个并发 claimSlot 可同时观察到 false 并都置 true(真 TOCTOU)。
4066
- * 现在用 Lease.acquireSync 做同步 test-and-set;未取得则由调用方退避重试。
4067
- */
4068
4137
  async _acquireAlloc() {
4069
4138
  const r = this._registry;
4070
4139
  let waited = 0;
@@ -4091,7 +4160,6 @@ var require_alloc = __commonJS({
4091
4160
  }
4092
4161
  }
4093
4162
  }
4094
- // 跨进程分配锁 best-effort:获取超时返回 null 并继续(fail-open 有界,登记后复检是第二道防线)。
4095
4163
  async _acquireXLock() {
4096
4164
  const f = String(this._registry._file || "") + ".alloc.lock";
4097
4165
  const deadline = Date.now() + XLOCK_TIMEOUT_MS;
@@ -4967,320 +5035,138 @@ var require_policies = __commonJS({
4967
5035
  }
4968
5036
  });
4969
5037
 
4970
- // src/domains/shell/core.js
4971
- var require_core3 = __commonJS({
4972
- "src/domains/shell/core.js"(exports2, module2) {
4973
- "use strict";
4974
- var BRAND2 = require_brand();
4975
- var DEFAULTS = {
4976
- enabled: true,
4977
- intervalMs: 2e4,
4978
- graceMs: 9e4,
4979
- updateGraceMs: 3e5,
4980
- maxRestarts: 5,
4981
- windowMs: 18e5,
4982
- phaseMaxAgeMs: 6e5,
4983
- // 壳进程判据的**唯一来源**是跨语言单源(brand.js#PROC_MATCH_GUI,与 brand.rs 同名同值):
4984
- // 此处再写一份字面量 = 改名漏一处 ⇒ pgrep 找不到壳(watchdog 反复拉起/双实例),或杀错进程。
4985
- procPattern: BRAND2.PROC_MATCH_GUI
4986
- };
4987
- var HEADLESS_FLAGS = Object.freeze([
4988
- "--shell-update-plan",
4989
- "--core-plan",
4990
- "--node-plan",
4991
- "--mirror-plan",
4992
- "--env-plan",
4993
- "--service-plan",
4994
- "--platform-matrix",
4995
- "--run-guard",
4996
- "--watchdog"
4997
- ]);
4998
- var HEADLESS_RE = new RegExp(HEADLESS_FLAGS.join("|"));
4999
- var SHELL_PROC_RE = new RegExp(BRAND2.PROC_MATCH_GUI_RE);
5000
- function isShellProcess(proc) {
5001
- const c = String(proc && proc.cmdline || "");
5002
- if (HEADLESS_RE.test(c)) return false;
5003
- return SHELL_PROC_RE.test(c);
5004
- }
5005
- function decide(i) {
5006
- const c = i.config || {};
5007
- if (i.alive > 0) return { action: "alive", reason: "\u58F3\u5728\u8FD0\u884C" };
5008
- if (i.absentForMs === null || i.absentForMs === void 0) {
5009
- return { action: "record", reason: "\u9996\u6B21\u89C2\u5BDF\u5230\u58F3\u7F3A\u5931\uFF0C\u5F00\u59CB\u8BA1\u65F6" };
5010
- }
5011
- const needMs = i.expectedAbsence ? c.updateGraceMs || DEFAULTS.updateGraceMs : c.graceMs || DEFAULTS.graceMs;
5012
- if (i.absentForMs < needMs) {
5013
- return { action: "wait", reason: i.expectedAbsence ? "\u58F3\u5904\u4E8E\u9884\u671F\u7F3A\u5E2D\uFF08\u66F4\u65B0/\u91CD\u542F\uFF09" : "\u672A\u8FBE\u5BBD\u9650\u671F", needMs };
5014
- }
5015
- if (!i.sessionAvailable) {
5016
- return { action: "skip", reason: "\u65E0\u56FE\u5F62\u4F1A\u8BDD\uFF08\u6CE8\u9500/\u7EAF\u7EC8\u7AEF\uFF09\uFF0C\u62C9\u8D77 GUI \u5FC5\u5931\u8D25" };
5017
- }
5018
- if ((i.restartsInWindow || 0) >= (c.maxRestarts || DEFAULTS.maxRestarts)) {
5019
- return { action: "skip", reason: "\u7A97\u53E3\u5185\u62C9\u8D77\u6B21\u6570\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u505C\u6B62\u91CD\u8BD5\uFF08\u9632\u98CE\u66B4\uFF09" };
5020
- }
5021
- if (!i.hasExe) {
5022
- return { action: "skip", reason: "\u65E0\u6CD5\u5B9A\u4F4D\u58F3\u53EF\u6267\u884C\u6587\u4EF6\uFF08identity.json \u672A\u8BB0\u5F55 exe\uFF09" };
5023
- }
5024
- return { action: "restart", reason: "\u58F3\u7F3A\u5931\u4E14\u5DF2\u8FC7\u5BBD\u9650\u671F", needMs };
5025
- }
5026
- function isUpdatePhase(phase) {
5027
- const p = String(phase || "");
5028
- return p === "restarting" || p.indexOf("shell-update") === 0;
5029
- }
5030
- function exeFromCmdline(cmdline) {
5031
- const s = String(cmdline || "").trim();
5032
- if (!s) return null;
5033
- if (s.startsWith('"')) {
5034
- const end = s.indexOf('"', 1);
5035
- return end > 1 ? s.slice(1, end) : null;
5036
- }
5037
- return s.split(/\s+/)[0] || null;
5038
- }
5039
- function deriveState(id, journal) {
5040
- const j = journal || {};
5041
- if (!j.to) return { state: "idle", reason: "\u65E0\u8FDB\u884C\u4E2D\u7684\u66F4\u65B0", journal: j, identity: id };
5042
- const cur = id && id.version ? String(id.version) : null;
5043
- if (cur && cur === j.to && id && id.phase === "ready") {
5044
- const next = j.confirmed === true ? j : Object.assign({}, j, { confirmed: true });
5045
- return { state: "confirmed", version: cur, reason: "\u58F3\u5DF2\u5065\u5EB7\u8FD0\u884C\u65B0\u7248\u672C", journal: next, identity: id };
5046
- }
5047
- return {
5048
- state: "pending",
5049
- target: j.to,
5050
- current: cur,
5051
- reason: cur === j.to ? "\u7B49\u5F85\u58F3\u4E0A\u62A5\u5C31\u7EEA" : "\u7B49\u5F85\u58F3\u91CD\u542F\u5230\u65B0\u7248\u672C",
5052
- journal: j,
5053
- identity: id
5054
- };
5055
- }
5056
- module2.exports = { DEFAULTS, isShellProcess, decide, isUpdatePhase, exeFromCmdline, deriveState, HEADLESS_FLAGS };
5057
- }
5058
- });
5059
-
5060
- // src/domains/shell/watchdog.js
5061
- var require_watchdog = __commonJS({
5062
- "src/domains/shell/watchdog.js"(exports2, module2) {
5038
+ // src/app/control/synchrony.js
5039
+ var require_synchrony = __commonJS({
5040
+ "src/app/control/synchrony.js"(exports2, module2) {
5063
5041
  "use strict";
5064
- var { DEFAULTS, decide, isShellProcess, isUpdatePhase } = require_core3();
5065
- function createShellWatchdog(deps) {
5066
- const o = deps || {};
5067
- const shell = o.shell;
5068
- const pidlookup = o.pidlookup;
5069
- const desktop = o.desktop;
5070
- const logger = o.logger || console;
5071
- const events = o.events || null;
5072
- const config = o.config || {};
5073
- const now = o.now || (() => Date.now());
5074
- const procPattern = config.shellProcPattern || DEFAULTS.procPattern;
5075
- let missingSince = null;
5076
- let restarts = [];
5077
- let busy = false;
5078
- let lastSkipReason = null;
5079
- let everSawAlive = false;
5080
- let expectedSince = null;
5081
- let phaseStale = false;
5082
- let phaseStaleWarned = false;
5083
- let journalStale = false;
5084
- let journalStaleWarned = false;
5085
- const log = (m) => {
5086
- try {
5087
- logger.info && logger.info("[shell-watchdog] " + m);
5088
- } catch {
5089
- }
5090
- };
5091
- const warn = (m) => {
5092
- try {
5093
- logger.warn && logger.warn("[shell-watchdog] " + m);
5094
- } catch {
5095
- }
5096
- };
5097
- function shellProcs() {
5098
- let procs = [];
5099
- try {
5100
- procs = pidlookup.pgrepList(procPattern) || [];
5101
- } catch {
5102
- procs = [];
5103
- }
5104
- return procs.filter(isShellProcess);
5042
+ var BeatScheduler = class {
5043
+ constructor(opts) {
5044
+ opts = opts || {};
5045
+ this.logger = opts.logger || null;
5046
+ this._beats = /* @__PURE__ */ new Map();
5047
+ this._timer = null;
5048
+ this._intervalMs = opts.intervalMs || 5e3;
5049
+ this._seq = 0;
5050
+ this._ticking = false;
5051
+ this._stopping = false;
5052
+ this.onBeatDone = null;
5105
5053
  }
5106
- function updatePhaseTracking(t) {
5107
- let phase = "";
5108
- try {
5109
- const id = shell.identity();
5110
- phase = String(id && id.phase || "");
5111
- } catch {
5112
- }
5113
- const inUpdate = isUpdatePhase(phase);
5114
- if (!inUpdate) {
5115
- expectedSince = null;
5116
- phaseStale = false;
5117
- phaseStaleWarned = false;
5118
- return;
5119
- }
5120
- if (expectedSince === null) expectedSince = t;
5121
- const maxAge = config.shellWatchdogPhaseMaxAgeMs || DEFAULTS.phaseMaxAgeMs;
5122
- phaseStale = t - expectedSince >= maxAge;
5123
- if (phaseStale && !phaseStaleWarned) {
5124
- phaseStaleWarned = true;
5125
- warn("identity.phase \u505C\u7559\u8FC7\u4E45\uFF08" + Math.round((t - expectedSince) / 1e3) + "s > " + Math.round(maxAge / 1e3) + "s\uFF09\uFF0C\u5224\u5B9A\u4E3A\u9648\u65E7\uFF1B\u4E0D\u518D\u5EF6\u957F\u5BBD\u9650");
5126
- }
5054
+ register(name, fn, opts) {
5055
+ if (typeof fn !== "function") throw new Error("beat " + name + " \u9700\u8981 fn");
5056
+ opts = opts || {};
5057
+ const every = opts.every === Infinity ? Infinity : Math.max(1, Number(opts.every) || 1);
5058
+ const firstDelayMs = typeof opts.firstDelayMs === "number" && opts.firstDelayMs >= 0 ? opts.firstDelayMs : null;
5059
+ const firstTicks = firstDelayMs !== null ? Math.max(0, Math.ceil(firstDelayMs / this._intervalMs)) : 0;
5060
+ this._beats.set(name, {
5061
+ fn,
5062
+ every,
5063
+ firstTicks,
5064
+ timeoutMs: typeof opts.timeoutMs === "number" && opts.timeoutMs > 0 ? opts.timeoutMs : 0,
5065
+ _seqSeen: 0,
5066
+ _lastSeq: null,
5067
+ _fired: false
5068
+ });
5069
+ return this;
5127
5070
  }
5128
- function updateJournalTracking(t) {
5129
- let j = null;
5130
- try {
5131
- j = shell.readJournal && shell.readJournal();
5132
- } catch {
5133
- }
5134
- if (!j || !j.to || j.confirmed) {
5135
- journalStale = false;
5136
- journalStaleWarned = false;
5137
- return;
5138
- }
5139
- const t0 = Date.parse(String(j.startedAt || ""));
5140
- if (!Number.isFinite(t0)) {
5141
- journalStale = false;
5142
- return;
5143
- }
5144
- const maxAge = config.shellWatchdogPhaseMaxAgeMs || DEFAULTS.phaseMaxAgeMs;
5145
- journalStale = t - t0 >= maxAge;
5146
- if (journalStale && !journalStaleWarned) {
5147
- journalStaleWarned = true;
5148
- warn("\u66F4\u65B0\u8D26\u672C\u672A\u786E\u8BA4\u5DF2\u8D85 " + Math.round((t - t0) / 1e3) + "s\uFF08> " + Math.round(maxAge / 1e3) + "s\uFF09\uFF0C\u5224\u5B9A\u4E3A\u9648\u65E7\uFF1B\u4E0D\u518D\u636E\u6B64\u5EF6\u957F\u5BBD\u9650");
5149
- }
5071
+ unregister(name) {
5072
+ this._beats.delete(name);
5073
+ return this;
5150
5074
  }
5151
- function expectedAbsence() {
5152
- let phase = "";
5153
- try {
5154
- const id = shell.identity();
5155
- phase = String(id && id.phase || "");
5156
- } catch {
5157
- }
5158
- const inUpdate = isUpdatePhase(phase);
5159
- if (inUpdate && !phaseStale) return true;
5160
- try {
5161
- const j = shell.readJournal && shell.readJournal();
5162
- if (j && j.to && !j.confirmed) return !journalStale;
5163
- } catch {
5164
- }
5165
- return false;
5075
+ has(name) {
5076
+ return this._beats.has(name);
5166
5077
  }
5167
- function exePath() {
5168
- if (config.shellExePath) return config.shellExePath;
5078
+ async withLock(fn) {
5079
+ if (this._ticking || this._stopping) return { __skipped: "busy" };
5080
+ this._ticking = true;
5169
5081
  try {
5170
- const id = shell.identity();
5171
- return id && id.exe || null;
5172
- } catch {
5173
- return null;
5082
+ return await fn();
5083
+ } finally {
5084
+ this._ticking = false;
5174
5085
  }
5175
5086
  }
5176
- async function tick() {
5177
- if (config.shellWatchdog === false) return { skipped: "disabled" };
5178
- if (busy) return { skipped: "busy" };
5179
- busy = true;
5087
+ async tick() {
5088
+ if (this._ticking || this._stopping) return { skipped: true, done: [], errors: [] };
5089
+ this._ticking = true;
5090
+ const done = [];
5091
+ const errors = [];
5092
+ const iv = this._intervalMs;
5180
5093
  try {
5181
- const t = now();
5182
- const procs = shellProcs();
5183
- const alive = procs.length;
5184
- if (alive > 0 && typeof o.onShellAlive === "function") {
5094
+ this._seq++;
5095
+ for (const [name, b] of this._beats) {
5096
+ b._seqSeen++;
5097
+ if (b.every === Infinity) {
5098
+ if (b._fired || b._seqSeen <= b.firstTicks) continue;
5099
+ b._fired = true;
5100
+ } else {
5101
+ if (b._seqSeen <= b.firstTicks) continue;
5102
+ if (b._lastSeq !== null && this._seq - b._lastSeq < b.every) continue;
5103
+ b._lastSeq = this._seq;
5104
+ }
5105
+ let p;
5185
5106
  try {
5186
- o.onShellAlive();
5187
- } catch {
5107
+ p = Promise.resolve(b.fn());
5108
+ } catch (e) {
5109
+ errors.push(name + ":" + (e && e.message));
5110
+ continue;
5188
5111
  }
5189
- }
5190
- if (typeof o.halted === "function" && o.halted()) {
5191
- lastSkipReason = "\u4F1A\u8BDD\u9000\u51FA\u4E2D/\u7528\u6237\u5DF2\u9000\u51FA\uFF08\u4E0D\u62C9\u8D77\uFF09";
5192
- return { skipped: "halted", reason: lastSkipReason };
5193
- }
5194
- if (alive > 0 && !everSawAlive) {
5195
- everSawAlive = true;
5196
- log("\u5DF2\u89C2\u6D4B\u5230\u684C\u9762\u58F3\u5728\u8FD0\u884C\uFF08pid=" + procs[0].pid + "\uFF09");
5197
- }
5198
- const absentForMs = alive > 0 ? null : missingSince === null ? null : t - missingSince;
5199
- updatePhaseTracking(t);
5200
- updateJournalTracking(t);
5201
- const expected = absentForMs === null ? false : expectedAbsence();
5202
- const exe = exePath();
5203
- restarts = restarts.filter((x) => t - x < (config.shellWatchdogWindowMs || DEFAULTS.windowMs));
5204
- const d = decide({
5205
- alive,
5206
- absentForMs,
5207
- expectedAbsence: expected,
5208
- sessionAvailable: desktop.sessionAvailable(),
5209
- restartsInWindow: restarts.length,
5210
- hasExe: !!exe,
5211
- config: {
5212
- graceMs: config.shellWatchdogGraceMs || DEFAULTS.graceMs,
5213
- updateGraceMs: config.shellWatchdogUpdateGraceMs || DEFAULTS.updateGraceMs,
5214
- maxRestarts: config.shellWatchdogMaxRestarts || DEFAULTS.maxRestarts
5112
+ if (b.timeoutMs && b.timeoutMs > 0) {
5113
+ p = Promise.race([p, new Promise((res) => {
5114
+ const t = setTimeout(() => res({ __beatTimeout: true }), b.timeoutMs);
5115
+ if (t.unref) t.unref();
5116
+ })]);
5215
5117
  }
5216
- });
5217
- if (d.action === "alive") {
5218
- missingSince = null;
5219
- lastSkipReason = null;
5220
- return { alive };
5221
- }
5222
- if (d.action === "record") {
5223
- missingSince = t;
5224
- log("\u684C\u9762\u58F3\u7F3A\u5931\uFF0C\u5F00\u59CB\u8BA1\u65F6\uFF08\u5BBD\u9650 " + Math.round((config.shellWatchdogGraceMs || DEFAULTS.graceMs) / 1e3) + "s\uFF09");
5225
- return { absent: true };
5226
- }
5227
- if (d.action === "wait") {
5228
- lastSkipReason = d.reason + "\uFF08" + Math.round(absentForMs / 1e3) + "s/" + Math.round(d.needMs / 1e3) + "s\uFF09";
5229
- return { waiting: d.reason };
5230
- }
5231
- if (d.action === "skip") {
5232
- if (lastSkipReason !== d.reason) {
5233
- lastSkipReason = d.reason;
5234
- warn("\u4E0D\u62C9\u8D77\u684C\u9762\u58F3\uFF1A" + d.reason);
5118
+ try {
5119
+ const r = await p;
5120
+ if (r && r.__beatTimeout) {
5121
+ errors.push(name + ":timeout");
5122
+ if (this.logger && this.logger.warn) this.logger.warn("[beat] " + name + " \u8D85\u8FC7 " + b.timeoutMs + "ms\uFF0C\u5DF2\u8DF3\u8FC7\u672C\u62CD\uFF08\u9632\u505C\u6446\uFF09");
5123
+ } else {
5124
+ done.push(name);
5125
+ }
5126
+ } catch (e) {
5127
+ errors.push(name + ":" + (e && e.message));
5128
+ if (this.logger && this.logger.warn) this.logger.warn("[beat] " + name + ": " + (e && e.message));
5235
5129
  }
5236
- return { skipped: d.reason };
5237
- }
5238
- restarts.push(t);
5239
- const r = await shell.restartShell({
5240
- exePath: exe,
5241
- procPattern,
5242
- shouldAbort: typeof o.halted === "function" ? () => o.halted() : void 0
5243
- });
5244
- if (r && r.ok) {
5245
- if (events) events.append("shell_watchdog_restart", { pid: r.pid, exe: r.exe, absentMs: absentForMs });
5246
- log("\u684C\u9762\u58F3\u7F3A\u5931 " + Math.round(absentForMs / 1e3) + "s\uFF0C\u5DF2\u62C9\u8D77 pid=" + r.pid + " exe=" + r.exe);
5247
- missingSince = null;
5248
- } else {
5249
- if (events) events.append("shell_watchdog_restart_failed", { error: r && r.error || "\u672A\u77E5", absentMs: absentForMs });
5250
- warn("\u62C9\u8D77\u684C\u9762\u58F3\u5931\u8D25\uFF1A" + (r && r.error || "\u672A\u77E5"));
5251
5130
  }
5252
- return { restarted: !!(r && r.ok), error: r && r.error || null };
5253
- } catch (e) {
5254
- warn("\u76D1\u63A7\u5F02\u5E38\uFF1A" + (e && e.message || e));
5255
- return { error: e && e.message || String(e) };
5256
5131
  } finally {
5257
- busy = false;
5132
+ this._ticking = false;
5133
+ }
5134
+ if (this.onBeatDone) {
5135
+ try {
5136
+ await this.onBeatDone({ done, errors });
5137
+ } catch (e) {
5138
+ if (this.logger && this.logger.warn) this.logger.warn("[beat] onBeatDone: " + (e && e.message));
5139
+ }
5258
5140
  }
5141
+ return { done, errors };
5259
5142
  }
5260
- function status() {
5261
- const t = now();
5262
- let session = { available: null, reason: null };
5263
- try {
5264
- session = desktop.describe();
5265
- } catch {
5143
+ start(intervalMs) {
5144
+ this._intervalMs = intervalMs || this._intervalMs;
5145
+ this._stopping = false;
5146
+ if (this._timer) clearInterval(this._timer);
5147
+ const self = this;
5148
+ this._timer = setInterval(() => {
5149
+ self.tick();
5150
+ }, this._intervalMs);
5151
+ if (this._timer && typeof this._timer.unref === "function") this._timer.unref();
5152
+ return this;
5153
+ }
5154
+ stop() {
5155
+ this._stopping = true;
5156
+ if (this._timer) {
5157
+ clearInterval(this._timer);
5158
+ this._timer = null;
5266
5159
  }
5267
- return {
5268
- enabled: config.shellWatchdog !== false,
5269
- intervalMs: config.shellWatchdogIntervalMs || DEFAULTS.intervalMs,
5270
- graceMs: config.shellWatchdogGraceMs || DEFAULTS.graceMs,
5271
- updateGraceMs: config.shellWatchdogUpdateGraceMs || DEFAULTS.updateGraceMs,
5272
- maxRestarts: config.shellWatchdogMaxRestarts || DEFAULTS.maxRestarts,
5273
- absentForMs: missingSince === null ? null : t - missingSince,
5274
- restartsInWindow: restarts.filter((x) => t - x < (config.shellWatchdogWindowMs || DEFAULTS.windowMs)).length,
5275
- everSawAlive,
5276
- lastSkipReason,
5277
- session,
5278
- expectedAbsence: expectedAbsence()
5279
- };
5280
5160
  }
5281
- return { tick, status, intervalMs: config.shellWatchdogIntervalMs || DEFAULTS.intervalMs };
5282
- }
5283
- module2.exports = { createShellWatchdog };
5161
+ resume() {
5162
+ this._stopping = false;
5163
+ }
5164
+ clear() {
5165
+ this._beats.clear();
5166
+ this.stop();
5167
+ }
5168
+ };
5169
+ module2.exports = { BeatScheduler };
5284
5170
  }
5285
5171
  });
5286
5172
 
@@ -5385,7 +5271,6 @@ var require_capability_profile = __commonJS({
5385
5271
  "use strict";
5386
5272
  var linux = {
5387
5273
  sandboxLaunch: true,
5388
- // 服务管理器一律走产品自身的进程管理(不借 systemd)⇒ 无 cgroup 强制,如实为 supervise。
5389
5274
  sandboxEnforcement: "supervise",
5390
5275
  pidAdoption: true,
5391
5276
  processTreeKill: true,
@@ -7012,7 +6897,7 @@ var require_portable = __commonJS({
7012
6897
  if (!pid || !Array.isArray(anchors) || !anchors.length) return false;
7013
6898
  const cmd2 = pidlookup.readCmdline(pid);
7014
6899
  if (!cmd2) return false;
7015
- return anchors.some((a) => a && cmd2.indexOf(String(a)) !== -1);
6900
+ return anchors.every((a) => a && cmd2.indexOf(String(a)) !== -1);
7016
6901
  }
7017
6902
  var portOf = (o) => {
7018
6903
  const p = Number(o && o.port);
@@ -7055,15 +6940,6 @@ var require_portable = __commonJS({
7055
6940
  kind: "portable",
7056
6941
  supportsUnits: false,
7057
6942
  supportsTransient: true,
7058
- /**
7059
- * 能力协商(根因 B 修法;与 router 域 `supports(cap)` 同一范式,下沉到平台层)。
7060
- *
7061
- * 判据来源:capability-profile.js 的 sandboxEnforcement —— 三平台均为 'supervise'
7062
- * (= 产品自身监控,**无 cgroup 级强制**)。此前该字段全仓 0 处查询,
7063
- * 于是 governor 在"限额根本无法生效"的前提下仍判违规并重启 ⇒ 永动机(P0)。
7064
- *
7065
- * 调用方(governor)必须先问后处置:不支持限额 ⇒ 只观测,绝不重启。
7066
- */
7067
6943
  supports(cap) {
7068
6944
  if (cap === "treeKill") return true;
7069
6945
  if (cap === "limits") return false;
@@ -7105,11 +6981,6 @@ var require_portable = __commonJS({
7105
6981
  }
7106
6982
  return true;
7107
6983
  },
7108
- // 审计 P0-I5:原实现在 Windows 上用 Atomics.wait(nap)**同步冻结事件循环**做轮询、且 SIGKILL 兜底分支
7109
- // 调用的 taskkill 完全异步不 await ⇒ 停止"无法确认"(常返回 false),进而实例仍被标 RUNNING、
7110
- // upgrade.js 在上个进程仍占端口时继续重装 ⇒ 与端口预检撞车回滚(正是 upgrade.js 想防的故障)。
7111
- // 修复:改为 async,用 real-timer 轮询(不阻塞事件循环),并等待 taskkill 真正结束(有界 10s,与 process.js killTree 同),
7112
- // 给足停止窗口再判定;仍超时再报 false(如实,不再假成功)。timeoutMs 仍被 STOP_WAIT_CAP_MS 截断(与调用方意图无关)。
7113
6984
  async stopUnit(unit, o) {
7114
6985
  const opts = o || {};
7115
6986
  const ours = findOurs(opts);
@@ -7216,19 +7087,6 @@ var require_portable = __commonJS({
7216
7087
  }
7217
7088
  return false;
7218
7089
  },
7219
- /**
7220
- * 单元是否活跃 —— 返回 Outcome 三态(根因 A 修法)。
7221
- *
7222
- * 此前返回 null 表示"未知",而调用方 instance/ops.js:67 用 `active !== false` 判,
7223
- * 于是 null(未知)被当成"仍活跃" ⇒ removeInstance 把未知报成删除成功(P0)。
7224
- *
7225
- * 关键修正:**无 anchors 时不得把"有人在监听"当成"我们的实例在跑"**。
7226
- * 端口是共享事实、不是身份(与 monitor.js#matchesAnchors 同一条判据);
7227
- * 缺锚点即无从验证身份 ⇒ 如实返回 unknown,由调用方决定(升级校验须失败并回滚)。
7228
- *
7229
- * 兼容:既有调用方若直接判真假,Outcome 对象恒为真值,行为同"true",不会静默反转;
7230
- * 但显式判据(isOk/isFail/isUnknown)才能区分三态——见 shared/outcome.js。
7231
- */
7232
7090
  isUnitActive(unit, o) {
7233
7091
  const opts = o || {};
7234
7092
  if (findOurs(opts)) return OUTCOME.OK;
@@ -7252,7 +7110,6 @@ var require_portable = __commonJS({
7252
7110
  transientUnitFile() {
7253
7111
  return null;
7254
7112
  },
7255
- // 审计 P0-I5:随 stopUnit 改为 async,cleanTransient 也须 await(不得再用 === false 同步判)。
7256
7113
  async cleanTransient(unit, o) {
7257
7114
  const opts = o || {};
7258
7115
  const errors = [];
@@ -7291,7 +7148,6 @@ var require_service = __commonJS({
7291
7148
  kind: UNSUPPORTED_KIND,
7292
7149
  supportsUnits: false,
7293
7150
  supportsTransient: false,
7294
- // 能力协商:与 portable 同类,未知平台一律不支持(保持方法集一致)。
7295
7151
  supports() {
7296
7152
  return false;
7297
7153
  },
@@ -8215,6 +8071,122 @@ var require_os = __commonJS({
8215
8071
  }
8216
8072
  });
8217
8073
 
8074
+ // src/platform/contract/pm-client.js
8075
+ var require_pm_client = __commonJS({
8076
+ "src/platform/contract/pm-client.js"(exports2, module2) {
8077
+ "use strict";
8078
+ var fs2 = require("node:fs");
8079
+ var path2 = require("node:path");
8080
+ var http2 = require("node:http");
8081
+ var stateRoot2 = require_state_root();
8082
+ var pidlookup = require_pidlookup();
8083
+ var FILE_NAME = "guard-mgmt.json";
8084
+ var SUPPORTED_SCHEMA = 1;
8085
+ var HOST = "127.0.0.1";
8086
+ var TIMEOUT_MS = 1500;
8087
+ var unavailableHook = null;
8088
+ function setUnavailableHook(fn) {
8089
+ unavailableHook = typeof fn === "function" ? fn : null;
8090
+ }
8091
+ function file() {
8092
+ return path2.join(stateRoot2.supervisorDir(), FILE_NAME);
8093
+ }
8094
+ function pidAlive2(pid) {
8095
+ if (!Number.isInteger(pid) || pid <= 0) return false;
8096
+ return pidlookup.probeAlive(pid) === "alive";
8097
+ }
8098
+ function endpoint() {
8099
+ let text;
8100
+ try {
8101
+ text = fs2.readFileSync(file(), "utf8");
8102
+ } catch {
8103
+ return null;
8104
+ }
8105
+ let j;
8106
+ try {
8107
+ j = JSON.parse(text);
8108
+ } catch {
8109
+ return null;
8110
+ }
8111
+ if (!j || typeof j !== "object" || j.schema !== SUPPORTED_SCHEMA) return null;
8112
+ const port = Number(j.port);
8113
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) return null;
8114
+ if (!pidAlive2(Number(j.pid))) return null;
8115
+ return { port, host: typeof j.host === "string" && j.host ? j.host : HOST };
8116
+ }
8117
+ function post(route, payload) {
8118
+ const ep = endpoint();
8119
+ if (!ep) {
8120
+ if (unavailableHook) {
8121
+ try {
8122
+ unavailableHook(route);
8123
+ } catch {
8124
+ }
8125
+ }
8126
+ return false;
8127
+ }
8128
+ let body;
8129
+ try {
8130
+ body = JSON.stringify(payload || {});
8131
+ } catch {
8132
+ return false;
8133
+ }
8134
+ const req = http2.request(
8135
+ {
8136
+ host: ep.host,
8137
+ port: ep.port,
8138
+ path: route,
8139
+ method: "POST",
8140
+ headers: { "content-type": "application/json", "content-length": Buffer.byteLength(body) },
8141
+ timeout: TIMEOUT_MS
8142
+ },
8143
+ (res) => {
8144
+ res.resume();
8145
+ }
8146
+ );
8147
+ req.on("timeout", () => {
8148
+ try {
8149
+ req.destroy();
8150
+ } catch {
8151
+ }
8152
+ });
8153
+ req.on("error", () => {
8154
+ });
8155
+ try {
8156
+ req.end(body);
8157
+ return true;
8158
+ } catch {
8159
+ return false;
8160
+ }
8161
+ }
8162
+ module2.exports = {
8163
+ FILE_NAME,
8164
+ SUPPORTED_SCHEMA,
8165
+ file,
8166
+ endpoint,
8167
+ setUnavailableHook,
8168
+ register(desc) {
8169
+ return post("/pm/register", desc);
8170
+ },
8171
+ setDesired(id, desired) {
8172
+ return post("/pm/set-desired", { id, desired });
8173
+ },
8174
+ onPhase(id, phase) {
8175
+ return post("/pm/on-phase", { id, phase });
8176
+ },
8177
+ recordExit(id, code, signal, startupFailure) {
8178
+ return post("/pm/record-exit", { id, code, signal, startup_failure: startupFailure === true });
8179
+ },
8180
+ requestRestart(id) {
8181
+ return post("/pm/request-restart", { id });
8182
+ },
8183
+ resetBackoff(id) {
8184
+ return post("/pm/reset-backoff", { id });
8185
+ }
8186
+ };
8187
+ }
8188
+ });
8189
+
8218
8190
  // src/app/assembly/bootstrap.js
8219
8191
  var require_bootstrap = __commonJS({
8220
8192
  "src/app/assembly/bootstrap.js"(exports2, module2) {
@@ -8225,11 +8197,20 @@ var require_bootstrap = __commonJS({
8225
8197
  var path2 = require("node:path");
8226
8198
  var fs2 = require("node:fs");
8227
8199
  var ports = require_ports().shared;
8228
- var { createShellWatchdog } = require_watchdog();
8229
8200
  var pidlook = require_pidlookup();
8201
+ var { BeatScheduler } = require_synchrony();
8230
8202
  var platform = require_os();
8231
8203
  var ENV_FORM_STARTUP_DELAY_MS = 3e3;
8232
8204
  function _bootstrap(host) {
8205
+ try {
8206
+ require_pm_client().setUnavailableHook((route) => {
8207
+ try {
8208
+ host.events.append("pm_endpoint_unavailable", { route });
8209
+ } catch {
8210
+ }
8211
+ });
8212
+ } catch {
8213
+ }
8233
8214
  try {
8234
8215
  host.events.append("guard_started", {
8235
8216
  pid: process.pid,
@@ -8250,40 +8231,56 @@ var require_bootstrap = __commonJS({
8250
8231
  routerAutostart: host.config.routerAutostart === true,
8251
8232
  routerMode: null,
8252
8233
  updateCheck: null,
8253
- shellWatchdog: null,
8254
8234
  lastRefresh: null
8255
8235
  };
8256
- host.tick();
8257
- host._timer = host.managedObjects ? null : setInterval(() => host.tick(), host.config.tickIntervalMs);
8258
- host._lastHeartbeatAt = Date.now();
8259
- host._heartbeatStalls = 0;
8260
- host._heartbeatBeat = host._heartbeatBeat || 0;
8261
- const heartbeatIv = host.config.tickIntervalMs || 5e3;
8262
- host._heartbeatTimer = setInterval(() => {
8263
- if (host._heartbeatBusy) return;
8264
- host._heartbeatBusy = true;
8265
- const iv = heartbeatIv;
8266
- const beat = ++host._heartbeatBeat;
8267
- host._lastHeartbeatAt = Date.now();
8268
- const objCount = host.managedObjects && typeof host.managedObjects.count === "function" ? host.managedObjects.count() : 1;
8269
- const stallMs = Math.max(3e4, iv * 12, objCount * 6 * iv + iv);
8270
- const guard = setTimeout(() => {
8271
- if (host._heartbeatBusy && beat === host._heartbeatBeat) {
8272
- host._heartbeatBusy = false;
8273
- host._heartbeatStalls++;
8274
- if (host.logger && host.logger.warn) {
8275
- host.logger.warn("[heartbeat] \u5355\u62CD\u8D85\u8FC7 " + stallMs + "ms \u672A\u7ED3\u7B97\uFF0C\u5F3A\u5236\u91CA\u653E\u9632\u505C\u6446\uFF08\u7B2C " + host._heartbeatStalls + " \u6B21\uFF09");
8276
- }
8236
+ host._startupFacts.bootAt = Date.now();
8237
+ host._startupFacts.updateCheck = host._startupFacts.updateCheck || null;
8238
+ if (!host._beatScheduler) host._beatScheduler = new BeatScheduler({ logger: host.logger, intervalMs: host.config.tickIntervalMs || 5e3 });
8239
+ const beat = host._beatScheduler;
8240
+ const tickMs = host.config.tickIntervalMs || 5e3;
8241
+ beat.unregister("main");
8242
+ beat.unregister("heartbeat");
8243
+ beat.unregister("instance-supervise");
8244
+ beat.unregister("upgrade-check");
8245
+ beat.register("main", () => host.tick(), { every: 1 });
8246
+ beat.register("heartbeat", () => {
8247
+ if (host.managedObjects) return host.managedObjects.heartbeat(tickMs);
8248
+ return host.tick();
8249
+ }, { every: 1, timeoutMs: Math.max(3e4, tickMs * 12) });
8250
+ beat.register("instance-supervise", async () => {
8251
+ if (!host.instances) return;
8252
+ try {
8253
+ for (const inst of host.instances.all()) {
8254
+ if (inst.domain === "native") continue;
8255
+ host.instances.supervise && host.instances.supervise(inst.id);
8277
8256
  }
8278
- }, stallMs);
8279
- if (guard && typeof guard.unref === "function") guard.unref();
8280
- Promise.resolve(host.managedObjects ? host.managedObjects.heartbeat(iv) : null).catch(() => {
8281
- }).finally(() => {
8282
- clearTimeout(guard);
8283
- if (beat === host._heartbeatBeat) host._heartbeatBusy = false;
8284
- });
8285
- }, heartbeatIv);
8286
- if (host.lanDaemonEnabled()) {
8257
+ } catch (e) {
8258
+ }
8259
+ try {
8260
+ host.instances.governSweep && host.instances.governSweep();
8261
+ } catch (e) {
8262
+ }
8263
+ }, { every: 1 });
8264
+ if (host.config.updateCheckEnabled !== false) {
8265
+ const checkDelayMs2 = host.config.initialCheckDelayMs || 2e4;
8266
+ const checkIntervalMs2 = Math.max(1, Math.round((host.config.updateCheckIntervalMs || 36e5) / tickMs));
8267
+ beat.register("upgrade-check", () => {
8268
+ try {
8269
+ host.nativeManager.checkUpdate();
8270
+ } catch (e) {
8271
+ }
8272
+ }, { every: checkIntervalMs2, firstDelayMs: checkDelayMs2 });
8273
+ }
8274
+ beat.tick();
8275
+ beat.start(tickMs);
8276
+ host._timer = null;
8277
+ host._heartbeatTimer = null;
8278
+ host._upgradeTimer = null;
8279
+ host._initialCheckTimer = null;
8280
+ host._lastHeartbeatAt = Date.now();
8281
+ host._heartbeatStalls = 0;
8282
+ host._heartbeatBeat = host._heartbeatBeat || 0;
8283
+ if (host.lanDaemonEnabled()) {
8287
8284
  host._syncLanState();
8288
8285
  const lrt = host._ensureLanRuntime(true);
8289
8286
  if (host.logger && host.logger.info) host.logger.info("[lan] L3b \u6A21\u5F0F\uFF1Alan-daemon " + (lrt.mode === "daemon" ? "\u5DF2\u5C31\u7EEA pid=" + (lrt.spawned || "(\u65E2\u6709)") : "\u672A\u5C31\u7EEA mode=" + lrt.mode));
@@ -8292,7 +8289,6 @@ var require_bootstrap = __commonJS({
8292
8289
  });
8293
8290
  host.lan.syncFrpc();
8294
8291
  }
8295
- if (!host.managedObjects) host.instances.startTimer(host.config.tickIntervalMs || 5e3);
8296
8292
  if (!host.lanDaemonEnabled()) {
8297
8293
  for (const inst of host.instances.all()) {
8298
8294
  if (inst.remoteMode === "lan" || inst.remoteMode === "wan") host.lan.syncProxy(inst).catch(() => {
@@ -8356,22 +8352,6 @@ var require_bootstrap = __commonJS({
8356
8352
  initialDelayMs: checkDelayMs,
8357
8353
  intervalMs: checkIntervalMs
8358
8354
  };
8359
- if (host.config.updateCheckEnabled !== false) {
8360
- host._initialCheckTimer = setTimeout(() => {
8361
- host.nativeManager.checkUpdate();
8362
- }, checkDelayMs);
8363
- host._upgradeTimer = setInterval(() => {
8364
- host.nativeManager.checkUpdate();
8365
- }, checkIntervalMs);
8366
- }
8367
- try {
8368
- host._startShellWatchdog();
8369
- } catch (e) {
8370
- if (host.logger && host.logger.warn) {
8371
- host.logger.warn("[shell-watchdog] \u542F\u52A8\u5F02\u5E38\uFF08\u4E0D\u5F71\u54CD\u5B88\u536B\u4E3B\u5FAA\u73AF\uFF09: " + (e && e.message || e));
8372
- }
8373
- }
8374
- host._startupFacts.shellWatchdog = !!host.shellWatchdog;
8375
8355
  const envTimer = setTimeout(() => {
8376
8356
  try {
8377
8357
  host._refreshEnvironmentForm();
@@ -8416,41 +8396,6 @@ var require_bootstrap = __commonJS({
8416
8396
  return null;
8417
8397
  });
8418
8398
  }
8419
- function _startShellWatchdog(host) {
8420
- if (host.config.shellWatchdog === false) {
8421
- host.logger.info && host.logger.info("[shell-watchdog] \u5DF2\u6309\u914D\u7F6E\u7981\u7528");
8422
- return;
8423
- }
8424
- try {
8425
- host.shellWatchdog = createShellWatchdog({
8426
- shell: host.shellDomain,
8427
- pidlookup: pidlook,
8428
- desktop: platform.desktop,
8429
- logger: host.logger,
8430
- events: host.events,
8431
- config: host.config,
8432
- halted: () => host._shellExitIntended(),
8433
- onShellAlive: () => {
8434
- if (!host._shellHalted) return;
8435
- if (host._stopping) return;
8436
- if (host._sessionHalting && host._sessionHalting()) return;
8437
- host._shellHalted = false;
8438
- try {
8439
- host.writeState(true);
8440
- } catch {
8441
- }
8442
- }
8443
- });
8444
- host._shellWatchdogTimer = setInterval(() => {
8445
- Promise.resolve(host.shellWatchdog.tick()).catch(() => {
8446
- });
8447
- }, host.shellWatchdog.intervalMs);
8448
- if (host._shellWatchdogTimer.unref) host._shellWatchdogTimer.unref();
8449
- host.logger.info && host.logger.info("[shell-watchdog] \u5DF2\u542F\u7528\uFF08\u5468\u671F " + Math.round(host.shellWatchdog.intervalMs / 1e3) + "s\uFF09");
8450
- } catch (e) {
8451
- host.logger.warn && host.logger.warn("[shell-watchdog] \u521D\u59CB\u5316\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u5B88\u536B\uFF09: " + (e && e.message || e));
8452
- }
8453
- }
8454
8399
  function _registerFixedPorts(host) {
8455
8400
  ports.register("dsh-main", host.config.targetPort);
8456
8401
  ports.registerSole("supervisor-api", host.config.apiPort);
@@ -8479,7 +8424,7 @@ var require_bootstrap = __commonJS({
8479
8424
  }
8480
8425
  }
8481
8426
  }
8482
- module2.exports = { _bootstrap, _startShellWatchdog, _registerFixedPorts, _bindNativeDshCommand, _refreshEnvironmentForm };
8427
+ module2.exports = { _bootstrap, _registerFixedPorts, _bindNativeDshCommand, _refreshEnvironmentForm };
8483
8428
  }
8484
8429
  });
8485
8430
 
@@ -8535,14 +8480,17 @@ var require_api_rebind = __commonJS({
8535
8480
  });
8536
8481
  server.listen(host.config.apiPort, host.config.apiHost, () => {
8537
8482
  bind._tries = 0;
8483
+ const bound = server.address();
8484
+ const realPort = bound && Number.isInteger(bound.port) ? bound.port : host.config.apiPort;
8538
8485
  host.api = server;
8486
+ if (realPort !== host.config.apiPort) host.config.apiPort = realPort;
8539
8487
  try {
8540
- portsShared.registerSole("supervisor-api", host.config.apiPort);
8488
+ portsShared.registerSole("supervisor-api", realPort);
8541
8489
  } catch (e) {
8542
8490
  host.logger.warn("ports.registerSole(supervisor-api) \u5931\u8D25: " + (e && e.message || e));
8543
8491
  }
8544
- host.events.append("api_listening", { host: host.config.apiHost, port: host.config.apiPort });
8545
- host.logger.info("api listening on " + host.config.apiHost + ":" + host.config.apiPort);
8492
+ host.events.append("api_listening", { host: host.config.apiHost, port: realPort });
8493
+ host.logger.info("api listening on " + host.config.apiHost + ":" + realPort);
8546
8494
  });
8547
8495
  };
8548
8496
  bind._tries = 0;
@@ -8564,19 +8512,21 @@ var require_api_rebind = __commonJS({
8564
8512
  host.logger.error("api error: " + (err ? err.message : String(err)));
8565
8513
  });
8566
8514
  server.listen(port, host.config.apiHost, () => {
8515
+ const bound = server.address();
8516
+ const realPort = bound && Number.isInteger(bound.port) ? bound.port : port;
8567
8517
  host.api = server;
8568
8518
  const prev = host.config.apiPort;
8569
- if (port !== prev) {
8570
- host.config.apiPort = port;
8571
- if (host.configPath) host.persistConfigPatch({ apiPort: port });
8519
+ if (realPort !== prev) {
8520
+ host.config.apiPort = realPort;
8521
+ if (host.configPath) host.persistConfigPatch({ apiPort: realPort });
8572
8522
  }
8573
8523
  try {
8574
- portsShared.registerSole("supervisor-api", port);
8524
+ portsShared.registerSole("supervisor-api", realPort);
8575
8525
  } catch (e) {
8576
8526
  host.logger.warn("ports.registerSole(supervisor-api) \u5931\u8D25: " + e.message);
8577
8527
  }
8578
- host.events.append("api_listening", { host: host.config.apiHost, port });
8579
- host.logger.info("api listening on " + host.config.apiHost + ":" + port);
8528
+ host.events.append("api_listening", { host: host.config.apiHost, port: realPort });
8529
+ host.logger.info("api listening on " + host.config.apiHost + ":" + realPort);
8580
8530
  });
8581
8531
  return server;
8582
8532
  };
@@ -10019,14 +9969,10 @@ var require_shutdown = __commonJS({
10019
9969
  host.events.append("guard_exit", {});
10020
9970
  host.logger.info("guard shutting down");
10021
9971
  host.writeState(true);
10022
- if (host._timer) clearInterval(host._timer);
10023
- if (host._heartbeatTimer) clearInterval(host._heartbeatTimer);
9972
+ if (host._beatScheduler) host._beatScheduler.stop();
10024
9973
  if (host._killTimer) clearTimeout(host._killTimer);
10025
9974
  if (host._adoptKillTimer) clearTimeout(host._adoptKillTimer);
10026
9975
  abortInflightNpm(host, "guard-shutdown");
10027
- if (host._initialCheckTimer) clearTimeout(host._initialCheckTimer);
10028
- if (host._upgradeTimer) clearInterval(host._upgradeTimer);
10029
- if (host._shellWatchdogTimer) clearInterval(host._shellWatchdogTimer);
10030
9976
  if (host.api) {
10031
9977
  try {
10032
9978
  host.api.close();
@@ -10071,37 +10017,10 @@ var require_shutdown = __commonJS({
10071
10017
  } catch (e) {
10072
10018
  host.logger.warn && host.logger.warn("shutdownAll persist shellHalted: " + e.message);
10073
10019
  }
10074
- if (host._shellWatchdogTimer) {
10075
- clearInterval(host._shellWatchdogTimer);
10076
- host._shellWatchdogTimer = null;
10077
- }
10078
- if (host._heartbeatTimer) {
10079
- clearInterval(host._heartbeatTimer);
10080
- host._heartbeatTimer = null;
10081
- }
10082
- if (host._timer) {
10083
- clearInterval(host._timer);
10084
- host._timer = null;
10085
- }
10086
- if (host._initialCheckTimer) {
10087
- clearTimeout(host._initialCheckTimer);
10088
- host._initialCheckTimer = null;
10089
- }
10090
- if (host._upgradeTimer) {
10091
- clearInterval(host._upgradeTimer);
10092
- host._upgradeTimer = null;
10093
- }
10094
- if (host._killTimer) {
10095
- clearTimeout(host._killTimer);
10096
- host._killTimer = null;
10097
- }
10098
- if (host._adoptKillTimer) {
10099
- clearTimeout(host._adoptKillTimer);
10100
- host._adoptKillTimer = null;
10101
- }
10102
10020
  host.logger.info("[session] \u9000\u51FA\u6D41\u7A0B\u5F00\u59CB\uFF1A\u505C\u6B62\u5168\u90E8\u88AB\u7BA1\u5BF9\u8C61\u2026");
10103
10021
  host.events && host.events.append("shutdown_all", {});
10104
10022
  abortInflightNpm(host, "session-exit");
10023
+ if (host._beatScheduler) host._beatScheduler.stop();
10105
10024
  host._stopMainDsh();
10106
10025
  await host._stopAllSandboxes();
10107
10026
  const stopDaemon = async (kind) => {
@@ -10337,6 +10256,12 @@ var require_instance_adapter = __commonJS({
10337
10256
  } catch (e) {
10338
10257
  d.logger() && d.logger().warn && d.logger().warn("sandbox setPhase: " + (e && e.message || e));
10339
10258
  }
10259
+ try {
10260
+ const rc = Number(inst.state && inst.state.restartCount) || 0;
10261
+ if (entry.restartCount !== rc) d.managedObjects().setRestartCount(entry.id, rc);
10262
+ } catch (e) {
10263
+ d.logger() && d.logger().warn && d.logger().warn("sandbox setRestartCount: " + (e && e.message || e));
10264
+ }
10340
10265
  }
10341
10266
  }
10342
10267
  };
@@ -10954,6 +10879,7 @@ var require_process2 = __commonJS({
10954
10879
  var native = require_command();
10955
10880
  var BRAND2 = require_brand();
10956
10881
  var { findManagedDshPort, applyMainPort } = require_port_rederive();
10882
+ var pmClient = require_pm_client();
10957
10883
  var DEPS = /* @__PURE__ */ new WeakMap();
10958
10884
  var HELPERS = [
10959
10885
  "MissingNotified",
@@ -11009,7 +10935,6 @@ var require_process2 = __commonJS({
11009
10935
  const d = depsOf(this);
11010
10936
  return native.nativeCommand(d.config(), d.pluginManager());
11011
10937
  },
11012
- // 启动窗口(毫秒):唯一的时间常量,spawn 时落成 startDeadline,窗口内退出即「启动失败」。
11013
10938
  startWindowMs() {
11014
10939
  const d = depsOf(this);
11015
10940
  const secs = Number(d.config().startsecs);
@@ -11089,6 +11014,7 @@ var require_process2 = __commonJS({
11089
11014
  errBuf.flush();
11090
11015
  if (d.mChild() !== child) return;
11091
11016
  d.events().append(BRAND2.EVENT_HARNESS_EXITED, { code, signal, phase: d.state().phase() });
11017
+ pmClient.recordExit("main", code, signal, d.state().phase() === "STARTING");
11092
11018
  d.mSetChild(null);
11093
11019
  if (d.stopping()) return;
11094
11020
  if (d.state().desired() !== "running") return;
@@ -11098,6 +11024,7 @@ var require_process2 = __commonJS({
11098
11024
  const why = code !== null ? String(code) : "sig" + signal;
11099
11025
  const inStartup = phase === "STARTING";
11100
11026
  if (inStartup || d.state().guardian()) {
11027
+ pmClient.requestRestart("main");
11101
11028
  d.beginRestart("exit:" + why, { startupFailure: inStartup });
11102
11029
  } else {
11103
11030
  d.writeCrashHalted(true);
@@ -11174,8 +11101,6 @@ var require_process2 = __commonJS({
11174
11101
  d.tokenService().scheduleCapture("main");
11175
11102
  d.state().write();
11176
11103
  },
11177
- // 重启:活过 startsecs 后退出(或人工重启)⇒ 正常重启,不记启动失败;窗口内退出 ⇒ 记一次启动失败并由限流裁决。
11178
- // 相位只有 STARTING / RUNNING / FAILED:重启=回到 STARTING(下一次 spawn 会开新的 startsecs 窗口)。
11179
11104
  _beginRestart(reason, opts) {
11180
11105
  const d = depsOf(this);
11181
11106
  const manual = !!(opts && opts.manual);
@@ -11316,7 +11241,6 @@ var require_signals = __commonJS({
11316
11241
  return null;
11317
11242
  }
11318
11243
  },
11319
- // 归属凭据:spawn 与 adopt 两条取得所有权的路线都要写;落盘走 writeAtomic + 0600。
11320
11244
  _writeMainOwner(dshPid, port) {
11321
11245
  const d = depsOf(this);
11322
11246
  try {
@@ -11372,7 +11296,6 @@ var require_signals = __commonJS({
11372
11296
  }
11373
11297
  }, d.config().stopGraceMs));
11374
11298
  },
11375
- // 代际 gen 保护共享的 _adoptKillTimer 槽位:定时器只在「本代仍是当前代」时置空。
11376
11299
  _killAdopted(pid) {
11377
11300
  const d = depsOf(this);
11378
11301
  const gen = (d.readAdoptKillGen() || 0) + 1;
@@ -11437,7 +11360,35 @@ var require_guardian = __commonJS({
11437
11360
  }
11438
11361
  return { start, count, tripped: count >= burst };
11439
11362
  }
11440
- module2.exports = { shouldGuard, bumpStartupFailure };
11363
+ function makeBudget(cfg) {
11364
+ const windowMs = cfg && Number(cfg.windowMs) > 0 ? Number(cfg.windowMs) : 6e4;
11365
+ const burst = cfg && Number(cfg.burst) >= 1 ? Number(cfg.burst) : 5;
11366
+ let state = { start: null, count: 0 };
11367
+ const self = {
11368
+ windowMs,
11369
+ burst,
11370
+ get count() {
11371
+ return state.count;
11372
+ },
11373
+ get tripped() {
11374
+ return state.count >= burst;
11375
+ },
11376
+ note(startupFailure) {
11377
+ if (startupFailure === false || startupFailure == null) {
11378
+ return { tripped: false, count: state.count, halted: false };
11379
+ }
11380
+ const dec = bumpStartupFailure(state, Date.now(), { windowMs, burst });
11381
+ state.start = dec.start;
11382
+ state.count = dec.count;
11383
+ return { tripped: dec.tripped, count: state.count, halted: dec.tripped };
11384
+ },
11385
+ reset() {
11386
+ state = { start: null, count: 0 };
11387
+ }
11388
+ };
11389
+ return self;
11390
+ }
11391
+ module2.exports = { shouldGuard, bumpStartupFailure, makeBudget };
11441
11392
  }
11442
11393
  });
11443
11394
 
@@ -11446,6 +11397,7 @@ var require_startup_throttle = __commonJS({
11446
11397
  "src/app/main/startup-throttle.js"(exports2, module2) {
11447
11398
  "use strict";
11448
11399
  var guardian = require_guardian();
11400
+ var pmClient = require_pm_client();
11449
11401
  var DEPS = /* @__PURE__ */ new WeakMap();
11450
11402
  function depsOf(host) {
11451
11403
  let d = DEPS.get(host);
@@ -11485,8 +11437,6 @@ var require_startup_throttle = __commonJS({
11485
11437
  }
11486
11438
  module2.exports = {
11487
11439
  methods: {
11488
- // 唯一一条限流:startupFailWindowMs 内「启动窗口内退出」达 startupFailBurst 次 ⇒ phase=FAILED。
11489
- // 到点即停自动重启:不排队、不退避、不指数等待;恢复的唯一入口是人工重试(_retryStartupFailure)。
11490
11440
  _noteStartupFailure() {
11491
11441
  const d = depsOf(this);
11492
11442
  const windowMs = d.config().startupFailWindowMs || 6e4;
@@ -11511,11 +11461,11 @@ var require_startup_throttle = __commonJS({
11511
11461
  );
11512
11462
  return { failed: true, count: dec.count };
11513
11463
  },
11514
- // 手动重试入口:清计数后回到 STARTING(随后由 STARTING 分支重新 spawn,新的 startsecs 窗口)。
11515
11464
  _retryStartupFailure() {
11516
11465
  const d = depsOf(this);
11517
11466
  d.mSetStartupFailWindowStart(null);
11518
11467
  d.mSetStartupFailCount(0);
11468
+ pmClient.resetBackoff("main");
11519
11469
  d.events().append("startup_retry", {});
11520
11470
  d.logger().warn("\u542F\u52A8\u5931\u8D25\u8BA1\u6570\u5DF2\u6E05\u96F6\uFF1A\u4EBA\u5DE5\u91CD\u8BD5\uFF0C\u56DE\u5230 STARTING");
11521
11471
  return { ok: true };
@@ -12065,8 +12015,6 @@ var require_runtime2 = __commonJS({
12065
12015
  spawnEnv: () => ({ DSH_SUPERVISOR_CONFIG: cfgPath }),
12066
12016
  logger: d.logger(),
12067
12017
  events: d.events(),
12068
- // `this` 是宿主(本方法经 host._daemonLifecycle(kind) 调用);箭头函数捕获它。
12069
- // 此前写作 host._exitIntended(),而 host 只存在于 depsOf(host) 形参作用域 ⇒ 延迟回调求值时 ReferenceError。
12070
12018
  exitIntended: () => this._exitIntended()
12071
12019
  });
12072
12020
  return lc[kind];
@@ -12934,7 +12882,7 @@ var require_credential = __commonJS({
12934
12882
  });
12935
12883
 
12936
12884
  // src/domains/relay/core.js
12937
- var require_core4 = __commonJS({
12885
+ var require_core3 = __commonJS({
12938
12886
  "src/domains/relay/core.js"(exports2, module2) {
12939
12887
  "use strict";
12940
12888
  var crypto = require("node:crypto");
@@ -12962,14 +12910,52 @@ var require_core4 = __commonJS({
12962
12910
  }
12963
12911
  return null;
12964
12912
  }
12965
- function upstreamPath(rawUrl) {
12966
- try {
12967
- const u = new URL(rawUrl, "http://127.0.0.1");
12968
- if (u.searchParams.has("token")) u.searchParams.delete("token");
12969
- return u.pathname + (u.searchParams.toString() ? "?" + u.searchParams.toString() : "");
12970
- } catch {
12971
- return rawUrl || "/";
12913
+ function stripGateTokenAndPreserve(rawUrl) {
12914
+ if (!rawUrl) return "/";
12915
+ let url = rawUrl;
12916
+ const hashIdx = url.indexOf("#");
12917
+ if (hashIdx >= 0) url = url.slice(0, hashIdx);
12918
+ const qIdx = url.indexOf("?");
12919
+ const path2 = qIdx >= 0 ? url.slice(0, qIdx) : url;
12920
+ if (qIdx < 0) return path2 || "/";
12921
+ const query = url.slice(qIdx + 1);
12922
+ if (!query) return path2 || "/";
12923
+ const kept = [];
12924
+ for (const pair of query.split("&")) {
12925
+ if (!pair) {
12926
+ kept.push(pair);
12927
+ continue;
12928
+ }
12929
+ const eq = pair.indexOf("=");
12930
+ const rawKey = eq >= 0 ? pair.slice(0, eq) : pair;
12931
+ let key = rawKey;
12932
+ try {
12933
+ key = decodeURIComponent(rawKey);
12934
+ } catch {
12935
+ }
12936
+ if (key === "token") continue;
12937
+ kept.push(pair);
12972
12938
  }
12939
+ const out = path2 || "/";
12940
+ return kept.length ? out + "?" + kept.join("&") : out;
12941
+ }
12942
+ function upstreamPath(rawUrl) {
12943
+ return stripGateTokenAndPreserve(rawUrl);
12944
+ }
12945
+ var POLYFILL_SCRIPT = `<script>
12946
+ if (typeof crypto.randomUUID !== 'function') {
12947
+ crypto.randomUUID = function () {
12948
+ var b = crypto.getRandomValues(new Uint8Array(16));
12949
+ b[6] = (b[6] & 0x0f) | 0x40;
12950
+ b[8] = (b[8] & 0x3f) | 0x80;
12951
+ var h = '';
12952
+ for (var i = 0; i < 16; i++) h += (i === 4 || i === 6 || i === 8 || i === 12 ? '-' : '') + ('0' + b[i].toString(16)).slice(-2);
12953
+ return h;
12954
+ };
12955
+ }
12956
+ </script>`;
12957
+ function redactLogPath(rawUrl) {
12958
+ return rawUrl ? stripGateTokenAndPreserve(rawUrl) : "";
12973
12959
  }
12974
12960
  function lanGateCookieValue(token, salt) {
12975
12961
  const t = String(token == null ? "" : token);
@@ -12977,23 +12963,6 @@ var require_core4 = __commonJS({
12977
12963
  if (!t || !s) return "";
12978
12964
  return crypto.createHash("sha256").update(s + "\n" + t).digest("hex");
12979
12965
  }
12980
- function hasValidToken(req, token, salt, mode) {
12981
- if (!token) return normalizeRemoteMode(mode) === "lan";
12982
- const url = new URL(req.url, "http://localhost");
12983
- const queryToken = url.searchParams.get("token");
12984
- if (queryToken && safeEqual(queryToken, token)) return true;
12985
- const cookies = req.headers.cookie || "";
12986
- const m = LAN_COOKIE_RE.exec(cookies);
12987
- if (m) {
12988
- try {
12989
- const want = lanGateCookieValue(token, salt);
12990
- return !!want && safeEqual(decodeURIComponent(m[1]), want);
12991
- } catch {
12992
- return false;
12993
- }
12994
- }
12995
- return false;
12996
- }
12997
12966
  function tokenGateDecision(req, token, salt, mode) {
12998
12967
  if (!token) return normalizeRemoteMode(mode) === "lan" ? { ok: true } : { ok: false, unauthorized: true };
12999
12968
  const url = new URL(req.url, "http://localhost");
@@ -13016,27 +12985,22 @@ var require_core4 = __commonJS({
13016
12985
  }
13017
12986
  return { ok: false, unauthorized: true };
13018
12987
  }
13019
- var POLYFILL_SCRIPT = `<script>
13020
- if (typeof crypto.randomUUID !== 'function') {
13021
- crypto.randomUUID = function () {
13022
- var b = crypto.getRandomValues(new Uint8Array(16));
13023
- b[6] = (b[6] & 0x0f) | 0x40;
13024
- b[8] = (b[8] & 0x3f) | 0x80;
13025
- var h = '';
13026
- for (var i = 0; i < 16; i++) h += (i === 4 || i === 6 || i === 8 || i === 12 ? '-' : '') + ('0' + b[i].toString(16)).slice(-2);
13027
- return h;
13028
- };
13029
- }
13030
- </script>`;
13031
- function redactLogPath(rawUrl) {
13032
- if (!rawUrl) return "";
13033
- try {
13034
- const u = new URL(rawUrl, "http://127.0.0.1");
13035
- if (u.searchParams.has("token")) u.searchParams.delete("token");
13036
- return u.pathname + (u.searchParams.toString() ? "?" + u.searchParams.toString() : "");
13037
- } catch {
13038
- return String(rawUrl || "").replace(/[?&]token=[^&]*/, "");
12988
+ function hasValidToken(req, token, salt, mode) {
12989
+ if (!token) return normalizeRemoteMode(mode) === "lan";
12990
+ const url = new URL(req.url, "http://localhost");
12991
+ const queryToken = url.searchParams.get("token");
12992
+ if (queryToken && safeEqual(queryToken, token)) return true;
12993
+ const cookies = req.headers.cookie || "";
12994
+ const m = LAN_COOKIE_RE.exec(cookies);
12995
+ if (m) {
12996
+ try {
12997
+ const want = lanGateCookieValue(token, salt);
12998
+ return !!want && safeEqual(decodeURIComponent(m[1]), want);
12999
+ } catch {
13000
+ return false;
13001
+ }
13039
13002
  }
13003
+ return false;
13040
13004
  }
13041
13005
  function buildFrpcToml(settings, instances) {
13042
13006
  const s = settings || {};
@@ -13067,8 +13031,6 @@ if (typeof crypto.randomUUID !== 'function') {
13067
13031
  return {
13068
13032
  serverAddr: String(j.serverAddr !== void 0 ? j.serverAddr : cur.serverAddr).trim(),
13069
13033
  serverPort: Number(j.serverPort) || cur.serverPort,
13070
- // authToken:null/undefined 归一为空串(frpc 遇空串不写 auth.token,避免 String(null)==="null" 被当成共享密钥);
13071
- // 非空串统一 trim,与 serverAddr 同口径。
13072
13034
  authToken: j.authToken === void 0 ? cur.authToken : j.authToken == null ? "" : String(j.authToken).trim(),
13073
13035
  user: String(j.user || cur.user || "dsh")
13074
13036
  };
@@ -13151,7 +13113,7 @@ if (typeof crypto.randomUUID !== 'function') {
13151
13113
  var require_lan2 = __commonJS({
13152
13114
  "src/app/domain-actions/lan.js"(exports2, module2) {
13153
13115
  "use strict";
13154
- var { validateWanAccess, generateRemoteToken } = require_core4();
13116
+ var { validateWanAccess, generateRemoteToken } = require_core3();
13155
13117
  var { remoteTokenStrength } = require_credential();
13156
13118
  var BRAND2 = require_brand();
13157
13119
  function lanModule(deps) {
@@ -13518,13 +13480,6 @@ var require_env = __commonJS({
13518
13480
  } catch {
13519
13481
  return null;
13520
13482
  }
13521
- })(),
13522
- shellWatchdog: (() => {
13523
- try {
13524
- return this.shellWatchdog && typeof this.shellWatchdog.status === "function" ? this.shellWatchdog.status() : null;
13525
- } catch {
13526
- return null;
13527
- }
13528
13483
  })()
13529
13484
  };
13530
13485
  },
@@ -13739,7 +13694,6 @@ var require_versions = __commonJS({
13739
13694
  }
13740
13695
  return dir;
13741
13696
  },
13742
- // 同步 spawn 会冻结事件循环,消费方含 HTTP 路径,故 git 子进程异步执行。
13743
13697
  async guardVersionLocal() {
13744
13698
  const d = depsOf(this);
13745
13699
  const root = d.vcsRoot();
@@ -13796,7 +13750,6 @@ var require_access = __commonJS({
13796
13750
  const cfg = this.config || {};
13797
13751
  return { configured: !!cfg.apiAccessKey, host: cfg.apiHost || void 0 };
13798
13752
  },
13799
- // 清空密钥必须同时回关 LAN(apiHost→127.0.0.1):只清 key 不动 apiHost 会留下「绑定 0.0.0.0 且零认证」的暴露窗口。
13800
13753
  setAccessKey(key) {
13801
13754
  try {
13802
13755
  const cfg = this.config || {};
@@ -14707,7 +14660,6 @@ var require_pool2 = __commonJS({
14707
14660
  this._backfillAt.delete(id);
14708
14661
  this.clear(id);
14709
14662
  }
14710
- // journalctl 同步查询会冻结生命周期 tick 的事件循环,故 journal 档非阻塞发射(见 capture.js)。
14711
14663
  capture(id) {
14712
14664
  const src = this._sources.get(id);
14713
14665
  if (!src) return null;
@@ -15024,6 +14976,35 @@ var require_npm = __commonJS({
15024
14976
  }
15025
14977
  });
15026
14978
 
14979
+ // src/domains/shell/core.js
14980
+ var require_core4 = __commonJS({
14981
+ "src/domains/shell/core.js"(exports2, module2) {
14982
+ "use strict";
14983
+ function isUpdatePhase(phase) {
14984
+ const p = String(phase || "");
14985
+ return p === "restarting" || p.indexOf("shell-update") === 0;
14986
+ }
14987
+ function deriveState(id, journal) {
14988
+ const j = journal || {};
14989
+ if (!j.to) return { state: "idle", reason: "\u65E0\u8FDB\u884C\u4E2D\u7684\u66F4\u65B0", journal: j, identity: id };
14990
+ const cur = id && id.version ? String(id.version) : null;
14991
+ if (cur && cur === j.to && id && id.phase === "ready") {
14992
+ const next = j.confirmed === true ? j : Object.assign({}, j, { confirmed: true });
14993
+ return { state: "confirmed", version: cur, reason: "\u58F3\u5DF2\u5065\u5EB7\u8FD0\u884C\u65B0\u7248\u672C", journal: next, identity: id };
14994
+ }
14995
+ return {
14996
+ state: "pending",
14997
+ target: j.to,
14998
+ current: cur,
14999
+ reason: cur === j.to ? "\u7B49\u5F85\u58F3\u4E0A\u62A5\u5C31\u7EEA" : "\u7B49\u5F85\u58F3\u91CD\u542F\u5230\u65B0\u7248\u672C",
15000
+ journal: j,
15001
+ identity: id
15002
+ };
15003
+ }
15004
+ module2.exports = { isUpdatePhase, deriveState };
15005
+ }
15006
+ });
15007
+
15027
15008
  // src/domains/shell/journal.js
15028
15009
  var require_journal = __commonJS({
15029
15010
  "src/domains/shell/journal.js"(exports2, module2) {
@@ -15032,7 +15013,7 @@ var require_journal = __commonJS({
15032
15013
  var fs2 = require("node:fs");
15033
15014
  var path2 = require("node:path");
15034
15015
  var { writeAtomic } = require_fs();
15035
- var { deriveState } = require_core3();
15016
+ var { deriveState } = require_core4();
15036
15017
  function shellDir() {
15037
15018
  return stateRoot2.shellDir();
15038
15019
  }
@@ -15111,116 +15092,6 @@ var require_journal = __commonJS({
15111
15092
  }
15112
15093
  });
15113
15094
 
15114
- // src/domains/shell/restart.js
15115
- var require_restart = __commonJS({
15116
- "src/domains/shell/restart.js"(exports2, module2) {
15117
- "use strict";
15118
- var spawnOS = require_spawn();
15119
- var pidlook = require_pidlookup();
15120
- var BRAND2 = require_brand();
15121
- var { identity } = require_journal();
15122
- var { exeFromCmdline, isShellProcess } = require_core3();
15123
- var { semverCompare } = require_version2();
15124
- var SHELL_RELEASE_PKG = "@lob-ox/shell-release";
15125
- async function checkUpdate(dist, opts) {
15126
- const id = identity();
15127
- const installed = id && id.version ? String(id.version) : null;
15128
- if (!dist || typeof dist.fetchLatestVersion !== "function") {
15129
- return { ok: false, installed, latest: null, updateAvailable: false, error: "\u5206\u53D1\u670D\u52A1\u672A\u521D\u59CB\u5316" };
15130
- }
15131
- try {
15132
- const latest = await dist.fetchLatestVersion(SHELL_RELEASE_PKG, "npm", { authoritative: (opts && opts.authoritative) === true });
15133
- if (!latest) {
15134
- return { ok: false, installed, latest: null, updateAvailable: false, error: "\u672A\u67E5\u8BE2\u5230\u58F3\u53D1\u5E03\u7248\u672C\uFF08\u53EF\u80FD\u5C1A\u672A\u53D1\u5E03\uFF09" };
15135
- }
15136
- const updateAvailable = Boolean(installed && semverCompare(latest, installed) > 0);
15137
- return { ok: true, installed, latest, updateAvailable };
15138
- } catch (e) {
15139
- return { ok: false, installed, latest: null, updateAvailable: false, error: e && e.message || String(e) };
15140
- }
15141
- }
15142
- async function restartShell(opts) {
15143
- const o = opts || {};
15144
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
15145
- const pattern = o.procPattern || BRAND2.PROC_MATCH_GUI;
15146
- let procs = [];
15147
- try {
15148
- procs = pidlook.pgrepList(pattern) || [];
15149
- } catch {
15150
- procs = [];
15151
- }
15152
- procs = procs.filter((p) => isShellProcess(p));
15153
- const exe = procs.length ? exeFromCmdline(procs[0].cmdline) : o.exePath || null;
15154
- if (!exe) return { ok: false, error: "\u65E0\u6CD5\u5B9A\u4F4D\u684C\u9762\u58F3\u53EF\u6267\u884C\u6587\u4EF6\uFF08\u58F3\u672A\u8FD0\u884C\u4E14\u672A\u63D0\u4F9B exePath\uFF09" };
15155
- const killed = [];
15156
- for (const p of procs) {
15157
- try {
15158
- process.kill(p.pid, "SIGTERM");
15159
- killed.push(p.pid);
15160
- } catch {
15161
- }
15162
- }
15163
- const waitGone = async (ms) => {
15164
- const deadline = Date.now() + ms;
15165
- while (Date.now() < deadline) {
15166
- const alive = procs.some((p) => {
15167
- try {
15168
- return pidlook.isAlive(p.pid);
15169
- } catch {
15170
- return false;
15171
- }
15172
- });
15173
- if (!alive) return true;
15174
- await sleep(200);
15175
- }
15176
- return !procs.some((p) => {
15177
- try {
15178
- return pidlook.isAlive(p.pid);
15179
- } catch {
15180
- return false;
15181
- }
15182
- });
15183
- };
15184
- let gone = await waitGone(o.graceMs || 6e3);
15185
- if (!gone) {
15186
- for (const p of procs) {
15187
- try {
15188
- process.kill(p.pid, "SIGKILL");
15189
- } catch {
15190
- }
15191
- }
15192
- gone = await waitGone(2e3);
15193
- }
15194
- if (!gone) {
15195
- return { ok: false, error: "\u65E7\u58F3\u8FDB\u7A0B\u672A\u80FD\u5728\u8D85\u65F6\u5185\u9000\u51FA\uFF0C\u5DF2\u653E\u5F03\u91CD\u542F\uFF08\u907F\u514D\u53CC\u5B9E\u4F8B\uFF09", killed };
15196
- }
15197
- if (typeof o.shouldAbort === "function") {
15198
- try {
15199
- if (o.shouldAbort()) return { ok: false, aborted: true, error: "\u4F1A\u8BDD\u5DF2\u9000\u51FA/\u9000\u51FA\u4E2D\uFF0C\u5DF2\u653E\u5F03\u62C9\u8D77\u684C\u9762\u58F3", killed };
15200
- } catch {
15201
- }
15202
- }
15203
- try {
15204
- const child = spawnOS.detachedIgnored(exe, [], { env: process.env });
15205
- child.on("error", (e) => {
15206
- try {
15207
- if (o.events && o.events.append) o.events.append("shell_restart_spawn_error", { exe, error: e && e.message || String(e) });
15208
- } catch {
15209
- }
15210
- });
15211
- child.unref();
15212
- if (!child.pid) {
15213
- return { ok: false, error: "\u62C9\u8D77\u65B0\u58F3\u5931\u8D25\uFF1A\u5B50\u8FDB\u7A0B\u672A\u542F\u52A8\uFF08" + exe + " \u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u6267\u884C\uFF09", killed };
15214
- }
15215
- return { ok: true, restarted: true, killed, pid: child.pid, exe };
15216
- } catch (e) {
15217
- return { ok: false, error: "\u62C9\u8D77\u65B0\u58F3\u5931\u8D25: " + (e && e.message || e), killed };
15218
- }
15219
- }
15220
- module2.exports = { SHELL_RELEASE_PKG, checkUpdate, restartShell };
15221
- }
15222
- });
15223
-
15224
15095
  // src/domains/shell/index.js
15225
15096
  var require_shell = __commonJS({
15226
15097
  "src/domains/shell/index.js"(exports2, module2) {
@@ -15234,8 +15105,7 @@ var require_shell = __commonJS({
15234
15105
  health,
15235
15106
  status
15236
15107
  } = require_journal();
15237
- var { SHELL_RELEASE_PKG, checkUpdate, restartShell } = require_restart();
15238
- module2.exports = { status, evaluate, health, markPending, identity, readJournal, shellDir, checkUpdate, restartShell, SHELL_RELEASE_PKG };
15108
+ module2.exports = { status, evaluate, health, markPending, identity, readJournal, shellDir };
15239
15109
  }
15240
15110
  });
15241
15111
 
@@ -15538,8 +15408,6 @@ var require_intents = __commonJS({
15538
15408
  if (this._pending.has(intent)) this._pending.delete(intent);
15539
15409
  return p;
15540
15410
  }
15541
- // W5:生产零调用,仅 session-lifecycle-test.js 用于「模拟重启后内存态为空」的夹具。
15542
- // 保留(它是一个合理的测试缝),不视为待删死代码。
15543
15411
  clear() {
15544
15412
  this._pending.clear();
15545
15413
  }
@@ -15726,7 +15594,6 @@ var require_core5 = __commonJS({
15726
15594
  routerAutostart: s.routerAutostart,
15727
15595
  routerMode: s.routerMode,
15728
15596
  updateCheck: s.updateCheck,
15729
- shellWatchdog: s.shellWatchdog,
15730
15597
  lastRefresh: s.lastRefresh
15731
15598
  };
15732
15599
  }
@@ -15784,12 +15651,10 @@ var require_core5 = __commonJS({
15784
15651
  host._upgradeHold = false;
15785
15652
  host._upgradeHoldSince = null;
15786
15653
  host._timer = null;
15654
+ host._beatScheduler = null;
15787
15655
  host._heartbeatBusy = false;
15788
15656
  host._killTimer = null;
15789
15657
  host._adoptKillTimer = null;
15790
- host._initialCheckTimer = null;
15791
- host._upgradeTimer = null;
15792
- host._shellWatchdogTimer = null;
15793
15658
  host._lastOccupiedWarn = 0;
15794
15659
  host._lastMainPortRederive = 0;
15795
15660
  host._lastPortUp = false;
@@ -17047,11 +16912,21 @@ var require_instance_lifecycle = __commonJS({
17047
16912
  });
17048
16913
 
17049
16914
  // src/domains/router/providers/restart.js
17050
- var require_restart2 = __commonJS({
16915
+ var require_restart = __commonJS({
17051
16916
  "src/domains/router/providers/restart.js"(exports2, module2) {
17052
16917
  "use strict";
17053
16918
  var { INSTANCE_STATES } = require_model2();
17054
16919
  var ports = require_ports().shared;
16920
+ var { makeBudget } = require_guardian();
16921
+ var PROXY_INSTANCE_THROTTLE = { windowMs: 10 * 60 * 1e3, burst: 5 };
16922
+ function budgetFor(inst) {
16923
+ if (!inst) return makeBudget(PROXY_INSTANCE_THROTTLE);
16924
+ if (!inst._restartBudget) inst._restartBudget = makeBudget(PROXY_INSTANCE_THROTTLE);
16925
+ return inst._restartBudget;
16926
+ }
16927
+ function resetBudget(inst) {
16928
+ if (inst) inst._restartBudget = makeBudget(PROXY_INSTANCE_THROTTLE);
16929
+ }
17055
16930
  function createRestartOrchestrator(deps) {
17056
16931
  const d = deps || {};
17057
16932
  const { startInstance, waitHealthy, isAlive, logger } = d;
@@ -17085,8 +16960,8 @@ var require_restart2 = __commonJS({
17085
16960
  }
17086
16961
  return { respawn };
17087
16962
  }
17088
- async function runReconcile(provider, allowStop) {
17089
- const out = { started: [], stopped: [], desired: [] };
16963
+ async function runReconcileThrottled(provider, allowStop) {
16964
+ const out = { started: [], stopped: [], desired: [], throttled: [] };
17090
16965
  const desired = provider.desiredRunningAccounts();
17091
16966
  const list = desired.list || [];
17092
16967
  out.desired = list.map((a) => a.keyId);
@@ -17095,12 +16970,25 @@ var require_restart2 = __commonJS({
17095
16970
  const inst = provider.instanceOf(acc);
17096
16971
  if (!inst || inst.pid || inst.startingPromise) continue;
17097
16972
  if (inst.status === INSTANCE_STATES.DEAD && !inst.pid) inst.status = INSTANCE_STATES.COLD;
16973
+ const b = budgetFor(inst);
16974
+ if (b.tripped) {
16975
+ inst._throttled = true;
16976
+ out.throttled.push(acc.keyId);
16977
+ continue;
16978
+ }
16979
+ inst._throttled = false;
16980
+ const wasRunning = !!inst.pid;
17098
16981
  try {
17099
16982
  const r = await provider.startInstance(inst);
16983
+ const startupFailure = !r.ok || !wasRunning && !inst.pid;
16984
+ b.note(startupFailure);
17100
16985
  if (r && r.ok) {
17101
16986
  await provider._waitHealthy(inst).catch(() => {
17102
16987
  });
17103
16988
  out.started.push(acc.keyId);
16989
+ if (b.tripped && provider.logger && provider.logger.warn) {
16990
+ provider.logger.warn("[reconcile] \u5B9E\u4F8B\u542F\u52A8\u53CD\u590D\u5931\u8D25\u5DF2\u8FBE\u4E0A\u9650 key=" + (acc.maskedKey || acc.keyId) + "\uFF0C\u505C\u6B62\u81EA\u52A8\u62C9\u8D77\uFF08\u7B49\u4EBA\u5DE5/\u7A97\u53E3\u8FC7\u671F\uFF09");
16991
+ }
17104
16992
  if (provider.logger && provider.logger.info) {
17105
16993
  const role = desired.active && acc.keyId === desired.active.keyId ? "\u5728\u7528" : "\u9884\u70ED";
17106
16994
  provider.logger.info("[reconcile] \u62C9\u8D77\u5B9E\u4F8B key=" + acc.maskedKey + "\uFF08" + role + "\uFF09");
@@ -17109,8 +16997,8 @@ var require_restart2 = __commonJS({
17109
16997
  } catch {
17110
16998
  }
17111
16999
  }
17112
- if (!allowStop) return out;
17113
17000
  for (const inst of (provider.instances || []).slice()) {
17001
+ if (!allowStop) break;
17114
17002
  if (!inst.pid) {
17115
17003
  if (!desiredIds.has(inst.keyId) && inst.port) {
17116
17004
  try {
@@ -17155,7 +17043,7 @@ var require_restart2 = __commonJS({
17155
17043
  }
17156
17044
  }
17157
17045
  if (provider._reconcileBusy) return provider._reconcileBusy;
17158
- const p = runReconcile(provider, allowStop);
17046
+ const p = runReconcileThrottled(provider, allowStop);
17159
17047
  provider._reconcileBusy = p;
17160
17048
  try {
17161
17049
  return await p;
@@ -17168,7 +17056,60 @@ var require_restart2 = __commonJS({
17168
17056
  reconcileInstances(provider).catch(() => {
17169
17057
  });
17170
17058
  }
17171
- module2.exports = { createRestartOrchestrator, runReconcile, reconcileInstances, reconcileNow };
17059
+ module2.exports = { createRestartOrchestrator, runReconcileThrottled, reconcileInstances, reconcileNow, budgetFor, resetBudget, PROXY_INSTANCE_THROTTLE };
17060
+ }
17061
+ });
17062
+
17063
+ // src/app/control/process-lifecycle.js
17064
+ var require_process_lifecycle = __commonJS({
17065
+ "src/app/control/process-lifecycle.js"(exports2, module2) {
17066
+ "use strict";
17067
+ var procOS = require_process();
17068
+ var pidlook = require_pidlookup();
17069
+ var DEFAULTS = {
17070
+ stopGraceMs: 9e3,
17071
+ portFreeTimeoutMs: 5e3
17072
+ };
17073
+ function sleep(ms) {
17074
+ return new Promise((r) => setTimeout(r, ms));
17075
+ }
17076
+ function isForeignOnPort(port, ownPid) {
17077
+ if (!Number.isInteger(port) || port <= 0) return false;
17078
+ const p = pidlook.findListeningPid(port);
17079
+ return p !== null && p !== ownPid;
17080
+ }
17081
+ async function waitPortFree(port, timeoutMs) {
17082
+ const dl = Date.now() + (timeoutMs || DEFAULTS.portFreeTimeoutMs);
17083
+ while (Date.now() < dl) {
17084
+ if (pidlook.findListeningPid(port) === null) return true;
17085
+ await sleep(150);
17086
+ }
17087
+ return pidlook.findListeningPid(port) === null;
17088
+ }
17089
+ async function stopProcess(opts) {
17090
+ const o = Object.assign({}, DEFAULTS, opts || {});
17091
+ const pid = typeof o.pid === "number" ? o.pid : null;
17092
+ const port = typeof o.port === "number" ? o.port : null;
17093
+ const ownGroup = !!o.ownGroup;
17094
+ if (pid && pid > 0) {
17095
+ procOS.killTree(pid, "SIGTERM", void 0, ownGroup ? { ownGroup: true } : void 0);
17096
+ }
17097
+ const dl = Date.now() + o.stopGraceMs;
17098
+ while (Date.now() < dl) {
17099
+ const alive = pid ? pidlook.isAlive ? pidlook.isAlive(pid) : false : false;
17100
+ if (!alive) break;
17101
+ await sleep(150);
17102
+ }
17103
+ if (port) {
17104
+ if (isForeignOnPort(port, pid)) {
17105
+ return { ok: false, stopped: false, foreign: true };
17106
+ }
17107
+ const free = await waitPortFree(port, o.portFreeTimeoutMs);
17108
+ if (!free) return { ok: false, stopped: false, portBusy: true };
17109
+ }
17110
+ return { ok: true, stopped: true };
17111
+ }
17112
+ module2.exports = { ProcessLifecycle: { stopProcess, waitPortFree, isForeignOnPort, DEFAULTS } };
17172
17113
  }
17173
17114
  });
17174
17115
 
@@ -17315,6 +17256,7 @@ var require_probe5 = __commonJS({
17315
17256
  var carrier = require_carrier();
17316
17257
  var procOS = require_process();
17317
17258
  var { INSTANCE_STATES } = require_model2();
17259
+ var { ProcessLifecycle } = require_process_lifecycle();
17318
17260
  var { getQuotaStrategy } = require_quota_strategies();
17319
17261
  var { quotaOverallStatus } = require_quota();
17320
17262
  var { cachedPkgBin, ensurePkgCached } = require_pkg_cache();
@@ -17343,7 +17285,7 @@ var require_probe5 = __commonJS({
17343
17285
  if (provider.logger && provider.logger.warn) provider.logger.warn("[proxy-instance] \u91CD\u542F\u5E78\u5B58\u8005\u5F03\u7528\u91CD\u62C9 pid=" + boundPid + " port=" + inst.port + "\uFF08stdio \u5F52\u5C5E\u65E7\u4EE3\uFF0C\u7981 adopt\uFF09");
17344
17286
  if (provider.events) provider.events.append("proxy_instance_survivor_reclaimed", { app: provider.proxyAppId, port: inst.port, pid: boundPid, reason: "restart-survivor-stdio-unsafe" });
17345
17287
  try {
17346
- procOS.killTree(boundPid, "SIGKILL");
17288
+ ProcessLifecycle.stopProcess({ pid: boundPid, port: inst.port });
17347
17289
  } catch {
17348
17290
  }
17349
17291
  const dl = Date.now() + 3e3;
@@ -17533,7 +17475,7 @@ var require_probe5 = __commonJS({
17533
17475
  if (provider.logger && provider.logger.warn) provider.logger.warn("[proxy-instance] \u751F\u547D\u5468\u671F\u76D1\u63A7\uFF1A\u5B9E\u4F8B\u65E0\u54CD\u5E94\uFF08\u7591\u4F3C\u5361\u6B7B\uFF09key=" + inst.maskedKey + " port=" + inst.port + " pid=" + inst.pid + " fails=" + inst._monitorFails + "\uFF0Ckill \u91CD\u62C9");
17534
17476
  if (provider.events) provider.events.append("proxy_instance_hang_restart", { app: provider.proxyAppId, port: inst.port, pid: inst.pid, fails: inst._monitorFails });
17535
17477
  try {
17536
- procOS.killTree(inst.pid, "SIGKILL", void 0, { ownGroup: true });
17478
+ ProcessLifecycle.stopProcess({ pid: inst.pid, port: inst.port });
17537
17479
  } catch {
17538
17480
  }
17539
17481
  inst.pid = null;
@@ -17688,7 +17630,8 @@ var require_process_pool = __commonJS({
17688
17630
  "src/domains/router/providers/process-pool.js"(exports2, module2) {
17689
17631
  "use strict";
17690
17632
  var life = require_instance_lifecycle();
17691
- var restart = require_restart2();
17633
+ var restart = require_restart();
17634
+ var { resetBudget } = restart;
17692
17635
  var probe = require_probe5();
17693
17636
  var pool = require_pool3();
17694
17637
  var pidlook = require_pidlookup();
@@ -17821,6 +17764,10 @@ var require_process_pool = __commonJS({
17821
17764
  }
17822
17765
  inst._restartAt = Date.now() + 12e4;
17823
17766
  inst._restartPending = null;
17767
+ try {
17768
+ resetBudget(inst);
17769
+ } catch {
17770
+ }
17824
17771
  if (this.logger && this.logger.warn) this.logger.warn("[proxy-instance] \u5B9E\u4F8B\u91CD\u542F key=" + inst.maskedKey + " port=" + inst.port + " reason=" + reason);
17825
17772
  const hadPid = !!inst.pid;
17826
17773
  try {
@@ -17940,7 +17887,7 @@ var require_proxy = __commonJS({
17940
17887
  var { npxLauncher } = require_npx_forms();
17941
17888
  var { buildCommand } = require_command2();
17942
17889
  var probe = require_probe5();
17943
- var restart = require_restart2();
17890
+ var restart = require_restart();
17944
17891
  var pool = require_pool3();
17945
17892
  var life = require_instance_lifecycle();
17946
17893
  var ProxyProvider = class extends withProcessPool(ProviderBase) {
@@ -18304,7 +18251,6 @@ var require_proxy_apps = __commonJS({
18304
18251
  healthPath: "/health",
18305
18252
  modelPath: "/v1/models",
18306
18253
  upstream: "https://api.commandcode.ai",
18307
- // keyEnv 是密钥注入的唯一 env 名(绝不进 cmdline);其余 env/超时一律不注入,跑上游默认配置。
18308
18254
  keyEnv: "CC_API_KEY",
18309
18255
  repo: "thaolaptrinh/commandcode-api-proxy",
18310
18256
  registry: "commandcode-api-proxy",
@@ -19012,16 +18958,6 @@ var require_usage = __commonJS({
19012
18958
  if (!this._dirty) return;
19013
18959
  this._writeTotals();
19014
18960
  }
19015
- /**
19016
- * 根因 E:缓存**新鲜度**与**可写性**两维度正交,不得耦合。
19017
- *
19018
- * 修前:`if (this.totals && this._canPersist()) return this.totals;`
19019
- * —— 闸门关闭(daemon 模式下 setPersistEnabled(false))时该条件恒假 ⇒ 每次 recordUsage
19020
- * 都重新读盘,而 _writeTotals 又因闸门关闭直接 return ⇒ 磁盘不变、内存增量被下次 load 覆盖
19021
- * ⇒ **用量计数被永久吞掉**。
19022
- *
19023
- * 现在:load 只看内存缓存是否存在(新鲜度);是否落盘由 _writeTotals 单独决定(可写性)。
19024
- */
19025
18961
  load() {
19026
18962
  if (this.totals) return this.totals;
19027
18963
  let t;
@@ -20058,6 +19994,7 @@ var require_scheduler2 = __commonJS({
20058
19994
  function createScheduler(deps) {
20059
19995
  const d = deps || {};
20060
19996
  const state = d.state;
19997
+ const beat = d.beatScheduler || null;
20061
19998
  const logger = d.logger || null;
20062
19999
  const refreshProxyUpdateInfo = d.refreshProxyUpdateInfo || (() => Promise.resolve());
20063
20000
  const refreshOfficialUsageAll = d.refreshOfficialUsageAll || (() => Promise.resolve());
@@ -20073,6 +20010,27 @@ var require_scheduler2 = __commonJS({
20073
20010
  });
20074
20011
  ensureProxyInstances().catch(() => {
20075
20012
  });
20013
+ if (beat) {
20014
+ beat.unregister("router-maint");
20015
+ beat.unregister("router-lifecycle");
20016
+ beat.unregister("router-pricing");
20017
+ beat.register("router-maint", () => {
20018
+ refreshProxyUpdateInfo().catch(() => {
20019
+ });
20020
+ probeIfDue();
20021
+ ensureProxyInstances().catch(() => {
20022
+ });
20023
+ }, { every: Math.max(1, Math.round(3e5 / (d.tickMs || 5e3))) });
20024
+ beat.register("router-lifecycle", () => {
20025
+ monitorInstanceHealth().catch(() => {
20026
+ });
20027
+ }, { every: Math.max(1, Math.round(3e4 / (d.tickMs || 5e3))) });
20028
+ beat.register("router-pricing", () => {
20029
+ refreshOfficialPricingAll().catch(() => {
20030
+ });
20031
+ }, { every: Math.max(1, Math.round(216e5 / (d.tickMs || 5e3))) });
20032
+ return;
20033
+ }
20076
20034
  if (state.maintTimer) clearInterval(state.maintTimer);
20077
20035
  state.maintTimer = setInterval(() => {
20078
20036
  refreshProxyUpdateInfo().catch(() => {
@@ -20094,6 +20052,12 @@ var require_scheduler2 = __commonJS({
20094
20052
  }), 6 * 3600 * 1e3);
20095
20053
  }
20096
20054
  function stop() {
20055
+ if (beat) {
20056
+ beat.unregister("router-maint");
20057
+ beat.unregister("router-lifecycle");
20058
+ beat.unregister("router-pricing");
20059
+ return;
20060
+ }
20097
20061
  if (state.maintTimer) {
20098
20062
  clearInterval(state.maintTimer);
20099
20063
  state.maintTimer = null;
@@ -20622,6 +20586,8 @@ var require_router3 = __commonJS({
20622
20586
  var RouterService = class {
20623
20587
  constructor(opts) {
20624
20588
  this.config = opts.config;
20589
+ this._beatScheduler = opts.beatScheduler || null;
20590
+ this._beatTickMs = opts.tickMs || opts.config && opts.config.tickIntervalMs || 5e3;
20625
20591
  this.dist = opts.dist || null;
20626
20592
  this.logger = opts.logger || console;
20627
20593
  this.events = opts.events || null;
@@ -20683,6 +20649,8 @@ var require_router3 = __commonJS({
20683
20649
  state,
20684
20650
  store: this.store,
20685
20651
  logger: this.logger,
20652
+ beatScheduler: this._beatScheduler,
20653
+ tickMs: this._beatTickMs,
20686
20654
  refreshProxyUpdateInfo: () => this.refreshProxyUpdateInfo(),
20687
20655
  refreshOfficialUsageAll: () => this.refreshOfficialUsageAll(),
20688
20656
  refreshOfficialPricingAll: () => this.refreshOfficialPricingAll()
@@ -21260,7 +21228,6 @@ var require_model3 = __commonJS({
21260
21228
  allocation: inst.state ? inst.state.allocation || null : null,
21261
21229
  usage: inst.state ? inst.state.usage || null : null,
21262
21230
  restartCount: inst.state ? inst.state.restartCount || 0 : 0,
21263
- // 启动失败限流(唯一一条规则)的可见量:窗口内计数 / 固定端口释放等待落点。
21264
21231
  startupFailCount: inst.state ? inst.state.startupFailCount || 0 : 0,
21265
21232
  restartAt: inst.state ? inst.state.restartAt || null : null,
21266
21233
  lastFailure: inst.state ? inst.state.lastFailure || null : null,
@@ -21343,12 +21310,6 @@ var require_store5 = __commonJS({
21343
21310
  _stripLegacyStateKeys(inst) {
21344
21311
  if (inst.state && Object.prototype.hasOwnProperty.call(inst.state, "version")) delete inst.state.version;
21345
21312
  }
21346
- /**
21347
- * 隔离损坏文件。
21348
- * Lease I2:隔离前**再次校验版本戳**——若自读取失败以来文件已被他人更新,
21349
- * 说明它不是损坏 ⇒ 放弃隔离(修前会直接 rename 掉这个更新的文件并把实例列表清空,即全量静默丢失)。
21350
- * 且保留可回溯副本:先复制再改名,不直接丢弃。
21351
- */
21352
21313
  _quarantineCorrupt(err) {
21353
21314
  let locked = false;
21354
21315
  try {
@@ -22589,25 +22550,7 @@ var require_ops2 = __commonJS({
22589
22550
  store.save();
22590
22551
  return { ok: true, instance: inst };
22591
22552
  }
22592
- function startTimer(intervalMs) {
22593
- if (timer) clearInterval(timer);
22594
- timer = setInterval(() => {
22595
- for (const inst of store.instances) {
22596
- if (inst.domain === "native") continue;
22597
- try {
22598
- lifecycle.supervise(inst.id);
22599
- } catch (e) {
22600
- logger.warn && logger.warn("[" + inst.id + "] supervise \u5931\u8D25: " + (e && e.message));
22601
- }
22602
- }
22603
- try {
22604
- lifecycle.governSweep();
22605
- } catch (e) {
22606
- logger.warn && logger.warn("governSweep: " + (e && e.message));
22607
- }
22608
- }, intervalMs || 5e3);
22609
- }
22610
- return { list, addInstance, removeInstance, updateInstance, startTimer };
22553
+ return { list, addInstance, removeInstance, updateInstance };
22611
22554
  }
22612
22555
  module2.exports = { createOps };
22613
22556
  }
@@ -22830,7 +22773,6 @@ var require_instance = __commonJS({
22830
22773
  map(fn) {
22831
22774
  return this._store.instances.map(fn);
22832
22775
  }
22833
- // 能力判定读平台表(有副作用)⇒ 从 governor 取,不经 pure 的 sandbox.js。
22834
22776
  get sandboxSupported() {
22835
22777
  return governor.sandboxSupported(this._sandboxSupportedOverride);
22836
22778
  }
@@ -22918,9 +22860,6 @@ var require_instance = __commonJS({
22918
22860
  upgradeStatus(id) {
22919
22861
  return this._upgrade.upgradeStatus(id);
22920
22862
  }
22921
- startTimer(ms) {
22922
- return this._ops.startTimer(ms);
22923
- }
22924
22863
  sandboxRoot(inst) {
22925
22864
  return sandbox.root(this.instancesRoot, inst);
22926
22865
  }
@@ -23818,23 +23757,14 @@ var require_process_spec = __commonJS({
23818
23757
  function defaultSpec() {
23819
23758
  return {
23820
23759
  runtime: null,
23821
- // 解释器(如 node);为 null 表示 bin 可直接执行
23822
23760
  bin: null,
23823
- // 可执行文件或入口脚本
23824
23761
  subcommand: null,
23825
- // 子命令(如 'web' / 'plugin')
23826
23762
  args: [],
23827
- // 用户参数
23828
23763
  port: null,
23829
- // --port 值(一等公民,不再各处拼装)
23830
23764
  patchFile: null,
23831
- // 插件 overlay(一等公民,不再事后 splice)
23832
23765
  storeDir: null,
23833
- // pnpm --store-dir
23834
23766
  install: false,
23835
- // 是否安装类操作(决定是否注入安全标志)
23836
23767
  extraFlags: []
23837
- // 其他固定标志
23838
23768
  };
23839
23769
  }
23840
23770
  function validate(spec) {
@@ -24044,7 +23974,7 @@ var require_cli = __commonJS({
24044
23974
  });
24045
23975
 
24046
23976
  // src/domains/plugin/restart.js
24047
- var require_restart3 = __commonJS({
23977
+ var require_restart2 = __commonJS({
24048
23978
  "src/domains/plugin/restart.js"(exports2, module2) {
24049
23979
  "use strict";
24050
23980
  function targetRunning(ctx, target) {
@@ -24085,7 +24015,7 @@ var require_restart3 = __commonJS({
24085
24015
  log("\u91CD\u542F\u5B9E\u4F8B\u300C" + (target.name || target.id) + "\u300D\u4F7F\u63D2\u4EF6\u53D8\u66F4\u751F\u6548\u2026");
24086
24016
  if (ctx.events) ctx.events.append("plugin_restart_started", { name: target.name || target.id, target: target.id, kind });
24087
24017
  try {
24088
- ctx.instances.stopInstance(target.id);
24018
+ await ctx.instances.stopInstance(target.id);
24089
24019
  } catch (e) {
24090
24020
  log("\u505C\u6B62\u5B9E\u4F8B\u5931\u8D25: " + e.message);
24091
24021
  }
@@ -24109,28 +24039,25 @@ var require_restart3 = __commonJS({
24109
24039
  return true;
24110
24040
  }
24111
24041
  if (target.kind === "native") {
24112
- if (!targetRunning(ctx, target)) {
24113
- log("\u539F\u751F DSH \u672A\u8FD0\u884C\uFF1A\u63D2\u4EF6\u53D8\u66F4\u5C06\u5728\u4E0B\u6B21\u542F\u52A8\u65F6\u751F\u6548");
24042
+ const nr = ctx.onNativeRestart;
24043
+ if (typeof nr !== "function") {
24044
+ log("\u539F\u751F DSH \u672A\u914D\u7F6E\u91CD\u542F\u56DE\u8C03\uFF1A\u63D2\u4EF6\u53D8\u66F4\u5C06\u5728\u4E0B\u6B21\u542F\u52A8\u65F6\u751F\u6548");
24114
24045
  return false;
24115
24046
  }
24116
- if (typeof ctx.onNativeRestart === "function") {
24117
- let rr;
24118
- try {
24119
- rr = ctx.onNativeRestart();
24120
- } catch (e) {
24121
- log("\u539F\u751F DSH \u91CD\u542F\u8BF7\u6C42\u5931\u8D25: " + e.message);
24122
- return false;
24123
- }
24124
- if (rr && rr.ok === false) {
24125
- log("\u539F\u751F DSH \u91CD\u542F\u8BF7\u6C42\u672A\u751F\u6548\uFF1A" + (rr && rr.error || "unknown"));
24126
- return false;
24127
- }
24128
- log("\u5DF2\u8BF7\u6C42\u91CD\u542F\u539F\u751F DSH \u4F7F\u63D2\u4EF6\u53D8\u66F4\u751F\u6548");
24129
- if (ctx.events) ctx.events.append("plugin_restart_done", { name: "\u539F\u751F\u5B9E\u4F8B", target: "native", kind, via: "supervisor" });
24130
- return true;
24047
+ let rr;
24048
+ try {
24049
+ rr = ctx.onNativeRestart();
24050
+ } catch (e) {
24051
+ log("\u539F\u751F DSH \u91CD\u542F\u8BF7\u6C42\u5931\u8D25: " + e.message);
24052
+ return false;
24131
24053
  }
24132
- log("\u63D0\u793A\uFF1A\u539F\u751F DSH \u9700\u91CD\u542F\u540E\u63D2\u4EF6\u53D8\u66F4\u751F\u6548\uFF08\u5F53\u524D\u672A\u914D\u7F6E\u81EA\u52A8\u91CD\u542F\uFF09");
24133
- return false;
24054
+ if (rr && rr.ok === false) {
24055
+ log("\u539F\u751F DSH \u91CD\u542F\u8BF7\u6C42\u672A\u751F\u6548\uFF1A" + (rr && rr.error || "unknown"));
24056
+ return false;
24057
+ }
24058
+ log("\u5DF2\u8BF7\u6C42\u91CD\u542F\u539F\u751F DSH \u4F7F\u63D2\u4EF6\u53D8\u66F4\u751F\u6548");
24059
+ if (ctx.events) ctx.events.append("plugin_restart_done", { name: "\u539F\u751F\u5B9E\u4F8B", target: "native", kind, via: "supervisor" });
24060
+ return true;
24134
24061
  }
24135
24062
  return false;
24136
24063
  } catch (e) {
@@ -24704,7 +24631,7 @@ var require_plugin = __commonJS({
24704
24631
  var store = require_store6();
24705
24632
  var targets = require_targets();
24706
24633
  var cli = require_cli();
24707
- var restart = require_restart3();
24634
+ var restart = require_restart2();
24708
24635
  var { createJobs } = require_jobs();
24709
24636
  var { createLayers } = require_layers();
24710
24637
  var ops = require_ops3();
@@ -24822,7 +24749,8 @@ var require_managed_object = __commonJS({
24822
24749
  "sandbox-instance": { label: "\u6C99\u7BB1\u5B9E\u4F8B", startable: true, guardable: true },
24823
24750
  "router-daemon": { label: "\u667A\u80FD\u8DEF\u7531 daemon", startable: true, guardable: true },
24824
24751
  "lan-daemon": { label: "\u8FDC\u7A0B\u63A7\u5236 daemon", startable: true, guardable: true },
24825
- plugin: { label: "\u63D2\u4EF6\uFF08\u805A\u5408\uFF09", startable: false, guardable: false }
24752
+ plugin: { label: "\u63D2\u4EF6\uFF08\u805A\u5408\uFF09", startable: false, guardable: false },
24753
+ shell: { label: "\u684C\u9762\u58F3", startable: false, guardable: false }
24826
24754
  };
24827
24755
  var _customKinds = {};
24828
24756
  function kindMeta(kind) {
@@ -24954,9 +24882,17 @@ var require_registry4 = __commonJS({
24954
24882
  var path2 = require("node:path");
24955
24883
  var { writeAtomic } = require_fs();
24956
24884
  var { DESIRED, MANAGED_KINDS, kindMeta, registerKind: registerManagedKind, createEntry, normalizeOwnership } = require_managed_object();
24885
+ var { legacyToEntryPhase } = require_phase();
24957
24886
  var PHASES = ["stopped", "installing", "starting", "running", "draining", "failed"];
24958
- var LEGACY_PHASES = { backoff: "failed", restarting: "starting" };
24959
24887
  var { runHeartbeat } = require_heartbeat();
24888
+ var pmClient = require_pm_client();
24889
+ var configDefaults = require_config();
24890
+ var RESTART_FALLBACK = { windowMs: 6e4, burst: 5 };
24891
+ var restartDefaults = () => {
24892
+ const r = configDefaults && configDefaults.restart;
24893
+ if (r && Number.isFinite(r.windowMs) && Number.isFinite(r.burst)) return { windowMs: r.windowMs, burst: r.burst };
24894
+ return RESTART_FALLBACK;
24895
+ };
24960
24896
  var ManagedRegistry = class {
24961
24897
  constructor(opts) {
24962
24898
  this.file = opts && opts.file;
@@ -25002,7 +24938,8 @@ var require_registry4 = __commonJS({
25002
24938
  try {
25003
24939
  if (!o || !kindMeta(o.kind)) continue;
25004
24940
  const e = createEntry({ kind: o.kind, id: o.id, name: o.name, desired: o.desired, ownership: o.ownership });
25005
- if (PHASES.includes(LEGACY_PHASES[o.phase] || o.phase)) e.phase = LEGACY_PHASES[o.phase] || o.phase;
24941
+ const np = legacyToEntryPhase(o.phase);
24942
+ if (PHASES.includes(np)) e.phase = np;
25006
24943
  if (Number.isInteger(o.restartCount) && o.restartCount >= 0) e.restartCount = o.restartCount;
25007
24944
  if (o.startupFailWindowStart === null || typeof o.startupFailWindowStart === "number") e.startupFailWindowStart = o.startupFailWindowStart;
25008
24945
  if (Number.isInteger(o.startupFailCount) && o.startupFailCount >= 0) e.startupFailCount = o.startupFailCount;
@@ -25070,8 +25007,6 @@ var require_registry4 = __commonJS({
25070
25007
  const i = this._objects.indexOf(e);
25071
25008
  if (i >= 0) this._objects.splice(i, 1);
25072
25009
  }
25073
- // 自定义 kind 扩展点:零调用但刻意保留(见 managed-object.js 的说明)——
25074
- // 它是「新增受管对象类型」的唯一入口,删除等于永久关闭扩展能力。
25075
25010
  registerKind(kind, meta) {
25076
25011
  registerManagedKind(kind, meta);
25077
25012
  return this;
@@ -25091,6 +25026,8 @@ var require_registry4 = __commonJS({
25091
25026
  this._syncPortsOwner(e, true);
25092
25027
  this._save();
25093
25028
  this._event("managed_object_registered", { kind: e.kind, id: e.id, name: e.name });
25029
+ const rd = restartDefaults();
25030
+ pmClient.register({ id: e.id, spawn_cmd: [], restart_window_ms: rd.windowMs, restart_burst: rd.burst });
25094
25031
  return e;
25095
25032
  }
25096
25033
  update(id, patch) {
@@ -25100,6 +25037,7 @@ var require_registry4 = __commonJS({
25100
25037
  if (p.desired !== void 0) {
25101
25038
  if (!DESIRED.includes(p.desired)) return { ok: false, error: "\u975E\u6CD5 desired: " + p.desired };
25102
25039
  e.desired = p.desired;
25040
+ pmClient.setDesired(e.id, p.desired);
25103
25041
  }
25104
25042
  if (p.name !== void 0) e.name = String(p.name || e.id);
25105
25043
  if (p.ownership !== void 0) {
@@ -25131,6 +25069,7 @@ var require_registry4 = __commonJS({
25131
25069
  this._drop(e);
25132
25070
  this._save();
25133
25071
  this._event("managed_object_removed", { kind: e.kind, id: e.id });
25072
+ pmClient.setDesired(e.id, "stopped");
25134
25073
  return { ok: true };
25135
25074
  }
25136
25075
  _releasePort(port, ownerId) {
@@ -25175,6 +25114,17 @@ var require_registry4 = __commonJS({
25175
25114
  };
25176
25115
  return e;
25177
25116
  }
25117
+ setRestartCount(id, n) {
25118
+ const e = this.get(id);
25119
+ if (!e) return null;
25120
+ const v = typeof n === "number" && n >= 0 ? Math.floor(n) : 0;
25121
+ if (e.restartCount !== v) {
25122
+ e.restartCount = v;
25123
+ this._event("managed_object_restart_count", { kind: e.kind, id: e.id, count: v });
25124
+ this._save();
25125
+ }
25126
+ return e;
25127
+ }
25178
25128
  setPhase(id, p) {
25179
25129
  const e = this.get(id);
25180
25130
  if (!e) return null;
@@ -25184,6 +25134,7 @@ var require_registry4 = __commonJS({
25184
25134
  e.lastTransitionAt = (/* @__PURE__ */ new Date()).toISOString();
25185
25135
  this._event("managed_object_phase", { kind: e.kind, id: e.id, phase: p });
25186
25136
  this._save();
25137
+ pmClient.onPhase(e.id, p);
25187
25138
  }
25188
25139
  return e;
25189
25140
  }
@@ -26026,7 +25977,6 @@ var require_installer = __commonJS({
26026
25977
  _manifest() {
26027
25978
  return manifest.read(this);
26028
25979
  }
26029
- // W5:生产零调用,仅 native-test.js 使用(写 manifest 的夹具入口)。保留为测试缝。
26030
25980
  _saveManifest(m) {
26031
25981
  return manifest.save(this, m);
26032
25982
  }
@@ -26343,6 +26293,8 @@ var require_domains = __commonJS({
26343
26293
  }
26344
26294
  host.router = new RouterService({
26345
26295
  config: host.config,
26296
+ beatScheduler: host._beatScheduler,
26297
+ tickMs: host.config.tickIntervalMs || 5e3,
26346
26298
  providerFile: path2.join(swDir, "providers.json"),
26347
26299
  usageTotalsFile: path2.join(swDir, "router-usage-totals.json"),
26348
26300
  logger: host.logger,
@@ -26364,7 +26316,6 @@ var require_domains = __commonJS({
26364
26316
  tasks: host.tasks,
26365
26317
  tokenService: host.tokenService,
26366
26318
  dshBin: host.config.command && host.config.command[1] ? host.config.command[1] : "dsh",
26367
- // 实例域限流参数:与主链同一条规则、按域参数化(窗口 600s > 5 × 30s 启动窗口);单源取自域常量。
26368
26319
  throttle: THROTTLE_DEFAULTS
26369
26320
  });
26370
26321
  host.instances.load();
@@ -26481,7 +26432,6 @@ var require_adapters = __commonJS({
26481
26432
  kind: "lan",
26482
26433
  name: "\u8FDC\u7A0B\u63A7\u5236",
26483
26434
  logger,
26484
- // start 的结果必须来自真实执行:reconcile/syncFrpc 各自如实返回 ok,任一失败即 ok:false(此前恒 true)。
26485
26435
  start: async () => {
26486
26436
  try {
26487
26437
  const rec = await lan.reconcile();
@@ -26729,7 +26679,7 @@ var require_lifecycle3 = __commonJS({
26729
26679
  return send(200, { sessionState: sup.sessionState ? sup.sessionState() : "unknown" });
26730
26680
  }
26731
26681
  if (req.method === "POST" && pathname === "/session/stop") {
26732
- if (!originAllowed(req, sup.config.apiPort)) {
26682
+ if (!originAllowed(req)) {
26733
26683
  req.resume();
26734
26684
  return send(403, { ok: false, error: "cross-origin request rejected" });
26735
26685
  }
@@ -26752,7 +26702,7 @@ var require_lifecycle3 = __commonJS({
26752
26702
  return lc ? send(200, lc.snapshot()) : send(404, { error: "\u6A21\u5757\u672A\u6CE8\u518C: " + id });
26753
26703
  }
26754
26704
  if (req.method === "POST" && action) {
26755
- if (!originAllowed(req, sup.config.apiPort)) {
26705
+ if (!originAllowed(req)) {
26756
26706
  req.resume();
26757
26707
  return send(403, { ok: false, error: "cross-origin request rejected" });
26758
26708
  }
@@ -26857,7 +26807,7 @@ var require_native = __commonJS({
26857
26807
  });
26858
26808
  }
26859
26809
  if (req.method === "POST" && pathname === "/native/check-update") {
26860
- if (!originAllowed(req, sup.config.apiPort)) {
26810
+ if (!originAllowed(req)) {
26861
26811
  req.resume();
26862
26812
  return send(403, { ok: false, error: "cross-origin request rejected" });
26863
26813
  }
@@ -26865,7 +26815,7 @@ var require_native = __commonJS({
26865
26815
  return;
26866
26816
  }
26867
26817
  if (req.method === "POST" && pathname === "/native/install") {
26868
- if (!originAllowed(req, sup.config.apiPort)) {
26818
+ if (!originAllowed(req)) {
26869
26819
  req.resume();
26870
26820
  return send(403, { ok: false, error: "cross-origin request rejected" });
26871
26821
  }
@@ -26883,7 +26833,7 @@ var require_native = __commonJS({
26883
26833
  return;
26884
26834
  }
26885
26835
  if (req.method === "POST" && pathname === "/native/upgrade") {
26886
- if (!originAllowed(req, sup.config.apiPort)) {
26836
+ if (!originAllowed(req)) {
26887
26837
  req.resume();
26888
26838
  return send(403, { ok: false, error: "cross-origin request rejected" });
26889
26839
  }
@@ -26904,7 +26854,7 @@ var require_native = __commonJS({
26904
26854
  return;
26905
26855
  }
26906
26856
  if (req.method === "POST" && pathname === "/native/uninstall") {
26907
- if (!originAllowed(req, sup.config.apiPort)) {
26857
+ if (!originAllowed(req)) {
26908
26858
  req.resume();
26909
26859
  return send(403, { ok: false, error: "cross-origin request rejected" });
26910
26860
  }
@@ -26913,7 +26863,7 @@ var require_native = __commonJS({
26913
26863
  return send(202, { ok: true, accepted: true, state: "uninstalling" });
26914
26864
  }
26915
26865
  if (req.method === "POST" && pathname === "/native/settings") {
26916
- if (!originAllowed(req, sup.config.apiPort)) {
26866
+ if (!originAllowed(req)) {
26917
26867
  req.resume();
26918
26868
  return send(403, { ok: false, error: "cross-origin request rejected" });
26919
26869
  }
@@ -26975,14 +26925,14 @@ var require_guard = __commonJS({
26975
26925
  }
26976
26926
  if (req.method === "POST" && pathname === "/guard/version/check") {
26977
26927
  req.resume();
26978
- if (!originAllowed(req, sup.config.apiPort)) return send(403, { ok: false, error: "cross-origin request rejected" });
26928
+ if (!originAllowed(req)) return send(403, { ok: false, error: "cross-origin request rejected" });
26979
26929
  return sup.guardVersionCheck().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
26980
26930
  }
26981
26931
  if (req.method === "GET" && pathname === "/autostart") {
26982
26932
  return send(200, sup.autostartStatus());
26983
26933
  }
26984
26934
  if (req.method === "POST" && pathname === "/autostart") {
26985
- if (!originAllowed(req, sup.config.apiPort)) {
26935
+ if (!originAllowed(req)) {
26986
26936
  req.resume();
26987
26937
  return send(403, { ok: false, error: "cross-origin request rejected" });
26988
26938
  }
@@ -27003,7 +26953,7 @@ var require_guard = __commonJS({
27003
26953
  return send(200, sup.lanPanelStatus());
27004
26954
  }
27005
26955
  if (req.method === "POST" && pathname === "/settings/lan") {
27006
- if (!originAllowed(req, sup.config.apiPort)) {
26956
+ if (!originAllowed(req)) {
27007
26957
  req.resume();
27008
26958
  return send(403, { ok: false, error: "cross-origin request rejected" });
27009
26959
  }
@@ -27024,7 +26974,7 @@ var require_guard = __commonJS({
27024
26974
  return send(200, sup.accessKeyStatus());
27025
26975
  }
27026
26976
  if (req.method === "POST" && pathname === "/settings/access-key") {
27027
- if (!originAllowed(req, sup.config.apiPort)) {
26977
+ if (!originAllowed(req)) {
27028
26978
  req.resume();
27029
26979
  return send(403, { ok: false, error: "cross-origin request rejected" });
27030
26980
  }
@@ -27046,7 +26996,7 @@ var require_guard = __commonJS({
27046
26996
  return send(200, sup.closeActionStatus());
27047
26997
  }
27048
26998
  if (req.method === "POST" && pathname === "/settings/close-action") {
27049
- if (!originAllowed(req, sup.config.apiPort)) {
26999
+ if (!originAllowed(req)) {
27050
27000
  req.resume();
27051
27001
  return send(403, {});
27052
27002
  }
@@ -27067,7 +27017,7 @@ var require_guard = __commonJS({
27067
27017
  return send(200, sup.externalBrowserStatus());
27068
27018
  }
27069
27019
  if (req.method === "POST" && pathname === "/settings/external-browser") {
27070
- if (!originAllowed(req, sup.config.apiPort)) {
27020
+ if (!originAllowed(req)) {
27071
27021
  req.resume();
27072
27022
  return send(403, {});
27073
27023
  }
@@ -27089,7 +27039,7 @@ var require_guard = __commonJS({
27089
27039
  return;
27090
27040
  }
27091
27041
  if (req.method === "POST" && pathname === "/shutdown") {
27092
- if (!originAllowed(req, sup.config.apiPort)) {
27042
+ if (!originAllowed(req)) {
27093
27043
  req.resume();
27094
27044
  return send(403, {});
27095
27045
  }
@@ -27104,7 +27054,7 @@ var require_guard = __commonJS({
27104
27054
  return;
27105
27055
  }
27106
27056
  if (req.method === "GET" && pathname === "/self-update/status") {
27107
- if (!originAllowed(req, sup.config.apiPort)) {
27057
+ if (!originAllowed(req)) {
27108
27058
  req.resume();
27109
27059
  return send(403, {});
27110
27060
  }
@@ -27130,14 +27080,14 @@ var require_guard = __commonJS({
27130
27080
  });
27131
27081
  }
27132
27082
  if (req.method === "GET" && pathname === "/env/dsh") {
27133
- if (!originAllowed(req, sup.config.apiPort)) {
27083
+ if (!originAllowed(req)) {
27134
27084
  req.resume();
27135
27085
  return send(403, {});
27136
27086
  }
27137
27087
  return send(200, sup.dshenvStatus());
27138
27088
  }
27139
27089
  if (req.method === "GET" && pathname === "/env/status") {
27140
- if (!originAllowed(req, sup.config.apiPort)) {
27090
+ if (!originAllowed(req)) {
27141
27091
  req.resume();
27142
27092
  return send(403, {});
27143
27093
  }
@@ -27147,7 +27097,7 @@ var require_guard = __commonJS({
27147
27097
  return sup.nodeLtsStatus().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27148
27098
  }
27149
27099
  if (req.method === "GET" && pathname === "/env/environment") {
27150
- if (!originAllowed(req, sup.config.apiPort)) {
27100
+ if (!originAllowed(req)) {
27151
27101
  req.resume();
27152
27102
  return send(403, { ok: false, error: "cross-origin request rejected" });
27153
27103
  }
@@ -27155,7 +27105,7 @@ var require_guard = __commonJS({
27155
27105
  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) }));
27156
27106
  }
27157
27107
  if (req.method === "GET" && pathname === "/env/environment/last") {
27158
- if (!originAllowed(req, sup.config.apiPort)) {
27108
+ if (!originAllowed(req)) {
27159
27109
  req.resume();
27160
27110
  return send(403, { ok: false, error: "cross-origin request rejected" });
27161
27111
  }
@@ -27166,7 +27116,7 @@ var require_guard = __commonJS({
27166
27116
  req.resume();
27167
27117
  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" });
27168
27118
  }
27169
- if (!originAllowed(req, sup.config.apiPort)) {
27119
+ if (!originAllowed(req)) {
27170
27120
  req.resume();
27171
27121
  return send(403, { ok: false, error: "cross-origin request rejected" });
27172
27122
  }
@@ -27211,7 +27161,7 @@ var require_router4 = __commonJS({
27211
27161
  return Promise.resolve(sup.routerStatusView()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, running: false, error: e && e.message || String(e) }));
27212
27162
  }
27213
27163
  if (req.method === "POST" && pathname.startsWith("/router/")) {
27214
- if (!originAllowed(req, sup.config.apiPort)) {
27164
+ if (!originAllowed(req)) {
27215
27165
  req.resume();
27216
27166
  return send(403, { ok: false, error: "cross-origin request rejected" });
27217
27167
  }
@@ -27228,7 +27178,7 @@ var require_router4 = __commonJS({
27228
27178
  return Promise.resolve(sup.routerProviders()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27229
27179
  }
27230
27180
  if (req.method === "POST" && pathname === "/router/providers/add") {
27231
- if (!originAllowed(req, sup.config.apiPort)) {
27181
+ if (!originAllowed(req)) {
27232
27182
  req.resume();
27233
27183
  return send(403, {});
27234
27184
  }
@@ -27247,7 +27197,7 @@ var require_router4 = __commonJS({
27247
27197
  return;
27248
27198
  }
27249
27199
  if (req.method === "POST" && pathname === "/router/proxy/login/start") {
27250
- if (!originAllowed(req, sup.config.apiPort)) {
27200
+ if (!originAllowed(req)) {
27251
27201
  req.resume();
27252
27202
  return send(403, {});
27253
27203
  }
@@ -27255,7 +27205,7 @@ var require_router4 = __commonJS({
27255
27205
  return;
27256
27206
  }
27257
27207
  if (req.method === "POST" && pathname === "/router/proxy/login/wait") {
27258
- if (!originAllowed(req, sup.config.apiPort)) {
27208
+ if (!originAllowed(req)) {
27259
27209
  req.resume();
27260
27210
  return send(403, {});
27261
27211
  }
@@ -27272,7 +27222,7 @@ var require_router4 = __commonJS({
27272
27222
  return;
27273
27223
  }
27274
27224
  if (req.method === "POST" && pathname === "/router/proxy/update/check") {
27275
- if (!originAllowed(req, sup.config.apiPort)) {
27225
+ if (!originAllowed(req)) {
27276
27226
  req.resume();
27277
27227
  return send(403, {});
27278
27228
  }
@@ -27280,7 +27230,7 @@ var require_router4 = __commonJS({
27280
27230
  return;
27281
27231
  }
27282
27232
  if (req.method === "POST" && pathname === "/router/proxy/update/apply") {
27283
- if (!originAllowed(req, sup.config.apiPort)) {
27233
+ if (!originAllowed(req)) {
27284
27234
  req.resume();
27285
27235
  return send(403, {});
27286
27236
  }
@@ -27299,7 +27249,7 @@ var require_router4 = __commonJS({
27299
27249
  return Promise.resolve(sup.routerApi().proxyUpdateStatus(u.searchParams.get("appId") || "")).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27300
27250
  }
27301
27251
  if (req.method === "POST" && pathname === "/router/providers/proxy/key") {
27302
- if (!originAllowed(req, sup.config.apiPort)) {
27252
+ if (!originAllowed(req)) {
27303
27253
  req.resume();
27304
27254
  return send(403, {});
27305
27255
  }
@@ -27314,7 +27264,7 @@ var require_router4 = __commonJS({
27314
27264
  return;
27315
27265
  }
27316
27266
  if (req.method === "POST" && pathname === "/router/providers/proxy/select") {
27317
- if (!originAllowed(req, sup.config.apiPort)) {
27267
+ if (!originAllowed(req)) {
27318
27268
  req.resume();
27319
27269
  return send(403, {});
27320
27270
  }
@@ -27329,7 +27279,7 @@ var require_router4 = __commonJS({
27329
27279
  return;
27330
27280
  }
27331
27281
  if (req.method === "POST" && pathname === "/router/providers/proxy/key/remove") {
27332
- if (!originAllowed(req, sup.config.apiPort)) {
27282
+ if (!originAllowed(req)) {
27333
27283
  req.resume();
27334
27284
  return send(403, {});
27335
27285
  }
@@ -27344,7 +27294,7 @@ var require_router4 = __commonJS({
27344
27294
  return;
27345
27295
  }
27346
27296
  if (req.method === "POST" && pathname === "/router/providers/keys/set") {
27347
- if (!originAllowed(req, sup.config.apiPort)) {
27297
+ if (!originAllowed(req)) {
27348
27298
  req.resume();
27349
27299
  return send(403, {});
27350
27300
  }
@@ -27360,7 +27310,7 @@ var require_router4 = __commonJS({
27360
27310
  return;
27361
27311
  }
27362
27312
  if (req.method === "POST" && pathname === "/router/providers/remove") {
27363
- if (!originAllowed(req, sup.config.apiPort)) {
27313
+ if (!originAllowed(req)) {
27364
27314
  req.resume();
27365
27315
  return send(403, {});
27366
27316
  }
@@ -27375,7 +27325,7 @@ var require_router4 = __commonJS({
27375
27325
  return;
27376
27326
  }
27377
27327
  if (req.method === "POST" && pathname === "/router/providers/key/use") {
27378
- if (!originAllowed(req, sup.config.apiPort)) {
27328
+ if (!originAllowed(req)) {
27379
27329
  req.resume();
27380
27330
  return send(403, {});
27381
27331
  }
@@ -27391,7 +27341,7 @@ var require_router4 = __commonJS({
27391
27341
  return;
27392
27342
  }
27393
27343
  if (req.method === "POST" && pathname === "/router/providers/account/discard") {
27394
- if (!originAllowed(req, sup.config.apiPort)) {
27344
+ if (!originAllowed(req)) {
27395
27345
  req.resume();
27396
27346
  return send(403, {});
27397
27347
  }
@@ -27406,7 +27356,7 @@ var require_router4 = __commonJS({
27406
27356
  return;
27407
27357
  }
27408
27358
  if (req.method === "POST" && pathname === "/router/providers/refresh") {
27409
- if (!originAllowed(req, sup.config.apiPort)) {
27359
+ if (!originAllowed(req)) {
27410
27360
  req.resume();
27411
27361
  return send(403, {});
27412
27362
  }
@@ -27422,7 +27372,7 @@ var require_router4 = __commonJS({
27422
27372
  return;
27423
27373
  }
27424
27374
  if (req.method === "POST" && pathname === "/router/providers/activate") {
27425
- if (!originAllowed(req, sup.config.apiPort)) {
27375
+ if (!originAllowed(req)) {
27426
27376
  req.resume();
27427
27377
  return send(403, {});
27428
27378
  }
@@ -27438,7 +27388,7 @@ var require_router4 = __commonJS({
27438
27388
  return;
27439
27389
  }
27440
27390
  if (req.method === "POST" && pathname === "/router/providers/deactivate") {
27441
- if (!originAllowed(req, sup.config.apiPort)) {
27391
+ if (!originAllowed(req)) {
27442
27392
  req.resume();
27443
27393
  return send(403, {});
27444
27394
  }
@@ -27488,7 +27438,7 @@ var require_plugins = __commonJS({
27488
27438
  return send(200, sup.pluginManager.installStatus(u.searchParams.get("job")));
27489
27439
  }
27490
27440
  if (req.method === "POST" && pathname.startsWith("/plugins/")) {
27491
- if (!originAllowed(req, sup.config.apiPort)) {
27441
+ if (!originAllowed(req)) {
27492
27442
  req.resume();
27493
27443
  return send(403, { ok: false, error: "cross-origin request rejected" });
27494
27444
  }
@@ -27564,7 +27514,7 @@ var require_dist = __commonJS({
27564
27514
  return;
27565
27515
  }
27566
27516
  if (req.method === "POST" && pathname === "/dist/registry/set") {
27567
- if (!originAllowed(req, sup.config.apiPort)) {
27517
+ if (!originAllowed(req)) {
27568
27518
  req.resume();
27569
27519
  return send(403, {});
27570
27520
  }
@@ -27583,7 +27533,7 @@ var require_dist = __commonJS({
27583
27533
  return;
27584
27534
  }
27585
27535
  if (req.method === "POST" && pathname === "/dist/registry/refresh") {
27586
- if (!originAllowed(req, sup.config.apiPort)) {
27536
+ if (!originAllowed(req)) {
27587
27537
  req.resume();
27588
27538
  return send(403, {});
27589
27539
  }
@@ -27591,7 +27541,7 @@ var require_dist = __commonJS({
27591
27541
  return;
27592
27542
  }
27593
27543
  if (req.method === "POST" && pathname === "/dist/registry/probe") {
27594
- if (!originAllowed(req, sup.config.apiPort)) {
27544
+ if (!originAllowed(req)) {
27595
27545
  req.resume();
27596
27546
  return send(403, {});
27597
27547
  }
@@ -27662,6 +27612,112 @@ var require_exchange = __commonJS({
27662
27612
  }
27663
27613
  });
27664
27614
 
27615
+ // src/platform/security/identity.js
27616
+ var require_identity2 = __commonJS({
27617
+ "src/platform/security/identity.js"(exports2, module2) {
27618
+ "use strict";
27619
+ var { normalizeRemoteAddress, isLoopbackAddress } = require_ip();
27620
+ function socketIsLoopback(req) {
27621
+ return isLoopbackAddress(req && req.socket && req.socket.remoteAddress);
27622
+ }
27623
+ function identify(req) {
27624
+ return {
27625
+ remote: normalizeRemoteAddress(req && req.socket && req.socket.remoteAddress),
27626
+ loopback: socketIsLoopback(req)
27627
+ };
27628
+ }
27629
+ module2.exports = { identify };
27630
+ }
27631
+ });
27632
+
27633
+ // src/api/identity.js
27634
+ var require_identity3 = __commonJS({
27635
+ "src/api/identity.js"(exports2, module2) {
27636
+ "use strict";
27637
+ var { normalizeRemoteAddress, isLoopbackAddress, isPrivateIpv4 } = require_ip();
27638
+ var { identify } = require_identity2();
27639
+ module2.exports = {
27640
+ identify,
27641
+ normalizeRemoteAddress,
27642
+ isPrivateIpv4,
27643
+ isLoopbackAddress
27644
+ };
27645
+ }
27646
+ });
27647
+
27648
+ // src/api/security.js
27649
+ var require_security = __commonJS({
27650
+ "src/api/security.js"(exports2, module2) {
27651
+ "use strict";
27652
+ var crypto = require("node:crypto");
27653
+ var { isPrivateIpv4 } = require_identity3();
27654
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
27655
+ function isLocalOrLanHost(h) {
27656
+ if (!h) return false;
27657
+ const s = String(h).toLowerCase();
27658
+ if (LOOPBACK_HOSTS.has(s)) return true;
27659
+ const bare = s.startsWith("[") && s.endsWith("]") ? s.slice(1, -1) : s;
27660
+ if (bare === "::1") return true;
27661
+ return isPrivateIpv4(bare);
27662
+ }
27663
+ function isShellOrigin(protocol, hostname) {
27664
+ const h = String(hostname || "").toLowerCase();
27665
+ if (protocol === "tauri:") return h === "localhost" || /(^|\.)tauri\.localhost$/.test(h);
27666
+ if (protocol === "http:" || protocol === "https:") return h === "tauri.localhost";
27667
+ return false;
27668
+ }
27669
+ function normalizeHostname(h) {
27670
+ const s = String(h || "").toLowerCase();
27671
+ return s.startsWith("[") && s.endsWith("]") ? s.slice(1, -1) : s;
27672
+ }
27673
+ function originAllowed(req, apiPort) {
27674
+ const host = req.headers.host;
27675
+ if (!host) return false;
27676
+ let hostname = "";
27677
+ {
27678
+ const m = /^(\[[^\]]+\]|[^:]+)(?::\d+)?$/.exec(String(host).trim());
27679
+ hostname = m ? m[1] : String(host).trim();
27680
+ if (!isLocalOrLanHost(hostname)) return false;
27681
+ }
27682
+ const o = req.headers.origin;
27683
+ if (!o) return true;
27684
+ try {
27685
+ const u = new URL(o);
27686
+ if (isShellOrigin(u.protocol, u.hostname)) return true;
27687
+ if (!isLocalOrLanHost(u.hostname)) return false;
27688
+ if (host && normalizeHostname(u.hostname) !== normalizeHostname(hostname)) return false;
27689
+ const port = u.port === "" ? u.protocol === "https:" ? "443" : "80" : u.port;
27690
+ return port === String(apiPort);
27691
+ } catch {
27692
+ return false;
27693
+ }
27694
+ }
27695
+ function safeKeyEqual(a, b) {
27696
+ const ha = crypto.createHash("sha256").update(String(a || "")).digest();
27697
+ const hb = crypto.createHash("sha256").update(String(b || "")).digest();
27698
+ return crypto.timingSafeEqual(ha, hb);
27699
+ }
27700
+ function requestHasAccessKey(req, key) {
27701
+ if (!key) return true;
27702
+ const ah = req.headers.authorization;
27703
+ if (typeof ah === "string" && ah.startsWith("Bearer ") && safeKeyEqual(ah.slice(7), key)) return true;
27704
+ try {
27705
+ const q = new URL(req.url, "http://localhost").searchParams.get("access_key");
27706
+ if (q && safeKeyEqual(q, key)) return true;
27707
+ } catch {
27708
+ }
27709
+ return false;
27710
+ }
27711
+ function liveApiPort(server, sup) {
27712
+ const live = server && typeof server.address === "function" ? server.address() : null;
27713
+ if (live && Number.isInteger(live.port) && live.port > 0) return live.port;
27714
+ const cfg = sup && sup.config && sup.config.apiPort;
27715
+ return Number.isInteger(cfg) ? cfg : null;
27716
+ }
27717
+ module2.exports = { originAllowed, isLocalOrLanHost, isShellOrigin, requestHasAccessKey, liveApiPort };
27718
+ }
27719
+ });
27720
+
27665
27721
  // src/api/domains/instances.js
27666
27722
  var require_instances = __commonJS({
27667
27723
  "src/api/domains/instances.js"(exports2, module2) {
@@ -27669,6 +27725,7 @@ var require_instances = __commonJS({
27669
27725
  var execPath = require_exec_path();
27670
27726
  var crypto = require("node:crypto");
27671
27727
  var { bootstrapDshCookie } = require_exchange();
27728
+ var { liveApiPort } = require_security();
27672
27729
  var OPEN_WEB_CODES = /* @__PURE__ */ new Map();
27673
27730
  var OPEN_WEB_CODE_TTL_MS = 3e4;
27674
27731
  function _sweepExpiredOpenWebCodes() {
@@ -27702,16 +27759,16 @@ var require_instances = __commonJS({
27702
27759
  return (sup.instances.list() || []).find((x) => x.id === id) || null;
27703
27760
  }
27704
27761
  function handleOpen(ctx) {
27705
- const { sup, req, res, identity, originAllowed, tokOf } = ctx;
27762
+ const { sup, req, res, identity, originAllowed, tokOf, server } = ctx;
27706
27763
  const url = new URL(req.url, "http://localhost");
27707
- const apiPort = sup.config.apiPort;
27764
+ const apiPort = liveApiPort(server, sup);
27708
27765
  const code = url.searchParams.get("code");
27709
27766
  const deny = (status, msg) => {
27710
27767
  res.writeHead(status, { "Content-Type": "text/plain; charset=utf-8" });
27711
27768
  res.end(msg);
27712
27769
  };
27713
27770
  if (!identity.loopback) return deny(403, "\u4EC5\u5141\u8BB8\u672C\u673A\u8BBF\u95EE");
27714
- if (!originAllowed(req, apiPort)) return deny(403, "origin not allowed");
27771
+ if (!originAllowed(req)) return deny(403, "origin not allowed");
27715
27772
  const rec = consumeOpenWebCode(code);
27716
27773
  if (!rec) return deny(code ? 400 : 404, code ? "\u6388\u6743\u7801\u65E0\u6548\u6216\u5DF2\u8FC7\u671F" : "\u7F3A\u5C11\u6388\u6743\u7801");
27717
27774
  const it = findInstanceOrMain(sup, rec.id);
@@ -27777,7 +27834,7 @@ var require_instances = __commonJS({
27777
27834
  return sharedErr + "\uFF1B" + FORMS + "\uFF1B\u9700\u8981\u5176\u5B83\u53EF\u6267\u884C\u8BF7\u8D70\u63D2\u4EF6\u5B89\u88C5\u901A\u9053";
27778
27835
  }
27779
27836
  function handle(ctx) {
27780
- const { sup, req, res, pathname, identity, send, collectBody, originAllowed, tokOf, browser } = ctx;
27837
+ const { sup, req, res, pathname, identity, send, collectBody, originAllowed, tokOf, browser, server } = ctx;
27781
27838
  function openInSystemBrowser(url) {
27782
27839
  return browser.openBrowser(url, { logger: sup.logger });
27783
27840
  }
@@ -27804,7 +27861,7 @@ var require_instances = __commonJS({
27804
27861
  return render();
27805
27862
  }
27806
27863
  if (req.method === "POST" && pathname.startsWith("/instances/")) {
27807
- if (!originAllowed(req, sup.config.apiPort)) {
27864
+ if (!originAllowed(req)) {
27808
27865
  req.resume();
27809
27866
  return send(403, {});
27810
27867
  }
@@ -27841,7 +27898,7 @@ var require_instances = __commonJS({
27841
27898
  if (!it) return send(404, { ok: false, error: "\u5B9E\u4F8B\u4E0D\u5B58\u5728" });
27842
27899
  if (!(Number(it.port) > 0)) return send(400, { ok: false, error: "\u975E\u6CD5\u7AEF\u53E3" });
27843
27900
  const code = issueOpenWebCode(j.id);
27844
- const url = "http://127.0.0.1:" + sup.config.apiPort + "/open?code=" + code;
27901
+ const url = "http://127.0.0.1:" + (liveApiPort(server, sup) || sup.config.apiPort) + "/open?code=" + code;
27845
27902
  return Promise.resolve(openInSystemBrowser(url)).then((r) => {
27846
27903
  if (!r.ok) dropOpenWebCode(code);
27847
27904
  return send(r.ok ? 200 : 500, r);
@@ -27886,7 +27943,7 @@ var require_relay = __commonJS({
27886
27943
  return Promise.resolve(sup.listLan()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27887
27944
  }
27888
27945
  if (req.method === "POST" && pathname.startsWith("/remote/")) {
27889
- if (!originAllowed(req, sup.config.apiPort)) {
27946
+ if (!originAllowed(req)) {
27890
27947
  req.resume();
27891
27948
  return send(403, {});
27892
27949
  }
@@ -27949,7 +28006,7 @@ var require_shell2 = __commonJS({
27949
28006
  }
27950
28007
  }
27951
28008
  if (req.method === "POST" && pathname === "/shell/health") {
27952
- if (!originAllowed(req, sup.config.apiPort)) {
28009
+ if (!originAllowed(req)) {
27953
28010
  req.resume();
27954
28011
  return send(403, {});
27955
28012
  }
@@ -27966,28 +28023,8 @@ var require_shell2 = __commonJS({
27966
28023
  }
27967
28024
  });
27968
28025
  }
27969
- if (req.method === "POST" && pathname === "/shell/update-pending") {
27970
- if (!originAllowed(req, sup.config.apiPort)) {
27971
- req.resume();
27972
- return send(403, {});
27973
- }
27974
- return collectBody(req, res, 8192, (body) => {
27975
- let j = {};
27976
- try {
27977
- j = body ? JSON.parse(body) : {};
27978
- } catch {
27979
- }
27980
- try {
27981
- const rec = shell.markPending(j.from || null, j.to || null);
27982
- if (sup.events) sup.events.append(BRAND2.EVENT_SHELL_UPDATE_PENDING, { from: rec.from, to: rec.to });
27983
- return send(200, { ok: true, journal: rec });
27984
- } catch (e) {
27985
- return send(500, { ok: false, error: e.message });
27986
- }
27987
- });
27988
- }
27989
28026
  if (req.method === "POST" && pathname === "/shell/check-update") {
27990
- if (!originAllowed(req, sup.config.apiPort)) {
28027
+ if (!originAllowed(req)) {
27991
28028
  req.resume();
27992
28029
  return send(403, {});
27993
28030
  }
@@ -27999,23 +28036,6 @@ var require_shell2 = __commonJS({
27999
28036
  return send(200, r);
28000
28037
  }).catch((e) => send(500, { ok: false, error: e.message }));
28001
28038
  }
28002
- if (req.method === "POST" && pathname === "/shell/restart") {
28003
- if (!originAllowed(req, sup.config.apiPort)) {
28004
- req.resume();
28005
- return send(403, {});
28006
- }
28007
- if (typeof sup._sessionHalting === "function" && sup._sessionHalting()) {
28008
- req.resume();
28009
- return send(409, { ok: false, error: "\u4F1A\u8BDD\u5DF2\u9000\u51FA/\u9000\u51FA\u4E2D\uFF0C\u62D2\u7EDD\u91CD\u542F\u684C\u9762\u58F3" });
28010
- }
28011
- req.resume();
28012
- return Promise.resolve(shell.restartShell({
28013
- shouldAbort: () => typeof sup._sessionHalting === "function" && sup._sessionHalting()
28014
- })).then((r) => {
28015
- if (sup.events) sup.events.append(BRAND2.EVENT_SHELL_RESTART_REQUESTED, { ok: r.ok, killed: r.killed || [], pid: r.pid || null });
28016
- return send(r.ok ? 200 : 500, r);
28017
- }).catch((e) => send(500, { ok: false, error: e.message }));
28018
- }
28019
28039
  if (req.method === "GET" || req.method === "POST") return send(404, { error: "not found", path: pathname });
28020
28040
  return send(405, { error: "method not allowed" });
28021
28041
  }
@@ -28179,106 +28199,6 @@ var require_static = __commonJS({
28179
28199
  }
28180
28200
  });
28181
28201
 
28182
- // src/platform/security/identity.js
28183
- var require_identity2 = __commonJS({
28184
- "src/platform/security/identity.js"(exports2, module2) {
28185
- "use strict";
28186
- var { normalizeRemoteAddress, isLoopbackAddress } = require_ip();
28187
- function socketIsLoopback(req) {
28188
- return isLoopbackAddress(req && req.socket && req.socket.remoteAddress);
28189
- }
28190
- function identify(req) {
28191
- return {
28192
- remote: normalizeRemoteAddress(req && req.socket && req.socket.remoteAddress),
28193
- loopback: socketIsLoopback(req)
28194
- };
28195
- }
28196
- module2.exports = { identify };
28197
- }
28198
- });
28199
-
28200
- // src/api/identity.js
28201
- var require_identity3 = __commonJS({
28202
- "src/api/identity.js"(exports2, module2) {
28203
- "use strict";
28204
- var { normalizeRemoteAddress, isLoopbackAddress, isPrivateIpv4 } = require_ip();
28205
- var { identify } = require_identity2();
28206
- module2.exports = {
28207
- identify,
28208
- normalizeRemoteAddress,
28209
- isPrivateIpv4,
28210
- isLoopbackAddress
28211
- };
28212
- }
28213
- });
28214
-
28215
- // src/api/security.js
28216
- var require_security = __commonJS({
28217
- "src/api/security.js"(exports2, module2) {
28218
- "use strict";
28219
- var crypto = require("node:crypto");
28220
- var { isPrivateIpv4 } = require_identity3();
28221
- var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
28222
- function isLocalOrLanHost(h) {
28223
- if (!h) return false;
28224
- const s = String(h).toLowerCase();
28225
- if (LOOPBACK_HOSTS.has(s)) return true;
28226
- const bare = s.startsWith("[") && s.endsWith("]") ? s.slice(1, -1) : s;
28227
- if (bare === "::1") return true;
28228
- return isPrivateIpv4(bare);
28229
- }
28230
- function isShellOrigin(protocol, hostname) {
28231
- const h = String(hostname || "").toLowerCase();
28232
- if (protocol === "tauri:") return h === "localhost" || /(^|\.)tauri\.localhost$/.test(h);
28233
- if (protocol === "http:" || protocol === "https:") return h === "tauri.localhost";
28234
- return false;
28235
- }
28236
- function normalizeHostname(h) {
28237
- const s = String(h || "").toLowerCase();
28238
- return s.startsWith("[") && s.endsWith("]") ? s.slice(1, -1) : s;
28239
- }
28240
- function originAllowed(req, apiPort) {
28241
- const host = req.headers.host;
28242
- if (!host) return false;
28243
- let hostname = "";
28244
- {
28245
- const m = /^(\[[^\]]+\]|[^:]+)(?::\d+)?$/.exec(String(host).trim());
28246
- hostname = m ? m[1] : String(host).trim();
28247
- if (!isLocalOrLanHost(hostname)) return false;
28248
- }
28249
- const o = req.headers.origin;
28250
- if (!o) return true;
28251
- try {
28252
- const u = new URL(o);
28253
- if (isShellOrigin(u.protocol, u.hostname)) return true;
28254
- if (!isLocalOrLanHost(u.hostname)) return false;
28255
- if (host && normalizeHostname(u.hostname) !== normalizeHostname(hostname)) return false;
28256
- const port = u.port === "" ? u.protocol === "https:" ? "443" : "80" : u.port;
28257
- return port === String(apiPort);
28258
- } catch {
28259
- return false;
28260
- }
28261
- }
28262
- function safeKeyEqual(a, b) {
28263
- const ha = crypto.createHash("sha256").update(String(a || "")).digest();
28264
- const hb = crypto.createHash("sha256").update(String(b || "")).digest();
28265
- return crypto.timingSafeEqual(ha, hb);
28266
- }
28267
- function requestHasAccessKey(req, key) {
28268
- if (!key) return true;
28269
- const ah = req.headers.authorization;
28270
- if (typeof ah === "string" && ah.startsWith("Bearer ") && safeKeyEqual(ah.slice(7), key)) return true;
28271
- try {
28272
- const q = new URL(req.url, "http://localhost").searchParams.get("access_key");
28273
- if (q && safeKeyEqual(q, key)) return true;
28274
- } catch {
28275
- }
28276
- return false;
28277
- }
28278
- module2.exports = { originAllowed, isLocalOrLanHost, isShellOrigin, requestHasAccessKey };
28279
- }
28280
- });
28281
-
28282
28202
  // src/api/transport/server.js
28283
28203
  var require_server = __commonJS({
28284
28204
  "src/api/transport/server.js"(exports2, module2) {
@@ -28288,7 +28208,7 @@ var require_server = __commonJS({
28288
28208
  var { collectBody } = require_body();
28289
28209
  var { serveStatic } = require_static();
28290
28210
  var { identify } = require_identity3();
28291
- var { originAllowed, requestHasAccessKey, isShellOrigin } = require_security();
28211
+ var { originAllowed, requestHasAccessKey, isShellOrigin, liveApiPort } = require_security();
28292
28212
  var browserExit = require_browser();
28293
28213
  var environmentExit = require_environment();
28294
28214
  function safeFail(res, err, where) {
@@ -28311,7 +28231,7 @@ var require_server = __commonJS({
28311
28231
  function createServer(sup, deps) {
28312
28232
  const browser = deps && deps.browser || browserExit;
28313
28233
  const environment = deps && deps.environment || environmentExit;
28314
- return http2.createServer((req, res) => {
28234
+ const server = http2.createServer((req, res) => {
28315
28235
  const shellOrigin = (() => {
28316
28236
  const o = req.headers.origin;
28317
28237
  if (!o) return null;
@@ -28341,6 +28261,7 @@ var require_server = __commonJS({
28341
28261
  return "";
28342
28262
  };
28343
28263
  const identity = identify(req);
28264
+ const originAllowedLive = (req2) => originAllowed(req2, liveApiPort(server, sup));
28344
28265
  const accessKey = sup && sup.config && sup.config.apiAccessKey || null;
28345
28266
  if (!identity.loopback && req.method !== "OPTIONS" && (!accessKey || !requestHasAccessKey(req, accessKey))) {
28346
28267
  res.writeHead(401, { "Content-Type": "application/json" });
@@ -28378,7 +28299,7 @@ var require_server = __commonJS({
28378
28299
  } catch (e) {
28379
28300
  return send(400, { error: "bad request encoding" });
28380
28301
  }
28381
- const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed, tokOf, browser, environment };
28302
+ const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed: originAllowedLive, tokOf, browser, environment, server };
28382
28303
  for (const d of API_DOMAINS) {
28383
28304
  if (d.owns(pathname)) {
28384
28305
  try {
@@ -28404,6 +28325,7 @@ var require_server = __commonJS({
28404
28325
  }
28405
28326
  return send(405, { error: "method not allowed" });
28406
28327
  });
28328
+ return server;
28407
28329
  }
28408
28330
  module2.exports = { createServer };
28409
28331
  }
@@ -28423,7 +28345,7 @@ var require_api = __commonJS({
28423
28345
  var require_session = __commonJS({
28424
28346
  "src/domains/relay/session.js"(exports2, module2) {
28425
28347
  "use strict";
28426
- var { cookieByName } = require_core4();
28348
+ var { cookieByName } = require_core3();
28427
28349
  var { bootstrapDshCookie } = require_exchange();
28428
28350
  function createSession(opts) {
28429
28351
  const o = opts || {};
@@ -28565,7 +28487,7 @@ var require_tunnel = __commonJS({
28565
28487
  "src/domains/relay/tunnel.js"(exports2, module2) {
28566
28488
  "use strict";
28567
28489
  var net = require("node:net");
28568
- var { isTrustedSource, hasValidToken, upstreamPath } = require_core4();
28490
+ var { isTrustedSource, hasValidToken, upstreamPath } = require_core3();
28569
28491
  function buildRawRequest(req, authority, cookie) {
28570
28492
  let raw = req.method + " " + upstreamPath(req.url) + " HTTP/1.1\r\n";
28571
28493
  for (const [k, v] of Object.entries(req.headers)) {
@@ -28651,7 +28573,7 @@ var require_proxy2 = __commonJS({
28651
28573
  "use strict";
28652
28574
  var http2 = require("node:http");
28653
28575
  var crypto = require("node:crypto");
28654
- var { isTrustedSource, tokenGateDecision, backoffGate, upstreamPath, redactLogPath, normalizeRemoteMode, POLYFILL_SCRIPT } = require_core4();
28576
+ var { isTrustedSource, tokenGateDecision, backoffGate, upstreamPath, redactLogPath, normalizeRemoteMode, POLYFILL_SCRIPT } = require_core3();
28655
28577
  var { createSession } = require_session();
28656
28578
  var { createTunnelHandler } = require_tunnel();
28657
28579
  var HTML_INJECT_MAX_BYTES = 2 * 1024 * 1024;
@@ -28676,7 +28598,7 @@ var require_proxy2 = __commonJS({
28676
28598
  }
28677
28599
  };
28678
28600
  }
28679
- function pipeWithHold(ur, res, clientReqPath, logger) {
28601
+ function pipeWithHold(ur, res, clientReqLog, logger) {
28680
28602
  let clientGone = false;
28681
28603
  const log = (lv, msg) => {
28682
28604
  if (logger && logger[lv]) {
@@ -28725,7 +28647,7 @@ var require_proxy2 = __commonJS({
28725
28647
  return headers;
28726
28648
  }
28727
28649
  function handleUpstream(ur, res, clientReqPath, onStatus, logger) {
28728
- const clientReqLog2 = redactLogPath(clientReqPath);
28650
+ const clientReqLog = redactLogPath(clientReqPath);
28729
28651
  if (onStatus) onStatus(ur.statusCode);
28730
28652
  const h = { ...ur.headers };
28731
28653
  const isHtml = String(h["content-type"] || "").includes("text/html");
@@ -28737,7 +28659,7 @@ var require_proxy2 = __commonJS({
28737
28659
  }
28738
28660
  res.writeHead(ur.statusCode || 502, h);
28739
28661
  if (!isHtml) {
28740
- pipeWithHold(ur, res, clientReqPath, logger);
28662
+ pipeWithHold(ur, res, clientReqLog, logger);
28741
28663
  return;
28742
28664
  }
28743
28665
  const chunks = [];
@@ -28771,7 +28693,7 @@ var require_proxy2 = __commonJS({
28771
28693
  chunks.push(c);
28772
28694
  return;
28773
28695
  }
28774
- log("HTML \u8D85\u4E0A\u9650 " + HTML_INJECT_MAX_BYTES + "B\uFF08\u5DF2\u6536 " + total + "B\uFF09\uFF0C\u653E\u5F03 polyfill \u6CE8\u5165\u6309\u6D41\u900F\u4F20 " + (clientReqLog2 || ""));
28696
+ log("HTML \u8D85\u4E0A\u9650 " + HTML_INJECT_MAX_BYTES + "B\uFF08\u5DF2\u6536 " + total + "B\uFF09\uFF0C\u653E\u5F03 polyfill \u6CE8\u5165\u6309\u6D41\u900F\u4F20 " + (clientReqLog || ""));
28775
28697
  const selfForward = !ur.readableEnded;
28776
28698
  switchToPassThrough();
28777
28699
  if (selfForward) res.write(c);
@@ -28951,14 +28873,8 @@ var require_ports3 = __commonJS({
28951
28873
  return registry.claimSlot(segment, owner, {
28952
28874
  preferred: o.preferred,
28953
28875
  bindingPreferred: !!o.bindingPreferred,
28954
- // RL-4:跨账本防撞。router caller 不传 reservedPorts(分配器跳过该检查,行为不变);
28955
- // 仅 relay 传入 router 账本已占端口,free-scan/preferred-claim 跳过 ⇒ 不会抢 router 的端口。
28956
28876
  reservedPorts: reservedPorts || void 0,
28957
28877
  onBindingLost: o.onBindingLost,
28958
- // 审计 RL-3:reclaimCmdMark 必须出现在 relay daemon 的真实命令行里,否则 pgrepList 永远匹配不到 ⇒ 回收是死代码。
28959
- // relay daemon 经 runtime.js 以 `node .../domains/relay/daemon.js -c <cfg>` 拉起(scripts.js:8),
28960
- // 命令行含 `domains/relay/daemon.js`(与 process.js#reclaimOrphans 用的 _cmdMarks 同源),故此处用同一标记。
28961
- // configPath 为空时 fail-closed 不回收:宁可留占用走冲突分支,也不按 cmdMark 误杀同名进程(reclaimByCmdMark 内部也已要求 cfgStr 必填)。
28962
28878
  reclaimCmdMark: "domains/relay/daemon.js",
28963
28879
  reclaimCfg: o.configPath || "",
28964
28880
  waitMs: 8e3
@@ -29157,7 +29073,7 @@ var require_frp = __commonJS({
29157
29073
  var path2 = require("node:path");
29158
29074
  var spawnOS = require_spawn();
29159
29075
  var { writeAtomic } = require_fs();
29160
- var { buildFrpcToml, validateFrpServerSettings } = require_core4();
29076
+ var { buildFrpcToml, validateFrpServerSettings } = require_core3();
29161
29077
  var { frpPlatformTag, download, installFrpc } = require_frp_install();
29162
29078
  var FrpManager = class {
29163
29079
  constructor(opts) {
@@ -29601,7 +29517,7 @@ var require_ops5 = __commonJS({
29601
29517
  var managed = require_managed();
29602
29518
  var portsvc = require_ports3();
29603
29519
  var { FrpManager } = require_frp();
29604
- var { normalizeRemoteMode, validateWanAccess, normalizeFrpSettings, validateFrpServerSettings, projectRemoteView } = require_core4();
29520
+ var { normalizeRemoteMode, validateWanAccess, normalizeFrpSettings, validateFrpServerSettings, projectRemoteView } = require_core3();
29605
29521
  var reconcile = require_reconcile();
29606
29522
  var lanServers = require_lan_servers();
29607
29523
  function applyRelayToken(host, existing, want) {