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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/core.cjs CHANGED
@@ -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.10");
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);
@@ -8559,7 +8424,7 @@ var require_bootstrap = __commonJS({
8559
8424
  }
8560
8425
  }
8561
8426
  }
8562
- module2.exports = { _bootstrap, _startShellWatchdog, _registerFixedPorts, _bindNativeDshCommand, _refreshEnvironmentForm };
8427
+ module2.exports = { _bootstrap, _registerFixedPorts, _bindNativeDshCommand, _refreshEnvironmentForm };
8563
8428
  }
8564
8429
  });
8565
8430
 
@@ -8615,14 +8480,17 @@ var require_api_rebind = __commonJS({
8615
8480
  });
8616
8481
  server.listen(host.config.apiPort, host.config.apiHost, () => {
8617
8482
  bind._tries = 0;
8483
+ const bound = server.address();
8484
+ const realPort = bound && Number.isInteger(bound.port) ? bound.port : host.config.apiPort;
8618
8485
  host.api = server;
8486
+ if (realPort !== host.config.apiPort) host.config.apiPort = realPort;
8619
8487
  try {
8620
- portsShared.registerSole("supervisor-api", host.config.apiPort);
8488
+ portsShared.registerSole("supervisor-api", realPort);
8621
8489
  } catch (e) {
8622
8490
  host.logger.warn("ports.registerSole(supervisor-api) \u5931\u8D25: " + (e && e.message || e));
8623
8491
  }
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);
8492
+ host.events.append("api_listening", { host: host.config.apiHost, port: realPort });
8493
+ host.logger.info("api listening on " + host.config.apiHost + ":" + realPort);
8626
8494
  });
8627
8495
  };
8628
8496
  bind._tries = 0;
@@ -8644,19 +8512,21 @@ var require_api_rebind = __commonJS({
8644
8512
  host.logger.error("api error: " + (err ? err.message : String(err)));
8645
8513
  });
8646
8514
  server.listen(port, host.config.apiHost, () => {
8515
+ const bound = server.address();
8516
+ const realPort = bound && Number.isInteger(bound.port) ? bound.port : port;
8647
8517
  host.api = server;
8648
8518
  const prev = host.config.apiPort;
8649
- if (port !== prev) {
8650
- host.config.apiPort = port;
8651
- if (host.configPath) host.persistConfigPatch({ apiPort: port });
8519
+ if (realPort !== prev) {
8520
+ host.config.apiPort = realPort;
8521
+ if (host.configPath) host.persistConfigPatch({ apiPort: realPort });
8652
8522
  }
8653
8523
  try {
8654
- portsShared.registerSole("supervisor-api", port);
8524
+ portsShared.registerSole("supervisor-api", realPort);
8655
8525
  } catch (e) {
8656
8526
  host.logger.warn("ports.registerSole(supervisor-api) \u5931\u8D25: " + e.message);
8657
8527
  }
8658
- host.events.append("api_listening", { host: host.config.apiHost, port });
8659
- host.logger.info("api listening on " + host.config.apiHost + ":" + port);
8528
+ host.events.append("api_listening", { host: host.config.apiHost, port: realPort });
8529
+ host.logger.info("api listening on " + host.config.apiHost + ":" + realPort);
8660
8530
  });
8661
8531
  return server;
8662
8532
  };
@@ -10099,14 +9969,10 @@ var require_shutdown = __commonJS({
10099
9969
  host.events.append("guard_exit", {});
10100
9970
  host.logger.info("guard shutting down");
10101
9971
  host.writeState(true);
10102
- if (host._timer) clearInterval(host._timer);
10103
- if (host._heartbeatTimer) clearInterval(host._heartbeatTimer);
9972
+ if (host._beatScheduler) host._beatScheduler.stop();
10104
9973
  if (host._killTimer) clearTimeout(host._killTimer);
10105
9974
  if (host._adoptKillTimer) clearTimeout(host._adoptKillTimer);
10106
9975
  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
9976
  if (host.api) {
10111
9977
  try {
10112
9978
  host.api.close();
@@ -10151,37 +10017,10 @@ var require_shutdown = __commonJS({
10151
10017
  } catch (e) {
10152
10018
  host.logger.warn && host.logger.warn("shutdownAll persist shellHalted: " + e.message);
10153
10019
  }
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
10020
  host.logger.info("[session] \u9000\u51FA\u6D41\u7A0B\u5F00\u59CB\uFF1A\u505C\u6B62\u5168\u90E8\u88AB\u7BA1\u5BF9\u8C61\u2026");
10183
10021
  host.events && host.events.append("shutdown_all", {});
10184
10022
  abortInflightNpm(host, "session-exit");
10023
+ if (host._beatScheduler) host._beatScheduler.stop();
10185
10024
  host._stopMainDsh();
10186
10025
  await host._stopAllSandboxes();
10187
10026
  const stopDaemon = async (kind) => {
@@ -10417,6 +10256,12 @@ var require_instance_adapter = __commonJS({
10417
10256
  } catch (e) {
10418
10257
  d.logger() && d.logger().warn && d.logger().warn("sandbox setPhase: " + (e && e.message || e));
10419
10258
  }
10259
+ try {
10260
+ const rc = Number(inst.state && inst.state.restartCount) || 0;
10261
+ if (entry.restartCount !== rc) d.managedObjects().setRestartCount(entry.id, rc);
10262
+ } catch (e) {
10263
+ d.logger() && d.logger().warn && d.logger().warn("sandbox setRestartCount: " + (e && e.message || e));
10264
+ }
10420
10265
  }
10421
10266
  }
10422
10267
  };
@@ -11034,6 +10879,7 @@ var require_process2 = __commonJS({
11034
10879
  var native = require_command();
11035
10880
  var BRAND2 = require_brand();
11036
10881
  var { findManagedDshPort, applyMainPort } = require_port_rederive();
10882
+ var pmClient = require_pm_client();
11037
10883
  var DEPS = /* @__PURE__ */ new WeakMap();
11038
10884
  var HELPERS = [
11039
10885
  "MissingNotified",
@@ -11089,7 +10935,6 @@ var require_process2 = __commonJS({
11089
10935
  const d = depsOf(this);
11090
10936
  return native.nativeCommand(d.config(), d.pluginManager());
11091
10937
  },
11092
- // 启动窗口(毫秒):唯一的时间常量,spawn 时落成 startDeadline,窗口内退出即「启动失败」。
11093
10938
  startWindowMs() {
11094
10939
  const d = depsOf(this);
11095
10940
  const secs = Number(d.config().startsecs);
@@ -11169,6 +11014,7 @@ var require_process2 = __commonJS({
11169
11014
  errBuf.flush();
11170
11015
  if (d.mChild() !== child) return;
11171
11016
  d.events().append(BRAND2.EVENT_HARNESS_EXITED, { code, signal, phase: d.state().phase() });
11017
+ pmClient.recordExit("main", code, signal, d.state().phase() === "STARTING");
11172
11018
  d.mSetChild(null);
11173
11019
  if (d.stopping()) return;
11174
11020
  if (d.state().desired() !== "running") return;
@@ -11178,6 +11024,7 @@ var require_process2 = __commonJS({
11178
11024
  const why = code !== null ? String(code) : "sig" + signal;
11179
11025
  const inStartup = phase === "STARTING";
11180
11026
  if (inStartup || d.state().guardian()) {
11027
+ pmClient.requestRestart("main");
11181
11028
  d.beginRestart("exit:" + why, { startupFailure: inStartup });
11182
11029
  } else {
11183
11030
  d.writeCrashHalted(true);
@@ -11254,8 +11101,6 @@ var require_process2 = __commonJS({
11254
11101
  d.tokenService().scheduleCapture("main");
11255
11102
  d.state().write();
11256
11103
  },
11257
- // 重启:活过 startsecs 后退出(或人工重启)⇒ 正常重启,不记启动失败;窗口内退出 ⇒ 记一次启动失败并由限流裁决。
11258
- // 相位只有 STARTING / RUNNING / FAILED:重启=回到 STARTING(下一次 spawn 会开新的 startsecs 窗口)。
11259
11104
  _beginRestart(reason, opts) {
11260
11105
  const d = depsOf(this);
11261
11106
  const manual = !!(opts && opts.manual);
@@ -11396,7 +11241,6 @@ var require_signals = __commonJS({
11396
11241
  return null;
11397
11242
  }
11398
11243
  },
11399
- // 归属凭据:spawn 与 adopt 两条取得所有权的路线都要写;落盘走 writeAtomic + 0600。
11400
11244
  _writeMainOwner(dshPid, port) {
11401
11245
  const d = depsOf(this);
11402
11246
  try {
@@ -11452,7 +11296,6 @@ var require_signals = __commonJS({
11452
11296
  }
11453
11297
  }, d.config().stopGraceMs));
11454
11298
  },
11455
- // 代际 gen 保护共享的 _adoptKillTimer 槽位:定时器只在「本代仍是当前代」时置空。
11456
11299
  _killAdopted(pid) {
11457
11300
  const d = depsOf(this);
11458
11301
  const gen = (d.readAdoptKillGen() || 0) + 1;
@@ -11517,7 +11360,35 @@ var require_guardian = __commonJS({
11517
11360
  }
11518
11361
  return { start, count, tripped: count >= burst };
11519
11362
  }
11520
- module2.exports = { shouldGuard, bumpStartupFailure };
11363
+ function makeBudget(cfg) {
11364
+ const windowMs = cfg && Number(cfg.windowMs) > 0 ? Number(cfg.windowMs) : 6e4;
11365
+ const burst = cfg && Number(cfg.burst) >= 1 ? Number(cfg.burst) : 5;
11366
+ let state = { start: null, count: 0 };
11367
+ const self = {
11368
+ windowMs,
11369
+ burst,
11370
+ get count() {
11371
+ return state.count;
11372
+ },
11373
+ get tripped() {
11374
+ return state.count >= burst;
11375
+ },
11376
+ note(startupFailure) {
11377
+ if (startupFailure === false || startupFailure == null) {
11378
+ return { tripped: false, count: state.count, halted: false };
11379
+ }
11380
+ const dec = bumpStartupFailure(state, Date.now(), { windowMs, burst });
11381
+ state.start = dec.start;
11382
+ state.count = dec.count;
11383
+ return { tripped: dec.tripped, count: state.count, halted: dec.tripped };
11384
+ },
11385
+ reset() {
11386
+ state = { start: null, count: 0 };
11387
+ }
11388
+ };
11389
+ return self;
11390
+ }
11391
+ module2.exports = { shouldGuard, bumpStartupFailure, makeBudget };
11521
11392
  }
11522
11393
  });
11523
11394
 
@@ -11526,6 +11397,7 @@ var require_startup_throttle = __commonJS({
11526
11397
  "src/app/main/startup-throttle.js"(exports2, module2) {
11527
11398
  "use strict";
11528
11399
  var guardian = require_guardian();
11400
+ var pmClient = require_pm_client();
11529
11401
  var DEPS = /* @__PURE__ */ new WeakMap();
11530
11402
  function depsOf(host) {
11531
11403
  let d = DEPS.get(host);
@@ -11565,8 +11437,6 @@ var require_startup_throttle = __commonJS({
11565
11437
  }
11566
11438
  module2.exports = {
11567
11439
  methods: {
11568
- // 唯一一条限流:startupFailWindowMs 内「启动窗口内退出」达 startupFailBurst 次 ⇒ phase=FAILED。
11569
- // 到点即停自动重启:不排队、不退避、不指数等待;恢复的唯一入口是人工重试(_retryStartupFailure)。
11570
11440
  _noteStartupFailure() {
11571
11441
  const d = depsOf(this);
11572
11442
  const windowMs = d.config().startupFailWindowMs || 6e4;
@@ -11591,11 +11461,11 @@ var require_startup_throttle = __commonJS({
11591
11461
  );
11592
11462
  return { failed: true, count: dec.count };
11593
11463
  },
11594
- // 手动重试入口:清计数后回到 STARTING(随后由 STARTING 分支重新 spawn,新的 startsecs 窗口)。
11595
11464
  _retryStartupFailure() {
11596
11465
  const d = depsOf(this);
11597
11466
  d.mSetStartupFailWindowStart(null);
11598
11467
  d.mSetStartupFailCount(0);
11468
+ pmClient.resetBackoff("main");
11599
11469
  d.events().append("startup_retry", {});
11600
11470
  d.logger().warn("\u542F\u52A8\u5931\u8D25\u8BA1\u6570\u5DF2\u6E05\u96F6\uFF1A\u4EBA\u5DE5\u91CD\u8BD5\uFF0C\u56DE\u5230 STARTING");
11601
11471
  return { ok: true };
@@ -12145,8 +12015,6 @@ var require_runtime2 = __commonJS({
12145
12015
  spawnEnv: () => ({ DSH_SUPERVISOR_CONFIG: cfgPath }),
12146
12016
  logger: d.logger(),
12147
12017
  events: d.events(),
12148
- // `this` 是宿主(本方法经 host._daemonLifecycle(kind) 调用);箭头函数捕获它。
12149
- // 此前写作 host._exitIntended(),而 host 只存在于 depsOf(host) 形参作用域 ⇒ 延迟回调求值时 ReferenceError。
12150
12018
  exitIntended: () => this._exitIntended()
12151
12019
  });
12152
12020
  return lc[kind];
@@ -13014,7 +12882,7 @@ var require_credential = __commonJS({
13014
12882
  });
13015
12883
 
13016
12884
  // src/domains/relay/core.js
13017
- var require_core4 = __commonJS({
12885
+ var require_core3 = __commonJS({
13018
12886
  "src/domains/relay/core.js"(exports2, module2) {
13019
12887
  "use strict";
13020
12888
  var crypto = require("node:crypto");
@@ -13042,14 +12910,52 @@ var require_core4 = __commonJS({
13042
12910
  }
13043
12911
  return null;
13044
12912
  }
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 || "/";
12913
+ function stripGateTokenAndPreserve(rawUrl) {
12914
+ if (!rawUrl) return "/";
12915
+ let url = rawUrl;
12916
+ const hashIdx = url.indexOf("#");
12917
+ if (hashIdx >= 0) url = url.slice(0, hashIdx);
12918
+ const qIdx = url.indexOf("?");
12919
+ const path2 = qIdx >= 0 ? url.slice(0, qIdx) : url;
12920
+ if (qIdx < 0) return path2 || "/";
12921
+ const query = url.slice(qIdx + 1);
12922
+ if (!query) return path2 || "/";
12923
+ const kept = [];
12924
+ for (const pair of query.split("&")) {
12925
+ if (!pair) {
12926
+ kept.push(pair);
12927
+ continue;
12928
+ }
12929
+ const eq = pair.indexOf("=");
12930
+ const rawKey = eq >= 0 ? pair.slice(0, eq) : pair;
12931
+ let key = rawKey;
12932
+ try {
12933
+ key = decodeURIComponent(rawKey);
12934
+ } catch {
12935
+ }
12936
+ if (key === "token") continue;
12937
+ kept.push(pair);
13052
12938
  }
12939
+ const out = path2 || "/";
12940
+ return kept.length ? out + "?" + kept.join("&") : out;
12941
+ }
12942
+ function upstreamPath(rawUrl) {
12943
+ return stripGateTokenAndPreserve(rawUrl);
12944
+ }
12945
+ var POLYFILL_SCRIPT = `<script>
12946
+ if (typeof crypto.randomUUID !== 'function') {
12947
+ crypto.randomUUID = function () {
12948
+ var b = crypto.getRandomValues(new Uint8Array(16));
12949
+ b[6] = (b[6] & 0x0f) | 0x40;
12950
+ b[8] = (b[8] & 0x3f) | 0x80;
12951
+ var h = '';
12952
+ for (var i = 0; i < 16; i++) h += (i === 4 || i === 6 || i === 8 || i === 12 ? '-' : '') + ('0' + b[i].toString(16)).slice(-2);
12953
+ return h;
12954
+ };
12955
+ }
12956
+ </script>`;
12957
+ function redactLogPath(rawUrl) {
12958
+ return rawUrl ? stripGateTokenAndPreserve(rawUrl) : "";
13053
12959
  }
13054
12960
  function lanGateCookieValue(token, salt) {
13055
12961
  const t = String(token == null ? "" : token);
@@ -13057,23 +12963,6 @@ var require_core4 = __commonJS({
13057
12963
  if (!t || !s) return "";
13058
12964
  return crypto.createHash("sha256").update(s + "\n" + t).digest("hex");
13059
12965
  }
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
12966
  function tokenGateDecision(req, token, salt, mode) {
13078
12967
  if (!token) return normalizeRemoteMode(mode) === "lan" ? { ok: true } : { ok: false, unauthorized: true };
13079
12968
  const url = new URL(req.url, "http://localhost");
@@ -13096,27 +12985,22 @@ var require_core4 = __commonJS({
13096
12985
  }
13097
12986
  return { ok: false, unauthorized: true };
13098
12987
  }
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=[^&]*/, "");
12988
+ function hasValidToken(req, token, salt, mode) {
12989
+ if (!token) return normalizeRemoteMode(mode) === "lan";
12990
+ const url = new URL(req.url, "http://localhost");
12991
+ const queryToken = url.searchParams.get("token");
12992
+ if (queryToken && safeEqual(queryToken, token)) return true;
12993
+ const cookies = req.headers.cookie || "";
12994
+ const m = LAN_COOKIE_RE.exec(cookies);
12995
+ if (m) {
12996
+ try {
12997
+ const want = lanGateCookieValue(token, salt);
12998
+ return !!want && safeEqual(decodeURIComponent(m[1]), want);
12999
+ } catch {
13000
+ return false;
13001
+ }
13119
13002
  }
13003
+ return false;
13120
13004
  }
13121
13005
  function buildFrpcToml(settings, instances) {
13122
13006
  const s = settings || {};
@@ -13147,8 +13031,6 @@ if (typeof crypto.randomUUID !== 'function') {
13147
13031
  return {
13148
13032
  serverAddr: String(j.serverAddr !== void 0 ? j.serverAddr : cur.serverAddr).trim(),
13149
13033
  serverPort: Number(j.serverPort) || cur.serverPort,
13150
- // authToken:null/undefined 归一为空串(frpc 遇空串不写 auth.token,避免 String(null)==="null" 被当成共享密钥);
13151
- // 非空串统一 trim,与 serverAddr 同口径。
13152
13034
  authToken: j.authToken === void 0 ? cur.authToken : j.authToken == null ? "" : String(j.authToken).trim(),
13153
13035
  user: String(j.user || cur.user || "dsh")
13154
13036
  };
@@ -13231,7 +13113,7 @@ if (typeof crypto.randomUUID !== 'function') {
13231
13113
  var require_lan2 = __commonJS({
13232
13114
  "src/app/domain-actions/lan.js"(exports2, module2) {
13233
13115
  "use strict";
13234
- var { validateWanAccess, generateRemoteToken } = require_core4();
13116
+ var { validateWanAccess, generateRemoteToken } = require_core3();
13235
13117
  var { remoteTokenStrength } = require_credential();
13236
13118
  var BRAND2 = require_brand();
13237
13119
  function lanModule(deps) {
@@ -13598,13 +13480,6 @@ var require_env = __commonJS({
13598
13480
  } catch {
13599
13481
  return null;
13600
13482
  }
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
13483
  })()
13609
13484
  };
13610
13485
  },
@@ -13819,7 +13694,6 @@ var require_versions = __commonJS({
13819
13694
  }
13820
13695
  return dir;
13821
13696
  },
13822
- // 同步 spawn 会冻结事件循环,消费方含 HTTP 路径,故 git 子进程异步执行。
13823
13697
  async guardVersionLocal() {
13824
13698
  const d = depsOf(this);
13825
13699
  const root = d.vcsRoot();
@@ -13876,7 +13750,6 @@ var require_access = __commonJS({
13876
13750
  const cfg = this.config || {};
13877
13751
  return { configured: !!cfg.apiAccessKey, host: cfg.apiHost || void 0 };
13878
13752
  },
13879
- // 清空密钥必须同时回关 LAN(apiHost→127.0.0.1):只清 key 不动 apiHost 会留下「绑定 0.0.0.0 且零认证」的暴露窗口。
13880
13753
  setAccessKey(key) {
13881
13754
  try {
13882
13755
  const cfg = this.config || {};
@@ -14787,7 +14660,6 @@ var require_pool2 = __commonJS({
14787
14660
  this._backfillAt.delete(id);
14788
14661
  this.clear(id);
14789
14662
  }
14790
- // journalctl 同步查询会冻结生命周期 tick 的事件循环,故 journal 档非阻塞发射(见 capture.js)。
14791
14663
  capture(id) {
14792
14664
  const src = this._sources.get(id);
14793
14665
  if (!src) return null;
@@ -15104,6 +14976,35 @@ var require_npm = __commonJS({
15104
14976
  }
15105
14977
  });
15106
14978
 
14979
+ // src/domains/shell/core.js
14980
+ var require_core4 = __commonJS({
14981
+ "src/domains/shell/core.js"(exports2, module2) {
14982
+ "use strict";
14983
+ function isUpdatePhase(phase) {
14984
+ const p = String(phase || "");
14985
+ return p === "restarting" || p.indexOf("shell-update") === 0;
14986
+ }
14987
+ function deriveState(id, journal) {
14988
+ const j = journal || {};
14989
+ if (!j.to) return { state: "idle", reason: "\u65E0\u8FDB\u884C\u4E2D\u7684\u66F4\u65B0", journal: j, identity: id };
14990
+ const cur = id && id.version ? String(id.version) : null;
14991
+ if (cur && cur === j.to && id && id.phase === "ready") {
14992
+ const next = j.confirmed === true ? j : Object.assign({}, j, { confirmed: true });
14993
+ return { state: "confirmed", version: cur, reason: "\u58F3\u5DF2\u5065\u5EB7\u8FD0\u884C\u65B0\u7248\u672C", journal: next, identity: id };
14994
+ }
14995
+ return {
14996
+ state: "pending",
14997
+ target: j.to,
14998
+ current: cur,
14999
+ reason: cur === j.to ? "\u7B49\u5F85\u58F3\u4E0A\u62A5\u5C31\u7EEA" : "\u7B49\u5F85\u58F3\u91CD\u542F\u5230\u65B0\u7248\u672C",
15000
+ journal: j,
15001
+ identity: id
15002
+ };
15003
+ }
15004
+ module2.exports = { isUpdatePhase, deriveState };
15005
+ }
15006
+ });
15007
+
15107
15008
  // src/domains/shell/journal.js
15108
15009
  var require_journal = __commonJS({
15109
15010
  "src/domains/shell/journal.js"(exports2, module2) {
@@ -15112,7 +15013,7 @@ var require_journal = __commonJS({
15112
15013
  var fs2 = require("node:fs");
15113
15014
  var path2 = require("node:path");
15114
15015
  var { writeAtomic } = require_fs();
15115
- var { deriveState } = require_core3();
15016
+ var { deriveState } = require_core4();
15116
15017
  function shellDir() {
15117
15018
  return stateRoot2.shellDir();
15118
15019
  }
@@ -15191,116 +15092,6 @@ var require_journal = __commonJS({
15191
15092
  }
15192
15093
  });
15193
15094
 
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
15095
  // src/domains/shell/index.js
15305
15096
  var require_shell = __commonJS({
15306
15097
  "src/domains/shell/index.js"(exports2, module2) {
@@ -15314,8 +15105,7 @@ var require_shell = __commonJS({
15314
15105
  health,
15315
15106
  status
15316
15107
  } = 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 };
15108
+ module2.exports = { status, evaluate, health, markPending, identity, readJournal, shellDir };
15319
15109
  }
15320
15110
  });
15321
15111
 
@@ -15618,8 +15408,6 @@ var require_intents = __commonJS({
15618
15408
  if (this._pending.has(intent)) this._pending.delete(intent);
15619
15409
  return p;
15620
15410
  }
15621
- // W5:生产零调用,仅 session-lifecycle-test.js 用于「模拟重启后内存态为空」的夹具。
15622
- // 保留(它是一个合理的测试缝),不视为待删死代码。
15623
15411
  clear() {
15624
15412
  this._pending.clear();
15625
15413
  }
@@ -15806,7 +15594,6 @@ var require_core5 = __commonJS({
15806
15594
  routerAutostart: s.routerAutostart,
15807
15595
  routerMode: s.routerMode,
15808
15596
  updateCheck: s.updateCheck,
15809
- shellWatchdog: s.shellWatchdog,
15810
15597
  lastRefresh: s.lastRefresh
15811
15598
  };
15812
15599
  }
@@ -15864,12 +15651,10 @@ var require_core5 = __commonJS({
15864
15651
  host._upgradeHold = false;
15865
15652
  host._upgradeHoldSince = null;
15866
15653
  host._timer = null;
15654
+ host._beatScheduler = null;
15867
15655
  host._heartbeatBusy = false;
15868
15656
  host._killTimer = null;
15869
15657
  host._adoptKillTimer = null;
15870
- host._initialCheckTimer = null;
15871
- host._upgradeTimer = null;
15872
- host._shellWatchdogTimer = null;
15873
15658
  host._lastOccupiedWarn = 0;
15874
15659
  host._lastMainPortRederive = 0;
15875
15660
  host._lastPortUp = false;
@@ -17127,11 +16912,21 @@ var require_instance_lifecycle = __commonJS({
17127
16912
  });
17128
16913
 
17129
16914
  // src/domains/router/providers/restart.js
17130
- var require_restart2 = __commonJS({
16915
+ var require_restart = __commonJS({
17131
16916
  "src/domains/router/providers/restart.js"(exports2, module2) {
17132
16917
  "use strict";
17133
16918
  var { INSTANCE_STATES } = require_model2();
17134
16919
  var ports = require_ports().shared;
16920
+ var { makeBudget } = require_guardian();
16921
+ var PROXY_INSTANCE_THROTTLE = { windowMs: 10 * 60 * 1e3, burst: 5 };
16922
+ function budgetFor(inst) {
16923
+ if (!inst) return makeBudget(PROXY_INSTANCE_THROTTLE);
16924
+ if (!inst._restartBudget) inst._restartBudget = makeBudget(PROXY_INSTANCE_THROTTLE);
16925
+ return inst._restartBudget;
16926
+ }
16927
+ function resetBudget(inst) {
16928
+ if (inst) inst._restartBudget = makeBudget(PROXY_INSTANCE_THROTTLE);
16929
+ }
17135
16930
  function createRestartOrchestrator(deps) {
17136
16931
  const d = deps || {};
17137
16932
  const { startInstance, waitHealthy, isAlive, logger } = d;
@@ -17165,8 +16960,8 @@ var require_restart2 = __commonJS({
17165
16960
  }
17166
16961
  return { respawn };
17167
16962
  }
17168
- async function runReconcile(provider, allowStop) {
17169
- const out = { started: [], stopped: [], desired: [] };
16963
+ async function runReconcileThrottled(provider, allowStop) {
16964
+ const out = { started: [], stopped: [], desired: [], throttled: [] };
17170
16965
  const desired = provider.desiredRunningAccounts();
17171
16966
  const list = desired.list || [];
17172
16967
  out.desired = list.map((a) => a.keyId);
@@ -17175,12 +16970,25 @@ var require_restart2 = __commonJS({
17175
16970
  const inst = provider.instanceOf(acc);
17176
16971
  if (!inst || inst.pid || inst.startingPromise) continue;
17177
16972
  if (inst.status === INSTANCE_STATES.DEAD && !inst.pid) inst.status = INSTANCE_STATES.COLD;
16973
+ const b = budgetFor(inst);
16974
+ if (b.tripped) {
16975
+ inst._throttled = true;
16976
+ out.throttled.push(acc.keyId);
16977
+ continue;
16978
+ }
16979
+ inst._throttled = false;
16980
+ const wasRunning = !!inst.pid;
17178
16981
  try {
17179
16982
  const r = await provider.startInstance(inst);
16983
+ const startupFailure = !r.ok || !wasRunning && !inst.pid;
16984
+ b.note(startupFailure);
17180
16985
  if (r && r.ok) {
17181
16986
  await provider._waitHealthy(inst).catch(() => {
17182
16987
  });
17183
16988
  out.started.push(acc.keyId);
16989
+ if (b.tripped && provider.logger && provider.logger.warn) {
16990
+ provider.logger.warn("[reconcile] \u5B9E\u4F8B\u542F\u52A8\u53CD\u590D\u5931\u8D25\u5DF2\u8FBE\u4E0A\u9650 key=" + (acc.maskedKey || acc.keyId) + "\uFF0C\u505C\u6B62\u81EA\u52A8\u62C9\u8D77\uFF08\u7B49\u4EBA\u5DE5/\u7A97\u53E3\u8FC7\u671F\uFF09");
16991
+ }
17184
16992
  if (provider.logger && provider.logger.info) {
17185
16993
  const role = desired.active && acc.keyId === desired.active.keyId ? "\u5728\u7528" : "\u9884\u70ED";
17186
16994
  provider.logger.info("[reconcile] \u62C9\u8D77\u5B9E\u4F8B key=" + acc.maskedKey + "\uFF08" + role + "\uFF09");
@@ -17189,8 +16997,8 @@ var require_restart2 = __commonJS({
17189
16997
  } catch {
17190
16998
  }
17191
16999
  }
17192
- if (!allowStop) return out;
17193
17000
  for (const inst of (provider.instances || []).slice()) {
17001
+ if (!allowStop) break;
17194
17002
  if (!inst.pid) {
17195
17003
  if (!desiredIds.has(inst.keyId) && inst.port) {
17196
17004
  try {
@@ -17235,7 +17043,7 @@ var require_restart2 = __commonJS({
17235
17043
  }
17236
17044
  }
17237
17045
  if (provider._reconcileBusy) return provider._reconcileBusy;
17238
- const p = runReconcile(provider, allowStop);
17046
+ const p = runReconcileThrottled(provider, allowStop);
17239
17047
  provider._reconcileBusy = p;
17240
17048
  try {
17241
17049
  return await p;
@@ -17248,7 +17056,60 @@ var require_restart2 = __commonJS({
17248
17056
  reconcileInstances(provider).catch(() => {
17249
17057
  });
17250
17058
  }
17251
- module2.exports = { createRestartOrchestrator, runReconcile, reconcileInstances, reconcileNow };
17059
+ module2.exports = { createRestartOrchestrator, runReconcileThrottled, reconcileInstances, reconcileNow, budgetFor, resetBudget, PROXY_INSTANCE_THROTTLE };
17060
+ }
17061
+ });
17062
+
17063
+ // src/app/control/process-lifecycle.js
17064
+ var require_process_lifecycle = __commonJS({
17065
+ "src/app/control/process-lifecycle.js"(exports2, module2) {
17066
+ "use strict";
17067
+ var procOS = require_process();
17068
+ var pidlook = require_pidlookup();
17069
+ var DEFAULTS = {
17070
+ stopGraceMs: 9e3,
17071
+ portFreeTimeoutMs: 5e3
17072
+ };
17073
+ function sleep(ms) {
17074
+ return new Promise((r) => setTimeout(r, ms));
17075
+ }
17076
+ function isForeignOnPort(port, ownPid) {
17077
+ if (!Number.isInteger(port) || port <= 0) return false;
17078
+ const p = pidlook.findListeningPid(port);
17079
+ return p !== null && p !== ownPid;
17080
+ }
17081
+ async function waitPortFree(port, timeoutMs) {
17082
+ const dl = Date.now() + (timeoutMs || DEFAULTS.portFreeTimeoutMs);
17083
+ while (Date.now() < dl) {
17084
+ if (pidlook.findListeningPid(port) === null) return true;
17085
+ await sleep(150);
17086
+ }
17087
+ return pidlook.findListeningPid(port) === null;
17088
+ }
17089
+ async function stopProcess(opts) {
17090
+ const o = Object.assign({}, DEFAULTS, opts || {});
17091
+ const pid = typeof o.pid === "number" ? o.pid : null;
17092
+ const port = typeof o.port === "number" ? o.port : null;
17093
+ const ownGroup = !!o.ownGroup;
17094
+ if (pid && pid > 0) {
17095
+ procOS.killTree(pid, "SIGTERM", void 0, ownGroup ? { ownGroup: true } : void 0);
17096
+ }
17097
+ const dl = Date.now() + o.stopGraceMs;
17098
+ while (Date.now() < dl) {
17099
+ const alive = pid ? pidlook.isAlive ? pidlook.isAlive(pid) : false : false;
17100
+ if (!alive) break;
17101
+ await sleep(150);
17102
+ }
17103
+ if (port) {
17104
+ if (isForeignOnPort(port, pid)) {
17105
+ return { ok: false, stopped: false, foreign: true };
17106
+ }
17107
+ const free = await waitPortFree(port, o.portFreeTimeoutMs);
17108
+ if (!free) return { ok: false, stopped: false, portBusy: true };
17109
+ }
17110
+ return { ok: true, stopped: true };
17111
+ }
17112
+ module2.exports = { ProcessLifecycle: { stopProcess, waitPortFree, isForeignOnPort, DEFAULTS } };
17252
17113
  }
17253
17114
  });
17254
17115
 
@@ -17395,6 +17256,7 @@ var require_probe5 = __commonJS({
17395
17256
  var carrier = require_carrier();
17396
17257
  var procOS = require_process();
17397
17258
  var { INSTANCE_STATES } = require_model2();
17259
+ var { ProcessLifecycle } = require_process_lifecycle();
17398
17260
  var { getQuotaStrategy } = require_quota_strategies();
17399
17261
  var { quotaOverallStatus } = require_quota();
17400
17262
  var { cachedPkgBin, ensurePkgCached } = require_pkg_cache();
@@ -17423,7 +17285,7 @@ var require_probe5 = __commonJS({
17423
17285
  if (provider.logger && provider.logger.warn) provider.logger.warn("[proxy-instance] \u91CD\u542F\u5E78\u5B58\u8005\u5F03\u7528\u91CD\u62C9 pid=" + boundPid + " port=" + inst.port + "\uFF08stdio \u5F52\u5C5E\u65E7\u4EE3\uFF0C\u7981 adopt\uFF09");
17424
17286
  if (provider.events) provider.events.append("proxy_instance_survivor_reclaimed", { app: provider.proxyAppId, port: inst.port, pid: boundPid, reason: "restart-survivor-stdio-unsafe" });
17425
17287
  try {
17426
- procOS.killTree(boundPid, "SIGKILL");
17288
+ ProcessLifecycle.stopProcess({ pid: boundPid, port: inst.port });
17427
17289
  } catch {
17428
17290
  }
17429
17291
  const dl = Date.now() + 3e3;
@@ -17613,7 +17475,7 @@ var require_probe5 = __commonJS({
17613
17475
  if (provider.logger && provider.logger.warn) provider.logger.warn("[proxy-instance] \u751F\u547D\u5468\u671F\u76D1\u63A7\uFF1A\u5B9E\u4F8B\u65E0\u54CD\u5E94\uFF08\u7591\u4F3C\u5361\u6B7B\uFF09key=" + inst.maskedKey + " port=" + inst.port + " pid=" + inst.pid + " fails=" + inst._monitorFails + "\uFF0Ckill \u91CD\u62C9");
17614
17476
  if (provider.events) provider.events.append("proxy_instance_hang_restart", { app: provider.proxyAppId, port: inst.port, pid: inst.pid, fails: inst._monitorFails });
17615
17477
  try {
17616
- procOS.killTree(inst.pid, "SIGKILL", void 0, { ownGroup: true });
17478
+ ProcessLifecycle.stopProcess({ pid: inst.pid, port: inst.port });
17617
17479
  } catch {
17618
17480
  }
17619
17481
  inst.pid = null;
@@ -17768,7 +17630,8 @@ var require_process_pool = __commonJS({
17768
17630
  "src/domains/router/providers/process-pool.js"(exports2, module2) {
17769
17631
  "use strict";
17770
17632
  var life = require_instance_lifecycle();
17771
- var restart = require_restart2();
17633
+ var restart = require_restart();
17634
+ var { resetBudget } = restart;
17772
17635
  var probe = require_probe5();
17773
17636
  var pool = require_pool3();
17774
17637
  var pidlook = require_pidlookup();
@@ -17901,6 +17764,10 @@ var require_process_pool = __commonJS({
17901
17764
  }
17902
17765
  inst._restartAt = Date.now() + 12e4;
17903
17766
  inst._restartPending = null;
17767
+ try {
17768
+ resetBudget(inst);
17769
+ } catch {
17770
+ }
17904
17771
  if (this.logger && this.logger.warn) this.logger.warn("[proxy-instance] \u5B9E\u4F8B\u91CD\u542F key=" + inst.maskedKey + " port=" + inst.port + " reason=" + reason);
17905
17772
  const hadPid = !!inst.pid;
17906
17773
  try {
@@ -18020,7 +17887,7 @@ var require_proxy = __commonJS({
18020
17887
  var { npxLauncher } = require_npx_forms();
18021
17888
  var { buildCommand } = require_command2();
18022
17889
  var probe = require_probe5();
18023
- var restart = require_restart2();
17890
+ var restart = require_restart();
18024
17891
  var pool = require_pool3();
18025
17892
  var life = require_instance_lifecycle();
18026
17893
  var ProxyProvider = class extends withProcessPool(ProviderBase) {
@@ -18384,7 +18251,6 @@ var require_proxy_apps = __commonJS({
18384
18251
  healthPath: "/health",
18385
18252
  modelPath: "/v1/models",
18386
18253
  upstream: "https://api.commandcode.ai",
18387
- // keyEnv 是密钥注入的唯一 env 名(绝不进 cmdline);其余 env/超时一律不注入,跑上游默认配置。
18388
18254
  keyEnv: "CC_API_KEY",
18389
18255
  repo: "thaolaptrinh/commandcode-api-proxy",
18390
18256
  registry: "commandcode-api-proxy",
@@ -19092,16 +18958,6 @@ var require_usage = __commonJS({
19092
18958
  if (!this._dirty) return;
19093
18959
  this._writeTotals();
19094
18960
  }
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
18961
  load() {
19106
18962
  if (this.totals) return this.totals;
19107
18963
  let t;
@@ -20138,6 +19994,7 @@ var require_scheduler2 = __commonJS({
20138
19994
  function createScheduler(deps) {
20139
19995
  const d = deps || {};
20140
19996
  const state = d.state;
19997
+ const beat = d.beatScheduler || null;
20141
19998
  const logger = d.logger || null;
20142
19999
  const refreshProxyUpdateInfo = d.refreshProxyUpdateInfo || (() => Promise.resolve());
20143
20000
  const refreshOfficialUsageAll = d.refreshOfficialUsageAll || (() => Promise.resolve());
@@ -20153,6 +20010,27 @@ var require_scheduler2 = __commonJS({
20153
20010
  });
20154
20011
  ensureProxyInstances().catch(() => {
20155
20012
  });
20013
+ if (beat) {
20014
+ beat.unregister("router-maint");
20015
+ beat.unregister("router-lifecycle");
20016
+ beat.unregister("router-pricing");
20017
+ beat.register("router-maint", () => {
20018
+ refreshProxyUpdateInfo().catch(() => {
20019
+ });
20020
+ probeIfDue();
20021
+ ensureProxyInstances().catch(() => {
20022
+ });
20023
+ }, { every: Math.max(1, Math.round(3e5 / (d.tickMs || 5e3))) });
20024
+ beat.register("router-lifecycle", () => {
20025
+ monitorInstanceHealth().catch(() => {
20026
+ });
20027
+ }, { every: Math.max(1, Math.round(3e4 / (d.tickMs || 5e3))) });
20028
+ beat.register("router-pricing", () => {
20029
+ refreshOfficialPricingAll().catch(() => {
20030
+ });
20031
+ }, { every: Math.max(1, Math.round(216e5 / (d.tickMs || 5e3))) });
20032
+ return;
20033
+ }
20156
20034
  if (state.maintTimer) clearInterval(state.maintTimer);
20157
20035
  state.maintTimer = setInterval(() => {
20158
20036
  refreshProxyUpdateInfo().catch(() => {
@@ -20174,6 +20052,12 @@ var require_scheduler2 = __commonJS({
20174
20052
  }), 6 * 3600 * 1e3);
20175
20053
  }
20176
20054
  function stop() {
20055
+ if (beat) {
20056
+ beat.unregister("router-maint");
20057
+ beat.unregister("router-lifecycle");
20058
+ beat.unregister("router-pricing");
20059
+ return;
20060
+ }
20177
20061
  if (state.maintTimer) {
20178
20062
  clearInterval(state.maintTimer);
20179
20063
  state.maintTimer = null;
@@ -20702,6 +20586,8 @@ var require_router3 = __commonJS({
20702
20586
  var RouterService = class {
20703
20587
  constructor(opts) {
20704
20588
  this.config = opts.config;
20589
+ this._beatScheduler = opts.beatScheduler || null;
20590
+ this._beatTickMs = opts.tickMs || opts.config && opts.config.tickIntervalMs || 5e3;
20705
20591
  this.dist = opts.dist || null;
20706
20592
  this.logger = opts.logger || console;
20707
20593
  this.events = opts.events || null;
@@ -20763,6 +20649,8 @@ var require_router3 = __commonJS({
20763
20649
  state,
20764
20650
  store: this.store,
20765
20651
  logger: this.logger,
20652
+ beatScheduler: this._beatScheduler,
20653
+ tickMs: this._beatTickMs,
20766
20654
  refreshProxyUpdateInfo: () => this.refreshProxyUpdateInfo(),
20767
20655
  refreshOfficialUsageAll: () => this.refreshOfficialUsageAll(),
20768
20656
  refreshOfficialPricingAll: () => this.refreshOfficialPricingAll()
@@ -21340,7 +21228,6 @@ var require_model3 = __commonJS({
21340
21228
  allocation: inst.state ? inst.state.allocation || null : null,
21341
21229
  usage: inst.state ? inst.state.usage || null : null,
21342
21230
  restartCount: inst.state ? inst.state.restartCount || 0 : 0,
21343
- // 启动失败限流(唯一一条规则)的可见量:窗口内计数 / 固定端口释放等待落点。
21344
21231
  startupFailCount: inst.state ? inst.state.startupFailCount || 0 : 0,
21345
21232
  restartAt: inst.state ? inst.state.restartAt || null : null,
21346
21233
  lastFailure: inst.state ? inst.state.lastFailure || null : null,
@@ -21423,12 +21310,6 @@ var require_store5 = __commonJS({
21423
21310
  _stripLegacyStateKeys(inst) {
21424
21311
  if (inst.state && Object.prototype.hasOwnProperty.call(inst.state, "version")) delete inst.state.version;
21425
21312
  }
21426
- /**
21427
- * 隔离损坏文件。
21428
- * Lease I2:隔离前**再次校验版本戳**——若自读取失败以来文件已被他人更新,
21429
- * 说明它不是损坏 ⇒ 放弃隔离(修前会直接 rename 掉这个更新的文件并把实例列表清空,即全量静默丢失)。
21430
- * 且保留可回溯副本:先复制再改名,不直接丢弃。
21431
- */
21432
21313
  _quarantineCorrupt(err) {
21433
21314
  let locked = false;
21434
21315
  try {
@@ -22669,25 +22550,7 @@ var require_ops2 = __commonJS({
22669
22550
  store.save();
22670
22551
  return { ok: true, instance: inst };
22671
22552
  }
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 };
22553
+ return { list, addInstance, removeInstance, updateInstance };
22691
22554
  }
22692
22555
  module2.exports = { createOps };
22693
22556
  }
@@ -22910,7 +22773,6 @@ var require_instance = __commonJS({
22910
22773
  map(fn) {
22911
22774
  return this._store.instances.map(fn);
22912
22775
  }
22913
- // 能力判定读平台表(有副作用)⇒ 从 governor 取,不经 pure 的 sandbox.js。
22914
22776
  get sandboxSupported() {
22915
22777
  return governor.sandboxSupported(this._sandboxSupportedOverride);
22916
22778
  }
@@ -22998,9 +22860,6 @@ var require_instance = __commonJS({
22998
22860
  upgradeStatus(id) {
22999
22861
  return this._upgrade.upgradeStatus(id);
23000
22862
  }
23001
- startTimer(ms) {
23002
- return this._ops.startTimer(ms);
23003
- }
23004
22863
  sandboxRoot(inst) {
23005
22864
  return sandbox.root(this.instancesRoot, inst);
23006
22865
  }
@@ -23898,23 +23757,14 @@ var require_process_spec = __commonJS({
23898
23757
  function defaultSpec() {
23899
23758
  return {
23900
23759
  runtime: null,
23901
- // 解释器(如 node);为 null 表示 bin 可直接执行
23902
23760
  bin: null,
23903
- // 可执行文件或入口脚本
23904
23761
  subcommand: null,
23905
- // 子命令(如 'web' / 'plugin')
23906
23762
  args: [],
23907
- // 用户参数
23908
23763
  port: null,
23909
- // --port 值(一等公民,不再各处拼装)
23910
23764
  patchFile: null,
23911
- // 插件 overlay(一等公民,不再事后 splice)
23912
23765
  storeDir: null,
23913
- // pnpm --store-dir
23914
23766
  install: false,
23915
- // 是否安装类操作(决定是否注入安全标志)
23916
23767
  extraFlags: []
23917
- // 其他固定标志
23918
23768
  };
23919
23769
  }
23920
23770
  function validate(spec) {
@@ -24124,7 +23974,7 @@ var require_cli = __commonJS({
24124
23974
  });
24125
23975
 
24126
23976
  // src/domains/plugin/restart.js
24127
- var require_restart3 = __commonJS({
23977
+ var require_restart2 = __commonJS({
24128
23978
  "src/domains/plugin/restart.js"(exports2, module2) {
24129
23979
  "use strict";
24130
23980
  function targetRunning(ctx, target) {
@@ -24165,7 +24015,7 @@ var require_restart3 = __commonJS({
24165
24015
  log("\u91CD\u542F\u5B9E\u4F8B\u300C" + (target.name || target.id) + "\u300D\u4F7F\u63D2\u4EF6\u53D8\u66F4\u751F\u6548\u2026");
24166
24016
  if (ctx.events) ctx.events.append("plugin_restart_started", { name: target.name || target.id, target: target.id, kind });
24167
24017
  try {
24168
- ctx.instances.stopInstance(target.id);
24018
+ await ctx.instances.stopInstance(target.id);
24169
24019
  } catch (e) {
24170
24020
  log("\u505C\u6B62\u5B9E\u4F8B\u5931\u8D25: " + e.message);
24171
24021
  }
@@ -24189,28 +24039,25 @@ var require_restart3 = __commonJS({
24189
24039
  return true;
24190
24040
  }
24191
24041
  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");
24042
+ const nr = ctx.onNativeRestart;
24043
+ if (typeof nr !== "function") {
24044
+ log("\u539F\u751F DSH \u672A\u914D\u7F6E\u91CD\u542F\u56DE\u8C03\uFF1A\u63D2\u4EF6\u53D8\u66F4\u5C06\u5728\u4E0B\u6B21\u542F\u52A8\u65F6\u751F\u6548");
24194
24045
  return false;
24195
24046
  }
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;
24047
+ let rr;
24048
+ try {
24049
+ rr = ctx.onNativeRestart();
24050
+ } catch (e) {
24051
+ log("\u539F\u751F DSH \u91CD\u542F\u8BF7\u6C42\u5931\u8D25: " + e.message);
24052
+ return false;
24211
24053
  }
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;
24054
+ if (rr && rr.ok === false) {
24055
+ log("\u539F\u751F DSH \u91CD\u542F\u8BF7\u6C42\u672A\u751F\u6548\uFF1A" + (rr && rr.error || "unknown"));
24056
+ return false;
24057
+ }
24058
+ log("\u5DF2\u8BF7\u6C42\u91CD\u542F\u539F\u751F DSH \u4F7F\u63D2\u4EF6\u53D8\u66F4\u751F\u6548");
24059
+ if (ctx.events) ctx.events.append("plugin_restart_done", { name: "\u539F\u751F\u5B9E\u4F8B", target: "native", kind, via: "supervisor" });
24060
+ return true;
24214
24061
  }
24215
24062
  return false;
24216
24063
  } catch (e) {
@@ -24784,7 +24631,7 @@ var require_plugin = __commonJS({
24784
24631
  var store = require_store6();
24785
24632
  var targets = require_targets();
24786
24633
  var cli = require_cli();
24787
- var restart = require_restart3();
24634
+ var restart = require_restart2();
24788
24635
  var { createJobs } = require_jobs();
24789
24636
  var { createLayers } = require_layers();
24790
24637
  var ops = require_ops3();
@@ -24902,7 +24749,8 @@ var require_managed_object = __commonJS({
24902
24749
  "sandbox-instance": { label: "\u6C99\u7BB1\u5B9E\u4F8B", startable: true, guardable: true },
24903
24750
  "router-daemon": { label: "\u667A\u80FD\u8DEF\u7531 daemon", startable: true, guardable: true },
24904
24751
  "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 }
24752
+ plugin: { label: "\u63D2\u4EF6\uFF08\u805A\u5408\uFF09", startable: false, guardable: false },
24753
+ shell: { label: "\u684C\u9762\u58F3", startable: false, guardable: false }
24906
24754
  };
24907
24755
  var _customKinds = {};
24908
24756
  function kindMeta(kind) {
@@ -25034,9 +24882,17 @@ var require_registry4 = __commonJS({
25034
24882
  var path2 = require("node:path");
25035
24883
  var { writeAtomic } = require_fs();
25036
24884
  var { DESIRED, MANAGED_KINDS, kindMeta, registerKind: registerManagedKind, createEntry, normalizeOwnership } = require_managed_object();
24885
+ var { legacyToEntryPhase } = require_phase();
25037
24886
  var PHASES = ["stopped", "installing", "starting", "running", "draining", "failed"];
25038
- var LEGACY_PHASES = { backoff: "failed", restarting: "starting" };
25039
24887
  var { runHeartbeat } = require_heartbeat();
24888
+ var pmClient = require_pm_client();
24889
+ var configDefaults = require_config();
24890
+ var RESTART_FALLBACK = { windowMs: 6e4, burst: 5 };
24891
+ var restartDefaults = () => {
24892
+ const r = configDefaults && configDefaults.restart;
24893
+ if (r && Number.isFinite(r.windowMs) && Number.isFinite(r.burst)) return { windowMs: r.windowMs, burst: r.burst };
24894
+ return RESTART_FALLBACK;
24895
+ };
25040
24896
  var ManagedRegistry = class {
25041
24897
  constructor(opts) {
25042
24898
  this.file = opts && opts.file;
@@ -25082,7 +24938,8 @@ var require_registry4 = __commonJS({
25082
24938
  try {
25083
24939
  if (!o || !kindMeta(o.kind)) continue;
25084
24940
  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;
24941
+ const np = legacyToEntryPhase(o.phase);
24942
+ if (PHASES.includes(np)) e.phase = np;
25086
24943
  if (Number.isInteger(o.restartCount) && o.restartCount >= 0) e.restartCount = o.restartCount;
25087
24944
  if (o.startupFailWindowStart === null || typeof o.startupFailWindowStart === "number") e.startupFailWindowStart = o.startupFailWindowStart;
25088
24945
  if (Number.isInteger(o.startupFailCount) && o.startupFailCount >= 0) e.startupFailCount = o.startupFailCount;
@@ -25150,8 +25007,6 @@ var require_registry4 = __commonJS({
25150
25007
  const i = this._objects.indexOf(e);
25151
25008
  if (i >= 0) this._objects.splice(i, 1);
25152
25009
  }
25153
- // 自定义 kind 扩展点:零调用但刻意保留(见 managed-object.js 的说明)——
25154
- // 它是「新增受管对象类型」的唯一入口,删除等于永久关闭扩展能力。
25155
25010
  registerKind(kind, meta) {
25156
25011
  registerManagedKind(kind, meta);
25157
25012
  return this;
@@ -25171,6 +25026,8 @@ var require_registry4 = __commonJS({
25171
25026
  this._syncPortsOwner(e, true);
25172
25027
  this._save();
25173
25028
  this._event("managed_object_registered", { kind: e.kind, id: e.id, name: e.name });
25029
+ const rd = restartDefaults();
25030
+ pmClient.register({ id: e.id, spawn_cmd: [], restart_window_ms: rd.windowMs, restart_burst: rd.burst });
25174
25031
  return e;
25175
25032
  }
25176
25033
  update(id, patch) {
@@ -25180,6 +25037,7 @@ var require_registry4 = __commonJS({
25180
25037
  if (p.desired !== void 0) {
25181
25038
  if (!DESIRED.includes(p.desired)) return { ok: false, error: "\u975E\u6CD5 desired: " + p.desired };
25182
25039
  e.desired = p.desired;
25040
+ pmClient.setDesired(e.id, p.desired);
25183
25041
  }
25184
25042
  if (p.name !== void 0) e.name = String(p.name || e.id);
25185
25043
  if (p.ownership !== void 0) {
@@ -25211,6 +25069,7 @@ var require_registry4 = __commonJS({
25211
25069
  this._drop(e);
25212
25070
  this._save();
25213
25071
  this._event("managed_object_removed", { kind: e.kind, id: e.id });
25072
+ pmClient.setDesired(e.id, "stopped");
25214
25073
  return { ok: true };
25215
25074
  }
25216
25075
  _releasePort(port, ownerId) {
@@ -25255,6 +25114,17 @@ var require_registry4 = __commonJS({
25255
25114
  };
25256
25115
  return e;
25257
25116
  }
25117
+ setRestartCount(id, n) {
25118
+ const e = this.get(id);
25119
+ if (!e) return null;
25120
+ const v = typeof n === "number" && n >= 0 ? Math.floor(n) : 0;
25121
+ if (e.restartCount !== v) {
25122
+ e.restartCount = v;
25123
+ this._event("managed_object_restart_count", { kind: e.kind, id: e.id, count: v });
25124
+ this._save();
25125
+ }
25126
+ return e;
25127
+ }
25258
25128
  setPhase(id, p) {
25259
25129
  const e = this.get(id);
25260
25130
  if (!e) return null;
@@ -25264,6 +25134,7 @@ var require_registry4 = __commonJS({
25264
25134
  e.lastTransitionAt = (/* @__PURE__ */ new Date()).toISOString();
25265
25135
  this._event("managed_object_phase", { kind: e.kind, id: e.id, phase: p });
25266
25136
  this._save();
25137
+ pmClient.onPhase(e.id, p);
25267
25138
  }
25268
25139
  return e;
25269
25140
  }
@@ -26106,7 +25977,6 @@ var require_installer = __commonJS({
26106
25977
  _manifest() {
26107
25978
  return manifest.read(this);
26108
25979
  }
26109
- // W5:生产零调用,仅 native-test.js 使用(写 manifest 的夹具入口)。保留为测试缝。
26110
25980
  _saveManifest(m) {
26111
25981
  return manifest.save(this, m);
26112
25982
  }
@@ -26423,6 +26293,8 @@ var require_domains = __commonJS({
26423
26293
  }
26424
26294
  host.router = new RouterService({
26425
26295
  config: host.config,
26296
+ beatScheduler: host._beatScheduler,
26297
+ tickMs: host.config.tickIntervalMs || 5e3,
26426
26298
  providerFile: path2.join(swDir, "providers.json"),
26427
26299
  usageTotalsFile: path2.join(swDir, "router-usage-totals.json"),
26428
26300
  logger: host.logger,
@@ -26444,7 +26316,6 @@ var require_domains = __commonJS({
26444
26316
  tasks: host.tasks,
26445
26317
  tokenService: host.tokenService,
26446
26318
  dshBin: host.config.command && host.config.command[1] ? host.config.command[1] : "dsh",
26447
- // 实例域限流参数:与主链同一条规则、按域参数化(窗口 600s > 5 × 30s 启动窗口);单源取自域常量。
26448
26319
  throttle: THROTTLE_DEFAULTS
26449
26320
  });
26450
26321
  host.instances.load();
@@ -26561,7 +26432,6 @@ var require_adapters = __commonJS({
26561
26432
  kind: "lan",
26562
26433
  name: "\u8FDC\u7A0B\u63A7\u5236",
26563
26434
  logger,
26564
- // start 的结果必须来自真实执行:reconcile/syncFrpc 各自如实返回 ok,任一失败即 ok:false(此前恒 true)。
26565
26435
  start: async () => {
26566
26436
  try {
26567
26437
  const rec = await lan.reconcile();
@@ -26809,7 +26679,7 @@ var require_lifecycle3 = __commonJS({
26809
26679
  return send(200, { sessionState: sup.sessionState ? sup.sessionState() : "unknown" });
26810
26680
  }
26811
26681
  if (req.method === "POST" && pathname === "/session/stop") {
26812
- if (!originAllowed(req, sup.config.apiPort)) {
26682
+ if (!originAllowed(req)) {
26813
26683
  req.resume();
26814
26684
  return send(403, { ok: false, error: "cross-origin request rejected" });
26815
26685
  }
@@ -26832,7 +26702,7 @@ var require_lifecycle3 = __commonJS({
26832
26702
  return lc ? send(200, lc.snapshot()) : send(404, { error: "\u6A21\u5757\u672A\u6CE8\u518C: " + id });
26833
26703
  }
26834
26704
  if (req.method === "POST" && action) {
26835
- if (!originAllowed(req, sup.config.apiPort)) {
26705
+ if (!originAllowed(req)) {
26836
26706
  req.resume();
26837
26707
  return send(403, { ok: false, error: "cross-origin request rejected" });
26838
26708
  }
@@ -26937,7 +26807,7 @@ var require_native = __commonJS({
26937
26807
  });
26938
26808
  }
26939
26809
  if (req.method === "POST" && pathname === "/native/check-update") {
26940
- if (!originAllowed(req, sup.config.apiPort)) {
26810
+ if (!originAllowed(req)) {
26941
26811
  req.resume();
26942
26812
  return send(403, { ok: false, error: "cross-origin request rejected" });
26943
26813
  }
@@ -26945,7 +26815,7 @@ var require_native = __commonJS({
26945
26815
  return;
26946
26816
  }
26947
26817
  if (req.method === "POST" && pathname === "/native/install") {
26948
- if (!originAllowed(req, sup.config.apiPort)) {
26818
+ if (!originAllowed(req)) {
26949
26819
  req.resume();
26950
26820
  return send(403, { ok: false, error: "cross-origin request rejected" });
26951
26821
  }
@@ -26963,7 +26833,7 @@ var require_native = __commonJS({
26963
26833
  return;
26964
26834
  }
26965
26835
  if (req.method === "POST" && pathname === "/native/upgrade") {
26966
- if (!originAllowed(req, sup.config.apiPort)) {
26836
+ if (!originAllowed(req)) {
26967
26837
  req.resume();
26968
26838
  return send(403, { ok: false, error: "cross-origin request rejected" });
26969
26839
  }
@@ -26984,7 +26854,7 @@ var require_native = __commonJS({
26984
26854
  return;
26985
26855
  }
26986
26856
  if (req.method === "POST" && pathname === "/native/uninstall") {
26987
- if (!originAllowed(req, sup.config.apiPort)) {
26857
+ if (!originAllowed(req)) {
26988
26858
  req.resume();
26989
26859
  return send(403, { ok: false, error: "cross-origin request rejected" });
26990
26860
  }
@@ -26993,7 +26863,7 @@ var require_native = __commonJS({
26993
26863
  return send(202, { ok: true, accepted: true, state: "uninstalling" });
26994
26864
  }
26995
26865
  if (req.method === "POST" && pathname === "/native/settings") {
26996
- if (!originAllowed(req, sup.config.apiPort)) {
26866
+ if (!originAllowed(req)) {
26997
26867
  req.resume();
26998
26868
  return send(403, { ok: false, error: "cross-origin request rejected" });
26999
26869
  }
@@ -27055,14 +26925,14 @@ var require_guard = __commonJS({
27055
26925
  }
27056
26926
  if (req.method === "POST" && pathname === "/guard/version/check") {
27057
26927
  req.resume();
27058
- if (!originAllowed(req, sup.config.apiPort)) return send(403, { ok: false, error: "cross-origin request rejected" });
26928
+ if (!originAllowed(req)) return send(403, { ok: false, error: "cross-origin request rejected" });
27059
26929
  return sup.guardVersionCheck().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27060
26930
  }
27061
26931
  if (req.method === "GET" && pathname === "/autostart") {
27062
26932
  return send(200, sup.autostartStatus());
27063
26933
  }
27064
26934
  if (req.method === "POST" && pathname === "/autostart") {
27065
- if (!originAllowed(req, sup.config.apiPort)) {
26935
+ if (!originAllowed(req)) {
27066
26936
  req.resume();
27067
26937
  return send(403, { ok: false, error: "cross-origin request rejected" });
27068
26938
  }
@@ -27083,7 +26953,7 @@ var require_guard = __commonJS({
27083
26953
  return send(200, sup.lanPanelStatus());
27084
26954
  }
27085
26955
  if (req.method === "POST" && pathname === "/settings/lan") {
27086
- if (!originAllowed(req, sup.config.apiPort)) {
26956
+ if (!originAllowed(req)) {
27087
26957
  req.resume();
27088
26958
  return send(403, { ok: false, error: "cross-origin request rejected" });
27089
26959
  }
@@ -27104,7 +26974,7 @@ var require_guard = __commonJS({
27104
26974
  return send(200, sup.accessKeyStatus());
27105
26975
  }
27106
26976
  if (req.method === "POST" && pathname === "/settings/access-key") {
27107
- if (!originAllowed(req, sup.config.apiPort)) {
26977
+ if (!originAllowed(req)) {
27108
26978
  req.resume();
27109
26979
  return send(403, { ok: false, error: "cross-origin request rejected" });
27110
26980
  }
@@ -27126,7 +26996,7 @@ var require_guard = __commonJS({
27126
26996
  return send(200, sup.closeActionStatus());
27127
26997
  }
27128
26998
  if (req.method === "POST" && pathname === "/settings/close-action") {
27129
- if (!originAllowed(req, sup.config.apiPort)) {
26999
+ if (!originAllowed(req)) {
27130
27000
  req.resume();
27131
27001
  return send(403, {});
27132
27002
  }
@@ -27147,7 +27017,7 @@ var require_guard = __commonJS({
27147
27017
  return send(200, sup.externalBrowserStatus());
27148
27018
  }
27149
27019
  if (req.method === "POST" && pathname === "/settings/external-browser") {
27150
- if (!originAllowed(req, sup.config.apiPort)) {
27020
+ if (!originAllowed(req)) {
27151
27021
  req.resume();
27152
27022
  return send(403, {});
27153
27023
  }
@@ -27169,7 +27039,7 @@ var require_guard = __commonJS({
27169
27039
  return;
27170
27040
  }
27171
27041
  if (req.method === "POST" && pathname === "/shutdown") {
27172
- if (!originAllowed(req, sup.config.apiPort)) {
27042
+ if (!originAllowed(req)) {
27173
27043
  req.resume();
27174
27044
  return send(403, {});
27175
27045
  }
@@ -27184,7 +27054,7 @@ var require_guard = __commonJS({
27184
27054
  return;
27185
27055
  }
27186
27056
  if (req.method === "GET" && pathname === "/self-update/status") {
27187
- if (!originAllowed(req, sup.config.apiPort)) {
27057
+ if (!originAllowed(req)) {
27188
27058
  req.resume();
27189
27059
  return send(403, {});
27190
27060
  }
@@ -27210,14 +27080,14 @@ var require_guard = __commonJS({
27210
27080
  });
27211
27081
  }
27212
27082
  if (req.method === "GET" && pathname === "/env/dsh") {
27213
- if (!originAllowed(req, sup.config.apiPort)) {
27083
+ if (!originAllowed(req)) {
27214
27084
  req.resume();
27215
27085
  return send(403, {});
27216
27086
  }
27217
27087
  return send(200, sup.dshenvStatus());
27218
27088
  }
27219
27089
  if (req.method === "GET" && pathname === "/env/status") {
27220
- if (!originAllowed(req, sup.config.apiPort)) {
27090
+ if (!originAllowed(req)) {
27221
27091
  req.resume();
27222
27092
  return send(403, {});
27223
27093
  }
@@ -27227,7 +27097,7 @@ var require_guard = __commonJS({
27227
27097
  return sup.nodeLtsStatus().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27228
27098
  }
27229
27099
  if (req.method === "GET" && pathname === "/env/environment") {
27230
- if (!originAllowed(req, sup.config.apiPort)) {
27100
+ if (!originAllowed(req)) {
27231
27101
  req.resume();
27232
27102
  return send(403, { ok: false, error: "cross-origin request rejected" });
27233
27103
  }
@@ -27235,7 +27105,7 @@ var require_guard = __commonJS({
27235
27105
  return Promise.resolve(force ? environment.refresh({ persist: true, force: true }) : environment.form()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: "\u73AF\u5883\u8868\u5355\u88C5\u914D\u5931\u8D25\uFF1A" + (e && e.message || e) }));
27236
27106
  }
27237
27107
  if (req.method === "GET" && pathname === "/env/environment/last") {
27238
- if (!originAllowed(req, sup.config.apiPort)) {
27108
+ if (!originAllowed(req)) {
27239
27109
  req.resume();
27240
27110
  return send(403, { ok: false, error: "cross-origin request rejected" });
27241
27111
  }
@@ -27246,7 +27116,7 @@ var require_guard = __commonJS({
27246
27116
  req.resume();
27247
27117
  return send(403, { ok: false, error: "\u4EC5\u5185\u6838\u6240\u5728\u673A\u5668\u53EF\u8BF7\u5185\u6838\u8C03\u8D77\u6D4F\u89C8\u5668\uFF0C\u8BF7\u590D\u5236\u6216\u81EA\u884C\u6253\u5F00\u8BE5\u5730\u5740" });
27248
27118
  }
27249
- if (!originAllowed(req, sup.config.apiPort)) {
27119
+ if (!originAllowed(req)) {
27250
27120
  req.resume();
27251
27121
  return send(403, { ok: false, error: "cross-origin request rejected" });
27252
27122
  }
@@ -27291,7 +27161,7 @@ var require_router4 = __commonJS({
27291
27161
  return Promise.resolve(sup.routerStatusView()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, running: false, error: e && e.message || String(e) }));
27292
27162
  }
27293
27163
  if (req.method === "POST" && pathname.startsWith("/router/")) {
27294
- if (!originAllowed(req, sup.config.apiPort)) {
27164
+ if (!originAllowed(req)) {
27295
27165
  req.resume();
27296
27166
  return send(403, { ok: false, error: "cross-origin request rejected" });
27297
27167
  }
@@ -27308,7 +27178,7 @@ var require_router4 = __commonJS({
27308
27178
  return Promise.resolve(sup.routerProviders()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27309
27179
  }
27310
27180
  if (req.method === "POST" && pathname === "/router/providers/add") {
27311
- if (!originAllowed(req, sup.config.apiPort)) {
27181
+ if (!originAllowed(req)) {
27312
27182
  req.resume();
27313
27183
  return send(403, {});
27314
27184
  }
@@ -27327,7 +27197,7 @@ var require_router4 = __commonJS({
27327
27197
  return;
27328
27198
  }
27329
27199
  if (req.method === "POST" && pathname === "/router/proxy/login/start") {
27330
- if (!originAllowed(req, sup.config.apiPort)) {
27200
+ if (!originAllowed(req)) {
27331
27201
  req.resume();
27332
27202
  return send(403, {});
27333
27203
  }
@@ -27335,7 +27205,7 @@ var require_router4 = __commonJS({
27335
27205
  return;
27336
27206
  }
27337
27207
  if (req.method === "POST" && pathname === "/router/proxy/login/wait") {
27338
- if (!originAllowed(req, sup.config.apiPort)) {
27208
+ if (!originAllowed(req)) {
27339
27209
  req.resume();
27340
27210
  return send(403, {});
27341
27211
  }
@@ -27352,7 +27222,7 @@ var require_router4 = __commonJS({
27352
27222
  return;
27353
27223
  }
27354
27224
  if (req.method === "POST" && pathname === "/router/proxy/update/check") {
27355
- if (!originAllowed(req, sup.config.apiPort)) {
27225
+ if (!originAllowed(req)) {
27356
27226
  req.resume();
27357
27227
  return send(403, {});
27358
27228
  }
@@ -27360,7 +27230,7 @@ var require_router4 = __commonJS({
27360
27230
  return;
27361
27231
  }
27362
27232
  if (req.method === "POST" && pathname === "/router/proxy/update/apply") {
27363
- if (!originAllowed(req, sup.config.apiPort)) {
27233
+ if (!originAllowed(req)) {
27364
27234
  req.resume();
27365
27235
  return send(403, {});
27366
27236
  }
@@ -27379,7 +27249,7 @@ var require_router4 = __commonJS({
27379
27249
  return Promise.resolve(sup.routerApi().proxyUpdateStatus(u.searchParams.get("appId") || "")).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27380
27250
  }
27381
27251
  if (req.method === "POST" && pathname === "/router/providers/proxy/key") {
27382
- if (!originAllowed(req, sup.config.apiPort)) {
27252
+ if (!originAllowed(req)) {
27383
27253
  req.resume();
27384
27254
  return send(403, {});
27385
27255
  }
@@ -27394,7 +27264,7 @@ var require_router4 = __commonJS({
27394
27264
  return;
27395
27265
  }
27396
27266
  if (req.method === "POST" && pathname === "/router/providers/proxy/select") {
27397
- if (!originAllowed(req, sup.config.apiPort)) {
27267
+ if (!originAllowed(req)) {
27398
27268
  req.resume();
27399
27269
  return send(403, {});
27400
27270
  }
@@ -27409,7 +27279,7 @@ var require_router4 = __commonJS({
27409
27279
  return;
27410
27280
  }
27411
27281
  if (req.method === "POST" && pathname === "/router/providers/proxy/key/remove") {
27412
- if (!originAllowed(req, sup.config.apiPort)) {
27282
+ if (!originAllowed(req)) {
27413
27283
  req.resume();
27414
27284
  return send(403, {});
27415
27285
  }
@@ -27424,7 +27294,7 @@ var require_router4 = __commonJS({
27424
27294
  return;
27425
27295
  }
27426
27296
  if (req.method === "POST" && pathname === "/router/providers/keys/set") {
27427
- if (!originAllowed(req, sup.config.apiPort)) {
27297
+ if (!originAllowed(req)) {
27428
27298
  req.resume();
27429
27299
  return send(403, {});
27430
27300
  }
@@ -27440,7 +27310,7 @@ var require_router4 = __commonJS({
27440
27310
  return;
27441
27311
  }
27442
27312
  if (req.method === "POST" && pathname === "/router/providers/remove") {
27443
- if (!originAllowed(req, sup.config.apiPort)) {
27313
+ if (!originAllowed(req)) {
27444
27314
  req.resume();
27445
27315
  return send(403, {});
27446
27316
  }
@@ -27455,7 +27325,7 @@ var require_router4 = __commonJS({
27455
27325
  return;
27456
27326
  }
27457
27327
  if (req.method === "POST" && pathname === "/router/providers/key/use") {
27458
- if (!originAllowed(req, sup.config.apiPort)) {
27328
+ if (!originAllowed(req)) {
27459
27329
  req.resume();
27460
27330
  return send(403, {});
27461
27331
  }
@@ -27471,7 +27341,7 @@ var require_router4 = __commonJS({
27471
27341
  return;
27472
27342
  }
27473
27343
  if (req.method === "POST" && pathname === "/router/providers/account/discard") {
27474
- if (!originAllowed(req, sup.config.apiPort)) {
27344
+ if (!originAllowed(req)) {
27475
27345
  req.resume();
27476
27346
  return send(403, {});
27477
27347
  }
@@ -27486,7 +27356,7 @@ var require_router4 = __commonJS({
27486
27356
  return;
27487
27357
  }
27488
27358
  if (req.method === "POST" && pathname === "/router/providers/refresh") {
27489
- if (!originAllowed(req, sup.config.apiPort)) {
27359
+ if (!originAllowed(req)) {
27490
27360
  req.resume();
27491
27361
  return send(403, {});
27492
27362
  }
@@ -27502,7 +27372,7 @@ var require_router4 = __commonJS({
27502
27372
  return;
27503
27373
  }
27504
27374
  if (req.method === "POST" && pathname === "/router/providers/activate") {
27505
- if (!originAllowed(req, sup.config.apiPort)) {
27375
+ if (!originAllowed(req)) {
27506
27376
  req.resume();
27507
27377
  return send(403, {});
27508
27378
  }
@@ -27518,7 +27388,7 @@ var require_router4 = __commonJS({
27518
27388
  return;
27519
27389
  }
27520
27390
  if (req.method === "POST" && pathname === "/router/providers/deactivate") {
27521
- if (!originAllowed(req, sup.config.apiPort)) {
27391
+ if (!originAllowed(req)) {
27522
27392
  req.resume();
27523
27393
  return send(403, {});
27524
27394
  }
@@ -27568,7 +27438,7 @@ var require_plugins = __commonJS({
27568
27438
  return send(200, sup.pluginManager.installStatus(u.searchParams.get("job")));
27569
27439
  }
27570
27440
  if (req.method === "POST" && pathname.startsWith("/plugins/")) {
27571
- if (!originAllowed(req, sup.config.apiPort)) {
27441
+ if (!originAllowed(req)) {
27572
27442
  req.resume();
27573
27443
  return send(403, { ok: false, error: "cross-origin request rejected" });
27574
27444
  }
@@ -27644,7 +27514,7 @@ var require_dist = __commonJS({
27644
27514
  return;
27645
27515
  }
27646
27516
  if (req.method === "POST" && pathname === "/dist/registry/set") {
27647
- if (!originAllowed(req, sup.config.apiPort)) {
27517
+ if (!originAllowed(req)) {
27648
27518
  req.resume();
27649
27519
  return send(403, {});
27650
27520
  }
@@ -27663,7 +27533,7 @@ var require_dist = __commonJS({
27663
27533
  return;
27664
27534
  }
27665
27535
  if (req.method === "POST" && pathname === "/dist/registry/refresh") {
27666
- if (!originAllowed(req, sup.config.apiPort)) {
27536
+ if (!originAllowed(req)) {
27667
27537
  req.resume();
27668
27538
  return send(403, {});
27669
27539
  }
@@ -27671,7 +27541,7 @@ var require_dist = __commonJS({
27671
27541
  return;
27672
27542
  }
27673
27543
  if (req.method === "POST" && pathname === "/dist/registry/probe") {
27674
- if (!originAllowed(req, sup.config.apiPort)) {
27544
+ if (!originAllowed(req)) {
27675
27545
  req.resume();
27676
27546
  return send(403, {});
27677
27547
  }
@@ -27742,6 +27612,112 @@ var require_exchange = __commonJS({
27742
27612
  }
27743
27613
  });
27744
27614
 
27615
+ // src/platform/security/identity.js
27616
+ var require_identity2 = __commonJS({
27617
+ "src/platform/security/identity.js"(exports2, module2) {
27618
+ "use strict";
27619
+ var { normalizeRemoteAddress, isLoopbackAddress } = require_ip();
27620
+ function socketIsLoopback(req) {
27621
+ return isLoopbackAddress(req && req.socket && req.socket.remoteAddress);
27622
+ }
27623
+ function identify(req) {
27624
+ return {
27625
+ remote: normalizeRemoteAddress(req && req.socket && req.socket.remoteAddress),
27626
+ loopback: socketIsLoopback(req)
27627
+ };
27628
+ }
27629
+ module2.exports = { identify };
27630
+ }
27631
+ });
27632
+
27633
+ // src/api/identity.js
27634
+ var require_identity3 = __commonJS({
27635
+ "src/api/identity.js"(exports2, module2) {
27636
+ "use strict";
27637
+ var { normalizeRemoteAddress, isLoopbackAddress, isPrivateIpv4 } = require_ip();
27638
+ var { identify } = require_identity2();
27639
+ module2.exports = {
27640
+ identify,
27641
+ normalizeRemoteAddress,
27642
+ isPrivateIpv4,
27643
+ isLoopbackAddress
27644
+ };
27645
+ }
27646
+ });
27647
+
27648
+ // src/api/security.js
27649
+ var require_security = __commonJS({
27650
+ "src/api/security.js"(exports2, module2) {
27651
+ "use strict";
27652
+ var crypto = require("node:crypto");
27653
+ var { isPrivateIpv4 } = require_identity3();
27654
+ var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
27655
+ function isLocalOrLanHost(h) {
27656
+ if (!h) return false;
27657
+ const s = String(h).toLowerCase();
27658
+ if (LOOPBACK_HOSTS.has(s)) return true;
27659
+ const bare = s.startsWith("[") && s.endsWith("]") ? s.slice(1, -1) : s;
27660
+ if (bare === "::1") return true;
27661
+ return isPrivateIpv4(bare);
27662
+ }
27663
+ function isShellOrigin(protocol, hostname) {
27664
+ const h = String(hostname || "").toLowerCase();
27665
+ if (protocol === "tauri:") return h === "localhost" || /(^|\.)tauri\.localhost$/.test(h);
27666
+ if (protocol === "http:" || protocol === "https:") return h === "tauri.localhost";
27667
+ return false;
27668
+ }
27669
+ function normalizeHostname(h) {
27670
+ const s = String(h || "").toLowerCase();
27671
+ return s.startsWith("[") && s.endsWith("]") ? s.slice(1, -1) : s;
27672
+ }
27673
+ function originAllowed(req, apiPort) {
27674
+ const host = req.headers.host;
27675
+ if (!host) return false;
27676
+ let hostname = "";
27677
+ {
27678
+ const m = /^(\[[^\]]+\]|[^:]+)(?::\d+)?$/.exec(String(host).trim());
27679
+ hostname = m ? m[1] : String(host).trim();
27680
+ if (!isLocalOrLanHost(hostname)) return false;
27681
+ }
27682
+ const o = req.headers.origin;
27683
+ if (!o) return true;
27684
+ try {
27685
+ const u = new URL(o);
27686
+ if (isShellOrigin(u.protocol, u.hostname)) return true;
27687
+ if (!isLocalOrLanHost(u.hostname)) return false;
27688
+ if (host && normalizeHostname(u.hostname) !== normalizeHostname(hostname)) return false;
27689
+ const port = u.port === "" ? u.protocol === "https:" ? "443" : "80" : u.port;
27690
+ return port === String(apiPort);
27691
+ } catch {
27692
+ return false;
27693
+ }
27694
+ }
27695
+ function safeKeyEqual(a, b) {
27696
+ const ha = crypto.createHash("sha256").update(String(a || "")).digest();
27697
+ const hb = crypto.createHash("sha256").update(String(b || "")).digest();
27698
+ return crypto.timingSafeEqual(ha, hb);
27699
+ }
27700
+ function requestHasAccessKey(req, key) {
27701
+ if (!key) return true;
27702
+ const ah = req.headers.authorization;
27703
+ if (typeof ah === "string" && ah.startsWith("Bearer ") && safeKeyEqual(ah.slice(7), key)) return true;
27704
+ try {
27705
+ const q = new URL(req.url, "http://localhost").searchParams.get("access_key");
27706
+ if (q && safeKeyEqual(q, key)) return true;
27707
+ } catch {
27708
+ }
27709
+ return false;
27710
+ }
27711
+ function liveApiPort(server, sup) {
27712
+ const live = server && typeof server.address === "function" ? server.address() : null;
27713
+ if (live && Number.isInteger(live.port) && live.port > 0) return live.port;
27714
+ const cfg = sup && sup.config && sup.config.apiPort;
27715
+ return Number.isInteger(cfg) ? cfg : null;
27716
+ }
27717
+ module2.exports = { originAllowed, isLocalOrLanHost, isShellOrigin, requestHasAccessKey, liveApiPort };
27718
+ }
27719
+ });
27720
+
27745
27721
  // src/api/domains/instances.js
27746
27722
  var require_instances = __commonJS({
27747
27723
  "src/api/domains/instances.js"(exports2, module2) {
@@ -27749,6 +27725,7 @@ var require_instances = __commonJS({
27749
27725
  var execPath = require_exec_path();
27750
27726
  var crypto = require("node:crypto");
27751
27727
  var { bootstrapDshCookie } = require_exchange();
27728
+ var { liveApiPort } = require_security();
27752
27729
  var OPEN_WEB_CODES = /* @__PURE__ */ new Map();
27753
27730
  var OPEN_WEB_CODE_TTL_MS = 3e4;
27754
27731
  function _sweepExpiredOpenWebCodes() {
@@ -27782,16 +27759,16 @@ var require_instances = __commonJS({
27782
27759
  return (sup.instances.list() || []).find((x) => x.id === id) || null;
27783
27760
  }
27784
27761
  function handleOpen(ctx) {
27785
- const { sup, req, res, identity, originAllowed, tokOf } = ctx;
27762
+ const { sup, req, res, identity, originAllowed, tokOf, server } = ctx;
27786
27763
  const url = new URL(req.url, "http://localhost");
27787
- const apiPort = sup.config.apiPort;
27764
+ const apiPort = liveApiPort(server, sup);
27788
27765
  const code = url.searchParams.get("code");
27789
27766
  const deny = (status, msg) => {
27790
27767
  res.writeHead(status, { "Content-Type": "text/plain; charset=utf-8" });
27791
27768
  res.end(msg);
27792
27769
  };
27793
27770
  if (!identity.loopback) return deny(403, "\u4EC5\u5141\u8BB8\u672C\u673A\u8BBF\u95EE");
27794
- if (!originAllowed(req, apiPort)) return deny(403, "origin not allowed");
27771
+ if (!originAllowed(req)) return deny(403, "origin not allowed");
27795
27772
  const rec = consumeOpenWebCode(code);
27796
27773
  if (!rec) return deny(code ? 400 : 404, code ? "\u6388\u6743\u7801\u65E0\u6548\u6216\u5DF2\u8FC7\u671F" : "\u7F3A\u5C11\u6388\u6743\u7801");
27797
27774
  const it = findInstanceOrMain(sup, rec.id);
@@ -27857,7 +27834,7 @@ var require_instances = __commonJS({
27857
27834
  return sharedErr + "\uFF1B" + FORMS + "\uFF1B\u9700\u8981\u5176\u5B83\u53EF\u6267\u884C\u8BF7\u8D70\u63D2\u4EF6\u5B89\u88C5\u901A\u9053";
27858
27835
  }
27859
27836
  function handle(ctx) {
27860
- const { sup, req, res, pathname, identity, send, collectBody, originAllowed, tokOf, browser } = ctx;
27837
+ const { sup, req, res, pathname, identity, send, collectBody, originAllowed, tokOf, browser, server } = ctx;
27861
27838
  function openInSystemBrowser(url) {
27862
27839
  return browser.openBrowser(url, { logger: sup.logger });
27863
27840
  }
@@ -27884,7 +27861,7 @@ var require_instances = __commonJS({
27884
27861
  return render();
27885
27862
  }
27886
27863
  if (req.method === "POST" && pathname.startsWith("/instances/")) {
27887
- if (!originAllowed(req, sup.config.apiPort)) {
27864
+ if (!originAllowed(req)) {
27888
27865
  req.resume();
27889
27866
  return send(403, {});
27890
27867
  }
@@ -27921,7 +27898,7 @@ var require_instances = __commonJS({
27921
27898
  if (!it) return send(404, { ok: false, error: "\u5B9E\u4F8B\u4E0D\u5B58\u5728" });
27922
27899
  if (!(Number(it.port) > 0)) return send(400, { ok: false, error: "\u975E\u6CD5\u7AEF\u53E3" });
27923
27900
  const code = issueOpenWebCode(j.id);
27924
- const url = "http://127.0.0.1:" + sup.config.apiPort + "/open?code=" + code;
27901
+ const url = "http://127.0.0.1:" + (liveApiPort(server, sup) || sup.config.apiPort) + "/open?code=" + code;
27925
27902
  return Promise.resolve(openInSystemBrowser(url)).then((r) => {
27926
27903
  if (!r.ok) dropOpenWebCode(code);
27927
27904
  return send(r.ok ? 200 : 500, r);
@@ -27966,7 +27943,7 @@ var require_relay = __commonJS({
27966
27943
  return Promise.resolve(sup.listLan()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
27967
27944
  }
27968
27945
  if (req.method === "POST" && pathname.startsWith("/remote/")) {
27969
- if (!originAllowed(req, sup.config.apiPort)) {
27946
+ if (!originAllowed(req)) {
27970
27947
  req.resume();
27971
27948
  return send(403, {});
27972
27949
  }
@@ -28029,7 +28006,7 @@ var require_shell2 = __commonJS({
28029
28006
  }
28030
28007
  }
28031
28008
  if (req.method === "POST" && pathname === "/shell/health") {
28032
- if (!originAllowed(req, sup.config.apiPort)) {
28009
+ if (!originAllowed(req)) {
28033
28010
  req.resume();
28034
28011
  return send(403, {});
28035
28012
  }
@@ -28046,28 +28023,8 @@ var require_shell2 = __commonJS({
28046
28023
  }
28047
28024
  });
28048
28025
  }
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
28026
  if (req.method === "POST" && pathname === "/shell/check-update") {
28070
- if (!originAllowed(req, sup.config.apiPort)) {
28027
+ if (!originAllowed(req)) {
28071
28028
  req.resume();
28072
28029
  return send(403, {});
28073
28030
  }
@@ -28079,23 +28036,6 @@ var require_shell2 = __commonJS({
28079
28036
  return send(200, r);
28080
28037
  }).catch((e) => send(500, { ok: false, error: e.message }));
28081
28038
  }
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
28039
  if (req.method === "GET" || req.method === "POST") return send(404, { error: "not found", path: pathname });
28100
28040
  return send(405, { error: "method not allowed" });
28101
28041
  }
@@ -28259,106 +28199,6 @@ var require_static = __commonJS({
28259
28199
  }
28260
28200
  });
28261
28201
 
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
28202
  // src/api/transport/server.js
28363
28203
  var require_server = __commonJS({
28364
28204
  "src/api/transport/server.js"(exports2, module2) {
@@ -28368,7 +28208,7 @@ var require_server = __commonJS({
28368
28208
  var { collectBody } = require_body();
28369
28209
  var { serveStatic } = require_static();
28370
28210
  var { identify } = require_identity3();
28371
- var { originAllowed, requestHasAccessKey, isShellOrigin } = require_security();
28211
+ var { originAllowed, requestHasAccessKey, isShellOrigin, liveApiPort } = require_security();
28372
28212
  var browserExit = require_browser();
28373
28213
  var environmentExit = require_environment();
28374
28214
  function safeFail(res, err, where) {
@@ -28391,7 +28231,7 @@ var require_server = __commonJS({
28391
28231
  function createServer(sup, deps) {
28392
28232
  const browser = deps && deps.browser || browserExit;
28393
28233
  const environment = deps && deps.environment || environmentExit;
28394
- return http2.createServer((req, res) => {
28234
+ const server = http2.createServer((req, res) => {
28395
28235
  const shellOrigin = (() => {
28396
28236
  const o = req.headers.origin;
28397
28237
  if (!o) return null;
@@ -28421,6 +28261,7 @@ var require_server = __commonJS({
28421
28261
  return "";
28422
28262
  };
28423
28263
  const identity = identify(req);
28264
+ const originAllowedLive = (req2) => originAllowed(req2, liveApiPort(server, sup));
28424
28265
  const accessKey = sup && sup.config && sup.config.apiAccessKey || null;
28425
28266
  if (!identity.loopback && req.method !== "OPTIONS" && (!accessKey || !requestHasAccessKey(req, accessKey))) {
28426
28267
  res.writeHead(401, { "Content-Type": "application/json" });
@@ -28458,7 +28299,7 @@ var require_server = __commonJS({
28458
28299
  } catch (e) {
28459
28300
  return send(400, { error: "bad request encoding" });
28460
28301
  }
28461
- const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed, tokOf, browser, environment };
28302
+ const ctx = { sup, req, res, pathname: decodedPathname, identity, send, collectBody, originAllowed: originAllowedLive, tokOf, browser, environment, server };
28462
28303
  for (const d of API_DOMAINS) {
28463
28304
  if (d.owns(pathname)) {
28464
28305
  try {
@@ -28484,6 +28325,7 @@ var require_server = __commonJS({
28484
28325
  }
28485
28326
  return send(405, { error: "method not allowed" });
28486
28327
  });
28328
+ return server;
28487
28329
  }
28488
28330
  module2.exports = { createServer };
28489
28331
  }
@@ -28503,7 +28345,7 @@ var require_api = __commonJS({
28503
28345
  var require_session = __commonJS({
28504
28346
  "src/domains/relay/session.js"(exports2, module2) {
28505
28347
  "use strict";
28506
- var { cookieByName } = require_core4();
28348
+ var { cookieByName } = require_core3();
28507
28349
  var { bootstrapDshCookie } = require_exchange();
28508
28350
  function createSession(opts) {
28509
28351
  const o = opts || {};
@@ -28645,7 +28487,7 @@ var require_tunnel = __commonJS({
28645
28487
  "src/domains/relay/tunnel.js"(exports2, module2) {
28646
28488
  "use strict";
28647
28489
  var net = require("node:net");
28648
- var { isTrustedSource, hasValidToken, upstreamPath } = require_core4();
28490
+ var { isTrustedSource, hasValidToken, upstreamPath } = require_core3();
28649
28491
  function buildRawRequest(req, authority, cookie) {
28650
28492
  let raw = req.method + " " + upstreamPath(req.url) + " HTTP/1.1\r\n";
28651
28493
  for (const [k, v] of Object.entries(req.headers)) {
@@ -28731,7 +28573,7 @@ var require_proxy2 = __commonJS({
28731
28573
  "use strict";
28732
28574
  var http2 = require("node:http");
28733
28575
  var crypto = require("node:crypto");
28734
- var { isTrustedSource, tokenGateDecision, backoffGate, upstreamPath, redactLogPath, normalizeRemoteMode, POLYFILL_SCRIPT } = require_core4();
28576
+ var { isTrustedSource, tokenGateDecision, backoffGate, upstreamPath, redactLogPath, normalizeRemoteMode, POLYFILL_SCRIPT } = require_core3();
28735
28577
  var { createSession } = require_session();
28736
28578
  var { createTunnelHandler } = require_tunnel();
28737
28579
  var HTML_INJECT_MAX_BYTES = 2 * 1024 * 1024;
@@ -28756,7 +28598,7 @@ var require_proxy2 = __commonJS({
28756
28598
  }
28757
28599
  };
28758
28600
  }
28759
- function pipeWithHold(ur, res, clientReqPath, logger) {
28601
+ function pipeWithHold(ur, res, clientReqLog, logger) {
28760
28602
  let clientGone = false;
28761
28603
  const log = (lv, msg) => {
28762
28604
  if (logger && logger[lv]) {
@@ -28805,7 +28647,7 @@ var require_proxy2 = __commonJS({
28805
28647
  return headers;
28806
28648
  }
28807
28649
  function handleUpstream(ur, res, clientReqPath, onStatus, logger) {
28808
- const clientReqLog2 = redactLogPath(clientReqPath);
28650
+ const clientReqLog = redactLogPath(clientReqPath);
28809
28651
  if (onStatus) onStatus(ur.statusCode);
28810
28652
  const h = { ...ur.headers };
28811
28653
  const isHtml = String(h["content-type"] || "").includes("text/html");
@@ -28817,7 +28659,7 @@ var require_proxy2 = __commonJS({
28817
28659
  }
28818
28660
  res.writeHead(ur.statusCode || 502, h);
28819
28661
  if (!isHtml) {
28820
- pipeWithHold(ur, res, clientReqPath, logger);
28662
+ pipeWithHold(ur, res, clientReqLog, logger);
28821
28663
  return;
28822
28664
  }
28823
28665
  const chunks = [];
@@ -28851,7 +28693,7 @@ var require_proxy2 = __commonJS({
28851
28693
  chunks.push(c);
28852
28694
  return;
28853
28695
  }
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 || ""));
28696
+ log("HTML \u8D85\u4E0A\u9650 " + HTML_INJECT_MAX_BYTES + "B\uFF08\u5DF2\u6536 " + total + "B\uFF09\uFF0C\u653E\u5F03 polyfill \u6CE8\u5165\u6309\u6D41\u900F\u4F20 " + (clientReqLog || ""));
28855
28697
  const selfForward = !ur.readableEnded;
28856
28698
  switchToPassThrough();
28857
28699
  if (selfForward) res.write(c);
@@ -29031,14 +28873,8 @@ var require_ports3 = __commonJS({
29031
28873
  return registry.claimSlot(segment, owner, {
29032
28874
  preferred: o.preferred,
29033
28875
  bindingPreferred: !!o.bindingPreferred,
29034
- // RL-4:跨账本防撞。router caller 不传 reservedPorts(分配器跳过该检查,行为不变);
29035
- // 仅 relay 传入 router 账本已占端口,free-scan/preferred-claim 跳过 ⇒ 不会抢 router 的端口。
29036
28876
  reservedPorts: reservedPorts || void 0,
29037
28877
  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
28878
  reclaimCmdMark: "domains/relay/daemon.js",
29043
28879
  reclaimCfg: o.configPath || "",
29044
28880
  waitMs: 8e3
@@ -29237,7 +29073,7 @@ var require_frp = __commonJS({
29237
29073
  var path2 = require("node:path");
29238
29074
  var spawnOS = require_spawn();
29239
29075
  var { writeAtomic } = require_fs();
29240
- var { buildFrpcToml, validateFrpServerSettings } = require_core4();
29076
+ var { buildFrpcToml, validateFrpServerSettings } = require_core3();
29241
29077
  var { frpPlatformTag, download, installFrpc } = require_frp_install();
29242
29078
  var FrpManager = class {
29243
29079
  constructor(opts) {
@@ -29681,7 +29517,7 @@ var require_ops5 = __commonJS({
29681
29517
  var managed = require_managed();
29682
29518
  var portsvc = require_ports3();
29683
29519
  var { FrpManager } = require_frp();
29684
- var { normalizeRemoteMode, validateWanAccess, normalizeFrpSettings, validateFrpServerSettings, projectRemoteView } = require_core4();
29520
+ var { normalizeRemoteMode, validateWanAccess, normalizeFrpSettings, validateFrpServerSettings, projectRemoteView } = require_core3();
29685
29521
  var reconcile = require_reconcile();
29686
29522
  var lanServers = require_lan_servers();
29687
29523
  function applyRelayToken(host, existing, want) {