@napuketto/loader 0.0.29 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -227,6 +227,85 @@ function isNativeNoiseLine(line) {
227
227
  return NATIVE_NOISE.test(line);
228
228
  }
229
229
  //#endregion
230
+ //#region src/pid-cmdline.ts
231
+ /**
232
+ * pid-cmdline.ts:跨进程命令行查询(instance-lock pid 复用根治,2026-09-10)。
233
+ *
234
+ * 背景(2026-09-08 实测事故):锁内 pid 的持有者已死、pid 被 Windows 复用给
235
+ * 无关进程时,process.kill(pid, 0) 探活成功 → 误判存活 → 新实例永久拒绝启动
236
+ * (只能手动删锁恢复)。根治:探活通过后再查该 pid 当前的真实命令行,与锁内
237
+ * cmdline 摘要比对——查不到(进程已退/查询失败)或不一致(pid 复用)均视为
238
+ * 持有者已死,允许接管。误判方向取舍:误判「死亡」→ 双实例抢 QQ 原生锁,
239
+ * 后启动者挂起退出(可恢复);误判「存活」→ 永久锁死需人工干预——前者代价
240
+ * 远低,故查询失败宁可接管。
241
+ *
242
+ * 平台实现:Windows 走 PowerShell Get-CimInstance Win32_Process 跨进程查询
243
+ * (仅在启动判活时调用一次,冷启动 ~0.5-2s 可接受;wmic 在新 Win11 已移除,
244
+ * 不可用)。Linux 走 /proc/<pid>/cmdline(\0 分隔)。其余平台(darwin 等)
245
+ * 不支持——判活退回纯 pid 探测(旧行为)。
246
+ *
247
+ * 比对不要求逐字符相等:锁内摘要来自 process.execArgv/argv join,查询侧
248
+ * 拿到的是操作系统记录的原始命令行,两者存在引号 / 大小写 / 路径分隔符差异
249
+ * ——统一经 normalizeCmdline 规范化后比对。残余差异源(8.3 短路径、相对
250
+ * 启动路径)极罕见,误判后果同上(可恢复),接受。
251
+ */
252
+ /** 当前平台是否支持跨进程 cmdline 查询(不支持则判活退回纯 pid 探测)。 */
253
+ const CMDLINE_PROBE_SUPPORTED = node_process.default.platform === "win32" || node_process.default.platform === "linux";
254
+ /**
255
+ * 查询指定 pid 的当前命令行。
256
+ *
257
+ * @returns 命令行原文;进程不存在 / 命令行不可得(权限、系统进程 null)/
258
+ * 查询机制失败(超时、PowerShell 缺失)均返回 null(调用方视为持有者已死)
259
+ */
260
+ function readCmdlineForPid(pid) {
261
+ if (node_process.default.platform === "win32") return readWindowsCmdline(pid);
262
+ if (node_process.default.platform === "linux") return readLinuxCmdline(pid);
263
+ return null;
264
+ }
265
+ /** Windows:PowerShell Get-CimInstance 查询(进程不存在时输出空 → null)。 */
266
+ function readWindowsCmdline(pid) {
267
+ try {
268
+ const cmdline = (0, node_child_process.execFileSync)("powershell.exe", [
269
+ "-NoProfile",
270
+ "-NonInteractive",
271
+ "-ExecutionPolicy",
272
+ "Bypass",
273
+ "-Command",
274
+ `[Console]::OutputEncoding=[System.Text.Encoding]::UTF8;(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CommandLine`
275
+ ], {
276
+ encoding: "utf-8",
277
+ timeout: 1e4,
278
+ windowsHide: true
279
+ }).trim();
280
+ return cmdline === "" ? null : cmdline;
281
+ } catch {
282
+ return null;
283
+ }
284
+ }
285
+ /** Linux:/proc/<pid>/cmdline(\0 分隔转空格;进程不存在抛错 → null)。 */
286
+ function readLinuxCmdline(pid) {
287
+ try {
288
+ const cmdline = (0, node_fs.readFileSync)(`/proc/${pid}/cmdline`, "utf-8").replaceAll("\0", " ").trim();
289
+ return cmdline === "" ? null : cmdline;
290
+ } catch {
291
+ return null;
292
+ }
293
+ }
294
+ /**
295
+ * 命令行规范化(比对用):统一大小写 / 去引号 / 路径分隔符归一 / 折叠空白。
296
+ * Windows 路径大小写不敏感,lowercase 安全。
297
+ */
298
+ function normalizeCmdline(cmdline) {
299
+ return cmdline.toLowerCase().replaceAll("\"", "").replaceAll("\\", "/").replace(/\s+/g, " ").trim();
300
+ }
301
+ /**
302
+ * 本进程命令行摘要(写锁用):execArgv(node 选项)+ argv(脚本与参数),
303
+ * 与操作系统记录的完整命令行结构一致(规范化后可比对)。
304
+ */
305
+ function selfCmdline() {
306
+ return [...node_process.default.execArgv, ...node_process.default.argv].join(" ").trim();
307
+ }
308
+ //#endregion
230
309
  //#region src/instance-lock.ts
231
310
  /**
232
311
  * instance-lock.ts:单实例锁(2026-08-07 根治「多实例抢数据目录锁挂起」)
@@ -242,6 +321,12 @@ function isNativeNoiseLine(line) {
242
321
  * - 锁存在但 PID 已死 → 崩溃残留,自动清理(rm + 重新获取)
243
322
  * - 锁存在且 PID 存活 → 已被占用,拒绝启动(返回占用 PID 供提示)
244
323
  *
324
+ * PID 复用根治(2026-09-10):PID 探活通过后再比对锁内 cmdline 摘要与该
325
+ * PID 当前真实命令行(pid-cmdline.ts)——查不到或不一致 → PID 已被无关
326
+ * 进程复用(持有者已死),视为残留允许接管。旧锁(无 cmdline 字段)无法
327
+ * 校验,保守维持「占用」判定(一次性过渡:新锁写入时自动带摘要)。详见
328
+ * pid-cmdline.ts 头注释的误判代价取舍。
329
+ *
245
330
  * 退出清理:进程正常退出时 releaseInstanceLock 删除锁文件;崩溃/强杀残留
246
331
  * 由下次启动的「PID 存活检测」兜底(PID 已死 → 残留视为过期)。
247
332
  *
@@ -285,6 +370,15 @@ function checkInstanceLock(dataDir) {
285
370
  pid: null,
286
371
  lockPath
287
372
  };
373
+ const lockCmdline = typeof info.cmdline === "string" && info.cmdline !== "" ? info.cmdline : void 0;
374
+ if (lockCmdline !== void 0 && CMDLINE_PROBE_SUPPORTED) {
375
+ const current = readCmdlineForPid(info.pid);
376
+ if (current === null || normalizeCmdline(current) !== normalizeCmdline(lockCmdline)) return {
377
+ occupied: false,
378
+ pid: null,
379
+ lockPath
380
+ };
381
+ }
288
382
  return {
289
383
  occupied: true,
290
384
  pid: info.pid,
@@ -301,10 +395,11 @@ function acquireInstanceLock(dataDir, cmdline) {
301
395
  if (pid === node_process.default.pid) return true;
302
396
  return false;
303
397
  }
398
+ const effectiveCmdline = cmdline !== void 0 && cmdline !== "" ? cmdline : selfCmdline();
304
399
  const info = {
305
400
  pid: node_process.default.pid,
306
401
  startedAt: Date.now(),
307
- ...cmdline !== void 0 && cmdline !== "" ? { cmdline } : {}
402
+ ...effectiveCmdline !== "" ? { cmdline: effectiveCmdline } : {}
308
403
  };
309
404
  try {
310
405
  (0, node_fs.rmSync)(lockPath, { force: true });
@@ -1297,6 +1392,7 @@ exports.linuxSevenZipUrl = linuxSevenZipUrl;
1297
1392
  exports.loadQqReleases = loadQqReleases;
1298
1393
  exports.locateQqPath = locateQqPath;
1299
1394
  exports.nodeZipUrl = nodeZipUrl;
1395
+ exports.readCmdlineForPid = readCmdlineForPid;
1300
1396
  exports.registerLockCleanup = registerLockCleanup;
1301
1397
  exports.releaseInstanceLock = releaseInstanceLock;
1302
1398
  exports.resolveDownloadUrl = resolveDownloadUrl;
package/dist/index.d.cts CHANGED
@@ -370,6 +370,12 @@ declare function isNativeNoiseLine(line: string): boolean;
370
370
  * - 锁存在但 PID 已死 → 崩溃残留,自动清理(rm + 重新获取)
371
371
  * - 锁存在且 PID 存活 → 已被占用,拒绝启动(返回占用 PID 供提示)
372
372
  *
373
+ * PID 复用根治(2026-09-10):PID 探活通过后再比对锁内 cmdline 摘要与该
374
+ * PID 当前真实命令行(pid-cmdline.ts)——查不到或不一致 → PID 已被无关
375
+ * 进程复用(持有者已死),视为残留允许接管。旧锁(无 cmdline 字段)无法
376
+ * 校验,保守维持「占用」判定(一次性过渡:新锁写入时自动带摘要)。详见
377
+ * pid-cmdline.ts 头注释的误判代价取舍。
378
+ *
373
379
  * 退出清理:进程正常退出时 releaseInstanceLock 删除锁文件;崩溃/强杀残留
374
380
  * 由下次启动的「PID 存活检测」兜底(PID 已死 → 残留视为过期)。
375
381
  *
@@ -620,6 +626,15 @@ declare const ENV: {
620
626
  readonly IPC: "NAPUTO_IPC";
621
627
  };
622
628
  //#endregion
629
+ //#region src/pid-cmdline.d.ts
630
+ /**
631
+ * 查询指定 pid 的当前命令行。
632
+ *
633
+ * @returns 命令行原文;进程不存在 / 命令行不可得(权限、系统进程 null)/
634
+ * 查询机制失败(超时、PowerShell 缺失)均返回 null(调用方视为持有者已死)
635
+ */
636
+ declare function readCmdlineForPid(pid: number): string | null;
637
+ //#endregion
623
638
  //#region src/qq-download.d.ts
624
639
  /** 下载选项。 */
625
640
  interface DownloadOptions {
@@ -757,5 +772,5 @@ declare function buildSpawnCommand(options: {
757
772
  selfHostPath: string;
758
773
  }): SpawnCommand;
759
774
  //#endregion
760
- export { DownloadError, type DownloadOptions, type DownloadResult, ENV, type ExtractOptions, INSTANCE_LOCK_FILE, IPC_VERSION, type InstanceLockCheck, type InstanceLockInfo, type IpcActionMessage, type IpcActionPayload, IpcActionPayloadSchema, type IpcBootPhase, IpcBootPhaseSchema, type IpcControlMessage, type IpcControlPayload, IpcControlPayloadSchema, type IpcEventMessage, type IpcEventPayload, IpcEventPayloadSchema, type IpcLogLevel, IpcLogLevelSchema, type IpcLogMessage, type IpcLogPayload, IpcLogPayloadSchema, type IpcLoginMessage, type IpcLoginPayload, IpcLoginPayloadSchema, type IpcMessage, IpcMessageSchema, type IpcPingMessage, type IpcPongMessage, type IpcQrMessage, type IpcResultMessage, type IpcResultPayload, IpcResultPayloadSchema, type IpcStatusMessage, type IpcStatusPayload, IpcStatusPayloadSchema, type LaunchOptions, type LaunchResult, QQ_FILES_DIR_NAME, type QqFileSource, type QqInstallInfo, type QqReleaseEntry, QqReleasesError, type QqReleasesFile, type ResolveQqFilesOptions, type SevenZipResult, type SpawnCommand, WIN_NODE_DIR_NAME, type WinNodeInfo, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureLinuxSevenZip, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, isNativeNoiseLine, latestRelease, launchSelfHost, linuxSevenZipUrl, loadQqReleases, locateQqPath, nodeZipUrl, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
775
+ export { DownloadError, type DownloadOptions, type DownloadResult, ENV, type ExtractOptions, INSTANCE_LOCK_FILE, IPC_VERSION, type InstanceLockCheck, type InstanceLockInfo, type IpcActionMessage, type IpcActionPayload, IpcActionPayloadSchema, type IpcBootPhase, IpcBootPhaseSchema, type IpcControlMessage, type IpcControlPayload, IpcControlPayloadSchema, type IpcEventMessage, type IpcEventPayload, IpcEventPayloadSchema, type IpcLogLevel, IpcLogLevelSchema, type IpcLogMessage, type IpcLogPayload, IpcLogPayloadSchema, type IpcLoginMessage, type IpcLoginPayload, IpcLoginPayloadSchema, type IpcMessage, IpcMessageSchema, type IpcPingMessage, type IpcPongMessage, type IpcQrMessage, type IpcResultMessage, type IpcResultPayload, IpcResultPayloadSchema, type IpcStatusMessage, type IpcStatusPayload, IpcStatusPayloadSchema, type LaunchOptions, type LaunchResult, QQ_FILES_DIR_NAME, type QqFileSource, type QqInstallInfo, type QqReleaseEntry, QqReleasesError, type QqReleasesFile, type ResolveQqFilesOptions, type SevenZipResult, type SpawnCommand, WIN_NODE_DIR_NAME, type WinNodeInfo, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureLinuxSevenZip, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, isNativeNoiseLine, latestRelease, launchSelfHost, linuxSevenZipUrl, loadQqReleases, locateQqPath, nodeZipUrl, readCmdlineForPid, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
761
776
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/host/ipc/ipc-types.ts","../src/host/ipc/native-noise.ts","../src/instance-lock.ts","../src/locate-qq.ts","../src/win-node.ts","../src/launcher.ts","../src/qq-download.ts","../src/qq-extract.ts","../src/qq-releases.ts","../src/wine.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/host/ipc/ipc-types.ts","../src/host/ipc/native-noise.ts","../src/instance-lock.ts","../src/locate-qq.ts","../src/win-node.ts","../src/launcher.ts","../src/pid-cmdline.ts","../src/qq-download.ts","../src/qq-extract.ts","../src/qq-releases.ts","../src/wine.ts"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -370,6 +370,12 @@ declare function isNativeNoiseLine(line: string): boolean;
370
370
  * - 锁存在但 PID 已死 → 崩溃残留,自动清理(rm + 重新获取)
371
371
  * - 锁存在且 PID 存活 → 已被占用,拒绝启动(返回占用 PID 供提示)
372
372
  *
373
+ * PID 复用根治(2026-09-10):PID 探活通过后再比对锁内 cmdline 摘要与该
374
+ * PID 当前真实命令行(pid-cmdline.ts)——查不到或不一致 → PID 已被无关
375
+ * 进程复用(持有者已死),视为残留允许接管。旧锁(无 cmdline 字段)无法
376
+ * 校验,保守维持「占用」判定(一次性过渡:新锁写入时自动带摘要)。详见
377
+ * pid-cmdline.ts 头注释的误判代价取舍。
378
+ *
373
379
  * 退出清理:进程正常退出时 releaseInstanceLock 删除锁文件;崩溃/强杀残留
374
380
  * 由下次启动的「PID 存活检测」兜底(PID 已死 → 残留视为过期)。
375
381
  *
@@ -620,6 +626,15 @@ declare const ENV: {
620
626
  readonly IPC: "NAPUTO_IPC";
621
627
  };
622
628
  //#endregion
629
+ //#region src/pid-cmdline.d.ts
630
+ /**
631
+ * 查询指定 pid 的当前命令行。
632
+ *
633
+ * @returns 命令行原文;进程不存在 / 命令行不可得(权限、系统进程 null)/
634
+ * 查询机制失败(超时、PowerShell 缺失)均返回 null(调用方视为持有者已死)
635
+ */
636
+ declare function readCmdlineForPid(pid: number): string | null;
637
+ //#endregion
623
638
  //#region src/qq-download.d.ts
624
639
  /** 下载选项。 */
625
640
  interface DownloadOptions {
@@ -757,5 +772,5 @@ declare function buildSpawnCommand(options: {
757
772
  selfHostPath: string;
758
773
  }): SpawnCommand;
759
774
  //#endregion
760
- export { DownloadError, type DownloadOptions, type DownloadResult, ENV, type ExtractOptions, INSTANCE_LOCK_FILE, IPC_VERSION, type InstanceLockCheck, type InstanceLockInfo, type IpcActionMessage, type IpcActionPayload, IpcActionPayloadSchema, type IpcBootPhase, IpcBootPhaseSchema, type IpcControlMessage, type IpcControlPayload, IpcControlPayloadSchema, type IpcEventMessage, type IpcEventPayload, IpcEventPayloadSchema, type IpcLogLevel, IpcLogLevelSchema, type IpcLogMessage, type IpcLogPayload, IpcLogPayloadSchema, type IpcLoginMessage, type IpcLoginPayload, IpcLoginPayloadSchema, type IpcMessage, IpcMessageSchema, type IpcPingMessage, type IpcPongMessage, type IpcQrMessage, type IpcResultMessage, type IpcResultPayload, IpcResultPayloadSchema, type IpcStatusMessage, type IpcStatusPayload, IpcStatusPayloadSchema, type LaunchOptions, type LaunchResult, QQ_FILES_DIR_NAME, type QqFileSource, type QqInstallInfo, type QqReleaseEntry, QqReleasesError, type QqReleasesFile, type ResolveQqFilesOptions, type SevenZipResult, type SpawnCommand, WIN_NODE_DIR_NAME, type WinNodeInfo, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureLinuxSevenZip, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, isNativeNoiseLine, latestRelease, launchSelfHost, linuxSevenZipUrl, loadQqReleases, locateQqPath, nodeZipUrl, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
775
+ export { DownloadError, type DownloadOptions, type DownloadResult, ENV, type ExtractOptions, INSTANCE_LOCK_FILE, IPC_VERSION, type InstanceLockCheck, type InstanceLockInfo, type IpcActionMessage, type IpcActionPayload, IpcActionPayloadSchema, type IpcBootPhase, IpcBootPhaseSchema, type IpcControlMessage, type IpcControlPayload, IpcControlPayloadSchema, type IpcEventMessage, type IpcEventPayload, IpcEventPayloadSchema, type IpcLogLevel, IpcLogLevelSchema, type IpcLogMessage, type IpcLogPayload, IpcLogPayloadSchema, type IpcLoginMessage, type IpcLoginPayload, IpcLoginPayloadSchema, type IpcMessage, IpcMessageSchema, type IpcPingMessage, type IpcPongMessage, type IpcQrMessage, type IpcResultMessage, type IpcResultPayload, IpcResultPayloadSchema, type IpcStatusMessage, type IpcStatusPayload, IpcStatusPayloadSchema, type LaunchOptions, type LaunchResult, QQ_FILES_DIR_NAME, type QqFileSource, type QqInstallInfo, type QqReleaseEntry, QqReleasesError, type QqReleasesFile, type ResolveQqFilesOptions, type SevenZipResult, type SpawnCommand, WIN_NODE_DIR_NAME, type WinNodeInfo, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureLinuxSevenZip, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, isNativeNoiseLine, latestRelease, launchSelfHost, linuxSevenZipUrl, loadQqReleases, locateQqPath, nodeZipUrl, readCmdlineForPid, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
761
776
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/host/ipc/ipc-types.ts","../src/host/ipc/native-noise.ts","../src/instance-lock.ts","../src/locate-qq.ts","../src/win-node.ts","../src/launcher.ts","../src/qq-download.ts","../src/qq-extract.ts","../src/qq-releases.ts","../src/wine.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/host/ipc/ipc-types.ts","../src/host/ipc/native-noise.ts","../src/instance-lock.ts","../src/locate-qq.ts","../src/win-node.ts","../src/launcher.ts","../src/pid-cmdline.ts","../src/qq-download.ts","../src/qq-extract.ts","../src/qq-releases.ts","../src/wine.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -203,6 +203,85 @@ function isNativeNoiseLine(line) {
203
203
  return NATIVE_NOISE.test(line);
204
204
  }
205
205
  //#endregion
206
+ //#region src/pid-cmdline.ts
207
+ /**
208
+ * pid-cmdline.ts:跨进程命令行查询(instance-lock pid 复用根治,2026-09-10)。
209
+ *
210
+ * 背景(2026-09-08 实测事故):锁内 pid 的持有者已死、pid 被 Windows 复用给
211
+ * 无关进程时,process.kill(pid, 0) 探活成功 → 误判存活 → 新实例永久拒绝启动
212
+ * (只能手动删锁恢复)。根治:探活通过后再查该 pid 当前的真实命令行,与锁内
213
+ * cmdline 摘要比对——查不到(进程已退/查询失败)或不一致(pid 复用)均视为
214
+ * 持有者已死,允许接管。误判方向取舍:误判「死亡」→ 双实例抢 QQ 原生锁,
215
+ * 后启动者挂起退出(可恢复);误判「存活」→ 永久锁死需人工干预——前者代价
216
+ * 远低,故查询失败宁可接管。
217
+ *
218
+ * 平台实现:Windows 走 PowerShell Get-CimInstance Win32_Process 跨进程查询
219
+ * (仅在启动判活时调用一次,冷启动 ~0.5-2s 可接受;wmic 在新 Win11 已移除,
220
+ * 不可用)。Linux 走 /proc/<pid>/cmdline(\0 分隔)。其余平台(darwin 等)
221
+ * 不支持——判活退回纯 pid 探测(旧行为)。
222
+ *
223
+ * 比对不要求逐字符相等:锁内摘要来自 process.execArgv/argv join,查询侧
224
+ * 拿到的是操作系统记录的原始命令行,两者存在引号 / 大小写 / 路径分隔符差异
225
+ * ——统一经 normalizeCmdline 规范化后比对。残余差异源(8.3 短路径、相对
226
+ * 启动路径)极罕见,误判后果同上(可恢复),接受。
227
+ */
228
+ /** 当前平台是否支持跨进程 cmdline 查询(不支持则判活退回纯 pid 探测)。 */
229
+ const CMDLINE_PROBE_SUPPORTED = process$1.platform === "win32" || process$1.platform === "linux";
230
+ /**
231
+ * 查询指定 pid 的当前命令行。
232
+ *
233
+ * @returns 命令行原文;进程不存在 / 命令行不可得(权限、系统进程 null)/
234
+ * 查询机制失败(超时、PowerShell 缺失)均返回 null(调用方视为持有者已死)
235
+ */
236
+ function readCmdlineForPid(pid) {
237
+ if (process$1.platform === "win32") return readWindowsCmdline(pid);
238
+ if (process$1.platform === "linux") return readLinuxCmdline(pid);
239
+ return null;
240
+ }
241
+ /** Windows:PowerShell Get-CimInstance 查询(进程不存在时输出空 → null)。 */
242
+ function readWindowsCmdline(pid) {
243
+ try {
244
+ const cmdline = execFileSync("powershell.exe", [
245
+ "-NoProfile",
246
+ "-NonInteractive",
247
+ "-ExecutionPolicy",
248
+ "Bypass",
249
+ "-Command",
250
+ `[Console]::OutputEncoding=[System.Text.Encoding]::UTF8;(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CommandLine`
251
+ ], {
252
+ encoding: "utf-8",
253
+ timeout: 1e4,
254
+ windowsHide: true
255
+ }).trim();
256
+ return cmdline === "" ? null : cmdline;
257
+ } catch {
258
+ return null;
259
+ }
260
+ }
261
+ /** Linux:/proc/<pid>/cmdline(\0 分隔转空格;进程不存在抛错 → null)。 */
262
+ function readLinuxCmdline(pid) {
263
+ try {
264
+ const cmdline = readFileSync(`/proc/${pid}/cmdline`, "utf-8").replaceAll("\0", " ").trim();
265
+ return cmdline === "" ? null : cmdline;
266
+ } catch {
267
+ return null;
268
+ }
269
+ }
270
+ /**
271
+ * 命令行规范化(比对用):统一大小写 / 去引号 / 路径分隔符归一 / 折叠空白。
272
+ * Windows 路径大小写不敏感,lowercase 安全。
273
+ */
274
+ function normalizeCmdline(cmdline) {
275
+ return cmdline.toLowerCase().replaceAll("\"", "").replaceAll("\\", "/").replace(/\s+/g, " ").trim();
276
+ }
277
+ /**
278
+ * 本进程命令行摘要(写锁用):execArgv(node 选项)+ argv(脚本与参数),
279
+ * 与操作系统记录的完整命令行结构一致(规范化后可比对)。
280
+ */
281
+ function selfCmdline() {
282
+ return [...process$1.execArgv, ...process$1.argv].join(" ").trim();
283
+ }
284
+ //#endregion
206
285
  //#region src/instance-lock.ts
207
286
  /**
208
287
  * instance-lock.ts:单实例锁(2026-08-07 根治「多实例抢数据目录锁挂起」)
@@ -218,6 +297,12 @@ function isNativeNoiseLine(line) {
218
297
  * - 锁存在但 PID 已死 → 崩溃残留,自动清理(rm + 重新获取)
219
298
  * - 锁存在且 PID 存活 → 已被占用,拒绝启动(返回占用 PID 供提示)
220
299
  *
300
+ * PID 复用根治(2026-09-10):PID 探活通过后再比对锁内 cmdline 摘要与该
301
+ * PID 当前真实命令行(pid-cmdline.ts)——查不到或不一致 → PID 已被无关
302
+ * 进程复用(持有者已死),视为残留允许接管。旧锁(无 cmdline 字段)无法
303
+ * 校验,保守维持「占用」判定(一次性过渡:新锁写入时自动带摘要)。详见
304
+ * pid-cmdline.ts 头注释的误判代价取舍。
305
+ *
221
306
  * 退出清理:进程正常退出时 releaseInstanceLock 删除锁文件;崩溃/强杀残留
222
307
  * 由下次启动的「PID 存活检测」兜底(PID 已死 → 残留视为过期)。
223
308
  *
@@ -261,6 +346,15 @@ function checkInstanceLock(dataDir) {
261
346
  pid: null,
262
347
  lockPath
263
348
  };
349
+ const lockCmdline = typeof info.cmdline === "string" && info.cmdline !== "" ? info.cmdline : void 0;
350
+ if (lockCmdline !== void 0 && CMDLINE_PROBE_SUPPORTED) {
351
+ const current = readCmdlineForPid(info.pid);
352
+ if (current === null || normalizeCmdline(current) !== normalizeCmdline(lockCmdline)) return {
353
+ occupied: false,
354
+ pid: null,
355
+ lockPath
356
+ };
357
+ }
264
358
  return {
265
359
  occupied: true,
266
360
  pid: info.pid,
@@ -277,10 +371,11 @@ function acquireInstanceLock(dataDir, cmdline) {
277
371
  if (pid === process$1.pid) return true;
278
372
  return false;
279
373
  }
374
+ const effectiveCmdline = cmdline !== void 0 && cmdline !== "" ? cmdline : selfCmdline();
280
375
  const info = {
281
376
  pid: process$1.pid,
282
377
  startedAt: Date.now(),
283
- ...cmdline !== void 0 && cmdline !== "" ? { cmdline } : {}
378
+ ...effectiveCmdline !== "" ? { cmdline: effectiveCmdline } : {}
284
379
  };
285
380
  try {
286
381
  rmSync(lockPath, { force: true });
@@ -1236,6 +1331,6 @@ async function ensureQqFiles(options = {}) {
1236
1331
  }
1237
1332
  }
1238
1333
  //#endregion
1239
- export { DownloadError, ENV, INSTANCE_LOCK_FILE, IPC_VERSION, IpcActionPayloadSchema, IpcBootPhaseSchema, IpcControlPayloadSchema, IpcEventPayloadSchema, IpcLogLevelSchema, IpcLogPayloadSchema, IpcLoginPayloadSchema, IpcMessageSchema, IpcResultPayloadSchema, IpcStatusPayloadSchema, QQ_FILES_DIR_NAME, QqReleasesError, WIN_NODE_DIR_NAME, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureLinuxSevenZip, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, isNativeNoiseLine, latestRelease, launchSelfHost, linuxSevenZipUrl, loadQqReleases, locateQqPath, nodeZipUrl, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
1334
+ export { DownloadError, ENV, INSTANCE_LOCK_FILE, IPC_VERSION, IpcActionPayloadSchema, IpcBootPhaseSchema, IpcControlPayloadSchema, IpcEventPayloadSchema, IpcLogLevelSchema, IpcLogPayloadSchema, IpcLoginPayloadSchema, IpcMessageSchema, IpcResultPayloadSchema, IpcStatusPayloadSchema, QQ_FILES_DIR_NAME, QqReleasesError, WIN_NODE_DIR_NAME, acquireInstanceLock, buildSpawnCommand, checkInstanceLock, clearCacheVersion, defaultStubDir, downloadFile, ensureLinuxSevenZip, ensureQqFiles, ensureWinNode, extractInstaller, extractWrapperFiles, findSevenZip, isLinux, isNativeNoiseLine, latestRelease, launchSelfHost, linuxSevenZipUrl, loadQqReleases, locateQqPath, nodeZipUrl, readCmdlineForPid, registerLockCleanup, releaseInstanceLock, resolveDownloadUrl, resolveQqFiles, resolveQqInstall, toWinePath, wineBinary };
1240
1335
 
1241
1336
  //# sourceMappingURL=index.mjs.map