@dsh-sup/dsh-core-linux-x64 0.1.6-BETA.4 → 0.1.6-BETA.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/core.cjs +498 -306
  2. package/package.json +1 -1
package/core.cjs CHANGED
@@ -60,11 +60,7 @@ var require_exec = __commonJS({
60
60
  function runAsync(bin, args, opts) {
61
61
  const o = opts || {};
62
62
  return new Promise((resolve) => {
63
- execFile(bin, args, Object.assign({}, options(o), { encoding: "utf8" }), (err, stdout, stderr) => {
64
- if (!err) {
65
- resolve({ ok: true, code: "0", stdout: String(stdout == null ? "" : stdout), stderr: String(stderr == null ? "" : stderr), timedOut: false, error: null });
66
- return;
67
- }
63
+ const fail = (err) => {
68
64
  if (o.logger && o.logger.warn) {
69
65
  try {
70
66
  o.logger.warn("[exec] (async) " + bin + " " + (args || []).join(" ").slice(0, 80) + " failed: " + (err && err.message || err));
@@ -75,12 +71,25 @@ var require_exec = __commonJS({
75
71
  resolve({
76
72
  ok: false,
77
73
  code: err.status != null ? String(err.status) : null,
78
- stdout: String(stdout == null ? err.stdout || "" : stdout),
79
- stderr: String(stderr == null ? err.stderr || "" : stderr),
74
+ stdout: String(err.stdout || ""),
75
+ stderr: String(err.stderr || ""),
80
76
  timedOut,
81
77
  error: err && err.message ? String(err.message) : String(err)
82
78
  });
83
- });
79
+ };
80
+ try {
81
+ execFile(bin, args, Object.assign({}, options(o), { encoding: "utf8" }), (err, stdout, stderr) => {
82
+ if (!err) {
83
+ resolve({ ok: true, code: "0", stdout: String(stdout == null ? "" : stdout), stderr: String(stderr == null ? "" : stderr), timedOut: false, error: null });
84
+ return;
85
+ }
86
+ err.stdout = stdout;
87
+ err.stderr = stderr;
88
+ fail(err);
89
+ });
90
+ } catch (e) {
91
+ fail(e);
92
+ }
84
93
  });
85
94
  }
86
95
  function runOutAsync(bin, args, opts) {
@@ -503,15 +512,21 @@ var require_probe = __commonJS({
503
512
  }
504
513
  return null;
505
514
  }
506
- function isAlive(pid) {
507
- if (!Number.isInteger(pid) || pid <= 0) return false;
515
+ function probeAlive(pid) {
516
+ if (!Number.isInteger(pid) || pid <= 0) return "dead";
508
517
  try {
509
518
  process.kill(pid, 0);
510
- return true;
519
+ return "alive";
511
520
  } catch (e) {
512
- return !!e && e.code === "EPERM";
521
+ const code = e && e.code;
522
+ if (code === "EPERM") return "alive";
523
+ if (code === "ESRCH") return "dead";
524
+ return "unknown";
513
525
  }
514
526
  }
527
+ function isAlive(pid) {
528
+ return probeAlive(pid) === "alive";
529
+ }
515
530
  function isZombie(pid) {
516
531
  if (!Number.isInteger(pid) || pid <= 0 || isWindows) return false;
517
532
  if (isLinux) {
@@ -611,6 +626,7 @@ var require_probe = __commonJS({
611
626
  readCmdline: readCmdline2,
612
627
  pgrepList,
613
628
  isAlive,
629
+ probeAlive,
614
630
  isZombie
615
631
  };
616
632
  }
@@ -637,6 +653,7 @@ var require_pidlookup = __commonJS({
637
653
  readCmdline: readCmdline2,
638
654
  pgrepList,
639
655
  isAlive,
656
+ probeAlive,
640
657
  isZombie
641
658
  } = require_probe();
642
659
  var isLinux = process.platform === "linux";
@@ -659,6 +676,7 @@ var require_pidlookup = __commonJS({
659
676
  module2.exports = {
660
677
  findListeningPid,
661
678
  isAlive,
679
+ probeAlive,
662
680
  isZombie,
663
681
  readCmdline: readCmdline2,
664
682
  normCmdline,
@@ -929,7 +947,7 @@ var require_version = __commonJS({
929
947
  var fs2 = require("node:fs");
930
948
  var path2 = require("node:path");
931
949
  function guardVersion() {
932
- if (true) return String("0.1.6-BETA.4");
950
+ if (true) return String("0.1.6-BETA.6");
933
951
  try {
934
952
  return JSON.parse(fs2.readFileSync(path2.join(__dirname, "..", "..", "package.json"), "utf8")).version || "unknown";
935
953
  } catch {
@@ -949,6 +967,8 @@ var require_main_record = __commonJS({
949
967
  const reg = () => typeof g.getManagedObjects === "function" ? g.getManagedObjects() : null;
950
968
  const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
951
969
  let fallback = null;
970
+ const DEFAULTS = { restartCount: 0, backoffLevel: 0, backoffUntil: null, crashWindowStart: null, crashWindowRestarts: 0 };
971
+ const buffered = /* @__PURE__ */ new Map();
952
972
  function entryOf() {
953
973
  const m = reg();
954
974
  if (!m || typeof m.get !== "function") return null;
@@ -964,8 +984,8 @@ var require_main_record = __commonJS({
964
984
  kind: "dsh",
965
985
  id: "main",
966
986
  name: "\u4E3B\u5B9E\u4F8B",
987
+ // 不带 guardian(B2-2/B2-3):目录 entry 形态已无该键,守护开关权威在 dsh-main.json。
967
988
  desired: "running",
968
- guardian: true,
969
989
  ownership: { ports: [], rootPath: null, unit: null, daemonScript: null, processMode: "spawn", meta: null },
970
990
  phase: "stopped",
971
991
  lastObserved: null,
@@ -994,11 +1014,26 @@ var require_main_record = __commonJS({
994
1014
  }
995
1015
  }
996
1016
  function storeOf() {
997
- return entryOf() || fallbackEntryOf();
1017
+ const e = entryOf();
1018
+ if (!e) return fallbackEntryOf();
1019
+ if (buffered.size) flushBuffered(e);
1020
+ return e;
1021
+ }
1022
+ function flushBuffered(e) {
1023
+ let changed = false;
1024
+ for (const [k, v] of buffered) {
1025
+ if (k in DEFAULTS && e[k] === DEFAULTS[k] && e[k] !== v) {
1026
+ e[k] = v;
1027
+ changed = true;
1028
+ }
1029
+ }
1030
+ buffered.clear();
1031
+ if (changed) persistCrashField();
998
1032
  }
999
1033
  function fieldOf(name, v, write) {
1000
1034
  const e = storeOf();
1001
1035
  if (write) {
1036
+ if (e === fallback && name in DEFAULTS) buffered.set(name, v);
1002
1037
  if (e[name] !== v) {
1003
1038
  e[name] = v;
1004
1039
  persistCrashField();
@@ -1280,8 +1315,8 @@ var require_fields = __commonJS({
1280
1315
  try {
1281
1316
  if (m && typeof m.setPhase === "function" && record.entryOf() === e) {
1282
1317
  if (e.phase !== ph) m.setPhase("main", ph);
1283
- } else if (e.phase !== ph) {
1284
- e.phase = ph;
1318
+ } else {
1319
+ record.fieldOf("phase", ph, true);
1285
1320
  }
1286
1321
  } catch (e2) {
1287
1322
  const l = logger();
@@ -1308,8 +1343,8 @@ var require_fields = __commonJS({
1308
1343
  try {
1309
1344
  if (m && typeof m.update === "function" && record.entryOf() === e) {
1310
1345
  if (e.desired !== want) m.update("main", { desired: want });
1311
- } else if (e.desired !== want) {
1312
- e.desired = want;
1346
+ } else {
1347
+ record.fieldOf("desired", want, true);
1313
1348
  }
1314
1349
  } catch (e2) {
1315
1350
  const l = logger();
@@ -1511,6 +1546,7 @@ var require_desired = __commonJS({
1511
1546
  const intents = () => typeof g.getIntents === "function" ? g.getIntents() : null;
1512
1547
  const events = () => typeof g.getEvents === "function" ? g.getEvents() : null;
1513
1548
  const configPath = () => typeof g.getConfigPath === "function" ? g.getConfigPath() : null;
1549
+ const configAliases = () => typeof g.getConfigAliases === "function" ? g.getConfigAliases() || [] : [];
1514
1550
  const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
1515
1551
  const setCrashHalted = typeof g.setCrashHalted === "function" ? g.setCrashHalted : () => {
1516
1552
  };
@@ -1592,7 +1628,9 @@ var require_desired = __commonJS({
1592
1628
  }
1593
1629
  }
1594
1630
  Object.assign(cur, patch);
1595
- delete cur.switcherAutoStart;
1631
+ for (const [from, to] of configAliases()) {
1632
+ if (cur[from] !== void 0 && cur[to] !== void 0) delete cur[from];
1633
+ }
1596
1634
  writeAtomic(p, JSON.stringify(cur, null, 2), { mode: 384 });
1597
1635
  return true;
1598
1636
  } catch (e) {
@@ -1741,6 +1779,7 @@ var require_collaborator = __commonJS({
1741
1779
  getIntents: g.getIntents,
1742
1780
  getEvents: g.getEvents,
1743
1781
  getConfigPath: g.getConfigPath,
1782
+ getConfigAliases: g.getConfigAliases,
1744
1783
  getLogger: logger,
1745
1784
  setCrashHalted: g.setCrashHalted,
1746
1785
  setManualRestart: g.setManualRestart,
@@ -2022,13 +2061,11 @@ var require_specs = __commonJS({
2022
2061
  const daemons = () => typeof g.getDaemons === "function" ? g.getDaemons() : null;
2023
2062
  const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
2024
2063
  function mainSpec() {
2025
- const m = state().readMainMeta();
2026
2064
  return {
2027
2065
  kind: "dsh",
2028
2066
  id: "main",
2029
2067
  name: "\u4E3B\u5B9E\u4F8B",
2030
2068
  desired: state().desired() === "stopped" ? "stopped" : "running",
2031
- guardian: m.guardian === true,
2032
2069
  ownership: {
2033
2070
  ports: [{ role: "dsh-main", port: Number(config().targetPort || 3080) }],
2034
2071
  rootPath: path2.join(os2.homedir(), ".dsh"),
@@ -2048,8 +2085,6 @@ var require_specs = __commonJS({
2048
2085
  kind: "sandbox-instance",
2049
2086
  id: inst.id,
2050
2087
  name: String(inst.name || inst.id),
2051
- desired: inst.state && inst.state.desired === "stopped" ? "stopped" : "running",
2052
- guardian: inst.guardian === true,
2053
2088
  ownership: {
2054
2089
  ports: [{ role: "inst", port: Number(inst.port) }],
2055
2090
  rootPath,
@@ -2063,7 +2098,7 @@ var require_specs = __commonJS({
2063
2098
  if (!m || !spec) return;
2064
2099
  try {
2065
2100
  const existing = m.get(spec.id);
2066
- if (existing) m.update(spec.id, { desired: spec.desired, guardian: spec.guardian, name: spec.name, ownership: spec.ownership });
2101
+ if (existing) m.update(spec.id, { desired: spec.desired, name: spec.name, ownership: spec.ownership });
2067
2102
  else m.register(spec);
2068
2103
  } catch (e) {
2069
2104
  const l = logger();
@@ -3172,6 +3207,14 @@ var require_store2 = __commonJS({
3172
3207
  }
3173
3208
  return out;
3174
3209
  }
3210
+ function fileStamp(file) {
3211
+ try {
3212
+ const st = fs2.statSync(file);
3213
+ return st.mtimeMs + ":" + st.size;
3214
+ } catch {
3215
+ return "0";
3216
+ }
3217
+ }
3175
3218
  function saveRecords(file, records) {
3176
3219
  try {
3177
3220
  fs2.mkdirSync(path2.dirname(file), { recursive: true });
@@ -3197,7 +3240,7 @@ var require_store2 = __commonJS({
3197
3240
  }
3198
3241
  return out;
3199
3242
  }
3200
- module2.exports = { loadRecords, saveRecords, extraRecords };
3243
+ module2.exports = { loadRecords, saveRecords, extraRecords, fileStamp };
3201
3244
  }
3202
3245
  });
3203
3246
 
@@ -3440,6 +3483,7 @@ var require_alloc = __commonJS({
3440
3483
  }
3441
3484
  r._allocLock = true;
3442
3485
  this._xrel = await this._acquireXLock();
3486
+ r._syncFromDisk();
3443
3487
  }
3444
3488
  _releaseAlloc() {
3445
3489
  this._registry._allocLock = false;
@@ -3694,6 +3738,14 @@ var require_pool = __commonJS({
3694
3738
  _load() {
3695
3739
  this._records = /* @__PURE__ */ new Map();
3696
3740
  for (const r of store.loadRecords(this._file)) this._records.set(r.port, r);
3741
+ this._diskStamp = store.fileStamp(this._file);
3742
+ }
3743
+ /** 跨进程对时(B2-5):ports.json 是多进程(守卫 + lan-daemon)共享事实源,各方全量
3744
+ * read-modify-write,陈旧内存快照会在 _save 时把他人新增整段覆盖丢失、或让分配器抢注
3745
+ * 他进程已登记的端口。指纹(mtime+size)变化即重载——所有写口与冲突判读口的入口。
3746
+ * 不动 _allocLock(复位会击穿本进程在飞分配的互斥)。 */
3747
+ _syncFromDisk() {
3748
+ if (store.fileStamp(this._file) !== this._diskStamp) this._load();
3697
3749
  }
3698
3750
  /** 重新从文件加载(读路径先 reload,以权威文件为准)。 */
3699
3751
  reload() {
@@ -3703,6 +3755,7 @@ var require_pool = __commonJS({
3703
3755
  }
3704
3756
  _save() {
3705
3757
  store.saveRecords(this._file, [...this._records.values()]);
3758
+ this._diskStamp = store.fileStamp(this._file);
3706
3759
  }
3707
3760
  /** 通用记录迁移:owner 命中任一前缀的记录 oldFile 到 newFile,并从旧文件清除。 */
3708
3761
  migrateByOwnerPrefix(oldFile, newFile, prefixes) {
@@ -3711,6 +3764,7 @@ var require_pool = __commonJS({
3711
3764
  /* 登记(固定 / 用户 / 动态) */
3712
3765
  /** 登记固定端口;同端口已被其它固定角色占用则报错;user/动态记录由固定权威覆盖。 */
3713
3766
  register(role, port) {
3767
+ this._syncFromDisk();
3714
3768
  const p = Number(port);
3715
3769
  if (!Number.isInteger(p) || p <= 0 || p > 65535) throw new Error("ports.register: \u975E\u6CD5\u7AEF\u53E3 " + port);
3716
3770
  const existing = this._records.get(p);
@@ -3723,8 +3777,21 @@ var require_pool = __commonJS({
3723
3777
  this._save();
3724
3778
  return p;
3725
3779
  }
3780
+ /** 登记固定端口,并保证该 role 在全表唯一(先清除同 role 的其它端口记录再登记)。
3781
+ * 登记表以端口号为键,避让/重绑后新端口是**追加**记录;只 release 旧端口不足以立住不变式
3782
+ * (该调用在内核侧被 catch{} 包住且忽略返回值),残留两条同 role 记录时任何「按 role 取号」
3783
+ * 的读法(本表 get、桌面壳读 ports.json)都可能拿到一个没人监听的端口。
3784
+ * @returns {number} port */
3785
+ registerSole(role, port) {
3786
+ const p = Number(port);
3787
+ for (const [existing, r] of [...this._records]) {
3788
+ if (r.role === role && existing !== p) this._records.delete(existing);
3789
+ }
3790
+ return this.register(role, p);
3791
+ }
3726
3792
  /** 登记用户配置端口(实例内部端口等);冲突(固定/保留池/已占)抛错。 */
3727
3793
  registerUser(port, owner) {
3794
+ this._syncFromDisk();
3728
3795
  const p = Number(port);
3729
3796
  if (!Number.isInteger(p) || p <= 0 || p > 65535) throw new Error("ports.registerUser: \u975E\u6CD5\u7AEF\u53E3 " + port);
3730
3797
  if (this._records.has(p)) throw new Error("\u7AEF\u53E3 " + p + " \u5DF2\u88AB [" + this._records.get(p).role + "] \u5360\u7528");
@@ -3736,6 +3803,7 @@ var require_pool = __commonJS({
3736
3803
  }
3737
3804
  /** 按 owner 释放端口(对象删除/关闭时调用)。 */
3738
3805
  unregister(owner) {
3806
+ this._syncFromDisk();
3739
3807
  let removed = false;
3740
3808
  for (const [p, r] of this._records) {
3741
3809
  if (r.owner === owner) {
@@ -3748,6 +3816,7 @@ var require_pool = __commonJS({
3748
3816
  /** 释放端口:不传 ownerId 按端口号;传了则仅当登记 owner 匹配才释放。空值检查必须先于 owner 比较。
3749
3817
  * @returns {boolean} 是否真的释放了一条记录 */
3750
3818
  release(port, ownerId) {
3819
+ this._syncFromDisk();
3751
3820
  const p = Number(port);
3752
3821
  const rec = this._records.get(p);
3753
3822
  if (!rec) return false;
@@ -3757,18 +3826,27 @@ var require_pool = __commonJS({
3757
3826
  return true;
3758
3827
  }
3759
3828
  /* 查询 */
3760
- /** 按 role 取端口(固定端口)。 */
3829
+ /** 按 role 取端口(固定端口)。同 role 有多条(老版本避让留下的残留记录)时取**最新登记**:
3830
+ * 桌面壳读 ports.json 用的是同一判据,两侧不许对「哪个端口是当前的」给出不同答案。 */
3761
3831
  get(role) {
3762
- for (const r of this._records.values()) if (r.role === role) return r.port;
3763
- return null;
3832
+ this._syncFromDisk();
3833
+ let best = null;
3834
+ for (const r of this._records.values()) {
3835
+ if (r.role !== role) continue;
3836
+ if (!best || (r.createdAt || 0) >= (best.createdAt || 0)) best = r;
3837
+ }
3838
+ return best ? best.port : null;
3764
3839
  }
3765
3840
  isRegistered(port) {
3841
+ this._syncFromDisk();
3766
3842
  return this._records.has(Number(port));
3767
3843
  }
3768
3844
  recordOf(port) {
3845
+ this._syncFromDisk();
3769
3846
  return this._records.get(Number(port)) || null;
3770
3847
  }
3771
3848
  byOwner(owner) {
3849
+ this._syncFromDisk();
3772
3850
  for (const r of this._records.values()) if (r.owner === owner) return r.port;
3773
3851
  return null;
3774
3852
  }
@@ -3781,6 +3859,7 @@ var require_pool = __commonJS({
3781
3859
  }
3782
3860
  /** 全部端口清单(按端口升序)。 */
3783
3861
  list() {
3862
+ this._syncFromDisk();
3784
3863
  return [...this._records.values()].sort((a, b) => a.port - b.port);
3785
3864
  }
3786
3865
  /** 只读聚合:本注册表 + 同目录下其它注册表文件(去重,本表优先)。
@@ -3805,6 +3884,7 @@ var require_pool = __commonJS({
3805
3884
  }
3806
3885
  /** 显式登记已分配端口(复用持久化端口时调用)。 */
3807
3886
  allocateMark(port, role, owner) {
3887
+ this._syncFromDisk();
3808
3888
  const p = Number(port);
3809
3889
  if (!this._records.has(p)) {
3810
3890
  this._records.set(p, { port: p, role: role || "dynamic", owner: owner || "dynamic", createdAt: Date.now() });
@@ -3999,6 +4079,7 @@ var require_collaborators = __commonJS({
3999
4079
  var { createCtl } = require_collaborator3();
4000
4080
  var { createOrphanScan } = require_collaborator4();
4001
4081
  var { ENTRY_FIELDS, PROC_FIELDS } = require_field_tables();
4082
+ var { aliases: CONFIG_ALIASES } = require_domain_config();
4002
4083
  var THIN_SPEC = {
4003
4084
  ctl: {
4004
4085
  call: "_ctlCall",
@@ -4088,6 +4169,7 @@ var require_collaborators = __commonJS({
4088
4169
  const state = createStateStore({
4089
4170
  getConfig: () => host2.config,
4090
4171
  getConfigPath: () => host2.configPath,
4172
+ getConfigAliases: () => CONFIG_ALIASES,
4091
4173
  getLogger: () => host2.logger,
4092
4174
  getEvents: () => host2.events,
4093
4175
  getManagedObjects: () => host2.managedObjects,
@@ -4133,7 +4215,6 @@ var require_collaborators = __commonJS({
4133
4215
  return host2;
4134
4216
  };
4135
4217
  host2._dshEntry = () => state.dshEntry();
4136
- host2._mainFallbackEntry = () => state.fallbackEntry();
4137
4218
  host2._persistCrashField = () => state.persistCrashField();
4138
4219
  host2._mStore = () => state.store();
4139
4220
  host2._mField = function(name, v) {
@@ -6278,7 +6359,7 @@ var require_bootstrap = __commonJS({
6278
6359
  }
6279
6360
  function _registerFixedPorts(host2) {
6280
6361
  ports.register("dsh-main", host2.config.targetPort);
6281
- ports.register("supervisor-api", host2.config.apiPort);
6362
+ ports.registerSole("supervisor-api", host2.config.apiPort);
6282
6363
  }
6283
6364
  function _bindNativeDshCommand(host2) {
6284
6365
  try {
@@ -6362,9 +6443,9 @@ var require_api_rebind = __commonJS({
6362
6443
  bind._tries = 0;
6363
6444
  host2.api = server;
6364
6445
  try {
6365
- portsShared.register("supervisor-api", host2.config.apiPort);
6446
+ portsShared.registerSole("supervisor-api", host2.config.apiPort);
6366
6447
  } catch (e) {
6367
- host2.logger.warn("ports.register(supervisor-api) \u5931\u8D25: " + (e && e.message || e));
6448
+ host2.logger.warn("ports.registerSole(supervisor-api) \u5931\u8D25: " + (e && e.message || e));
6368
6449
  }
6369
6450
  host2.events.append("api_listening", { host: host2.config.apiHost, port: host2.config.apiPort });
6370
6451
  host2.logger.info("api listening on " + host2.config.apiHost + ":" + host2.config.apiPort);
@@ -6392,17 +6473,13 @@ var require_api_rebind = __commonJS({
6392
6473
  host2.api = server;
6393
6474
  const prev = host2.config.apiPort;
6394
6475
  if (port !== prev) {
6395
- try {
6396
- portsShared.release(prev, "system:supervisor-api");
6397
- } catch {
6398
- }
6399
6476
  host2.config.apiPort = port;
6400
6477
  if (host2.configPath) host2.persistConfigPatch({ apiPort: port });
6401
6478
  }
6402
6479
  try {
6403
- portsShared.register("supervisor-api", port);
6480
+ portsShared.registerSole("supervisor-api", port);
6404
6481
  } catch (e) {
6405
- host2.logger.warn("ports.register(actual) \u5931\u8D25: " + e.message);
6482
+ host2.logger.warn("ports.registerSole(supervisor-api) \u5931\u8D25: " + e.message);
6406
6483
  }
6407
6484
  host2.events.append("api_listening", { host: host2.config.apiHost, port });
6408
6485
  host2.logger.info("api listening on " + host2.config.apiHost + ":" + port);
@@ -7804,8 +7881,8 @@ var require_instance_adapter = __commonJS({
7804
7881
  return { ok: running, error: running ? null : "\u6C99\u7BB1\u5B9E\u4F8B\u672A\u8FD0\u884C" };
7805
7882
  },
7806
7883
  /** 目录项 <- 实例域状态对齐(监督拍后调用):实例已删 -> 注销(防死登记);存在 -> 经
7807
- * sandboxSpec 同步 name/guardian/ownership + phase 落目录词表。desired 走的是实例意图字段的
7808
- * 投影(不是 phase 推导),所以观测路径写它不会改写任何意图(契约 M-1)。 */
7884
+ * sandboxSpec 同步 name/guardian/ownership + phase 落目录词表。沙箱不申报 desired
7885
+ * (运行意图无第二落点,B2-1),观测路径因此不可能改写任何意图。 */
7809
7886
  _syncSandboxRegistryEntry(entry) {
7810
7887
  const d = depsOf(this);
7811
7888
  if (!entry || !d.managedObjects() || !d.instances()) return;
@@ -7838,6 +7915,9 @@ var require_decide = __commonJS({
7838
7915
  "src/app/main/decide.js"(exports2, module2) {
7839
7916
  "use strict";
7840
7917
  var pidlook = require_pidlookup();
7918
+ function startDeadlinePassed(deadline, now) {
7919
+ return !!(deadline && now > deadline);
7920
+ }
7841
7921
  var DEPS = /* @__PURE__ */ new WeakMap();
7842
7922
  function depsOf(host2) {
7843
7923
  let d = DEPS.get(host2);
@@ -7925,7 +8005,7 @@ var require_decide = __commonJS({
7925
8005
  upgradeHold: d.upgradeHold() === true,
7926
8006
  manualRestart: d.manualRestart() === true,
7927
8007
  spawnBlocked: !!(d.mSpawnBlockedUntil() && now < d.mSpawnBlockedUntil()),
7928
- startDeadlinePassed: !!(d.mStartDeadline() && now > d.mStartDeadline()),
8008
+ startDeadlinePassed: startDeadlinePassed(d.mStartDeadline(), now),
7929
8009
  restartDue: d.mRestartAt() === null || now >= d.mRestartAt(),
7930
8010
  backoffDue: d.mBackoffUntil() === null || now >= d.mBackoffUntil(),
7931
8011
  // `_shouldRun()` 有两个否决位,快照必须建模(crashHalted/sessionHalting),否则影子每拍
@@ -8000,7 +8080,9 @@ var require_decide = __commonJS({
8000
8080
  _decideCrashRestart(reason) {
8001
8081
  return decideCrashRestart(reason);
8002
8082
  }
8003
- }
8083
+ },
8084
+ // 非 host 方法:纯谓词导出,controller 与本文件快照判据共用(facets 只安装 methods)。
8085
+ startDeadlinePassed
8004
8086
  };
8005
8087
  }
8006
8088
  });
@@ -8051,6 +8133,7 @@ var require_controller = __commonJS({
8051
8133
  "use strict";
8052
8134
  var pidlook = require_pidlookup();
8053
8135
  var monitor = require_monitor();
8136
+ var { startDeadlinePassed } = require_decide();
8054
8137
  var DEPS = /* @__PURE__ */ new WeakMap();
8055
8138
  function depsOf(host2) {
8056
8139
  let d = DEPS.get(host2);
@@ -8155,6 +8238,9 @@ var require_controller = __commonJS({
8155
8238
  mStartDeadline() {
8156
8239
  return host2._mStartDeadline();
8157
8240
  },
8241
+ mSetStartDeadline(v) {
8242
+ return host2._mSetStartDeadline(v);
8243
+ },
8158
8244
  mRestartAt() {
8159
8245
  return host2._mRestartAt();
8160
8246
  },
@@ -8302,7 +8388,9 @@ var require_controller = __commonJS({
8302
8388
  }
8303
8389
  case "STARTING": {
8304
8390
  if (portUp && healthOk) d.main().enterRunning();
8305
- else if (Date.now() > d.mStartDeadline()) d.main().beginRestart("start_timeout", { countCrash: true });
8391
+ else if (d.mStartDeadline() === null) {
8392
+ d.mSetStartDeadline(Date.now() + d.config().startTimeoutMs);
8393
+ } else if (startDeadlinePassed(d.mStartDeadline(), Date.now())) d.main().beginRestart("start_timeout", { countCrash: true });
8306
8394
  break;
8307
8395
  }
8308
8396
  case "RUNNING": {
@@ -9556,13 +9644,7 @@ var require_process_wait = __commonJS({
9556
9644
  async function waitProcessExit(pid, timeoutMs) {
9557
9645
  const deadline = Date.now() + timeoutMs;
9558
9646
  while (Date.now() < deadline) {
9559
- let alive = false;
9560
- try {
9561
- alive = pidlook.isAlive ? pidlook.isAlive(pid) : true;
9562
- } catch {
9563
- alive = false;
9564
- }
9565
- if (!alive) return true;
9647
+ if (!pidlook.isAlive(pid)) return true;
9566
9648
  await new Promise((r) => setTimeout(r, 200));
9567
9649
  }
9568
9650
  return false;
@@ -9680,13 +9762,15 @@ var require_process3 = __commonJS({
9680
9762
  } catch {
9681
9763
  }
9682
9764
  }
9765
+ /** 判活(platform/pidlookup.probeAlive 单源):unknown 不 fail-open——必须有第二条证据
9766
+ * (ctl 端口属主正是该 pid 且 cmdline 匹配本服务)才认活,否则按死走 reclaim/spawn。
9767
+ * fail-open 会让已死 daemon 被判活,此后既不接管也不拉起,永不自愈。 */
9683
9768
  _pidAlive(pid) {
9684
9769
  if (!pid) return false;
9685
- try {
9686
- return pidlook.isAlive ? !!pidlook.isAlive(pid) : true;
9687
- } catch {
9688
- return true;
9689
- }
9770
+ const st = pidlook.probeAlive(pid);
9771
+ if (st === "alive") return true;
9772
+ if (st === "dead") return false;
9773
+ return this._ctlOwnerPid() === pid;
9690
9774
  }
9691
9775
  /** ctl 端口的监听者是否就是本服务进程(cmdline 匹配)。 */
9692
9776
  _ctlOwnerPid() {
@@ -10192,6 +10276,7 @@ var require_identity = __commonJS({
10192
10276
  "use strict";
10193
10277
  var fs2 = require("node:fs");
10194
10278
  var path2 = require("node:path");
10279
+ var { isAlive } = require_pidlookup();
10195
10280
  function lockPid(p) {
10196
10281
  if (!p) return null;
10197
10282
  try {
@@ -10201,14 +10286,8 @@ var require_identity = __commonJS({
10201
10286
  return null;
10202
10287
  }
10203
10288
  }
10204
- function pidAlive(pid) {
10205
- if (!Number.isInteger(pid) || pid <= 0) return false;
10206
- try {
10207
- process.kill(pid, 0);
10208
- return true;
10209
- } catch (e) {
10210
- return !!(e && e.code === "EPERM");
10211
- }
10289
+ function pidAlive2(pid) {
10290
+ return isAlive(pid);
10212
10291
  }
10213
10292
  function acquireLock2(p, onErr) {
10214
10293
  if (!p) return false;
@@ -10233,7 +10312,7 @@ var require_identity = __commonJS({
10233
10312
  if (attempt()) return true;
10234
10313
  const holder = lockPid(p);
10235
10314
  if (holder === process.pid) return true;
10236
- if (holder !== null && pidAlive(holder)) return false;
10315
+ if (holder !== null && pidAlive2(holder)) return false;
10237
10316
  try {
10238
10317
  fs2.unlinkSync(p);
10239
10318
  } catch {
@@ -10271,7 +10350,7 @@ var require_identity = __commonJS({
10271
10350
  }
10272
10351
  module2.exports = {
10273
10352
  // 测试缝:锁的取/放/读主是纯 fs + kill(0) 语义,直接导出给回归用。
10274
- _lockPrimitives: { acquireLock: acquireLock2, releaseLock: releaseLock2, lockPid, pidAlive },
10353
+ _lockPrimitives: { acquireLock: acquireLock2, releaseLock: releaseLock2, lockPid, pidAlive: pidAlive2 },
10275
10354
  methods: {
10276
10355
  _lanLockPath() {
10277
10356
  const d = depsOf(this);
@@ -10589,7 +10668,7 @@ var require_ports2 = __commonJS({
10589
10668
  "use strict";
10590
10669
  var probe = require_probe2();
10591
10670
  var ports = require_ports().shared;
10592
- var SIBLING_REGISTRIES = ["ports-lan.json", "ports-router.json"];
10671
+ var SIBLING_REGISTRIES = ["ports-router.json"];
10593
10672
  var DEPS = /* @__PURE__ */ new WeakMap();
10594
10673
  function depsOf(host2) {
10595
10674
  let d = DEPS.get(host2);
@@ -11185,24 +11264,32 @@ var require_lan2 = __commonJS({
11185
11264
  }
11186
11265
  }
11187
11266
  return {
11188
- /** 远程控制模式唯一写入口(off|lan|wan)。wan 前置闸:必须先有合规访问令牌。 */
11267
+ /** 远程控制模式唯一写入口(off|lan|wan)。mode 必须显式给出——缺省归 'off' 会让
11268
+ * 漏字段的请求静默关闭远程控制。wan 前置闸:必须先有合规访问令牌。 */
11189
11269
  setRemoteMode(id, mode) {
11190
- const want = mode === "lan" || mode === "wan" ? mode : "off";
11270
+ if (mode !== "off" && mode !== "lan" && mode !== "wan") {
11271
+ return { ok: false, error: "mode \u5FC5\u987B\u663E\u5F0F\u7ED9\u51FA\uFF08off|lan|wan\uFF09" };
11272
+ }
11191
11273
  const target = resolveTarget(id);
11192
11274
  if (!target) return { ok: false, error: "\u5B9E\u4F8B\u4E0D\u5B58\u5728" };
11193
- if (want === "wan") {
11275
+ if (mode === "wan") {
11194
11276
  const v = validateWanAccess({ remoteToken: target.remoteToken });
11195
11277
  if (!v.ok) return { ok: false, error: v.error };
11196
11278
  }
11197
11279
  if (target.kind === "main") {
11198
- if (target.mode !== want) applyMainIntent({ remoteMode: want }, { id: "main", name: "\u539F\u751F DSH", mode: want });
11280
+ if (target.mode !== mode) applyMainIntent({ remoteMode: mode }, { id: "main", name: "\u539F\u751F DSH", mode });
11199
11281
  return { ok: true };
11200
11282
  }
11201
- return g.getInstances().updateInstance(id, { remoteMode: want });
11283
+ return g.getInstances().updateInstance(id, { remoteMode: mode });
11202
11284
  },
11203
- /** 访问令牌唯一写入口(空串=清除;lan 模式可无令牌,wan 模式的守门由执行边界闸兜住)。 */
11285
+ /** 访问令牌唯一写入口。token 必须是字符串:空串=显式清除;缺字段/非字符串=请求方缺陷,
11286
+ * 拒绝而非当作清除(漏 token 字段清掉访问凭据是事故,不是语义)。lan 模式可无令牌,
11287
+ * wan 模式的守门由执行边界闸兜住。 */
11204
11288
  setRemoteToken(id, token) {
11205
- const next = String(token || "");
11289
+ if (typeof token !== "string") {
11290
+ return { ok: false, error: "token \u5FC5\u987B\u663E\u5F0F\u7ED9\u51FA\uFF08\u7A7A\u4E32=\u6E05\u9664\uFF09" };
11291
+ }
11292
+ const next = token;
11206
11293
  if (next && !remoteTokenStrength(next).ok) {
11207
11294
  return { ok: false, error: "\u8FDC\u7A0B\u8BBF\u95EE\u4EE4\u724C\uFF08remoteToken\uFF09\u81F3\u5C11 8 \u4F4D" };
11208
11295
  }
@@ -11286,16 +11373,17 @@ var require_env_catalog = __commonJS({
11286
11373
  const v = ex2.runOut(bin, (Array.isArray(args) ? args : []).concat(["--version"]), { timeoutMs: 3e3 });
11287
11374
  return v ? v.trim() || null : null;
11288
11375
  }
11376
+ function whichVersionAsync(bin, args) {
11377
+ return ex2.runOutAsync(bin, (Array.isArray(args) ? args : []).concat(["--version"]), { timeoutMs: 3e3 }).then((v) => v ? v.trim() || null : null);
11378
+ }
11289
11379
  var _verCache = /* @__PURE__ */ new Map();
11290
11380
  var CACHE_TTL = 1e4;
11291
- function cachedWhichVersion(bin, args) {
11381
+ function cacheKey(bin, args) {
11292
11382
  const a = Array.isArray(args) ? args : [];
11293
- const key = bin + "\0" + a.join("\0");
11294
- const hit = _verCache.get(key);
11295
- const now = Date.now();
11296
- if (hit && now - hit.at < CACHE_TTL) return hit.v;
11297
- const v = whichVersion(bin, a);
11298
- _verCache.set(key, { at: now, v });
11383
+ return bin + "\0" + a.join("\0");
11384
+ }
11385
+ function cacheSet(key, v) {
11386
+ _verCache.set(key, { at: Date.now(), v });
11299
11387
  if (_verCache.size > 16) {
11300
11388
  let oldest = null;
11301
11389
  for (const [k, e] of _verCache) if (!oldest || e.at < oldest.at) oldest = { k, at: e.at };
@@ -11303,6 +11391,19 @@ var require_env_catalog = __commonJS({
11303
11391
  }
11304
11392
  return v;
11305
11393
  }
11394
+ function cachedWhichVersion(bin, args) {
11395
+ const key = cacheKey(bin, args);
11396
+ const hit = _verCache.get(key);
11397
+ const now = Date.now();
11398
+ if (hit && now - hit.at < CACHE_TTL) return hit.v;
11399
+ return cacheSet(key, whichVersion(bin, args));
11400
+ }
11401
+ function cachedWhichVersionAsync(bin, args) {
11402
+ const key = cacheKey(bin, args);
11403
+ const hit = _verCache.get(key);
11404
+ if (hit && Date.now() - hit.at < CACHE_TTL) return Promise.resolve(hit.v);
11405
+ return whichVersionAsync(bin, args).then((v) => cacheSet(key, v));
11406
+ }
11306
11407
  var MIN_NODE_DEFAULT = "v22.12.0";
11307
11408
  var _runtimeMetaCache = null;
11308
11409
  var _runtimeMetaAt = 0;
@@ -11329,50 +11430,68 @@ var require_env_catalog = __commonJS({
11329
11430
  }
11330
11431
  return true;
11331
11432
  }
11332
- function probeNode() {
11333
- const v = cachedWhichVersion("node");
11433
+ function nodeVerdict(v) {
11334
11434
  if (!v) return null;
11335
11435
  const m = /v?(\d+\.\d+\.\d+)/.exec(String(v));
11336
11436
  const ver = m ? m[1] : String(v).trim();
11337
11437
  const min = String(runtimeMeta().minNode || MIN_NODE_DEFAULT);
11338
11438
  return { version: "v" + ver, min, meets: verAtLeast(ver, min) };
11339
11439
  }
11440
+ function probeNode() {
11441
+ return nodeVerdict(cachedWhichVersion("node"));
11442
+ }
11443
+ function probeNodeAsync() {
11444
+ return cachedWhichVersionAsync("node").then(nodeVerdict);
11445
+ }
11340
11446
  function probeNpm() {
11341
11447
  const l = runtime.npmLauncher();
11342
11448
  return cachedWhichVersion(l.program, l.args);
11343
11449
  }
11450
+ function probeNpmAsync() {
11451
+ const l = runtime.npmLauncher();
11452
+ return cachedWhichVersionAsync(l.program, l.args);
11453
+ }
11344
11454
  var SYSTEM_ENTRIES = {
11345
- node: { label: "Node.js", required: true, probe: probeNode },
11346
- npm: { label: "npm", required: true, probe: probeNpm },
11347
- git: { label: "git", required: false, probe: () => cachedWhichVersion("git") }
11455
+ node: { label: "Node.js", required: true, probe: probeNode, probeAsync: probeNodeAsync },
11456
+ npm: { label: "npm", required: true, probe: probeNpm, probeAsync: probeNpmAsync },
11457
+ git: { label: "git", required: false, probe: () => cachedWhichVersion("git"), probeAsync: () => cachedWhichVersionAsync("git") }
11348
11458
  };
11459
+ function entryView(id, e, v) {
11460
+ if (v && typeof v === "object" && typeof v.meets === "boolean") {
11461
+ return {
11462
+ label: e.label,
11463
+ required: e.required,
11464
+ state: v.meets ? "ok" : "outdated",
11465
+ version: v.version,
11466
+ min: v.min,
11467
+ meets: v.meets,
11468
+ detail: v.meets ? v.version : v.version + "\uFF08\u4F4E\u4E8E\u6700\u4F4E\u8981\u6C42 " + v.min + "\uFF09"
11469
+ };
11470
+ }
11471
+ return { label: e.label, required: e.required, state: v ? "ok" : "missing", detail: v };
11472
+ }
11349
11473
  var EnvCatalog = class {
11350
11474
  constructor(config) {
11351
11475
  this.config = config || {};
11352
11476
  }
11353
- /** 系统二进制条目探测:{ id: { label, required, state, detail } }。state 三态:
11354
- * ok = 存在且满足门槛(Node 需 >= 壳投放的 minNode);outdated = 存在但低于门槛;missing = 不存在。
11355
- * 兼容:detail 保持字符串,新增字段(version/min/meets)放 detail 之外,不破坏既有契约。 */
11477
+ /** 系统二进制条目探测(同步口径):{ id: 条目视图 }。仅限启动早期/CLI;HTTP 路径用 probeAsync。 */
11356
11478
  probe() {
11357
11479
  const out = {};
11358
11480
  for (const [id, e] of Object.entries(SYSTEM_ENTRIES)) {
11359
- const v = e.probe() || null;
11360
- if (v && typeof v === "object" && typeof v.meets === "boolean") {
11361
- out[id] = {
11362
- label: e.label,
11363
- required: e.required,
11364
- state: v.meets ? "ok" : "outdated",
11365
- version: v.version,
11366
- min: v.min,
11367
- meets: v.meets,
11368
- detail: v.meets ? v.version : v.version + "\uFF08\u4F4E\u4E8E\u6700\u4F4E\u8981\u6C42 " + v.min + "\uFF09"
11369
- };
11370
- } else {
11371
- out[id] = { label: e.label, required: e.required, state: v ? "ok" : "missing", detail: v };
11372
- }
11481
+ out[id] = entryView(id, e, e.probe() || null);
11373
11482
  }
11374
11483
  return out;
11375
11484
  }
11485
+ /** 异步口径:条目并行探测(最坏 3s x N 的串行冻结 -> 全程不阻塞事件循环)。 */
11486
+ async probeAsync() {
11487
+ const entries = Object.entries(SYSTEM_ENTRIES);
11488
+ const vals = await Promise.all(entries.map(([, e]) => e.probeAsync()));
11489
+ const out = {};
11490
+ entries.forEach(([id, e], i) => {
11491
+ out[id] = entryView(id, e, vals[i] || null);
11492
+ });
11493
+ return out;
11494
+ }
11376
11495
  /** 内核更新依赖条目(单写入者契约:安装/重启归桌面壳,守卫只读 corePackageName 查版本状态)。
11377
11496
  * id 仍为 selfUpdate 以兼容既有 /env/status 消费方。 */
11378
11497
  selfUpdateEntry() {
@@ -11416,20 +11535,22 @@ var require_env = __commonJS({
11416
11535
  var fs2 = require("node:fs");
11417
11536
  var { EnvCatalog } = require_env_catalog();
11418
11537
  var runtimeContract = require_runtime();
11419
- function envCatalogSummary(that) {
11538
+ async function envCatalogSummary(that) {
11420
11539
  const cat = new EnvCatalog(that.config);
11421
11540
  const extra = {};
11422
11541
  const d = that.dshenvStatus();
11423
11542
  extra.dsh = cat.dshEntry(d.binOk, d.installed, d.bin);
11424
11543
  extra.selfUpdate = cat.selfUpdateEntry();
11425
- return cat.summary(extra);
11544
+ return cat.summary(extra, await cat.probeAsync());
11426
11545
  }
11427
11546
  module2.exports = {
11428
11547
  methods: {
11429
- envStatus() {
11548
+ // 异步:全部子进程探测(EnvCatalog/契约回读)走异步口径——本方法挂在 /env/status 上,
11549
+ // 同步 execFileSync 会把守卫事件循环冻结在探测超时上(心跳/自愈停摆,B1-6 收口)。
11550
+ async envStatus() {
11430
11551
  const c = runtimeContract.read() || {};
11431
- const cat = new EnvCatalog(this.config).probe();
11432
- const en = this.nativeManager && typeof this.nativeManager.checkEnvironment === "function" ? this.nativeManager.checkEnvironment() : null;
11552
+ const cat = await new EnvCatalog(this.config).probeAsync();
11553
+ const en = this.nativeManager && typeof this.nativeManager.checkEnvironment === "function" ? await this.nativeManager.checkEnvironment() : null;
11433
11554
  return {
11434
11555
  node: { detected: cat.node.detail || null, runtime: c.nodeVersion || null, path: c.nodePath || null },
11435
11556
  // npm 与 node 同构三段:detected = 本机实跑版本;runtime = 壳实跑后投放的版本
@@ -11441,7 +11562,7 @@ var require_env = __commonJS({
11441
11562
  ok: cat.node.state === "ok" && cat.npm.state === "ok",
11442
11563
  npmRoot: en ? en.npmRoot : null,
11443
11564
  // EnvCatalog 声明式视图(面板环境卡用)
11444
- catalog: envCatalogSummary(this),
11565
+ catalog: await envCatalogSummary(this),
11445
11566
  // 平台能力矩阵:三平台静态档位 x 实际工具探测;前端据此做能力感知呈现与降级提示。
11446
11567
  capabilities: (() => {
11447
11568
  try {
@@ -11667,15 +11788,16 @@ var require_versions = __commonJS({
11667
11788
  return { ok: false, error: e.message };
11668
11789
  }
11669
11790
  },
11670
- /** 读磁盘上运行位的自报版本:spawn --version,解析版本行。
11791
+ /** 读磁盘上运行位的自报版本:spawn --version,解析版本行(异步:20s 上限的同步 exec
11792
+ * 在 HTTP 路径上会冻结守卫整条事件循环,判据不变)。
11671
11793
  * 条件是 updatable(sea-binary 或 launcher):launcher 的 bin 入口同样可执行,
11672
11794
  * 若只认 sea-binary 则发布态永远读不到磁盘实况,updatePending 恒 false。
11673
11795
  * source-shell 不支持:其 --version 报的是开发目录版本,与 npm 安装无关。 */
11674
- _readBinarySelfVersion() {
11796
+ async _readBinarySelfVersion() {
11675
11797
  const dep = deploy.detect();
11676
11798
  if (!dep.updatable || !dep.runningTarget) return null;
11677
11799
  try {
11678
- const out = ex2.runOut(dep.runningTarget, ["--version"], { timeoutMs: 2e4 });
11800
+ const out = await ex2.runOutAsync(dep.runningTarget, ["--version"], { timeoutMs: 2e4 });
11679
11801
  const m = /dsh-supervisor v([^\s]+)/.exec(out);
11680
11802
  return m ? m[1] : null;
11681
11803
  } catch {
@@ -11697,18 +11819,18 @@ var require_versions = __commonJS({
11697
11819
  }
11698
11820
  return dir;
11699
11821
  },
11700
- /** 本地视角(无网络 I/O,同步安全):commit + 是否配了 upstream。 */
11701
- guardVersionLocal() {
11822
+ /** 本地视角(无网络 I/O;git 子进程异步执行——同步 spawn 会冻结事件循环,
11823
+ * 消费方含 HTTP 路径,见 exec.js 同步仅限启动早期/CLI 的纪律)。 */
11824
+ async guardVersionLocal() {
11702
11825
  const d = depsOf(this);
11703
11826
  const root = d.vcsRoot();
11704
- let commit = null;
11705
- commit = (ex2.runOut("git", ["-C", root, "rev-parse", "--short", "HEAD"]) || "").trim() || null;
11827
+ const [rawCommit, rawUp] = await Promise.all([
11828
+ ex2.runOutAsync("git", ["-C", root, "rev-parse", "--short", "HEAD"]),
11829
+ ex2.runOutAsync("git", ["-C", root, "rev-parse", "--abbrev-ref", "@{u}"])
11830
+ ]);
11831
+ const commit = (rawCommit || "").trim() || null;
11706
11832
  let upstream = "local";
11707
- try {
11708
- const up = (ex2.runOut("git", ["-C", root, "rev-parse", "--abbrev-ref", "@{u}"]) || "").trim();
11709
- if (up) upstream = "git-repo";
11710
- } catch {
11711
- }
11833
+ if ((rawUp || "").trim()) upstream = "git-repo";
11712
11834
  return { version: d.guardVersion(), runningVersion: d.guardVersion(), commit, updateAvailable: false, upstream, latest: d.guardVersion() };
11713
11835
  },
11714
11836
  /** 完整版本检查(async):本地 commit + 远端 fetch 比对。
@@ -11716,20 +11838,17 @@ var require_versions = __commonJS({
11716
11838
  * fetch 失败/超时只降级为「本地视图」,不抛错。 */
11717
11839
  async guardVersionCheck() {
11718
11840
  const d = depsOf(this);
11719
- const base = d.guardVersionLocal();
11841
+ const base = await d.guardVersionLocal();
11720
11842
  if (base.upstream !== "git-repo") return base;
11721
11843
  const root = d.vcsRoot();
11722
11844
  const fetchOk = await ex2.runOutAsync("git", ["-C", root, "fetch", "--quiet"], { timeoutMs: 1e4 }) !== null;
11723
11845
  if (!fetchOk) return base;
11724
11846
  let updateAvailable = false;
11725
- try {
11726
- const ahead = (ex2.runOut("git", ["-C", root, "rev-list", "--count", "HEAD..@{u}"]) || "").trim();
11727
- updateAvailable = parseInt(ahead, 10) > 0;
11728
- } catch {
11729
- }
11847
+ const ahead = (await ex2.runOutAsync("git", ["-C", root, "rev-list", "--count", "HEAD..@{u}"], { timeoutMs: 1e4 }) || "").trim();
11848
+ updateAvailable = parseInt(ahead, 10) > 0;
11730
11849
  const dep = deploy.detect();
11731
11850
  let diskVersion = null;
11732
- if (dep.updatable) diskVersion = d.readBinarySelfVersion();
11851
+ if (dep.updatable) diskVersion = await d.readBinarySelfVersion();
11733
11852
  const updatePending = !!(diskVersion && diskVersion !== d.guardVersion());
11734
11853
  return { ...base, diskVersion, updatePending };
11735
11854
  }
@@ -11813,7 +11932,9 @@ var require_access = __commonJS({
11813
11932
  return { ok: false, error: e.message };
11814
11933
  }
11815
11934
  }
11816
- }
11935
+ },
11936
+ // settings 门面的写口核验件(B2-4):lan-panel 共用同一「写后读回」口径,不各写各的。
11937
+ verifyPersisted
11817
11938
  };
11818
11939
  }
11819
11940
  });
@@ -11920,9 +12041,8 @@ var require_netinfo = __commonJS({
11920
12041
  var require_lan_panel = __commonJS({
11921
12042
  "src/app/settings/lan-panel.js"(exports2, module2) {
11922
12043
  "use strict";
11923
- var fs2 = require("node:fs");
11924
12044
  var netInfo = require_netinfo();
11925
- var { writeAtomic } = require_fs();
12045
+ var { verifyPersisted } = require_access();
11926
12046
  var DEPS = /* @__PURE__ */ new WeakMap();
11927
12047
  function depsOf(host2) {
11928
12048
  let d = DEPS.get(host2);
@@ -11932,6 +12052,7 @@ var require_lan_panel = __commonJS({
11932
12052
  logger: () => host2.logger,
11933
12053
  events: () => host2.events,
11934
12054
  configPath: () => host2.configPath,
12055
+ state: () => host2.state,
11935
12056
  api: () => host2.api,
11936
12057
  lanPanelStatus: () => host2.lanPanelStatus(),
11937
12058
  apiRebind: () => host2._apiRebind()
@@ -11973,14 +12094,9 @@ var require_lan_panel = __commonJS({
11973
12094
  d.config().apiHost = host2;
11974
12095
  let persistError = null;
11975
12096
  if (d.configPath()) {
11976
- try {
11977
- const doc = JSON.parse(fs2.readFileSync(d.configPath(), "utf8"));
11978
- doc.apiHost = host2;
11979
- writeAtomic(d.configPath(), JSON.stringify(doc, null, 2), { mode: 384 });
11980
- } catch (e) {
11981
- persistError = "persist apiHost: " + e.message;
11982
- d.logger().error(persistError);
11983
- }
12097
+ d.state().persistConfigPatch({ apiHost: host2 });
12098
+ persistError = verifyPersisted(d.configPath(), { apiHost: host2 });
12099
+ if (persistError) d.logger().error(persistError);
11984
12100
  }
11985
12101
  if (changed && d.api() && typeof d.api().close === "function") d.apiRebind();
11986
12102
  if (d.events()) d.events().append("lan_panel_changed", { enabled: on });
@@ -12598,6 +12714,8 @@ var require_pool2 = __commonJS({
12598
12714
  this._bus = new FollowBus({ logger: this.logger });
12599
12715
  this._schedules = /* @__PURE__ */ new Map();
12600
12716
  this._seq = 0;
12717
+ this._attachGen = /* @__PURE__ */ new Map();
12718
+ this._journalFn = o.journal || capture.captureJournal;
12601
12719
  this._backfillAt = /* @__PURE__ */ new Map();
12602
12720
  this._poolFile = o.poolFile ? path2.resolve(o.poolFile) : null;
12603
12721
  this._loaded = false;
@@ -12622,6 +12740,7 @@ var require_pool2 = __commonJS({
12622
12740
  const unit = s.unit || prev && prev.unit || null;
12623
12741
  const file = s.file || prev && prev.file || null;
12624
12742
  this._sources.set(id, { kind, unit, file, lines: prev && prev.lines || [] });
12743
+ this._attachGen.set(id, (this._attachGen.get(id) || 0) + 1);
12625
12744
  const rec = this._records.get(id);
12626
12745
  if (rec) rec.kind = kind;
12627
12746
  return true;
@@ -12646,9 +12765,16 @@ var require_pool2 = __commonJS({
12646
12765
  return this._commit(id, hit.token, hit.source);
12647
12766
  }
12648
12767
  if (src.unit && kinds.isCaptured(src.kind)) {
12649
- Promise.resolve().then(() => capture.captureJournal(src.unit, { logger: this.logger })).then((j) => {
12768
+ const gen = this._attachGen.get(id) || 0;
12769
+ const fresh = () => (this._attachGen.get(id) || 0) === gen ? this._sources.get(id) : null;
12770
+ Promise.resolve().then(() => {
12771
+ const cur = fresh();
12772
+ return cur ? this._journalFn(cur.unit, { logger: this.logger }) : null;
12773
+ }).then((j) => {
12650
12774
  if (!j) return;
12651
- if (src.file) this._persistLine(id, src.file, j.line);
12775
+ const cur = fresh();
12776
+ if (!cur) return;
12777
+ if (cur.file) this._persistLine(id, cur.file, j.line);
12652
12778
  this._commit(id, j.token, j.source);
12653
12779
  }).catch(() => {
12654
12780
  });
@@ -12716,6 +12842,7 @@ var require_pool2 = __commonJS({
12716
12842
  this._backfillAt.delete(id);
12717
12843
  const src = this._sources.get(id);
12718
12844
  if (src && src.lines && src.lines.length) src.lines.length = 0;
12845
+ this._attachGen.set(id, (this._attachGen.get(id) || 0) + 1);
12719
12846
  this._records.delete(id);
12720
12847
  this._persistPool();
12721
12848
  this._bus.emit(id, null, null);
@@ -13646,7 +13773,6 @@ var require_core5 = __commonJS({
13646
13773
  host2._routerFacade = null;
13647
13774
  host2._lc = null;
13648
13775
  host2._dshMainLive = null;
13649
- host2._fallbackEntry = null;
13650
13776
  host2._lastStateBody = null;
13651
13777
  host2._shadowSeq = 0;
13652
13778
  host2._shadowConsistentBeats = 0;
@@ -17193,10 +17319,11 @@ var require_oauth = __commonJS({
17193
17319
  const d = deps || {};
17194
17320
  const ports = d.ports;
17195
17321
  const openInBrowser = d.openInBrowser;
17196
- const st = { _ccLogin: null, _ccLoginPromise: null, _ccLoginResolve: null, _ccLoginReject: null };
17322
+ const st = { _ccLogin: null, _ccLoginPromise: null, _ccLoginResolve: null, _ccLoginReject: null, _ccLoginRound: 0 };
17197
17323
  async function commandcodeLoginStart() {
17198
17324
  const STUDIO_BASE = "https://commandcode.ai";
17199
17325
  const state = crypto.randomBytes(32).toString("base64url");
17326
+ const roundId = ++st._ccLoginRound;
17200
17327
  if (st._ccLogin && st._ccLogin.server) {
17201
17328
  const oldState = st._ccLogin.state;
17202
17329
  try {
@@ -17258,6 +17385,11 @@ var require_oauth = __commonJS({
17258
17385
  if (b.length > 1e4) req.destroy();
17259
17386
  });
17260
17387
  req.on("end", () => {
17388
+ if (st._ccLoginRound !== roundId) {
17389
+ res.writeHead(410);
17390
+ res.end(callbackJson({ success: false, error: "Stale login round" }));
17391
+ return;
17392
+ }
17261
17393
  try {
17262
17394
  const j = JSON.parse(b || "{}");
17263
17395
  if (j && typeof j === "object" && "error" in j) {
@@ -17348,6 +17480,7 @@ var require_oauth = __commonJS({
17348
17480
  });
17349
17481
  st._ccLoginPromise = promise;
17350
17482
  const tmpProfile = openInBrowser(authUrl, () => {
17483
+ if (st._ccLoginRound !== roundId) return;
17351
17484
  if (st._ccLoginReject) {
17352
17485
  const r = st._ccLoginReject;
17353
17486
  st._ccLoginReject = null;
@@ -19234,10 +19367,7 @@ var require_model3 = __commonJS({
19234
19367
  inst.state.lastError = null;
19235
19368
  }
19236
19369
  if (inst.state) inst.state.phase = inst.state.phase || "STOPPED";
19237
- if (inst.state && inst.state.desired === void 0) {
19238
- const p = inst.state.phase;
19239
- inst.state.desired = p === "RUNNING" || p === "STARTING" || p === "INSTALLING" ? "running" : "stopped";
19240
- }
19370
+ if (inst.state) delete inst.state.desired;
19241
19371
  return inst;
19242
19372
  }
19243
19373
  function createRecord(payload, id) {
@@ -19262,7 +19392,7 @@ var require_model3 = __commonJS({
19262
19392
  protectHome: payload.protectHome === void 0 ? false : !!payload.protectHome
19263
19393
  // 资源配额不接收户输入:启动时由 governor 按机器预算与活跃实例数推导。
19264
19394
  },
19265
- state: { phase: "STOPPED", desired: "stopped", restartCount: 0, backoffLevel: 0, lastProbeOk: null },
19395
+ state: { phase: "STOPPED", restartCount: 0, backoffLevel: 0, lastProbeOk: null },
19266
19396
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
19267
19397
  };
19268
19398
  }
@@ -19502,6 +19632,7 @@ var require_lifecycle = __commonJS({
19502
19632
  "use strict";
19503
19633
  var fs2 = require("node:fs");
19504
19634
  var monitor = require_monitor();
19635
+ var ports = require_ports().shared;
19505
19636
  var guardian = require_guardian();
19506
19637
  var sandbox = require_sandbox();
19507
19638
  var governor = require_governor();
@@ -19544,7 +19675,7 @@ var require_lifecycle = __commonJS({
19544
19675
  logger.info && logger.info("cleaned stale transient unit: " + unit);
19545
19676
  }
19546
19677
  }
19547
- function _systemdStart(inst) {
19678
+ function _systemdStart(inst, opts) {
19548
19679
  try {
19549
19680
  const cmdArr = sandbox.effectiveCommand(instancesRoot, deps.dshBin, inst);
19550
19681
  if (!cmdArr || !cmdArr.length) return { ok: false, error: "\u5B9E\u4F8B\u672A\u914D\u7F6E\u542F\u52A8\u547D\u4EE4" };
@@ -19560,6 +19691,15 @@ var require_lifecycle = __commonJS({
19560
19691
  logger.warn && logger.warn("[" + inst.id + "] " + msg);
19561
19692
  return { ok: false, error: msg };
19562
19693
  }
19694
+ const takenBy = ports.recordOf(inst.port);
19695
+ if (takenBy && takenBy.owner !== "inst:" + inst.id) {
19696
+ const msg = "PORT_TAKEN:" + (takenBy.owner || takenBy.role);
19697
+ inst.state.lastError = msg;
19698
+ store.save();
19699
+ if (events) events.append("inst_start_refused", { id: inst.id, name: inst.name, error: msg });
19700
+ logger.warn && logger.warn("[" + inst.id + "] " + msg);
19701
+ return { ok: false, error: msg };
19702
+ }
19563
19703
  if (probe(inst).running) return { ok: false, error: "\u7AEF\u53E3 " + inst.port + " \u5DF2\u88AB\u5360\u7528" };
19564
19704
  const alloc = governor.currentAllocation(store.instances, inst.id, machineFactsNow());
19565
19705
  inst.state.allocation = alloc;
@@ -19579,6 +19719,12 @@ var require_lifecycle = __commonJS({
19579
19719
  inst.state.phase = "STARTING";
19580
19720
  inst.state.startAt = Date.now();
19581
19721
  inst.state.lastError = null;
19722
+ if (opts && opts.manual) {
19723
+ inst.state.restartCount = 0;
19724
+ inst.state.backoffLevel = 0;
19725
+ inst.state.backoffUntil = null;
19726
+ inst.state.lastFailAt = null;
19727
+ }
19582
19728
  store.save();
19583
19729
  if (inst.port && hooks.onInstanceStart) hooks.onInstanceStart(inst);
19584
19730
  if (events) events.append("inst_started", { id: inst.id, port: inst.port });
@@ -19604,10 +19750,6 @@ var require_lifecycle = __commonJS({
19604
19750
  return { ok: false, error: adm.error };
19605
19751
  }
19606
19752
  }
19607
- if (inst.state.desired !== "running") {
19608
- inst.state.desired = "running";
19609
- store.save();
19610
- }
19611
19753
  store.ensureDirs(inst);
19612
19754
  const dshEntry = sandbox.dshEntry(instancesRoot, inst);
19613
19755
  if (!fs2.existsSync(dshEntry)) {
@@ -19616,10 +19758,9 @@ var require_lifecycle = __commonJS({
19616
19758
  return { ok: true, installing: true };
19617
19759
  }
19618
19760
  }
19619
- return _systemdStart(inst);
19761
+ return _systemdStart(inst, opts);
19620
19762
  }
19621
- function stop(id, opts) {
19622
- const transient = !!(opts && opts.intent === "transient");
19763
+ function stop(id) {
19623
19764
  const inst = store.instances.find((i) => i.id === id);
19624
19765
  if (!inst) return { ok: false, error: "\u5B9E\u4F8B\u4E0D\u5B58\u5728" };
19625
19766
  if (!isSandboxSupported()) return { ok: false, error: "\u5F53\u524D\u5E73\u53F0\u4E0D\u652F\u6301\u6C99\u7BB1\u5B9E\u4F8B\uFF08\u80FD\u529B\u77E9\u9635\u89C1 GET /env/status \u7684 capabilities.sandboxLaunch\uFF1B\u9650\u989D\u6267\u884C\u6863\u4F4D\u89C1 capabilities.sandboxEnforcement\uFF09" };
@@ -19641,7 +19782,6 @@ var require_lifecycle = __commonJS({
19641
19782
  }
19642
19783
  inst.state.phase = "STOPPED";
19643
19784
  inst.state.usage = null;
19644
- if (!transient) inst.state.desired = "stopped";
19645
19785
  runtime.delete(inst.id);
19646
19786
  store.save();
19647
19787
  if (inst.port && hooks.onInstanceStop) hooks.onInstanceStop(inst);
@@ -19697,15 +19837,17 @@ var require_lifecycle = __commonJS({
19697
19837
  }).catch(() => {
19698
19838
  });
19699
19839
  }
19840
+ }
19841
+ function governSweep() {
19700
19842
  const roster = _sandboxRoster();
19701
- if (!roster.length) return;
19843
+ if (!roster.length) return { ok: true, entries: 0 };
19702
19844
  let plan;
19703
19845
  try {
19704
19846
  const f = machineFactsNow();
19705
19847
  plan = governor.decide({ totalMemBytes: f.totalMemBytes, cpuCount: f.cpuCount, roster });
19706
19848
  } catch (e) {
19707
- logger.warn && logger.warn("[" + inst.id + "] govern decide \u5931\u8D25: " + (e && e.message));
19708
- return;
19849
+ logger.warn && logger.warn("govern decide \u5931\u8D25: " + (e && e.message));
19850
+ return { ok: false, error: e && e.message || String(e) };
19709
19851
  }
19710
19852
  const now = Date.now();
19711
19853
  for (const entry of plan.entries) {
@@ -19730,19 +19872,21 @@ var require_lifecycle = __commonJS({
19730
19872
  }
19731
19873
  }
19732
19874
  }
19733
- if (!entry.violation || entry.id !== inst.id) continue;
19875
+ if (!entry.violation) continue;
19734
19876
  const v = entry.violation;
19735
19877
  const kindLabel = v.kind === "memory" ? "\u5185\u5B58" : "CPU";
19736
19878
  const reason = "\u8D44\u6E90\u8FDD\u89C4:" + kindLabel + "\u6301\u7EED\u8D85\u9650(\u5B9E\u9645 " + v.actual + "/\u9650\u989D " + v.target + ")";
19737
- if (events) events.append("inst_resource_violation", { id: inst.id, name: inst.name, kind: v.kind, actual: v.actual, target: v.target });
19738
- logger.warn && logger.warn("[" + inst.id + "] " + reason);
19879
+ if (events) events.append("inst_resource_violation", { id: target.id, name: target.name, kind: v.kind, actual: v.actual, target: v.target });
19880
+ logger.warn && logger.warn("[" + target.id + "] " + reason);
19739
19881
  try {
19740
- service.stopUnit("dsh-web@" + inst.id, Object.assign({ timeoutMs: 2e4 }, sandbox.launchCtx(instancesRoot, deps.dshBin, inst)));
19882
+ service.stopUnit("dsh-web@" + target.id, Object.assign({ timeoutMs: 2e4 }, sandbox.launchCtx(instancesRoot, deps.dshBin, target)));
19741
19883
  } catch (e) {
19742
- logger.warn && logger.warn("[" + inst.id + "] \u8FDD\u89C4\u505C\u5355\u5143\u5F02\u5E38: " + (e && e.message));
19884
+ logger.warn && logger.warn("[" + target.id + "] \u8FDD\u89C4\u505C\u5355\u5143\u5F02\u5E38: " + (e && e.message));
19743
19885
  }
19744
- stateMachine.restart(stateDeps(), inst, reason);
19886
+ stateMachine.restart(stateDeps(), target, reason);
19745
19887
  }
19888
+ store.save();
19889
+ return { ok: true, entries: plan.entries.length };
19746
19890
  }
19747
19891
  function supervise(id) {
19748
19892
  const inst = store.instances.find((i) => i.id === id);
@@ -19836,7 +19980,7 @@ var require_lifecycle = __commonJS({
19836
19980
  }
19837
19981
  return { ok: true };
19838
19982
  }
19839
- return { _prepareSystemd, start, stop, probe, probeInstance, supervise };
19983
+ return { _prepareSystemd, start, stop, probe, probeInstance, supervise, governSweep };
19840
19984
  }
19841
19985
  module2.exports = { createLifecycle };
19842
19986
  }
@@ -20145,7 +20289,7 @@ var require_upgrade = __commonJS({
20145
20289
  tasks.stepState(task.id, tasks.get(task.id).steps.indexOf(s), "running");
20146
20290
  }
20147
20291
  try {
20148
- await lifecycle.stop(id, { intent: "transient" });
20292
+ await lifecycle.stop(id);
20149
20293
  } catch {
20150
20294
  }
20151
20295
  if (task) {
@@ -20193,7 +20337,7 @@ var require_upgrade = __commonJS({
20193
20337
  tasks.log(task.id, "\u81EA\u52A8\u56DE\u6EDA\u5230 " + oldVersion + "\u2026");
20194
20338
  }
20195
20339
  try {
20196
- const rs = await lifecycle.stop(id, { intent: "transient" });
20340
+ const rs = await lifecycle.stop(id);
20197
20341
  if (rs && rs.ok === false && task) tasks.log(task.id, "\u56DE\u6EDA\u524D\u505C\u6B62\u5931\u8D25\uFF08\u7EE7\u7EED\u56DE\u88C5\u65E7\u7248\uFF09\uFF1A" + (rs.error || ""));
20198
20342
  } catch (e) {
20199
20343
  if (task) tasks.log(task.id, "\u56DE\u6EDA\u524D\u505C\u6B62\u5F02\u5E38\uFF08\u7EE7\u7EED\u56DE\u88C5\u65E7\u7248\uFF09\uFF1A" + (e && e.message || e));
@@ -20480,6 +20624,11 @@ var require_ops2 = __commonJS({
20480
20624
  } catch {
20481
20625
  }
20482
20626
  }
20627
+ try {
20628
+ lifecycle.governSweep();
20629
+ } catch (e) {
20630
+ logger.warn && logger.warn("governSweep: " + (e && e.message));
20631
+ }
20483
20632
  }, intervalMs || 5e3);
20484
20633
  }
20485
20634
  return { list, addInstance, removeInstance, updateInstance, startTimer };
@@ -20772,12 +20921,16 @@ var require_instance = __commonJS({
20772
20921
  startInstance(id, opts) {
20773
20922
  return this._lifecycle.start(id, opts);
20774
20923
  }
20775
- stopInstance(id, opts) {
20776
- return this._lifecycle.stop(id, opts);
20924
+ stopInstance(id) {
20925
+ return this._lifecycle.stop(id);
20777
20926
  }
20778
20927
  supervise(id) {
20779
20928
  return this._lifecycle.supervise(id);
20780
20929
  }
20930
+ /** 治理单拍(B2-6e):心跳拍末由 onBeatDone 调一次,全花名册 decide+下发+违规处置。 */
20931
+ governSweep() {
20932
+ return this._lifecycle.governSweep();
20933
+ }
20781
20934
  probeInstance(id) {
20782
20935
  return this._lifecycle.probeInstance(id);
20783
20936
  }
@@ -21099,8 +21252,6 @@ var require_market = __commonJS({
21099
21252
  this._ts = 0;
21100
21253
  this._inFlight = null;
21101
21254
  this.buildBudgetMs = opts.buildBudgetMs || 24e4;
21102
- this._deadline = 0;
21103
- this._truncatedSources = /* @__PURE__ */ new Set();
21104
21255
  this.loadFromDisk();
21105
21256
  }
21106
21257
  loadFromDisk() {
@@ -21147,19 +21298,18 @@ var require_market = __commonJS({
21147
21298
  }
21148
21299
  async buildIndex() {
21149
21300
  const start = Date.now();
21150
- this._deadline = Date.now() + this.buildBudgetMs;
21151
- this._truncatedSources = /* @__PURE__ */ new Set();
21301
+ const bctx = { deadline: start + this.buildBudgetMs, truncated: /* @__PURE__ */ new Set() };
21152
21302
  try {
21153
- return await this._buildIndexInner(start);
21303
+ return await this._buildIndexInner(start, bctx);
21154
21304
  } finally {
21155
- this._deadline = 0;
21305
+ bctx.deadline = 0;
21156
21306
  }
21157
21307
  }
21158
- /** 预算是否已耗尽(供各源的批次循环调用)。 */
21159
- _budgetExhausted() {
21160
- return this._deadline > 0 && Date.now() >= this._deadline;
21308
+ /** 预算是否已耗尽(供各源的批次循环调用;无 ctx = 单源直调,不设预算)。 */
21309
+ _budgetExhausted(bctx) {
21310
+ return !!bctx && bctx.deadline > 0 && Date.now() >= bctx.deadline;
21161
21311
  }
21162
- async _buildIndexInner(start) {
21312
+ async _buildIndexInner(start, bctx) {
21163
21313
  const plugins = [];
21164
21314
  const seen = /* @__PURE__ */ new Set();
21165
21315
  const add = (p) => {
@@ -21167,11 +21317,11 @@ var require_market = __commonJS({
21167
21317
  seen.add(p.name);
21168
21318
  plugins.push(p);
21169
21319
  };
21170
- const npm = await this.indexNpm();
21320
+ const npm = await this.indexNpm(bctx);
21171
21321
  npm.forEach(add);
21172
- const gh = await this.indexGithub();
21322
+ const gh = await this.indexGithub(bctx);
21173
21323
  gh.forEach(add);
21174
- const community = await this.indexCommunity();
21324
+ const community = await this.indexCommunity(bctx);
21175
21325
  community.forEach(add);
21176
21326
  for (const p of plugins) {
21177
21327
  p.category = p.category || classify(p);
@@ -21179,7 +21329,7 @@ var require_market = __commonJS({
21179
21329
  }
21180
21330
  plugins.sort((a, b) => (b.stars || 0) - (a.stars || 0));
21181
21331
  const prev = this._cache;
21182
- const truncated = this._truncatedSources || /* @__PURE__ */ new Set();
21332
+ const truncated = bctx.truncated;
21183
21333
  if (prev && prev.plugins && prev.plugins.length > 0 && truncated.size > 0) {
21184
21334
  const freshNames = new Set(plugins.map((pp) => pp.name));
21185
21335
  const kept = prev.plugins.filter((pp) => truncated.has(pp.source) && !freshNames.has(pp.name));
@@ -21214,8 +21364,8 @@ var require_market = __commonJS({
21214
21364
  this.logger.info && this.logger.info("market index built in " + (Date.now() - start) + "ms: " + plugins.length + " plugins (npm=" + npm.length + ", gh=" + gh.length + ", community=" + community.length + ")");
21215
21365
  return this._cache;
21216
21366
  }
21217
- /** npm 源:搜 deepseek-harness 受限 dsh,逐个检测 dsh.bundle。 */
21218
- async indexNpm() {
21367
+ /** npm 源:搜 deepseek-harness 受限 dsh,逐个检测 dsh.bundle。bctx 为本次构建的预算上下文(见 buildIndex)。 */
21368
+ async indexNpm(bctx) {
21219
21369
  const out = [];
21220
21370
  const queries = ["keywords:deepseek-harness", "keywords:dsh-bundle", "keywords:dsh-plugin"];
21221
21371
  const allNames = /* @__PURE__ */ new Set();
@@ -21242,8 +21392,8 @@ var require_market = __commonJS({
21242
21392
  this.logger.info && this.logger.info("npm candidates: " + names.length);
21243
21393
  const batch = 8;
21244
21394
  for (let i = 0; i < names.length; i += batch) {
21245
- if (this._budgetExhausted()) {
21246
- this._truncatedSources.add("npm");
21395
+ if (this._budgetExhausted(bctx)) {
21396
+ bctx.truncated.add("npm");
21247
21397
  this.logger.warn && this.logger.warn("market: npm \u6E90\u9884\u7B97\u8017\u5C3D\uFF0C\u5DF2\u5904\u7406 " + i + "/" + names.length + " \u4E2A\u5019\u9009");
21248
21398
  break;
21249
21399
  }
@@ -21273,7 +21423,7 @@ var require_market = __commonJS({
21273
21423
  return fetchLatest(await this._npmOrigin(), name);
21274
21424
  }
21275
21425
  /** GitHub 源:搜 topic:dsh-plugin + deepseek-harness,逐个验证 dsh.bundle。 */
21276
- async indexGithub() {
21426
+ async indexGithub(bctx) {
21277
21427
  const out = [];
21278
21428
  const topics = ["dsh-plugin", "deepseek-harness"];
21279
21429
  const seen = /* @__PURE__ */ new Set();
@@ -21303,7 +21453,7 @@ var require_market = __commonJS({
21303
21453
  return repoPkg(fullName);
21304
21454
  }
21305
21455
  /** 社区列表:抓 awesome-dsh-plugin README 白名单(官方社区维护的精选)。 */
21306
- async indexCommunity() {
21456
+ async indexCommunity(bctx) {
21307
21457
  const out = [];
21308
21458
  try {
21309
21459
  const md = await rawGet("awesome-dsh-plugin/awesome-dsh-plugin/main/README.md", false, 3e4);
@@ -21315,8 +21465,8 @@ var require_market = __commonJS({
21315
21465
  }
21316
21466
  const seenName = /* @__PURE__ */ new Set();
21317
21467
  for (let i = 0; i < links.length; i += 8) {
21318
- if (this._budgetExhausted()) {
21319
- this._truncatedSources.add("community");
21468
+ if (this._budgetExhausted(bctx)) {
21469
+ bctx.truncated.add("community");
21320
21470
  this.logger.warn && this.logger.warn("market: community \u6E90\u9884\u7B97\u8017\u5C3D\uFF0C\u5DF2\u5904\u7406 " + i + "/" + links.length + " \u4E2A\u5019\u9009");
21321
21471
  break;
21322
21472
  }
@@ -21838,7 +21988,7 @@ var require_restart3 = __commonJS({
21838
21988
  log("\u91CD\u542F\u5B9E\u4F8B\u300C" + (target.name || target.id) + "\u300D\u4F7F\u63D2\u4EF6\u53D8\u66F4\u751F\u6548\u2026");
21839
21989
  if (ctx.events) ctx.events.append("plugin_restart_started", { name: target.name || target.id, target: target.id, kind });
21840
21990
  try {
21841
- ctx.instances.stopInstance(target.id, { intent: "transient" });
21991
+ ctx.instances.stopInstance(target.id);
21842
21992
  } catch (e) {
21843
21993
  log("\u505C\u6B62\u5B9E\u4F8B\u5931\u8D25: " + e.message);
21844
21994
  }
@@ -22549,10 +22699,6 @@ var require_managed_object = __commonJS({
22549
22699
  function registerKind(kind, meta) {
22550
22700
  _customKinds[kind] = Object.assign({ label: kind, startable: false, guardable: false }, meta || {});
22551
22701
  }
22552
- var DOMAIN_A_KINDS = /* @__PURE__ */ new Set(["dsh", "sandbox-instance"]);
22553
- function isDomainA(kind) {
22554
- return DOMAIN_A_KINDS.has(kind);
22555
- }
22556
22702
  function createEntry(o) {
22557
22703
  const meta = kindMeta(o.kind);
22558
22704
  if (!meta) throw new Error("\u672A\u77E5\u53D7\u7BA1\u5BF9\u8C61\u7C7B\u578B: " + o.kind + "\uFF08\u5148 registerKind \u58F0\u660E\uFF09");
@@ -22563,8 +22709,9 @@ var require_managed_object = __commonJS({
22563
22709
  name: String(o.name || o.id),
22564
22710
  // desired 两域共用字段名但语义不同:域 A=用户意图;域 B=「当前业务是否需要它」的条件
22565
22711
  desired: o.desired === "stopped" ? "stopped" : "running",
22566
- // guardian 域 A 专有(见上);域 B 不物化,旧残留才清得掉
22567
- ...isDomainA(o.kind) ? { guardian: o.guardian === true } : {},
22712
+ // guardian 开关的权威在域记录本身(dsh-main.json / inst.guardian),消费者全部直读源;
22713
+ // 目录曾在域 A entry 上物化该字段但零读者(B2-2 收口)。createEntry 永不物化 guardian 键
22714
+ // = 老库残留的天然一次性清理口(load 经本函数重建即消失),无需迁移脚本。
22568
22715
  ownership: normalizeOwnership(o.ownership),
22569
22716
  // 初始 stopped;业务不得直接改,由 heartbeat 调谐循环写入
22570
22717
  phase: "stopped",
@@ -22599,7 +22746,7 @@ var require_managed_object = __commonJS({
22599
22746
  // 域备注(只读参考)
22600
22747
  };
22601
22748
  }
22602
- module2.exports = { DESIRED, MANAGED_KINDS, kindMeta, registerKind, isDomainA, createEntry, normalizeOwnership };
22749
+ module2.exports = { DESIRED, MANAGED_KINDS, kindMeta, registerKind, createEntry, normalizeOwnership };
22603
22750
  }
22604
22751
  });
22605
22752
 
@@ -22666,6 +22813,13 @@ var require_heartbeat = __commonJS({
22666
22813
  registry._log("warn", "heartbeat " + (ad.supervise ? "supervise" : "observe") + "(" + e.kind + ":" + e.id + "): " + (err && err.message || err));
22667
22814
  }
22668
22815
  }
22816
+ if (typeof registry.onBeatDone === "function") {
22817
+ try {
22818
+ await registry.onBeatDone({ observed, errors });
22819
+ } catch (err) {
22820
+ registry._log("warn", "heartbeat onBeatDone: " + (err && err.message || err));
22821
+ }
22822
+ }
22669
22823
  return { observed, errors };
22670
22824
  }
22671
22825
  module2.exports = { runHeartbeat, withTimeout, ADAPTER_TIMEOUT_TICKS };
@@ -22679,7 +22833,7 @@ var require_registry3 = __commonJS({
22679
22833
  var fs2 = require("node:fs");
22680
22834
  var path2 = require("node:path");
22681
22835
  var { writeAtomic } = require_fs();
22682
- var { DESIRED, MANAGED_KINDS, kindMeta, registerKind: registerManagedKind, isDomainA, createEntry, normalizeOwnership } = require_managed_object();
22836
+ var { DESIRED, MANAGED_KINDS, kindMeta, registerKind: registerManagedKind, createEntry, normalizeOwnership } = require_managed_object();
22683
22837
  var PHASES = ["stopped", "installing", "starting", "running", "draining", "backoff", "failed", "restarting"];
22684
22838
  var { runHeartbeat } = require_heartbeat();
22685
22839
  var ManagedRegistry = class {
@@ -22730,7 +22884,7 @@ var require_registry3 = __commonJS({
22730
22884
  for (const o of arr) {
22731
22885
  try {
22732
22886
  if (!o || !kindMeta(o.kind)) continue;
22733
- const e = createEntry({ kind: o.kind, id: o.id, name: o.name, desired: o.desired, guardian: isDomainA(o.kind) ? o.guardian : void 0, ownership: o.ownership });
22887
+ const e = createEntry({ kind: o.kind, id: o.id, name: o.name, desired: o.desired, ownership: o.ownership });
22734
22888
  if (PHASES.includes(o.phase)) e.phase = o.phase;
22735
22889
  if (Number.isInteger(o.backoffLevel)) e.backoffLevel = o.backoffLevel;
22736
22890
  if (typeof o.backoffUntil === "number" && o.backoffUntil > Date.now()) e.backoffUntil = o.backoffUntil;
@@ -22757,7 +22911,7 @@ var require_registry3 = __commonJS({
22757
22911
  kind: o.kind,
22758
22912
  id: o.id,
22759
22913
  name: o.name,
22760
- // desired 两域共用(语义不同,见 createEntry);guardian 仅域 A 落盘。
22914
+ // desired 两域共用(语义不同,见 createEntry);guardian 不落盘(B2-2,权威在域记录)。
22761
22915
  desired: o.desired,
22762
22916
  ownership: o.ownership,
22763
22917
  phase: o.phase,
@@ -22769,7 +22923,7 @@ var require_registry3 = __commonJS({
22769
22923
  startedAt: o.startedAt,
22770
22924
  createdAt: o.createdAt,
22771
22925
  updatedAt: o.updatedAt
22772
- }, isDomainA(o.kind) ? { guardian: o.guardian === true } : {}))
22926
+ }))
22773
22927
  }, null, 2);
22774
22928
  writeAtomic(this.file, body, { mode: 384 });
22775
22929
  } catch (e) {
@@ -22831,8 +22985,8 @@ var require_registry3 = __commonJS({
22831
22985
  this._event("managed_object_registered", { kind: e.kind, id: e.id, name: e.name });
22832
22986
  return e;
22833
22987
  }
22834
- /** 对象变更申报(desired/guardian/ownership/name)。guardian 是域 A 专有:
22835
- * 域 B 基础设施既不写入,还主动删键清除早期版本残留(下一次 update 即归一)。 */
22988
+ /** 对象变更申报(desired/ownership/name)。guardian 不接受申报(B2-2):createEntry 永不
22989
+ * 物化该键,patch 里带 guardian 一律忽略,老库残留由 load 重建时清理。 */
22836
22990
  update(id, patch) {
22837
22991
  const e = this.get(id);
22838
22992
  if (!e) return { ok: false, error: "\u672A\u6CE8\u518C: " + id };
@@ -22841,11 +22995,6 @@ var require_registry3 = __commonJS({
22841
22995
  if (!DESIRED.includes(p.desired)) return { ok: false, error: "\u975E\u6CD5 desired: " + p.desired };
22842
22996
  e.desired = p.desired;
22843
22997
  }
22844
- if (isDomainA(e.kind)) {
22845
- if (p.guardian !== void 0) e.guardian = p.guardian === true;
22846
- } else if ("guardian" in e) {
22847
- delete e.guardian;
22848
- }
22849
22998
  if (p.name !== void 0) e.name = String(p.name || e.id);
22850
22999
  if (p.ownership !== void 0) {
22851
23000
  const old = e.ownership.ports;
@@ -23263,20 +23412,23 @@ var require_npm = __commonJS({
23263
23412
  const l = runtimeContract.npmLauncher();
23264
23413
  return { program: l.program, args: l.args };
23265
23414
  }
23266
- function resolveNpmRoot(host2) {
23415
+ async function resolveNpmRoot(host2) {
23267
23416
  if (host2.npmRoot) return host2.npmRoot;
23268
23417
  const l = npmLaunch(host2);
23269
- const r = ex2.runOut(l.program, l.args.concat(["root", "-g"]));
23418
+ const r = await ex2.runOutAsync(l.program, l.args.concat(["root", "-g"]));
23270
23419
  return r ? r.trim() : null;
23271
23420
  }
23272
- function checkEnvironment(host2) {
23421
+ async function checkEnvironment(host2) {
23273
23422
  const errors = [];
23274
- const nv = ex2.runOut("node", ["--version"]);
23275
- if (!nv || !nv.trim()) errors.push("node \u672A\u5B89\u88C5\u6216\u4E0D\u53EF\u6267\u884C");
23276
23423
  const l = npmLaunch(host2);
23277
- const npmv = ex2.runOut(l.program, l.args.concat(["--version"]));
23424
+ const [nv, npmv, npmRoot] = await Promise.all([
23425
+ ex2.runOutAsync("node", ["--version"]),
23426
+ ex2.runOutAsync(l.program, l.args.concat(["--version"])),
23427
+ resolveNpmRoot(host2)
23428
+ ]);
23429
+ if (!nv || !nv.trim()) errors.push("node \u672A\u5B89\u88C5\u6216\u4E0D\u53EF\u6267\u884C");
23278
23430
  if (!npmv || !npmv.trim()) errors.push("npm \u672A\u5B89\u88C5\u6216\u4E0D\u53EF\u6267\u884C");
23279
- return { ok: errors.length === 0, errors, npmRoot: resolveNpmRoot(host2) };
23431
+ return { ok: errors.length === 0, errors, npmRoot };
23280
23432
  }
23281
23433
  async function latestVersion(host2) {
23282
23434
  if (!host2.dist || !host2.config.packageName) throw new Error("\u5206\u53D1\u670D\u52A1\u672A\u521D\u59CB\u5316\uFF0C\u65E0\u6CD5\u67E5\u8BE2\u6700\u65B0\u7248\u672C");
@@ -23375,13 +23527,14 @@ var require_ops4 = __commonJS({
23375
23527
  }
23376
23528
  async function install(host2, version) {
23377
23529
  if (!policies.isValidVersion(version)) return { ok: false, error: "\u975E\u6CD5\u7248\u672C\u53F7: " + version };
23378
- const env = host2.checkEnvironment();
23379
- if (!env.ok) return { ok: false, error: "\u73AF\u5883\u68C0\u67E5\u5931\u8D25: " + env.errors.join("; ") };
23380
23530
  host2.installing = true;
23381
23531
  host2.installLog = [];
23382
- const task = beginTask(host2, "install", { to: version || null, createdBy: "user" });
23383
- let target = version;
23532
+ let task = null;
23384
23533
  try {
23534
+ const env = await host2.checkEnvironment();
23535
+ if (!env.ok) return { ok: false, error: "\u73AF\u5883\u68C0\u67E5\u5931\u8D25: " + env.errors.join("; ") };
23536
+ task = beginTask(host2, "install", { to: version || null, createdBy: "user" });
23537
+ let target = version;
23385
23538
  if (!target) {
23386
23539
  target = await host2._latestVersion().catch(() => null);
23387
23540
  if (!target) {
@@ -23404,7 +23557,7 @@ var require_ops4 = __commonJS({
23404
23557
  return { ok: false, error: res.error, output: res.output };
23405
23558
  }
23406
23559
  const isFirstInstall = !host2._manifest();
23407
- host2._recordManifest(target, isFirstInstall ? host2._claimDataPaths() : void 0);
23560
+ await host2._recordManifest(target, isFirstInstall ? host2._claimDataPaths() : void 0);
23408
23561
  try {
23409
23562
  if (typeof host2._bindNativeDshCommand === "function") host2._bindNativeDshCommand();
23410
23563
  } catch (e) {
@@ -23429,8 +23582,6 @@ var require_ops4 = __commonJS({
23429
23582
  if (host2.uninstalling) return { ok: false, error: "\u5378\u8F7D\u8FDB\u884C\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u518D\u88C5" };
23430
23583
  if (policies.busy(host2)) return { ok: false, error: "\u5347\u7EA7\u8FDB\u884C\u4E2D\uFF0C\u8BF7\u7A0D\u540E\u518D\u88C5\uFF08state=" + host2.upgradeState + "\uFF09" };
23431
23584
  if (!policies.isValidVersion(version)) return { ok: false, error: "\u975E\u6CD5\u7248\u672C\u53F7: " + version };
23432
- const env = host2.checkEnvironment();
23433
- if (!env.ok) return { ok: false, error: "\u73AF\u5883\u68C0\u67E5\u5931\u8D25: " + env.errors.join("; ") };
23434
23585
  install(host2, version).then(() => {
23435
23586
  }).catch((e) => {
23436
23587
  host2.installing = null;
@@ -23623,7 +23774,7 @@ var require_upgrade2 = __commonJS({
23623
23774
  if (!res.ok) throw new Error(res.error || "install failed");
23624
23775
  const newV = host2.installedVersion();
23625
23776
  if (newV !== target) throw new Error("\u5B89\u88C5\u540E\u7248\u672C\u6821\u9A8C\u5931\u8D25\uFF1A\u671F\u671B " + target + "\uFF0C\u5B9E\u9645 " + newV);
23626
- host2._recordManifest(newV || target);
23777
+ await host2._recordManifest(newV || target);
23627
23778
  if (host2.events) host2.events.append("upgrade_installed", { from: oldV, to: target });
23628
23779
  log(host2, "\u5B89\u88C5\u5B8C\u6210\uFF0C\u78C1\u76D8\u7248\u672C " + newV);
23629
23780
  if (task) {
@@ -23693,9 +23844,9 @@ var require_upgrade2 = __commonJS({
23693
23844
  async function rollbackAfterFailedVerify(host2, oldV, task, healthy) {
23694
23845
  log(host2, "\u5065\u5EB7\u9A8C\u8BC1\u5931\u8D25\uFF08" + healthy.reason + "\uFF09");
23695
23846
  if (task) host2.tasks.log(task.id, "\u5065\u5EB7\u9A8C\u8BC1\u5931\u8D25\uFF08" + healthy.reason + "\uFF09");
23696
- host2.rolledBack = true;
23697
23847
  host2.upgradeState = "rolling_back";
23698
23848
  const rb = await rollbackNative(host2, oldV, task);
23849
+ host2.rolledBack = rb.ok === true;
23699
23850
  host2.upgradeError = rb.ok ? "\u5347\u7EA7\u5931\u8D25\uFF0C\u5DF2\u56DE\u6EDA\u5230 " + oldV : "\u5347\u7EA7\u5931\u8D25\u4E14\u56DE\u6EDA\u5931\u8D25\uFF1A" + (rb.error || "");
23700
23851
  host2.upgradeState = "failed";
23701
23852
  host2.upgradeFinishedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -23727,7 +23878,7 @@ var require_upgrade2 = __commonJS({
23727
23878
  }
23728
23879
  tlog("\u56DE\u6EDA\u5B8C\u6210\uFF0C\u78C1\u76D8\u7248\u672C " + oldVersion);
23729
23880
  try {
23730
- host2._recordManifest(oldVersion);
23881
+ await host2._recordManifest(oldVersion);
23731
23882
  } catch (e2) {
23732
23883
  tlog("manifest \u66F4\u65B0\u5931\u8D25: " + e2.message);
23733
23884
  }
@@ -23742,7 +23893,6 @@ var require_upgrade2 = __commonJS({
23742
23893
  }
23743
23894
  async function rollbackAfterFailure(host2) {
23744
23895
  host2.upgradeState = "rolling_back";
23745
- host2.rolledBack = true;
23746
23896
  if (host2.events) host2.events.append("upgrade_rollback_started", { to: host2.oldVersion });
23747
23897
  log(host2, "\u56DE\u6EDA\u5230 " + host2.oldVersion + "\u2026");
23748
23898
  const registry = await host2._selectRegistry();
@@ -23763,8 +23913,9 @@ var require_upgrade2 = __commonJS({
23763
23913
  return { ok: false };
23764
23914
  }
23765
23915
  log(host2, "\u56DE\u6EDA\u5B8C\u6210\u3002");
23916
+ host2.rolledBack = true;
23766
23917
  try {
23767
- host2._recordManifest(host2.oldVersion);
23918
+ await host2._recordManifest(host2.oldVersion);
23768
23919
  } catch (e2) {
23769
23920
  log(host2, "manifest \u66F4\u65B0\u5931\u8D25: " + e2.message);
23770
23921
  }
@@ -23914,8 +24065,8 @@ var require_installer = __commonJS({
23914
24065
  _saveManifest(m) {
23915
24066
  return manifest.save(this, m);
23916
24067
  }
23917
- _recordManifest(version, dataPaths) {
23918
- return manifest.record(this, version, dataPaths, this.npmRoot || npm.resolveNpmRoot(this));
24068
+ async _recordManifest(version, dataPaths) {
24069
+ return manifest.record(this, version, dataPaths, this.npmRoot || await npm.resolveNpmRoot(this));
23919
24070
  }
23920
24071
  _claimDataPaths() {
23921
24072
  return manifest.claimDataPaths(this);
@@ -24297,6 +24448,7 @@ var require_domains = __commonJS({
24297
24448
  host2.managedObjects.registerAdapter("dsh", { supervise: () => host2._dshSuperviseOnce(), tickEvery: 1 });
24298
24449
  host2.managedObjects.registerAdapter("sandbox-instance", { supervise: (entry) => host2._sandboxSuperviseOnce(entry), tickEvery: 1 });
24299
24450
  }
24451
+ if (host2.managedObjects) host2.managedObjects.onBeatDone = () => host2.instances.governSweep();
24300
24452
  } catch (e) {
24301
24453
  host2.logger && host2.logger.warn && host2.logger.warn("managed registry init: " + (e && e.message));
24302
24454
  }
@@ -24891,7 +25043,7 @@ var require_guard = __commonJS({
24891
25043
  }
24892
25044
  }
24893
25045
  if (req.method === "GET" && pathname === "/guard/version") {
24894
- return send(200, sup.guardVersionLocal());
25046
+ return Promise.resolve(sup.guardVersionLocal()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
24895
25047
  }
24896
25048
  if (req.method === "POST" && pathname === "/guard/version/check") {
24897
25049
  req.resume();
@@ -25036,7 +25188,7 @@ var require_guard = __commonJS({
25036
25188
  req.resume();
25037
25189
  return send(403, {});
25038
25190
  }
25039
- return send(200, sup.envStatus());
25191
+ return Promise.resolve(sup.envStatus()).then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
25040
25192
  }
25041
25193
  if (req.method === "GET" && pathname === "/env/node-lts") {
25042
25194
  return sup.nodeLtsStatus().then((r) => send(200, r)).catch((e) => send(500, { ok: false, error: e.message }));
@@ -25691,7 +25843,7 @@ var require_instances = __commonJS({
25691
25843
  const r = sup.instances.updateInstance(j.id, j);
25692
25844
  return send(r && r.ok ? 200 : 400, r);
25693
25845
  }
25694
- if (act === "start" && j.id) return Promise.resolve(sup.instances.startInstance(j.id)).then((r) => send(r && r.ok ? 200 : 400, r)).catch((e) => send(500, { ok: false, error: e.message }));
25846
+ if (act === "start" && j.id) return Promise.resolve(sup.instances.startInstance(j.id, { manual: true })).then((r) => send(r && r.ok ? 200 : 400, r)).catch((e) => send(500, { ok: false, error: e.message }));
25695
25847
  if (act === "stop" && j.id) {
25696
25848
  const r = sup.instances.stopInstance(j.id);
25697
25849
  return send(r && r.ok ? 200 : 400, r);
@@ -25995,7 +26147,8 @@ var require_static = __commonJS({
25995
26147
  ".png": "image/png",
25996
26148
  ".ico": "image/x-icon"
25997
26149
  };
25998
- var CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'";
26150
+ var FRAME_ANCESTORS = "tauri://localhost http://tauri.localhost https://tauri.localhost";
26151
+ var CSP = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; frame-ancestors " + FRAME_ANCESTORS;
25999
26152
  function serveStatic(res, file, corsOrigin) {
26000
26153
  if (!UI_DIR) {
26001
26154
  res.writeHead(503, { "Content-Type": "text/plain; charset=utf-8" });
@@ -27904,28 +28057,45 @@ function printSelfVersion() {
27904
28057
  const { guardVersion } = require_version();
27905
28058
  console.log("dsh-supervisor v" + guardVersion());
27906
28059
  }
27907
- function apiRequest(method, apiPath) {
28060
+ function apiRaw(method, apiPath, body) {
27908
28061
  return new Promise((resolve) => {
27909
28062
  const addr = readApiAddress();
27910
28063
  const req = http.request(
27911
- { hostname: addr.host, port: addr.port, path: apiPath, method, timeout: 5e3 },
28064
+ {
28065
+ hostname: addr.host,
28066
+ port: addr.port,
28067
+ path: apiPath,
28068
+ method,
28069
+ timeout: 5e3,
28070
+ headers: body ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } : {}
28071
+ },
27912
28072
  (res) => {
27913
- let body = "";
27914
- res.on("data", (d) => body += d);
27915
- res.on("end", () => {
27916
- try {
27917
- resolve(JSON.parse(body));
27918
- } catch {
27919
- resolve({ error: "parse error", raw: body });
27920
- }
27921
- });
27922
- res.on("error", () => resolve({ error: "response error" }));
28073
+ let text = "";
28074
+ res.setEncoding("utf8");
28075
+ res.on("data", (d) => text += d);
28076
+ res.on("end", () => resolve({ status: res.statusCode, text }));
28077
+ res.on("error", (e) => resolve({ status: 0, text: "", error: e.message }));
27923
28078
  }
27924
28079
  );
27925
- req.on("error", () => resolve({ error: "daemon \u672A\u8FD0\u884C\u6216\u8FDE\u63A5\u5931\u8D25" }));
28080
+ req.on("timeout", () => req.destroy(new Error("api-timeout")));
28081
+ req.on(
28082
+ "error",
28083
+ (e) => resolve({ status: 0, text: "", error: e && e.message === "api-timeout" ? "daemon \u54CD\u5E94\u8D85\u65F6\uFF085s\uFF09" : "daemon \u672A\u8FD0\u884C\u6216\u8FDE\u63A5\u5931\u8D25" })
28084
+ );
28085
+ if (body) req.write(body);
27926
28086
  req.end();
27927
28087
  });
27928
28088
  }
28089
+ function apiRequest(method, apiPath) {
28090
+ return apiRaw(method, apiPath).then(({ status, text, error }) => {
28091
+ if (error) return { error };
28092
+ try {
28093
+ return JSON.parse(text);
28094
+ } catch {
28095
+ return { error: "parse error", raw: text };
28096
+ }
28097
+ });
28098
+ }
27929
28099
  var LOCK_FILE = process.env.DSH_SUPERVISOR_LOCK_FILE || path.join(SUPERVISOR_DIR, "guard.lock");
27930
28100
  var LOCK_OWNER = { pid: process.pid, started: Date.now(), entry: process.argv[1] || "" };
27931
28101
  function readLock() {
@@ -27946,6 +28116,41 @@ function readLock() {
27946
28116
  function isOwnGuardEntry(cmdline) {
27947
28117
  return typeof cmdline === "string" && cmdline.includes("dsh-supervisor");
27948
28118
  }
28119
+ var LOCK_HEARTBEAT_MS = 2e3;
28120
+ var LOCK_STALE_MS = LOCK_HEARTBEAT_MS * 15;
28121
+ function lockSilentMs() {
28122
+ try {
28123
+ return Math.max(0, Date.now() - fs.statSync(LOCK_FILE).mtimeMs);
28124
+ } catch {
28125
+ return null;
28126
+ }
28127
+ }
28128
+ function pidAlive(pid) {
28129
+ try {
28130
+ process.kill(pid, 0);
28131
+ return true;
28132
+ } catch (e) {
28133
+ return e.code === "EPERM";
28134
+ }
28135
+ }
28136
+ var LOCK_TAKE_RETRY_MS = 8e3;
28137
+ var LOCK_TAKE_NAP_MS = 250;
28138
+ function napSync(ms) {
28139
+ try {
28140
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
28141
+ } catch {
28142
+ }
28143
+ }
28144
+ function startLockHeartbeat() {
28145
+ const t = setInterval(() => {
28146
+ try {
28147
+ const d = /* @__PURE__ */ new Date();
28148
+ fs.utimesSync(LOCK_FILE, d, d);
28149
+ } catch {
28150
+ }
28151
+ }, LOCK_HEARTBEAT_MS);
28152
+ if (t.unref) t.unref();
28153
+ }
27949
28154
  function acquireLock() {
27950
28155
  fs.mkdirSync(SUPERVISOR_DIR, { recursive: true });
27951
28156
  const tryCreate = () => {
@@ -27960,26 +28165,47 @@ function acquireLock() {
27960
28165
  process.exit(1);
27961
28166
  }
27962
28167
  };
27963
- if (tryCreate()) return true;
28168
+ const take = () => {
28169
+ if (!tryCreate()) return null;
28170
+ startLockHeartbeat();
28171
+ return true;
28172
+ };
28173
+ const first = take();
28174
+ if (first) return true;
27964
28175
  const held = readLock();
27965
28176
  if (held) {
27966
- let alive = false;
27967
- try {
27968
- process.kill(held.pid, 0);
27969
- alive = true;
27970
- } catch (err) {
27971
- alive = err.code === "EPERM";
28177
+ if (!pidAlive(held.pid)) {
28178
+ console.log("[supervisor] \u6E05\u7406\u9648\u65E7\u5B88\u536B\u9501\uFF08\u6301\u6709 pid " + held.pid + " \u5DF2\u9000\u51FA\uFF09");
28179
+ } else {
28180
+ const cmdline = readCmdline(held.pid);
28181
+ if (cmdline && isOwnGuardEntry(cmdline)) {
28182
+ const deadline = Date.now() + LOCK_TAKE_RETRY_MS;
28183
+ while (pidAlive(held.pid)) {
28184
+ if (Date.now() >= deadline) {
28185
+ return "\u6301\u6709 pid " + held.pid + " \u5B58\u6D3B\u3001\u547D\u4EE4\u884C\u662F\u672C\u4EA7\u54C1\u7684\u5B88\u536B\u5165\u53E3\uFF0C\u6709\u754C\u7B49\u5F85 " + Math.round(LOCK_TAKE_RETRY_MS / 1e3) + "s \u540E\u4ECD\u672A\u8BA9\u51FA";
28186
+ }
28187
+ napSync(LOCK_TAKE_NAP_MS);
28188
+ }
28189
+ console.log("[supervisor] \u524D\u4EFB\u5B88\u536B pid " + held.pid + " \u5728\u7B49\u5F85\u4E2D\u9000\u51FA -> \u63A5\u7BA1\u5B88\u536B\u9501");
28190
+ } else if (cmdline) {
28191
+ console.log("[supervisor] \u6E05\u7406\u9648\u65E7\u5B88\u536B\u9501\uFF08\u6301\u6709 pid " + held.pid + " \u5B58\u6D3B\u4F46\u547D\u4EE4\u884C\u4E0D\u662F\u672C\u4EA7\u54C1\u7684\u5B88\u536B\u5165\u53E3\uFF1A" + cmdline + "\uFF09");
28192
+ } else {
28193
+ const silent = lockSilentMs();
28194
+ if (silent !== null && silent >= LOCK_STALE_MS) {
28195
+ console.log("[supervisor] \u6E05\u7406\u9648\u65E7\u5B88\u536B\u9501\uFF08\u6301\u6709 pid " + held.pid + " \u5B58\u6D3B\u4F46\u547D\u4EE4\u884C\u8BFB\u4E0D\u5230\uFF0C\u4E14\u9501\u5DF2 " + Math.round(silent / 1e3) + "s \u672A\u7EED\u7EA6 -> \u5224\u5B9A\u539F\u6301\u6709\u8005\u5DF2\u6B7B\uFF09");
28196
+ } else {
28197
+ return "\u6301\u6709 pid " + held.pid + " \u5B58\u6D3B\u3001\u547D\u4EE4\u884C\u8BFB\u4E0D\u5230\uFF0C" + (silent === null ? "\u9501\u72B6\u6001\u4E0D\u53EF\u7EDF\u8BA1\uFF08\u4FDD\u5B88\u8BA9\u4F4D\uFF09" : "\u9501 " + Math.round(silent / 1e3) + "s \u524D\u4ECD\u5728\u7EED\u7EA6\uFF08\u4FDD\u5B88\u8BA9\u4F4D\uFF09");
28198
+ }
28199
+ }
27972
28200
  }
27973
- const cmdline = alive ? readCmdline(held.pid) : null;
27974
- if (alive && !cmdline) return false;
27975
- if (alive && isOwnGuardEntry(cmdline)) return false;
27976
- console.log("[supervisor] \u6E05\u7406\u9648\u65E7\u5B88\u536B\u9501\uFF08\u6301\u6709 pid " + held.pid + (alive ? " \u5B58\u6D3B\u4F46\u547D\u4EE4\u884C\u4E0D\u662F\u672C\u4EA7\u54C1\u7684\u5B88\u536B\u5165\u53E3\uFF1A" + cmdline : " \u5DF2\u9000\u51FA") + "\uFF09");
27977
28201
  }
27978
28202
  try {
27979
28203
  fs.unlinkSync(LOCK_FILE);
27980
28204
  } catch {
27981
28205
  }
27982
- return tryCreate();
28206
+ const again = take();
28207
+ if (again) return true;
28208
+ return "\u6E05\u7406\u9648\u65E7\u9501\u540E\u4ECD\u521B\u5EFA\u4E0D\u4E86\uFF08" + LOCK_FILE + " \u4E0D\u53EF\u5199\uFF1F\uFF09";
27983
28209
  }
27984
28210
  function releaseLock() {
27985
28211
  try {
@@ -27991,8 +28217,9 @@ function releaseLock() {
27991
28217
  function cmdDaemon() {
27992
28218
  const moved = stateRoot.migrateLegacy();
27993
28219
  for (const m of moved) console.log("[migrate] \u72B6\u6001\u76EE\u5F55\u8FC1\u79FB: " + m);
27994
- if (!acquireLock()) {
27995
- console.error("[supervisor] \u5DF2\u6709\u5B88\u536B\u5B9E\u4F8B\u5728\u8FD0\u884C\uFF08\u9501 " + LOCK_FILE + "\uFF09\uFF0C\u672C\u8FDB\u7A0B\u9000\u51FA\u3002");
28220
+ const lock = acquireLock();
28221
+ if (lock !== true) {
28222
+ console.error("[supervisor] \u672C\u8FDB\u7A0B\u9000\u51FA\uFF1A\u672A\u53D6\u5F97\u5B88\u536B\u9501\uFF08\u9501 " + LOCK_FILE + "\uFF09\u2014\u2014 " + lock);
27996
28223
  process.exit(1);
27997
28224
  }
27998
28225
  process.on("exit", releaseLock);
@@ -28187,26 +28414,9 @@ function cmdVersion() {
28187
28414
  });
28188
28415
  }
28189
28416
  function cmdSelfUpdate(action) {
28190
- const addr = readApiAddress();
28191
- const apiOne = (method, p, body) => new Promise((resolve) => {
28192
- const req = http.request({ hostname: addr.host, port: addr.port, path: p, method, timeout: 5e3, headers: body ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } : {} }, (res) => {
28193
- let b = "";
28194
- res.on("data", (d) => b += d);
28195
- res.on("end", () => {
28196
- try {
28197
- resolve(JSON.parse(b));
28198
- } catch {
28199
- resolve({ error: "parse error", raw: b });
28200
- }
28201
- });
28202
- });
28203
- req.on("error", () => resolve({ error: "daemon \u672A\u8FD0\u884C\u6216\u8FDE\u63A5\u5931\u8D25" }));
28204
- if (body) req.write(body);
28205
- req.end();
28206
- });
28207
28417
  (async () => {
28208
28418
  if (action === "check" || !action) {
28209
- const s = await apiOne("GET", "/self-update/status");
28419
+ const s = await apiRequest("GET", "/self-update/status");
28210
28420
  if (s.error) return console.log("\u68C0\u67E5\u5931\u8D25: " + s.error);
28211
28421
  if (s.ok) {
28212
28422
  console.log("\u5B88\u536B\u5F53\u524D\u7248\u672C: " + s.installed);
@@ -28224,31 +28434,13 @@ function cmdSelfUpdate(action) {
28224
28434
  })();
28225
28435
  }
28226
28436
  function cmdUpgrade(requested) {
28227
- const post = () => new Promise((resolve) => {
28228
- const addr = readApiAddress();
28229
- const body = requested ? JSON.stringify({ version: requested }) : void 0;
28230
- const req = http.request(
28231
- {
28232
- hostname: addr.host,
28233
- port: addr.port,
28234
- path: "/native/upgrade",
28235
- method: "POST",
28236
- timeout: 5e3,
28237
- headers: body ? { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) } : {}
28238
- },
28239
- (res) => {
28240
- let b = "";
28241
- res.on("data", (d) => b += d);
28242
- res.on("end", () => resolve({ code: res.statusCode, body: b }));
28243
- }
28244
- );
28245
- req.on("error", () => resolve({ code: 0, body: "" }));
28246
- if (body) req.write(body);
28247
- req.end();
28248
- });
28249
- post().then(({ code, body }) => {
28437
+ const body = requested ? JSON.stringify({ version: requested }) : void 0;
28438
+ const post = () => apiRaw("POST", "/native/upgrade", body);
28439
+ post().then(({ status, text, error }) => {
28440
+ const code = error ? 0 : status;
28441
+ const outText = error ? "" : text;
28250
28442
  if (code !== 202) {
28251
- console.log("\u5347\u7EA7\u672A\u88AB\u63A5\u53D7:", body || "HTTP " + code);
28443
+ console.log("\u5347\u7EA7\u672A\u88AB\u63A5\u53D7:", outText || "HTTP " + code);
28252
28444
  process.exit(1);
28253
28445
  }
28254
28446
  console.log("\u5347\u7EA7\u5DF2\u5F00\u59CB\uFF0C\u8DDF\u8E2A\u8FDB\u5EA6\uFF08\u5148\u505C DSH \u2192 \u5B89\u88C5 \u2192 \u81EA\u52A8\u62C9\u8D77\uFF0C\u9700\u6570\u5206\u949F\uFF09\u2026");