@dsh-sup/dsh-core-win-x64 0.1.6-BETA.2 → 0.1.6-BETA.3

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 +1323 -1304
  2. package/package.json +1 -1
package/core.cjs CHANGED
@@ -107,1503 +107,1503 @@ var require_exec = __commonJS({
107
107
  }
108
108
  });
109
109
 
110
- // src/platform/service/state-root.js
111
- var require_state_root = __commonJS({
112
- "src/platform/service/state-root.js"(exports2, module2) {
110
+ // src/platform/os/pidlookup/norm.js
111
+ var require_norm = __commonJS({
112
+ "src/platform/os/pidlookup/norm.js"(exports2, module2) {
113
113
  "use strict";
114
- var fs2 = require("node:fs");
115
- var os2 = require("node:os");
116
- var path2 = require("node:path");
117
- var SCHEMA = 1;
118
- function root() {
119
- const override = process.env.DSH_SUPERVISOR_HOME;
120
- if (override && String(override).trim()) return path2.resolve(String(override).trim());
121
- if (process.platform === "win32") {
122
- const local = process.env.LOCALAPPDATA || path2.join(os2.homedir(), "AppData", "Local");
123
- return path2.join(local, "dsh-supervisor");
114
+ function parseProcNetTcpInodes(txt, port) {
115
+ const inodes = /* @__PURE__ */ new Set();
116
+ for (const lineRaw of String(txt || "").split("\n")) {
117
+ const cols = lineRaw.trim().split(/\s+/);
118
+ if (cols.length < 10) continue;
119
+ const local = cols[1];
120
+ const st = cols[3];
121
+ const inode = cols[9];
122
+ if (!local || !inode) continue;
123
+ const p = local.split(":")[1];
124
+ if (st === "0A" && p && parseInt(p, 16) === port) inodes.add("socket:[" + inode + "]");
124
125
  }
125
- if (process.platform === "darwin") {
126
- return path2.join(os2.homedir(), "Library", "Application Support", "dsh-supervisor");
126
+ return inodes;
127
+ }
128
+ function parseLsofPid(out) {
129
+ for (const line of String(out || "").split("\n")) {
130
+ const m = line.trim().split(/\s+/);
131
+ if (m.length >= 2 && /^\d+$/.test(m[1])) return Number(m[1]);
127
132
  }
128
- const xdg = process.env.XDG_STATE_HOME;
129
- return xdg && String(xdg).trim() ? path2.join(String(xdg).trim(), "dsh-supervisor") : path2.join(os2.homedir(), ".local", "state", "dsh-supervisor");
133
+ return null;
130
134
  }
131
- function supervisorDir() {
132
- return path2.join(root(), "supervisor");
135
+ function parseNetstatPid(out, port) {
136
+ const want = String(port);
137
+ for (const line of String(out || "").split("\n")) {
138
+ const parts = line.trim().split(/\s+/);
139
+ if (parts.length >= 5 && (parts[0] === "TCP" || parts[0] === "TCPv6") && parts[3] === "LISTENING") {
140
+ const lp = parts[1];
141
+ const p = lp.slice(lp.lastIndexOf(":") + 1);
142
+ if (p === want) {
143
+ const pid = Number(parts[4]);
144
+ if (Number.isInteger(pid) && pid > 0) return pid;
145
+ }
146
+ }
147
+ }
148
+ return null;
133
149
  }
134
- function shellDir() {
135
- return path2.join(root(), "shell");
150
+ function parseSsPid(out) {
151
+ const m = out && /pid=(\d+)/.exec(String(out));
152
+ return m ? Number(m[1]) : null;
136
153
  }
137
- function legacySupervisorDir() {
138
- return path2.join(os2.homedir(), ".dsh", "supervisor");
154
+ function parseWmicCommandLine(out) {
155
+ if (!out) return null;
156
+ const m = /CommandLine=([\s\S]*)/.exec(String(out));
157
+ const v = m ? m[1].trim() : "";
158
+ return v || null;
139
159
  }
140
- function legacyShellDir() {
141
- return path2.join(os2.homedir(), ".dsh", "shell");
160
+ function parsePowerShellCommandLine(out) {
161
+ const v = out ? String(out).trim() : "";
162
+ return v || null;
142
163
  }
143
- function migrateLegacy() {
144
- const moved = [];
145
- for (const [from, to] of [
146
- [legacySupervisorDir(), supervisorDir()],
147
- [legacyShellDir(), shellDir()]
148
- ]) {
149
- try {
150
- if (!fs2.existsSync(from)) continue;
151
- fs2.mkdirSync(to, { recursive: true });
152
- for (const name of fs2.readdirSync(from)) {
153
- const src = path2.join(from, name);
154
- const dst = path2.join(to, name);
155
- if (fs2.existsSync(dst)) continue;
156
- try {
157
- fs2.renameSync(src, dst);
158
- moved.push(src + " -> " + dst);
159
- } catch {
160
- }
161
- }
162
- try {
163
- if (fs2.readdirSync(from).length === 0) fs2.rmdirSync(from);
164
- } catch {
165
- }
166
- } catch {
167
- }
168
- }
169
- return moved;
164
+ function normCmdline(s) {
165
+ return String(s || "").replace(/\\/g, "/");
170
166
  }
171
- module2.exports = { SCHEMA, root, supervisorDir, shellDir, migrateLegacy };
167
+ module2.exports = {
168
+ parseProcNetTcpInodes,
169
+ parseLsofPid,
170
+ parseNetstatPid,
171
+ parseSsPid,
172
+ parseWmicCommandLine,
173
+ parsePowerShellCommandLine,
174
+ normCmdline
175
+ };
172
176
  }
173
177
  });
174
178
 
175
- // src/platform/service/config.js
176
- var require_config = __commonJS({
177
- "src/platform/service/config.js"(exports2, module2) {
179
+ // src/platform/os/exec-path.js
180
+ var require_exec_path = __commonJS({
181
+ "src/platform/os/exec-path.js"(exports2, module2) {
178
182
  "use strict";
179
- var os2 = require("node:os");
183
+ var fs2 = require("node:fs");
180
184
  var path2 = require("node:path");
181
- function expandHome(p) {
182
- if (typeof p !== "string") return p;
183
- if (p === "~") return os2.homedir();
184
- if (p.startsWith("~/")) return path2.join(os2.homedir(), p.slice(2));
185
- return p;
186
- }
187
- var SUP = require_state_root().supervisorDir();
188
- var NO_EXTENSION = Object.freeze({ defaults: [], aliases: [] });
189
- function normalizeExtension(ext) {
190
- if (!ext || typeof ext !== "object") return NO_EXTENSION;
191
- return {
192
- defaults: Array.isArray(ext.defaults) ? ext.defaults : [],
193
- aliases: Array.isArray(ext.aliases) ? ext.aliases : []
194
- };
195
- }
196
- var BASE_DEFAULTS = {
197
- probeIntervalMs: 5e3,
198
- // 健康探测三层:L0 进程存活 + L1 端口监听 + L2 HTTP GET healthUrl。probeTimeoutMs = 单次探测超时;
199
- // failThreshold = 连续失败判故障(防抖动);httpProbeEnabled=false 退化为端口在线即健康。
200
- probeTimeoutMs: 3e3,
201
- failThreshold: 2,
202
- httpProbeEnabled: true,
203
- startTimeoutMs: 3e4,
204
- stopGraceMs: 1e4,
205
- portReleaseWaitMs: 1e4,
206
- crashWindowMs: 6e5,
207
- crashBurst: 5,
208
- backoff: [3e4, 6e4, 12e4, 3e5, 6e5],
209
- apiHost: "127.0.0.1",
210
- // API 端口:高位不常用段起始(常用端口段易冲突);守卫启动被占则自动顺延并持久化。
211
- apiPort: 36360,
212
- // 注意:daemon 控制通道端口属业务域知识,经注入声明提供(同上),原键名与值逐字保留。
213
- // 动态端口池(范围是配置项,非编译期常量):null = 内置默认池,默认避开 OS 动态端口范围
214
- // (Linux ip_local_port_range=32768-60999),落在 IANA User 段低位。额外池由域装配期申报(DS-G4)。
215
- portPools: null,
216
- stateFile: path2.join(SUP, "state.json"),
217
- // 日志目录布局:事件在 events/guard.events.log,分级日志在 log/;显式配置的路径尊重用户给定值,不强改。
218
- logFile: path2.join(SUP, "events", "guard.events.log"),
219
- eventsMaxBytes: 5 * 1024 * 1024,
220
- supervisorLogFile: path2.join(SUP, "log", "guard.log"),
221
- dshLogFile: path2.join(SUP, "log", "dsh.log"),
222
- upgradeLogFile: path2.join(SUP, "log", "upgrade.log"),
223
- logLevel: "info",
224
- logMaxBytes: 5 * 1024 * 1024,
225
- notifyEnabled: true,
226
- // 内核更新单写入者 = 桌面壳:corePackageName 仅为内核自身 npm 子包名,守卫只读它查版本状态,
227
- // 安装/升级由壳执行。不得再引入 manifest 更新通道(残留键已删,config.json 中未知键加载时忽略)。
228
- corePackageName: null,
229
- pluginsProfileName: "web",
230
- packageName: "@deepseek-ai/dsh",
231
- // 灰度名单(RELEASE-CHANNEL-CONTRACT):本机 canary:true 即灰度机,仅对内核包生效,对第三方包
232
- // (packageName)无效;未置真即非灰度。
233
- canary: false,
234
- // 最小兜底镜像源(不变量 C2):完整目录归壳管理,经 <产品状态根>/supervisor/registry.json 投放,
235
- // 内核由 platform/distribution 读取(契约见 platform/contract/registry.js);
236
- // 此处官方源 + npmmirror 两条兜底,保证契约不可用时仍可安装。
237
- registries: [
238
- "https://registry.npmjs.org",
239
- "https://registry.npmmirror.com"
240
- ],
241
- updateCheckEnabled: true,
242
- updateCheckIntervalMs: 36e5,
243
- initialCheckDelayMs: 2e4,
244
- upgradeTimeoutMs: 6e5,
245
- installCommandTemplate: ["npm", "install", "-g", "{pkg}@{version}"],
246
- // apiAccessKey(可选)出回环访问密钥:配置后,0.0.0.0(局域网)与 FRP 公网通道的 API 请求必须携带
247
- // Authorization: Bearer <key> 或 ?access_key=<key>,否则 401;回环豁免。
248
- // 不配置 = LAN 受 RFC1918 白名单约束,FRP 暴露仍强制 remoteToken。
249
- apiAccessKey: null,
250
- // 关闭窗口行为(系统级):'hide' = 隐藏至托盘、服务常驻(默认);'exit' = 退出管家并停全部服务链。
251
- closeAction: "hide"
252
- };
253
- function buildDefaults2(ext) {
254
- const pending = normalizeExtension(ext).defaults.slice();
255
- const out = {};
256
- for (const key of Object.keys(BASE_DEFAULTS)) {
257
- for (let i = pending.length - 1; i >= 0; i--) {
258
- const g = pending[i];
259
- if (g && g.at === key) {
260
- if (g.values && typeof g.values === "object") Object.assign(out, g.values);
261
- pending.splice(i, 1);
262
- }
263
- }
264
- out[key] = BASE_DEFAULTS[key];
265
- }
266
- for (const g of pending) {
267
- if (g && g.values && typeof g.values === "object") Object.assign(out, g.values);
185
+ var os2 = require("node:os");
186
+ function candidateNames(base, platform) {
187
+ const win = (platform || process.platform) === "win32";
188
+ if (!win) return [base];
189
+ const exts = String(process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
190
+ const names = [base + ".exe", base + ".cmd", base + ".bat"];
191
+ for (const e of exts) {
192
+ const n = base + e.toLowerCase();
193
+ if (!names.some((x) => x.toLowerCase() === n)) names.push(n);
268
194
  }
269
- return out;
195
+ names.push(base);
196
+ return [...new Set(names)];
270
197
  }
271
- var DEFAULTS = buildDefaults2(null);
272
- function normalize(raw, ext) {
273
- const extension = normalizeExtension(ext);
274
- const provided = raw || {};
275
- const cfg = Object.assign(buildDefaults2(extension), provided);
276
- cfg.stateFile = expandHome(cfg.stateFile);
277
- cfg.logFile = expandHome(cfg.logFile);
278
- cfg.supervisorLogFile = expandHome(cfg.supervisorLogFile);
279
- cfg.dshLogFile = expandHome(cfg.dshLogFile);
280
- cfg.upgradeLogFile = expandHome(cfg.upgradeLogFile);
281
- let u;
198
+ function isExecutableFile(p, platform) {
282
199
  try {
283
- u = new URL(cfg.healthUrl);
200
+ if (!fs2.statSync(p).isFile()) return false;
201
+ if ((platform || process.platform) === "win32") return true;
202
+ fs2.accessSync(p, fs2.constants.X_OK);
203
+ return true;
284
204
  } catch {
285
- throw new Error("config.healthUrl \u65E0\u6548: " + JSON.stringify(cfg.healthUrl));
286
- }
287
- cfg.targetHost = u.hostname;
288
- cfg.targetPort = Number(u.port || (u.protocol === "https:" ? 443 : 80));
289
- for (const [from, to] of extension.aliases) {
290
- if (provided[to] === void 0 && provided[from] !== void 0) cfg[to] = provided[from] === true;
291
- }
292
- const cmdPort = extractPortFromCommand(cfg.command);
293
- if (cmdPort !== null) cfg.targetPort = cmdPort;
294
- cfg.probeTimeoutMs = Number.isFinite(Number(cfg.probeTimeoutMs)) && Number(cfg.probeTimeoutMs) > 0 ? Number(cfg.probeTimeoutMs) : 3e3;
295
- cfg.failThreshold = Number.isInteger(Number(cfg.failThreshold)) && Number(cfg.failThreshold) >= 1 ? Number(cfg.failThreshold) : 2;
296
- cfg.httpProbeEnabled = cfg.httpProbeEnabled !== false;
297
- if (!Array.isArray(cfg.command) || cfg.command.length === 0) {
298
- throw new Error("config.command \u7F3A\u5931\uFF1A\u9700\u8981\u4E00\u4E2A\u547D\u4EE4\u6570\u7EC4");
205
+ return false;
299
206
  }
300
- return cfg;
301
207
  }
302
- function extractPortFromCommand(command) {
303
- if (!Array.isArray(command)) return null;
304
- for (let i = 0; i < command.length; i++) {
305
- const a = String(command[i]);
306
- if ((a === "--port" || a === "-p") && i + 1 < command.length) {
307
- const n = Number(command[i + 1]);
308
- if (Number.isInteger(n) && n > 0 && n <= 65535) return n;
309
- }
310
- const m = /^--port=(\d+)$/.exec(a);
311
- if (m) {
312
- const n = Number(m[1]);
313
- if (Number.isInteger(n) && n > 0 && n <= 65535) return n;
208
+ function firstExecutable(dir, base, platform) {
209
+ if (!dir) return null;
210
+ for (const name of candidateNames(base, platform)) {
211
+ const p = path2.join(dir, name);
212
+ try {
213
+ if (isExecutableFile(p, platform)) return p;
214
+ } catch {
314
215
  }
315
216
  }
316
217
  return null;
317
218
  }
318
- module2.exports = { DEFAULTS, BASE_DEFAULTS, buildDefaults: buildDefaults2, normalize, extractPortFromCommand };
319
- }
320
- });
321
-
322
- // src/app/settings/domain-config.js
323
- var require_domain_config = __commonJS({
324
- "src/app/settings/domain-config.js"(exports2, module2) {
325
- "use strict";
326
- var defaults = [
327
- {
328
- at: "portPools",
329
- // daemon 控制通道端口:集中定义,杜绝散落硬编码。
330
- // 这两个值同时是 app/ctl/client.js 与两个 daemon 的兜底端口,不得单独改动
331
- // (改动即需同步 8 处 43107/43108 兜底常量)。
332
- values: {
333
- routerCtlPort: 43107,
334
- lanCtlPort: 43108
335
- }
336
- },
337
- {
338
- at: "corePackageName",
339
- // 智能路由启动开关。
340
- values: {
341
- routerAutostart: false
219
+ function standardDirs(platform, home, env) {
220
+ const pl = platform || process.platform;
221
+ const h = home || os2.homedir();
222
+ const e = env || process.env;
223
+ const dirs = [];
224
+ if (pl === "win32") {
225
+ if (e.APPDATA) dirs.push(path2.join(e.APPDATA, "npm"));
226
+ if (e.LOCALAPPDATA) dirs.push(path2.join(e.LOCALAPPDATA, "Programs", "dsh-supervisor"));
227
+ dirs.push(path2.join(h, ".local", "bin"));
228
+ } else {
229
+ dirs.push(path2.join(h, ".local", "bin"));
230
+ dirs.push(path2.join(h, ".npm-global", "bin"));
231
+ if (pl === "darwin") {
232
+ dirs.push("/opt/homebrew/bin");
233
+ dirs.push("/usr/local/bin");
342
234
  }
343
235
  }
344
- ];
345
- var aliases = [
346
- ["switcherAutoStart", "routerAutostart"]
347
- ];
348
- function extension() {
349
- return {
350
- defaults: defaults.map((g) => ({ at: g.at, values: Object.assign({}, g.values) })),
351
- aliases: aliases.map((a) => a.slice())
352
- };
236
+ return dirs;
353
237
  }
354
- module2.exports = { extension, defaults, aliases };
355
- }
356
- });
357
-
358
- // src/platform/service/version.js
359
- var require_version = __commonJS({
360
- "src/platform/service/version.js"(exports2, module2) {
361
- "use strict";
362
- var fs2 = require("node:fs");
363
- var path2 = require("node:path");
364
- function guardVersion() {
365
- if (true) return String("0.1.6-BETA.2");
366
- try {
367
- return JSON.parse(fs2.readFileSync(path2.join(__dirname, "..", "..", "package.json"), "utf8")).version || "unknown";
368
- } catch {
369
- return "unknown";
238
+ function inPath(base, platform, env) {
239
+ const e = env || process.env;
240
+ const raw = e.PATH || e.Path || "";
241
+ for (const d of raw.split(path2.delimiter)) {
242
+ if (!d) continue;
243
+ const hit = firstExecutable(d, base, platform);
244
+ if (hit) return hit;
370
245
  }
246
+ return null;
371
247
  }
372
- module2.exports = { guardVersion };
373
- }
374
- });
375
-
376
- // src/app/state/main-record.js
377
- var require_main_record = __commonJS({
378
- "src/app/state/main-record.js"(exports2, module2) {
379
- "use strict";
380
- function createMainRecord(deps) {
381
- const g = deps || {};
382
- const reg = () => typeof g.getManagedObjects === "function" ? g.getManagedObjects() : null;
383
- const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
384
- let fallback = null;
385
- function entryOf() {
386
- const m = reg();
387
- if (!m || typeof m.get !== "function") return null;
248
+ function resolveExecutable(base, opts) {
249
+ const o = opts || {};
250
+ const pl = o.platform;
251
+ const env = o.env;
252
+ const E = env || process.env;
253
+ if (o.envVar && E[o.envVar]) {
254
+ const v = E[o.envVar];
255
+ if (isExecutableFile(v, pl)) return v;
256
+ }
257
+ const inPathHit = inPath(base, pl, env);
258
+ if (inPathHit) return inPathHit;
259
+ for (const d of [...o.extraDirs || [], ...standardDirs(pl, void 0, env)]) {
260
+ const hit = firstExecutable(d, base, pl);
261
+ if (hit) return hit;
262
+ }
263
+ return null;
264
+ }
265
+ function npxBin(opts) {
266
+ const o = opts || {};
267
+ const pl = o.platform || process.platform;
268
+ const env = o.env || process.env;
269
+ if (pl !== "win32") return "npx";
270
+ const resolved = resolveExecutable("npx", { platform: pl, env, extraDirs: [
271
+ env.APPDATA ? path2.join(env.APPDATA, "npm") : null
272
+ ].filter(Boolean) });
273
+ if (resolved) return resolved;
274
+ return "npx.cmd";
275
+ }
276
+ function npmBin(opts) {
277
+ const o = opts || {};
278
+ const pl = o.platform || process.platform;
279
+ const env = o.env || process.env;
280
+ if (pl !== "win32") return "npm";
281
+ const resolved = resolveExecutable("npm", { platform: pl, env, extraDirs: [
282
+ env.APPDATA ? path2.join(env.APPDATA, "npm") : null
283
+ ].filter(Boolean) });
284
+ if (resolved) return resolved;
285
+ return "npm.cmd";
286
+ }
287
+ var DSH_PKG = ["@deepseek-ai", "dsh"];
288
+ function dshJsIn(prefix) {
289
+ return path2.join(prefix, "node_modules", ...DSH_PKG, "lib", "bin.js");
290
+ }
291
+ function resolveDsh(opts) {
292
+ const o = opts || {};
293
+ const pl = o.platform || process.platform;
294
+ const env = o.env || process.env;
295
+ const isFile = (p) => {
388
296
  try {
389
- return m.get("main") || null;
297
+ return fs2.statSync(p).isFile();
390
298
  } catch {
391
- return null;
299
+ return false;
392
300
  }
393
- }
394
- function fallbackEntryOf() {
395
- if (!fallback) {
396
- fallback = {
397
- kind: "dsh",
398
- id: "main",
399
- name: "\u4E3B\u5B9E\u4F8B",
400
- desired: "running",
401
- guardian: true,
402
- ownership: { ports: [], rootPath: null, unit: null, daemonScript: null, processMode: "spawn", meta: null },
403
- phase: "stopped",
404
- lastObserved: null,
405
- backoffLevel: 0,
406
- backoffUntil: null,
407
- crashWindowStart: null,
408
- crashWindowRestarts: 0,
409
- restartCount: 0,
410
- startedAt: null,
411
- lastTransitionAt: null,
412
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
413
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
414
- process: null
415
- };
301
+ };
302
+ const asJs = (bin, launcher) => ({ runtime: process.execPath, bin, isJs: true, launcher: launcher || null });
303
+ if (env.DSH_BIN) {
304
+ try {
305
+ const r = fs2.realpathSync(env.DSH_BIN);
306
+ if (isFile(r)) return asJs(r, env.DSH_BIN);
307
+ } catch {
416
308
  }
417
- return fallback;
418
309
  }
419
- function persistCrashField() {
420
- const m = reg();
310
+ const hit = resolveExecutable("dsh", { platform: pl, env });
311
+ if (hit) {
421
312
  try {
422
- if (m && typeof m.persistCrashState === "function") m.persistCrashState();
423
- else if (m && typeof m._save === "function") m._save();
424
- } catch (e) {
425
- const l = logger();
426
- if (l && l.warn) l.warn("persistCrashField: " + (e && e.message || e));
313
+ const real = fs2.realpathSync(hit);
314
+ if (isFile(real) && /\.(js|cjs|mjs)$/i.test(real)) return asJs(real, hit);
315
+ } catch {
427
316
  }
317
+ const js = dshJsIn(path2.dirname(hit));
318
+ if (isFile(js)) return asJs(js, hit);
319
+ if (isFile(hit) && !/\.(cmd|bat|exe)$/i.test(hit)) return asJs(hit, hit);
320
+ return { runtime: null, bin: hit, isJs: false, launcher: hit };
428
321
  }
429
- function storeOf() {
430
- return entryOf() || fallbackEntryOf();
322
+ if (o.npmRoot) {
323
+ const js = dshJsIn(o.npmRoot);
324
+ if (isFile(js)) return asJs(js, null);
431
325
  }
432
- function fieldOf(name, v, write) {
433
- const e = storeOf();
434
- if (write) {
435
- if (e[name] !== v) {
436
- e[name] = v;
437
- persistCrashField();
438
- }
439
- return e;
326
+ return null;
327
+ }
328
+ function knownDshEntries(opts) {
329
+ const o = opts || {};
330
+ const out = [];
331
+ try {
332
+ const r = resolveDsh(o);
333
+ if (r) {
334
+ if (typeof r.bin === "string" && r.bin) out.push(r.bin);
335
+ if (typeof r.launcher === "string" && r.launcher) out.push(r.launcher);
440
336
  }
441
- return e[name];
337
+ } catch {
442
338
  }
443
- function procFieldOf(name, v, write) {
444
- const e = storeOf();
445
- let p = e.process;
446
- if (!p) {
447
- p = e.process = {
448
- child: null,
449
- adoptedPid: null,
450
- adopted: false,
451
- observedOnly: false,
452
- startDeadline: null,
453
- restartAt: null,
454
- spawnBlockedUntil: null,
455
- missingNotified: false,
456
- failStreak: 0,
457
- lastProbeAt: null,
458
- lastProbeOk: null,
459
- lastProbeHttpOk: null,
460
- lastFailure: null,
461
- lastRestartAt: null
462
- };
463
- }
464
- if (write) {
465
- if (p[name] !== v) p[name] = v;
466
- return p;
339
+ if (o.npmRoot) {
340
+ try {
341
+ out.push(dshJsIn(o.npmRoot));
342
+ } catch {
467
343
  }
468
- return p[name];
469
344
  }
470
- return { entryOf, fallbackEntryOf, persistCrashField, storeOf, fieldOf, procFieldOf };
471
- }
472
- module2.exports = { createMainRecord };
345
+ if (typeof o.dshBin === "string" && /^(?:[A-Za-z]:[\\/]|[\\/])/.test(o.dshBin)) out.push(o.dshBin);
346
+ return [...new Set(out.filter((x) => typeof x === "string" && x))];
347
+ }
348
+ function commandEntryViolation(cmdArr, opts) {
349
+ const o = opts || {};
350
+ const rp = o.realpath || ((p) => fs2.realpathSync(p));
351
+ if (!Array.isArray(cmdArr) || !cmdArr.length) return null;
352
+ const head = String(cmdArr[0] || "");
353
+ const NODE_HEAD = /* @__PURE__ */ new Set(["node", "node.exe"]);
354
+ const baseOf = (p) => String(p).split(/[\\/]/).pop().toLowerCase();
355
+ let entry = head;
356
+ const nodeHead = NODE_HEAD.has(baseOf(head));
357
+ if (nodeHead) {
358
+ if (cmdArr.length < 2) return "\u542F\u52A8\u547D\u4EE4\u4EE5 node \u6253\u5934\u4F46\u7F3A\u5C11 DSH \u5165\u53E3\u53C2\u6570";
359
+ entry = String(cmdArr[1] || "");
360
+ }
361
+ if (!entry) return "\u542F\u52A8\u547D\u4EE4\u7F3A\u5C11 DSH \u5165\u53E3";
362
+ const isAbsolute = (p) => /^(?:[A-Za-z]:[\\/]|[\\/])/.test(String(p));
363
+ const hasSep = (p) => /[\\/]/.test(String(p));
364
+ if (!hasSep(entry)) {
365
+ if (nodeHead && o.requireAbsoluteEntry) {
366
+ return "command[0] \u4E3A node \u65F6 command[1] \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u7684 DSH \u5165\u53E3\uFF08\u76F8\u5BF9/\u88F8\u540D\u4F1A\u6309\u5DE5\u4F5C\u76EE\u5F55\u6216 PATH \u89E3\u6790\uFF09";
367
+ }
368
+ return null;
369
+ }
370
+ if (!isAbsolute(entry)) {
371
+ return "DSH \u5165\u53E3\u4E0D\u63A5\u53D7\u76F8\u5BF9\u8DEF\u5F84\uFF08\u4F1A\u6309\u8C03\u7528\u65B9\u5DE5\u4F5C\u76EE\u5F55\u89E3\u6790\uFF1B\u6C99\u7BB1\u5B9E\u4F8B\u7684\u8BE5\u76EE\u5F55\u6C99\u7BB1\u5185\u53EF\u5199\uFF09";
372
+ }
373
+ let real = null;
374
+ try {
375
+ real = rp(entry);
376
+ } catch {
377
+ }
378
+ if (typeof o.allowEntry === "function") {
379
+ try {
380
+ if (o.allowEntry(entry, real)) return null;
381
+ } catch {
382
+ }
383
+ }
384
+ if (real === null) return "DSH \u5165\u53E3\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u89E3\u6790\uFF08fail-closed\uFF09\uFF1A" + entry;
385
+ for (const f of Array.isArray(o.files) ? o.files : []) {
386
+ try {
387
+ if (rp(f) === real) return null;
388
+ } catch {
389
+ }
390
+ }
391
+ for (const r of Array.isArray(o.roots) ? o.roots : []) {
392
+ let rr;
393
+ try {
394
+ rr = rp(r);
395
+ } catch {
396
+ continue;
397
+ }
398
+ const base = String(rr).replace(/[\\/]+$/, "");
399
+ if (real === base || real.indexOf(base + path2.sep) === 0) return null;
400
+ }
401
+ return "DSH \u5165\u53E3\u4E0D\u5728\u5141\u8BB8\u4F4D\u7F6E\uFF08\u987B\u4E3A\u8BE5\u5B9E\u4F8B\u5B89\u88C5\u6839\u4E4B\u4E0B\u7684\u5165\u53E3\uFF0C\u6216\u5185\u6838\u89E3\u6790\u51FA\u7684\u5DF2\u77E5 DSH \u5165\u53E3\uFF09\uFF1A" + entry;
402
+ }
403
+ module2.exports = {
404
+ resolveExecutable,
405
+ candidateNames,
406
+ standardDirs,
407
+ npmBin,
408
+ npxBin,
409
+ resolveDsh,
410
+ dshJsIn,
411
+ knownDshEntries,
412
+ commandEntryViolation,
413
+ isExecutableFile
414
+ };
473
415
  }
474
416
  });
475
417
 
476
- // src/platform/util/fs.js
477
- var require_fs = __commonJS({
478
- "src/platform/util/fs.js"(exports2, module2) {
418
+ // src/platform/os/pidlookup/probe.js
419
+ var require_probe = __commonJS({
420
+ "src/platform/os/pidlookup/probe.js"(exports2, module2) {
479
421
  "use strict";
480
422
  var fs2 = require("node:fs");
481
- var path2 = require("node:path");
482
- function dirSizeBytes(root) {
483
- let total = 0;
484
- let seen = 0;
485
- const MAX = 2e5;
486
- const walk = (dir) => {
487
- if (seen > MAX) return;
488
- let entries;
423
+ var ex2 = require_exec();
424
+ var { isExecutableFile } = require_exec_path();
425
+ var {
426
+ parseProcNetTcpInodes,
427
+ parseLsofPid,
428
+ parseNetstatPid,
429
+ parseSsPid,
430
+ parseWmicCommandLine,
431
+ parsePowerShellCommandLine
432
+ } = require_norm();
433
+ var isLinux = process.platform === "linux";
434
+ var isMac = process.platform === "darwin";
435
+ var isWindows = process.platform === "win32";
436
+ function linuxListeningInodes(port) {
437
+ const inodes = /* @__PURE__ */ new Set();
438
+ for (const f of ["/proc/net/tcp", "/proc/net/tcp6"]) {
439
+ let txt = "";
489
440
  try {
490
- entries = fs2.readdirSync(dir, { withFileTypes: true });
441
+ txt = fs2.readFileSync(f, "utf8");
491
442
  } catch {
492
- return;
443
+ continue;
493
444
  }
494
- for (const en of entries) {
495
- if (seen > MAX) return;
496
- const full = path2.join(dir, en.name);
497
- if (en.isSymbolicLink()) continue;
498
- if (en.isDirectory()) walk(full);
499
- else if (en.isFile()) {
445
+ for (const x of parseProcNetTcpInodes(txt, port)) inodes.add(x);
446
+ }
447
+ return inodes;
448
+ }
449
+ function linuxFind(port) {
450
+ try {
451
+ const inodes = linuxListeningInodes(port);
452
+ if (!inodes.size) return null;
453
+ const entries = fs2.readdirSync("/proc").filter((e) => /^\d+$/.test(e));
454
+ for (const pid of entries) {
455
+ let fds;
456
+ try {
457
+ fds = fs2.readdirSync("/proc/" + pid + "/fd");
458
+ } catch {
459
+ continue;
460
+ }
461
+ for (const fd of fds) {
462
+ let link;
500
463
  try {
501
- const st = fs2.statSync(full);
502
- total += st.size;
464
+ link = fs2.readlinkSync("/proc/" + pid + "/fd/" + fd);
503
465
  } catch {
466
+ continue;
504
467
  }
468
+ if (inodes.has(link)) return Number(pid);
505
469
  }
506
- seen++;
507
470
  }
508
- };
471
+ } catch {
472
+ }
473
+ return null;
474
+ }
475
+ function macFind(port) {
509
476
  try {
510
- walk(root);
477
+ const out = ex2.runOut("lsof", ["-nP", "-iTCP:" + port, "-sTCP:LISTEN"], { timeoutMs: 3e3 });
478
+ if (!out) return null;
479
+ return parseLsofPid(out);
511
480
  } catch {
512
481
  }
513
- return total;
482
+ return null;
514
483
  }
515
- function writeAtomic(file, data, opts) {
516
- const mode = opts && typeof opts.mode === "number" ? opts.mode : 384;
517
- const fp = path2.resolve(file);
518
- const dir = path2.dirname(fp);
519
- const tmp = fp + ".tmp." + process.pid + "." + Date.now();
484
+ function winFind(port) {
520
485
  try {
521
- if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
522
- fs2.writeFileSync(tmp, data, { mode });
523
- try {
524
- fs2.chmodSync(tmp, mode);
525
- } catch {
526
- }
527
- fs2.renameSync(tmp, fp);
486
+ const out = ex2.runOut("netstat", ["-ano"], { timeoutMs: 3e3 });
487
+ if (!out) return null;
488
+ return parseNetstatPid(out, port);
489
+ } catch {
490
+ }
491
+ return null;
492
+ }
493
+ function linuxFindSs(port) {
494
+ const candidates = ["ss", "/usr/sbin/ss", "/usr/bin/ss", "/bin/ss"];
495
+ for (const ssBin of candidates) {
496
+ if (ssBin.includes("/") && !isExecutableFile(ssBin)) continue;
528
497
  try {
529
- fs2.chmodSync(fp, mode);
498
+ const out = ex2.runOut(ssBin, ["-tlnHp", "sport = :" + port], { timeoutMs: 3e3 });
499
+ const pid = parseSsPid(out);
500
+ if (pid !== null) return pid;
530
501
  } catch {
531
502
  }
532
- return fp;
503
+ }
504
+ return null;
505
+ }
506
+ function isAlive(pid) {
507
+ if (!Number.isInteger(pid) || pid <= 0) return false;
508
+ try {
509
+ process.kill(pid, 0);
510
+ return true;
533
511
  } catch (e) {
512
+ return !!e && e.code === "EPERM";
513
+ }
514
+ }
515
+ function isZombie(pid) {
516
+ if (!Number.isInteger(pid) || pid <= 0 || isWindows) return false;
517
+ if (isLinux) {
534
518
  try {
535
- if (fs2.existsSync(tmp)) fs2.truncateSync(tmp, 0);
519
+ const st = fs2.readFileSync("/proc/" + pid + "/stat", "utf8");
520
+ const idx = st.lastIndexOf(") ");
521
+ return idx >= 0 && st[idx + 2] === "Z";
536
522
  } catch {
523
+ return false;
537
524
  }
538
- throw e;
525
+ }
526
+ try {
527
+ const o = ex2.runOut("ps", ["-o", "state=", "-p", String(pid)], { timeoutMs: 3e3 });
528
+ return !!o && /^Z/.test(o.trim());
529
+ } catch {
530
+ return false;
539
531
  }
540
532
  }
541
- module2.exports = { dirSizeBytes, writeAtomic };
542
- }
543
- });
544
-
545
- // src/app/state/main-store.js
546
- var require_main_store = __commonJS({
547
- "src/app/state/main-store.js"(exports2, module2) {
548
- "use strict";
549
- var fs2 = require("node:fs");
550
- var path2 = require("node:path");
551
- var { writeAtomic } = require_fs();
552
- function createMainStore(deps) {
553
- const g = deps || {};
554
- const config = () => typeof g.getConfig === "function" ? g.getConfig() || {} : {};
555
- const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
556
- let live = null;
557
- let corrupt = false;
558
- function dshMainFile() {
533
+ function readCmdline2(pid) {
534
+ if (isLinux) {
559
535
  try {
560
- return path2.join(path2.dirname(config().stateFile), "dsh-main.json");
536
+ const buf = fs2.readFileSync("/proc/" + pid + "/cmdline");
537
+ return buf.toString("utf8").replace(/\0/g, " ").trim();
561
538
  } catch {
562
539
  return null;
563
540
  }
564
541
  }
565
- function registryFileName() {
542
+ if (isMac) {
566
543
  try {
567
- const b = path2.basename(config().stateFile || "state.json", ".json");
568
- return b === "state" ? "managed-objects.json" : b + ".managed-objects.json";
544
+ const o = ex2.runOut("ps", ["-o", "command=", "-p", String(pid)], { timeoutMs: 3e3 });
545
+ return o ? o.trim() || null : null;
569
546
  } catch {
570
- return "managed-objects.json";
571
- }
572
- }
573
- function readDshMainFile() {
574
- try {
575
- const f = dshMainFile();
576
- if (f && fs2.existsSync(f)) {
577
- const j = JSON.parse(fs2.readFileSync(f, "utf8"));
578
- corrupt = false;
579
- return {
580
- guardian: j.guardian === true,
581
- remoteMode: legacyRemoteMode(j),
582
- remoteToken: String(j.remoteToken || "")
583
- };
584
- }
585
- } catch (e) {
586
- corrupt = true;
587
- const l = logger();
588
- if (l && l.warn) l.warn("dsh-main.json \u8BFB/\u89E3\u6790\u5931\u8D25\uFF0C\u5199\u56DE\u5C06\u88AB\u62D2\u7EDD\u76F4\u81F3\u663E\u5F0F\u91CD\u8BBE remoteToken: " + (e && e.message || e));
547
+ return null;
589
548
  }
590
- return { guardian: false, remoteMode: "off", remoteToken: "" };
591
549
  }
592
- function legacyRemoteMode(j) {
593
- if (j.remoteMode === "lan" || j.remoteMode === "wan") return j.remoteMode;
594
- if (j.remoteEnabled === true && j.frpEnabled === true) return "wan";
595
- if (j.remoteEnabled === true) return "lan";
596
- return "off";
597
- }
598
- function readDshMain() {
599
- if (live) return live;
600
- live = readDshMainFile();
601
- return live;
550
+ if (isWindows) {
551
+ const out = ex2.runOut("wmic", ["process", "where", "ProcessId=" + pid, "get", "CommandLine", "/value"], { timeoutMs: 5e3 });
552
+ const viaWmic = parseWmicCommandLine(out);
553
+ if (viaWmic) return viaWmic;
554
+ {
555
+ const ps = "(Get-CimInstance Win32_Process -Filter 'ProcessId=" + pid + "').CommandLine";
556
+ const o = ex2.runOut("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { timeoutMs: 5e3 });
557
+ return parsePowerShellCommandLine(o);
558
+ }
602
559
  }
603
- function writeDshMain(meta) {
604
- const m = meta || {};
605
- if (!live) live = readDshMainFile();
606
- if (corrupt && !(typeof m.remoteToken === "string" && m.remoteToken)) {
607
- const l = logger();
608
- if (l && l.warn) l.warn("_writeDshMain: \u6587\u4EF6\u635F\u574F\u6001\uFF0C\u62D2\u7EDD\u4EE5\u9ED8\u8BA4\u503C\u8986\u76D6\u5199\u56DE");
609
- return;
560
+ return null;
561
+ }
562
+ function pgrepList(pattern) {
563
+ const out = [];
564
+ const readCmd = (pid) => readCmdline2(pid) || "";
565
+ try {
566
+ if (isMac) {
567
+ const pids = (ex2.runOut("pgrep", ["-f", String(pattern)], { timeoutMs: 3e3 }) || "").split(/\r?\n/);
568
+ for (const line of pids) {
569
+ const pid = parseInt(line.trim(), 10);
570
+ if (!Number.isInteger(pid) || pid <= 0) continue;
571
+ const cmd2 = readCmd(pid);
572
+ if (!cmd2) continue;
573
+ out.push({ pid, cmdline: cmd2 });
574
+ }
575
+ return out;
610
576
  }
611
- corrupt = false;
612
- Object.assign(live, m);
613
- const f = dshMainFile();
614
- if (!f) return;
615
- try {
616
- const cur = readDshMain();
617
- const merged = Object.assign({}, cur, m);
618
- const dir = path2.dirname(f);
619
- fs2.mkdirSync(dir, { recursive: true });
620
- const body = JSON.stringify({
621
- guardian: merged.guardian === true,
622
- remoteMode: merged.remoteMode === "lan" || merged.remoteMode === "wan" ? merged.remoteMode : "off",
623
- remoteToken: String(merged.remoteToken || "")
624
- }, null, 2);
625
- writeAtomic(f, body, { mode: 384 });
626
- } catch (e) {
627
- const l = logger();
628
- if (l && l.warn) l.warn("_writeDshMain: " + (e && e.message || e));
577
+ if (isWindows) {
578
+ const ps = "Get-CimInstance Win32_Process | Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress";
579
+ const j = ex2.runOut("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { timeoutMs: 8e3 }) || "";
580
+ let arr = [];
581
+ try {
582
+ arr = JSON.parse(j);
583
+ if (!Array.isArray(arr)) arr = [arr];
584
+ } catch {
585
+ }
586
+ for (const it of arr) {
587
+ if (!it || !it.ProcessId) continue;
588
+ const pid = Number(it.ProcessId);
589
+ if (!Number.isInteger(pid) || pid <= 0) continue;
590
+ const cmd2 = String(it.CommandLine || "");
591
+ if (!cmd2.includes(pattern)) continue;
592
+ out.push({ pid, cmdline: cmd2 });
593
+ }
594
+ return out;
595
+ }
596
+ const res = ex2.runOut("pgrep", ["-af", String(pattern)], { timeoutMs: 3e3 }) || "";
597
+ for (const line of res.split(/\r?\n/)) {
598
+ const m = /^(\d+)\s+([\s\S]*)$/.exec(line.trim());
599
+ if (m) out.push({ pid: Number(m[1]), cmdline: m[2] });
629
600
  }
601
+ } catch {
630
602
  }
631
- return { dshMainFile, registryFileName, readDshMain, readDshMainFile, writeDshMain };
603
+ return out;
632
604
  }
633
- module2.exports = { createMainStore };
605
+ module2.exports = {
606
+ linuxListeningInodes,
607
+ linuxFind,
608
+ macFind,
609
+ winFind,
610
+ linuxFindSs,
611
+ readCmdline: readCmdline2,
612
+ pgrepList,
613
+ isAlive,
614
+ isZombie
615
+ };
634
616
  }
635
617
  });
636
618
 
637
- // src/app/state/field-tables.js
638
- var require_field_tables = __commonJS({
639
- "src/app/state/field-tables.js"(exports2, module2) {
619
+ // src/platform/os/pidlookup/index.js
620
+ var require_pidlookup = __commonJS({
621
+ "src/platform/os/pidlookup/index.js"(exports2, module2) {
640
622
  "use strict";
641
- var ENTRY_FIELDS = [
642
- // [读写 helper 后缀, entry 字段]
643
- ["CrashWindowStart", "crashWindowStart"],
644
- ["CrashWindowRestarts", "crashWindowRestarts"],
645
- ["BackoffLevel", "backoffLevel"],
646
- ["BackoffUntil", "backoffUntil"],
647
- ["RestartCount", "restartCount"]
648
- ];
649
- var PROC_FIELDS = [
650
- // [读写 helper 后缀, process 字段, 是否布尔]
651
- ["Child", "child", false],
652
- ["AdoptPid", "adoptedPid", false],
653
- ["Adopted", "adopted", true],
654
- ["ObservedOnly", "observedOnly", true],
655
- ["FailStreak", "failStreak", false],
656
- ["RestartAt", "restartAt", false],
657
- ["StartDeadline", "startDeadline", false],
658
- ["SpawnBlockedUntil", "spawnBlockedUntil", false],
659
- ["MissingNotified", "missingNotified", true],
660
- ["LastProbeAt", "lastProbeAt", false],
661
- ["LastProbeOk", "lastProbeOk", false],
662
- ["LastProbeHttpOk", "lastProbeHttpOk", false],
663
- ["LastFailure", "lastFailure", false],
664
- ["LastRestartAt", "lastRestartAt", false]
665
- ];
666
- module2.exports = { ENTRY_FIELDS, PROC_FIELDS };
623
+ var {
624
+ parseProcNetTcpInodes,
625
+ parseLsofPid,
626
+ parseNetstatPid,
627
+ parseSsPid,
628
+ parseWmicCommandLine,
629
+ parsePowerShellCommandLine,
630
+ normCmdline
631
+ } = require_norm();
632
+ var {
633
+ linuxFind,
634
+ linuxFindSs,
635
+ macFind,
636
+ winFind,
637
+ readCmdline: readCmdline2,
638
+ pgrepList,
639
+ isAlive,
640
+ isZombie
641
+ } = require_probe();
642
+ var isLinux = process.platform === "linux";
643
+ var isMac = process.platform === "darwin";
644
+ function findListeningPid(port) {
645
+ if (!Number.isInteger(port) || port <= 0) return null;
646
+ if (isLinux) {
647
+ const a = linuxFind(port);
648
+ if (a !== null && a !== void 0) return a;
649
+ return linuxFindSs(port);
650
+ }
651
+ if (isMac) return macFind(port);
652
+ return winFind(port);
653
+ }
654
+ function isDshCmdline(pid) {
655
+ const cmd2 = readCmdline2(pid);
656
+ if (!cmd2) return false;
657
+ return /(^|\s)(node|.*dsh.*)(\s|$)/i.test(cmd2) && /dsh/i.test(cmd2);
658
+ }
659
+ module2.exports = {
660
+ findListeningPid,
661
+ isAlive,
662
+ isZombie,
663
+ readCmdline: readCmdline2,
664
+ normCmdline,
665
+ isDshCmdline,
666
+ pgrepList,
667
+ parseProcNetTcpInodes,
668
+ parseLsofPid,
669
+ parseNetstatPid,
670
+ parseSsPid,
671
+ parseWmicCommandLine,
672
+ parsePowerShellCommandLine
673
+ };
667
674
  }
668
675
  });
669
676
 
670
- // src/app/state/phase.js
671
- var require_phase = __commonJS({
672
- "src/app/state/phase.js"(exports2, module2) {
677
+ // src/platform/service/state-root.js
678
+ var require_state_root = __commonJS({
679
+ "src/platform/service/state-root.js"(exports2, module2) {
673
680
  "use strict";
674
- function legacyToEntryPhase(ph) {
675
- return { STOPPED: "stopped", STARTING: "starting", RUNNING: "running", RESTARTING: "restarting", BACKOFF: "backoff", OBSERVED: "stopped" }[ph] || "stopped";
681
+ var fs2 = require("node:fs");
682
+ var os2 = require("node:os");
683
+ var path2 = require("node:path");
684
+ var SCHEMA = 1;
685
+ function root() {
686
+ const override = process.env.DSH_SUPERVISOR_HOME;
687
+ if (override && String(override).trim()) return path2.resolve(String(override).trim());
688
+ if (process.platform === "win32") {
689
+ const local = process.env.LOCALAPPDATA || path2.join(os2.homedir(), "AppData", "Local");
690
+ return path2.join(local, "dsh-supervisor");
691
+ }
692
+ if (process.platform === "darwin") {
693
+ return path2.join(os2.homedir(), "Library", "Application Support", "dsh-supervisor");
694
+ }
695
+ const xdg = process.env.XDG_STATE_HOME;
696
+ return xdg && String(xdg).trim() ? path2.join(String(xdg).trim(), "dsh-supervisor") : path2.join(os2.homedir(), ".local", "state", "dsh-supervisor");
676
697
  }
677
- function entryToLegacyPhase(ph) {
678
- return { stopped: "STOPPED", starting: "STARTING", running: "RUNNING", restarting: "RESTARTING", backoff: "BACKOFF" }[ph] || "STOPPED";
698
+ function supervisorDir() {
699
+ return path2.join(root(), "supervisor");
679
700
  }
680
- module2.exports = { legacyToEntryPhase, entryToLegacyPhase };
701
+ function shellDir() {
702
+ return path2.join(root(), "shell");
703
+ }
704
+ function legacySupervisorDir() {
705
+ return path2.join(os2.homedir(), ".dsh", "supervisor");
706
+ }
707
+ function legacyShellDir() {
708
+ return path2.join(os2.homedir(), ".dsh", "shell");
709
+ }
710
+ function migrateLegacy() {
711
+ const moved = [];
712
+ for (const [from, to] of [
713
+ [legacySupervisorDir(), supervisorDir()],
714
+ [legacyShellDir(), shellDir()]
715
+ ]) {
716
+ try {
717
+ if (!fs2.existsSync(from)) continue;
718
+ fs2.mkdirSync(to, { recursive: true });
719
+ for (const name of fs2.readdirSync(from)) {
720
+ const src = path2.join(from, name);
721
+ const dst = path2.join(to, name);
722
+ if (fs2.existsSync(dst)) continue;
723
+ try {
724
+ fs2.renameSync(src, dst);
725
+ moved.push(src + " -> " + dst);
726
+ } catch {
727
+ }
728
+ }
729
+ try {
730
+ if (fs2.readdirSync(from).length === 0) fs2.rmdirSync(from);
731
+ } catch {
732
+ }
733
+ } catch {
734
+ }
735
+ }
736
+ return moved;
737
+ }
738
+ module2.exports = { SCHEMA, root, supervisorDir, shellDir, migrateLegacy };
681
739
  }
682
740
  });
683
741
 
684
- // src/app/state/fields.js
685
- var require_fields = __commonJS({
686
- "src/app/state/fields.js"(exports2, module2) {
742
+ // src/platform/service/config.js
743
+ var require_config = __commonJS({
744
+ "src/platform/service/config.js"(exports2, module2) {
687
745
  "use strict";
688
- var { ENTRY_FIELDS, PROC_FIELDS } = require_field_tables();
689
- var { legacyToEntryPhase, entryToLegacyPhase } = require_phase();
690
- function createFields(deps) {
691
- const g = deps || {};
692
- const record = g.record;
693
- const mainStore = g.mainStore;
694
- const reg = () => typeof g.getManagedObjects === "function" ? g.getManagedObjects() : null;
695
- const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
696
- function toEntry(ph) {
697
- return legacyToEntryPhase(ph);
698
- }
699
- function toLegacy(ph) {
700
- return entryToLegacyPhase(ph);
701
- }
702
- function phase() {
703
- const e = record.storeOf();
704
- const p = e.process || null;
705
- const upper = entryToLegacyPhase(e.phase || "stopped");
706
- if (upper === "STOPPED" && p && p.observedOnly && p.adopted) return "OBSERVED";
707
- return upper;
708
- }
709
- function setPhase(upper) {
710
- const e = record.storeOf();
711
- const ph = legacyToEntryPhase(upper);
712
- const m = reg();
713
- try {
714
- if (m && typeof m.setPhase === "function" && record.entryOf() === e) {
715
- if (e.phase !== ph) m.setPhase("main", ph);
716
- } else if (e.phase !== ph) {
717
- e.phase = ph;
746
+ var os2 = require("node:os");
747
+ var path2 = require("node:path");
748
+ function expandHome(p) {
749
+ if (typeof p !== "string") return p;
750
+ if (p === "~") return os2.homedir();
751
+ if (p.startsWith("~/")) return path2.join(os2.homedir(), p.slice(2));
752
+ return p;
753
+ }
754
+ var SUP = require_state_root().supervisorDir();
755
+ var NO_EXTENSION = Object.freeze({ defaults: [], aliases: [] });
756
+ function normalizeExtension(ext) {
757
+ if (!ext || typeof ext !== "object") return NO_EXTENSION;
758
+ return {
759
+ defaults: Array.isArray(ext.defaults) ? ext.defaults : [],
760
+ aliases: Array.isArray(ext.aliases) ? ext.aliases : []
761
+ };
762
+ }
763
+ var BASE_DEFAULTS = {
764
+ probeIntervalMs: 5e3,
765
+ // 健康探测三层:L0 进程存活 + L1 端口监听 + L2 HTTP GET healthUrl。probeTimeoutMs = 单次探测超时;
766
+ // failThreshold = 连续失败判故障(防抖动);httpProbeEnabled=false 退化为端口在线即健康。
767
+ probeTimeoutMs: 3e3,
768
+ failThreshold: 2,
769
+ httpProbeEnabled: true,
770
+ startTimeoutMs: 3e4,
771
+ stopGraceMs: 1e4,
772
+ portReleaseWaitMs: 1e4,
773
+ crashWindowMs: 6e5,
774
+ crashBurst: 5,
775
+ backoff: [3e4, 6e4, 12e4, 3e5, 6e5],
776
+ apiHost: "127.0.0.1",
777
+ // API 端口:高位不常用段起始(常用端口段易冲突);守卫启动被占则自动顺延并持久化。
778
+ apiPort: 36360,
779
+ // 注意:daemon 控制通道端口属业务域知识,经注入声明提供(同上),原键名与值逐字保留。
780
+ // 动态端口池(范围是配置项,非编译期常量):null = 内置默认池,默认避开 OS 动态端口范围
781
+ // (Linux ip_local_port_range=32768-60999),落在 IANA User 段低位。额外池由域装配期申报(DS-G4)。
782
+ portPools: null,
783
+ stateFile: path2.join(SUP, "state.json"),
784
+ // 日志目录布局:事件在 events/guard.events.log,分级日志在 log/;显式配置的路径尊重用户给定值,不强改。
785
+ logFile: path2.join(SUP, "events", "guard.events.log"),
786
+ eventsMaxBytes: 5 * 1024 * 1024,
787
+ supervisorLogFile: path2.join(SUP, "log", "guard.log"),
788
+ dshLogFile: path2.join(SUP, "log", "dsh.log"),
789
+ upgradeLogFile: path2.join(SUP, "log", "upgrade.log"),
790
+ logLevel: "info",
791
+ logMaxBytes: 5 * 1024 * 1024,
792
+ notifyEnabled: true,
793
+ // 内核更新单写入者 = 桌面壳:corePackageName 仅为内核自身 npm 子包名,守卫只读它查版本状态,
794
+ // 安装/升级由壳执行。不得再引入 manifest 更新通道(残留键已删,config.json 中未知键加载时忽略)。
795
+ corePackageName: null,
796
+ pluginsProfileName: "web",
797
+ packageName: "@deepseek-ai/dsh",
798
+ // 灰度名单(RELEASE-CHANNEL-CONTRACT):本机 canary:true 即灰度机,仅对内核包生效,对第三方包
799
+ // (packageName)无效;未置真即非灰度。
800
+ canary: false,
801
+ // 最小兜底镜像源(不变量 C2):完整目录归壳管理,经 <产品状态根>/supervisor/registry.json 投放,
802
+ // 内核由 platform/distribution 读取(契约见 platform/contract/registry.js);
803
+ // 此处官方源 + npmmirror 两条兜底,保证契约不可用时仍可安装。
804
+ registries: [
805
+ "https://registry.npmjs.org",
806
+ "https://registry.npmmirror.com"
807
+ ],
808
+ updateCheckEnabled: true,
809
+ updateCheckIntervalMs: 36e5,
810
+ initialCheckDelayMs: 2e4,
811
+ upgradeTimeoutMs: 6e5,
812
+ installCommandTemplate: ["npm", "install", "-g", "{pkg}@{version}"],
813
+ // apiAccessKey(可选)出回环访问密钥:配置后,0.0.0.0(局域网)与 FRP 公网通道的 API 请求必须携带
814
+ // Authorization: Bearer <key> 或 ?access_key=<key>,否则 401;回环豁免。
815
+ // 不配置 = LAN 受 RFC1918 白名单约束,FRP 暴露仍强制 remoteToken。
816
+ apiAccessKey: null,
817
+ // 关闭窗口行为(系统级):'hide' = 隐藏至托盘、服务常驻(默认);'exit' = 退出管家并停全部服务链。
818
+ closeAction: "hide"
819
+ };
820
+ function buildDefaults2(ext) {
821
+ const pending = normalizeExtension(ext).defaults.slice();
822
+ const out = {};
823
+ for (const key of Object.keys(BASE_DEFAULTS)) {
824
+ for (let i = pending.length - 1; i >= 0; i--) {
825
+ const g = pending[i];
826
+ if (g && g.at === key) {
827
+ if (g.values && typeof g.values === "object") Object.assign(out, g.values);
828
+ pending.splice(i, 1);
718
829
  }
719
- } catch (e2) {
720
- const l = logger();
721
- if (l && l.warn) l.warn("_mSetPhase: " + (e2 && e2.message || e2));
722
830
  }
831
+ out[key] = BASE_DEFAULTS[key];
723
832
  }
724
- function guardian() {
725
- try {
726
- return mainStore.readDshMain().guardian === true;
727
- } catch {
728
- return false;
729
- }
833
+ for (const g of pending) {
834
+ if (g && g.values && typeof g.values === "object") Object.assign(out, g.values);
730
835
  }
731
- function mainGuardian() {
732
- return guardian();
836
+ return out;
837
+ }
838
+ var DEFAULTS = buildDefaults2(null);
839
+ function normalize(raw, ext) {
840
+ const extension = normalizeExtension(ext);
841
+ const provided = raw || {};
842
+ const cfg = Object.assign(buildDefaults2(extension), provided);
843
+ cfg.stateFile = expandHome(cfg.stateFile);
844
+ cfg.logFile = expandHome(cfg.logFile);
845
+ cfg.supervisorLogFile = expandHome(cfg.supervisorLogFile);
846
+ cfg.dshLogFile = expandHome(cfg.dshLogFile);
847
+ cfg.upgradeLogFile = expandHome(cfg.upgradeLogFile);
848
+ let u;
849
+ try {
850
+ u = new URL(cfg.healthUrl);
851
+ } catch {
852
+ throw new Error("config.healthUrl \u65E0\u6548: " + JSON.stringify(cfg.healthUrl));
733
853
  }
734
- function desired() {
735
- return record.storeOf().desired === "stopped" ? "stopped" : "running";
854
+ cfg.targetHost = u.hostname;
855
+ cfg.targetPort = Number(u.port || (u.protocol === "https:" ? 443 : 80));
856
+ for (const [from, to] of extension.aliases) {
857
+ if (provided[to] === void 0 && provided[from] !== void 0) cfg[to] = provided[from] === true;
736
858
  }
737
- function setDesired(v) {
738
- const want = v === "stopped" ? "stopped" : "running";
739
- const e = record.storeOf();
740
- const m = reg();
741
- try {
742
- if (m && typeof m.update === "function" && record.entryOf() === e) {
743
- if (e.desired !== want) m.update("main", { desired: want });
744
- } else if (e.desired !== want) {
745
- e.desired = want;
746
- }
747
- } catch (e2) {
748
- const l = logger();
749
- if (l && l.warn) l.warn("_mSetDesired: " + (e2 && e2.message || e2));
750
- }
859
+ const cmdPort = extractPortFromCommand(cfg.command);
860
+ if (cmdPort !== null) cfg.targetPort = cmdPort;
861
+ cfg.probeTimeoutMs = Number.isFinite(Number(cfg.probeTimeoutMs)) && Number(cfg.probeTimeoutMs) > 0 ? Number(cfg.probeTimeoutMs) : 3e3;
862
+ cfg.failThreshold = Number.isInteger(Number(cfg.failThreshold)) && Number(cfg.failThreshold) >= 1 ? Number(cfg.failThreshold) : 2;
863
+ cfg.httpProbeEnabled = cfg.httpProbeEnabled !== false;
864
+ if (!Array.isArray(cfg.command) || cfg.command.length === 0) {
865
+ throw new Error("config.command \u7F3A\u5931\uFF1A\u9700\u8981\u4E00\u4E2A\u547D\u4EE4\u6570\u7EC4");
751
866
  }
752
- function field(name, v) {
753
- return arguments.length >= 2 ? record.fieldOf(name, v, true) : record.fieldOf(name, void 0, false);
867
+ return cfg;
868
+ }
869
+ function extractPortFromCommand(command) {
870
+ if (!Array.isArray(command)) return null;
871
+ for (let i = 0; i < command.length; i++) {
872
+ const a = String(command[i]);
873
+ if ((a === "--port" || a === "-p") && i + 1 < command.length) {
874
+ const n = Number(command[i + 1]);
875
+ if (Number.isInteger(n) && n > 0 && n <= 65535) return n;
876
+ }
877
+ const m = /^--port=(\d+)$/.exec(a);
878
+ if (m) {
879
+ const n = Number(m[1]);
880
+ if (Number.isInteger(n) && n > 0 && n <= 65535) return n;
881
+ }
754
882
  }
755
- function procField(name, v) {
756
- return arguments.length >= 2 ? record.procFieldOf(name, v, true) : record.procFieldOf(name, void 0, false);
883
+ return null;
884
+ }
885
+ module2.exports = { DEFAULTS, BASE_DEFAULTS, buildDefaults: buildDefaults2, normalize, extractPortFromCommand };
886
+ }
887
+ });
888
+
889
+ // src/app/settings/domain-config.js
890
+ var require_domain_config = __commonJS({
891
+ "src/app/settings/domain-config.js"(exports2, module2) {
892
+ "use strict";
893
+ var defaults = [
894
+ {
895
+ at: "portPools",
896
+ // daemon 控制通道端口:集中定义,杜绝散落硬编码。
897
+ // 这两个值同时是 app/ctl/client.js 与两个 daemon 的兜底端口,不得单独改动
898
+ // (改动即需同步 8 处 43107/43108 兜底常量)。
899
+ values: {
900
+ routerCtlPort: 43107,
901
+ lanCtlPort: 43108
902
+ }
903
+ },
904
+ {
905
+ at: "corePackageName",
906
+ // 智能路由启动开关。
907
+ values: {
908
+ routerAutostart: false
909
+ }
757
910
  }
758
- const getProc = (n) => record.procFieldOf(n);
759
- const setProc = (n, v) => {
760
- record.procFieldOf(n, v, true);
761
- };
762
- const getEntry = (n) => record.fieldOf(n);
763
- const setEntry = (n, v) => {
764
- record.fieldOf(n, v, true);
765
- };
766
- const accessors = {
767
- phase: { get: () => phase(), set: (v) => {
768
- setPhase(v);
769
- } },
770
- desired: { get: () => desired(), set: (v) => {
771
- setDesired(v);
772
- } },
773
- child: { get: () => getProc("child"), set: (c) => {
774
- setProc("child", c);
775
- } },
776
- adoptedPid: { get: () => getProc("adoptedPid"), set: (v) => {
777
- setProc("adoptedPid", v);
778
- } },
779
- adopted: { get: () => getProc("adopted") === true, set: (v) => {
780
- setProc("adopted", v === true);
781
- } },
782
- observedOnly: { get: () => getProc("observedOnly") === true, set: (v) => {
783
- setProc("observedOnly", v === true);
784
- } },
785
- restartCount: { get: () => {
786
- const v = getEntry("restartCount");
787
- return typeof v === "number" ? v : 0;
788
- }, set: (v) => {
789
- setEntry("restartCount", v);
790
- } },
791
- spawnBlockedUntil: { get: () => {
792
- const v = getProc("spawnBlockedUntil");
793
- return v === void 0 ? null : v;
794
- }, set: (v) => {
795
- setProc("spawnBlockedUntil", v);
796
- } },
797
- missingNotified: { get: () => getProc("missingNotified") === true, set: (v) => {
798
- setProc("missingNotified", v === true);
799
- } }
800
- };
911
+ ];
912
+ var aliases = [
913
+ ["switcherAutoStart", "routerAutostart"]
914
+ ];
915
+ function extension() {
801
916
  return {
802
- phase,
803
- setPhase,
804
- guardian,
805
- mainGuardian,
806
- desired,
807
- setDesired,
808
- field,
809
- procField,
810
- accessors,
811
- legacyToEntryPhase: toEntry,
812
- entryToLegacyPhase: toLegacy,
813
- child: () => getProc("child"),
814
- adoptPid: () => getProc("adoptedPid"),
815
- observedOnly: () => getProc("observedOnly") === true,
816
- setObservedOnly: (v) => {
817
- setProc("observedOnly", v === true);
818
- },
819
- ENTRY_FIELDS,
820
- PROC_FIELDS
917
+ defaults: defaults.map((g) => ({ at: g.at, values: Object.assign({}, g.values) })),
918
+ aliases: aliases.map((a) => a.slice())
821
919
  };
822
920
  }
823
- module2.exports = { createFields };
921
+ module2.exports = { extension, defaults, aliases };
824
922
  }
825
923
  });
826
924
 
827
- // src/app/state/store.js
828
- var require_store = __commonJS({
829
- "src/app/state/store.js"(exports2, module2) {
925
+ // src/platform/service/version.js
926
+ var require_version = __commonJS({
927
+ "src/platform/service/version.js"(exports2, module2) {
830
928
  "use strict";
831
929
  var fs2 = require("node:fs");
832
930
  var path2 = require("node:path");
833
- var { writeAtomic } = require_fs();
834
- function createStore(deps) {
835
- const g = deps || {};
836
- const record = g.record;
837
- const fields = g.fields;
838
- const mainStore = g.mainStore;
839
- const upgradeHold = g.upgradeHold;
840
- const config = () => typeof g.getConfig === "function" ? g.getConfig() || {} : {};
841
- const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
842
- const events = () => typeof g.getEvents === "function" ? g.getEvents() : null;
843
- const instances = () => typeof g.getInstances === "function" ? g.getInstances() : null;
844
- const views = () => typeof g.getViews === "function" ? g.getViews() : null;
845
- const reg = () => typeof g.getManagedObjects === "function" ? g.getManagedObjects() : null;
846
- let lastStateBody = null;
847
- function writeState(force) {
848
- try {
849
- const snap = views().status();
850
- const updatedAt = snap.updatedAt;
851
- snap.updatedAt = null;
852
- const body = JSON.stringify(snap, null, 2);
853
- if (!force && body === lastStateBody) return;
854
- lastStateBody = body;
855
- snap.updatedAt = updatedAt;
856
- const dir = path2.dirname(config().stateFile);
857
- fs2.mkdirSync(dir, { recursive: true });
858
- writeAtomic(config().stateFile, JSON.stringify(snap, null, 2), { mode: 384 });
859
- } catch (e) {
860
- const l = logger();
861
- if (l && l.error) l.error("state write failed: " + e.message);
862
- }
863
- }
864
- function loadState() {
865
- let raw = null;
866
- try {
867
- raw = JSON.parse(fs2.readFileSync(config().stateFile, "utf8"));
868
- } catch (e) {
869
- if (!e || e.code !== "ENOENT") {
870
- const l = logger();
871
- if (l && l.warn) l.warn("state load failed\uFF08\u6309\u7A7A\u72B6\u6001\u7EE7\u7EED\uFF09: " + (e && e.message || e));
872
- }
873
- }
874
- if (raw && typeof raw === "object") try {
875
- if (raw.desired === "stopped" || raw.desired === "running") {
876
- const m = reg();
877
- const registryHasSource = !!(m && m._loadedFromDisk);
878
- if (!registryHasSource) fields.setDesired(raw.desired);
879
- }
880
- if (typeof raw.restartCount === "number") record.fieldOf("restartCount", raw.restartCount, true);
881
- if (typeof raw.backoffLevel === "number") record.fieldOf("backoffLevel", raw.backoffLevel, true);
882
- if (typeof raw.crashWindowStart === "number" || raw.crashWindowStart === null) record.fieldOf("crashWindowStart", raw.crashWindowStart, true);
883
- if (typeof raw.crashWindowRestarts === "number") record.fieldOf("crashWindowRestarts", raw.crashWindowRestarts, true);
884
- if (typeof raw.lastFailure === "string" || raw.lastFailure === null) record.procFieldOf("lastFailure", raw.lastFailure, true);
885
- if (typeof raw.lastRestartAt === "string" || raw.lastRestartAt === null) record.procFieldOf("lastRestartAt", raw.lastRestartAt, true);
886
- if (raw.upgradeHold === true) upgradeHold.enter();
887
- if (raw.shellHalted === true && typeof g.setShellHalted === "function") g.setShellHalted(true);
888
- } catch (e) {
889
- const l = logger();
890
- if (l && l.warn) l.warn("state restore partial\uFF08\u5B57\u6BB5\u7EA7\u8DF3\u8FC7\uFF0Cboot \u4ECD\u7EE7\u7EED\uFF09: " + (e && e.message || e));
891
- }
892
- try {
893
- fields.setPhase("STOPPED");
894
- } catch {
895
- }
896
- }
897
- function migrateMainRecord() {
898
- try {
899
- const im = instances();
900
- if (!im || !Array.isArray(im.instances)) return;
901
- const idx = im.instances.findIndex((i) => i.id === "main");
902
- if (idx < 0) return;
903
- const main = im.instances[idx];
904
- const f = mainStore.dshMainFile();
905
- if (f && !fs2.existsSync(f)) {
906
- mainStore.writeDshMain({
907
- guardian: main.guardian === true,
908
- remoteMode: main.remoteEnabled === true ? main.frpEnabled === true ? "wan" : "lan" : "off",
909
- remoteToken: String(main.remoteToken || "")
910
- });
911
- }
912
- im.instances.splice(idx, 1);
913
- if (im.save) {
914
- try {
915
- im.save();
916
- } catch {
917
- }
918
- }
919
- const l = logger();
920
- if (l && l.info) l.info("[main] \u6982\u5FF5\u6E05\u5206\uFF1Amain \u8BB0\u5F55\u5DF2\u8FC1\u51FA instances.json \u2192 dsh-main.json");
921
- const ev = events();
922
- if (ev) ev.append("main_meta_migrated", {});
923
- } catch (e) {
924
- const l = logger();
925
- if (l && l.warn) l.warn("_migrateMainRecord: " + (e && e.message));
926
- }
931
+ function guardVersion() {
932
+ if (true) return String("0.1.6-BETA.3");
933
+ try {
934
+ return JSON.parse(fs2.readFileSync(path2.join(__dirname, "..", "..", "package.json"), "utf8")).version || "unknown";
935
+ } catch {
936
+ return "unknown";
927
937
  }
928
- return { writeState, loadState, migrateMainRecord };
929
938
  }
930
- module2.exports = { createStore };
939
+ module2.exports = { guardVersion };
931
940
  }
932
941
  });
933
942
 
934
- // src/app/state/desired.js
935
- var require_desired = __commonJS({
936
- "src/app/state/desired.js"(exports2, module2) {
943
+ // src/app/state/main-record.js
944
+ var require_main_record = __commonJS({
945
+ "src/app/state/main-record.js"(exports2, module2) {
937
946
  "use strict";
938
- var fs2 = require("node:fs");
939
- var { writeAtomic } = require_fs();
940
- function createDesired(deps) {
947
+ function createMainRecord(deps) {
941
948
  const g = deps || {};
942
- const fields = g.fields;
943
- const store = g.store;
944
- const intents = () => typeof g.getIntents === "function" ? g.getIntents() : null;
945
- const events = () => typeof g.getEvents === "function" ? g.getEvents() : null;
946
- const configPath = () => typeof g.getConfigPath === "function" ? g.getConfigPath() : null;
949
+ const reg = () => typeof g.getManagedObjects === "function" ? g.getManagedObjects() : null;
947
950
  const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
948
- const setCrashHalted = typeof g.setCrashHalted === "function" ? g.setCrashHalted : () => {
949
- };
950
- const setManualRestart = typeof g.setManualRestart === "function" ? g.setManualRestart : () => {
951
- };
952
- const tick = () => {
953
- if (typeof g.tick === "function") g.tick();
954
- };
955
- const stopProcess = (why) => {
956
- if (typeof g.stopProcess === "function") g.stopProcess(why);
957
- };
958
- function setDesired(v) {
959
- if (v !== "running" && v !== "stopped") return { error: "invalid desired" };
960
- if (v === "running") {
961
- const it = intents();
962
- if (it) it.register("start");
963
- setCrashHalted(false);
964
- }
965
- if (v === "running" && fields.phase() === "OBSERVED") {
966
- fields.setObservedOnly(false);
967
- fields.setPhase("STOPPED");
968
- }
969
- if (v === "stopped" && fields.phase() === "OBSERVED" && fields.observedOnly()) {
970
- stopProcess("desired_stopped");
971
- }
972
- if (fields.desired() !== v) {
973
- fields.setDesired(v);
974
- const ev = events();
975
- if (ev) ev.append("desired_changed", { desired: v });
976
- store.writeState();
977
- }
978
- tick();
979
- return { ok: true, desired: fields.desired() };
980
- }
981
- function requestRestart() {
982
- if (fields.desired() === "stopped") {
983
- const ev2 = events();
984
- if (ev2) ev2.append("manual_restart_requested", { ignored: "desired=stopped" });
985
- return { ok: false, error: "desired=stopped\uFF0C\u8BF7\u5148 /start" };
986
- }
987
- setManualRestart(true);
988
- setCrashHalted(false);
989
- const it = intents();
990
- if (it) it.register("restart");
991
- const ev = events();
992
- if (ev) ev.append("manual_restart_requested", {});
993
- tick();
994
- return { ok: true };
995
- }
996
- function persistConfigPatch(patch) {
997
- const p = configPath();
998
- if (!p) return false;
999
- const abort = (m) => {
1000
- const l = logger();
1001
- if (l && l.warn) l.warn("config persist aborted (fail-closed): " + m);
1002
- const ev = events();
1003
- if (ev) {
1004
- try {
1005
- ev.append("config_persist_aborted", { reason: m });
1006
- } catch {
1007
- }
1008
- }
1009
- return false;
1010
- };
951
+ let fallback = null;
952
+ function entryOf() {
953
+ const m = reg();
954
+ if (!m || typeof m.get !== "function") return null;
1011
955
  try {
1012
- let cur = {};
1013
- let raw = null;
1014
- try {
1015
- raw = fs2.readFileSync(p, "utf8");
1016
- } catch (e) {
1017
- if (!e || e.code !== "ENOENT") return abort("read failed: " + (e && e.message || e));
1018
- }
1019
- if (raw !== null) {
1020
- try {
1021
- cur = JSON.parse(raw);
1022
- if (!cur || typeof cur !== "object" || Array.isArray(cur)) throw new Error("root is not an object");
1023
- } catch (e) {
1024
- return abort("parse failed, original bytes preserved: " + (e && e.message || e));
1025
- }
1026
- }
1027
- Object.assign(cur, patch);
1028
- delete cur.switcherAutoStart;
1029
- writeAtomic(p, JSON.stringify(cur, null, 2), { mode: 384 });
1030
- return true;
1031
- } catch (e) {
1032
- const l = logger();
1033
- if (l && l.warn) l.warn("config persist failed: " + e.message);
1034
- return false;
956
+ return m.get("main") || null;
957
+ } catch {
958
+ return null;
1035
959
  }
1036
960
  }
1037
- return { setDesired, requestRestart, persistConfigPatch };
1038
- }
1039
- module2.exports = { createDesired };
1040
- }
1041
- });
1042
-
1043
- // src/platform/os/pidlookup/norm.js
1044
- var require_norm = __commonJS({
1045
- "src/platform/os/pidlookup/norm.js"(exports2, module2) {
1046
- "use strict";
1047
- function parseProcNetTcpInodes(txt, port) {
1048
- const inodes = /* @__PURE__ */ new Set();
1049
- for (const lineRaw of String(txt || "").split("\n")) {
1050
- const cols = lineRaw.trim().split(/\s+/);
1051
- if (cols.length < 10) continue;
1052
- const local = cols[1];
1053
- const st = cols[3];
1054
- const inode = cols[9];
1055
- if (!local || !inode) continue;
1056
- const p = local.split(":")[1];
1057
- if (st === "0A" && p && parseInt(p, 16) === port) inodes.add("socket:[" + inode + "]");
961
+ function fallbackEntryOf() {
962
+ if (!fallback) {
963
+ fallback = {
964
+ kind: "dsh",
965
+ id: "main",
966
+ name: "\u4E3B\u5B9E\u4F8B",
967
+ desired: "running",
968
+ guardian: true,
969
+ ownership: { ports: [], rootPath: null, unit: null, daemonScript: null, processMode: "spawn", meta: null },
970
+ phase: "stopped",
971
+ lastObserved: null,
972
+ backoffLevel: 0,
973
+ backoffUntil: null,
974
+ crashWindowStart: null,
975
+ crashWindowRestarts: 0,
976
+ restartCount: 0,
977
+ startedAt: null,
978
+ lastTransitionAt: null,
979
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
980
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
981
+ process: null
982
+ };
983
+ }
984
+ return fallback;
1058
985
  }
1059
- return inodes;
1060
- }
1061
- function parseLsofPid(out) {
1062
- for (const line of String(out || "").split("\n")) {
1063
- const m = line.trim().split(/\s+/);
1064
- if (m.length >= 2 && /^\d+$/.test(m[1])) return Number(m[1]);
986
+ function persistCrashField() {
987
+ const m = reg();
988
+ try {
989
+ if (m && typeof m.persistCrashState === "function") m.persistCrashState();
990
+ else if (m && typeof m._save === "function") m._save();
991
+ } catch (e) {
992
+ const l = logger();
993
+ if (l && l.warn) l.warn("persistCrashField: " + (e && e.message || e));
994
+ }
1065
995
  }
1066
- return null;
1067
- }
1068
- function parseNetstatPid(out, port) {
1069
- const want = String(port);
1070
- for (const line of String(out || "").split("\n")) {
1071
- const parts = line.trim().split(/\s+/);
1072
- if (parts.length >= 5 && (parts[0] === "TCP" || parts[0] === "TCPv6") && parts[3] === "LISTENING") {
1073
- const lp = parts[1];
1074
- const p = lp.slice(lp.lastIndexOf(":") + 1);
1075
- if (p === want) {
1076
- const pid = Number(parts[4]);
1077
- if (Number.isInteger(pid) && pid > 0) return pid;
996
+ function storeOf() {
997
+ return entryOf() || fallbackEntryOf();
998
+ }
999
+ function fieldOf(name, v, write) {
1000
+ const e = storeOf();
1001
+ if (write) {
1002
+ if (e[name] !== v) {
1003
+ e[name] = v;
1004
+ persistCrashField();
1078
1005
  }
1006
+ return e;
1079
1007
  }
1008
+ return e[name];
1080
1009
  }
1081
- return null;
1082
- }
1083
- function parseSsPid(out) {
1084
- const m = out && /pid=(\d+)/.exec(String(out));
1085
- return m ? Number(m[1]) : null;
1086
- }
1087
- function parseWmicCommandLine(out) {
1088
- if (!out) return null;
1089
- const m = /CommandLine=([\s\S]*)/.exec(String(out));
1090
- const v = m ? m[1].trim() : "";
1091
- return v || null;
1092
- }
1093
- function parsePowerShellCommandLine(out) {
1094
- const v = out ? String(out).trim() : "";
1095
- return v || null;
1096
- }
1097
- function normCmdline(s) {
1098
- return String(s || "").replace(/\\/g, "/");
1010
+ function procFieldOf(name, v, write) {
1011
+ const e = storeOf();
1012
+ let p = e.process;
1013
+ if (!p) {
1014
+ p = e.process = {
1015
+ child: null,
1016
+ adoptedPid: null,
1017
+ adopted: false,
1018
+ observedOnly: false,
1019
+ startDeadline: null,
1020
+ restartAt: null,
1021
+ spawnBlockedUntil: null,
1022
+ missingNotified: false,
1023
+ failStreak: 0,
1024
+ lastProbeAt: null,
1025
+ lastProbeOk: null,
1026
+ lastProbeHttpOk: null,
1027
+ lastFailure: null,
1028
+ lastRestartAt: null
1029
+ };
1030
+ }
1031
+ if (write) {
1032
+ if (p[name] !== v) p[name] = v;
1033
+ return p;
1034
+ }
1035
+ return p[name];
1036
+ }
1037
+ return { entryOf, fallbackEntryOf, persistCrashField, storeOf, fieldOf, procFieldOf };
1099
1038
  }
1100
- module2.exports = {
1101
- parseProcNetTcpInodes,
1102
- parseLsofPid,
1103
- parseNetstatPid,
1104
- parseSsPid,
1105
- parseWmicCommandLine,
1106
- parsePowerShellCommandLine,
1107
- normCmdline
1108
- };
1039
+ module2.exports = { createMainRecord };
1109
1040
  }
1110
1041
  });
1111
1042
 
1112
- // src/platform/os/exec-path.js
1113
- var require_exec_path = __commonJS({
1114
- "src/platform/os/exec-path.js"(exports2, module2) {
1043
+ // src/platform/util/fs.js
1044
+ var require_fs = __commonJS({
1045
+ "src/platform/util/fs.js"(exports2, module2) {
1115
1046
  "use strict";
1116
1047
  var fs2 = require("node:fs");
1117
1048
  var path2 = require("node:path");
1118
- var os2 = require("node:os");
1119
- function candidateNames(base, platform) {
1120
- const win = (platform || process.platform) === "win32";
1121
- if (!win) return [base];
1122
- const exts = String(process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
1123
- const names = [base + ".exe", base + ".cmd", base + ".bat"];
1124
- for (const e of exts) {
1125
- const n = base + e.toLowerCase();
1126
- if (!names.some((x) => x.toLowerCase() === n)) names.push(n);
1127
- }
1128
- names.push(base);
1129
- return [...new Set(names)];
1130
- }
1131
- function isExecutableFile(p, platform) {
1049
+ function dirSizeBytes(root) {
1050
+ let total = 0;
1051
+ let seen = 0;
1052
+ const MAX = 2e5;
1053
+ const walk = (dir) => {
1054
+ if (seen > MAX) return;
1055
+ let entries;
1056
+ try {
1057
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
1058
+ } catch {
1059
+ return;
1060
+ }
1061
+ for (const en of entries) {
1062
+ if (seen > MAX) return;
1063
+ const full = path2.join(dir, en.name);
1064
+ if (en.isSymbolicLink()) continue;
1065
+ if (en.isDirectory()) walk(full);
1066
+ else if (en.isFile()) {
1067
+ try {
1068
+ const st = fs2.statSync(full);
1069
+ total += st.size;
1070
+ } catch {
1071
+ }
1072
+ }
1073
+ seen++;
1074
+ }
1075
+ };
1132
1076
  try {
1133
- if (!fs2.statSync(p).isFile()) return false;
1134
- if ((platform || process.platform) === "win32") return true;
1135
- fs2.accessSync(p, fs2.constants.X_OK);
1136
- return true;
1077
+ walk(root);
1137
1078
  } catch {
1138
- return false;
1139
1079
  }
1080
+ return total;
1140
1081
  }
1141
- function firstExecutable(dir, base, platform) {
1142
- if (!dir) return null;
1143
- for (const name of candidateNames(base, platform)) {
1144
- const p = path2.join(dir, name);
1082
+ function writeAtomic(file, data, opts) {
1083
+ const mode = opts && typeof opts.mode === "number" ? opts.mode : 384;
1084
+ const fp = path2.resolve(file);
1085
+ const dir = path2.dirname(fp);
1086
+ const tmp = fp + ".tmp." + process.pid + "." + Date.now();
1087
+ try {
1088
+ if (!fs2.existsSync(dir)) fs2.mkdirSync(dir, { recursive: true });
1089
+ fs2.writeFileSync(tmp, data, { mode });
1145
1090
  try {
1146
- if (isExecutableFile(p, platform)) return p;
1091
+ fs2.chmodSync(tmp, mode);
1147
1092
  } catch {
1148
1093
  }
1149
- }
1150
- return null;
1151
- }
1152
- function standardDirs(platform, home, env) {
1153
- const pl = platform || process.platform;
1154
- const h = home || os2.homedir();
1155
- const e = env || process.env;
1156
- const dirs = [];
1157
- if (pl === "win32") {
1158
- if (e.APPDATA) dirs.push(path2.join(e.APPDATA, "npm"));
1159
- if (e.LOCALAPPDATA) dirs.push(path2.join(e.LOCALAPPDATA, "Programs", "dsh-supervisor"));
1160
- dirs.push(path2.join(h, ".local", "bin"));
1161
- } else {
1162
- dirs.push(path2.join(h, ".local", "bin"));
1163
- dirs.push(path2.join(h, ".npm-global", "bin"));
1164
- if (pl === "darwin") {
1165
- dirs.push("/opt/homebrew/bin");
1166
- dirs.push("/usr/local/bin");
1094
+ fs2.renameSync(tmp, fp);
1095
+ try {
1096
+ fs2.chmodSync(fp, mode);
1097
+ } catch {
1167
1098
  }
1168
- }
1169
- return dirs;
1170
- }
1171
- function inPath(base, platform, env) {
1172
- const e = env || process.env;
1173
- const raw = e.PATH || e.Path || "";
1174
- for (const d of raw.split(path2.delimiter)) {
1175
- if (!d) continue;
1176
- const hit = firstExecutable(d, base, platform);
1177
- if (hit) return hit;
1178
- }
1179
- return null;
1180
- }
1181
- function resolveExecutable(base, opts) {
1182
- const o = opts || {};
1183
- const pl = o.platform;
1184
- const env = o.env;
1185
- const E = env || process.env;
1186
- if (o.envVar && E[o.envVar]) {
1187
- const v = E[o.envVar];
1188
- if (isExecutableFile(v, pl)) return v;
1189
- }
1190
- const inPathHit = inPath(base, pl, env);
1191
- if (inPathHit) return inPathHit;
1192
- for (const d of [...o.extraDirs || [], ...standardDirs(pl, void 0, env)]) {
1193
- const hit = firstExecutable(d, base, pl);
1194
- if (hit) return hit;
1195
- }
1196
- return null;
1197
- }
1198
- function npxBin(opts) {
1199
- const o = opts || {};
1200
- const pl = o.platform || process.platform;
1201
- const env = o.env || process.env;
1202
- if (pl !== "win32") return "npx";
1203
- const resolved = resolveExecutable("npx", { platform: pl, env, extraDirs: [
1204
- env.APPDATA ? path2.join(env.APPDATA, "npm") : null
1205
- ].filter(Boolean) });
1206
- if (resolved) return resolved;
1207
- return "npx.cmd";
1208
- }
1209
- function npmBin(opts) {
1210
- const o = opts || {};
1211
- const pl = o.platform || process.platform;
1212
- const env = o.env || process.env;
1213
- if (pl !== "win32") return "npm";
1214
- const resolved = resolveExecutable("npm", { platform: pl, env, extraDirs: [
1215
- env.APPDATA ? path2.join(env.APPDATA, "npm") : null
1216
- ].filter(Boolean) });
1217
- if (resolved) return resolved;
1218
- return "npm.cmd";
1219
- }
1220
- var DSH_PKG = ["@deepseek-ai", "dsh"];
1221
- function dshJsIn(prefix) {
1222
- return path2.join(prefix, "node_modules", ...DSH_PKG, "lib", "bin.js");
1223
- }
1224
- function resolveDsh(opts) {
1225
- const o = opts || {};
1226
- const pl = o.platform || process.platform;
1227
- const env = o.env || process.env;
1228
- const isFile = (p) => {
1099
+ return fp;
1100
+ } catch (e) {
1229
1101
  try {
1230
- return fs2.statSync(p).isFile();
1102
+ if (fs2.existsSync(tmp)) fs2.truncateSync(tmp, 0);
1231
1103
  } catch {
1232
- return false;
1233
1104
  }
1234
- };
1235
- const asJs = (bin, launcher) => ({ runtime: process.execPath, bin, isJs: true, launcher: launcher || null });
1236
- if (env.DSH_BIN) {
1105
+ throw e;
1106
+ }
1107
+ }
1108
+ module2.exports = { dirSizeBytes, writeAtomic };
1109
+ }
1110
+ });
1111
+
1112
+ // src/app/state/main-store.js
1113
+ var require_main_store = __commonJS({
1114
+ "src/app/state/main-store.js"(exports2, module2) {
1115
+ "use strict";
1116
+ var fs2 = require("node:fs");
1117
+ var path2 = require("node:path");
1118
+ var { writeAtomic } = require_fs();
1119
+ function createMainStore(deps) {
1120
+ const g = deps || {};
1121
+ const config = () => typeof g.getConfig === "function" ? g.getConfig() || {} : {};
1122
+ const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
1123
+ let live = null;
1124
+ let corrupt = false;
1125
+ function dshMainFile() {
1237
1126
  try {
1238
- const r = fs2.realpathSync(env.DSH_BIN);
1239
- if (isFile(r)) return asJs(r, env.DSH_BIN);
1127
+ return path2.join(path2.dirname(config().stateFile), "dsh-main.json");
1240
1128
  } catch {
1129
+ return null;
1241
1130
  }
1242
1131
  }
1243
- const hit = resolveExecutable("dsh", { platform: pl, env });
1244
- if (hit) {
1132
+ function registryFileName() {
1245
1133
  try {
1246
- const real = fs2.realpathSync(hit);
1247
- if (isFile(real) && /\.(js|cjs|mjs)$/i.test(real)) return asJs(real, hit);
1134
+ const b = path2.basename(config().stateFile || "state.json", ".json");
1135
+ return b === "state" ? "managed-objects.json" : b + ".managed-objects.json";
1248
1136
  } catch {
1137
+ return "managed-objects.json";
1249
1138
  }
1250
- const js = dshJsIn(path2.dirname(hit));
1251
- if (isFile(js)) return asJs(js, hit);
1252
- if (isFile(hit) && !/\.(cmd|bat|exe)$/i.test(hit)) return asJs(hit, hit);
1253
- return { runtime: null, bin: hit, isJs: false, launcher: hit };
1254
- }
1255
- if (o.npmRoot) {
1256
- const js = dshJsIn(o.npmRoot);
1257
- if (isFile(js)) return asJs(js, null);
1258
1139
  }
1259
- return null;
1260
- }
1261
- function knownDshEntries(opts) {
1262
- const o = opts || {};
1263
- const out = [];
1264
- try {
1265
- const r = resolveDsh(o);
1266
- if (r) {
1267
- if (typeof r.bin === "string" && r.bin) out.push(r.bin);
1268
- if (typeof r.launcher === "string" && r.launcher) out.push(r.launcher);
1140
+ function readDshMainFile() {
1141
+ try {
1142
+ const f = dshMainFile();
1143
+ if (f && fs2.existsSync(f)) {
1144
+ const j = JSON.parse(fs2.readFileSync(f, "utf8"));
1145
+ corrupt = false;
1146
+ return {
1147
+ guardian: j.guardian === true,
1148
+ remoteMode: legacyRemoteMode(j),
1149
+ remoteToken: String(j.remoteToken || "")
1150
+ };
1151
+ }
1152
+ } catch (e) {
1153
+ corrupt = true;
1154
+ const l = logger();
1155
+ if (l && l.warn) l.warn("dsh-main.json \u8BFB/\u89E3\u6790\u5931\u8D25\uFF0C\u5199\u56DE\u5C06\u88AB\u62D2\u7EDD\u76F4\u81F3\u663E\u5F0F\u91CD\u8BBE remoteToken: " + (e && e.message || e));
1269
1156
  }
1270
- } catch {
1157
+ return { guardian: false, remoteMode: "off", remoteToken: "" };
1271
1158
  }
1272
- if (o.npmRoot) {
1159
+ function legacyRemoteMode(j) {
1160
+ if (j.remoteMode === "lan" || j.remoteMode === "wan") return j.remoteMode;
1161
+ if (j.remoteEnabled === true && j.frpEnabled === true) return "wan";
1162
+ if (j.remoteEnabled === true) return "lan";
1163
+ return "off";
1164
+ }
1165
+ function readDshMain() {
1166
+ if (live) return live;
1167
+ live = readDshMainFile();
1168
+ return live;
1169
+ }
1170
+ function writeDshMain(meta) {
1171
+ const m = meta || {};
1172
+ if (!live) live = readDshMainFile();
1173
+ if (corrupt && !(typeof m.remoteToken === "string" && m.remoteToken)) {
1174
+ const l = logger();
1175
+ if (l && l.warn) l.warn("_writeDshMain: \u6587\u4EF6\u635F\u574F\u6001\uFF0C\u62D2\u7EDD\u4EE5\u9ED8\u8BA4\u503C\u8986\u76D6\u5199\u56DE");
1176
+ return;
1177
+ }
1178
+ corrupt = false;
1179
+ Object.assign(live, m);
1180
+ const f = dshMainFile();
1181
+ if (!f) return;
1273
1182
  try {
1274
- out.push(dshJsIn(o.npmRoot));
1275
- } catch {
1183
+ const cur = readDshMain();
1184
+ const merged = Object.assign({}, cur, m);
1185
+ const dir = path2.dirname(f);
1186
+ fs2.mkdirSync(dir, { recursive: true });
1187
+ const body = JSON.stringify({
1188
+ guardian: merged.guardian === true,
1189
+ remoteMode: merged.remoteMode === "lan" || merged.remoteMode === "wan" ? merged.remoteMode : "off",
1190
+ remoteToken: String(merged.remoteToken || "")
1191
+ }, null, 2);
1192
+ writeAtomic(f, body, { mode: 384 });
1193
+ } catch (e) {
1194
+ const l = logger();
1195
+ if (l && l.warn) l.warn("_writeDshMain: " + (e && e.message || e));
1276
1196
  }
1277
1197
  }
1278
- if (typeof o.dshBin === "string" && /^(?:[A-Za-z]:[\\/]|[\\/])/.test(o.dshBin)) out.push(o.dshBin);
1279
- return [...new Set(out.filter((x) => typeof x === "string" && x))];
1198
+ return { dshMainFile, registryFileName, readDshMain, readDshMainFile, writeDshMain };
1280
1199
  }
1281
- function commandEntryViolation(cmdArr, opts) {
1282
- const o = opts || {};
1283
- const rp = o.realpath || ((p) => fs2.realpathSync(p));
1284
- if (!Array.isArray(cmdArr) || !cmdArr.length) return null;
1285
- const head = String(cmdArr[0] || "");
1286
- const NODE_HEAD = /* @__PURE__ */ new Set(["node", "node.exe"]);
1287
- const baseOf = (p) => String(p).split(/[\\/]/).pop().toLowerCase();
1288
- let entry = head;
1289
- const nodeHead = NODE_HEAD.has(baseOf(head));
1290
- if (nodeHead) {
1291
- if (cmdArr.length < 2) return "\u542F\u52A8\u547D\u4EE4\u4EE5 node \u6253\u5934\u4F46\u7F3A\u5C11 DSH \u5165\u53E3\u53C2\u6570";
1292
- entry = String(cmdArr[1] || "");
1200
+ module2.exports = { createMainStore };
1201
+ }
1202
+ });
1203
+
1204
+ // src/app/state/field-tables.js
1205
+ var require_field_tables = __commonJS({
1206
+ "src/app/state/field-tables.js"(exports2, module2) {
1207
+ "use strict";
1208
+ var ENTRY_FIELDS = [
1209
+ // [读写 helper 后缀, entry 字段]
1210
+ ["CrashWindowStart", "crashWindowStart"],
1211
+ ["CrashWindowRestarts", "crashWindowRestarts"],
1212
+ ["BackoffLevel", "backoffLevel"],
1213
+ ["BackoffUntil", "backoffUntil"],
1214
+ ["RestartCount", "restartCount"]
1215
+ ];
1216
+ var PROC_FIELDS = [
1217
+ // [读写 helper 后缀, process 字段, 是否布尔]
1218
+ ["Child", "child", false],
1219
+ ["AdoptPid", "adoptedPid", false],
1220
+ ["Adopted", "adopted", true],
1221
+ ["ObservedOnly", "observedOnly", true],
1222
+ ["FailStreak", "failStreak", false],
1223
+ ["RestartAt", "restartAt", false],
1224
+ ["StartDeadline", "startDeadline", false],
1225
+ ["SpawnBlockedUntil", "spawnBlockedUntil", false],
1226
+ ["MissingNotified", "missingNotified", true],
1227
+ ["LastProbeAt", "lastProbeAt", false],
1228
+ ["LastProbeOk", "lastProbeOk", false],
1229
+ ["LastProbeHttpOk", "lastProbeHttpOk", false],
1230
+ ["LastFailure", "lastFailure", false],
1231
+ ["LastRestartAt", "lastRestartAt", false]
1232
+ ];
1233
+ module2.exports = { ENTRY_FIELDS, PROC_FIELDS };
1234
+ }
1235
+ });
1236
+
1237
+ // src/app/state/phase.js
1238
+ var require_phase = __commonJS({
1239
+ "src/app/state/phase.js"(exports2, module2) {
1240
+ "use strict";
1241
+ function legacyToEntryPhase(ph) {
1242
+ return { STOPPED: "stopped", STARTING: "starting", RUNNING: "running", RESTARTING: "restarting", BACKOFF: "backoff", OBSERVED: "stopped" }[ph] || "stopped";
1243
+ }
1244
+ function entryToLegacyPhase(ph) {
1245
+ return { stopped: "STOPPED", starting: "STARTING", running: "RUNNING", restarting: "RESTARTING", backoff: "BACKOFF" }[ph] || "STOPPED";
1246
+ }
1247
+ module2.exports = { legacyToEntryPhase, entryToLegacyPhase };
1248
+ }
1249
+ });
1250
+
1251
+ // src/app/state/fields.js
1252
+ var require_fields = __commonJS({
1253
+ "src/app/state/fields.js"(exports2, module2) {
1254
+ "use strict";
1255
+ var { ENTRY_FIELDS, PROC_FIELDS } = require_field_tables();
1256
+ var { legacyToEntryPhase, entryToLegacyPhase } = require_phase();
1257
+ function createFields(deps) {
1258
+ const g = deps || {};
1259
+ const record = g.record;
1260
+ const mainStore = g.mainStore;
1261
+ const reg = () => typeof g.getManagedObjects === "function" ? g.getManagedObjects() : null;
1262
+ const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
1263
+ function toEntry(ph) {
1264
+ return legacyToEntryPhase(ph);
1293
1265
  }
1294
- if (!entry) return "\u542F\u52A8\u547D\u4EE4\u7F3A\u5C11 DSH \u5165\u53E3";
1295
- const isAbsolute = (p) => /^(?:[A-Za-z]:[\\/]|[\\/])/.test(String(p));
1296
- const hasSep = (p) => /[\\/]/.test(String(p));
1297
- if (!hasSep(entry)) {
1298
- if (nodeHead && o.requireAbsoluteEntry) {
1299
- return "command[0] \u4E3A node \u65F6 command[1] \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\u7684 DSH \u5165\u53E3\uFF08\u76F8\u5BF9/\u88F8\u540D\u4F1A\u6309\u5DE5\u4F5C\u76EE\u5F55\u6216 PATH \u89E3\u6790\uFF09";
1300
- }
1301
- return null;
1266
+ function toLegacy(ph) {
1267
+ return entryToLegacyPhase(ph);
1302
1268
  }
1303
- if (!isAbsolute(entry)) {
1304
- return "DSH \u5165\u53E3\u4E0D\u63A5\u53D7\u76F8\u5BF9\u8DEF\u5F84\uFF08\u4F1A\u6309\u8C03\u7528\u65B9\u5DE5\u4F5C\u76EE\u5F55\u89E3\u6790\uFF1B\u6C99\u7BB1\u5B9E\u4F8B\u7684\u8BE5\u76EE\u5F55\u6C99\u7BB1\u5185\u53EF\u5199\uFF09";
1269
+ function phase() {
1270
+ const e = record.storeOf();
1271
+ const p = e.process || null;
1272
+ const upper = entryToLegacyPhase(e.phase || "stopped");
1273
+ if (upper === "STOPPED" && p && p.observedOnly && p.adopted) return "OBSERVED";
1274
+ return upper;
1305
1275
  }
1306
- let real = null;
1307
- try {
1308
- real = rp(entry);
1309
- } catch {
1276
+ function setPhase(upper) {
1277
+ const e = record.storeOf();
1278
+ const ph = legacyToEntryPhase(upper);
1279
+ const m = reg();
1280
+ try {
1281
+ if (m && typeof m.setPhase === "function" && record.entryOf() === e) {
1282
+ if (e.phase !== ph) m.setPhase("main", ph);
1283
+ } else if (e.phase !== ph) {
1284
+ e.phase = ph;
1285
+ }
1286
+ } catch (e2) {
1287
+ const l = logger();
1288
+ if (l && l.warn) l.warn("_mSetPhase: " + (e2 && e2.message || e2));
1289
+ }
1310
1290
  }
1311
- if (typeof o.allowEntry === "function") {
1291
+ function guardian() {
1312
1292
  try {
1313
- if (o.allowEntry(entry, real)) return null;
1293
+ return mainStore.readDshMain().guardian === true;
1314
1294
  } catch {
1295
+ return false;
1315
1296
  }
1316
1297
  }
1317
- if (real === null) return "DSH \u5165\u53E3\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u89E3\u6790\uFF08fail-closed\uFF09\uFF1A" + entry;
1318
- for (const f of Array.isArray(o.files) ? o.files : []) {
1298
+ function mainGuardian() {
1299
+ return guardian();
1300
+ }
1301
+ function desired() {
1302
+ return record.storeOf().desired === "stopped" ? "stopped" : "running";
1303
+ }
1304
+ function setDesired(v) {
1305
+ const want = v === "stopped" ? "stopped" : "running";
1306
+ const e = record.storeOf();
1307
+ const m = reg();
1319
1308
  try {
1320
- if (rp(f) === real) return null;
1321
- } catch {
1309
+ if (m && typeof m.update === "function" && record.entryOf() === e) {
1310
+ if (e.desired !== want) m.update("main", { desired: want });
1311
+ } else if (e.desired !== want) {
1312
+ e.desired = want;
1313
+ }
1314
+ } catch (e2) {
1315
+ const l = logger();
1316
+ if (l && l.warn) l.warn("_mSetDesired: " + (e2 && e2.message || e2));
1322
1317
  }
1323
1318
  }
1324
- for (const r of Array.isArray(o.roots) ? o.roots : []) {
1325
- let rr;
1326
- try {
1327
- rr = rp(r);
1328
- } catch {
1329
- continue;
1330
- }
1331
- const base = String(rr).replace(/[\\/]+$/, "");
1332
- if (real === base || real.indexOf(base + path2.sep) === 0) return null;
1319
+ function field(name, v) {
1320
+ return arguments.length >= 2 ? record.fieldOf(name, v, true) : record.fieldOf(name, void 0, false);
1333
1321
  }
1334
- return "DSH \u5165\u53E3\u4E0D\u5728\u5141\u8BB8\u4F4D\u7F6E\uFF08\u987B\u4E3A\u8BE5\u5B9E\u4F8B\u5B89\u88C5\u6839\u4E4B\u4E0B\u7684\u5165\u53E3\uFF0C\u6216\u5185\u6838\u89E3\u6790\u51FA\u7684\u5DF2\u77E5 DSH \u5165\u53E3\uFF09\uFF1A" + entry;
1322
+ function procField(name, v) {
1323
+ return arguments.length >= 2 ? record.procFieldOf(name, v, true) : record.procFieldOf(name, void 0, false);
1324
+ }
1325
+ const getProc = (n) => record.procFieldOf(n);
1326
+ const setProc = (n, v) => {
1327
+ record.procFieldOf(n, v, true);
1328
+ };
1329
+ const getEntry = (n) => record.fieldOf(n);
1330
+ const setEntry = (n, v) => {
1331
+ record.fieldOf(n, v, true);
1332
+ };
1333
+ const accessors = {
1334
+ phase: { get: () => phase(), set: (v) => {
1335
+ setPhase(v);
1336
+ } },
1337
+ desired: { get: () => desired(), set: (v) => {
1338
+ setDesired(v);
1339
+ } },
1340
+ child: { get: () => getProc("child"), set: (c) => {
1341
+ setProc("child", c);
1342
+ } },
1343
+ adoptedPid: { get: () => getProc("adoptedPid"), set: (v) => {
1344
+ setProc("adoptedPid", v);
1345
+ } },
1346
+ adopted: { get: () => getProc("adopted") === true, set: (v) => {
1347
+ setProc("adopted", v === true);
1348
+ } },
1349
+ observedOnly: { get: () => getProc("observedOnly") === true, set: (v) => {
1350
+ setProc("observedOnly", v === true);
1351
+ } },
1352
+ restartCount: { get: () => {
1353
+ const v = getEntry("restartCount");
1354
+ return typeof v === "number" ? v : 0;
1355
+ }, set: (v) => {
1356
+ setEntry("restartCount", v);
1357
+ } },
1358
+ spawnBlockedUntil: { get: () => {
1359
+ const v = getProc("spawnBlockedUntil");
1360
+ return v === void 0 ? null : v;
1361
+ }, set: (v) => {
1362
+ setProc("spawnBlockedUntil", v);
1363
+ } },
1364
+ missingNotified: { get: () => getProc("missingNotified") === true, set: (v) => {
1365
+ setProc("missingNotified", v === true);
1366
+ } }
1367
+ };
1368
+ return {
1369
+ phase,
1370
+ setPhase,
1371
+ guardian,
1372
+ mainGuardian,
1373
+ desired,
1374
+ setDesired,
1375
+ field,
1376
+ procField,
1377
+ accessors,
1378
+ legacyToEntryPhase: toEntry,
1379
+ entryToLegacyPhase: toLegacy,
1380
+ child: () => getProc("child"),
1381
+ adoptPid: () => getProc("adoptedPid"),
1382
+ observedOnly: () => getProc("observedOnly") === true,
1383
+ setObservedOnly: (v) => {
1384
+ setProc("observedOnly", v === true);
1385
+ },
1386
+ ENTRY_FIELDS,
1387
+ PROC_FIELDS
1388
+ };
1335
1389
  }
1336
- module2.exports = {
1337
- resolveExecutable,
1338
- candidateNames,
1339
- standardDirs,
1340
- npmBin,
1341
- npxBin,
1342
- resolveDsh,
1343
- dshJsIn,
1344
- knownDshEntries,
1345
- commandEntryViolation,
1346
- isExecutableFile
1347
- };
1390
+ module2.exports = { createFields };
1348
1391
  }
1349
1392
  });
1350
1393
 
1351
- // src/platform/os/pidlookup/probe.js
1352
- var require_probe = __commonJS({
1353
- "src/platform/os/pidlookup/probe.js"(exports2, module2) {
1394
+ // src/app/state/store.js
1395
+ var require_store = __commonJS({
1396
+ "src/app/state/store.js"(exports2, module2) {
1354
1397
  "use strict";
1355
1398
  var fs2 = require("node:fs");
1356
- var ex2 = require_exec();
1357
- var { isExecutableFile } = require_exec_path();
1358
- var {
1359
- parseProcNetTcpInodes,
1360
- parseLsofPid,
1361
- parseNetstatPid,
1362
- parseSsPid,
1363
- parseWmicCommandLine,
1364
- parsePowerShellCommandLine
1365
- } = require_norm();
1366
- var isLinux = process.platform === "linux";
1367
- var isMac = process.platform === "darwin";
1368
- var isWindows = process.platform === "win32";
1369
- function linuxListeningInodes(port) {
1370
- const inodes = /* @__PURE__ */ new Set();
1371
- for (const f of ["/proc/net/tcp", "/proc/net/tcp6"]) {
1372
- let txt = "";
1399
+ var path2 = require("node:path");
1400
+ var { writeAtomic } = require_fs();
1401
+ function createStore(deps) {
1402
+ const g = deps || {};
1403
+ const record = g.record;
1404
+ const fields = g.fields;
1405
+ const mainStore = g.mainStore;
1406
+ const upgradeHold = g.upgradeHold;
1407
+ const config = () => typeof g.getConfig === "function" ? g.getConfig() || {} : {};
1408
+ const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
1409
+ const events = () => typeof g.getEvents === "function" ? g.getEvents() : null;
1410
+ const instances = () => typeof g.getInstances === "function" ? g.getInstances() : null;
1411
+ const views = () => typeof g.getViews === "function" ? g.getViews() : null;
1412
+ const reg = () => typeof g.getManagedObjects === "function" ? g.getManagedObjects() : null;
1413
+ let lastStateBody = null;
1414
+ function writeState(force) {
1373
1415
  try {
1374
- txt = fs2.readFileSync(f, "utf8");
1375
- } catch {
1376
- continue;
1416
+ const snap = views().status();
1417
+ const updatedAt = snap.updatedAt;
1418
+ snap.updatedAt = null;
1419
+ const body = JSON.stringify(snap, null, 2);
1420
+ if (!force && body === lastStateBody) return;
1421
+ lastStateBody = body;
1422
+ snap.updatedAt = updatedAt;
1423
+ const dir = path2.dirname(config().stateFile);
1424
+ fs2.mkdirSync(dir, { recursive: true });
1425
+ writeAtomic(config().stateFile, JSON.stringify(snap, null, 2), { mode: 384 });
1426
+ } catch (e) {
1427
+ const l = logger();
1428
+ if (l && l.error) l.error("state write failed: " + e.message);
1377
1429
  }
1378
- for (const x of parseProcNetTcpInodes(txt, port)) inodes.add(x);
1379
1430
  }
1380
- return inodes;
1381
- }
1382
- function linuxFind(port) {
1383
- try {
1384
- const inodes = linuxListeningInodes(port);
1385
- if (!inodes.size) return null;
1386
- const entries = fs2.readdirSync("/proc").filter((e) => /^\d+$/.test(e));
1387
- for (const pid of entries) {
1388
- let fds;
1389
- try {
1390
- fds = fs2.readdirSync("/proc/" + pid + "/fd");
1391
- } catch {
1392
- continue;
1431
+ function loadState() {
1432
+ let raw = null;
1433
+ try {
1434
+ raw = JSON.parse(fs2.readFileSync(config().stateFile, "utf8"));
1435
+ } catch (e) {
1436
+ if (!e || e.code !== "ENOENT") {
1437
+ const l = logger();
1438
+ if (l && l.warn) l.warn("state load failed\uFF08\u6309\u7A7A\u72B6\u6001\u7EE7\u7EED\uFF09: " + (e && e.message || e));
1393
1439
  }
1394
- for (const fd of fds) {
1395
- let link;
1396
- try {
1397
- link = fs2.readlinkSync("/proc/" + pid + "/fd/" + fd);
1398
- } catch {
1399
- continue;
1400
- }
1401
- if (inodes.has(link)) return Number(pid);
1440
+ }
1441
+ if (raw && typeof raw === "object") try {
1442
+ if (raw.desired === "stopped" || raw.desired === "running") {
1443
+ const m = reg();
1444
+ const registryHasSource = !!(m && m._loadedFromDisk);
1445
+ if (!registryHasSource) fields.setDesired(raw.desired);
1402
1446
  }
1447
+ if (typeof raw.restartCount === "number") record.fieldOf("restartCount", raw.restartCount, true);
1448
+ if (typeof raw.backoffLevel === "number") record.fieldOf("backoffLevel", raw.backoffLevel, true);
1449
+ if (typeof raw.crashWindowStart === "number" || raw.crashWindowStart === null) record.fieldOf("crashWindowStart", raw.crashWindowStart, true);
1450
+ if (typeof raw.crashWindowRestarts === "number") record.fieldOf("crashWindowRestarts", raw.crashWindowRestarts, true);
1451
+ if (typeof raw.lastFailure === "string" || raw.lastFailure === null) record.procFieldOf("lastFailure", raw.lastFailure, true);
1452
+ if (typeof raw.lastRestartAt === "string" || raw.lastRestartAt === null) record.procFieldOf("lastRestartAt", raw.lastRestartAt, true);
1453
+ if (raw.upgradeHold === true) upgradeHold.enter();
1454
+ if (raw.shellHalted === true && typeof g.setShellHalted === "function") g.setShellHalted(true);
1455
+ } catch (e) {
1456
+ const l = logger();
1457
+ if (l && l.warn) l.warn("state restore partial\uFF08\u5B57\u6BB5\u7EA7\u8DF3\u8FC7\uFF0Cboot \u4ECD\u7EE7\u7EED\uFF09: " + (e && e.message || e));
1403
1458
  }
1404
- } catch {
1405
- }
1406
- return null;
1407
- }
1408
- function macFind(port) {
1409
- try {
1410
- const out = ex2.runOut("lsof", ["-nP", "-iTCP:" + port, "-sTCP:LISTEN"], { timeoutMs: 3e3 });
1411
- if (!out) return null;
1412
- return parseLsofPid(out);
1413
- } catch {
1414
- }
1415
- return null;
1416
- }
1417
- function winFind(port) {
1418
- try {
1419
- const out = ex2.runOut("netstat", ["-ano"], { timeoutMs: 3e3 });
1420
- if (!out) return null;
1421
- return parseNetstatPid(out, port);
1422
- } catch {
1423
- }
1424
- return null;
1425
- }
1426
- function linuxFindSs(port) {
1427
- const candidates = ["ss", "/usr/sbin/ss", "/usr/bin/ss", "/bin/ss"];
1428
- for (const ssBin of candidates) {
1429
- if (ssBin.includes("/") && !isExecutableFile(ssBin)) continue;
1430
1459
  try {
1431
- const out = ex2.runOut(ssBin, ["-tlnHp", "sport = :" + port], { timeoutMs: 3e3 });
1432
- const pid = parseSsPid(out);
1433
- if (pid !== null) return pid;
1460
+ fields.setPhase("STOPPED");
1434
1461
  } catch {
1435
1462
  }
1436
1463
  }
1437
- return null;
1438
- }
1439
- function isAlive(pid) {
1440
- if (!Number.isInteger(pid) || pid <= 0) return false;
1441
- try {
1442
- process.kill(pid, 0);
1443
- return true;
1444
- } catch (e) {
1445
- return !!e && e.code === "EPERM";
1446
- }
1447
- }
1448
- function isZombie(pid) {
1449
- if (!Number.isInteger(pid) || pid <= 0 || isWindows) return false;
1450
- if (isLinux) {
1464
+ function migrateMainRecord() {
1451
1465
  try {
1452
- const st = fs2.readFileSync("/proc/" + pid + "/stat", "utf8");
1453
- const idx = st.lastIndexOf(") ");
1454
- return idx >= 0 && st[idx + 2] === "Z";
1455
- } catch {
1456
- return false;
1466
+ const im = instances();
1467
+ if (!im || !Array.isArray(im.instances)) return;
1468
+ const idx = im.instances.findIndex((i) => i.id === "main");
1469
+ if (idx < 0) return;
1470
+ const main = im.instances[idx];
1471
+ const f = mainStore.dshMainFile();
1472
+ if (f && !fs2.existsSync(f)) {
1473
+ mainStore.writeDshMain({
1474
+ guardian: main.guardian === true,
1475
+ remoteMode: main.remoteEnabled === true ? main.frpEnabled === true ? "wan" : "lan" : "off",
1476
+ remoteToken: String(main.remoteToken || "")
1477
+ });
1478
+ }
1479
+ im.instances.splice(idx, 1);
1480
+ if (im.save) {
1481
+ try {
1482
+ im.save();
1483
+ } catch {
1484
+ }
1485
+ }
1486
+ const l = logger();
1487
+ if (l && l.info) l.info("[main] \u6982\u5FF5\u6E05\u5206\uFF1Amain \u8BB0\u5F55\u5DF2\u8FC1\u51FA instances.json \u2192 dsh-main.json");
1488
+ const ev = events();
1489
+ if (ev) ev.append("main_meta_migrated", {});
1490
+ } catch (e) {
1491
+ const l = logger();
1492
+ if (l && l.warn) l.warn("_migrateMainRecord: " + (e && e.message));
1457
1493
  }
1458
1494
  }
1459
- try {
1460
- const o = ex2.runOut("ps", ["-o", "state=", "-p", String(pid)], { timeoutMs: 3e3 });
1461
- return !!o && /^Z/.test(o.trim());
1462
- } catch {
1463
- return false;
1464
- }
1495
+ return { writeState, loadState, migrateMainRecord };
1465
1496
  }
1466
- function readCmdline(pid) {
1467
- if (isLinux) {
1468
- try {
1469
- const buf = fs2.readFileSync("/proc/" + pid + "/cmdline");
1470
- return buf.toString("utf8").replace(/\0/g, " ").trim();
1471
- } catch {
1472
- return null;
1497
+ module2.exports = { createStore };
1498
+ }
1499
+ });
1500
+
1501
+ // src/app/state/desired.js
1502
+ var require_desired = __commonJS({
1503
+ "src/app/state/desired.js"(exports2, module2) {
1504
+ "use strict";
1505
+ var fs2 = require("node:fs");
1506
+ var { writeAtomic } = require_fs();
1507
+ function createDesired(deps) {
1508
+ const g = deps || {};
1509
+ const fields = g.fields;
1510
+ const store = g.store;
1511
+ const intents = () => typeof g.getIntents === "function" ? g.getIntents() : null;
1512
+ const events = () => typeof g.getEvents === "function" ? g.getEvents() : null;
1513
+ const configPath = () => typeof g.getConfigPath === "function" ? g.getConfigPath() : null;
1514
+ const logger = () => typeof g.getLogger === "function" ? g.getLogger() : null;
1515
+ const setCrashHalted = typeof g.setCrashHalted === "function" ? g.setCrashHalted : () => {
1516
+ };
1517
+ const setManualRestart = typeof g.setManualRestart === "function" ? g.setManualRestart : () => {
1518
+ };
1519
+ const tick = () => {
1520
+ if (typeof g.tick === "function") g.tick();
1521
+ };
1522
+ const stopProcess = (why) => {
1523
+ if (typeof g.stopProcess === "function") g.stopProcess(why);
1524
+ };
1525
+ function setDesired(v) {
1526
+ if (v !== "running" && v !== "stopped") return { error: "invalid desired" };
1527
+ if (v === "running") {
1528
+ const it = intents();
1529
+ if (it) it.register("start");
1530
+ setCrashHalted(false);
1473
1531
  }
1474
- }
1475
- if (isMac) {
1476
- try {
1477
- const o = ex2.runOut("ps", ["-o", "command=", "-p", String(pid)], { timeoutMs: 3e3 });
1478
- return o ? o.trim() || null : null;
1479
- } catch {
1480
- return null;
1532
+ if (v === "running" && fields.phase() === "OBSERVED") {
1533
+ fields.setObservedOnly(false);
1534
+ fields.setPhase("STOPPED");
1535
+ }
1536
+ if (v === "stopped" && fields.phase() === "OBSERVED" && fields.observedOnly()) {
1537
+ stopProcess("desired_stopped");
1538
+ }
1539
+ if (fields.desired() !== v) {
1540
+ fields.setDesired(v);
1541
+ const ev = events();
1542
+ if (ev) ev.append("desired_changed", { desired: v });
1543
+ store.writeState();
1481
1544
  }
1545
+ tick();
1546
+ return { ok: true, desired: fields.desired() };
1482
1547
  }
1483
- if (isWindows) {
1484
- const out = ex2.runOut("wmic", ["process", "where", "ProcessId=" + pid, "get", "CommandLine", "/value"], { timeoutMs: 5e3 });
1485
- const viaWmic = parseWmicCommandLine(out);
1486
- if (viaWmic) return viaWmic;
1487
- {
1488
- const ps = "(Get-CimInstance Win32_Process -Filter 'ProcessId=" + pid + "').CommandLine";
1489
- const o = ex2.runOut("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { timeoutMs: 5e3 });
1490
- return parsePowerShellCommandLine(o);
1548
+ function requestRestart() {
1549
+ if (fields.desired() === "stopped") {
1550
+ const ev2 = events();
1551
+ if (ev2) ev2.append("manual_restart_requested", { ignored: "desired=stopped" });
1552
+ return { ok: false, error: "desired=stopped\uFF0C\u8BF7\u5148 /start" };
1491
1553
  }
1554
+ setManualRestart(true);
1555
+ setCrashHalted(false);
1556
+ const it = intents();
1557
+ if (it) it.register("restart");
1558
+ const ev = events();
1559
+ if (ev) ev.append("manual_restart_requested", {});
1560
+ tick();
1561
+ return { ok: true };
1492
1562
  }
1493
- return null;
1494
- }
1495
- function pgrepList(pattern) {
1496
- const out = [];
1497
- const readCmd = (pid) => readCmdline(pid) || "";
1498
- try {
1499
- if (isMac) {
1500
- const pids = (ex2.runOut("pgrep", ["-f", String(pattern)], { timeoutMs: 3e3 }) || "").split(/\r?\n/);
1501
- for (const line of pids) {
1502
- const pid = parseInt(line.trim(), 10);
1503
- if (!Number.isInteger(pid) || pid <= 0) continue;
1504
- const cmd2 = readCmd(pid);
1505
- if (!cmd2) continue;
1506
- out.push({ pid, cmdline: cmd2 });
1563
+ function persistConfigPatch(patch) {
1564
+ const p = configPath();
1565
+ if (!p) return false;
1566
+ const abort = (m) => {
1567
+ const l = logger();
1568
+ if (l && l.warn) l.warn("config persist aborted (fail-closed): " + m);
1569
+ const ev = events();
1570
+ if (ev) {
1571
+ try {
1572
+ ev.append("config_persist_aborted", { reason: m });
1573
+ } catch {
1574
+ }
1507
1575
  }
1508
- return out;
1509
- }
1510
- if (isWindows) {
1511
- const ps = "Get-CimInstance Win32_Process | Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress";
1512
- const j = ex2.runOut("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { timeoutMs: 8e3 }) || "";
1513
- let arr = [];
1576
+ return false;
1577
+ };
1578
+ try {
1579
+ let cur = {};
1580
+ let raw = null;
1514
1581
  try {
1515
- arr = JSON.parse(j);
1516
- if (!Array.isArray(arr)) arr = [arr];
1517
- } catch {
1582
+ raw = fs2.readFileSync(p, "utf8");
1583
+ } catch (e) {
1584
+ if (!e || e.code !== "ENOENT") return abort("read failed: " + (e && e.message || e));
1518
1585
  }
1519
- for (const it of arr) {
1520
- if (!it || !it.ProcessId) continue;
1521
- const pid = Number(it.ProcessId);
1522
- if (!Number.isInteger(pid) || pid <= 0) continue;
1523
- const cmd2 = String(it.CommandLine || "");
1524
- if (!cmd2.includes(pattern)) continue;
1525
- out.push({ pid, cmdline: cmd2 });
1586
+ if (raw !== null) {
1587
+ try {
1588
+ cur = JSON.parse(raw);
1589
+ if (!cur || typeof cur !== "object" || Array.isArray(cur)) throw new Error("root is not an object");
1590
+ } catch (e) {
1591
+ return abort("parse failed, original bytes preserved: " + (e && e.message || e));
1592
+ }
1526
1593
  }
1527
- return out;
1528
- }
1529
- const res = ex2.runOut("pgrep", ["-af", String(pattern)], { timeoutMs: 3e3 }) || "";
1530
- for (const line of res.split(/\r?\n/)) {
1531
- const m = /^(\d+)\s+([\s\S]*)$/.exec(line.trim());
1532
- if (m) out.push({ pid: Number(m[1]), cmdline: m[2] });
1594
+ Object.assign(cur, patch);
1595
+ delete cur.switcherAutoStart;
1596
+ writeAtomic(p, JSON.stringify(cur, null, 2), { mode: 384 });
1597
+ return true;
1598
+ } catch (e) {
1599
+ const l = logger();
1600
+ if (l && l.warn) l.warn("config persist failed: " + e.message);
1601
+ return false;
1533
1602
  }
1534
- } catch {
1535
- }
1536
- return out;
1537
- }
1538
- module2.exports = {
1539
- linuxListeningInodes,
1540
- linuxFind,
1541
- macFind,
1542
- winFind,
1543
- linuxFindSs,
1544
- readCmdline,
1545
- pgrepList,
1546
- isAlive,
1547
- isZombie
1548
- };
1549
- }
1550
- });
1551
-
1552
- // src/platform/os/pidlookup/index.js
1553
- var require_pidlookup = __commonJS({
1554
- "src/platform/os/pidlookup/index.js"(exports2, module2) {
1555
- "use strict";
1556
- var {
1557
- parseProcNetTcpInodes,
1558
- parseLsofPid,
1559
- parseNetstatPid,
1560
- parseSsPid,
1561
- parseWmicCommandLine,
1562
- parsePowerShellCommandLine,
1563
- normCmdline
1564
- } = require_norm();
1565
- var {
1566
- linuxFind,
1567
- linuxFindSs,
1568
- macFind,
1569
- winFind,
1570
- readCmdline,
1571
- pgrepList,
1572
- isAlive,
1573
- isZombie
1574
- } = require_probe();
1575
- var isLinux = process.platform === "linux";
1576
- var isMac = process.platform === "darwin";
1577
- function findListeningPid(port) {
1578
- if (!Number.isInteger(port) || port <= 0) return null;
1579
- if (isLinux) {
1580
- const a = linuxFind(port);
1581
- if (a !== null && a !== void 0) return a;
1582
- return linuxFindSs(port);
1583
1603
  }
1584
- if (isMac) return macFind(port);
1585
- return winFind(port);
1586
- }
1587
- function isDshCmdline(pid) {
1588
- const cmd2 = readCmdline(pid);
1589
- if (!cmd2) return false;
1590
- return /(^|\s)(node|.*dsh.*)(\s|$)/i.test(cmd2) && /dsh/i.test(cmd2);
1604
+ return { setDesired, requestRestart, persistConfigPatch };
1591
1605
  }
1592
- module2.exports = {
1593
- findListeningPid,
1594
- isAlive,
1595
- isZombie,
1596
- readCmdline,
1597
- normCmdline,
1598
- isDshCmdline,
1599
- pgrepList,
1600
- parseProcNetTcpInodes,
1601
- parseLsofPid,
1602
- parseNetstatPid,
1603
- parseSsPid,
1604
- parseWmicCommandLine,
1605
- parsePowerShellCommandLine
1606
- };
1606
+ module2.exports = { createDesired };
1607
1607
  }
1608
1608
  });
1609
1609
 
@@ -27806,6 +27806,7 @@ var path = require("node:path");
27806
27806
  var os = require("node:os");
27807
27807
  var http = require("node:http");
27808
27808
  var ex = require_exec();
27809
+ var { readCmdline } = require_pidlookup();
27809
27810
  function findPackageRoot(start) {
27810
27811
  let d = start;
27811
27812
  for (let i = 0; i < 6; i += 1) {
@@ -27918,12 +27919,31 @@ function apiRequest(method, apiPath) {
27918
27919
  });
27919
27920
  }
27920
27921
  var LOCK_FILE = process.env.DSH_SUPERVISOR_LOCK_FILE || path.join(SUPERVISOR_DIR, "guard.lock");
27922
+ var LOCK_OWNER = { pid: process.pid, started: Date.now(), entry: process.argv[1] || "" };
27923
+ function readLock() {
27924
+ let raw;
27925
+ try {
27926
+ raw = fs.readFileSync(LOCK_FILE, "utf8");
27927
+ } catch {
27928
+ return null;
27929
+ }
27930
+ try {
27931
+ const j = JSON.parse(raw);
27932
+ if (j && Number.isInteger(j.pid) && j.pid > 0) return j;
27933
+ } catch {
27934
+ }
27935
+ const pid = parseInt(raw, 10);
27936
+ return Number.isInteger(pid) && pid > 0 ? { pid } : null;
27937
+ }
27938
+ function isOwnGuardEntry(cmdline) {
27939
+ return typeof cmdline === "string" && cmdline.includes("dsh-supervisor");
27940
+ }
27921
27941
  function acquireLock() {
27922
27942
  fs.mkdirSync(SUPERVISOR_DIR, { recursive: true });
27923
27943
  const tryCreate = () => {
27924
27944
  try {
27925
27945
  const fd = fs.openSync(LOCK_FILE, "wx");
27926
- fs.writeSync(fd, String(process.pid));
27946
+ fs.writeSync(fd, JSON.stringify(LOCK_OWNER));
27927
27947
  fs.closeSync(fd);
27928
27948
  return true;
27929
27949
  } catch (e) {
@@ -27933,18 +27953,19 @@ function acquireLock() {
27933
27953
  }
27934
27954
  };
27935
27955
  if (tryCreate()) return true;
27936
- let holder = null;
27937
- try {
27938
- holder = parseInt(fs.readFileSync(LOCK_FILE, "utf8"), 10);
27939
- } catch {
27940
- }
27941
- if (Number.isInteger(holder) && holder > 0) {
27956
+ const held = readLock();
27957
+ if (held) {
27958
+ let alive = false;
27942
27959
  try {
27943
- process.kill(holder, 0);
27944
- return false;
27960
+ process.kill(held.pid, 0);
27961
+ alive = true;
27945
27962
  } catch (err) {
27946
- if (err.code === "EPERM") return false;
27963
+ alive = err.code === "EPERM";
27947
27964
  }
27965
+ const cmdline = alive ? readCmdline(held.pid) : null;
27966
+ if (alive && !cmdline) return false;
27967
+ if (alive && isOwnGuardEntry(cmdline)) return false;
27968
+ 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");
27948
27969
  }
27949
27970
  try {
27950
27971
  fs.unlinkSync(LOCK_FILE);
@@ -27954,10 +27975,8 @@ function acquireLock() {
27954
27975
  }
27955
27976
  function releaseLock() {
27956
27977
  try {
27957
- if (fs.existsSync(LOCK_FILE)) {
27958
- const holder = parseInt(fs.readFileSync(LOCK_FILE, "utf8"), 10);
27959
- if (holder === process.pid) fs.unlinkSync(LOCK_FILE);
27960
- }
27978
+ const held = readLock();
27979
+ if (held && held.pid === process.pid) fs.unlinkSync(LOCK_FILE);
27961
27980
  } catch {
27962
27981
  }
27963
27982
  }