@napuketto/loader 0.0.14 → 0.0.17

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.
@@ -25,6 +25,7 @@ let node_path = require("node:path");
25
25
  let node_process = require("node:process");
26
26
  node_process = __toESM(node_process, 1);
27
27
  let node_os = require("node:os");
28
+ let zod = require("zod");
28
29
  let node_readline = require("node:readline");
29
30
  //#region src/instance-lock.ts
30
31
  /**
@@ -459,20 +460,156 @@ function errorCodeOf(err) {
459
460
  }
460
461
  return "UNKNOWN";
461
462
  }
462
- //#endregion
463
- //#region src/host/ipc/ipc-types.ts
464
- /** 消息类型集合(解码校验用)。 */
465
- const IPC_MESSAGE_TYPES = /* @__PURE__ */ new Set([
466
- "status",
467
- "login",
468
- "qr",
469
- "event",
470
- "result",
471
- "log",
472
- "ping",
473
- "pong",
474
- "action",
475
- "control"
463
+ /** 引导阶段(status 消息)。 */
464
+ const IpcBootPhaseSchema = zod.z.enum([
465
+ "booting",
466
+ "dlopening",
467
+ "logging",
468
+ "sessioning",
469
+ "ready",
470
+ "failed"
471
+ ]);
472
+ /** 结构化日志级别(pino level)。 */
473
+ const IpcLogLevelSchema = zod.z.enum([
474
+ "trace",
475
+ "debug",
476
+ "info",
477
+ "warn",
478
+ "error",
479
+ "fatal"
480
+ ]);
481
+ /** 登录状态(与 kernel LoginState 结构等价)。 */
482
+ const LoginStateSchema = zod.z.enum([
483
+ "idle",
484
+ "waiting_scan",
485
+ "scanned",
486
+ "logged_in",
487
+ "failed"
488
+ ]);
489
+ /** selfInfo(与 kernel SelfInfo 结构等价)。 */
490
+ const SelfInfoSchema = zod.z.object({
491
+ uin: zod.z.string(),
492
+ uid: zod.z.string(),
493
+ nick: zod.z.string()
494
+ });
495
+ /** status 消息 payload。 */
496
+ const IpcStatusPayloadSchema = zod.z.object({
497
+ phase: IpcBootPhaseSchema,
498
+ message: zod.z.string().optional(),
499
+ error: zod.z.object({
500
+ code: zod.z.string(),
501
+ message: zod.z.string()
502
+ }).optional()
503
+ });
504
+ /** login 消息 payload。 */
505
+ const IpcLoginPayloadSchema = zod.z.object({
506
+ state: LoginStateSchema,
507
+ selfInfo: SelfInfoSchema.optional(),
508
+ /** 失败原因(state=failed 时;如「登录超时,请刷新页面重试」)。 */
509
+ message: zod.z.string().optional()
510
+ });
511
+ /** event 消息 payload(kernel 事件通道形状,翻译层按 service/name 断言具体类型)。 */
512
+ const IpcEventPayloadSchema = zod.z.object({
513
+ service: zod.z.string(),
514
+ name: zod.z.string(),
515
+ args: zod.z.array(zod.z.unknown())
516
+ });
517
+ /** log 消息 payload。 */
518
+ const IpcLogPayloadSchema = zod.z.object({
519
+ level: IpcLogLevelSchema,
520
+ message: zod.z.string()
521
+ });
522
+ /** 动作响应 payload(result 消息)。 */
523
+ const IpcResultPayloadSchema = zod.z.union([zod.z.object({
524
+ ok: zod.z.literal(true),
525
+ value: zod.z.unknown().optional()
526
+ }), zod.z.object({
527
+ ok: zod.z.literal(false),
528
+ error: zod.z.object({
529
+ code: zod.z.string(),
530
+ message: zod.z.string()
531
+ })
532
+ })]);
533
+ /** 动作请求 payload(action 消息)。 */
534
+ const IpcActionPayloadSchema = zod.z.object({
535
+ action: zod.z.string(),
536
+ params: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
537
+ });
538
+ /** 控制指令 payload(control 消息,父→子)。 */
539
+ const IpcControlPayloadSchema = zod.z.union([
540
+ zod.z.object({ command: zod.z.literal("stop") }),
541
+ zod.z.object({ command: zod.z.literal("restart") }),
542
+ zod.z.object({
543
+ command: zod.z.literal("login"),
544
+ uin: zod.z.string().optional(),
545
+ qr: zod.z.boolean().optional()
546
+ })
547
+ ]);
548
+ const IpcStatusMessageSchema = zod.z.object({
549
+ v: zod.z.literal(1),
550
+ type: zod.z.literal("status"),
551
+ payload: IpcStatusPayloadSchema
552
+ });
553
+ const IpcLoginMessageSchema = zod.z.object({
554
+ v: zod.z.literal(1),
555
+ type: zod.z.literal("login"),
556
+ payload: IpcLoginPayloadSchema
557
+ });
558
+ const IpcQrMessageSchema = zod.z.object({
559
+ v: zod.z.literal(1),
560
+ type: zod.z.literal("qr"),
561
+ payload: zod.z.object({
562
+ pngBase64: zod.z.string(),
563
+ qrcodeUrl: zod.z.string()
564
+ })
565
+ });
566
+ const IpcEventMessageSchema = zod.z.object({
567
+ v: zod.z.literal(1),
568
+ type: zod.z.literal("event"),
569
+ payload: IpcEventPayloadSchema
570
+ });
571
+ const IpcResultMessageSchema = zod.z.object({
572
+ v: zod.z.literal(1),
573
+ type: zod.z.literal("result"),
574
+ id: zod.z.number(),
575
+ payload: IpcResultPayloadSchema
576
+ });
577
+ const IpcLogMessageSchema = zod.z.object({
578
+ v: zod.z.literal(1),
579
+ type: zod.z.literal("log"),
580
+ payload: IpcLogPayloadSchema
581
+ });
582
+ const IpcPingMessageSchema = zod.z.object({
583
+ v: zod.z.literal(1),
584
+ type: zod.z.literal("ping")
585
+ });
586
+ const IpcPongMessageSchema = zod.z.object({
587
+ v: zod.z.literal(1),
588
+ type: zod.z.literal("pong")
589
+ });
590
+ const IpcActionMessageSchema = zod.z.object({
591
+ v: zod.z.literal(1),
592
+ type: zod.z.literal("action"),
593
+ id: zod.z.number(),
594
+ payload: IpcActionPayloadSchema
595
+ });
596
+ const IpcControlMessageSchema = zod.z.object({
597
+ v: zod.z.literal(1),
598
+ type: zod.z.literal("control"),
599
+ payload: IpcControlPayloadSchema
600
+ });
601
+ /** IPC 消息判别联合(协议边界,运行时经 safeParse 校验)。 */
602
+ const IpcMessageSchema = zod.z.discriminatedUnion("type", [
603
+ IpcStatusMessageSchema,
604
+ IpcLoginMessageSchema,
605
+ IpcQrMessageSchema,
606
+ IpcEventMessageSchema,
607
+ IpcResultMessageSchema,
608
+ IpcLogMessageSchema,
609
+ IpcPingMessageSchema,
610
+ IpcPongMessageSchema,
611
+ IpcActionMessageSchema,
612
+ IpcControlMessageSchema
476
613
  ]);
477
614
  //#endregion
478
615
  //#region src/host/ipc/ipc-sender.ts
@@ -615,22 +752,18 @@ function attachIpcServices(actions, services) {
615
752
  /**
616
753
  * ipc-codec.ts:IPC 消息编解码(JSON 行协议,与 koishi 插件侧 src/ipc/codec.ts 对齐)。
617
754
  *
618
- * 每行一条 JSON 消息,`\n` 结尾。解码宽松:非法行 / 空行 / v 不匹配 /
619
- * 未知 type → null,调用方忽略,不崩通道。
755
+ * 每行一条 JSON 消息,`\n` 结尾。解码用 IpcMessageSchema.safeParse 校验:
756
+ * 非法行 / 空行 / v 不匹配 / type 未知 / payload 形状非法 → null,调用方忽略,
757
+ * 不崩通道。
620
758
  */
621
- /** 类型守卫:v 匹配 + type 合法。 */
622
- function isIpcMessage(value) {
623
- if (typeof value !== "object" || value === null) return false;
624
- const record = value;
625
- return record["v"] === 1 && typeof record["type"] === "string" && IPC_MESSAGE_TYPES.has(record["type"]);
626
- }
627
- /** 解码一行 JSON;非法行/空行返回 null。 */
759
+ /** 解码一行 JSON;非法行/空行/形状不合法返回 null。 */
628
760
  function decodeIpcMessage(line) {
629
761
  const trimmed = line.trim();
630
762
  if (trimmed === "") return null;
631
763
  try {
632
764
  const parsed = JSON.parse(trimmed);
633
- return isIpcMessage(parsed) ? parsed : null;
765
+ const result = IpcMessageSchema.safeParse(parsed);
766
+ return result.success ? result.data : null;
634
767
  } catch {
635
768
  return null;
636
769
  }
@@ -644,6 +777,7 @@ function decodeIpcMessage(line) {
644
777
  * - stdin readline 收 action / control / ping
645
778
  * - action → 动作表执行 → result 响应(请求 id 匹配)
646
779
  * - control stop/restart → 退出回调(默认 process.exit(0),由驱动层重启)
780
+ * - control login → onLogin 回调(触发重新登录 / 强制扫码)
647
781
  * - ping → 自动回 pong
648
782
  * - 心跳:定期发 ping(koishi 插件探活)
649
783
  */
@@ -665,7 +799,7 @@ function startIpcServer(options) {
665
799
  handleAction(message.id, message.payload.action, message.payload.params, options.actions);
666
800
  break;
667
801
  case "control":
668
- handleControl(message.payload, onExit);
802
+ handleControl(message.payload, onExit, options.onLogin);
669
803
  break;
670
804
  case "ping": sendPong();
671
805
  }
@@ -680,9 +814,16 @@ function startIpcServer(options) {
680
814
  async function handleAction(id, action, params, actions) {
681
815
  sendResult(id, await callIpcAction(actions, action, params));
682
816
  }
683
- /** 控制指令处理(stop/restart 退出;login 预留——引导期登录由 bootstrap 内部驱动)。 */
684
- function handleControl(payload, onExit) {
685
- if (payload.command === "stop" || payload.command === "restart") onExit();
817
+ /** 控制指令处理(stop/restart 退出;login 触发重新登录 / 强制扫码)。 */
818
+ function handleControl(payload, onExit, onLogin) {
819
+ if (payload.command === "stop" || payload.command === "restart") {
820
+ onExit();
821
+ return;
822
+ }
823
+ if (payload.command === "login") onLogin?.({
824
+ ...payload.uin !== void 0 ? { uin: payload.uin } : {},
825
+ ...payload.qr !== void 0 ? { qr: payload.qr } : {}
826
+ });
686
827
  }
687
828
  //#endregion
688
829
  //#region src/host/core/login.ts
@@ -1312,6 +1453,33 @@ function buildLoginOpts(Appid, forcedUin, ref) {
1312
1453
  };
1313
1454
  return opts;
1314
1455
  }
1456
+ /** control login 指令 → 重新登录(qr=true 强制扫码跳过快速登录,uin 指定账号)。 */
1457
+ function createLoginControlHandler(core, Appid) {
1458
+ return (payload) => {
1459
+ const ipcMode = env.NAPUTO_IPC === "1";
1460
+ const opts = {
1461
+ appid: String(Appid),
1462
+ initTimeoutMs: 2e4,
1463
+ qrFallback: true,
1464
+ ...payload.uin !== void 0 ? { quickUin: payload.uin } : {},
1465
+ ...payload.qr === true ? { qrOnly: true } : {},
1466
+ onLoginProgress: (progress) => {
1467
+ if (ipcMode && progress.qr !== void 0) sendQr(progress.qr.pngBase64, progress.qr.qrcodeUrl);
1468
+ if (ipcMode && isLoginState(progress.state)) sendLogin(progress.state, progress.selfInfo, progress.message);
1469
+ }
1470
+ };
1471
+ core.login(opts).then((result) => {
1472
+ if (ipcMode && result !== null) sendLogin("logged_in", {
1473
+ uin: result.uin,
1474
+ uid: result.uid,
1475
+ nick: result.nick ?? ""
1476
+ });
1477
+ }).catch((err) => {
1478
+ log(`bootstrap: control login 失败: ${errMsg(err)}`);
1479
+ if (ipcMode) sendLogin("failed", void 0, errMsg(err));
1480
+ });
1481
+ };
1482
+ }
1315
1483
  /** V2 登录后替换 session:优先 QQ 主 session,其次 vehicle 单例表;自建宿主例外。 */
1316
1484
  function replaceSession(kernel, core, state, ctx) {
1317
1485
  if (env.NAPUTO_SELF_HOST === "1") return null;
@@ -1337,13 +1505,15 @@ async function activateSession(kernel, state, ctx, chosen, loginResult, Appid, b
1337
1505
  try {
1338
1506
  const qqDataRoot = typeof kernel.resolveQqUserDataRoot === "function" ? kernel.resolveQqUserDataRoot(state.wrapperExports) : null;
1339
1507
  const accountPath = qqDataRoot && typeof kernel.resolveQqGlobalPath === "function" ? kernel.resolveQqGlobalPath(qqDataRoot) : bootEnv.dataDir || ".";
1508
+ const machineGuid = typeof kernel.readMachineGuid === "function" ? kernel.readMachineGuid(ctx.loginService) : "";
1340
1509
  const sessionConfig = kernel.buildSessionConfig({
1341
1510
  appid: Appid,
1342
1511
  fullVersion: bootEnv.qqVersion || "",
1343
1512
  selfUin: loginResult.uin,
1344
1513
  selfUid: loginResult.uid,
1345
1514
  accountPath,
1346
- downloadPath: (0, node_path.join)(accountPath, "NapCat", "temp")
1515
+ downloadPath: (0, node_path.join)(accountPath, "NapCat", "temp"),
1516
+ machineGuid
1347
1517
  });
1348
1518
  const listener = kernel.createLifecycleSessionListener();
1349
1519
  await kernel.initAndStartSession(ctx, sessionConfig, listener, { timeoutMs: 2e4 });
@@ -1400,7 +1570,10 @@ async function bootstrapWithCore(kernel, state, bootEnv, Appid) {
1400
1570
  let ipcActions = null;
1401
1571
  if (env.NAPUTO_IPC === "1") {
1402
1572
  ipcActions = createIpcActionsForCore(core);
1403
- startIpcServer({ actions: ipcActions });
1573
+ startIpcServer({
1574
+ actions: ipcActions,
1575
+ onLogin: createLoginControlHandler(core, Appid)
1576
+ });
1404
1577
  }
1405
1578
  const ctx = core.attachWrapper(state.wrapperExports, { ...bootEnv });
1406
1579
  log(`bootstrap: attachWrapper OK, engine=${typeof ctx.engine}, session=${ctx.session !== null}`);
@@ -1459,9 +1632,17 @@ async function bootstrapWithCore(kernel, state, bootEnv, Appid) {
1459
1632
  * 取到后 core.setSession 替换 → waitSessionReady 确认 getMsgService READY。
1460
1633
  */
1461
1634
  /** ⭐ appid 动态解析(2026-08-06 P2-0 实测:硬编码 537237765 在 9.9.33 扫码失败
1462
- * 「请下载最新版」;major.node 解析的 537376818 成功)。自研,参考 NapCat 思路。 */
1635
+ * 「请下载最新版」;major.node 解析的 537376818 成功)。自研,参考 NapCat 思路。
1636
+ * 2026-08 硬编码审计:删除第三层字面量兜底 537237765——解析失败复用 kernel 的
1637
+ * resolveAppidQua(已改为失败抛 KernelError),旧 kernel 则显式抛错,不再静默回退。 */
1463
1638
  function resolveAppid(kernel, bootEnv) {
1464
- return typeof kernel.parseAppidFromMajor === "function" && bootEnv.wrapperPath && kernel.parseAppidFromMajor((0, node_path.join)((0, node_path.dirname)(bootEnv.wrapperPath), "major.node")) || typeof kernel.resolveAppidQua === "function" && kernel.resolveAppidQua(bootEnv.qqVersion || "").appid || "537237765";
1639
+ const majorPath = bootEnv.wrapperPath ? (0, node_path.join)((0, node_path.dirname)(bootEnv.wrapperPath), "major.node") : void 0;
1640
+ if (typeof kernel.resolveAppidQua === "function") return kernel.resolveAppidQua(bootEnv.qqVersion || "", majorPath).appid;
1641
+ if (typeof kernel.parseAppidFromMajor === "function" && majorPath !== void 0) {
1642
+ const appid = kernel.parseAppidFromMajor(majorPath);
1643
+ if (appid !== null) return appid;
1644
+ }
1645
+ throw new Error("无法从 major.node 解析 appid,请确认 wrapper.node/major.node 完整,或更新 qq-releases.json");
1465
1646
  }
1466
1647
  /** 快速登录 + session 初始化(kernel lifecycle 路径;生命周期方法缺失则跳过)。 */
1467
1648
  async function quickLoginAndStartSession(kernel, ctx, Appid, bootEnv) {
@@ -1478,13 +1659,15 @@ async function quickLoginAndStartSession(kernel, ctx, Appid, bootEnv) {
1478
1659
  }
1479
1660
  const loginResult = await kernel.quickLogin(ctx, {});
1480
1661
  log(`bootstrap: quickLogin OK, uin=${loginResult.uin}, uid=${loginResult.uid}`);
1662
+ const machineGuid = typeof kernel.readMachineGuid === "function" ? kernel.readMachineGuid(ctx.loginService) : "";
1481
1663
  const sessionConfig = kernel.buildSessionConfig({
1482
1664
  appid: Appid,
1483
1665
  fullVersion: bootEnv.qqVersion || "",
1484
1666
  selfUin: loginResult.uin,
1485
1667
  selfUid: loginResult.uid,
1486
1668
  accountPath: bootEnv.dataDir || ".",
1487
- downloadPath: (0, node_path.join)(bootEnv.dataDir || ".", "temp")
1669
+ downloadPath: (0, node_path.join)(bootEnv.dataDir || ".", "temp"),
1670
+ machineGuid
1488
1671
  });
1489
1672
  const listener = kernel.createLifecycleSessionListener();
1490
1673
  await kernel.initAndStartSession(ctx, sessionConfig, listener, { timeoutMs: 2e4 });
@@ -1534,9 +1717,9 @@ async function bootstrap(state) {
1534
1717
  dataDir: env.NAPUTO_CFG_DIR || "",
1535
1718
  wrapperPath: env.NAPUTO_WRAPPER_PATH || ""
1536
1719
  };
1537
- const Appid = resolveAppid(kernel, bootEnv);
1538
- log(`bootstrap: appid=${Appid}(wrapper=${bootEnv.wrapperPath})`);
1539
1720
  try {
1721
+ const Appid = resolveAppid(kernel, bootEnv);
1722
+ log(`bootstrap: appid=${Appid}(wrapper=${bootEnv.wrapperPath})`);
1540
1723
  if (kernel.NapukettoCore !== void 0) await bootstrapWithCore(kernel, state, bootEnv, Appid);
1541
1724
  else await bootstrapFallback(kernel, state, bootEnv, Appid);
1542
1725
  } catch (e) {
package/dist/index.cjs CHANGED
@@ -21,6 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
+ let zod = require("zod");
24
25
  let node_fs = require("node:fs");
25
26
  let node_path = require("node:path");
26
27
  let node_process = require("node:process");
@@ -30,6 +31,171 @@ let node_url = require("node:url");
30
31
  let node_fs_promises = require("node:fs/promises");
31
32
  let node_crypto = require("node:crypto");
32
33
  let node_https = require("node:https");
34
+ //#region src/host/ipc/ipc-types.ts
35
+ /**
36
+ * ipc-types.ts:IPC 协议消息(koishi 插件 ↔ self-host 子进程,单一来源)。
37
+ *
38
+ * 协议契约(koishi 插件 design.md §5.6):`{ v, type, id?, payload }` JSON 行。
39
+ * 用 zod v4 定义单一 schema,两侧(loader host + koishi 插件)import 同一份:
40
+ * 类型经 z.infer 派生、运行时经 safeParse 校验,消除「两份手写 types.ts」漂移。
41
+ *
42
+ * 领域字段(LoginState / SelfInfo / QrCodeData)与 kernel 同名类型结构等价,
43
+ * 在协议边界内联为 zod schema(kernel 不依赖 zod,故不在 kernel 定义)。
44
+ */
45
+ /** IPC 协议版本(解码校验)。 */
46
+ const IPC_VERSION = 1;
47
+ /** 引导阶段(status 消息)。 */
48
+ const IpcBootPhaseSchema = zod.z.enum([
49
+ "booting",
50
+ "dlopening",
51
+ "logging",
52
+ "sessioning",
53
+ "ready",
54
+ "failed"
55
+ ]);
56
+ /** 结构化日志级别(pino level)。 */
57
+ const IpcLogLevelSchema = zod.z.enum([
58
+ "trace",
59
+ "debug",
60
+ "info",
61
+ "warn",
62
+ "error",
63
+ "fatal"
64
+ ]);
65
+ /** 登录状态(与 kernel LoginState 结构等价)。 */
66
+ const LoginStateSchema = zod.z.enum([
67
+ "idle",
68
+ "waiting_scan",
69
+ "scanned",
70
+ "logged_in",
71
+ "failed"
72
+ ]);
73
+ /** selfInfo(与 kernel SelfInfo 结构等价)。 */
74
+ const SelfInfoSchema = zod.z.object({
75
+ uin: zod.z.string(),
76
+ uid: zod.z.string(),
77
+ nick: zod.z.string()
78
+ });
79
+ /** status 消息 payload。 */
80
+ const IpcStatusPayloadSchema = zod.z.object({
81
+ phase: IpcBootPhaseSchema,
82
+ message: zod.z.string().optional(),
83
+ error: zod.z.object({
84
+ code: zod.z.string(),
85
+ message: zod.z.string()
86
+ }).optional()
87
+ });
88
+ /** login 消息 payload。 */
89
+ const IpcLoginPayloadSchema = zod.z.object({
90
+ state: LoginStateSchema,
91
+ selfInfo: SelfInfoSchema.optional(),
92
+ /** 失败原因(state=failed 时;如「登录超时,请刷新页面重试」)。 */
93
+ message: zod.z.string().optional()
94
+ });
95
+ /** event 消息 payload(kernel 事件通道形状,翻译层按 service/name 断言具体类型)。 */
96
+ const IpcEventPayloadSchema = zod.z.object({
97
+ service: zod.z.string(),
98
+ name: zod.z.string(),
99
+ args: zod.z.array(zod.z.unknown())
100
+ });
101
+ /** log 消息 payload。 */
102
+ const IpcLogPayloadSchema = zod.z.object({
103
+ level: IpcLogLevelSchema,
104
+ message: zod.z.string()
105
+ });
106
+ /** 动作响应 payload(result 消息)。 */
107
+ const IpcResultPayloadSchema = zod.z.union([zod.z.object({
108
+ ok: zod.z.literal(true),
109
+ value: zod.z.unknown().optional()
110
+ }), zod.z.object({
111
+ ok: zod.z.literal(false),
112
+ error: zod.z.object({
113
+ code: zod.z.string(),
114
+ message: zod.z.string()
115
+ })
116
+ })]);
117
+ /** 动作请求 payload(action 消息)。 */
118
+ const IpcActionPayloadSchema = zod.z.object({
119
+ action: zod.z.string(),
120
+ params: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
121
+ });
122
+ /** 控制指令 payload(control 消息,父→子)。 */
123
+ const IpcControlPayloadSchema = zod.z.union([
124
+ zod.z.object({ command: zod.z.literal("stop") }),
125
+ zod.z.object({ command: zod.z.literal("restart") }),
126
+ zod.z.object({
127
+ command: zod.z.literal("login"),
128
+ uin: zod.z.string().optional(),
129
+ qr: zod.z.boolean().optional()
130
+ })
131
+ ]);
132
+ const IpcStatusMessageSchema = zod.z.object({
133
+ v: zod.z.literal(1),
134
+ type: zod.z.literal("status"),
135
+ payload: IpcStatusPayloadSchema
136
+ });
137
+ const IpcLoginMessageSchema = zod.z.object({
138
+ v: zod.z.literal(1),
139
+ type: zod.z.literal("login"),
140
+ payload: IpcLoginPayloadSchema
141
+ });
142
+ const IpcQrMessageSchema = zod.z.object({
143
+ v: zod.z.literal(1),
144
+ type: zod.z.literal("qr"),
145
+ payload: zod.z.object({
146
+ pngBase64: zod.z.string(),
147
+ qrcodeUrl: zod.z.string()
148
+ })
149
+ });
150
+ const IpcEventMessageSchema = zod.z.object({
151
+ v: zod.z.literal(1),
152
+ type: zod.z.literal("event"),
153
+ payload: IpcEventPayloadSchema
154
+ });
155
+ const IpcResultMessageSchema = zod.z.object({
156
+ v: zod.z.literal(1),
157
+ type: zod.z.literal("result"),
158
+ id: zod.z.number(),
159
+ payload: IpcResultPayloadSchema
160
+ });
161
+ const IpcLogMessageSchema = zod.z.object({
162
+ v: zod.z.literal(1),
163
+ type: zod.z.literal("log"),
164
+ payload: IpcLogPayloadSchema
165
+ });
166
+ const IpcPingMessageSchema = zod.z.object({
167
+ v: zod.z.literal(1),
168
+ type: zod.z.literal("ping")
169
+ });
170
+ const IpcPongMessageSchema = zod.z.object({
171
+ v: zod.z.literal(1),
172
+ type: zod.z.literal("pong")
173
+ });
174
+ const IpcActionMessageSchema = zod.z.object({
175
+ v: zod.z.literal(1),
176
+ type: zod.z.literal("action"),
177
+ id: zod.z.number(),
178
+ payload: IpcActionPayloadSchema
179
+ });
180
+ const IpcControlMessageSchema = zod.z.object({
181
+ v: zod.z.literal(1),
182
+ type: zod.z.literal("control"),
183
+ payload: IpcControlPayloadSchema
184
+ });
185
+ /** IPC 消息判别联合(协议边界,运行时经 safeParse 校验)。 */
186
+ const IpcMessageSchema = zod.z.discriminatedUnion("type", [
187
+ IpcStatusMessageSchema,
188
+ IpcLoginMessageSchema,
189
+ IpcQrMessageSchema,
190
+ IpcEventMessageSchema,
191
+ IpcResultMessageSchema,
192
+ IpcLogMessageSchema,
193
+ IpcPingMessageSchema,
194
+ IpcPongMessageSchema,
195
+ IpcActionMessageSchema,
196
+ IpcControlMessageSchema
197
+ ]);
198
+ //#endregion
33
199
  //#region src/instance-lock.ts
34
200
  /**
35
201
  * instance-lock.ts:单实例锁(2026-08-07 根治「多实例抢数据目录锁挂起」)
@@ -635,8 +801,14 @@ const ENV = {
635
801
  * qq-releases.ts:版本清单读取(P1)。
636
802
  *
637
803
  * 运行时从包根 qq-releases.json 读取(发布时 files 含它,安装后位于
638
- * node_modules/@napuketto/loader/qq-releases.json)。清单由 CI/社区 PR 维护
639
- * (设计文档 §2.2)。NAPUTO_QQ_URL 可运行时覆盖下载地址。
804
+ * node_modules/@napuketto/loader/qq-releases.json)。
805
+ *
806
+ * 维护机制(真实存在,非「声称」):清单由 CI 自动维护——
807
+ * .github/workflows/update-qq-releases.yml(每日 cron + 手动触发)调用
808
+ * scripts/qq-releases/update-qq-releases.ts(纯 Node,与本地共用),抓取 QQ 官方下载配置
809
+ * 解析最新版 + 下载安装包计算 sha256 + 解析安装包内部版本目录后更新本清单
810
+ * (保留历史条目、known 按版本递增)。社区 PR 仍可手动补充。NAPUTO_QQ_URL
811
+ * 可运行时覆盖下载地址(用户拿到新链接时用)。
640
812
  */
641
813
  /** 清单解析失败。 */
642
814
  var QqReleasesError = class extends Error {
@@ -909,6 +1081,17 @@ async function ensureQqFiles(options = {}) {
909
1081
  exports.DownloadError = DownloadError;
910
1082
  exports.ENV = ENV;
911
1083
  exports.INSTANCE_LOCK_FILE = INSTANCE_LOCK_FILE;
1084
+ exports.IPC_VERSION = IPC_VERSION;
1085
+ exports.IpcActionPayloadSchema = IpcActionPayloadSchema;
1086
+ exports.IpcBootPhaseSchema = IpcBootPhaseSchema;
1087
+ exports.IpcControlPayloadSchema = IpcControlPayloadSchema;
1088
+ exports.IpcEventPayloadSchema = IpcEventPayloadSchema;
1089
+ exports.IpcLogLevelSchema = IpcLogLevelSchema;
1090
+ exports.IpcLogPayloadSchema = IpcLogPayloadSchema;
1091
+ exports.IpcLoginPayloadSchema = IpcLoginPayloadSchema;
1092
+ exports.IpcMessageSchema = IpcMessageSchema;
1093
+ exports.IpcResultPayloadSchema = IpcResultPayloadSchema;
1094
+ exports.IpcStatusPayloadSchema = IpcStatusPayloadSchema;
912
1095
  exports.QQ_FILES_DIR_NAME = QQ_FILES_DIR_NAME;
913
1096
  exports.QqReleasesError = QqReleasesError;
914
1097
  exports.WIN_NODE_DIR_NAME = WIN_NODE_DIR_NAME;