@lob-ox/core-darwin-x64 0.0.8 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core.cjs CHANGED
@@ -998,8 +998,6 @@ var require_brand = __commonJS({
998
998
  var CLI_SHIM_NAMES = ["lobox", "lobox.cmd", "lobox.ps1"];
999
999
  var GUI_BIN_NAMES = ["lobox-shell", "lobox-shell.exe"];
1000
1000
  var PROC_MATCH_GUARD = SHARED.proc.matchGuard;
1001
- var PROC_MATCH_GUI = "lobox-shell";
1002
- var PROC_MATCH_GUI_RE = "lobox-shell(\\.exe)?";
1003
1001
  var BRIDGE_MSG_KERNEL_UPDATE_REQUEST = SHARED.bridge.msgKernelUpdateRequest;
1004
1002
  var BRIDGE_MSG_KERNEL_UPDATE_RESULT = SHARED.bridge.msgKernelUpdateResult;
1005
1003
  var BRIDGE_MSG_KERNEL_UPDATE_PROGRESS = SHARED.bridge.msgKernelUpdateProgress;
@@ -1016,9 +1014,7 @@ var require_brand = __commonJS({
1016
1014
  var EVENT_HARNESS_REMOTE_TOKEN_CHANGED = "harness_remote_token_changed";
1017
1015
  var EVENT_LAN_HARNESS_TOKEN_UPDATED = "lan_harness_token_updated";
1018
1016
  var EVENT_UPGRADE_STOPPING_HARNESS = "upgrade_stopping_harness";
1019
- var EVENT_SHELL_UPDATE_PENDING = "shell_update_pending";
1020
1017
  var EVENT_SHELL_UPDATE_CHECKED = "shell_update_checked";
1021
- var EVENT_SHELL_RESTART_REQUESTED = "shell_restart_requested";
1022
1018
  var SYSTEMD_TEMPLATE_ASIDE_SUFFIX = ".disabled-by-lobox-";
1023
1019
  function corePackageName(tag) {
1024
1020
  return NPM_SCOPE + "/" + CORE_PKG_PREFIX + tag;
@@ -1120,8 +1116,6 @@ var require_brand = __commonJS({
1120
1116
  CLI_SHIM_NAMES,
1121
1117
  GUI_BIN_NAMES,
1122
1118
  PROC_MATCH_GUARD,
1123
- PROC_MATCH_GUI,
1124
- PROC_MATCH_GUI_RE,
1125
1119
  BRIDGE_MSG_KERNEL_UPDATE_REQUEST,
1126
1120
  BRIDGE_MSG_KERNEL_UPDATE_RESULT,
1127
1121
  BRIDGE_MSG_KERNEL_UPDATE_PROGRESS,
@@ -1138,9 +1132,7 @@ var require_brand = __commonJS({
1138
1132
  EVENT_HARNESS_REMOTE_TOKEN_CHANGED,
1139
1133
  EVENT_LAN_HARNESS_TOKEN_UPDATED,
1140
1134
  EVENT_UPGRADE_STOPPING_HARNESS,
1141
- EVENT_SHELL_UPDATE_PENDING,
1142
1135
  EVENT_SHELL_UPDATE_CHECKED,
1143
- EVENT_SHELL_RESTART_REQUESTED,
1144
1136
  SYSTEMD_TEMPLATE_ASIDE_SUFFIX,
1145
1137
  corePackageName,
1146
1138
  stateRoot: stateRoot2,
@@ -1312,14 +1304,11 @@ var require_config = __commonJS({
1312
1304
  };
1313
1305
  }
1314
1306
  var BASE_DEFAULTS = {
1315
- // 守卫监控节拍(进程存活只由 child 的 exit/close 事件与平台 pidlookup 判定,见 app/main/*)。
1316
1307
  tickIntervalMs: 5e3,
1317
- // 启动窗口(秒):spawn 后 startsecs 内退出 ⇒ 记一次「启动失败」;活过 startsecs 后退出 ⇒ 正常重启,不计失败。
1318
1308
  startsecs: 10,
1319
- // 唯一一条限流:startupFailWindowMs 内「启动失败」达 startupFailBurst 次 ⇒ phase=FAILED,停止自动重启,等人工重试。
1320
- // 不变量:startupFailWindowMs > startupFailBurst × startsecs×1000(60s > 5×10s);同一规则按域参数化,实例域见 domains/instance/state-machine.js。
1321
1309
  startupFailWindowMs: 6e4,
1322
1310
  startupFailBurst: 5,
1311
+ restart: { windowMs: 6e4, burst: 5, baseDelayMs: 2e3, maxDelayMs: 6e4 },
1323
1312
  stopGraceMs: 1e4,
1324
1313
  portReleaseWaitMs: 1e4,
1325
1314
  apiHost: "127.0.0.1",
@@ -1347,7 +1336,6 @@ var require_config = __commonJS({
1347
1336
  initialCheckDelayMs: 2e4,
1348
1337
  upgradeTimeoutMs: 6e5,
1349
1338
  installCommandTemplate: ["npm", "install", "-g", "{pkg}@{version}"],
1350
- // apiAccessKey:非回环须带 Authorization: Bearer <key> 或 ?access_key=<key>,回环豁免;不配置则 LAN 受 RFC1918 约束、FRP 仍强制 remoteToken。
1351
1339
  apiAccessKey: null,
1352
1340
  closeAction: "hide",
1353
1341
  externalBrowser: null
@@ -1483,7 +1471,7 @@ var require_version = __commonJS({
1483
1471
  var fs2 = require("node:fs");
1484
1472
  var path2 = require("node:path");
1485
1473
  function guardVersion() {
1486
- if (true) return String("0.0.8");
1474
+ if (true) return String("0.0.11");
1487
1475
  try {
1488
1476
  return JSON.parse(fs2.readFileSync(path2.join(__dirname, "..", "..", "package.json"), "utf8")).version || "unknown";
1489
1477
  } catch {
@@ -1822,28 +1810,38 @@ var require_field_tables = __commonJS({
1822
1810
  var require_phase = __commonJS({
1823
1811
  "src/app/state/phase.js"(exports2, module2) {
1824
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
+ }
1825
1835
  function legacyToEntryPhase(ph) {
1826
- return {
1827
- STOPPED: "stopped",
1828
- STARTING: "starting",
1829
- RUNNING: "running",
1830
- FAILED: "failed",
1831
- OBSERVED: "stopped",
1832
- RESTARTING: "starting",
1833
- BACKOFF: "failed"
1834
- }[ph] || "stopped";
1836
+ return LEGACY_TO_ENTRY[ph] || "stopped";
1835
1837
  }
1836
- function entryToLegacyPhase(ph) {
1837
- return {
1838
- stopped: "STOPPED",
1839
- starting: "STARTING",
1840
- running: "RUNNING",
1841
- failed: "FAILED",
1842
- restarting: "STARTING",
1843
- backoff: "FAILED"
1844
- }[ph] || "STOPPED";
1838
+ function isKnownEntry(ph) {
1839
+ return Object.prototype.hasOwnProperty.call(ENTRY_TO_LEGACY, ph);
1840
+ }
1841
+ function isKnownLegacy(ph) {
1842
+ return Object.prototype.hasOwnProperty.call(LEGACY_TO_ENTRY, ph);
1845
1843
  }
1846
- module2.exports = { legacyToEntryPhase, entryToLegacyPhase };
1844
+ module2.exports = { entryToLegacyPhase, legacyToEntryPhase, isKnownEntry, isKnownLegacy };
1847
1845
  }
1848
1846
  });
1849
1847
 
@@ -2290,7 +2288,6 @@ var require_decide = __commonJS({
2290
2288
  return {
2291
2289
  phase,
2292
2290
  desired: d.state().desired(),
2293
- // 端口视角(不是存活判据):只喂孤儿接管/占用分支。
2294
2291
  portUp: d.portUp() === true,
2295
2292
  childAlive: childAlive(d.mChild()),
2296
2293
  adoptedAlive: adoptedAlive(d.mAdoptPid()),
@@ -2302,7 +2299,6 @@ var require_decide = __commonJS({
2302
2299
  manualRestart: d.manualRestart() === true,
2303
2300
  spawnBlocked: !!(d.mSpawnBlockedUntil() && now < d.mSpawnBlockedUntil()),
2304
2301
  startDeadlinePassed: startDeadlinePassed(startDeadline, now),
2305
- // STARTING 且没有存活进程/没记启动窗口 = 已判定重启,等端口释放落点。
2306
2302
  restartInFlight: phase === "STARTING" && startDeadline === null,
2307
2303
  restartDue: d.mRestartAt() === null || now >= d.mRestartAt(),
2308
2304
  crashHalted: d.crashHalted() === true,
@@ -4138,13 +4134,6 @@ var require_alloc = __commonJS({
4138
4134
  this._releaseAlloc();
4139
4135
  }
4140
4136
  }
4141
- /**
4142
- * Lease I1:把"检查"与"置位"放在**同一个同步块**内。
4143
- *
4144
- * 修前:`while (r._allocLock) { await sleep(10); } r._allocLock = true;`
4145
- * —— 检查与置位之间隔着 await,两个并发 claimSlot 可同时观察到 false 并都置 true(真 TOCTOU)。
4146
- * 现在用 Lease.acquireSync 做同步 test-and-set;未取得则由调用方退避重试。
4147
- */
4148
4137
  async _acquireAlloc() {
4149
4138
  const r = this._registry;
4150
4139
  let waited = 0;
@@ -4171,7 +4160,6 @@ var require_alloc = __commonJS({
4171
4160
  }
4172
4161
  }
4173
4162
  }
4174
- // 跨进程分配锁 best-effort:获取超时返回 null 并继续(fail-open 有界,登记后复检是第二道防线)。
4175
4163
  async _acquireXLock() {
4176
4164
  const f = String(this._registry._file || "") + ".alloc.lock";
4177
4165
  const deadline = Date.now() + XLOCK_TIMEOUT_MS;
@@ -5047,320 +5035,138 @@ var require_policies = __commonJS({
5047
5035
  }
5048
5036
  });
5049
5037
 
5050
- // src/domains/shell/core.js
5051
- var require_core3 = __commonJS({
5052
- "src/domains/shell/core.js"(exports2, module2) {
5053
- "use strict";
5054
- var BRAND2 = require_brand();
5055
- var DEFAULTS = {
5056
- enabled: true,
5057
- intervalMs: 2e4,
5058
- graceMs: 9e4,
5059
- updateGraceMs: 3e5,
5060
- maxRestarts: 5,
5061
- windowMs: 18e5,
5062
- phaseMaxAgeMs: 6e5,
5063
- // 壳进程判据的**唯一来源**是跨语言单源(brand.js#PROC_MATCH_GUI,与 brand.rs 同名同值):
5064
- // 此处再写一份字面量 = 改名漏一处 ⇒ pgrep 找不到壳(watchdog 反复拉起/双实例),或杀错进程。
5065
- procPattern: BRAND2.PROC_MATCH_GUI
5066
- };
5067
- var HEADLESS_FLAGS = Object.freeze([
5068
- "--shell-update-plan",
5069
- "--core-plan",
5070
- "--node-plan",
5071
- "--mirror-plan",
5072
- "--env-plan",
5073
- "--service-plan",
5074
- "--platform-matrix",
5075
- "--run-guard",
5076
- "--watchdog"
5077
- ]);
5078
- var HEADLESS_RE = new RegExp(HEADLESS_FLAGS.join("|"));
5079
- var SHELL_PROC_RE = new RegExp(BRAND2.PROC_MATCH_GUI_RE);
5080
- function isShellProcess(proc) {
5081
- const c = String(proc && proc.cmdline || "");
5082
- if (HEADLESS_RE.test(c)) return false;
5083
- return SHELL_PROC_RE.test(c);
5084
- }
5085
- function decide(i) {
5086
- const c = i.config || {};
5087
- if (i.alive > 0) return { action: "alive", reason: "\u58F3\u5728\u8FD0\u884C" };
5088
- if (i.absentForMs === null || i.absentForMs === void 0) {
5089
- return { action: "record", reason: "\u9996\u6B21\u89C2\u5BDF\u5230\u58F3\u7F3A\u5931\uFF0C\u5F00\u59CB\u8BA1\u65F6" };
5090
- }
5091
- const needMs = i.expectedAbsence ? c.updateGraceMs || DEFAULTS.updateGraceMs : c.graceMs || DEFAULTS.graceMs;
5092
- if (i.absentForMs < needMs) {
5093
- 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 };
5094
- }
5095
- if (!i.sessionAvailable) {
5096
- return { action: "skip", reason: "\u65E0\u56FE\u5F62\u4F1A\u8BDD\uFF08\u6CE8\u9500/\u7EAF\u7EC8\u7AEF\uFF09\uFF0C\u62C9\u8D77 GUI \u5FC5\u5931\u8D25" };
5097
- }
5098
- if ((i.restartsInWindow || 0) >= (c.maxRestarts || DEFAULTS.maxRestarts)) {
5099
- 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" };
5100
- }
5101
- if (!i.hasExe) {
5102
- return { action: "skip", reason: "\u65E0\u6CD5\u5B9A\u4F4D\u58F3\u53EF\u6267\u884C\u6587\u4EF6\uFF08identity.json \u672A\u8BB0\u5F55 exe\uFF09" };
5103
- }
5104
- return { action: "restart", reason: "\u58F3\u7F3A\u5931\u4E14\u5DF2\u8FC7\u5BBD\u9650\u671F", needMs };
5105
- }
5106
- function isUpdatePhase(phase) {
5107
- const p = String(phase || "");
5108
- return p === "restarting" || p.indexOf("shell-update") === 0;
5109
- }
5110
- function exeFromCmdline(cmdline) {
5111
- const s = String(cmdline || "").trim();
5112
- if (!s) return null;
5113
- if (s.startsWith('"')) {
5114
- const end = s.indexOf('"', 1);
5115
- return end > 1 ? s.slice(1, end) : null;
5116
- }
5117
- return s.split(/\s+/)[0] || null;
5118
- }
5119
- function deriveState(id, journal) {
5120
- const j = journal || {};
5121
- if (!j.to) return { state: "idle", reason: "\u65E0\u8FDB\u884C\u4E2D\u7684\u66F4\u65B0", journal: j, identity: id };
5122
- const cur = id && id.version ? String(id.version) : null;
5123
- if (cur && cur === j.to && id && id.phase === "ready") {
5124
- const next = j.confirmed === true ? j : Object.assign({}, j, { confirmed: true });
5125
- return { state: "confirmed", version: cur, reason: "\u58F3\u5DF2\u5065\u5EB7\u8FD0\u884C\u65B0\u7248\u672C", journal: next, identity: id };
5126
- }
5127
- return {
5128
- state: "pending",
5129
- target: j.to,
5130
- current: cur,
5131
- reason: cur === j.to ? "\u7B49\u5F85\u58F3\u4E0A\u62A5\u5C31\u7EEA" : "\u7B49\u5F85\u58F3\u91CD\u542F\u5230\u65B0\u7248\u672C",
5132
- journal: j,
5133
- identity: id
5134
- };
5135
- }
5136
- module2.exports = { DEFAULTS, isShellProcess, decide, isUpdatePhase, exeFromCmdline, deriveState, HEADLESS_FLAGS };
5137
- }
5138
- });
5139
-
5140
- // src/domains/shell/watchdog.js
5141
- var require_watchdog = __commonJS({
5142
- "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) {
5143
5041
  "use strict";
5144
- var { DEFAULTS, decide, isShellProcess, isUpdatePhase } = require_core3();
5145
- function createShellWatchdog(deps) {
5146
- const o = deps || {};
5147
- const shell = o.shell;
5148
- const pidlookup = o.pidlookup;
5149
- const desktop = o.desktop;
5150
- const logger = o.logger || console;
5151
- const events = o.events || null;
5152
- const config = o.config || {};
5153
- const now = o.now || (() => Date.now());
5154
- const procPattern = config.shellProcPattern || DEFAULTS.procPattern;
5155
- let missingSince = null;
5156
- let restarts = [];
5157
- let busy = false;
5158
- let lastSkipReason = null;
5159
- let everSawAlive = false;
5160
- let expectedSince = null;
5161
- let phaseStale = false;
5162
- let phaseStaleWarned = false;
5163
- let journalStale = false;
5164
- let journalStaleWarned = false;
5165
- const log = (m) => {
5166
- try {
5167
- logger.info && logger.info("[shell-watchdog] " + m);
5168
- } catch {
5169
- }
5170
- };
5171
- const warn = (m) => {
5172
- try {
5173
- logger.warn && logger.warn("[shell-watchdog] " + m);
5174
- } catch {
5175
- }
5176
- };
5177
- function shellProcs() {
5178
- let procs = [];
5179
- try {
5180
- procs = pidlookup.pgrepList(procPattern) || [];
5181
- } catch {
5182
- procs = [];
5183
- }
5184
- 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;
5185
5053
  }
5186
- function updatePhaseTracking(t) {
5187
- let phase = "";
5188
- try {
5189
- const id = shell.identity();
5190
- phase = String(id && id.phase || "");
5191
- } catch {
5192
- }
5193
- const inUpdate = isUpdatePhase(phase);
5194
- if (!inUpdate) {
5195
- expectedSince = null;
5196
- phaseStale = false;
5197
- phaseStaleWarned = false;
5198
- return;
5199
- }
5200
- if (expectedSince === null) expectedSince = t;
5201
- const maxAge = config.shellWatchdogPhaseMaxAgeMs || DEFAULTS.phaseMaxAgeMs;
5202
- phaseStale = t - expectedSince >= maxAge;
5203
- if (phaseStale && !phaseStaleWarned) {
5204
- phaseStaleWarned = true;
5205
- 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");
5206
- }
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;
5207
5070
  }
5208
- function updateJournalTracking(t) {
5209
- let j = null;
5210
- try {
5211
- j = shell.readJournal && shell.readJournal();
5212
- } catch {
5213
- }
5214
- if (!j || !j.to || j.confirmed) {
5215
- journalStale = false;
5216
- journalStaleWarned = false;
5217
- return;
5218
- }
5219
- const t0 = Date.parse(String(j.startedAt || ""));
5220
- if (!Number.isFinite(t0)) {
5221
- journalStale = false;
5222
- return;
5223
- }
5224
- const maxAge = config.shellWatchdogPhaseMaxAgeMs || DEFAULTS.phaseMaxAgeMs;
5225
- journalStale = t - t0 >= maxAge;
5226
- if (journalStale && !journalStaleWarned) {
5227
- journalStaleWarned = true;
5228
- 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");
5229
- }
5071
+ unregister(name) {
5072
+ this._beats.delete(name);
5073
+ return this;
5230
5074
  }
5231
- function expectedAbsence() {
5232
- let phase = "";
5233
- try {
5234
- const id = shell.identity();
5235
- phase = String(id && id.phase || "");
5236
- } catch {
5237
- }
5238
- const inUpdate = isUpdatePhase(phase);
5239
- if (inUpdate && !phaseStale) return true;
5240
- try {
5241
- const j = shell.readJournal && shell.readJournal();
5242
- if (j && j.to && !j.confirmed) return !journalStale;
5243
- } catch {
5244
- }
5245
- return false;
5075
+ has(name) {
5076
+ return this._beats.has(name);
5246
5077
  }
5247
- function exePath() {
5248
- if (config.shellExePath) return config.shellExePath;
5078
+ async withLock(fn) {
5079
+ if (this._ticking || this._stopping) return { __skipped: "busy" };
5080
+ this._ticking = true;
5249
5081
  try {
5250
- const id = shell.identity();
5251
- return id && id.exe || null;
5252
- } catch {
5253
- return null;
5082
+ return await fn();
5083
+ } finally {
5084
+ this._ticking = false;
5254
5085
  }
5255
5086
  }
5256
- async function tick() {
5257
- if (config.shellWatchdog === false) return { skipped: "disabled" };
5258
- if (busy) return { skipped: "busy" };
5259
- 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;
5260
5093
  try {
5261
- const t = now();
5262
- const procs = shellProcs();
5263
- const alive = procs.length;
5264
- 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;
5265
5106
  try {
5266
- o.onShellAlive();
5267
- } catch {
5107
+ p = Promise.resolve(b.fn());
5108
+ } catch (e) {
5109
+ errors.push(name + ":" + (e && e.message));
5110
+ continue;
5268
5111
  }
5269
- }
5270
- if (typeof o.halted === "function" && o.halted()) {
5271
- lastSkipReason = "\u4F1A\u8BDD\u9000\u51FA\u4E2D/\u7528\u6237\u5DF2\u9000\u51FA\uFF08\u4E0D\u62C9\u8D77\uFF09";
5272
- return { skipped: "halted", reason: lastSkipReason };
5273
- }
5274
- if (alive > 0 && !everSawAlive) {
5275
- everSawAlive = true;
5276
- log("\u5DF2\u89C2\u6D4B\u5230\u684C\u9762\u58F3\u5728\u8FD0\u884C\uFF08pid=" + procs[0].pid + "\uFF09");
5277
- }
5278
- const absentForMs = alive > 0 ? null : missingSince === null ? null : t - missingSince;
5279
- updatePhaseTracking(t);
5280
- updateJournalTracking(t);
5281
- const expected = absentForMs === null ? false : expectedAbsence();
5282
- const exe = exePath();
5283
- restarts = restarts.filter((x) => t - x < (config.shellWatchdogWindowMs || DEFAULTS.windowMs));
5284
- const d = decide({
5285
- alive,
5286
- absentForMs,
5287
- expectedAbsence: expected,
5288
- sessionAvailable: desktop.sessionAvailable(),
5289
- restartsInWindow: restarts.length,
5290
- hasExe: !!exe,
5291
- config: {
5292
- graceMs: config.shellWatchdogGraceMs || DEFAULTS.graceMs,
5293
- updateGraceMs: config.shellWatchdogUpdateGraceMs || DEFAULTS.updateGraceMs,
5294
- 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
+ })]);
5295
5117
  }
5296
- });
5297
- if (d.action === "alive") {
5298
- missingSince = null;
5299
- lastSkipReason = null;
5300
- return { alive };
5301
- }
5302
- if (d.action === "record") {
5303
- missingSince = t;
5304
- log("\u684C\u9762\u58F3\u7F3A\u5931\uFF0C\u5F00\u59CB\u8BA1\u65F6\uFF08\u5BBD\u9650 " + Math.round((config.shellWatchdogGraceMs || DEFAULTS.graceMs) / 1e3) + "s\uFF09");
5305
- return { absent: true };
5306
- }
5307
- if (d.action === "wait") {
5308
- lastSkipReason = d.reason + "\uFF08" + Math.round(absentForMs / 1e3) + "s/" + Math.round(d.needMs / 1e3) + "s\uFF09";
5309
- return { waiting: d.reason };
5310
- }
5311
- if (d.action === "skip") {
5312
- if (lastSkipReason !== d.reason) {
5313
- lastSkipReason = d.reason;
5314
- 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));
5315
5129
  }
5316
- return { skipped: d.reason };
5317
- }
5318
- restarts.push(t);
5319
- const r = await shell.restartShell({
5320
- exePath: exe,
5321
- procPattern,
5322
- shouldAbort: typeof o.halted === "function" ? () => o.halted() : void 0
5323
- });
5324
- if (r && r.ok) {
5325
- if (events) events.append("shell_watchdog_restart", { pid: r.pid, exe: r.exe, absentMs: absentForMs });
5326
- log("\u684C\u9762\u58F3\u7F3A\u5931 " + Math.round(absentForMs / 1e3) + "s\uFF0C\u5DF2\u62C9\u8D77 pid=" + r.pid + " exe=" + r.exe);
5327
- missingSince = null;
5328
- } else {
5329
- if (events) events.append("shell_watchdog_restart_failed", { error: r && r.error || "\u672A\u77E5", absentMs: absentForMs });
5330
- warn("\u62C9\u8D77\u684C\u9762\u58F3\u5931\u8D25\uFF1A" + (r && r.error || "\u672A\u77E5"));
5331
5130
  }
5332
- return { restarted: !!(r && r.ok), error: r && r.error || null };
5333
- } catch (e) {
5334
- warn("\u76D1\u63A7\u5F02\u5E38\uFF1A" + (e && e.message || e));
5335
- return { error: e && e.message || String(e) };
5336
5131
  } finally {
5337
- 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
+ }
5338
5140
  }
5141
+ return { done, errors };
5339
5142
  }
5340
- function status() {
5341
- const t = now();
5342
- let session = { available: null, reason: null };
5343
- try {
5344
- session = desktop.describe();
5345
- } 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;
5346
5159
  }
5347
- return {
5348
- enabled: config.shellWatchdog !== false,
5349
- intervalMs: config.shellWatchdogIntervalMs || DEFAULTS.intervalMs,
5350
- graceMs: config.shellWatchdogGraceMs || DEFAULTS.graceMs,
5351
- updateGraceMs: config.shellWatchdogUpdateGraceMs || DEFAULTS.updateGraceMs,
5352
- maxRestarts: config.shellWatchdogMaxRestarts || DEFAULTS.maxRestarts,
5353
- absentForMs: missingSince === null ? null : t - missingSince,
5354
- restartsInWindow: restarts.filter((x) => t - x < (config.shellWatchdogWindowMs || DEFAULTS.windowMs)).length,
5355
- everSawAlive,
5356
- lastSkipReason,
5357
- session,
5358
- expectedAbsence: expectedAbsence()
5359
- };
5360
5160
  }
5361
- return { tick, status, intervalMs: config.shellWatchdogIntervalMs || DEFAULTS.intervalMs };
5362
- }
5363
- 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 };
5364
5170
  }
5365
5171
  });
5366
5172
 
@@ -5465,7 +5271,6 @@ var require_capability_profile = __commonJS({
5465
5271
  "use strict";
5466
5272
  var linux = {
5467
5273
  sandboxLaunch: true,
5468
- // 服务管理器一律走产品自身的进程管理(不借 systemd)⇒ 无 cgroup 强制,如实为 supervise。
5469
5274
  sandboxEnforcement: "supervise",
5470
5275
  pidAdoption: true,
5471
5276
  processTreeKill: true,
@@ -7092,7 +6897,7 @@ var require_portable = __commonJS({
7092
6897
  if (!pid || !Array.isArray(anchors) || !anchors.length) return false;
7093
6898
  const cmd2 = pidlookup.readCmdline(pid);
7094
6899
  if (!cmd2) return false;
7095
- return anchors.some((a) => a && cmd2.indexOf(String(a)) !== -1);
6900
+ return anchors.every((a) => a && cmd2.indexOf(String(a)) !== -1);
7096
6901
  }
7097
6902
  var portOf = (o) => {
7098
6903
  const p = Number(o && o.port);
@@ -7135,15 +6940,6 @@ var require_portable = __commonJS({
7135
6940
  kind: "portable",
7136
6941
  supportsUnits: false,
7137
6942
  supportsTransient: true,
7138
- /**
7139
- * 能力协商(根因 B 修法;与 router 域 `supports(cap)` 同一范式,下沉到平台层)。
7140
- *
7141
- * 判据来源:capability-profile.js 的 sandboxEnforcement —— 三平台均为 'supervise'
7142
- * (= 产品自身监控,**无 cgroup 级强制**)。此前该字段全仓 0 处查询,
7143
- * 于是 governor 在"限额根本无法生效"的前提下仍判违规并重启 ⇒ 永动机(P0)。
7144
- *
7145
- * 调用方(governor)必须先问后处置:不支持限额 ⇒ 只观测,绝不重启。
7146
- */
7147
6943
  supports(cap) {
7148
6944
  if (cap === "treeKill") return true;
7149
6945
  if (cap === "limits") return false;
@@ -7185,11 +6981,6 @@ var require_portable = __commonJS({
7185
6981
  }
7186
6982
  return true;
7187
6983
  },
7188
- // 审计 P0-I5:原实现在 Windows 上用 Atomics.wait(nap)**同步冻结事件循环**做轮询、且 SIGKILL 兜底分支
7189
- // 调用的 taskkill 完全异步不 await ⇒ 停止"无法确认"(常返回 false),进而实例仍被标 RUNNING、
7190
- // upgrade.js 在上个进程仍占端口时继续重装 ⇒ 与端口预检撞车回滚(正是 upgrade.js 想防的故障)。
7191
- // 修复:改为 async,用 real-timer 轮询(不阻塞事件循环),并等待 taskkill 真正结束(有界 10s,与 process.js killTree 同),
7192
- // 给足停止窗口再判定;仍超时再报 false(如实,不再假成功)。timeoutMs 仍被 STOP_WAIT_CAP_MS 截断(与调用方意图无关)。
7193
6984
  async stopUnit(unit, o) {
7194
6985
  const opts = o || {};
7195
6986
  const ours = findOurs(opts);
@@ -7296,19 +7087,6 @@ var require_portable = __commonJS({
7296
7087
  }
7297
7088
  return false;
7298
7089
  },
7299
- /**
7300
- * 单元是否活跃 —— 返回 Outcome 三态(根因 A 修法)。
7301
- *
7302
- * 此前返回 null 表示"未知",而调用方 instance/ops.js:67 用 `active !== false` 判,
7303
- * 于是 null(未知)被当成"仍活跃" ⇒ removeInstance 把未知报成删除成功(P0)。
7304
- *
7305
- * 关键修正:**无 anchors 时不得把"有人在监听"当成"我们的实例在跑"**。
7306
- * 端口是共享事实、不是身份(与 monitor.js#matchesAnchors 同一条判据);
7307
- * 缺锚点即无从验证身份 ⇒ 如实返回 unknown,由调用方决定(升级校验须失败并回滚)。
7308
- *
7309
- * 兼容:既有调用方若直接判真假,Outcome 对象恒为真值,行为同"true",不会静默反转;
7310
- * 但显式判据(isOk/isFail/isUnknown)才能区分三态——见 shared/outcome.js。
7311
- */
7312
7090
  isUnitActive(unit, o) {
7313
7091
  const opts = o || {};
7314
7092
  if (findOurs(opts)) return OUTCOME.OK;
@@ -7332,7 +7110,6 @@ var require_portable = __commonJS({
7332
7110
  transientUnitFile() {
7333
7111
  return null;
7334
7112
  },
7335
- // 审计 P0-I5:随 stopUnit 改为 async,cleanTransient 也须 await(不得再用 === false 同步判)。
7336
7113
  async cleanTransient(unit, o) {
7337
7114
  const opts = o || {};
7338
7115
  const errors = [];
@@ -7371,7 +7148,6 @@ var require_service = __commonJS({
7371
7148
  kind: UNSUPPORTED_KIND,
7372
7149
  supportsUnits: false,
7373
7150
  supportsTransient: false,
7374
- // 能力协商:与 portable 同类,未知平台一律不支持(保持方法集一致)。
7375
7151
  supports() {
7376
7152
  return false;
7377
7153
  },
@@ -8295,6 +8071,122 @@ var require_os = __commonJS({
8295
8071
  }
8296
8072
  });
8297
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
+
8298
8190
  // src/app/assembly/bootstrap.js
8299
8191
  var require_bootstrap = __commonJS({
8300
8192
  "src/app/assembly/bootstrap.js"(exports2, module2) {
@@ -8305,11 +8197,20 @@ var require_bootstrap = __commonJS({
8305
8197
  var path2 = require("node:path");
8306
8198
  var fs2 = require("node:fs");
8307
8199
  var ports = require_ports().shared;
8308
- var { createShellWatchdog } = require_watchdog();
8309
8200
  var pidlook = require_pidlookup();
8201
+ var { BeatScheduler } = require_synchrony();
8310
8202
  var platform = require_os();
8311
8203
  var ENV_FORM_STARTUP_DELAY_MS = 3e3;
8312
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
+ }
8313
8214
  try {
8314
8215
  host.events.append("guard_started", {
8315
8216
  pid: process.pid,
@@ -8330,39 +8231,55 @@ var require_bootstrap = __commonJS({
8330
8231
  routerAutostart: host.config.routerAutostart === true,
8331
8232
  routerMode: null,
8332
8233
  updateCheck: null,
8333
- shellWatchdog: null,
8334
8234
  lastRefresh: null
8335
8235
  };
8336
- host.tick();
8337
- host._timer = host.managedObjects ? null : setInterval(() => host.tick(), host.config.tickIntervalMs);
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);
8256
+ }
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;
8338
8280
  host._lastHeartbeatAt = Date.now();
8339
8281
  host._heartbeatStalls = 0;
8340
8282
  host._heartbeatBeat = host._heartbeatBeat || 0;
8341
- const heartbeatIv = host.config.tickIntervalMs || 5e3;
8342
- host._heartbeatTimer = setInterval(() => {
8343
- if (host._heartbeatBusy) return;
8344
- host._heartbeatBusy = true;
8345
- const iv = heartbeatIv;
8346
- const beat = ++host._heartbeatBeat;
8347
- host._lastHeartbeatAt = Date.now();
8348
- const objCount = host.managedObjects && typeof host.managedObjects.count === "function" ? host.managedObjects.count() : 1;
8349
- const stallMs = Math.max(3e4, iv * 12, objCount * 6 * iv + iv);
8350
- const guard = setTimeout(() => {
8351
- if (host._heartbeatBusy && beat === host._heartbeatBeat) {
8352
- host._heartbeatBusy = false;
8353
- host._heartbeatStalls++;
8354
- if (host.logger && host.logger.warn) {
8355
- 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");
8356
- }
8357
- }
8358
- }, stallMs);
8359
- if (guard && typeof guard.unref === "function") guard.unref();
8360
- Promise.resolve(host.managedObjects ? host.managedObjects.heartbeat(iv) : null).catch(() => {
8361
- }).finally(() => {
8362
- clearTimeout(guard);
8363
- if (beat === host._heartbeatBeat) host._heartbeatBusy = false;
8364
- });
8365
- }, heartbeatIv);
8366
8283
  if (host.lanDaemonEnabled()) {
8367
8284
  host._syncLanState();
8368
8285
  const lrt = host._ensureLanRuntime(true);
@@ -8372,7 +8289,6 @@ var require_bootstrap = __commonJS({
8372
8289
  });
8373
8290
  host.lan.syncFrpc();
8374
8291
  }
8375
- if (!host.managedObjects) host.instances.startTimer(host.config.tickIntervalMs || 5e3);
8376
8292
  if (!host.lanDaemonEnabled()) {
8377
8293
  for (const inst of host.instances.all()) {
8378
8294
  if (inst.remoteMode === "lan" || inst.remoteMode === "wan") host.lan.syncProxy(inst).catch(() => {
@@ -8436,22 +8352,6 @@ var require_bootstrap = __commonJS({
8436
8352
  initialDelayMs: checkDelayMs,
8437
8353
  intervalMs: checkIntervalMs
8438
8354
  };
8439
- if (host.config.updateCheckEnabled !== false) {
8440
- host._initialCheckTimer = setTimeout(() => {
8441
- host.nativeManager.checkUpdate();
8442
- }, checkDelayMs);
8443
- host._upgradeTimer = setInterval(() => {
8444
- host.nativeManager.checkUpdate();
8445
- }, checkIntervalMs);
8446
- }
8447
- try {
8448
- host._startShellWatchdog();
8449
- } catch (e) {
8450
- if (host.logger && host.logger.warn) {
8451
- host.logger.warn("[shell-watchdog] \u542F\u52A8\u5F02\u5E38\uFF08\u4E0D\u5F71\u54CD\u5B88\u536B\u4E3B\u5FAA\u73AF\uFF09: " + (e && e.message || e));
8452
- }
8453
- }
8454
- host._startupFacts.shellWatchdog = !!host.shellWatchdog;
8455
8355
  const envTimer = setTimeout(() => {
8456
8356
  try {
8457
8357
  host._refreshEnvironmentForm();
@@ -8496,41 +8396,6 @@ var require_bootstrap = __commonJS({
8496
8396
  return null;
8497
8397
  });
8498
8398
  }
8499
- function _startShellWatchdog(host) {
8500
- if (host.config.shellWatchdog === false) {
8501
- host.logger.info && host.logger.info("[shell-watchdog] \u5DF2\u6309\u914D\u7F6E\u7981\u7528");
8502
- return;
8503
- }
8504
- try {
8505
- host.shellWatchdog = createShellWatchdog({
8506
- shell: host.shellDomain,
8507
- pidlookup: pidlook,
8508
- desktop: platform.desktop,
8509
- logger: host.logger,
8510
- events: host.events,
8511
- config: host.config,
8512
- halted: () => host._shellExitIntended(),
8513
- onShellAlive: () => {
8514
- if (!host._shellHalted) return;
8515
- if (host._stopping) return;
8516
- if (host._sessionHalting && host._sessionHalting()) return;
8517
- host._shellHalted = false;
8518
- try {
8519
- host.writeState(true);
8520
- } catch {
8521
- }
8522
- }
8523
- });
8524
- host._shellWatchdogTimer = setInterval(() => {
8525
- Promise.resolve(host.shellWatchdog.tick()).catch(() => {
8526
- });
8527
- }, host.shellWatchdog.intervalMs);
8528
- if (host._shellWatchdogTimer.unref) host._shellWatchdogTimer.unref();
8529
- host.logger.info && host.logger.info("[shell-watchdog] \u5DF2\u542F\u7528\uFF08\u5468\u671F " + Math.round(host.shellWatchdog.intervalMs / 1e3) + "s\uFF09");
8530
- } catch (e) {
8531
- host.logger.warn && host.logger.warn("[shell-watchdog] \u521D\u59CB\u5316\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u5B88\u536B\uFF09: " + (e && e.message || e));
8532
- }
8533
- }
8534
8399
  function _registerFixedPorts(host) {
8535
8400
  ports.register("dsh-main", host.config.targetPort);
8536
8401
  ports.registerSole("supervisor-api", host.config.apiPort);
@@ -8542,7 +8407,13 @@ var require_bootstrap = __commonJS({
8542
8407
  const isBare = policies.isBareCommand(cur);
8543
8408
  if (!isBare) return;
8544
8409
  const d = execPath.resolveDsh();
8545
- if (!d || !d.bin) return;
8410
+ if (!d || !d.bin) {
8411
+ try {
8412
+ host.logger.warn && host.logger.warn("\u539F\u751F DSH \u542F\u52A8\u671F\u7ED1\u5B9A\u8DF3\u8FC7\uFF1A\u672A\u89E3\u6790\u5230\u5165\u53E3\uFF08PATH \u53EF\u80FD\u5C1A\u672A\u767B\u8BB0\uFF09\uFF1Bspawn \u671F\u4F1A\u518D\u6B21\u89E3\u6790");
8413
+ } catch {
8414
+ }
8415
+ return;
8416
+ }
8546
8417
  host.config.command = d.isJs ? [d.runtime || process.execPath, d.bin, ...cmd2.slice(2)] : [d.bin, ...cmd2.slice(2)];
8547
8418
  try {
8548
8419
  host.events && host.events.append(BRAND2.EVENT_HARNESS_COMMAND_BOUND, { from: cur || null, to: host.config.command[1] });
@@ -8559,7 +8430,7 @@ var require_bootstrap = __commonJS({
8559
8430
  }
8560
8431
  }
8561
8432
  }
8562
- module2.exports = { _bootstrap, _startShellWatchdog, _registerFixedPorts, _bindNativeDshCommand, _refreshEnvironmentForm };
8433
+ module2.exports = { _bootstrap, _registerFixedPorts, _bindNativeDshCommand, _refreshEnvironmentForm };
8563
8434
  }
8564
8435
  });
8565
8436
 
@@ -8615,14 +8486,17 @@ var require_api_rebind = __commonJS({
8615
8486
  });
8616
8487
  server.listen(host.config.apiPort, host.config.apiHost, () => {
8617
8488
  bind._tries = 0;
8489
+ const bound = server.address();
8490
+ const realPort = bound && Number.isInteger(bound.port) ? bound.port : host.config.apiPort;
8618
8491
  host.api = server;
8492
+ if (realPort !== host.config.apiPort) host.config.apiPort = realPort;
8619
8493
  try {
8620
- portsShared.registerSole("supervisor-api", host.config.apiPort);
8494
+ portsShared.registerSole("supervisor-api", realPort);
8621
8495
  } catch (e) {
8622
8496
  host.logger.warn("ports.registerSole(supervisor-api) \u5931\u8D25: " + (e && e.message || e));
8623
8497
  }
8624
- host.events.append("api_listening", { host: host.config.apiHost, port: host.config.apiPort });
8625
- host.logger.info("api listening on " + host.config.apiHost + ":" + host.config.apiPort);
8498
+ host.events.append("api_listening", { host: host.config.apiHost, port: realPort });
8499
+ host.logger.info("api listening on " + host.config.apiHost + ":" + realPort);
8626
8500
  });
8627
8501
  };
8628
8502
  bind._tries = 0;
@@ -8644,19 +8518,21 @@ var require_api_rebind = __commonJS({
8644
8518
  host.logger.error("api error: " + (err ? err.message : String(err)));
8645
8519
  });
8646
8520
  server.listen(port, host.config.apiHost, () => {
8521
+ const bound = server.address();
8522
+ const realPort = bound && Number.isInteger(bound.port) ? bound.port : port;
8647
8523
  host.api = server;
8648
8524
  const prev = host.config.apiPort;
8649
- if (port !== prev) {
8650
- host.config.apiPort = port;
8651
- if (host.configPath) host.persistConfigPatch({ apiPort: port });
8525
+ if (realPort !== prev) {
8526
+ host.config.apiPort = realPort;
8527
+ if (host.configPath) host.persistConfigPatch({ apiPort: realPort });
8652
8528
  }
8653
8529
  try {
8654
- portsShared.registerSole("supervisor-api", port);
8530
+ portsShared.registerSole("supervisor-api", realPort);
8655
8531
  } catch (e) {
8656
8532
  host.logger.warn("ports.registerSole(supervisor-api) \u5931\u8D25: " + e.message);
8657
8533
  }
8658
- host.events.append("api_listening", { host: host.config.apiHost, port });
8659
- host.logger.info("api listening on " + host.config.apiHost + ":" + port);
8534
+ host.events.append("api_listening", { host: host.config.apiHost, port: realPort });
8535
+ host.logger.info("api listening on " + host.config.apiHost + ":" + realPort);
8660
8536
  });
8661
8537
  return server;
8662
8538
  };
@@ -10099,14 +9975,10 @@ var require_shutdown = __commonJS({
10099
9975
  host.events.append("guard_exit", {});
10100
9976
  host.logger.info("guard shutting down");
10101
9977
  host.writeState(true);
10102
- if (host._timer) clearInterval(host._timer);
10103
- if (host._heartbeatTimer) clearInterval(host._heartbeatTimer);
9978
+ if (host._beatScheduler) host._beatScheduler.stop();
10104
9979
  if (host._killTimer) clearTimeout(host._killTimer);
10105
9980
  if (host._adoptKillTimer) clearTimeout(host._adoptKillTimer);
10106
9981
  abortInflightNpm(host, "guard-shutdown");
10107
- if (host._initialCheckTimer) clearTimeout(host._initialCheckTimer);
10108
- if (host._upgradeTimer) clearInterval(host._upgradeTimer);
10109
- if (host._shellWatchdogTimer) clearInterval(host._shellWatchdogTimer);
10110
9982
  if (host.api) {
10111
9983
  try {
10112
9984
  host.api.close();
@@ -10151,37 +10023,10 @@ var require_shutdown = __commonJS({
10151
10023
  } catch (e) {
10152
10024
  host.logger.warn && host.logger.warn("shutdownAll persist shellHalted: " + e.message);
10153
10025
  }
10154
- if (host._shellWatchdogTimer) {
10155
- clearInterval(host._shellWatchdogTimer);
10156
- host._shellWatchdogTimer = null;
10157
- }
10158
- if (host._heartbeatTimer) {
10159
- clearInterval(host._heartbeatTimer);
10160
- host._heartbeatTimer = null;
10161
- }
10162
- if (host._timer) {
10163
- clearInterval(host._timer);
10164
- host._timer = null;
10165
- }
10166
- if (host._initialCheckTimer) {
10167
- clearTimeout(host._initialCheckTimer);
10168
- host._initialCheckTimer = null;
10169
- }
10170
- if (host._upgradeTimer) {
10171
- clearInterval(host._upgradeTimer);
10172
- host._upgradeTimer = null;
10173
- }
10174
- if (host._killTimer) {
10175
- clearTimeout(host._killTimer);
10176
- host._killTimer = null;
10177
- }
10178
- if (host._adoptKillTimer) {
10179
- clearTimeout(host._adoptKillTimer);
10180
- host._adoptKillTimer = null;
10181
- }
10182
10026
  host.logger.info("[session] \u9000\u51FA\u6D41\u7A0B\u5F00\u59CB\uFF1A\u505C\u6B62\u5168\u90E8\u88AB\u7BA1\u5BF9\u8C61\u2026");
10183
10027
  host.events && host.events.append("shutdown_all", {});
10184
10028
  abortInflightNpm(host, "session-exit");
10029
+ if (host._beatScheduler) host._beatScheduler.stop();
10185
10030
  host._stopMainDsh();
10186
10031
  await host._stopAllSandboxes();
10187
10032
  const stopDaemon = async (kind) => {
@@ -10417,6 +10262,12 @@ var require_instance_adapter = __commonJS({
10417
10262
  } catch (e) {
10418
10263
  d.logger() && d.logger().warn && d.logger().warn("sandbox setPhase: " + (e && e.message || e));
10419
10264
  }
10265
+ try {
10266
+ const rc = Number(inst.state && inst.state.restartCount) || 0;
10267
+ if (entry.restartCount !== rc) d.managedObjects().setRestartCount(entry.id, rc);
10268
+ } catch (e) {
10269
+ d.logger() && d.logger().warn && d.logger().warn("sandbox setRestartCount: " + (e && e.message || e));
10270
+ }
10420
10271
  }
10421
10272
  }
10422
10273
  };
@@ -10916,8 +10767,20 @@ var require_command = __commonJS({
10916
10767
  "use strict";
10917
10768
  var fs2 = require("node:fs");
10918
10769
  var dshCli = require_dsh_cli();
10770
+ var execPath = require_exec_path();
10771
+ var policies = require_policies();
10772
+ function resolveBareCommand(command) {
10773
+ if (!Array.isArray(command) || command.length === 0) return command;
10774
+ const idx = command.length > 1 ? 1 : 0;
10775
+ const cur = command[idx];
10776
+ if (!policies.isBareCommand(cur)) return command;
10777
+ const d = execPath.resolveDsh();
10778
+ if (!d || !d.bin) return command;
10779
+ const rest = command.slice(idx + 1);
10780
+ return d.isJs ? [d.runtime || process.execPath, d.bin, ...rest] : [d.bin, ...rest];
10781
+ }
10919
10782
  function nativeCommand(config, pluginManager) {
10920
- const command = config.command || [];
10783
+ const command = resolveBareCommand(config.command || []);
10921
10784
  const patchFile = pluginManager && pluginManager.overlayFile && fs2.existsSync(pluginManager.overlayFile) ? pluginManager.overlayFile : null;
10922
10785
  const out = [];
10923
10786
  let portSet = false;
@@ -11034,6 +10897,7 @@ var require_process2 = __commonJS({
11034
10897
  var native = require_command();
11035
10898
  var BRAND2 = require_brand();
11036
10899
  var { findManagedDshPort, applyMainPort } = require_port_rederive();
10900
+ var pmClient = require_pm_client();
11037
10901
  var DEPS = /* @__PURE__ */ new WeakMap();
11038
10902
  var HELPERS = [
11039
10903
  "MissingNotified",
@@ -11089,7 +10953,6 @@ var require_process2 = __commonJS({
11089
10953
  const d = depsOf(this);
11090
10954
  return native.nativeCommand(d.config(), d.pluginManager());
11091
10955
  },
11092
- // 启动窗口(毫秒):唯一的时间常量,spawn 时落成 startDeadline,窗口内退出即「启动失败」。
11093
10956
  startWindowMs() {
11094
10957
  const d = depsOf(this);
11095
10958
  const secs = Number(d.config().startsecs);
@@ -11169,6 +11032,7 @@ var require_process2 = __commonJS({
11169
11032
  errBuf.flush();
11170
11033
  if (d.mChild() !== child) return;
11171
11034
  d.events().append(BRAND2.EVENT_HARNESS_EXITED, { code, signal, phase: d.state().phase() });
11035
+ pmClient.recordExit("main", code, signal, d.state().phase() === "STARTING");
11172
11036
  d.mSetChild(null);
11173
11037
  if (d.stopping()) return;
11174
11038
  if (d.state().desired() !== "running") return;
@@ -11178,6 +11042,7 @@ var require_process2 = __commonJS({
11178
11042
  const why = code !== null ? String(code) : "sig" + signal;
11179
11043
  const inStartup = phase === "STARTING";
11180
11044
  if (inStartup || d.state().guardian()) {
11045
+ pmClient.requestRestart("main");
11181
11046
  d.beginRestart("exit:" + why, { startupFailure: inStartup });
11182
11047
  } else {
11183
11048
  d.writeCrashHalted(true);
@@ -11254,8 +11119,6 @@ var require_process2 = __commonJS({
11254
11119
  d.tokenService().scheduleCapture("main");
11255
11120
  d.state().write();
11256
11121
  },
11257
- // 重启:活过 startsecs 后退出(或人工重启)⇒ 正常重启,不记启动失败;窗口内退出 ⇒ 记一次启动失败并由限流裁决。
11258
- // 相位只有 STARTING / RUNNING / FAILED:重启=回到 STARTING(下一次 spawn 会开新的 startsecs 窗口)。
11259
11122
  _beginRestart(reason, opts) {
11260
11123
  const d = depsOf(this);
11261
11124
  const manual = !!(opts && opts.manual);
@@ -11396,7 +11259,6 @@ var require_signals = __commonJS({
11396
11259
  return null;
11397
11260
  }
11398
11261
  },
11399
- // 归属凭据:spawn 与 adopt 两条取得所有权的路线都要写;落盘走 writeAtomic + 0600。
11400
11262
  _writeMainOwner(dshPid, port) {
11401
11263
  const d = depsOf(this);
11402
11264
  try {
@@ -11452,7 +11314,6 @@ var require_signals = __commonJS({
11452
11314
  }
11453
11315
  }, d.config().stopGraceMs));
11454
11316
  },
11455
- // 代际 gen 保护共享的 _adoptKillTimer 槽位:定时器只在「本代仍是当前代」时置空。
11456
11317
  _killAdopted(pid) {
11457
11318
  const d = depsOf(this);
11458
11319
  const gen = (d.readAdoptKillGen() || 0) + 1;
@@ -11517,7 +11378,35 @@ var require_guardian = __commonJS({
11517
11378
  }
11518
11379
  return { start, count, tripped: count >= burst };
11519
11380
  }
11520
- module2.exports = { shouldGuard, bumpStartupFailure };
11381
+ function makeBudget(cfg) {
11382
+ const windowMs = cfg && Number(cfg.windowMs) > 0 ? Number(cfg.windowMs) : 6e4;
11383
+ const burst = cfg && Number(cfg.burst) >= 1 ? Number(cfg.burst) : 5;
11384
+ let state = { start: null, count: 0 };
11385
+ const self = {
11386
+ windowMs,
11387
+ burst,
11388
+ get count() {
11389
+ return state.count;
11390
+ },
11391
+ get tripped() {
11392
+ return state.count >= burst;
11393
+ },
11394
+ note(startupFailure) {
11395
+ if (startupFailure === false || startupFailure == null) {
11396
+ return { tripped: false, count: state.count, halted: false };
11397
+ }
11398
+ const dec = bumpStartupFailure(state, Date.now(), { windowMs, burst });
11399
+ state.start = dec.start;
11400
+ state.count = dec.count;
11401
+ return { tripped: dec.tripped, count: state.count, halted: dec.tripped };
11402
+ },
11403
+ reset() {
11404
+ state = { start: null, count: 0 };
11405
+ }
11406
+ };
11407
+ return self;
11408
+ }
11409
+ module2.exports = { shouldGuard, bumpStartupFailure, makeBudget };
11521
11410
  }
11522
11411
  });
11523
11412
 
@@ -11526,6 +11415,7 @@ var require_startup_throttle = __commonJS({
11526
11415
  "src/app/main/startup-throttle.js"(exports2, module2) {
11527
11416
  "use strict";
11528
11417
  var guardian = require_guardian();
11418
+ var pmClient = require_pm_client();
11529
11419
  var DEPS = /* @__PURE__ */ new WeakMap();
11530
11420
  function depsOf(host) {
11531
11421
  let d = DEPS.get(host);
@@ -11565,8 +11455,6 @@ var require_startup_throttle = __commonJS({
11565
11455
  }
11566
11456
  module2.exports = {
11567
11457
  methods: {
11568
- // 唯一一条限流:startupFailWindowMs 内「启动窗口内退出」达 startupFailBurst 次 ⇒ phase=FAILED。
11569
- // 到点即停自动重启:不排队、不退避、不指数等待;恢复的唯一入口是人工重试(_retryStartupFailure)。
11570
11458
  _noteStartupFailure() {
11571
11459
  const d = depsOf(this);
11572
11460
  const windowMs = d.config().startupFailWindowMs || 6e4;
@@ -11591,11 +11479,11 @@ var require_startup_throttle = __commonJS({
11591
11479
  );
11592
11480
  return { failed: true, count: dec.count };
11593
11481
  },
11594
- // 手动重试入口:清计数后回到 STARTING(随后由 STARTING 分支重新 spawn,新的 startsecs 窗口)。
11595
11482
  _retryStartupFailure() {
11596
11483
  const d = depsOf(this);
11597
11484
  d.mSetStartupFailWindowStart(null);
11598
11485
  d.mSetStartupFailCount(0);
11486
+ pmClient.resetBackoff("main");
11599
11487
  d.events().append("startup_retry", {});
11600
11488
  d.logger().warn("\u542F\u52A8\u5931\u8D25\u8BA1\u6570\u5DF2\u6E05\u96F6\uFF1A\u4EBA\u5DE5\u91CD\u8BD5\uFF0C\u56DE\u5230 STARTING");
11601
11489
  return { ok: true };
@@ -12145,8 +12033,6 @@ var require_runtime2 = __commonJS({
12145
12033
  spawnEnv: () => ({ DSH_SUPERVISOR_CONFIG: cfgPath }),
12146
12034
  logger: d.logger(),
12147
12035
  events: d.events(),
12148
- // `this` 是宿主(本方法经 host._daemonLifecycle(kind) 调用);箭头函数捕获它。
12149
- // 此前写作 host._exitIntended(),而 host 只存在于 depsOf(host) 形参作用域 ⇒ 延迟回调求值时 ReferenceError。
12150
12036
  exitIntended: () => this._exitIntended()
12151
12037
  });
12152
12038
  return lc[kind];
@@ -13014,7 +12900,7 @@ var require_credential = __commonJS({
13014
12900
  });
13015
12901
 
13016
12902
  // src/domains/relay/core.js
13017
- var require_core4 = __commonJS({
12903
+ var require_core3 = __commonJS({
13018
12904
  "src/domains/relay/core.js"(exports2, module2) {
13019
12905
  "use strict";
13020
12906
  var crypto = require("node:crypto");
@@ -13042,14 +12928,52 @@ var require_core4 = __commonJS({
13042
12928
  }
13043
12929
  return null;
13044
12930
  }
13045
- function upstreamPath(rawUrl) {
13046
- try {
13047
- const u = new URL(rawUrl, "http://127.0.0.1");
13048
- if (u.searchParams.has("token")) u.searchParams.delete("token");
13049
- return u.pathname + (u.searchParams.toString() ? "?" + u.searchParams.toString() : "");
13050
- } catch {
13051
- return rawUrl || "/";
12931
+ function stripGateTokenAndPreserve(rawUrl) {
12932
+ if (!rawUrl) return "/";
12933
+ let url = rawUrl;
12934
+ const hashIdx = url.indexOf("#");
12935
+ if (hashIdx >= 0) url = url.slice(0, hashIdx);
12936
+ const qIdx = url.indexOf("?");
12937
+ const path2 = qIdx >= 0 ? url.slice(0, qIdx) : url;
12938
+ if (qIdx < 0) return path2 || "/";
12939
+ const query = url.slice(qIdx + 1);
12940
+ if (!query) return path2 || "/";
12941
+ const kept = [];
12942
+ for (const pair of query.split("&")) {
12943
+ if (!pair) {
12944
+ kept.push(pair);
12945
+ continue;
12946
+ }
12947
+ const eq = pair.indexOf("=");
12948
+ const rawKey = eq >= 0 ? pair.slice(0, eq) : pair;
12949
+ let key = rawKey;
12950
+ try {
12951
+ key = decodeURIComponent(rawKey);
12952
+ } catch {
12953
+ }
12954
+ if (key === "token") continue;
12955
+ kept.push(pair);
13052
12956
  }
12957
+ const out = path2 || "/";
12958
+ return kept.length ? out + "?" + kept.join("&") : out;
12959
+ }
12960
+ function upstreamPath(rawUrl) {
12961
+ return stripGateTokenAndPreserve(rawUrl);
12962
+ }
12963
+ var POLYFILL_SCRIPT = `<script>
12964
+ if (typeof crypto.randomUUID !== 'function') {
12965
+ crypto.randomUUID = function () {
12966
+ var b = crypto.getRandomValues(new Uint8Array(16));
12967
+ b[6] = (b[6] & 0x0f) | 0x40;
12968
+ b[8] = (b[8] & 0x3f) | 0x80;
12969
+ var h = '';
12970
+ for (var i = 0; i < 16; i++) h += (i === 4 || i === 6 || i === 8 || i === 12 ? '-' : '') + ('0' + b[i].toString(16)).slice(-2);
12971
+ return h;
12972
+ };
12973
+ }
12974
+ </script>`;
12975
+ function redactLogPath(rawUrl) {
12976
+ return rawUrl ? stripGateTokenAndPreserve(rawUrl) : "";
13053
12977
  }
13054
12978
  function lanGateCookieValue(token, salt) {
13055
12979
  const t = String(token == null ? "" : token);
@@ -13057,23 +12981,6 @@ var require_core4 = __commonJS({
13057
12981
  if (!t || !s) return "";
13058
12982
  return crypto.createHash("sha256").update(s + "\n" + t).digest("hex");
13059
12983
  }
13060
- function hasValidToken(req, token, salt, mode) {
13061
- if (!token) return normalizeRemoteMode(mode) === "lan";
13062
- const url = new URL(req.url, "http://localhost");
13063
- const queryToken = url.searchParams.get("token");
13064
- if (queryToken && safeEqual(queryToken, token)) return true;
13065
- const cookies = req.headers.cookie || "";
13066
- const m = LAN_COOKIE_RE.exec(cookies);
13067
- if (m) {
13068
- try {
13069
- const want = lanGateCookieValue(token, salt);
13070
- return !!want && safeEqual(decodeURIComponent(m[1]), want);
13071
- } catch {
13072
- return false;
13073
- }
13074
- }
13075
- return false;
13076
- }
13077
12984
  function tokenGateDecision(req, token, salt, mode) {
13078
12985
  if (!token) return normalizeRemoteMode(mode) === "lan" ? { ok: true } : { ok: false, unauthorized: true };
13079
12986
  const url = new URL(req.url, "http://localhost");
@@ -13096,27 +13003,22 @@ var require_core4 = __commonJS({
13096
13003
  }
13097
13004
  return { ok: false, unauthorized: true };
13098
13005
  }
13099
- var POLYFILL_SCRIPT = `<script>
13100
- if (typeof crypto.randomUUID !== 'function') {
13101
- crypto.randomUUID = function () {
13102
- var b = crypto.getRandomValues(new Uint8Array(16));
13103
- b[6] = (b[6] & 0x0f) | 0x40;
13104
- b[8] = (b[8] & 0x3f) | 0x80;
13105
- var h = '';
13106
- for (var i = 0; i < 16; i++) h += (i === 4 || i === 6 || i === 8 || i === 12 ? '-' : '') + ('0' + b[i].toString(16)).slice(-2);
13107
- return h;
13108
- };
13109
- }
13110
- </script>`;
13111
- function redactLogPath(rawUrl) {
13112
- if (!rawUrl) return "";
13113
- try {
13114
- const u = new URL(rawUrl, "http://127.0.0.1");
13115
- if (u.searchParams.has("token")) u.searchParams.delete("token");
13116
- return u.pathname + (u.searchParams.toString() ? "?" + u.searchParams.toString() : "");
13117
- } catch {
13118
- return String(rawUrl || "").replace(/[?&]token=[^&]*/, "");
13006
+ function hasValidToken(req, token, salt, mode) {
13007
+ if (!token) return normalizeRemoteMode(mode) === "lan";
13008
+ const url = new URL(req.url, "http://localhost");
13009
+ const queryToken = url.searchParams.get("token");
13010
+ if (queryToken && safeEqual(queryToken, token)) return true;
13011
+ const cookies = req.headers.cookie || "";
13012
+ const m = LAN_COOKIE_RE.exec(cookies);
13013
+ if (m) {
13014
+ try {
13015
+ const want = lanGateCookieValue(token, salt);
13016
+ return !!want && safeEqual(decodeURIComponent(m[1]), want);
13017
+ } catch {
13018
+ return false;
13019
+ }
13119
13020
  }
13021
+ return false;
13120
13022
  }
13121
13023
  function buildFrpcToml(settings, instances) {
13122
13024
  const s = settings || {};
@@ -13147,8 +13049,6 @@ if (typeof crypto.randomUUID !== 'function') {
13147
13049
  return {
13148
13050
  serverAddr: String(j.serverAddr !== void 0 ? j.serverAddr : cur.serverAddr).trim(),
13149
13051
  serverPort: Number(j.serverPort) || cur.serverPort,
13150
- // authToken:null/undefined 归一为空串(frpc 遇空串不写 auth.token,避免 String(null)==="null" 被当成共享密钥);
13151
- // 非空串统一 trim,与 serverAddr 同口径。
13152
13052
  authToken: j.authToken === void 0 ? cur.authToken : j.authToken == null ? "" : String(j.authToken).trim(),
13153
13053
  user: String(j.user || cur.user || "dsh")
13154
13054
  };
@@ -13231,7 +13131,7 @@ if (typeof crypto.randomUUID !== 'function') {
13231
13131
  var require_lan2 = __commonJS({
13232
13132
  "src/app/domain-actions/lan.js"(exports2, module2) {
13233
13133
  "use strict";
13234
- var { validateWanAccess, generateRemoteToken } = require_core4();
13134
+ var { validateWanAccess, generateRemoteToken } = require_core3();
13235
13135
  var { remoteTokenStrength } = require_credential();
13236
13136
  var BRAND2 = require_brand();
13237
13137
  function lanModule(deps) {
@@ -13598,13 +13498,6 @@ var require_env = __commonJS({
13598
13498
  } catch {
13599
13499
  return null;
13600
13500
  }
13601
- })(),
13602
- shellWatchdog: (() => {
13603
- try {
13604
- return this.shellWatchdog && typeof this.shellWatchdog.status === "function" ? this.shellWatchdog.status() : null;
13605
- } catch {
13606
- return null;
13607
- }
13608
13501
  })()
13609
13502
  };
13610
13503
  },
@@ -13819,7 +13712,6 @@ var require_versions = __commonJS({
13819
13712
  }
13820
13713
  return dir;
13821
13714
  },
13822
- // 同步 spawn 会冻结事件循环,消费方含 HTTP 路径,故 git 子进程异步执行。
13823
13715
  async guardVersionLocal() {
13824
13716
  const d = depsOf(this);
13825
13717
  const root = d.vcsRoot();
@@ -13876,7 +13768,6 @@ var require_access = __commonJS({
13876
13768
  const cfg = this.config || {};
13877
13769
  return { configured: !!cfg.apiAccessKey, host: cfg.apiHost || void 0 };
13878
13770
  },
13879
- // 清空密钥必须同时回关 LAN(apiHost→127.0.0.1):只清 key 不动 apiHost 会留下「绑定 0.0.0.0 且零认证」的暴露窗口。
13880
13771
  setAccessKey(key) {
13881
13772
  try {
13882
13773
  const cfg = this.config || {};
@@ -14787,7 +14678,6 @@ var require_pool2 = __commonJS({
14787
14678
  this._backfillAt.delete(id);
14788
14679
  this.clear(id);
14789
14680
  }
14790
- // journalctl 同步查询会冻结生命周期 tick 的事件循环,故 journal 档非阻塞发射(见 capture.js)。
14791
14681
  capture(id) {
14792
14682
  const src = this._sources.get(id);
14793
14683
  if (!src) return null;
@@ -15104,6 +14994,35 @@ var require_npm = __commonJS({
15104
14994
  }
15105
14995
  });
15106
14996
 
14997
+ // src/domains/shell/core.js
14998
+ var require_core4 = __commonJS({
14999
+ "src/domains/shell/core.js"(exports2, module2) {
15000
+ "use strict";
15001
+ function isUpdatePhase(phase) {
15002
+ const p = String(phase || "");
15003
+ return p === "restarting" || p.indexOf("shell-update") === 0;
15004
+ }
15005
+ function deriveState(id, journal) {
15006
+ const j = journal || {};
15007
+ if (!j.to) return { state: "idle", reason: "\u65E0\u8FDB\u884C\u4E2D\u7684\u66F4\u65B0", journal: j, identity: id };
15008
+ const cur = id && id.version ? String(id.version) : null;
15009
+ if (cur && cur === j.to && id && id.phase === "ready") {
15010
+ const next = j.confirmed === true ? j : Object.assign({}, j, { confirmed: true });
15011
+ return { state: "confirmed", version: cur, reason: "\u58F3\u5DF2\u5065\u5EB7\u8FD0\u884C\u65B0\u7248\u672C", journal: next, identity: id };
15012
+ }
15013
+ return {
15014
+ state: "pending",
15015
+ target: j.to,
15016
+ current: cur,
15017
+ reason: cur === j.to ? "\u7B49\u5F85\u58F3\u4E0A\u62A5\u5C31\u7EEA" : "\u7B49\u5F85\u58F3\u91CD\u542F\u5230\u65B0\u7248\u672C",
15018
+ journal: j,
15019
+ identity: id
15020
+ };
15021
+ }
15022
+ module2.exports = { isUpdatePhase, deriveState };
15023
+ }
15024
+ });
15025
+
15107
15026
  // src/domains/shell/journal.js
15108
15027
  var require_journal = __commonJS({
15109
15028
  "src/domains/shell/journal.js"(exports2, module2) {
@@ -15112,7 +15031,7 @@ var require_journal = __commonJS({
15112
15031
  var fs2 = require("node:fs");
15113
15032
  var path2 = require("node:path");
15114
15033
  var { writeAtomic } = require_fs();
15115
- var { deriveState } = require_core3();
15034
+ var { deriveState } = require_core4();
15116
15035
  function shellDir() {
15117
15036
  return stateRoot2.shellDir();
15118
15037
  }
@@ -15191,116 +15110,6 @@ var require_journal = __commonJS({
15191
15110
  }
15192
15111
  });
15193
15112
 
15194
- // src/domains/shell/restart.js
15195
- var require_restart = __commonJS({
15196
- "src/domains/shell/restart.js"(exports2, module2) {
15197
- "use strict";
15198
- var spawnOS = require_spawn();
15199
- var pidlook = require_pidlookup();
15200
- var BRAND2 = require_brand();
15201
- var { identity } = require_journal();
15202
- var { exeFromCmdline, isShellProcess } = require_core3();
15203
- var { semverCompare } = require_version2();
15204
- var SHELL_RELEASE_PKG = "@lob-ox/shell-release";
15205
- async function checkUpdate(dist, opts) {
15206
- const id = identity();
15207
- const installed = id && id.version ? String(id.version) : null;
15208
- if (!dist || typeof dist.fetchLatestVersion !== "function") {
15209
- return { ok: false, installed, latest: null, updateAvailable: false, error: "\u5206\u53D1\u670D\u52A1\u672A\u521D\u59CB\u5316" };
15210
- }
15211
- try {
15212
- const latest = await dist.fetchLatestVersion(SHELL_RELEASE_PKG, "npm", { authoritative: (opts && opts.authoritative) === true });
15213
- if (!latest) {
15214
- 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" };
15215
- }
15216
- const updateAvailable = Boolean(installed && semverCompare(latest, installed) > 0);
15217
- return { ok: true, installed, latest, updateAvailable };
15218
- } catch (e) {
15219
- return { ok: false, installed, latest: null, updateAvailable: false, error: e && e.message || String(e) };
15220
- }
15221
- }
15222
- async function restartShell(opts) {
15223
- const o = opts || {};
15224
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
15225
- const pattern = o.procPattern || BRAND2.PROC_MATCH_GUI;
15226
- let procs = [];
15227
- try {
15228
- procs = pidlook.pgrepList(pattern) || [];
15229
- } catch {
15230
- procs = [];
15231
- }
15232
- procs = procs.filter((p) => isShellProcess(p));
15233
- const exe = procs.length ? exeFromCmdline(procs[0].cmdline) : o.exePath || null;
15234
- 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" };
15235
- const killed = [];
15236
- for (const p of procs) {
15237
- try {
15238
- process.kill(p.pid, "SIGTERM");
15239
- killed.push(p.pid);
15240
- } catch {
15241
- }
15242
- }
15243
- const waitGone = async (ms) => {
15244
- const deadline = Date.now() + ms;
15245
- while (Date.now() < deadline) {
15246
- const alive = procs.some((p) => {
15247
- try {
15248
- return pidlook.isAlive(p.pid);
15249
- } catch {
15250
- return false;
15251
- }
15252
- });
15253
- if (!alive) return true;
15254
- await sleep(200);
15255
- }
15256
- return !procs.some((p) => {
15257
- try {
15258
- return pidlook.isAlive(p.pid);
15259
- } catch {
15260
- return false;
15261
- }
15262
- });
15263
- };
15264
- let gone = await waitGone(o.graceMs || 6e3);
15265
- if (!gone) {
15266
- for (const p of procs) {
15267
- try {
15268
- process.kill(p.pid, "SIGKILL");
15269
- } catch {
15270
- }
15271
- }
15272
- gone = await waitGone(2e3);
15273
- }
15274
- if (!gone) {
15275
- 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 };
15276
- }
15277
- if (typeof o.shouldAbort === "function") {
15278
- try {
15279
- 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 };
15280
- } catch {
15281
- }
15282
- }
15283
- try {
15284
- const child = spawnOS.detachedIgnored(exe, [], { env: process.env });
15285
- child.on("error", (e) => {
15286
- try {
15287
- if (o.events && o.events.append) o.events.append("shell_restart_spawn_error", { exe, error: e && e.message || String(e) });
15288
- } catch {
15289
- }
15290
- });
15291
- child.unref();
15292
- if (!child.pid) {
15293
- 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 };
15294
- }
15295
- return { ok: true, restarted: true, killed, pid: child.pid, exe };
15296
- } catch (e) {
15297
- return { ok: false, error: "\u62C9\u8D77\u65B0\u58F3\u5931\u8D25: " + (e && e.message || e), killed };
15298
- }
15299
- }
15300
- module2.exports = { SHELL_RELEASE_PKG, checkUpdate, restartShell };
15301
- }
15302
- });
15303
-
15304
15113
  // src/domains/shell/index.js
15305
15114
  var require_shell = __commonJS({
15306
15115
  "src/domains/shell/index.js"(exports2, module2) {
@@ -15314,8 +15123,7 @@ var require_shell = __commonJS({
15314
15123
  health,
15315
15124
  status
15316
15125
  } = require_journal();
15317
- var { SHELL_RELEASE_PKG, checkUpdate, restartShell } = require_restart();
15318
- module2.exports = { status, evaluate, health, markPending, identity, readJournal, shellDir, checkUpdate, restartShell, SHELL_RELEASE_PKG };
15126
+ module2.exports = { status, evaluate, health, markPending, identity, readJournal, shellDir };
15319
15127
  }
15320
15128
  });
15321
15129
 
@@ -15618,8 +15426,6 @@ var require_intents = __commonJS({
15618
15426
  if (this._pending.has(intent)) this._pending.delete(intent);
15619
15427
  return p;
15620
15428
  }
15621
- // W5:生产零调用,仅 session-lifecycle-test.js 用于「模拟重启后内存态为空」的夹具。
15622
- // 保留(它是一个合理的测试缝),不视为待删死代码。
15623
15429
  clear() {
15624
15430
  this._pending.clear();
15625
15431
  }
@@ -15806,7 +15612,6 @@ var require_core5 = __commonJS({
15806
15612
  routerAutostart: s.routerAutostart,
15807
15613
  routerMode: s.routerMode,
15808
15614
  updateCheck: s.updateCheck,
15809
- shellWatchdog: s.shellWatchdog,
15810
15615
  lastRefresh: s.lastRefresh
15811
15616
  };
15812
15617
  }
@@ -15864,12 +15669,10 @@ var require_core5 = __commonJS({
15864
15669
  host._upgradeHold = false;
15865
15670
  host._upgradeHoldSince = null;
15866
15671
  host._timer = null;
15672
+ host._beatScheduler = null;
15867
15673
  host._heartbeatBusy = false;
15868
15674
  host._killTimer = null;
15869
15675
  host._adoptKillTimer = null;
15870
- host._initialCheckTimer = null;
15871
- host._upgradeTimer = null;
15872
- host._shellWatchdogTimer = null;
15873
15676
  host._lastOccupiedWarn = 0;
15874
15677
  host._lastMainPortRederive = 0;
15875
15678
  host._lastPortUp = false;
@@ -17127,11 +16930,21 @@ var require_instance_lifecycle = __commonJS({
17127
16930
  });
17128
16931
 
17129
16932
  // src/domains/router/providers/restart.js
17130
- var require_restart2 = __commonJS({
16933
+ var require_restart = __commonJS({
17131
16934
  "src/domains/router/providers/restart.js"(exports2, module2) {
17132
16935
  "use strict";
17133
16936
  var { INSTANCE_STATES } = require_model2();
17134
16937
  var ports = require_ports().shared;
16938
+ var { makeBudget } = require_guardian();
16939
+ var PROXY_INSTANCE_THROTTLE = { windowMs: 10 * 60 * 1e3, burst: 5 };
16940
+ function budgetFor(inst) {
16941
+ if (!inst) return makeBudget(PROXY_INSTANCE_THROTTLE);
16942
+ if (!inst._restartBudget) inst._restartBudget = makeBudget(PROXY_INSTANCE_THROTTLE);
16943
+ return inst._restartBudget;
16944
+ }
16945
+ function resetBudget(inst) {
16946
+ if (inst) inst._restartBudget = makeBudget(PROXY_INSTANCE_THROTTLE);
16947
+ }
17135
16948
  function createRestartOrchestrator(deps) {
17136
16949
  const d = deps || {};
17137
16950
  const { startInstance, waitHealthy, isAlive, logger } = d;
@@ -17165,8 +16978,8 @@ var require_restart2 = __commonJS({
17165
16978
  }
17166
16979
  return { respawn };
17167
16980
  }
17168
- async function runReconcile(provider, allowStop) {
17169
- const out = { started: [], stopped: [], desired: [] };
16981
+ async function runReconcileThrottled(provider, allowStop) {
16982
+ const out = { started: [], stopped: [], desired: [], throttled: [] };
17170
16983
  const desired = provider.desiredRunningAccounts();
17171
16984
  const list = desired.list || [];
17172
16985
  out.desired = list.map((a) => a.keyId);
@@ -17175,12 +16988,25 @@ var require_restart2 = __commonJS({
17175
16988
  const inst = provider.instanceOf(acc);
17176
16989
  if (!inst || inst.pid || inst.startingPromise) continue;
17177
16990
  if (inst.status === INSTANCE_STATES.DEAD && !inst.pid) inst.status = INSTANCE_STATES.COLD;
16991
+ const b = budgetFor(inst);
16992
+ if (b.tripped) {
16993
+ inst._throttled = true;
16994
+ out.throttled.push(acc.keyId);
16995
+ continue;
16996
+ }
16997
+ inst._throttled = false;
16998
+ const wasRunning = !!inst.pid;
17178
16999
  try {
17179
17000
  const r = await provider.startInstance(inst);
17001
+ const startupFailure = !r.ok || !wasRunning && !inst.pid;
17002
+ b.note(startupFailure);
17180
17003
  if (r && r.ok) {
17181
17004
  await provider._waitHealthy(inst).catch(() => {
17182
17005
  });
17183
17006
  out.started.push(acc.keyId);
17007
+ if (b.tripped && provider.logger && provider.logger.warn) {
17008
+ 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");
17009
+ }
17184
17010
  if (provider.logger && provider.logger.info) {
17185
17011
  const role = desired.active && acc.keyId === desired.active.keyId ? "\u5728\u7528" : "\u9884\u70ED";
17186
17012
  provider.logger.info("[reconcile] \u62C9\u8D77\u5B9E\u4F8B key=" + acc.maskedKey + "\uFF08" + role + "\uFF09");
@@ -17189,8 +17015,8 @@ var require_restart2 = __commonJS({
17189
17015
  } catch {
17190
17016
  }
17191
17017
  }
17192
- if (!allowStop) return out;
17193
17018
  for (const inst of (provider.instances || []).slice()) {
17019
+ if (!allowStop) break;
17194
17020
  if (!inst.pid) {
17195
17021
  if (!desiredIds.has(inst.keyId) && inst.port) {
17196
17022
  try {
@@ -17235,7 +17061,7 @@ var require_restart2 = __commonJS({
17235
17061
  }
17236
17062
  }
17237
17063
  if (provider._reconcileBusy) return provider._reconcileBusy;
17238
- const p = runReconcile(provider, allowStop);
17064
+ const p = runReconcileThrottled(provider, allowStop);
17239
17065
  provider._reconcileBusy = p;
17240
17066
  try {
17241
17067
  return await p;
@@ -17248,7 +17074,60 @@ var require_restart2 = __commonJS({
17248
17074
  reconcileInstances(provider).catch(() => {
17249
17075
  });
17250
17076
  }
17251
- module2.exports = { createRestartOrchestrator, runReconcile, reconcileInstances, reconcileNow };
17077
+ module2.exports = { createRestartOrchestrator, runReconcileThrottled, reconcileInstances, reconcileNow, budgetFor, resetBudget, PROXY_INSTANCE_THROTTLE };
17078
+ }
17079
+ });
17080
+
17081
+ // src/app/control/process-lifecycle.js
17082
+ var require_process_lifecycle = __commonJS({
17083
+ "src/app/control/process-lifecycle.js"(exports2, module2) {
17084
+ "use strict";
17085
+ var procOS = require_process();
17086
+ var pidlook = require_pidlookup();
17087
+ var DEFAULTS = {
17088
+ stopGraceMs: 9e3,
17089
+ portFreeTimeoutMs: 5e3
17090
+ };
17091
+ function sleep(ms) {
17092
+ return new Promise((r) => setTimeout(r, ms));
17093
+ }
17094
+ function isForeignOnPort(port, ownPid) {
17095
+ if (!Number.isInteger(port) || port <= 0) return false;
17096
+ const p = pidlook.findListeningPid(port);
17097
+ return p !== null && p !== ownPid;
17098
+ }
17099
+ async function waitPortFree(port, timeoutMs) {
17100
+ const dl = Date.now() + (timeoutMs || DEFAULTS.portFreeTimeoutMs);
17101
+ while (Date.now() < dl) {
17102
+ if (pidlook.findListeningPid(port) === null) return true;
17103
+ await sleep(150);
17104
+ }
17105
+ return pidlook.findListeningPid(port) === null;
17106
+ }
17107
+ async function stopProcess(opts) {
17108
+ const o = Object.assign({}, DEFAULTS, opts || {});
17109
+ const pid = typeof o.pid === "number" ? o.pid : null;
17110
+ const port = typeof o.port === "number" ? o.port : null;
17111
+ const ownGroup = !!o.ownGroup;
17112
+ if (pid && pid > 0) {
17113
+ procOS.killTree(pid, "SIGTERM", void 0, ownGroup ? { ownGroup: true } : void 0);
17114
+ }
17115
+ const dl = Date.now() + o.stopGraceMs;
17116
+ while (Date.now() < dl) {
17117
+ const alive = pid ? pidlook.isAlive ? pidlook.isAlive(pid) : false : false;
17118
+ if (!alive) break;
17119
+ await sleep(150);
17120
+ }
17121
+ if (port) {
17122
+ if (isForeignOnPort(port, pid)) {
17123
+ return { ok: false, stopped: false, foreign: true };
17124
+ }
17125
+ const free = await waitPortFree(port, o.portFreeTimeoutMs);
17126
+ if (!free) return { ok: false, stopped: false, portBusy: true };
17127
+ }
17128
+ return { ok: true, stopped: true };
17129
+ }
17130
+ module2.exports = { ProcessLifecycle: { stopProcess, waitPortFree, isForeignOnPort, DEFAULTS } };
17252
17131
  }
17253
17132
  });
17254
17133
 
@@ -17395,6 +17274,7 @@ var require_probe5 = __commonJS({
17395
17274
  var carrier = require_carrier();
17396
17275
  var procOS = require_process();
17397
17276
  var { INSTANCE_STATES } = require_model2();
17277
+ var { ProcessLifecycle } = require_process_lifecycle();
17398
17278
  var { getQuotaStrategy } = require_quota_strategies();
17399
17279
  var { quotaOverallStatus } = require_quota();
17400
17280
  var { cachedPkgBin, ensurePkgCached } = require_pkg_cache();
@@ -17423,7 +17303,7 @@ var require_probe5 = __commonJS({
17423
17303
  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");
17424
17304
  if (provider.events) provider.events.append("proxy_instance_survivor_reclaimed", { app: provider.proxyAppId, port: inst.port, pid: boundPid, reason: "restart-survivor-stdio-unsafe" });
17425
17305
  try {
17426
- procOS.killTree(boundPid, "SIGKILL");
17306
+ ProcessLifecycle.stopProcess({ pid: boundPid, port: inst.port });
17427
17307
  } catch {
17428
17308
  }
17429
17309
  const dl = Date.now() + 3e3;
@@ -17613,7 +17493,7 @@ var require_probe5 = __commonJS({
17613
17493
  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");
17614
17494
  if (provider.events) provider.events.append("proxy_instance_hang_restart", { app: provider.proxyAppId, port: inst.port, pid: inst.pid, fails: inst._monitorFails });
17615
17495
  try {
17616
- procOS.killTree(inst.pid, "SIGKILL", void 0, { ownGroup: true });
17496
+ ProcessLifecycle.stopProcess({ pid: inst.pid, port: inst.port });
17617
17497
  } catch {
17618
17498
  }
17619
17499
  inst.pid = null;
@@ -17768,7 +17648,8 @@ var require_process_pool = __commonJS({
17768
17648
  "src/domains/router/providers/process-pool.js"(exports2, module2) {
17769
17649
  "use strict";
17770
17650
  var life = require_instance_lifecycle();
17771
- var restart = require_restart2();
17651
+ var restart = require_restart();
17652
+ var { resetBudget } = restart;
17772
17653
  var probe = require_probe5();
17773
17654
  var pool = require_pool3();
17774
17655
  var pidlook = require_pidlookup();
@@ -17901,6 +17782,10 @@ var require_process_pool = __commonJS({
17901
17782
  }
17902
17783
  inst._restartAt = Date.now() + 12e4;
17903
17784
  inst._restartPending = null;
17785
+ try {
17786
+ resetBudget(inst);
17787
+ } catch {
17788
+ }
17904
17789
  if (this.logger && this.logger.warn) this.logger.warn("[proxy-instance] \u5B9E\u4F8B\u91CD\u542F key=" + inst.maskedKey + " port=" + inst.port + " reason=" + reason);
17905
17790
  const hadPid = !!inst.pid;
17906
17791
  try {
@@ -18020,7 +17905,7 @@ var require_proxy = __commonJS({
18020
17905
  var { npxLauncher } = require_npx_forms();
18021
17906
  var { buildCommand } = require_command2();
18022
17907
  var probe = require_probe5();
18023
- var restart = require_restart2();
17908
+ var restart = require_restart();
18024
17909
  var pool = require_pool3();
18025
17910
  var life = require_instance_lifecycle();
18026
17911
  var ProxyProvider = class extends withProcessPool(ProviderBase) {
@@ -18384,7 +18269,6 @@ var require_proxy_apps = __commonJS({
18384
18269
  healthPath: "/health",
18385
18270
  modelPath: "/v1/models",
18386
18271
  upstream: "https://api.commandcode.ai",
18387
- // keyEnv 是密钥注入的唯一 env 名(绝不进 cmdline);其余 env/超时一律不注入,跑上游默认配置。
18388
18272
  keyEnv: "CC_API_KEY",
18389
18273
  repo: "thaolaptrinh/commandcode-api-proxy",
18390
18274
  registry: "commandcode-api-proxy",
@@ -19092,16 +18976,6 @@ var require_usage = __commonJS({
19092
18976
  if (!this._dirty) return;
19093
18977
  this._writeTotals();
19094
18978
  }
19095
- /**
19096
- * 根因 E:缓存**新鲜度**与**可写性**两维度正交,不得耦合。
19097
- *
19098
- * 修前:`if (this.totals && this._canPersist()) return this.totals;`
19099
- * —— 闸门关闭(daemon 模式下 setPersistEnabled(false))时该条件恒假 ⇒ 每次 recordUsage
19100
- * 都重新读盘,而 _writeTotals 又因闸门关闭直接 return ⇒ 磁盘不变、内存增量被下次 load 覆盖
19101
- * ⇒ **用量计数被永久吞掉**。
19102
- *
19103
- * 现在:load 只看内存缓存是否存在(新鲜度);是否落盘由 _writeTotals 单独决定(可写性)。
19104
- */
19105
18979
  load() {
19106
18980
  if (this.totals) return this.totals;
19107
18981
  let t;
@@ -20138,6 +20012,7 @@ var require_scheduler2 = __commonJS({
20138
20012
  function createScheduler(deps) {
20139
20013
  const d = deps || {};
20140
20014
  const state = d.state;
20015
+ const beat = d.beatScheduler || null;
20141
20016
  const logger = d.logger || null;
20142
20017
  const refreshProxyUpdateInfo = d.refreshProxyUpdateInfo || (() => Promise.resolve());
20143
20018
  const refreshOfficialUsageAll = d.refreshOfficialUsageAll || (() => Promise.resolve());
@@ -20153,6 +20028,27 @@ var require_scheduler2 = __commonJS({
20153
20028
  });
20154
20029
  ensureProxyInstances().catch(() => {
20155
20030
  });
20031
+ if (beat) {
20032
+ beat.unregister("router-maint");
20033
+ beat.unregister("router-lifecycle");
20034
+ beat.unregister("router-pricing");
20035
+ beat.register("router-maint", () => {
20036
+ refreshProxyUpdateInfo().catch(() => {
20037
+ });
20038
+ probeIfDue();
20039
+ ensureProxyInstances().catch(() => {
20040
+ });
20041
+ }, { every: Math.max(1, Math.round(3e5 / (d.tickMs || 5e3))) });
20042
+ beat.register("router-lifecycle", () => {
20043
+ monitorInstanceHealth().catch(() => {
20044
+ });
20045
+ }, { every: Math.max(1, Math.round(3e4 / (d.tickMs || 5e3))) });
20046
+ beat.register("router-pricing", () => {
20047
+ refreshOfficialPricingAll().catch(() => {
20048
+ });
20049
+ }, { every: Math.max(1, Math.round(216e5 / (d.tickMs || 5e3))) });
20050
+ return;
20051
+ }
20156
20052
  if (state.maintTimer) clearInterval(state.maintTimer);
20157
20053
  state.maintTimer = setInterval(() => {
20158
20054
  refreshProxyUpdateInfo().catch(() => {
@@ -20174,6 +20070,12 @@ var require_scheduler2 = __commonJS({
20174
20070
  }), 6 * 3600 * 1e3);
20175
20071
  }
20176
20072
  function stop() {
20073
+ if (beat) {
20074
+ beat.unregister("router-maint");
20075
+ beat.unregister("router-lifecycle");
20076
+ beat.unregister("router-pricing");
20077
+ return;
20078
+ }
20177
20079
  if (state.maintTimer) {
20178
20080
  clearInterval(state.maintTimer);
20179
20081
  state.maintTimer = null;
@@ -20702,6 +20604,8 @@ var require_router3 = __commonJS({
20702
20604
  var RouterService = class {
20703
20605
  constructor(opts) {
20704
20606
  this.config = opts.config;
20607
+ this._beatScheduler = opts.beatScheduler || null;
20608
+ this._beatTickMs = opts.tickMs || opts.config && opts.config.tickIntervalMs || 5e3;
20705
20609
  this.dist = opts.dist || null;
20706
20610
  this.logger = opts.logger || console;
20707
20611
  this.events = opts.events || null;
@@ -20763,6 +20667,8 @@ var require_router3 = __commonJS({
20763
20667
  state,
20764
20668
  store: this.store,
20765
20669
  logger: this.logger,
20670
+ beatScheduler: this._beatScheduler,
20671
+ tickMs: this._beatTickMs,
20766
20672
  refreshProxyUpdateInfo: () => this.refreshProxyUpdateInfo(),
20767
20673
  refreshOfficialUsageAll: () => this.refreshOfficialUsageAll(),
20768
20674
  refreshOfficialPricingAll: () => this.refreshOfficialPricingAll()
@@ -21340,7 +21246,6 @@ var require_model3 = __commonJS({
21340
21246
  allocation: inst.state ? inst.state.allocation || null : null,
21341
21247
  usage: inst.state ? inst.state.usage || null : null,
21342
21248
  restartCount: inst.state ? inst.state.restartCount || 0 : 0,
21343
- // 启动失败限流(唯一一条规则)的可见量:窗口内计数 / 固定端口释放等待落点。
21344
21249
  startupFailCount: inst.state ? inst.state.startupFailCount || 0 : 0,
21345
21250
  restartAt: inst.state ? inst.state.restartAt || null : null,
21346
21251
  lastFailure: inst.state ? inst.state.lastFailure || null : null,
@@ -21423,12 +21328,6 @@ var require_store5 = __commonJS({
21423
21328
  _stripLegacyStateKeys(inst) {
21424
21329
  if (inst.state && Object.prototype.hasOwnProperty.call(inst.state, "version")) delete inst.state.version;
21425
21330
  }
21426
- /**
21427
- * 隔离损坏文件。
21428
- * Lease I2:隔离前**再次校验版本戳**——若自读取失败以来文件已被他人更新,
21429
- * 说明它不是损坏 ⇒ 放弃隔离(修前会直接 rename 掉这个更新的文件并把实例列表清空,即全量静默丢失)。
21430
- * 且保留可回溯副本:先复制再改名,不直接丢弃。
21431
- */
21432
21331
  _quarantineCorrupt(err) {
21433
21332
  let locked = false;
21434
21333
  try {
@@ -22669,25 +22568,7 @@ var require_ops2 = __commonJS({
22669
22568
  store.save();
22670
22569
  return { ok: true, instance: inst };
22671
22570
  }
22672
- function startTimer(intervalMs) {
22673
- if (timer) clearInterval(timer);
22674
- timer = setInterval(() => {
22675
- for (const inst of store.instances) {
22676
- if (inst.domain === "native") continue;
22677
- try {
22678
- lifecycle.supervise(inst.id);
22679
- } catch (e) {
22680
- logger.warn && logger.warn("[" + inst.id + "] supervise \u5931\u8D25: " + (e && e.message));
22681
- }
22682
- }
22683
- try {
22684
- lifecycle.governSweep();
22685
- } catch (e) {
22686
- logger.warn && logger.warn("governSweep: " + (e && e.message));
22687
- }
22688
- }, intervalMs || 5e3);
22689
- }
22690
- return { list, addInstance, removeInstance, updateInstance, startTimer };
22571
+ return { list, addInstance, removeInstance, updateInstance };
22691
22572
  }
22692
22573
  module2.exports = { createOps };
22693
22574
  }
@@ -22910,7 +22791,6 @@ var require_instance = __commonJS({
22910
22791
  map(fn) {
22911
22792
  return this._store.instances.map(fn);
22912
22793
  }
22913
- // 能力判定读平台表(有副作用)⇒ 从 governor 取,不经 pure 的 sandbox.js。
22914
22794
  get sandboxSupported() {
22915
22795
  return governor.sandboxSupported(this._sandboxSupportedOverride);
22916
22796
  }
@@ -22998,9 +22878,6 @@ var require_instance = __commonJS({
22998
22878
  upgradeStatus(id) {
22999
22879
  return this._upgrade.upgradeStatus(id);
23000
22880
  }
23001
- startTimer(ms) {
23002
- return this._ops.startTimer(ms);
23003
- }
23004
22881
  sandboxRoot(inst) {
23005
22882
  return sandbox.root(this.instancesRoot, inst);
23006
22883
  }
@@ -23898,23 +23775,14 @@ var require_process_spec = __commonJS({
23898
23775
  function defaultSpec() {
23899
23776
  return {
23900
23777
  runtime: null,
23901
- // 解释器(如 node);为 null 表示 bin 可直接执行
23902
23778
  bin: null,
23903
- // 可执行文件或入口脚本
23904
23779
  subcommand: null,
23905
- // 子命令(如 'web' / 'plugin')
23906
23780
  args: [],
23907
- // 用户参数
23908
23781
  port: null,
23909
- // --port 值(一等公民,不再各处拼装)
23910
23782
  patchFile: null,
23911
- // 插件 overlay(一等公民,不再事后 splice)
23912
23783
  storeDir: null,
23913
- // pnpm --store-dir
23914
23784
  install: false,
23915
- // 是否安装类操作(决定是否注入安全标志)
23916
23785
  extraFlags: []
23917
- // 其他固定标志
23918
23786
  };
23919
23787
  }
23920
23788
  function validate(spec) {
@@ -24124,7 +23992,7 @@ var require_cli = __commonJS({
24124
23992
  });
24125
23993
 
24126
23994
  // src/domains/plugin/restart.js
24127
- var require_restart3 = __commonJS({
23995
+ var require_restart2 = __commonJS({
24128
23996
  "src/domains/plugin/restart.js"(exports2, module2) {
24129
23997
  "use strict";
24130
23998
  function targetRunning(ctx, target) {
@@ -24165,7 +24033,7 @@ var require_restart3 = __commonJS({
24165
24033
  log("\u91CD\u542F\u5B9E\u4F8B\u300C" + (target.name || target.id) + "\u300D\u4F7F\u63D2\u4EF6\u53D8\u66F4\u751F\u6548\u2026");
24166
24034
  if (ctx.events) ctx.events.append("plugin_restart_started", { name: target.name || target.id, target: target.id, kind });
24167
24035
  try {
24168
- ctx.instances.stopInstance(target.id);
24036
+ await ctx.instances.stopInstance(target.id);
24169
24037
  } catch (e) {
24170
24038
  log("\u505C\u6B62\u5B9E\u4F8B\u5931\u8D25: " + e.message);
24171
24039
  }
@@ -24189,28 +24057,25 @@ var require_restart3 = __commonJS({
24189
24057
  return true;
24190
24058
  }
24191
24059
  if (target.kind === "native") {
24192
- if (!targetRunning(ctx, target)) {
24193
- log("\u539F\u751F DSH \u672A\u8FD0\u884C\uFF1A\u63D2\u4EF6\u53D8\u66F4\u5C06\u5728\u4E0B\u6B21\u542F\u52A8\u65F6\u751F\u6548");
24060
+ const nr = ctx.onNativeRestart;
24061
+ if (typeof nr !== "function") {
24062
+ 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");
24194
24063
  return false;
24195
24064
  }
24196
- if (typeof ctx.onNativeRestart === "function") {
24197
- let rr;
24198
- try {
24199
- rr = ctx.onNativeRestart();
24200
- } catch (e) {
24201
- log("\u539F\u751F DSH \u91CD\u542F\u8BF7\u6C42\u5931\u8D25: " + e.message);
24202
- return false;
24203
- }
24204
- if (rr && rr.ok === false) {
24205
- log("\u539F\u751F DSH \u91CD\u542F\u8BF7\u6C42\u672A\u751F\u6548\uFF1A" + (rr && rr.error || "unknown"));
24206
- return false;
24207
- }
24208
- log("\u5DF2\u8BF7\u6C42\u91CD\u542F\u539F\u751F DSH \u4F7F\u63D2\u4EF6\u53D8\u66F4\u751F\u6548");
24209
- if (ctx.events) ctx.events.append("plugin_restart_done", { name: "\u539F\u751F\u5B9E\u4F8B", target: "native", kind, via: "supervisor" });
24210
- return true;
24065
+ let rr;
24066
+ try {
24067
+ rr = ctx.onNativeRestart();
24068
+ } catch (e) {
24069
+ log("\u539F\u751F DSH \u91CD\u542F\u8BF7\u6C42\u5931\u8D25: " + e.message);
24070
+ return false;
24211
24071
  }
24212
- 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");
24213
- return false;
24072
+ if (rr && rr.ok === false) {
24073
+ log("\u539F\u751F DSH \u91CD\u542F\u8BF7\u6C42\u672A\u751F\u6548\uFF1A" + (rr && rr.error || "unknown"));
24074
+ return false;
24075
+ }
24076
+ log("\u5DF2\u8BF7\u6C42\u91CD\u542F\u539F\u751F DSH \u4F7F\u63D2\u4EF6\u53D8\u66F4\u751F\u6548");
24077
+ if (ctx.events) ctx.events.append("plugin_restart_done", { name: "\u539F\u751F\u5B9E\u4F8B", target: "native", kind, via: "supervisor" });
24078
+ return true;
24214
24079
  }
24215
24080
  return false;
24216
24081
  } catch (e) {
@@ -24784,7 +24649,7 @@ var require_plugin = __commonJS({
24784
24649
  var store = require_store6();
24785
24650
  var targets = require_targets();
24786
24651
  var cli = require_cli();
24787
- var restart = require_restart3();
24652
+ var restart = require_restart2();
24788
24653
  var { createJobs } = require_jobs();
24789
24654
  var { createLayers } = require_layers();
24790
24655
  var ops = require_ops3();
@@ -24902,7 +24767,8 @@ var require_managed_object = __commonJS({
24902
24767
  "sandbox-instance": { label: "\u6C99\u7BB1\u5B9E\u4F8B", startable: true, guardable: true },
24903
24768
  "router-daemon": { label: "\u667A\u80FD\u8DEF\u7531 daemon", startable: true, guardable: true },
24904
24769
  "lan-daemon": { label: "\u8FDC\u7A0B\u63A7\u5236 daemon", startable: true, guardable: true },
24905
- plugin: { label: "\u63D2\u4EF6\uFF08\u805A\u5408\uFF09", startable: false, guardable: false }
24770
+ plugin: { label: "\u63D2\u4EF6\uFF08\u805A\u5408\uFF09", startable: false, guardable: false },
24771
+ shell: { label: "\u684C\u9762\u58F3", startable: false, guardable: false }
24906
24772
  };
24907
24773
  var _customKinds = {};
24908
24774
  function kindMeta(kind) {
@@ -25034,9 +24900,17 @@ var require_registry4 = __commonJS({
25034
24900
  var path2 = require("node:path");
25035
24901
  var { writeAtomic } = require_fs();
25036
24902
  var { DESIRED, MANAGED_KINDS, kindMeta, registerKind: registerManagedKind, createEntry, normalizeOwnership } = require_managed_object();
24903
+ var { legacyToEntryPhase } = require_phase();
25037
24904
  var PHASES = ["stopped", "installing", "starting", "running", "draining", "failed"];
25038
- var LEGACY_PHASES = { backoff: "failed", restarting: "starting" };
25039
24905
  var { runHeartbeat } = require_heartbeat();
24906
+ var pmClient = require_pm_client();
24907
+ var configDefaults = require_config();
24908
+ var RESTART_FALLBACK = { windowMs: 6e4, burst: 5 };
24909
+ var restartDefaults = () => {
24910
+ const r = configDefaults && configDefaults.restart;
24911
+ if (r && Number.isFinite(r.windowMs) && Number.isFinite(r.burst)) return { windowMs: r.windowMs, burst: r.burst };
24912
+ return RESTART_FALLBACK;
24913
+ };
25040
24914
  var ManagedRegistry = class {
25041
24915
  constructor(opts) {
25042
24916
  this.file = opts && opts.file;
@@ -25082,7 +24956,8 @@ var require_registry4 = __commonJS({
25082
24956
  try {
25083
24957
  if (!o || !kindMeta(o.kind)) continue;
25084
24958
  const e = createEntry({ kind: o.kind, id: o.id, name: o.name, desired: o.desired, ownership: o.ownership });
25085
- if (PHASES.includes(LEGACY_PHASES[o.phase] || o.phase)) e.phase = LEGACY_PHASES[o.phase] || o.phase;
24959
+ const np = legacyToEntryPhase(o.phase);
24960
+ if (PHASES.includes(np)) e.phase = np;
25086
24961
  if (Number.isInteger(o.restartCount) && o.restartCount >= 0) e.restartCount = o.restartCount;
25087
24962
  if (o.startupFailWindowStart === null || typeof o.startupFailWindowStart === "number") e.startupFailWindowStart = o.startupFailWindowStart;
25088
24963
  if (Number.isInteger(o.startupFailCount) && o.startupFailCount >= 0) e.startupFailCount = o.startupFailCount;
@@ -25150,8 +25025,6 @@ var require_registry4 = __commonJS({
25150
25025
  const i = this._objects.indexOf(e);
25151
25026
  if (i >= 0) this._objects.splice(i, 1);
25152
25027
  }
25153
- // 自定义 kind 扩展点:零调用但刻意保留(见 managed-object.js 的说明)——
25154
- // 它是「新增受管对象类型」的唯一入口,删除等于永久关闭扩展能力。
25155
25028
  registerKind(kind, meta) {
25156
25029
  registerManagedKind(kind, meta);
25157
25030
  return this;
@@ -25171,6 +25044,8 @@ var require_registry4 = __commonJS({
25171
25044
  this._syncPortsOwner(e, true);
25172
25045
  this._save();
25173
25046
  this._event("managed_object_registered", { kind: e.kind, id: e.id, name: e.name });
25047
+ const rd = restartDefaults();
25048
+ pmClient.register({ id: e.id, spawn_cmd: [], restart_window_ms: rd.windowMs, restart_burst: rd.burst });
25174
25049
  return e;
25175
25050
  }
25176
25051
  update(id, patch) {
@@ -25180,6 +25055,7 @@ var require_registry4 = __commonJS({
25180
25055
  if (p.desired !== void 0) {
25181
25056
  if (!DESIRED.includes(p.desired)) return { ok: false, error: "\u975E\u6CD5 desired: " + p.desired };
25182
25057
  e.desired = p.desired;
25058
+ pmClient.setDesired(e.id, p.desired);
25183
25059
  }
25184
25060
  if (p.name !== void 0) e.name = String(p.name || e.id);
25185
25061
  if (p.ownership !== void 0) {
@@ -25211,6 +25087,7 @@ var require_registry4 = __commonJS({
25211
25087
  this._drop(e);
25212
25088
  this._save();
25213
25089
  this._event("managed_object_removed", { kind: e.kind, id: e.id });
25090
+ pmClient.setDesired(e.id, "stopped");
25214
25091
  return { ok: true };
25215
25092
  }
25216
25093
  _releasePort(port, ownerId) {
@@ -25255,6 +25132,17 @@ var require_registry4 = __commonJS({
25255
25132
  };
25256
25133
  return e;
25257
25134
  }
25135
+ setRestartCount(id, n) {
25136
+ const e = this.get(id);
25137
+ if (!e) return null;
25138
+ const v = typeof n === "number" && n >= 0 ? Math.floor(n) : 0;
25139
+ if (e.restartCount !== v) {
25140
+ e.restartCount = v;
25141
+ this._event("managed_object_restart_count", { kind: e.kind, id: e.id, count: v });
25142
+ this._save();
25143
+ }
25144
+ return e;
25145
+ }
25258
25146
  setPhase(id, p) {
25259
25147
  const e = this.get(id);
25260
25148
  if (!e) return null;
@@ -25264,6 +25152,7 @@ var require_registry4 = __commonJS({
25264
25152
  e.lastTransitionAt = (/* @__PURE__ */ new Date()).toISOString();
25265
25153
  this._event("managed_object_phase", { kind: e.kind, id: e.id, phase: p });
25266
25154
  this._save();
25155
+ pmClient.onPhase(e.id, p);
25267
25156
  }
25268
25157
  return e;
25269
25158
  }
@@ -26106,7 +25995,6 @@ var require_installer = __commonJS({
26106
25995
  _manifest() {
26107
25996
  return manifest.read(this);
26108
25997
  }
26109
- // W5:生产零调用,仅 native-test.js 使用(写 manifest 的夹具入口)。保留为测试缝。
26110
25998
  _saveManifest(m) {
26111
25999
  return manifest.save(this, m);
26112
26000
  }
@@ -26423,6 +26311,8 @@ var require_domains = __commonJS({
26423
26311
  }
26424
26312
  host.router = new RouterService({
26425
26313
  config: host.config,
26314
+ beatScheduler: host._beatScheduler,
26315
+ tickMs: host.config.tickIntervalMs || 5e3,
26426
26316
  providerFile: path2.join(swDir, "providers.json"),
26427
26317
  usageTotalsFile: path2.join(swDir, "router-usage-totals.json"),
26428
26318
  logger: host.logger,
@@ -26444,7 +26334,6 @@ var require_domains = __commonJS({
26444
26334
  tasks: host.tasks,
26445
26335
  tokenService: host.tokenService,
26446
26336
  dshBin: host.config.command && host.config.command[1] ? host.config.command[1] : "dsh",
26447
- // 实例域限流参数:与主链同一条规则、按域参数化(窗口 600s > 5 × 30s 启动窗口);单源取自域常量。
26448
26337
  throttle: THROTTLE_DEFAULTS
26449
26338
  });
26450
26339
  host.instances.load();
@@ -26561,7 +26450,6 @@ var require_adapters = __commonJS({
26561
26450
  kind: "lan",
26562
26451
  name: "\u8FDC\u7A0B\u63A7\u5236",
26563
26452
  logger,
26564
- // start 的结果必须来自真实执行:reconcile/syncFrpc 各自如实返回 ok,任一失败即 ok:false(此前恒 true)。
26565
26453
  start: async () => {
26566
26454
  try {
26567
26455
  const rec = await lan.reconcile();
@@ -26809,7 +26697,7 @@ var require_lifecycle3 = __commonJS({
26809
26697
  return send(200, { sessionState: sup.sessionState ? sup.sessionState() : "unknown" });
26810
26698
  }
26811
26699
  if (req.method === "POST" && pathname === "/session/stop") {
26812
- if (!originAllowed(req, sup.config.apiPort)) {
26700
+ if (!originAllowed(req)) {
26813
26701
  req.resume();
26814
26702
  return send(403, { ok: false, error: "cross-origin request rejected" });
26815
26703
  }
@@ -26832,7 +26720,7 @@ var require_lifecycle3 = __commonJS({
26832
26720
  return lc ? send(200, lc.snapshot()) : send(404, { error: "\u6A21\u5757\u672A\u6CE8\u518C: " + id });
26833
26721
  }
26834
26722
  if (req.method === "POST" && action) {
26835
- if (!originAllowed(req, sup.config.apiPort)) {
26723
+ if (!originAllowed(req)) {
26836
26724
  req.resume();
26837
26725
  return send(403, { ok: false, error: "cross-origin request rejected" });
26838
26726
  }
@@ -26937,7 +26825,7 @@ var require_native = __commonJS({
26937
26825
  });
26938
26826
  }
26939
26827
  if (req.method === "POST" && pathname === "/native/check-update") {
26940
- if (!originAllowed(req, sup.config.apiPort)) {
26828
+ if (!originAllowed(req)) {
26941
26829
  req.resume();
26942
26830
  return send(403, { ok: false, error: "cross-origin request rejected" });
26943
26831
  }
@@ -26945,7 +26833,7 @@ var require_native = __commonJS({
26945
26833
  return;
26946
26834
  }
26947
26835
  if (req.method === "POST" && pathname === "/native/install") {
26948
- if (!originAllowed(req, sup.config.apiPort)) {
26836
+ if (!originAllowed(req)) {
26949
26837
  req.resume();
26950
26838
  return send(403, { ok: false, error: "cross-origin request rejected" });
26951
26839
  }
@@ -26963,7 +26851,7 @@ var require_native = __commonJS({
26963
26851
  return;
26964
26852
  }
26965
26853
  if (req.method === "POST" && pathname === "/native/upgrade") {
26966
- if (!originAllowed(req, sup.config.apiPort)) {
26854
+ if (!originAllowed(req)) {
26967
26855
  req.resume();
26968
26856
  return send(403, { ok: false, error: "cross-origin request rejected" });
26969
26857
  }
@@ -26984,7 +26872,7 @@ var require_native = __commonJS({
26984
26872
  return;
26985
26873
  }
26986
26874
  if (req.method === "POST" && pathname === "/native/uninstall") {
26987
- if (!originAllowed(req, sup.config.apiPort)) {
26875
+ if (!originAllowed(req)) {
26988
26876
  req.resume();
26989
26877
  return send(403, { ok: false, error: "cross-origin request rejected" });
26990
26878
  }
@@ -26993,7 +26881,7 @@ var require_native = __commonJS({
26993
26881
  return send(202, { ok: true, accepted: true, state: "uninstalling" });
26994
26882
  }
26995
26883
  if (req.method === "POST" && pathname === "/native/settings") {
26996
- if (!originAllowed(req, sup.config.apiPort)) {
26884
+ if (!originAllowed(req)) {
26997
26885
  req.resume();
26998
26886
  return send(403, { ok: false, error: "cross-origin request rejected" });
26999
26887
  }
@@ -27055,14 +26943,14 @@ var require_guard = __commonJS({
27055
26943
  }
27056
26944
  if (req.method === "POST" && pathname === "/guard/version/check") {
27057
26945
  req.resume();
27058
- if (!originAllowed(req, sup.config.apiPort)) return send(403, { ok: false, error: "cross-origin request rejected" });
26946
+ if (!originAllowed(req)) return send(403, { ok: false, error: "cross-origin request rejected" });
27059
26947
  return sup.guardVersionCheck().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27060
26948
  }
27061
26949
  if (req.method === "GET" && pathname === "/autostart") {
27062
26950
  return send(200, sup.autostartStatus());
27063
26951
  }
27064
26952
  if (req.method === "POST" && pathname === "/autostart") {
27065
- if (!originAllowed(req, sup.config.apiPort)) {
26953
+ if (!originAllowed(req)) {
27066
26954
  req.resume();
27067
26955
  return send(403, { ok: false, error: "cross-origin request rejected" });
27068
26956
  }
@@ -27083,7 +26971,7 @@ var require_guard = __commonJS({
27083
26971
  return send(200, sup.lanPanelStatus());
27084
26972
  }
27085
26973
  if (req.method === "POST" && pathname === "/settings/lan") {
27086
- if (!originAllowed(req, sup.config.apiPort)) {
26974
+ if (!originAllowed(req)) {
27087
26975
  req.resume();
27088
26976
  return send(403, { ok: false, error: "cross-origin request rejected" });
27089
26977
  }
@@ -27104,7 +26992,7 @@ var require_guard = __commonJS({
27104
26992
  return send(200, sup.accessKeyStatus());
27105
26993
  }
27106
26994
  if (req.method === "POST" && pathname === "/settings/access-key") {
27107
- if (!originAllowed(req, sup.config.apiPort)) {
26995
+ if (!originAllowed(req)) {
27108
26996
  req.resume();
27109
26997
  return send(403, { ok: false, error: "cross-origin request rejected" });
27110
26998
  }
@@ -27126,7 +27014,7 @@ var require_guard = __commonJS({
27126
27014
  return send(200, sup.closeActionStatus());
27127
27015
  }
27128
27016
  if (req.method === "POST" && pathname === "/settings/close-action") {
27129
- if (!originAllowed(req, sup.config.apiPort)) {
27017
+ if (!originAllowed(req)) {
27130
27018
  req.resume();
27131
27019
  return send(403, {});
27132
27020
  }
@@ -27147,7 +27035,7 @@ var require_guard = __commonJS({
27147
27035
  return send(200, sup.externalBrowserStatus());
27148
27036
  }
27149
27037
  if (req.method === "POST" && pathname === "/settings/external-browser") {
27150
- if (!originAllowed(req, sup.config.apiPort)) {
27038
+ if (!originAllowed(req)) {
27151
27039
  req.resume();
27152
27040
  return send(403, {});
27153
27041
  }
@@ -27169,7 +27057,7 @@ var require_guard = __commonJS({
27169
27057
  return;
27170
27058
  }
27171
27059
  if (req.method === "POST" && pathname === "/shutdown") {
27172
- if (!originAllowed(req, sup.config.apiPort)) {
27060
+ if (!originAllowed(req)) {
27173
27061
  req.resume();
27174
27062
  return send(403, {});
27175
27063
  }
@@ -27184,7 +27072,7 @@ var require_guard = __commonJS({
27184
27072
  return;
27185
27073
  }
27186
27074
  if (req.method === "GET" && pathname === "/self-update/status") {
27187
- if (!originAllowed(req, sup.config.apiPort)) {
27075
+ if (!originAllowed(req)) {
27188
27076
  req.resume();
27189
27077
  return send(403, {});
27190
27078
  }
@@ -27210,14 +27098,14 @@ var require_guard = __commonJS({
27210
27098
  });
27211
27099
  }
27212
27100
  if (req.method === "GET" && pathname === "/env/dsh") {
27213
- if (!originAllowed(req, sup.config.apiPort)) {
27101
+ if (!originAllowed(req)) {
27214
27102
  req.resume();
27215
27103
  return send(403, {});
27216
27104
  }
27217
27105
  return send(200, sup.dshenvStatus());
27218
27106
  }
27219
27107
  if (req.method === "GET" && pathname === "/env/status") {
27220
- if (!originAllowed(req, sup.config.apiPort)) {
27108
+ if (!originAllowed(req)) {
27221
27109
  req.resume();
27222
27110
  return send(403, {});
27223
27111
  }
@@ -27227,7 +27115,7 @@ var require_guard = __commonJS({
27227
27115
  return sup.nodeLtsStatus().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27228
27116
  }
27229
27117
  if (req.method === "GET" && pathname === "/env/environment") {
27230
- if (!originAllowed(req, sup.config.apiPort)) {
27118
+ if (!originAllowed(req)) {
27231
27119
  req.resume();
27232
27120
  return send(403, { ok: false, error: "cross-origin request rejected" });
27233
27121
  }
@@ -27235,7 +27123,7 @@ var require_guard = __commonJS({
27235
27123
  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) }));
27236
27124
  }
27237
27125
  if (req.method === "GET" && pathname === "/env/environment/last") {
27238
- if (!originAllowed(req, sup.config.apiPort)) {
27126
+ if (!originAllowed(req)) {
27239
27127
  req.resume();
27240
27128
  return send(403, { ok: false, error: "cross-origin request rejected" });
27241
27129
  }
@@ -27246,7 +27134,7 @@ var require_guard = __commonJS({
27246
27134
  req.resume();
27247
27135
  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" });
27248
27136
  }
27249
- if (!originAllowed(req, sup.config.apiPort)) {
27137
+ if (!originAllowed(req)) {
27250
27138
  req.resume();
27251
27139
  return send(403, { ok: false, error: "cross-origin request rejected" });
27252
27140
  }
@@ -27291,7 +27179,7 @@ var require_router4 = __commonJS({
27291
27179
  return Promise.resolve(sup.routerStatusView()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, running: false, error: e && e.message || String(e) }));
27292
27180
  }
27293
27181
  if (req.method === "POST" && pathname.startsWith("/router/")) {
27294
- if (!originAllowed(req, sup.config.apiPort)) {
27182
+ if (!originAllowed(req)) {
27295
27183
  req.resume();
27296
27184
  return send(403, { ok: false, error: "cross-origin request rejected" });
27297
27185
  }
@@ -27308,7 +27196,7 @@ var require_router4 = __commonJS({
27308
27196
  return Promise.resolve(sup.routerProviders()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27309
27197
  }
27310
27198
  if (req.method === "POST" && pathname === "/router/providers/add") {
27311
- if (!originAllowed(req, sup.config.apiPort)) {
27199
+ if (!originAllowed(req)) {
27312
27200
  req.resume();
27313
27201
  return send(403, {});
27314
27202
  }
@@ -27327,7 +27215,7 @@ var require_router4 = __commonJS({
27327
27215
  return;
27328
27216
  }
27329
27217
  if (req.method === "POST" && pathname === "/router/proxy/login/start") {
27330
- if (!originAllowed(req, sup.config.apiPort)) {
27218
+ if (!originAllowed(req)) {
27331
27219
  req.resume();
27332
27220
  return send(403, {});
27333
27221
  }
@@ -27335,7 +27223,7 @@ var require_router4 = __commonJS({
27335
27223
  return;
27336
27224
  }
27337
27225
  if (req.method === "POST" && pathname === "/router/proxy/login/wait") {
27338
- if (!originAllowed(req, sup.config.apiPort)) {
27226
+ if (!originAllowed(req)) {
27339
27227
  req.resume();
27340
27228
  return send(403, {});
27341
27229
  }
@@ -27352,7 +27240,7 @@ var require_router4 = __commonJS({
27352
27240
  return;
27353
27241
  }
27354
27242
  if (req.method === "POST" && pathname === "/router/proxy/update/check") {
27355
- if (!originAllowed(req, sup.config.apiPort)) {
27243
+ if (!originAllowed(req)) {
27356
27244
  req.resume();
27357
27245
  return send(403, {});
27358
27246
  }
@@ -27360,7 +27248,7 @@ var require_router4 = __commonJS({
27360
27248
  return;
27361
27249
  }
27362
27250
  if (req.method === "POST" && pathname === "/router/proxy/update/apply") {
27363
- if (!originAllowed(req, sup.config.apiPort)) {
27251
+ if (!originAllowed(req)) {
27364
27252
  req.resume();
27365
27253
  return send(403, {});
27366
27254
  }
@@ -27379,7 +27267,7 @@ var require_router4 = __commonJS({
27379
27267
  return Promise.resolve(sup.routerApi().proxyUpdateStatus(u.searchParams.get("appId") || "")).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27380
27268
  }
27381
27269
  if (req.method === "POST" && pathname === "/router/providers/proxy/key") {
27382
- if (!originAllowed(req, sup.config.apiPort)) {
27270
+ if (!originAllowed(req)) {
27383
27271
  req.resume();
27384
27272
  return send(403, {});
27385
27273
  }
@@ -27394,7 +27282,7 @@ var require_router4 = __commonJS({
27394
27282
  return;
27395
27283
  }
27396
27284
  if (req.method === "POST" && pathname === "/router/providers/proxy/select") {
27397
- if (!originAllowed(req, sup.config.apiPort)) {
27285
+ if (!originAllowed(req)) {
27398
27286
  req.resume();
27399
27287
  return send(403, {});
27400
27288
  }
@@ -27409,7 +27297,7 @@ var require_router4 = __commonJS({
27409
27297
  return;
27410
27298
  }
27411
27299
  if (req.method === "POST" && pathname === "/router/providers/proxy/key/remove") {
27412
- if (!originAllowed(req, sup.config.apiPort)) {
27300
+ if (!originAllowed(req)) {
27413
27301
  req.resume();
27414
27302
  return send(403, {});
27415
27303
  }
@@ -27424,7 +27312,7 @@ var require_router4 = __commonJS({
27424
27312
  return;
27425
27313
  }
27426
27314
  if (req.method === "POST" && pathname === "/router/providers/keys/set") {
27427
- if (!originAllowed(req, sup.config.apiPort)) {
27315
+ if (!originAllowed(req)) {
27428
27316
  req.resume();
27429
27317
  return send(403, {});
27430
27318
  }
@@ -27440,7 +27328,7 @@ var require_router4 = __commonJS({
27440
27328
  return;
27441
27329
  }
27442
27330
  if (req.method === "POST" && pathname === "/router/providers/remove") {
27443
- if (!originAllowed(req, sup.config.apiPort)) {
27331
+ if (!originAllowed(req)) {
27444
27332
  req.resume();
27445
27333
  return send(403, {});
27446
27334
  }
@@ -27455,7 +27343,7 @@ var require_router4 = __commonJS({
27455
27343
  return;
27456
27344
  }
27457
27345
  if (req.method === "POST" && pathname === "/router/providers/key/use") {
27458
- if (!originAllowed(req, sup.config.apiPort)) {
27346
+ if (!originAllowed(req)) {
27459
27347
  req.resume();
27460
27348
  return send(403, {});
27461
27349
  }
@@ -27471,7 +27359,7 @@ var require_router4 = __commonJS({
27471
27359
  return;
27472
27360
  }
27473
27361
  if (req.method === "POST" && pathname === "/router/providers/account/discard") {
27474
- if (!originAllowed(req, sup.config.apiPort)) {
27362
+ if (!originAllowed(req)) {
27475
27363
  req.resume();
27476
27364
  return send(403, {});
27477
27365
  }
@@ -27486,7 +27374,7 @@ var require_router4 = __commonJS({
27486
27374
  return;
27487
27375
  }
27488
27376
  if (req.method === "POST" && pathname === "/router/providers/refresh") {
27489
- if (!originAllowed(req, sup.config.apiPort)) {
27377
+ if (!originAllowed(req)) {
27490
27378
  req.resume();
27491
27379
  return send(403, {});
27492
27380
  }
@@ -27502,7 +27390,7 @@ var require_router4 = __commonJS({
27502
27390
  return;
27503
27391
  }
27504
27392
  if (req.method === "POST" && pathname === "/router/providers/activate") {
27505
- if (!originAllowed(req, sup.config.apiPort)) {
27393
+ if (!originAllowed(req)) {
27506
27394
  req.resume();
27507
27395
  return send(403, {});
27508
27396
  }
@@ -27518,7 +27406,7 @@ var require_router4 = __commonJS({
27518
27406
  return;
27519
27407
  }
27520
27408
  if (req.method === "POST" && pathname === "/router/providers/deactivate") {
27521
- if (!originAllowed(req, sup.config.apiPort)) {
27409
+ if (!originAllowed(req)) {
27522
27410
  req.resume();
27523
27411
  return send(403, {});
27524
27412
  }
@@ -27568,7 +27456,7 @@ var require_plugins = __commonJS({
27568
27456
  return send(200, sup.pluginManager.installStatus(u.searchParams.get("job")));
27569
27457
  }
27570
27458
  if (req.method === "POST" && pathname.startsWith("/plugins/")) {
27571
- if (!originAllowed(req, sup.config.apiPort)) {
27459
+ if (!originAllowed(req)) {
27572
27460
  req.resume();
27573
27461
  return send(403, { ok: false, error: "cross-origin request rejected" });
27574
27462
  }
@@ -27644,7 +27532,7 @@ var require_dist = __commonJS({
27644
27532
  return;
27645
27533
  }
27646
27534
  if (req.method === "POST" && pathname === "/dist/registry/set") {
27647
- if (!originAllowed(req, sup.config.apiPort)) {
27535
+ if (!originAllowed(req)) {
27648
27536
  req.resume();
27649
27537
  return send(403, {});
27650
27538
  }
@@ -27663,7 +27551,7 @@ var require_dist = __commonJS({
27663
27551
  return;
27664
27552
  }
27665
27553
  if (req.method === "POST" && pathname === "/dist/registry/refresh") {
27666
- if (!originAllowed(req, sup.config.apiPort)) {
27554
+ if (!originAllowed(req)) {
27667
27555
  req.resume();
27668
27556
  return send(403, {});
27669
27557
  }
@@ -27671,7 +27559,7 @@ var require_dist = __commonJS({
27671
27559
  return;
27672
27560
  }
27673
27561
  if (req.method === "POST" && pathname === "/dist/registry/probe") {
27674
- if (!originAllowed(req, sup.config.apiPort)) {
27562
+ if (!originAllowed(req)) {
27675
27563
  req.resume();
27676
27564
  return send(403, {});
27677
27565
  }
@@ -27742,6 +27630,112 @@ var require_exchange = __commonJS({
27742
27630
  }
27743
27631
  });
27744
27632
 
27633
+ // src/platform/security/identity.js
27634
+ var require_identity2 = __commonJS({
27635
+ "src/platform/security/identity.js"(exports2, module2) {
27636
+ "use strict";
27637
+ var { normalizeRemoteAddress, isLoopbackAddress } = require_ip();
27638
+ function socketIsLoopback(req) {
27639
+ return isLoopbackAddress(req && req.socket && req.socket.remoteAddress);
27640
+ }
27641
+ function identify(req) {
27642
+ return {
27643
+ remote: normalizeRemoteAddress(req && req.socket && req.socket.remoteAddress),
27644
+ loopback: socketIsLoopback(req)
27645
+ };
27646
+ }
27647
+ module2.exports = { identify };
27648
+ }
27649
+ });
27650
+
27651
+ // src/api/identity.js
27652
+ var require_identity3 = __commonJS({
27653
+ "src/api/identity.js"(exports2, module2) {
27654
+ "use strict";
27655
+ var { normalizeRemoteAddress, isLoopbackAddress, isPrivateIpv4 } = require_ip();
27656
+ var { identify } = require_identity2();
27657
+ module2.exports = {
27658
+ identify,
27659
+ normalizeRemoteAddress,
27660
+ isPrivateIpv4,
27661
+ isLoopbackAddress
27662
+ };
27663
+ }
27664
+ });
27665
+
27666
+ // src/api/security.js
27667
+ var require_security = __commonJS({
27668
+ "src/api/security.js"(exports2, module2) {
27669
+ "use strict";
27670
+ var crypto = require("node:crypto");
27671
+ var { isPrivateIpv4 } = require_identity3();
27672
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
27673
+ function isLocalOrLanHost(h) {
27674
+ if (!h) return false;
27675
+ const s = String(h).toLowerCase();
27676
+ if (LOOPBACK_HOSTS.has(s)) return true;
27677
+ const bare = s.startsWith("[") && s.endsWith("]") ? s.slice(1, -1) : s;
27678
+ if (bare === "::1") return true;
27679
+ return isPrivateIpv4(bare);
27680
+ }
27681
+ function isShellOrigin(protocol, hostname) {
27682
+ const h = String(hostname || "").toLowerCase();
27683
+ if (protocol === "tauri:") return h === "localhost" || /(^|\.)tauri\.localhost$/.test(h);
27684
+ if (protocol === "http:" || protocol === "https:") return h === "tauri.localhost";
27685
+ return false;
27686
+ }
27687
+ function normalizeHostname(h) {
27688
+ const s = String(h || "").toLowerCase();
27689
+ return s.startsWith("[") && s.endsWith("]") ? s.slice(1, -1) : s;
27690
+ }
27691
+ function originAllowed(req, apiPort) {
27692
+ const host = req.headers.host;
27693
+ if (!host) return false;
27694
+ let hostname = "";
27695
+ {
27696
+ const m = /^(\[[^\]]+\]|[^:]+)(?::\d+)?$/.exec(String(host).trim());
27697
+ hostname = m ? m[1] : String(host).trim();
27698
+ if (!isLocalOrLanHost(hostname)) return false;
27699
+ }
27700
+ const o = req.headers.origin;
27701
+ if (!o) return true;
27702
+ try {
27703
+ const u = new URL(o);
27704
+ if (isShellOrigin(u.protocol, u.hostname)) return true;
27705
+ if (!isLocalOrLanHost(u.hostname)) return false;
27706
+ if (host && normalizeHostname(u.hostname) !== normalizeHostname(hostname)) return false;
27707
+ const port = u.port === "" ? u.protocol === "https:" ? "443" : "80" : u.port;
27708
+ return port === String(apiPort);
27709
+ } catch {
27710
+ return false;
27711
+ }
27712
+ }
27713
+ function safeKeyEqual(a, b) {
27714
+ const ha = crypto.createHash("sha256").update(String(a || "")).digest();
27715
+ const hb = crypto.createHash("sha256").update(String(b || "")).digest();
27716
+ return crypto.timingSafeEqual(ha, hb);
27717
+ }
27718
+ function requestHasAccessKey(req, key) {
27719
+ if (!key) return true;
27720
+ const ah = req.headers.authorization;
27721
+ if (typeof ah === "string" && ah.startsWith("Bearer ") && safeKeyEqual(ah.slice(7), key)) return true;
27722
+ try {
27723
+ const q = new URL(req.url, "http://localhost").searchParams.get("access_key");
27724
+ if (q && safeKeyEqual(q, key)) return true;
27725
+ } catch {
27726
+ }
27727
+ return false;
27728
+ }
27729
+ function liveApiPort(server, sup) {
27730
+ const live = server && typeof server.address === "function" ? server.address() : null;
27731
+ if (live && Number.isInteger(live.port) && live.port > 0) return live.port;
27732
+ const cfg = sup && sup.config && sup.config.apiPort;
27733
+ return Number.isInteger(cfg) ? cfg : null;
27734
+ }
27735
+ module2.exports = { originAllowed, isLocalOrLanHost, isShellOrigin, requestHasAccessKey, liveApiPort };
27736
+ }
27737
+ });
27738
+
27745
27739
  // src/api/domains/instances.js
27746
27740
  var require_instances = __commonJS({
27747
27741
  "src/api/domains/instances.js"(exports2, module2) {
@@ -27749,6 +27743,7 @@ var require_instances = __commonJS({
27749
27743
  var execPath = require_exec_path();
27750
27744
  var crypto = require("node:crypto");
27751
27745
  var { bootstrapDshCookie } = require_exchange();
27746
+ var { liveApiPort } = require_security();
27752
27747
  var OPEN_WEB_CODES = /* @__PURE__ */ new Map();
27753
27748
  var OPEN_WEB_CODE_TTL_MS = 3e4;
27754
27749
  function _sweepExpiredOpenWebCodes() {
@@ -27782,16 +27777,16 @@ var require_instances = __commonJS({
27782
27777
  return (sup.instances.list() || []).find((x) => x.id === id) || null;
27783
27778
  }
27784
27779
  function handleOpen(ctx) {
27785
- const { sup, req, res, identity, originAllowed, tokOf } = ctx;
27780
+ const { sup, req, res, identity, originAllowed, tokOf, server } = ctx;
27786
27781
  const url = new URL(req.url, "http://localhost");
27787
- const apiPort = sup.config.apiPort;
27782
+ const apiPort = liveApiPort(server, sup);
27788
27783
  const code = url.searchParams.get("code");
27789
27784
  const deny = (status, msg) => {
27790
27785
  res.writeHead(status, { "Content-Type": "text/plain; charset=utf-8" });
27791
27786
  res.end(msg);
27792
27787
  };
27793
27788
  if (!identity.loopback) return deny(403, "\u4EC5\u5141\u8BB8\u672C\u673A\u8BBF\u95EE");
27794
- if (!originAllowed(req, apiPort)) return deny(403, "origin not allowed");
27789
+ if (!originAllowed(req)) return deny(403, "origin not allowed");
27795
27790
  const rec = consumeOpenWebCode(code);
27796
27791
  if (!rec) return deny(code ? 400 : 404, code ? "\u6388\u6743\u7801\u65E0\u6548\u6216\u5DF2\u8FC7\u671F" : "\u7F3A\u5C11\u6388\u6743\u7801");
27797
27792
  const it = findInstanceOrMain(sup, rec.id);
@@ -27857,7 +27852,7 @@ var require_instances = __commonJS({
27857
27852
  return sharedErr + "\uFF1B" + FORMS + "\uFF1B\u9700\u8981\u5176\u5B83\u53EF\u6267\u884C\u8BF7\u8D70\u63D2\u4EF6\u5B89\u88C5\u901A\u9053";
27858
27853
  }
27859
27854
  function handle(ctx) {
27860
- const { sup, req, res, pathname, identity, send, collectBody, originAllowed, tokOf, browser } = ctx;
27855
+ const { sup, req, res, pathname, identity, send, collectBody, originAllowed, tokOf, browser, server } = ctx;
27861
27856
  function openInSystemBrowser(url) {
27862
27857
  return browser.openBrowser(url, { logger: sup.logger });
27863
27858
  }
@@ -27884,7 +27879,7 @@ var require_instances = __commonJS({
27884
27879
  return render();
27885
27880
  }
27886
27881
  if (req.method === "POST" && pathname.startsWith("/instances/")) {
27887
- if (!originAllowed(req, sup.config.apiPort)) {
27882
+ if (!originAllowed(req)) {
27888
27883
  req.resume();
27889
27884
  return send(403, {});
27890
27885
  }
@@ -27921,7 +27916,7 @@ var require_instances = __commonJS({
27921
27916
  if (!it) return send(404, { ok: false, error: "\u5B9E\u4F8B\u4E0D\u5B58\u5728" });
27922
27917
  if (!(Number(it.port) > 0)) return send(400, { ok: false, error: "\u975E\u6CD5\u7AEF\u53E3" });
27923
27918
  const code = issueOpenWebCode(j.id);
27924
- const url = "http://127.0.0.1:" + sup.config.apiPort + "/open?code=" + code;
27919
+ const url = "http://127.0.0.1:" + (liveApiPort(server, sup) || sup.config.apiPort) + "/open?code=" + code;
27925
27920
  return Promise.resolve(openInSystemBrowser(url)).then((r) => {
27926
27921
  if (!r.ok) dropOpenWebCode(code);
27927
27922
  return send(r.ok ? 200 : 500, r);
@@ -27966,7 +27961,7 @@ var require_relay = __commonJS({
27966
27961
  return Promise.resolve(sup.listLan()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27967
27962
  }
27968
27963
  if (req.method === "POST" && pathname.startsWith("/remote/")) {
27969
- if (!originAllowed(req, sup.config.apiPort)) {
27964
+ if (!originAllowed(req)) {
27970
27965
  req.resume();
27971
27966
  return send(403, {});
27972
27967
  }
@@ -28029,7 +28024,7 @@ var require_shell2 = __commonJS({
28029
28024
  }
28030
28025
  }
28031
28026
  if (req.method === "POST" && pathname === "/shell/health") {
28032
- if (!originAllowed(req, sup.config.apiPort)) {
28027
+ if (!originAllowed(req)) {
28033
28028
  req.resume();
28034
28029
  return send(403, {});
28035
28030
  }
@@ -28046,28 +28041,8 @@ var require_shell2 = __commonJS({
28046
28041
  }
28047
28042
  });
28048
28043
  }
28049
- if (req.method === "POST" && pathname === "/shell/update-pending") {
28050
- if (!originAllowed(req, sup.config.apiPort)) {
28051
- req.resume();
28052
- return send(403, {});
28053
- }
28054
- return collectBody(req, res, 8192, (body) => {
28055
- let j = {};
28056
- try {
28057
- j = body ? JSON.parse(body) : {};
28058
- } catch {
28059
- }
28060
- try {
28061
- const rec = shell.markPending(j.from || null, j.to || null);
28062
- if (sup.events) sup.events.append(BRAND2.EVENT_SHELL_UPDATE_PENDING, { from: rec.from, to: rec.to });
28063
- return send(200, { ok: true, journal: rec });
28064
- } catch (e) {
28065
- return send(500, { ok: false, error: e.message });
28066
- }
28067
- });
28068
- }
28069
28044
  if (req.method === "POST" && pathname === "/shell/check-update") {
28070
- if (!originAllowed(req, sup.config.apiPort)) {
28045
+ if (!originAllowed(req)) {
28071
28046
  req.resume();
28072
28047
  return send(403, {});
28073
28048
  }
@@ -28079,23 +28054,6 @@ var require_shell2 = __commonJS({
28079
28054
  return send(200, r);
28080
28055
  }).catch((e) => send(500, { ok: false, error: e.message }));
28081
28056
  }
28082
- if (req.method === "POST" && pathname === "/shell/restart") {
28083
- if (!originAllowed(req, sup.config.apiPort)) {
28084
- req.resume();
28085
- return send(403, {});
28086
- }
28087
- if (typeof sup._sessionHalting === "function" && sup._sessionHalting()) {
28088
- req.resume();
28089
- return send(409, { ok: false, error: "\u4F1A\u8BDD\u5DF2\u9000\u51FA/\u9000\u51FA\u4E2D\uFF0C\u62D2\u7EDD\u91CD\u542F\u684C\u9762\u58F3" });
28090
- }
28091
- req.resume();
28092
- return Promise.resolve(shell.restartShell({
28093
- shouldAbort: () => typeof sup._sessionHalting === "function" && sup._sessionHalting()
28094
- })).then((r) => {
28095
- if (sup.events) sup.events.append(BRAND2.EVENT_SHELL_RESTART_REQUESTED, { ok: r.ok, killed: r.killed || [], pid: r.pid || null });
28096
- return send(r.ok ? 200 : 500, r);
28097
- }).catch((e) => send(500, { ok: false, error: e.message }));
28098
- }
28099
28057
  if (req.method === "GET" || req.method === "POST") return send(404, { error: "not found", path: pathname });
28100
28058
  return send(405, { error: "method not allowed" });
28101
28059
  }
@@ -28259,106 +28217,6 @@ var require_static = __commonJS({
28259
28217
  }
28260
28218
  });
28261
28219
 
28262
- // src/platform/security/identity.js
28263
- var require_identity2 = __commonJS({
28264
- "src/platform/security/identity.js"(exports2, module2) {
28265
- "use strict";
28266
- var { normalizeRemoteAddress, isLoopbackAddress } = require_ip();
28267
- function socketIsLoopback(req) {
28268
- return isLoopbackAddress(req && req.socket && req.socket.remoteAddress);
28269
- }
28270
- function identify(req) {
28271
- return {
28272
- remote: normalizeRemoteAddress(req && req.socket && req.socket.remoteAddress),
28273
- loopback: socketIsLoopback(req)
28274
- };
28275
- }
28276
- module2.exports = { identify };
28277
- }
28278
- });
28279
-
28280
- // src/api/identity.js
28281
- var require_identity3 = __commonJS({
28282
- "src/api/identity.js"(exports2, module2) {
28283
- "use strict";
28284
- var { normalizeRemoteAddress, isLoopbackAddress, isPrivateIpv4 } = require_ip();
28285
- var { identify } = require_identity2();
28286
- module2.exports = {
28287
- identify,
28288
- normalizeRemoteAddress,
28289
- isPrivateIpv4,
28290
- isLoopbackAddress
28291
- };
28292
- }
28293
- });
28294
-
28295
- // src/api/security.js
28296
- var require_security = __commonJS({
28297
- "src/api/security.js"(exports2, module2) {
28298
- "use strict";
28299
- var crypto = require("node:crypto");
28300
- var { isPrivateIpv4 } = require_identity3();
28301
- var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
28302
- function isLocalOrLanHost(h) {
28303
- if (!h) return false;
28304
- const s = String(h).toLowerCase();
28305
- if (LOOPBACK_HOSTS.has(s)) return true;
28306
- const bare = s.startsWith("[") && s.endsWith("]") ? s.slice(1, -1) : s;
28307
- if (bare === "::1") return true;
28308
- return isPrivateIpv4(bare);
28309
- }
28310
- function isShellOrigin(protocol, hostname) {
28311
- const h = String(hostname || "").toLowerCase();
28312
- if (protocol === "tauri:") return h === "localhost" || /(^|\.)tauri\.localhost$/.test(h);
28313
- if (protocol === "http:" || protocol === "https:") return h === "tauri.localhost";
28314
- return false;
28315
- }
28316
- function normalizeHostname(h) {
28317
- const s = String(h || "").toLowerCase();
28318
- return s.startsWith("[") && s.endsWith("]") ? s.slice(1, -1) : s;
28319
- }
28320
- function originAllowed(req, apiPort) {
28321
- const host = req.headers.host;
28322
- if (!host) return false;
28323
- let hostname = "";
28324
- {
28325
- const m = /^(\[[^\]]+\]|[^:]+)(?::\d+)?$/.exec(String(host).trim());
28326
- hostname = m ? m[1] : String(host).trim();
28327
- if (!isLocalOrLanHost(hostname)) return false;
28328
- }
28329
- const o = req.headers.origin;
28330
- if (!o) return true;
28331
- try {
28332
- const u = new URL(o);
28333
- if (isShellOrigin(u.protocol, u.hostname)) return true;
28334
- if (!isLocalOrLanHost(u.hostname)) return false;
28335
- if (host && normalizeHostname(u.hostname) !== normalizeHostname(hostname)) return false;
28336
- const port = u.port === "" ? u.protocol === "https:" ? "443" : "80" : u.port;
28337
- return port === String(apiPort);
28338
- } catch {
28339
- return false;
28340
- }
28341
- }
28342
- function safeKeyEqual(a, b) {
28343
- const ha = crypto.createHash("sha256").update(String(a || "")).digest();
28344
- const hb = crypto.createHash("sha256").update(String(b || "")).digest();
28345
- return crypto.timingSafeEqual(ha, hb);
28346
- }
28347
- function requestHasAccessKey(req, key) {
28348
- if (!key) return true;
28349
- const ah = req.headers.authorization;
28350
- if (typeof ah === "string" && ah.startsWith("Bearer ") && safeKeyEqual(ah.slice(7), key)) return true;
28351
- try {
28352
- const q = new URL(req.url, "http://localhost").searchParams.get("access_key");
28353
- if (q && safeKeyEqual(q, key)) return true;
28354
- } catch {
28355
- }
28356
- return false;
28357
- }
28358
- module2.exports = { originAllowed, isLocalOrLanHost, isShellOrigin, requestHasAccessKey };
28359
- }
28360
- });
28361
-
28362
28220
  // src/api/transport/server.js
28363
28221
  var require_server = __commonJS({
28364
28222
  "src/api/transport/server.js"(exports2, module2) {
@@ -28368,7 +28226,7 @@ var require_server = __commonJS({
28368
28226
  var { collectBody } = require_body();
28369
28227
  var { serveStatic } = require_static();
28370
28228
  var { identify } = require_identity3();
28371
- var { originAllowed, requestHasAccessKey, isShellOrigin } = require_security();
28229
+ var { originAllowed, requestHasAccessKey, isShellOrigin, liveApiPort } = require_security();
28372
28230
  var browserExit = require_browser();
28373
28231
  var environmentExit = require_environment();
28374
28232
  function safeFail(res, err, where) {
@@ -28391,7 +28249,7 @@ var require_server = __commonJS({
28391
28249
  function createServer(sup, deps) {
28392
28250
  const browser = deps && deps.browser || browserExit;
28393
28251
  const environment = deps && deps.environment || environmentExit;
28394
- return http2.createServer((req, res) => {
28252
+ const server = http2.createServer((req, res) => {
28395
28253
  const shellOrigin = (() => {
28396
28254
  const o = req.headers.origin;
28397
28255
  if (!o) return null;
@@ -28421,6 +28279,7 @@ var require_server = __commonJS({
28421
28279
  return "";
28422
28280
  };
28423
28281
  const identity = identify(req);
28282
+ const originAllowedLive = (req2) => originAllowed(req2, liveApiPort(server, sup));
28424
28283
  const accessKey = sup && sup.config && sup.config.apiAccessKey || null;
28425
28284
  if (!identity.loopback && req.method !== "OPTIONS" && (!accessKey || !requestHasAccessKey(req, accessKey))) {
28426
28285
  res.writeHead(401, { "Content-Type": "application/json" });
@@ -28458,7 +28317,7 @@ var require_server = __commonJS({
28458
28317
  } catch (e) {
28459
28318
  return send(400, { error: "bad request encoding" });
28460
28319
  }
28461
- const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed, tokOf, browser, environment };
28320
+ const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed: originAllowedLive, tokOf, browser, environment, server };
28462
28321
  for (const d of API_DOMAINS) {
28463
28322
  if (d.owns(pathname)) {
28464
28323
  try {
@@ -28484,6 +28343,7 @@ var require_server = __commonJS({
28484
28343
  }
28485
28344
  return send(405, { error: "method not allowed" });
28486
28345
  });
28346
+ return server;
28487
28347
  }
28488
28348
  module2.exports = { createServer };
28489
28349
  }
@@ -28503,7 +28363,7 @@ var require_api = __commonJS({
28503
28363
  var require_session = __commonJS({
28504
28364
  "src/domains/relay/session.js"(exports2, module2) {
28505
28365
  "use strict";
28506
- var { cookieByName } = require_core4();
28366
+ var { cookieByName } = require_core3();
28507
28367
  var { bootstrapDshCookie } = require_exchange();
28508
28368
  function createSession(opts) {
28509
28369
  const o = opts || {};
@@ -28645,7 +28505,7 @@ var require_tunnel = __commonJS({
28645
28505
  "src/domains/relay/tunnel.js"(exports2, module2) {
28646
28506
  "use strict";
28647
28507
  var net = require("node:net");
28648
- var { isTrustedSource, hasValidToken, upstreamPath } = require_core4();
28508
+ var { isTrustedSource, hasValidToken, upstreamPath } = require_core3();
28649
28509
  function buildRawRequest(req, authority, cookie) {
28650
28510
  let raw = req.method + " " + upstreamPath(req.url) + " HTTP/1.1\r\n";
28651
28511
  for (const [k, v] of Object.entries(req.headers)) {
@@ -28731,7 +28591,7 @@ var require_proxy2 = __commonJS({
28731
28591
  "use strict";
28732
28592
  var http2 = require("node:http");
28733
28593
  var crypto = require("node:crypto");
28734
- var { isTrustedSource, tokenGateDecision, backoffGate, upstreamPath, redactLogPath, normalizeRemoteMode, POLYFILL_SCRIPT } = require_core4();
28594
+ var { isTrustedSource, tokenGateDecision, backoffGate, upstreamPath, redactLogPath, normalizeRemoteMode, POLYFILL_SCRIPT } = require_core3();
28735
28595
  var { createSession } = require_session();
28736
28596
  var { createTunnelHandler } = require_tunnel();
28737
28597
  var HTML_INJECT_MAX_BYTES = 2 * 1024 * 1024;
@@ -28756,7 +28616,7 @@ var require_proxy2 = __commonJS({
28756
28616
  }
28757
28617
  };
28758
28618
  }
28759
- function pipeWithHold(ur, res, clientReqPath, logger) {
28619
+ function pipeWithHold(ur, res, clientReqLog, logger) {
28760
28620
  let clientGone = false;
28761
28621
  const log = (lv, msg) => {
28762
28622
  if (logger && logger[lv]) {
@@ -28805,7 +28665,7 @@ var require_proxy2 = __commonJS({
28805
28665
  return headers;
28806
28666
  }
28807
28667
  function handleUpstream(ur, res, clientReqPath, onStatus, logger) {
28808
- const clientReqLog2 = redactLogPath(clientReqPath);
28668
+ const clientReqLog = redactLogPath(clientReqPath);
28809
28669
  if (onStatus) onStatus(ur.statusCode);
28810
28670
  const h = { ...ur.headers };
28811
28671
  const isHtml = String(h["content-type"] || "").includes("text/html");
@@ -28817,7 +28677,7 @@ var require_proxy2 = __commonJS({
28817
28677
  }
28818
28678
  res.writeHead(ur.statusCode || 502, h);
28819
28679
  if (!isHtml) {
28820
- pipeWithHold(ur, res, clientReqPath, logger);
28680
+ pipeWithHold(ur, res, clientReqLog, logger);
28821
28681
  return;
28822
28682
  }
28823
28683
  const chunks = [];
@@ -28851,7 +28711,7 @@ var require_proxy2 = __commonJS({
28851
28711
  chunks.push(c);
28852
28712
  return;
28853
28713
  }
28854
- 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 || ""));
28714
+ 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 || ""));
28855
28715
  const selfForward = !ur.readableEnded;
28856
28716
  switchToPassThrough();
28857
28717
  if (selfForward) res.write(c);
@@ -29031,14 +28891,8 @@ var require_ports3 = __commonJS({
29031
28891
  return registry.claimSlot(segment, owner, {
29032
28892
  preferred: o.preferred,
29033
28893
  bindingPreferred: !!o.bindingPreferred,
29034
- // RL-4:跨账本防撞。router caller 不传 reservedPorts(分配器跳过该检查,行为不变);
29035
- // 仅 relay 传入 router 账本已占端口,free-scan/preferred-claim 跳过 ⇒ 不会抢 router 的端口。
29036
28894
  reservedPorts: reservedPorts || void 0,
29037
28895
  onBindingLost: o.onBindingLost,
29038
- // 审计 RL-3:reclaimCmdMark 必须出现在 relay daemon 的真实命令行里,否则 pgrepList 永远匹配不到 ⇒ 回收是死代码。
29039
- // relay daemon 经 runtime.js 以 `node .../domains/relay/daemon.js -c <cfg>` 拉起(scripts.js:8),
29040
- // 命令行含 `domains/relay/daemon.js`(与 process.js#reclaimOrphans 用的 _cmdMarks 同源),故此处用同一标记。
29041
- // configPath 为空时 fail-closed 不回收:宁可留占用走冲突分支,也不按 cmdMark 误杀同名进程(reclaimByCmdMark 内部也已要求 cfgStr 必填)。
29042
28896
  reclaimCmdMark: "domains/relay/daemon.js",
29043
28897
  reclaimCfg: o.configPath || "",
29044
28898
  waitMs: 8e3
@@ -29237,7 +29091,7 @@ var require_frp = __commonJS({
29237
29091
  var path2 = require("node:path");
29238
29092
  var spawnOS = require_spawn();
29239
29093
  var { writeAtomic } = require_fs();
29240
- var { buildFrpcToml, validateFrpServerSettings } = require_core4();
29094
+ var { buildFrpcToml, validateFrpServerSettings } = require_core3();
29241
29095
  var { frpPlatformTag, download, installFrpc } = require_frp_install();
29242
29096
  var FrpManager = class {
29243
29097
  constructor(opts) {
@@ -29681,7 +29535,7 @@ var require_ops5 = __commonJS({
29681
29535
  var managed = require_managed();
29682
29536
  var portsvc = require_ports3();
29683
29537
  var { FrpManager } = require_frp();
29684
- var { normalizeRemoteMode, validateWanAccess, normalizeFrpSettings, validateFrpServerSettings, projectRemoteView } = require_core4();
29538
+ var { normalizeRemoteMode, validateWanAccess, normalizeFrpSettings, validateFrpServerSettings, projectRemoteView } = require_core3();
29685
29539
  var reconcile = require_reconcile();
29686
29540
  var lanServers = require_lan_servers();
29687
29541
  function applyRelayToken(host, existing, want) {