@napuketto/kernel 0.7.0 → 0.7.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["stringifyTomlText","parseTomlText","VIDEO_THUMB_KEY","errMsgString","sleep","errMsgString","errMsgString","httpsGet","osVersionString","osType","PlatformTypeValue"],"sources":["../src/infra/errors.ts","../src/infra/config.ts","../src/infra/log-config.ts","../src/infra/log-format.ts","../src/infra/logger.ts","../src/infra/paths.ts","../src/apis/result.ts","../src/apis/friend.ts","../src/types/entities.ts","../src/types/listeners/group.ts","../src/types/services/msg-service.ts","../src/types/message-element.ts","../src/types/services/group-service.ts","../src/types/wrapper.ts","../src/apis/group.ts","../src/apis/group-notify.ts","../src/apis/msg.ts","../src/apis/stranger-info.ts","../src/apis/profile.ts","../src/apis/profile-like.ts","../src/apis/recent-contact.ts","../src/apis/richmedia.ts","../src/apis/ticket.ts","../src/apis/webapi.ts","../src/bridge/friend-bridge.ts","../src/bridge/group-bridge.ts","../src/bridge/msg-bridge.ts","../src/event-channel.ts","../src/cache/buddy-cache.ts","../src/login/wait.ts","../src/cache/group-cache.ts","../src/context.ts","../src/wrapper/wrapper-adapters.ts","../src/login/login-connect.ts","../src/login/lifecycle.ts","../src/login/login.ts","../src/wrapper/qq-data-path.ts","../src/wrapper/wrapper-config.ts","../src/wrapper/wrapper-loader.ts","../src/core.ts","../src/wrapper/session-resolver.ts","../src/wrapper/probe/probe-serialize.ts","../src/wrapper/probe/probe-utils.ts","../src/wrapper/probe/probe-services.ts","../src/wrapper/probe/probe.ts","../src/wrapper/probe/probe-listener.ts","../src/wrapper/wrapper-version.ts"],"sourcesContent":["/**\n * kernel 类型化错误(ADR-009 / ADR-017)\n *\n * apis 层内部解包原生 `{ result, errMsg }`:成功返回纯业务值,失败抛 KernelError。\n * 协议层只需维护 `KernelErrorCode → 协议错误码` 映射表,不解析错误逻辑。\n */\n\n/**\n * 错误码分类。P1 探测真实错误返回后再增删。\n * 当前 INVALID_PARAM 也承担基础设施层(配置校验失败等)的错误语义。\n */\nexport type KernelErrorCode =\n | \"SEND_FAILED\" // 发送失败(原生拒绝)\n | \"PERMISSION_DENIED\" // 无权限(禁言、非管理员等)\n | \"NOT_FOUND\" // 目标不存在(群/成员/消息)\n | \"TIMEOUT\" // 操作超时(含文件预测超时)\n | \"NOT_LOGIN\" // 未登录或已掉线\n | \"INVALID_PARAM\" // 参数非法(原生拒绝 / 配置校验失败)\n | \"INVALID_STATE\" // 状态非法(如 session 未创建即 startNT)\n | \"UNKNOWN\"; // 兜底\n\n/**\n * 全部错误码集合。协议层映射表可据此推导:\n * `Record<(typeof KERNEL_ERROR_CODES)[number], number>`。\n */\nexport const KERNEL_ERROR_CODES = [\n \"SEND_FAILED\",\n \"PERMISSION_DENIED\",\n \"NOT_FOUND\",\n \"TIMEOUT\",\n \"NOT_LOGIN\",\n \"INVALID_PARAM\",\n \"INVALID_STATE\",\n \"UNKNOWN\",\n] as const;\n\n/** kernel 类型化错误:apis / 基础设施层抛出的统一错误形态。 */\nexport class KernelError extends Error {\n readonly code: KernelErrorCode;\n\n constructor(message: string, code: KernelErrorCode = \"UNKNOWN\", options?: ErrorOptions) {\n super(message, options);\n this.name = \"KernelError\";\n this.code = code;\n }\n}\n\n/** 便捷构造:`kernelError('消息不存在', 'NOT_FOUND', { cause })`。 */\nexport function kernelError(\n message: string,\n code: KernelErrorCode = \"UNKNOWN\",\n options?: ErrorOptions,\n): KernelError {\n return new KernelError(message, code, options);\n}\n\n/** 类型守卫:协议层 catch 统一映射时区分 KernelError 与意外错误。 */\nexport function isKernelError(err: unknown): err is KernelError {\n return err instanceof KernelError;\n}\n\n/**\n * 配置校验失败(INVALID_PARAM 子型,2026-10-25 嵌入式宿主契约):仅用于\n * 「配置未就绪」一族——主配置校验/反序列化、内联配置、协议段 seed 校验。\n * code 恒为 INVALID_PARAM,协议层错误码映射零涟漪;退出码分类走 instanceof\n * (CLI/self-host 进程以 EXIT_CONFIG_INVALID 退出),与错误文案完全解耦——\n * 文案是人读的,退出码是宿主消费的机器契约。\n */\nexport class ConfigError extends KernelError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, \"INVALID_PARAM\", options);\n this.name = \"ConfigError\";\n }\n}\n\n/** 便捷构造:`configError('主配置缺少 accounts', { cause })`。 */\nexport function configError(message: string, options?: ErrorOptions): ConfigError {\n return new ConfigError(message, options);\n}\n\n/** 配置校验失败的进程退出码(UNIX sysexits EX_CONFIG 惯例)。 */\nexport const EXIT_CONFIG_INVALID = 78;\n","/**\n * 配置基类(ADR-012)\n *\n * 读文件 → 校验 → 内存对象 → 变更写回。支持 **JSON / TOML** 两种文本格式\n * (按扩展名推断:.toml → TOML(smol-toml),其余 → JSON)。\n *\n * kernel 不依赖 zod:只依赖校验器的最小 `parse` 形状(`ConfigSchema`),\n * 协议包的 zod schema 天然满足;kernel 主配置用手写校验器包装。\n * 协议配置 schema 在各自协议包,见各包 docs/design.md。\n */\nimport { mkdirSync } from \"node:fs\";\nimport { readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, extname } from \"node:path\";\nimport { parse as parseTomlText, stringify as stringifyTomlText } from \"smol-toml\";\nimport { ConfigError } from \"./errors.js\";\n\n/** 配置文本格式。 */\nexport type ConfigFormat = \"json\" | \"toml\";\n\n/** 按扩展名推断格式(.toml → toml,其余 → json)。 */\nfunction inferFormat(path: string): ConfigFormat {\n if (extname(path).toLowerCase() === \".toml\") {\n return \"toml\";\n }\n return \"json\";\n}\n\nfunction isMissingFileError(err: unknown): boolean {\n if (err instanceof Error) {\n return (err as NodeJS.ErrnoException).code === \"ENOENT\";\n }\n return false;\n}\n\n/**\n * 校验器最小接口。zod 的 `ZodType<T>`(`parse(data: unknown): T`)天然满足;\n * 手写校验器包装成该形状即可。\n */\nexport interface ConfigSchema<T> {\n parse: (input: unknown) => T;\n}\n\nexport interface ConfigOptions<T> {\n /** 配置文件绝对路径。 */\n path: string;\n /** 校验器(协议 schema 用 zod;kernel 主配置用手写校验器包装)。 */\n schema: ConfigSchema<T>;\n /** 默认值:文件缺失时生成并落盘;校验以它为兜底。 */\n defaults: T;\n /** 内存初值(已校验对象):load() 时直接使用,跳过文件读写(全局 TOML 分段的装配方式)。 */\n seed?: T | undefined;\n /** 文本格式(缺省按扩展名推断)。 */\n format?: ConfigFormat | undefined;\n}\n\n/**\n * 配置基类。每次 `load()` / `save()` 都经过 schema 校验,\n * 校验失败抛 `KernelError('INVALID_PARAM')`,不静默吞掉。\n * - `load()`:seed 存在 → 直接用 seed;否则读文件(缺失落默认值)。\n * - `save()`:按格式序列化(TOML/JSON)原子写。\n */\nexport class ConfigBase<T> {\n readonly path: string;\n readonly defaults: T;\n\n private readonly schema: ConfigSchema<T>;\n private readonly format: ConfigFormat;\n private readonly seed: T | undefined;\n private value: T;\n\n constructor(opts: ConfigOptions<T>) {\n this.path = opts.path;\n this.defaults = opts.defaults;\n this.schema = opts.schema;\n this.format = opts.format ?? inferFormat(opts.path);\n this.seed = opts.seed;\n this.value = opts.seed ?? opts.defaults;\n }\n\n /** 当前内存值(只读消费;外部不得直接修改返回对象)。 */\n get(): T {\n return this.value;\n }\n\n /**\n * 加载配置:\n * - seed 存在 → 直接用 seed(已校验,不读文件;全局 TOML 分段装配方式);\n * - 文件缺失 → 写入默认值并落盘(首次运行自动生成),返回默认值;\n * - 文件存在 → 读取 + 校验,更新内存,返回。\n */\n async load(): Promise<T> {\n if (this.seed !== undefined) {\n this.value = this.seed;\n return this.value;\n }\n let raw: string;\n try {\n raw = await readFile(this.path, \"utf8\");\n } catch (err) {\n if (isMissingFileError(err)) {\n await this.save(this.defaults);\n return this.value;\n }\n throw new ConfigError(`读取配置文件失败: ${this.path}`, { cause: err });\n }\n this.value = this.parse(raw);\n return this.value;\n }\n\n /**\n * 校验纯文本配置(反序列化 + schema 校验,不读写文件;`config validate`\n * 等只读场景用)。错误包装与 load()/save() 完全一致(ConfigError,\n * 文案含 this.path),保证 validate 与运行时校验逐字一致。\n */\n parseText(raw: string): T {\n return this.parse(raw);\n }\n\n /** 校验并落盘(原子写:临时文件 + rename),随后更新内存。 */\n async save(next: T): Promise<void> {\n const parsed = this.parse(this.serialize(next));\n mkdirSync(dirname(this.path), { recursive: true });\n const tmpPath = `${this.path}.tmp`;\n await writeFile(tmpPath, `${this.serialize(parsed)}\\n`, \"utf8\");\n await rename(tmpPath, this.path);\n this.value = parsed;\n }\n\n /** 对象 → 文本(TOML/JSON)。 */\n private serialize(value: T): string {\n if (this.format === \"toml\") {\n return stringifyTomlText(value as Record<string, unknown>);\n }\n return JSON.stringify(value, null, 4);\n }\n\n /** 文本 → 对象(TOML/JSON)。 */\n private deserialize(raw: string): unknown {\n if (this.format === \"toml\") {\n return parseTomlText(raw);\n }\n return JSON.parse(raw) as unknown;\n }\n\n /** 反序列化 + schema 校验,错误统一包装为 ConfigError(配置未就绪一族)。 */\n private parse(raw: string): T {\n let input: unknown;\n try {\n input = this.deserialize(raw);\n } catch (err) {\n throw new ConfigError(`配置文件 ${this.format.toUpperCase()} 解析失败: ${this.path}`, {\n cause: err,\n });\n }\n try {\n return this.schema.parse(input);\n } catch (err) {\n throw new ConfigError(`配置文件校验失败: ${this.path}`, { cause: err });\n }\n }\n}\n\n/** 解析 TOML 文本 → 对象(boot.cjs / 探测脚本等无 ConfigBase 场景复用)。 */\nexport function parseToml(text: string): Record<string, unknown> {\n return parseTomlText(text);\n}\n\n/** 对象 → TOML 文本(config init 生成全局配置文件等场景)。 */\nexport function stringifyToml(value: Record<string, unknown>): string {\n return stringifyTomlText(value);\n}\n","/**\n * [log] 配置段解析(napuketto.toml 全局段,2026-10-25 日志可配置化)。\n *\n * 统一解析入口:cli(apps/cli)与 loader 引导链共用本函数(经 kernel 导出),\n * 避免两处手写解析漂移。输入为 TOML 解析后的 [log] 段原对象(缺失传 undefined)\n * + 环境变量快照;输出为已解析的 LogConfig(env 覆盖优先于 toml)。\n *\n * 环境变量(优先级高于 toml;非法值忽略——与既有 NAPKETTO_LOG_LEVEL 行为一致):\n * - NAPUKETTO_LOG_LEVEL / NAPKETTO_LOG_LEVEL(兼容旧拼写,NAPUKETTO_ 优先)\n * - NAPUKETTO_LOG_FORMAT(pretty | single-line | json)\n * - NAPUKETTO_NO_BANNER(1/true = 关 banner)\n * - NAPUKETTO_LOG_COLORIZE(1/true/0/false,CLI 顶层进程向子进程传播控制台\n * 渲染决策用;toml 不暴露此键)\n */\nimport { kernelError } from \"./errors.js\";\nimport type { LogLevel } from \"./logger.js\";\n\n/** 控制台输出格式。 */\nexport type LogFormat = \"pretty\" | \"single-line\" | \"json\";\n/** 时间戳渲染模式(见 LogConfig.timestamp)。 */\nexport type TimestampMode = \"local\" | \"iso8601\" | \"elapsed\";\n/** 可按模块覆盖级别的模块名(= logger 来源名)。 */\nexport type LogModuleName = \"cli\" | \"kernel\" | \"loader\" | \"ob11\";\n\n/** pino 标准级别 + silent。 */\nconst LOG_LEVEL_VALUES: readonly LogLevel[] = [\n \"trace\",\n \"debug\",\n \"info\",\n \"warn\",\n \"error\",\n \"fatal\",\n \"silent\",\n];\nconst LOG_FORMAT_VALUES: readonly LogFormat[] = [\"pretty\", \"single-line\", \"json\"];\nconst TIMESTAMP_MODE_VALUES: readonly TimestampMode[] = [\"local\", \"iso8601\", \"elapsed\"];\nconst MODULE_NAME_VALUES: readonly LogModuleName[] = [\"cli\", \"kernel\", \"loader\", \"ob11\"];\n\n/** 独立文件 sink 配置([log.file])。 */\nexport interface LogFileConfig {\n /** 文件路径(相对路径由消费方按数据根展开)。 */\n path: string;\n /** 文件目的地级别(可低于控制台级别)。 */\n level: LogLevel;\n}\n\n/** [log] 段解析结果(env 已覆盖)。 */\nexport interface LogConfig {\n /** 控制台(全局)级别。 */\n level: LogLevel;\n /**\n * 显式配置的输出格式;undefined = 未配置——消费端按 TTY 自动\n * (TTY = pretty 保持终端体验 / pipe = single-line 嵌入式友好),\n * 显式配置值不受 TTY 影响。\n */\n format?: LogFormat;\n /** 是否打印启动 banner(CLI 入口)。 */\n banner: boolean;\n /** 时间戳渲染模式(json 格式恒为 ISO8601,不受此键影响)。 */\n timestamp: TimestampMode;\n /** false = 未绑定频道的群消息接收行不打(需 kurobot 握手拿到绑定表后生效)。 */\n unboundReceive: boolean;\n /** 按模块覆盖全局级别。 */\n levels: Partial<Record<LogModuleName, LogLevel>>;\n /** 独立文件 sink(不配 = 沿用各层缺省文件日志)。 */\n file?: LogFileConfig;\n /**\n * 控制台颜色(env 专用:CLI 顶层进程向 pipe 后面的子进程传播用;\n * undefined = 消费端自动判定 TTY + pretty)。\n */\n colorize?: boolean;\n}\n\n/** 环境变量快照类型(process.env 的结构子集,便于测试注入)。 */\nexport type LogEnvSource = Record<string, string | undefined>;\n\n/** 校验级别字面量;非法值抛 INVALID_PARAM。 */\nfunction requireLevel(value: unknown, key: string): LogLevel {\n if (typeof value !== \"string\" || !(LOG_LEVEL_VALUES as readonly string[]).includes(value)) {\n throw kernelError(\n `主配置 ${key} 必须是日志级别(${LOG_LEVEL_VALUES.join(\" / \")})`,\n \"INVALID_PARAM\",\n );\n }\n return value as LogLevel;\n}\n\n/** 宽松级别解析(env 覆盖用;非法/缺失返回 undefined 不生效)。 */\nfunction optionalLevel(value: string | undefined): LogLevel | undefined {\n if (value === undefined) {\n return undefined;\n }\n return (LOG_LEVEL_VALUES as readonly string[]).includes(value)\n ? (value as LogLevel)\n : undefined;\n}\n\n/** 宽松格式解析(env 覆盖用)。 */\nfunction optionalFormat(value: string | undefined): LogFormat | undefined {\n if (value === undefined) {\n return undefined;\n }\n return (LOG_FORMAT_VALUES as readonly string[]).includes(value)\n ? (value as LogFormat)\n : undefined;\n}\n\n/** 宽松布尔解析(env 用:仅识别 1/true/0/false,其余忽略)。 */\nfunction optionalBool(value: string | undefined): boolean | undefined {\n if (value === \"1\" || value === \"true\") {\n return true;\n }\n if (value === \"0\" || value === \"false\") {\n return false;\n }\n return undefined;\n}\n\n/** 解析 [log.levels] 按模块覆盖表(未知模块名报错,键集封闭防拼写错误)。 */\nfunction parseModuleLevels(raw: unknown): Partial<Record<LogModuleName, LogLevel>> {\n if (raw === undefined) {\n return {};\n }\n if (typeof raw !== \"object\" || raw === null) {\n throw kernelError(\"主配置 log.levels 必须是对象\", \"INVALID_PARAM\");\n }\n const out: Partial<Record<LogModuleName, LogLevel>> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (!(MODULE_NAME_VALUES as readonly string[]).includes(key)) {\n throw kernelError(\n `主配置 log.levels 未知模块 \"${key}\"(合法模块:${MODULE_NAME_VALUES.join(\" / \")})`,\n \"INVALID_PARAM\",\n );\n }\n out[key as LogModuleName] = requireLevel(value, `log.levels.${key}`);\n }\n return out;\n}\n\n/** 解析 [log.file] 独立文件 sink。 */\nfunction parseLogFile(raw: unknown, fallbackLevel: LogLevel): LogFileConfig {\n if (typeof raw !== \"object\" || raw === null) {\n throw kernelError(\"主配置 log.file 必须是对象\", \"INVALID_PARAM\");\n }\n const record = raw as Record<string, unknown>;\n const path = record[\"path\"];\n if (typeof path !== \"string\" || path === \"\") {\n throw kernelError(\"主配置 log.file.path 必须是非空字符串\", \"INVALID_PARAM\");\n }\n const level =\n record[\"level\"] === undefined\n ? fallbackLevel\n : requireLevel(record[\"level\"], \"log.file.level\");\n return { path, level };\n}\n\n/** [log] 解析中间产物(可变草稿:toml 字段与 env 覆盖分步填充)。 */\ninterface LogConfigDraft extends LogConfig {\n /** [log.file] 是否显式配置 level(env 覆盖判断用;不进最终配置面)。 */\n fileLevelExplicit?: boolean;\n}\n\n/** 校验字面量枚举值(非法抛 INVALID_PARAM,报错文案带键名与合法值集)。 */\nfunction requireEnum<T extends string>(value: unknown, key: string, values: readonly T[]): T {\n if (typeof value !== \"string\" || !(values as readonly string[]).includes(value)) {\n throw kernelError(`主配置 ${key} 必须是(${values.join(\" / \")})`, \"INVALID_PARAM\");\n }\n return value as T;\n}\n\n/** 校验布尔字段(undefined 保留原值)。 */\nfunction applyBoolean(\n draft: LogConfigDraft,\n raw: Record<string, unknown>,\n key: \"banner\" | \"unboundReceive\",\n): void {\n if (raw[key] === undefined) {\n return;\n }\n if (typeof raw[key] !== \"boolean\") {\n throw kernelError(`主配置 log.${key} 必须是布尔值`, \"INVALID_PARAM\");\n }\n draft[key] = raw[key] as boolean;\n}\n\n/** toml [log] 段字段填充(level/format/timestamp/banner/unboundReceive)。 */\nfunction applySectionFields(draft: LogConfigDraft, raw: Record<string, unknown>): void {\n if (raw[\"level\"] !== undefined) {\n draft.level = requireLevel(raw[\"level\"], \"log.level\");\n }\n if (raw[\"format\"] !== undefined) {\n draft.format = requireEnum(raw[\"format\"], \"log.format\", LOG_FORMAT_VALUES);\n }\n if (raw[\"timestamp\"] !== undefined) {\n draft.timestamp = requireEnum(raw[\"timestamp\"], \"log.timestamp\", TIMESTAMP_MODE_VALUES);\n }\n applyBoolean(draft, raw, \"banner\");\n applyBoolean(draft, raw, \"unboundReceive\");\n}\n\n/** env 覆盖(优先级高于 toml;非法值忽略):兼容旧拼写 NAPKETTO_LOG_LEVEL。\n * file.level 显式配置时不被 env 全局级别顶掉(缺省跟随全局)。 */\nfunction applyEnvOverrides(draft: LogConfigDraft, env: LogEnvSource): void {\n const envLevel =\n optionalLevel(env[\"NAPUKETTO_LOG_LEVEL\"]) ?? optionalLevel(env[\"NAPKETTO_LOG_LEVEL\"]);\n if (envLevel !== undefined) {\n draft.level = envLevel;\n if (draft.file !== undefined && draft.fileLevelExplicit !== true) {\n draft.file = { ...draft.file, level: envLevel };\n }\n }\n const envFormat = optionalFormat(env[\"NAPUKETTO_LOG_FORMAT\"]);\n if (envFormat !== undefined) {\n draft.format = envFormat;\n }\n if (optionalBool(env[\"NAPUKETTO_NO_BANNER\"]) === true) {\n draft.banner = false;\n }\n const envColorize = optionalBool(env[\"NAPUKETTO_LOG_COLORIZE\"]);\n if (envColorize !== undefined) {\n draft.colorize = envColorize;\n }\n}\n\n/**\n * 解析 [log] 段 + 环境变量覆盖。\n *\n * @param section TOML 解析后的 [log] 段(缺失/undefined = 全缺省)\n * @param env 环境变量快照(默认空对象 = 无 env 覆盖)\n */\nexport function parseLogConfig(section: unknown, env: LogEnvSource = {}): LogConfig {\n const draft: LogConfigDraft = {\n level: \"info\",\n banner: true,\n timestamp: \"local\",\n unboundReceive: true,\n levels: {},\n };\n if (section !== undefined) {\n if (typeof section !== \"object\" || section === null) {\n throw kernelError(\"主配置 log 段必须是对象\", \"INVALID_PARAM\");\n }\n const raw = section as Record<string, unknown>;\n applySectionFields(draft, raw);\n draft.levels = parseModuleLevels(raw[\"levels\"]);\n if (raw[\"file\"] !== undefined) {\n draft.file = parseLogFile(raw[\"file\"], draft.level);\n draft.fileLevelExplicit =\n typeof raw[\"file\"] === \"object\" &&\n raw[\"file\"] !== null &&\n (raw[\"file\"] as Record<string, unknown>)[\"level\"] !== undefined;\n }\n }\n applyEnvOverrides(draft, env);\n // 内部传递字段不进最终配置面\n delete draft.fileLevelExplicit;\n return draft;\n}\n","/**\n * 结构化控制台流(single-line / json 格式,2026-10-25 日志可配置化)。\n *\n * pino-pretty 面向交互终端(多行展开 / JSON 内联渲染),其 singleLine 输出为\n * `msg {\"k\":\"v\"}` 形态且 messageFormat 会与字段追加双重渲染——无法干净满足\n * 嵌入式宿主的「一条记录一行、字段 k=v / NDJSON」契约,故自写轻量渲染流:\n *\n * single-line:[时间] LEVEL (name/pid): 消息 key=value key=value\n * json:NDJSON,每行 {time, level, module, account?, pid, msg, ...fields}\n *\n * 时间渲染(LogConfig.timestamp):local = [HH:MM:ss.l](与 pretty 缺省一致)、\n * iso8601 = 2026-10-25T16:01:33.294+08:00、elapsed = +123.456s(进程内单调秒)。\n * json 的 time 恒为 ISO8601(机器可解析契约,不受 timestamp 键影响)。\n */\nimport process from \"node:process\";\nimport type pino from \"pino\";\nimport type { TimestampMode } from \"./log-config.js\";\n\n/** 级别数值 → 名称(pino 标准映射)。 */\nconst LEVEL_LABELS: Record<number, string> = {\n 10: \"trace\",\n 20: \"debug\",\n 30: \"info\",\n 40: \"warn\",\n 50: \"error\",\n 60: \"fatal\",\n};\n\n/** single-line 颜色(colorize 时仅着色级别与元数据头,正文默认色)。 */\nconst LEVEL_COLORS: Record<string, string> = {\n trace: \"\\u001b[34m\",\n debug: \"\\u001b[36m\",\n info: \"\\u001b[32m\",\n warn: \"\\u001b[33m\",\n error: \"\\u001b[31m\",\n fatal: \"\\u001b[91m\",\n};\nconst DIM = \"\\u001b[2m\";\nconst RESET = \"\\u001b[0m\";\n\n/** 结构化流选项。 */\nexport interface ConsoleFormatStreamOptions {\n /** 输出格式(pretty 走 pino-pretty,不进本流)。 */\n format: \"single-line\" | \"json\";\n /** 时间戳渲染模式。 */\n timestamp: TimestampMode;\n /** single-line 是否着色(json 恒不着色)。 */\n colorize: boolean;\n /** 输出目的地(缺省 process.stdout;测试注入用)。 */\n destination?: NodeJS.WriteStream;\n}\n\n/** 2 位补零。 */\nfunction pad2(n: number): string {\n return String(n).padStart(2, \"0\");\n}\n\n/** 单调墙钟毫秒 → 本地时区 ISO8601(+08:00 形态;new Date(epoch) 构造不受 Date.now 篡改影响)。 */\nfunction isoLocalTime(epochMs: number): string {\n const d = new Date(epochMs);\n const offsetMinutes = -d.getTimezoneOffset();\n const sign = offsetMinutes >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offsetMinutes);\n return (\n `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}` +\n `T${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}` +\n `.${String(d.getMilliseconds()).padStart(3, \"0\")}` +\n `${sign}${pad2(Math.floor(abs / 60))}:${pad2(abs % 60)}`\n );\n}\n\n/** 单调墙钟毫秒 → 本地时分秒(与 pino-pretty SYS:HH:MM:ss.l 一致的短格式)。 */\nfunction localShortTime(epochMs: number): string {\n const d = new Date(epochMs);\n return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${String(\n d.getMilliseconds(),\n ).padStart(3, \"0\")}`;\n}\n\n/** 单调墙钟毫秒 → 进程内相对秒(+123.456s)。 */\nfunction elapsedLabel(epochMs: number, timeOriginMs: number): string {\n return `+${((epochMs - timeOriginMs) / 1000).toFixed(3)}s`;\n}\n\n/** 渲染记录时间(timestamp 模式分发;json 恒 ISO8601)。 */\nfunction renderTime(\n rawTime: unknown,\n mode: TimestampMode,\n forceIso: boolean,\n timeOriginMs: number,\n): string {\n if (typeof rawTime === \"string\") {\n // pretty+elapsed 等场景记录时间已是预渲染字符串,原样透出\n return rawTime;\n }\n const epoch = typeof rawTime === \"number\" ? rawTime : timeOriginMs;\n if (forceIso || mode === \"iso8601\") {\n return isoLocalTime(epoch);\n }\n if (mode === \"elapsed\") {\n return elapsedLabel(epoch, timeOriginMs);\n }\n return localShortTime(epoch);\n}\n\n/** 级别数值 → 标签名。 */\nfunction levelLabel(raw: unknown): string {\n if (typeof raw === \"number\") {\n return LEVEL_LABELS[raw] ?? String(raw);\n }\n return typeof raw === \"string\" ? raw.toUpperCase() : \"LOG\";\n}\n\n/** k=v 内联值:字符串裸输出(含空格时不加引号——宽松人类格式),其余 JSON 序列化。 */\nfunction inlineValue(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\n/** 结构化输出忽略的标准字段(渲染进头/独立字段,不进 k=v 尾部)。 */\nconst RESERVED_KEYS = new Set([\"pid\", \"hostname\", \"level\", \"time\", \"msg\", \"name\"]);\n\n/** 渲染上下文(流构造时固化,渲染函数共享)。 */\ninterface RenderContext {\n timestamp: TimestampMode;\n timeOriginMs: number;\n useColor: boolean;\n}\n\n/** json 渲染:NDJSON 契约 {time, level, module, account?, pid, msg, ...fields}。 */\nfunction renderJsonLine(record: Record<string, unknown>, ctx: RenderContext): string {\n const { time, level, name, module, account, pid, msg: _msg, ...fields } = record;\n const msg = typeof record[\"msg\"] === \"string\" ? record[\"msg\"] : \"\";\n const jsonLine = {\n time: renderTime(time, ctx.timestamp, true, ctx.timeOriginMs),\n level: levelLabel(level).toLowerCase(),\n ...(module !== undefined\n ? { module: module as string }\n : typeof name === \"string\"\n ? { module: name }\n : {}),\n ...(account !== undefined ? { account: account as string } : {}),\n ...(typeof pid === \"number\" ? { pid } : {}),\n msg,\n ...fields,\n };\n return `${JSON.stringify(jsonLine)}\\n`;\n}\n\n/** single-line 元数据头:(name/pid),无 name 时 (pid)。 */\nfunction renderMeta(record: Record<string, unknown>): string {\n const pid = record[\"pid\"] ?? \"-\";\n const name = record[\"name\"];\n return typeof name === \"string\" ? `(${name}/${pid})` : `(${pid})`;\n}\n\n/** single-line 附加字段:非保留键按 k=v 内联(无字段时空串)。 */\nfunction renderKvFields(record: Record<string, unknown>): string {\n const fields: string[] = [];\n for (const [key, value] of Object.entries(record)) {\n if (!RESERVED_KEYS.has(key)) {\n fields.push(`${key}=${inlineValue(value)}`);\n }\n }\n return fields.length > 0 ? ` ${fields.join(\" \")}` : \"\";\n}\n\n/** single-line 渲染:[时间] LEVEL (name/pid): 消息 k=v k=v(级别大写与 pretty 一致)。 */\nfunction renderSingleLine(record: Record<string, unknown>, ctx: RenderContext): string {\n const label = levelLabel(record[\"level\"]);\n const msg = typeof record[\"msg\"] === \"string\" ? record[\"msg\"] : \"\";\n const time = renderTime(record[\"time\"], ctx.timestamp, false, ctx.timeOriginMs);\n const labelUpper = label.toUpperCase();\n const meta = renderMeta(record);\n const fieldsSuffix = renderKvFields(record);\n if (ctx.useColor) {\n const color = LEVEL_COLORS[label.toLowerCase()] ?? \"\";\n return `${DIM}[${time}]${RESET} ${color}${labelUpper}${RESET} ${DIM}${meta}:${RESET} ${msg}${fieldsSuffix}\\n`;\n }\n return `[${time}] ${labelUpper} ${meta}: ${msg}${fieldsSuffix}\\n`;\n}\n\n/** 创建结构化控制台流(single-line / json)。 */\nexport function createConsoleFormatStream(\n opts: ConsoleFormatStreamOptions,\n): pino.DestinationStream {\n const out = opts.destination ?? process.stdout;\n const ctx: RenderContext = {\n timestamp: opts.timestamp,\n timeOriginMs: performance.timeOrigin,\n useColor: opts.format === \"single-line\" && opts.colorize,\n };\n const render = opts.format === \"json\" ? renderJsonLine : renderSingleLine;\n\n return {\n write(line: string) {\n let record: Record<string, unknown>;\n try {\n record = JSON.parse(line) as Record<string, unknown>;\n } catch {\n // 非法 JSON 行原样透出(不吞日志)\n out.write(`${line}\\n`);\n return;\n }\n out.write(render(record, ctx));\n },\n };\n}\n","/**\n * pino 日志封装(ADR-007)\n *\n * - console(pretty 可读输出 / single-line / json 结构化)+ 可选文件双目的地\n * - 统一 level 控制(控制台与文件可分别设级,2026-10-25 日志可配置化)\n * - 内置 redact(token / 票据等敏感字段不打日志),可追加\n * - 无全局单例(ADR-015 推论):每进程实例化一份,由装配层持有\n *\n * 时间源(2026-10-25 修复):QQNT 原生层会在 onRecvSysMsg 回调窗口内篡改进程\n * Date(实测记录时间冻结为假值、回调外恢复),故时间戳统一取\n * performance.timeOrigin + performance.now() 单调派生墙钟,不受 Date 篡改影响。\n */\nimport { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport process from \"node:process\";\nimport pino from \"pino\";\nimport pinoPretty from \"pino-pretty\";\nimport { KernelError } from \"./errors.js\";\nimport type { LogFormat, TimestampMode } from \"./log-config.js\";\nimport { createConsoleFormatStream } from \"./log-format.js\";\n\n/** 默认 redact 路径:顶层 token/票据 + 任意深度的密码/密钥。 */\nconst DEFAULT_REDACT_PATHS = [\"token\", \"ticket\", \"cookie\", \"password\", \"secret\"] as const;\n\n/** 级别数值序(求多目的地最低级别用)。 */\nconst LEVEL_ORDER: readonly pino.LevelWithSilent[] = [\n \"trace\",\n \"debug\",\n \"info\",\n \"warn\",\n \"error\",\n \"fatal\",\n \"silent\",\n];\n\n/** 取两级别中更低(更宽容)的一个。 */\nfunction minLevel(a: pino.LevelWithSilent, b: pino.LevelWithSilent): pino.LevelWithSilent {\n return LEVEL_ORDER.indexOf(a) <= LEVEL_ORDER.indexOf(b) ? a : b;\n}\n\n/** 单调墙钟毫秒(timeOrigin 为进程启动真实 Unix 时间,不受进程内 Date 篡改影响)。 */\nfunction wallMs(): number {\n return performance.timeOrigin + performance.now();\n}\n\n/** 进程内相对秒标签(elapsed 模式,pretty 字符串时间路径用)。 */\nfunction elapsedLabelNow(): string {\n return `+${(performance.now() / 1000).toFixed(3)}s`;\n}\n\nfunction toSingle(streams: Array<{ stream: pino.DestinationStream }>): pino.DestinationStream {\n const [only] = streams;\n if (only === undefined) {\n // 不可达:调用方已保证 streams 非空\n throw new KernelError(\"logger 内部错误:目的地列表为空\", \"UNKNOWN\");\n }\n return only.stream;\n}\n\n/** 日志级别(pino 标准级别 + silent)。 */\nexport type LogLevel = \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\" | \"fatal\" | \"silent\";\n\n/**\n * 可选属性统一带 `| undefined`(2026-10-25):exactOptionalPropertyTypes 下\n * 纯直传调用点(装配层透传 LogConfig 字段)可写 `format: cfg.format` 一类\n * 直赋值,免去逐字段条件展开(展开链是复杂度热点)。\n */\ntype Optional<T> = T | undefined;\n\nexport interface LoggerOptions {\n /** 控制台级别,默认 `info`。 */\n level?: Optional<LogLevel>;\n /** 是否输出到 console,默认 true。 */\n console?: Optional<boolean>;\n /** 文件日志路径;不传则不写文件(父目录自动创建)。 */\n file?: Optional<string>;\n /** 文件目的地级别(缺省同 level;可低于控制台级别保留排查信息)。 */\n fileLevel?: Optional<LogLevel>;\n /**\n * 输出格式,默认按 TTY 自动:TTY = pretty(终端体验不变)/ pipe = single-line\n * (嵌入式逐行转发友好)。显式指定后不受 TTY 影响。\n */\n format?: Optional<LogFormat>;\n /** 时间戳渲染模式(local/iso8601/elapsed),默认 local。 */\n timestamp?: Optional<TimestampMode>;\n /** 控制台颜色(缺省自动:pretty 且 TTY 才着色)。 */\n colorize?: Optional<boolean>;\n /** 控制台目的地(缺省 process.stdout;测试注入用)。 */\n destination?: Optional<NodeJS.WriteStream>;\n /** 附加基础字段(如 `{ name: 'kernel' }`),自动保留 pid;name 用 pino 保留字段\n * (logger name),pino-pretty 渲染为 `(name/pid)` 元数据头。 */\n base?: Optional<Record<string, unknown>>;\n /** 追加的敏感字段路径(默认内置 token/票据等,见 DEFAULT_REDACT_PATHS)。 */\n redact?: string[];\n}\n\n/**\n * 创建 pino logger:console(pretty/single-line/json)+ 可选文件,统一 level / redact。\n *\n * 文件目的地用同步写入(`sync: true`):日志量级下性能可接受,且进程退出不丢日志,\n * 无需额外的 flush 步骤(对比异步 SonicBoom 的 flushSync 时序陷阱)。\n * 文件目的地恒为 JSON 行(数值时间戳),格式/时间戳渲染仅作用于控制台目的地。\n */\n/** 控制台流构造参数(格式分支共用)。 */\ninterface ConsoleStreamParams {\n format: LogFormat;\n timestampMode: TimestampMode;\n colorize: boolean;\n destination: NodeJS.WriteStream;\n}\n\n/** 构造控制台流:pretty 走 pino-pretty,single-line/json 走结构化渲染流。 */\nfunction buildConsoleStream(params: ConsoleStreamParams): pino.DestinationStream {\n const { format, timestampMode, colorize, destination } = params;\n if (format !== \"pretty\") {\n return createConsoleFormatStream({\n format,\n timestamp: timestampMode,\n colorize,\n destination,\n });\n }\n return pinoPretty({\n colorize,\n // 短格式时间(2026-08-07 用户定稿):完整日期+时区前缀近 60 字符,\n // 每条日志重复占满横向视线;缩短为时分秒+毫秒,文件日志(JSON)\n // 仍保留完整时间戳可精确追溯。\n // ⚠️ 必须带 SYS: 前缀:无前缀按 UTC 渲染(北京 = UTC+8,时间会差 8 小时),\n // SYS: 才用本地系统时间。iso8601 模式用完整日期+时区后缀;elapsed 模式\n // 时间为预渲染字符串(不可解析则 pretty 原样输出),不走 translateTime。\n ...(timestampMode === \"local\" ? { translateTime: \"SYS:HH:MM:ss.l\" as const } : {}),\n ...(timestampMode === \"iso8601\"\n ? { translateTime: \"SYS:yyyy-mm-dd'T'HH:MM:ss.l Z\" as const }\n : {}),\n // 原生多行展开(默认值,显式声明):属性按 key/value 逐行渲染,\n // 由 pino-pretty 自带颜色高亮(key 紫色 / 字符串青色),不手写格式化\n // 函数对抗原生渲染——高频业务日志(如收到消息)在调用点直接传\n // 纯字符串,不传对象,天然单行。\n singleLine: false,\n // ⚠️ 必须显式传 destination=process.stdout:pino-pretty 默认用\n // sonic-boom 直写 fd 1(UTF-8 字节流),在 pnpm start(cmd.exe +\n // 管道 936 转码)链路下中文被按 GBK 解码成乱码、ANSI 残留;\n // 走 process.stdout 的 TTY 路径(WriteConsoleW UTF-16)编码正确。\n destination,\n });\n}\n\n/** 解析后的渲染参数(createLogger 决策折叠,降低主函数复杂度)。 */\ninterface ResolvedRenderParams {\n format: LogFormat;\n timestampMode: TimestampMode;\n colorize: boolean;\n destination: NodeJS.WriteStream;\n}\n\n/** 折叠 LoggerOptions 的渲染缺省:格式按 TTY 自动(TTY pretty / pipe single-line)、\n * 颜色仅在 pretty 且 TTY 时开。 */\nfunction resolveRenderParams(opts: LoggerOptions): ResolvedRenderParams {\n const destination = opts.destination ?? process.stdout;\n const isTty = destination.isTTY === true;\n const format: LogFormat = opts.format ?? (isTty ? \"pretty\" : \"single-line\");\n return {\n format,\n timestampMode: opts.timestamp ?? \"local\",\n colorize: opts.colorize ?? (format === \"pretty\" && isTty),\n destination,\n };\n}\n\nexport function createLogger(opts: LoggerOptions = {}): pino.Logger {\n const consoleLevel = opts.level ?? \"info\";\n const fileLevel = opts.fileLevel ?? consoleLevel;\n const { file, base } = opts;\n const render = resolveRenderParams(opts);\n\n const streams: Array<{ stream: pino.DestinationStream; level: pino.LevelWithSilent }> = [];\n if ((opts.console ?? true) === true) {\n streams.push({ stream: buildConsoleStream(render), level: consoleLevel });\n }\n if (file) {\n mkdirSync(dirname(file), { recursive: true });\n streams.push({ stream: pino.destination({ dest: file, sync: true }), level: fileLevel });\n }\n if (streams.length === 0) {\n throw new KernelError(\"logger 至少需要一个输出目标:console 或 file\", \"INVALID_PARAM\");\n }\n\n // 记录产生门槛 = 全部目的地中最低级别(multistream 再按各自级别分发过滤)\n let gate: pino.LevelWithSilent = \"fatal\";\n for (const entry of streams) {\n gate = minLevel(gate, entry.level);\n }\n\n // 时间戳:local/iso8601 记数值(单调墙钟)交渲染层;elapsed 在 pretty 字符串\n // 时间路径预渲染(pino-pretty 对不可解析字符串时间原样输出)\n const pinoTimestamp =\n render.format === \"pretty\" && render.timestampMode === \"elapsed\"\n ? (): string => `,\"time\":\"${elapsedLabelNow()}\"`\n : (): string => `,\"time\":${wallMs()}`;\n\n const pinoOptions: pino.LoggerOptions = {\n level: gate,\n redact: [...DEFAULT_REDACT_PATHS, ...(opts.redact ?? [])],\n timestamp: pinoTimestamp,\n };\n if (base) {\n pinoOptions.base = { pid: process.pid, ...base };\n }\n\n let stream: pino.DestinationStream | ReturnType<typeof pino.multistream>;\n if (streams.length === 1) {\n stream = toSingle(streams);\n } else {\n stream = pino.multistream(streams);\n }\n\n return pino(pinoOptions, stream);\n}\n","/**\n * 数据目录布局(ADR-016,2026-08-08 修订:数据根默认放项目根)\n *\n * ```\n * <数据根>/<qq号>/ # 每账号独立目录(ADR-015 多账号前提)\n * ├── config/ # 各账号独立配置(历史产物,协议段已走 seed)\n * ├── logs/ # pino 文件日志\n * └── cache/ # 临时文件、媒体缓存\n *\n * <项目根>/napuketto.toml # 全局配置文件(放项目根目录,不入库)\n * <项目根>/.napuketto/ # 数据根(默认值,2026-08-08 用户拍板:直接放项目主目录)\n * ```\n *\n * 数据根优先级:cli `--data-dir`(显式参数)> `NAPKETTO_DATA` 环境变量 >\n * `<项目根>/.napuketto`(默认,部署原因不放用户目录)。\n * 配置文件路径见 `resolveConfigPath`(项目根探测,与数据根解耦)。\n */\nimport { existsSync, mkdirSync, readdirSync, rmSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { fileURLToPath } from \"node:url\";\n\n/** 默认数据根目录名(项目根下,2026-08-08 用户拍板:不放用户目录)。 */\nexport const DEFAULT_DATA_ROOT_NAME = \".napuketto\";\n\n/** 全局配置文件名。 */\nexport const MAIN_CONFIG_FILE = \"napuketto.toml\";\n\nexport interface PathOptions {\n /** 显式数据根(cli `--data-dir` 传入,优先级最高)。 */\n dataRoot?: string;\n /** QQ 号,用于多账号隔离;缺省时直接使用数据根本身。 */\n account?: string;\n}\n\n/** `~` 前缀展开为用户主目录(跨平台配置文件写法 `dataDir = \"~/.napuketto\"`)。 */\nfunction expandTilde(p: string): string {\n if (p === \"~\") {\n return homedir();\n }\n if (p.startsWith(\"~/\") || p.startsWith(\"~\\\\\")) {\n return join(homedir(), p.slice(2));\n }\n return p;\n}\n\n/**\n * 项目根查找:入口模块向上找 `pnpm-workspace.yaml`(monorepo 开发场景,即仓库根)→\n * cwd 向上找 `pnpm-workspace.yaml` / `package.json`(发布后用户项目)→ undefined(兜底)。\n */\nfunction resolveProjectRoot(): string | undefined {\n const moduleDir = dirname(fileURLToPath(import.meta.url));\n const moduleRoot = findProjectRoot(moduleDir, \"pnpm-workspace.yaml\");\n if (moduleRoot !== undefined) {\n return moduleRoot;\n }\n return (\n findProjectRoot(process.cwd(), \"pnpm-workspace.yaml\") ??\n findProjectRoot(process.cwd(), \"package.json\")\n );\n}\n\n/**\n * 解析数据根:显式参数 > 环境变量 > `<项目根>/.napuketto`(默认)> 用户目录兜底(裸脚本)。\n * 支持 `~` 前缀(展开为用户主目录)与相对路径(相对 cwd 解析)。\n * 返回绝对路径。独立导出便于 cli / 探测脚本复用。\n */\nexport function resolveDataRoot(dataRoot?: string): string {\n const explicit = dataRoot ?? process.env[\"NAPKETTO_DATA\"];\n if (explicit !== undefined && explicit !== \"\") {\n return resolve(expandTilde(explicit));\n }\n const projectRoot = resolveProjectRoot();\n if (projectRoot !== undefined) {\n return join(projectRoot, DEFAULT_DATA_ROOT_NAME);\n }\n return join(homedir(), DEFAULT_DATA_ROOT_NAME);\n}\n\n/** 向上查找含标志文件的目录(项目根探测:monorepo 根 / 用户项目根)。 */\nfunction findProjectRoot(startDir: string, marker: string): string | undefined {\n let dir = startDir;\n for (;;) {\n if (existsSync(join(dir, marker))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n return undefined;\n }\n dir = parent;\n }\n}\n\nexport interface ConfigPathOptions {\n /** 兜底数据根(探测全部失败时用 `<dataRoot>/napuketto.toml`,保持旧行为)。 */\n dataRoot?: string;\n}\n\n/**\n * 全局配置文件路径解析(2026-08-07 用户拍板:**配置文件放项目根目录,\n * 数据仍按数据根组织**——数据根只承载账号目录/日志/缓存/QQ 数据)。\n *\n * 优先级:\n * 1. `NAPKETTO_CONFIG` 环境变量(显式指定完整路径,任意场景可用)\n * 2. 项目根探测(入口模块向上找 pnpm-workspace.yaml → cwd 向上找,见 resolveProjectRoot)\n * 3. 数据根兜底 `<dataRoot>/napuketto.toml`(旧行为兼容)\n *\n * 返回绝对路径。独立导出便于 cli / loader 装配链复用。\n */\nexport function resolveConfigPath(opts: ConfigPathOptions = {}): string {\n const explicit = process.env[\"NAPKETTO_CONFIG\"];\n if (explicit !== undefined && explicit !== \"\") {\n return resolve(explicit);\n }\n const projectRoot = resolveProjectRoot();\n if (projectRoot !== undefined) {\n return join(projectRoot, MAIN_CONFIG_FILE);\n }\n return join(opts.dataRoot ?? resolveDataRoot(), MAIN_CONFIG_FILE);\n}\n\n/**\n * 路径装配器:持有账号数据目录下的全部子目录。\n * 无全局单例(ADR-015 推论)——每账号每进程实例化一份。\n */\nexport class PathWrapper {\n /** 数据根(绝对路径)。 */\n readonly root: string;\n /** 账号数据目录(绝对路径),缺省 account 时等于 root。 */\n readonly accountDir: string;\n /** config 目录(napuketto.json + 各协议配置)。 */\n readonly configDir: string;\n /** logs 目录(pino 文件日志)。 */\n readonly logsDir: string;\n /** cache 目录(临时文件、媒体缓存)。 */\n readonly cacheDir: string;\n\n constructor(opts: PathOptions = {}) {\n const root = resolveDataRoot(opts.dataRoot);\n this.root = root;\n if (opts.account) {\n this.accountDir = join(root, opts.account);\n } else {\n this.accountDir = root;\n }\n this.configDir = join(this.accountDir, \"config\");\n this.logsDir = join(this.accountDir, \"logs\");\n this.cacheDir = join(this.accountDir, \"cache\");\n }\n\n /** 递归创建全部目录,启动装配时调用一次。 */\n ensure(): void {\n for (const dir of [this.configDir, this.logsDir, this.cacheDir]) {\n mkdirSync(dir, { recursive: true });\n }\n }\n\n /** 账号目录内的文件路径(如 `file('config', 'napuketto.json')`)。 */\n file(...segments: string[]): string {\n return join(this.accountDir, ...segments);\n }\n\n /**\n * 清空缓存目录(clean_cache 动作经注入回调消费)。\n * 只删除 cacheDir 下的条目,保留目录本身。\n */\n clearCache(): void {\n let entries: string[];\n try {\n entries = readdirSync(this.cacheDir);\n } catch {\n return; // 目录不存在视为空\n }\n for (const entry of entries) {\n rmSync(join(this.cacheDir, entry), { recursive: true, force: true });\n }\n }\n}\n","/**\n * 原生调用结果解包(ADR-009 统一错误语义)\n *\n * wrapper.node 的 service 方法大多返回 `{ result, errMsg }`:\n * - unwrap:result 为 number 且非 0 → 抛 KernelError\n * - checkLooseResult:宽松形状(result 可能缺失/非数字)→ 仅当 result 为\n * 数字且非 0 时抛错(探测期兼容:某些 API 成功时 result 缺省或为对象)\n * - unwrapResult:result !== 0 → 按 errMsg 内容映射语义错误码(msg 系专用)\n */\n\nimport { type KernelErrorCode, kernelError } from \"../infra/index.js\";\nimport type { GeneralCallResult } from \"../types/index.js\";\n\n/** errMsg 关键词 → 错误码规则(顺序即优先级,命中任一组关键词即映射)。 */\nconst RESULT_CODE_RULES: ReadonlyArray<{ code: KernelErrorCode; keywords: readonly string[] }> = [\n { code: \"NOT_LOGIN\", keywords: [\"未登录\", \"login\"] },\n { code: \"PERMISSION_DENIED\", keywords: [\"无权限\", \"permission\"] },\n { code: \"NOT_FOUND\", keywords: [\"不存在\", \"not found\"] },\n];\n\n/** 按 errMsg 关键词映射错误码(未命中 → UNKNOWN)。 */\nfunction mapResultCode(msg: string): KernelErrorCode {\n for (const rule of RESULT_CODE_RULES) {\n if (rule.keywords.some((k) => msg.includes(k))) {\n return rule.code;\n }\n }\n return \"UNKNOWN\";\n}\n\n/** 原生 result 解包(result 字段非 0 抛 KernelError)。 */\nexport function unwrap(label: string, result: number, errMsg?: string): void {\n if (result === 0) {\n return;\n }\n throw kernelError(`${label} 失败: ${errMsg ?? \"无错误详情\"}`, \"UNKNOWN\");\n}\n\n/** 宽松 result 校验(result 为数字非 0 视为失败;缺失/非数字视为成功)。 */\nexport function checkLooseResult(\n label: string,\n res: { result?: unknown; errMsg?: unknown } | null | undefined,\n): void {\n if (res === undefined || res === null || typeof res.result !== \"number\" || res.result === 0) {\n return;\n }\n throw kernelError(`${label} 失败: ${String(res.errMsg ?? \"\")}`, \"UNKNOWN\");\n}\n\n/** 原生结果解包:result !== 0 抛 KernelError(errMsg 语义映射错误码)。 */\nexport function unwrapResult<T extends GeneralCallResult>(label: string, raw: T): void {\n if (raw.result === 0) {\n return;\n }\n const msg = raw.errMsg || \"无错误详情\";\n // 语义码优先(未登录/无权限/不存在),未命中且为 sendMsg 时兜底 SEND_FAILED\n let code = mapResultCode(msg);\n if (code === \"UNKNOWN\" && label === \"sendMsg\") {\n code = \"SEND_FAILED\";\n }\n throw kernelError(`${label} 失败: ${msg}`, code);\n}\n","/**\n * FriendApi:好友语义化 API(ADR-009 统一错误语义)\n *\n * 内部解包原生返回:成功返回纯业务值,失败抛 KernelError。\n * 方法面(P2-4 + P2-11):好友列表(uid + 昵称/备注)+ 好友请求\n * (getBuddyReq / approvalFriendRequest / setBuddyRemark / delBuddy /\n * getDoubtBuddyReq / approvalDoubtBuddyReq)。\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n BuddyCategory,\n BuddyReq,\n DoubtBuddyReq,\n NodeIKernelBuddyService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\nimport { checkLooseResult, unwrap } from \"./result.js\";\n\n/** 好友条目(uid + 昵称/备注,昵称由 buddy service 缓存补全)。 */\nexport interface Friend {\n uid: string;\n uin: string;\n nickname: string;\n remark: string;\n}\n\n/** 带分类的好友列表项(get_friends_with_category 返回)。 */\nexport interface FriendCategory {\n categoryId: number;\n categorySortId: number;\n categoryName: string;\n categoryMbCount: number;\n onlineCount: number;\n buddyList: Friend[];\n}\n\n/** 可疑好友申请(OB11 结构,get_doubt_friends_add_request 返回)。 */\nexport interface DoubtFriendRequestInfo {\n flag: string;\n uin: number;\n nick?: string;\n source?: string;\n reason?: string;\n msg?: string;\n group_code?: string;\n time?: string;\n type: \"doubt\";\n}\n\n/** FriendApi 构造选项。 */\nexport interface FriendApiOptions {\n /** uid→uin 转换器(通常注入 GroupApi.uidToUin);缺省 uin 退化为 uid。 */\n uidToUin?: (uids: string[]) => Promise<Map<string, string>>;\n}\n\n/** 好友 API:从 session 拿 buddy service,包装成语义化方法。 */\nexport class FriendApi {\n private readonly service: NodeIKernelBuddyService;\n private readonly uidToUin: ((uids: string[]) => Promise<Map<string, string>>) | undefined;\n\n constructor(session: NodeIQQNTWrapperSession, opts: FriendApiOptions = {}) {\n const service = session.getBuddyService() as unknown as NodeIKernelBuddyService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getBuddyService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n this.uidToUin = opts.uidToUin;\n }\n\n /** 好友列表(分类拍平为 Friend[];昵称/备注从 service 缓存读取)。 */\n async getFriendList(): Promise<Friend[]> {\n let categories: BuddyCategory[];\n try {\n categories = await this.service.getBuddyListFromCache(0);\n } catch {\n const raw = await this.service.getBuddyListV2(\"cli\", true, 0);\n if (raw.result !== 0) {\n throw kernelError(`getBuddyListV2 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n categories = raw.data;\n }\n // buddyList(明细,T10 实证)优先,buddyUids(旧描述字段)兜底\n const uids = categories.flatMap(\n (cat) => cat.buddyList?.map((b) => b.uid) ?? cat.buddyUids ?? [],\n );\n let uinMap = new Map<string, string>();\n if (this.uidToUin !== undefined) {\n uinMap = await this.uidToUin(uids);\n }\n const out: Friend[] = [];\n for (const uid of uids) {\n out.push({\n uid,\n uin: uinMap.get(uid) ?? uid,\n nickname: this.service.getBuddyNick(uid),\n remark: this.service.getBuddyRemark(uid),\n });\n }\n return out;\n }\n\n /** 带分类的好友列表(get_friends_with_category,分类保留)。 */\n async getFriendCategories(): Promise<FriendCategory[]> {\n const categories = await this.getBuddyCategories();\n const friendMap = new Map<string, Friend>();\n for (const friend of await this.getFriendList()) {\n friendMap.set(friend.uid, friend);\n }\n return categories.map((cat) => {\n const buddies: Friend[] = [];\n for (const uid of cat.buddyList?.map((b) => b.uid) ?? cat.buddyUids ?? []) {\n const friend = friendMap.get(uid);\n if (friend !== undefined) {\n buddies.push(friend);\n }\n }\n return {\n categoryId: cat.categoryId,\n categorySortId: cat.categorySortId,\n categoryName: cat.categroyName,\n categoryMbCount: cat.categroyMbCount,\n onlineCount: cat.onlineCount,\n buddyList: buddies,\n };\n });\n }\n\n /** 原始分类(getBuddyListV2 / 缓存)。 */\n private async getBuddyCategories(): Promise<BuddyCategory[]> {\n try {\n return await this.service.getBuddyListFromCache(0);\n } catch {\n const raw = await this.service.getBuddyListV2(\"cli\", true, 0);\n if (raw.result !== 0) {\n throw kernelError(`getBuddyListV2 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return raw.data;\n }\n }\n\n /** 判断是否好友。 */\n isBuddy(uid: string): boolean {\n return this.service.isBuddy(uid);\n }\n\n /** 好友申请列表(set_friend_add_request 应答前查找匹配项)。 */\n async getBuddyReqList(): Promise<BuddyReq[]> {\n const raw = await this.service.getBuddyReq();\n unwrap(\"getBuddyReq\", raw.result, raw.errMsg);\n return raw.buddyReqs ?? [];\n }\n\n /** 应答好友申请(accept=true 同意;remark 由调用方另行 setFriendRemark)。 */\n async handleFriendRequest(notify: BuddyReq, accept: boolean): Promise<void> {\n await this.service.approvalFriendRequest({\n friendUid: notify.friendUid,\n reqTime: notify.reqTime,\n accept,\n });\n }\n\n /** 设置好友备注(void 语义乐观处理)。 */\n setFriendRemark(uid: string, remark: string): void {\n this.service.setBuddyRemark({ uid, remark });\n }\n\n /** 删除好友(tempBlock=拉黑,tempBothDel=双向删除)。 */\n async deleteFriend(uid: string, tempBlock = false, tempBothDel = false): Promise<void> {\n const res = (await this.service.delBuddy({\n friendUid: uid,\n tempBlock,\n tempBothDel,\n })) as { result?: unknown; errMsg?: unknown } | undefined;\n checkLooseResult(\"delBuddy\", res);\n }\n\n /** 可疑好友申请列表(get_doubt_friends_add_request;uin 经 uidToUin 转换)。 */\n async getDoubtFriendRequest(count: number): Promise<DoubtFriendRequestInfo[]> {\n const reqId = String(Date.now());\n const raw = await this.service.getDoubtBuddyReq(reqId, count, \"\");\n unwrap(\"getDoubtBuddyReq\", raw.result, raw.errMsg);\n const list: DoubtBuddyReq[] = raw.doubtList ?? [];\n let uinMap = new Map<string, string>();\n if (this.uidToUin !== undefined && list.length > 0) {\n uinMap = await this.uidToUin(list.map((item) => item.uid));\n }\n const out: DoubtFriendRequestInfo[] = [];\n for (const item of list) {\n out.push(this.toDoubtFriendRequestInfo(item, uinMap));\n }\n return out;\n }\n\n /** 单项转换:DoubtBuddyReq → OB11 结构(可选字段条件赋值,exactOptionalPropertyTypes)。 */\n private toDoubtFriendRequestInfo(\n item: DoubtBuddyReq,\n uinMap: Map<string, string>,\n ): DoubtFriendRequestInfo {\n return toDoubtFriendRequestInfo(item, uinMap);\n }\n\n /** 处理可疑好友申请(set_doubt_friends_add_request;uid=flag)。 */\n handleDoubtFriendRequest(uid: string): void {\n this.service.approvalDoubtBuddyReq(uid, \"\", \"\");\n }\n}\n\n/** 可选字段条件赋值(undefined 跳过,兼容 exactOptionalPropertyTypes)。 */\nfunction assignOptional<K extends keyof DoubtFriendRequestInfo>(\n info: DoubtFriendRequestInfo,\n key: K,\n value: DoubtFriendRequestInfo[K] | undefined,\n): void {\n if (value !== undefined) {\n info[key] = value;\n }\n}\n\n/** 单项转换:DoubtBuddyReq → OB11 结构(导出供单测,friend.test.ts)。 */\nexport function toDoubtFriendRequestInfo(\n item: DoubtBuddyReq,\n uinMap: Map<string, string>,\n): DoubtFriendRequestInfo {\n const info: DoubtFriendRequestInfo = {\n flag: item.uid,\n uin: Number(uinMap.get(item.uid) ?? item.uid),\n type: \"doubt\",\n };\n assignOptional(info, \"nick\", item.nick);\n assignOptional(info, \"source\", item.source);\n assignOptional(info, \"reason\", item.reason);\n assignOptional(info, \"msg\", item.msg);\n assignOptional(info, \"group_code\", item.groupCode);\n assignOptional(info, \"time\", item.reqTime);\n return info;\n}\n","/**\n * 实体类型层(运行时探测产物 + 公开资料作说明书,ADR-006)\n *\n * ⚠️ 部分字段按公开资料(wrapper 外部契约)自研描述,待下一次进程内\n * 探测(probe.ts)校准——接口签名是外部系统事实,自研描述其形状。\n */\n\n/** 会话类型(QQ wrapper 外部契约)。 */\nexport const ChatType = {\n C2C: 1, // 私聊\n GROUP: 2, // 群聊\n DISC: 3, // 讨论组\n GUILD: 4, // 频道\n C2C_TEMP: 100, // 临时会话(群内私聊)\n} as const;\nexport type ChatType = (typeof ChatType)[keyof typeof ChatType];\n\n/** 会话对象(peer 目标:群号 / 用户 uid)。 */\nexport interface Peer {\n chatType: ChatType;\n peerUid: string;\n guildId?: string;\n}\n\n/** 文本元素(at 用 atType + atUid 表达)。 */\nexport interface TextElement {\n content: string;\n atType?: number; // 1=全体 2=单人 4=我\n atUid?: string;\n}\n\n/** 图片元素(发送时 picPath 必须;接收含 url)。 */\nexport interface PicElement {\n picPath?: string;\n picUrl?: string;\n picType?: number;\n picWidth?: number;\n picHeight?: number;\n md5?: string;\n fileName?: string;\n sourcePath?: string;\n thumbPath?: string;\n // 发送用完整字段(NapCat 式,2026-08-11 实证:缺任一发送失败)\n md5HexStr?: string;\n fileSize?: string;\n original?: boolean;\n picSubType?: number;\n fileUuid?: string;\n fileSubId?: string;\n thumbFileSize?: number;\n summary?: string;\n}\n\n/** 语音元素(silk)。 */\nexport interface PttElement {\n filePath?: string;\n md5?: string;\n fileName?: string;\n duration?: number;\n /** 语音转文字结果(translatePtt2Text 异步写回)。 */\n text?: string;\n // 发送用完整字段(NapCat 式,2026-08-12 实证:只给 filePath 会触发 wrapper\n // 内部 \"Cannot convert undefined or null to object\"(缺字段转换失败))\n md5HexStr?: string;\n fileSize?: string;\n formatType?: number;\n voiceType?: number;\n voiceChangeType?: number;\n canConvert2Text?: boolean;\n waveAmplitudes?: number[];\n fileSubId?: string;\n playState?: number;\n autoConvertText?: number;\n storeID?: number;\n otherBusinessInfo?: { aiVoiceType: number };\n // 收向完整字段(2026-09-08 B1 实测快照,getLatestDbMsgs 历史消息提取)\n fileId?: number;\n fileUuid?: string;\n translateStatus?: number;\n /** 传输状态:实测 2=收到已下载(数据库态,此时挪走本地文件调 downloadRichMedia 不重下)、4=自己发送已上传。 */\n transferStatus?: number;\n progress?: number;\n invalidState?: number;\n fileBizId?: unknown;\n import_rich_media_context?: unknown;\n isInApplicationDataPath?: boolean | null;\n}\n\n/** 表情元素。 */\nexport interface FaceElement {\n faceIndex: number;\n faceType?: number;\n}\n\n/** 回复元素(引用消息)。 */\nexport interface ReplyElement {\n replayMsgId: string;\n replayMsgSeq?: string;\n replayMsgTime?: string;\n senderUid?: string;\n senderUin?: string;\n}\n\n/** 视频元素(接收 + 发送)。 */\nexport interface VideoElement {\n filePath?: string;\n videoUrl?: string;\n fileName?: string;\n fileSize?: string;\n // 接收字段(2026-10-17 收向实测快照,getLatestDbMsgs 历史视频消息)\n videoMd5?: string;\n thumbMd5?: string;\n /** 时长(秒)。 */\n fileTime?: number;\n thumbSize?: number;\n thumbWidth?: number;\n thumbHeight?: number;\n fileFormat?: number;\n busiType?: number;\n subBusiType?: number;\n /**\n * 缩略图路径。进程内收向实测 `Map<number, string>`(key 0 = Thumb 图片路径);\n * 经 JSON 序列化(IPC 行协议等)后退化为平面对象(`{\"0\": path}`,且无规整的\n * 传输会丢成空对象 `{}`)。接收读取统一走 message-element 的\n * readVideoThumbPath(Map / 平面对象 / 字符串三形态均容忍)。\n * 发送时必须传真实 Map——JSON 序列化会丢成 `{}`,wrapper 转换失败。\n */\n thumbPath?: Map<number, string> | Record<string, string> | string;\n transferStatus?: number;\n progress?: number;\n invalidState?: number;\n fileUuid?: string;\n fileSubId?: string;\n fileBizId?: unknown;\n originVideoMd5?: string;\n import_rich_media_context?: unknown;\n sourceVideoCodecFormat?: number;\n storeID?: number;\n original?: boolean;\n videoFrom?: number;\n isInApplicationDataPath?: boolean | null;\n /** 发送补充字段(PIC/PTT 同款 md5 字段名)。 */\n md5HexStr?: string;\n}\n\n/** 文件元素。 */\nexport interface FileElement {\n filePath?: string;\n fileName?: string;\n fileSize?: string;\n fileUuid?: string;\n}\n\n/** 灰色提示子类型(系统事件类型,QQ wrapper 契约)。 */\nexport const GrayTipSubType = {\n REVOKE: 1, // 撤回\n PROCLAMATION: 2, // 群公告\n GROUP: 4, // 群成员变动(TipGroupElement)\n BUDDY: 5, // 好友变动\n FEED: 6,\n ESSENCE: 7, // 精华消息\n GROUP_NOTIFY: 8, // 群通知\n BUDDY_NOTIFY: 9,\n FILE: 10,\n JSON: 17,\n} as const;\nexport type GrayTipSubType = (typeof GrayTipSubType)[keyof typeof GrayTipSubType];\n\n/** 群成员变动类型(TipGroupElement.type)。 */\nexport const TipGroupElementType = {\n MEMBER_ADD: 1, // 入群\n DISBANDED: 2, // 群解散\n QUIT: 3, // 退群\n CREATED: 4, // 建群\n GROUP_NAME_MODIFIED: 5, // 群名修改\n BLOCK: 6,\n UNBLOCK: 7,\n SHUT_UP: 8, // 禁言\n} as const;\nexport type TipGroupElementType = (typeof TipGroupElementType)[keyof typeof TipGroupElementType];\n\n/** 撤回元素(revokeElement)。 */\nexport interface GrayTipRevokeElement {\n operatorUid: string;\n operatorNick?: string;\n operatorRole?: string;\n isSelfOperate?: boolean;\n wording?: string;\n}\n\n/** AIOP 操作元素(戳一戳等,aioOpGrayTipElement)。 */\nexport interface TipAioOpGrayTipElement {\n operateType?: number;\n peerUid?: string;\n fromGrpCodeOfTmpChat?: string;\n}\n\n/** 群成员变动元素(groupElement 内的 TipGroupElement)。 */\nexport interface TipGroupElement {\n type?: TipGroupElementType;\n groupName?: string;\n memberUid?: string;\n memberNick?: string;\n memberRemark?: string;\n adminUid?: string;\n adminNick?: string;\n adminRemark?: string;\n shutUp?: {\n curTime?: string;\n duration?: string; // 禁言秒数\n admin?: { uid?: string; card?: string; name?: string; role?: number };\n member?: { uid?: string; card?: string; name?: string; role?: number };\n };\n}\n\n/** 灰色提示元素(elementType=8,系统事件载体)。 */\nexport interface GrayTipElement {\n subElementType?: GrayTipSubType;\n revokeElement?: GrayTipRevokeElement;\n groupElement?: TipGroupElement;\n aioOpGrayTipElement?: TipAioOpGrayTipElement;\n xmlElement?: { busiId?: string; content?: string; templId?: string };\n jsonGrayTipElement?: { busiId?: string; jsonStr?: string };\n}\n\n/** 消息元素(RawMessage.elements 成员,elementType + 子元素互斥)。 */\nexport interface RawElement {\n elementType: number;\n elementId?: string;\n textElement?: TextElement;\n picElement?: PicElement;\n pttElement?: PttElement;\n faceElement?: FaceElement;\n replyElement?: ReplyElement;\n videoElement?: VideoElement;\n fileElement?: FileElement;\n grayTipElement?: GrayTipElement;\n /** ARK 卡片元素(elementType=10 ARK,小程序/分享卡片,2026-10-25 B 站卡片实测)。bytesData 为卡片 JSON 字符串;其余字段待探测校准。 */\n arkElement?: { bytesData: string };\n /** 合并转发元素(elementType=16 MULTI_FORWARD)。 */\n multiForwardMsgElement?: {\n resId: string;\n fileName: string;\n xmlContent: string;\n };\n}\n\n/** QQ 消息(RawMessage,说明书参考字段,探测后校准)。 */\nexport interface RawMessage {\n msgId: string; // 雪花 ID\n msgSeq: string; // 消息序列号\n msgTime: string; // 时间戳\n msgRandom?: string;\n msgType: number;\n chatType: ChatType;\n peerUid: string; // 群号 / 用户 uid\n peerUin: string; // 群号 / 用户 QQ 号\n senderUid: string;\n senderUin: string;\n peerName: string;\n sendNickName: string;\n sendMemberName?: string;\n /** 发送状态(0=失败 1=发送中 2=成功 3=成功无seq)。onMsgInfoListUpdate 事件填充。 */\n sendStatus?: number;\n elements: RawElement[];\n records?: RawMessage[];\n guildId?: string;\n [key: string]: unknown;\n}\n","/**\n * Listener 接口层(运行时探测产物 + 公开资料作说明书,ADR-003 / ADR-006)\n *\n * GroupListener:群服务(GroupService)的原生回调监听接口。\n * 签名依据 wrapper 外部契约自研描述(零复制)——事件名约定 `${Service}/${method}`,由 event-channel 编译期推导。\n * 用 type 别名(非 interface):满足 ListenerShape(Record<string, unknown>)约束。\n */\nimport type {\n Group,\n GroupDetailInfo,\n GroupMember,\n GroupNotify,\n ShutUpGroupMember,\n} from \"../services/group-service.js\";\n\n/** 群列表更新类型(说明书参考,值待探测校准)。 */\nexport const GroupListUpdateType = {\n /** 全量更新(groupList 为完整列表)。 */\n ALL: 0,\n /** 增量更新(合并到现有缓存)。 */\n PARTIAL: 1,\n} as const;\nexport type GroupListUpdateType = (typeof GroupListUpdateType)[keyof typeof GroupListUpdateType];\n\n/** 成员数据来源(onMemberInfoChange 第二参,说明书参考)。 */\nexport const GroupMemberDataSource = {\n /** 本地缓存。 */\n CACHE: 0,\n /** 网络拉取。 */\n NETWORK: 1,\n} as const;\nexport type GroupMemberDataSource =\n (typeof GroupMemberDataSource)[keyof typeof GroupMemberDataSource];\n\n/** onMemberListChange 参数(sceneId 为群号 groupCode)。 */\nexport interface GroupMemberListChange {\n sceneId: string;\n ids: string[];\n /** uid → GroupMember。 */\n infos: Map<string, GroupMember>;\n hasPrev: boolean;\n hasNext: boolean;\n hasRobot: boolean;\n}\n\n/** 群服务(GroupService)的原生回调监听接口。 */\nexport type GroupListener = {\n /** 群列表初始化完成(listEmpty:是否无群)。 */\n onGroupListInited: (listEmpty: boolean) => void;\n /** 群列表更新。 */\n onGroupListUpdate: (updateType: GroupListUpdateType, groupList: Group[]) => void;\n /** 群详情变化。 */\n onGroupDetailInfoChange: (detailInfo: GroupDetailInfo) => void;\n /** 群成员列表变化(sceneId=groupCode)。 */\n onMemberListChange: (arg: GroupMemberListChange) => void;\n /** 群成员信息变化(dataSource 区分本地缓存/网络)。 */\n onMemberInfoChange: (\n groupCode: string,\n dataSource: GroupMemberDataSource,\n members: Map<string, GroupMember>,\n ) => void;\n /** 群通知更新(doubt:是否可疑/忽略列表)。 */\n onGroupNotifiesUpdated: (doubt: boolean, notifies: GroupNotify[]) => void;\n /** 群通知单屏(分页 seq)。 */\n onGroupSingleScreenNotifies: (doubt: boolean, seq: string, notifies: GroupNotify[]) => void;\n /** 群禁言成员列表变化。 */\n onShutUpMemberListChanged: (groupCode: string, members: ShutUpGroupMember[]) => void;\n /**\n * 群精华列表变化(OB11 group_essence 候选源,2026-09-10 接线)。\n * 证据:wrapper.node 9.9.33-52230 字符串——小写名位于 listener 反射字符串簇,\n * 与已知 onMemberListChange/onBuddyReqChange 交错(同一字符串表页);\n * API 族旁证 addGroupEssence/getGroupLatestEssenceList。\n * ⚠️ 参数形状待真实事件校准(unknown 透传,订阅方 raw 日志观测)。\n */\n onGroupEssenceListChange: (arg: unknown) => void;\n};\n","/**\n * NodeIKernelMsgService:消息服务接口面(自研描述,非移植)\n *\n * 依据:getMsgService() 运行时反射 + wrapper 外部契约(接口签名是 QQ 的外部事实,\n * 自研描述其形状,零复制实现)。\n * 只收录 apis/msg 当前需要的方法;其余 200+ 方法按需探测后补齐。\n */\nimport type {\n FaceElement,\n FileElement,\n Peer,\n PicElement,\n PttElement,\n RawMessage,\n ReplyElement,\n TextElement,\n VideoElement,\n} from \"../entities.js\";\n\n/** 原生通用返回:result 非 0 即失败(errMsg 附原因)。 */\nexport interface GeneralCallResult {\n result: number;\n errMsg: string;\n}\n\n/** 消息元素类型枚举(QQ wrapper 外部契约,自研描述)。 */\nexport const ElementType = {\n UNKNOWN: 0,\n TEXT: 1,\n PIC: 2,\n FILE: 3,\n PTT: 4,\n VIDEO: 5,\n FACE: 6,\n REPLY: 7,\n GRAY_TIP: 8,\n WALLET: 9,\n ARK: 10,\n MFACE: 11,\n LIVE_GIFT: 12,\n STRUCT_LONG_MSG: 13,\n MARKDOWN: 14,\n GIPHY: 15,\n MULTI_FORWARD: 16,\n INLINE_KEYBOARD: 17,\n INTEXT_GIFT: 18,\n CALENDAR: 19,\n} as const;\nexport type ElementType = (typeof ElementType)[keyof typeof ElementType];\n\n/** 发送消息元素(session 发送用,elementType + 各子元素互斥)。 */\nexport interface SendMessageElement {\n elementType: ElementType;\n elementId?: string;\n textElement?: TextElement;\n faceElement?: FaceElement;\n replyElement?: ReplyElement;\n picElement?: PicElement;\n pttElement?: PttElement;\n videoElement?: VideoElement;\n /** ARK 卡片(小程序/分享卡片);bytesData 为卡片 JSON 字符串(与接收侧 RawElement 同字段名,2026-10-25 实测)。 */\n arkElement?: { bytesData: string };\n /** 文件(upload_private/group_file;字段值待探测校准,pic/ptt 同款发送链)。 */\n fileElement?: FileElement;\n}\n\n/** 戳一戳参数(sendNudge,说明书参考,字段值待探测校准)。 */\nexport interface NudgeParam {\n /** 被戳者 uid。 */\n receiverUid: string;\n /** 发起者 uid(机器人自己)。 */\n callerUid: string;\n /** 会话 id(群场景 = 群号;好友场景缺省)。 */\n confId?: string;\n /** 场景操作码(接收侧 aioOp 同名;发送取值待校准)。 */\n aioOp?: number;\n}\n\n/** 消息服务:apis/msg 用到的核心方法面。 */\nexport interface NodeIKernelMsgService {\n addKernelMsgListener(listener: unknown): number;\n removeKernelMsgListener(listenerId: number): void;\n generateMsgUniqueId(chatType: number, time: string): string;\n sendMsg(\n msgId: string,\n peer: Peer,\n elements: SendMessageElement[],\n map: Map<number, unknown>,\n ): Promise<GeneralCallResult>;\n recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>;\n getMsgs(\n peer: Peer,\n msgId: string,\n count: number,\n queryOrder: boolean,\n ): Promise<GeneralCallResult & { msgList?: RawMessage[] }>;\n /** 按 msgId 批量拉取消息(get_msg / 精华消息 / ptt 转文字共用)。 */\n getMsgsByMsgId(\n peer: Peer,\n ids: string[],\n ): Promise<GeneralCallResult & { msgList?: RawMessage[] }>;\n /**\n * 富媒体主动下载(get_record 语音下载,2026-09-08 B1 实测:单参数对象,\n * resolve undefined——结果只能经重拉消息元素观察;参数残缺时静默 no-op 不抛错;\n * transferStatus=2 已下载态调用为 no-op,详见 kernel design.md §5)。\n */\n downloadRichMedia(param: {\n msgId: string;\n elemId: string;\n chatType: number;\n downloadType: number;\n thumbSize: number;\n }): Promise<void>;\n setMsgRead(peer: Peer): Promise<GeneralCallResult>;\n /** 消息表情表态(set_msg_emoji_like;setOrCancel=true 点赞,false 取消)。 */\n setMsgEmojiLikes(\n peer: Peer,\n msgSeq: string,\n emojiId: string,\n emojiType: string,\n setOrCancel: boolean,\n ): Promise<GeneralCallResult>;\n /** 语音转文字(fetch_ptt_text;异步转写,结果写回 pttElement.text)。 */\n translatePtt2Text(msgId: string, peer: Peer, msgElement: unknown): Promise<GeneralCallResult>;\n /** 输入状态(set_input_status;eventType=1 输入中,0 停止)。 */\n sendShowInputStatusReq(chatType: number, eventType: number, toUid: string): Promise<unknown>;\n /**\n * 富媒体文件目标路径(NapCat 式发送图片前置:md5+文件名 → QQ 内部 Ori/Thumb 路径)。\n * 返回纯文件名(相对 QQ 数据根),需配合 NodeQQNTWrapperUtil.copyFile 放置文件。\n */\n getRichMediaFilePathForGuild(req: {\n md5HexStr: string;\n fileName: string;\n elementType: number;\n elementSubType: number;\n thumbSize: number;\n needCreate: boolean;\n downloadType: number;\n file_uuid: string;\n }): string;\n /** 合并转发组装(send_group/private_forward_msg;返回 MULTI_FORWARD 元素)。 */\n buildMultiForwardMsg(req: {\n srcMsgIds: string[];\n srcContact: Peer;\n }): Promise<GeneralCallResult & { rspInfo?: { elements?: SendMessageElement[] } }>;\n /** 获取合并转发内容(get_forward_msg;resId 取自 multiForwardMsgElement)。 */\n getMultiMsg(\n peer: Peer,\n msgId: string,\n resId: string,\n ): Promise<GeneralCallResult & { msgList?: RawMessage[] }>;\n /** 单条转发(forward_group/friend_single_msg;msgIds 源、dstPeers 目标)。 */\n forwardMsg(\n msgIds: string[],\n peer: Peer,\n dstPeers: Peer[],\n commentElements: unknown,\n ): Promise<GeneralCallResult>;\n /** 设置在线状态(set_online_status;customStatus 为自定义状态)。 */\n setStatus(args: {\n status: number;\n extStatus: number;\n batteryStatus: number;\n customStatus?: {\n faceId: string;\n wording: string;\n faceType: string;\n };\n }): Promise<GeneralCallResult>;\n /** 戳一戳发送(send_friend/group_nudge;参数形状待探测校准)。 */\n sendNudge(param: NudgeParam): Promise<unknown>;\n /** 会话置顶(set_peer_pin;参数待探测校准)。 */\n setContactLocalTop(peer: Peer, isTop: boolean): Promise<unknown>;\n /** 收藏表情查询(get_custom_face_url_list;desc 空串 = 全量,形状待校准)。 */\n queryFavEmojiByDesc(desc: string): Promise<GeneralCallResult & { emojiInfoList?: unknown }>;\n}\n","/**\n * canonical 消息元素模型(协议无关,ADR-008 延伸)\n *\n * 描述 QQ 消息的事实结构,是各协议翻译层的公共中间表示:\n * kernel 只做一次 NT ↔ canonical(toCanonicalElements / toSendElements),\n * 各协议(onebot11/satori)只写薄映射 canonical ↔ 协议格式。\n */\n\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n FaceElement,\n FileElement,\n PicElement,\n PttElement,\n RawElement,\n RawMessage,\n ReplyElement,\n TextElement,\n VideoElement,\n} from \"./entities.js\";\nimport { ElementType, type SendMessageElement } from \"./services/msg-service.js\";\n\n/** 文本 at 类型(QQ wrapper 契约,自研描述)。 */\nconst AtType = { ALL: 1, ONE: 2, ME: 4 } as const;\n\n/**\n * 视频缩略图在 thumbPath 中的 key(收向实测 Map<0, path>;平面对象形态下\n * 数字 key 经序列化变字符串 \"0\"。apis/msg.ts 发送侧有同名常量,types 层\n * 不能反向 import apis(依赖方向),此处独立定义)。\n */\nconst VIDEO_THUMB_KEY = 0;\n\n/**\n * 读视频缩略图路径(接收方向形态不唯一,防御读取):\n * - 进程内为 `Map<0, path>`(收向实测);\n * - 跨进程/JSON 序列化后退化为平面对象 `{\"0\": path}`(`JSON.stringify(Map)`\n * 内容全丢,无规整的传输会得到空对象 `{}`);\n * - 个别形态可能直接给字符串路径(兜底容忍)。\n *\n * 2026-10-24 线上事故:koishi IPC 模式下 thumbPath 到达父进程已是平面对象,\n * 直接 `.get(0)` 抛 TypeError,整条视频消息事件分派失败。\n */\nfunction readVideoThumbPath(thumb: VideoElement[\"thumbPath\"]): string | undefined {\n if (thumb === undefined || thumb === \"\") {\n return undefined;\n }\n if (typeof thumb === \"string\") {\n return thumb;\n }\n if (thumb instanceof Map) {\n const value = thumb.get(VIDEO_THUMB_KEY);\n return value === \"\" ? undefined : value;\n }\n const value = thumb[String(VIDEO_THUMB_KEY)];\n return value === \"\" ? undefined : value;\n}\n\n/** TEXT 元素 → canonical(at 或纯文本)。 */\nfunction textToCanonical(textEl: TextElement | undefined): CanonicalElement {\n if (textEl === undefined) {\n return { type: \"text\", text: \"\" };\n }\n const { atType, atUid, content } = textEl;\n if (atType === AtType.ALL) {\n return { type: \"at\", target: \"all\" };\n }\n if (atType === AtType.ONE || atType === AtType.ME) {\n // 2026-08-07:at 段补 display(显示名)——QQ 的 content 通常是 \"@昵称\",\n // 剥掉 @ 作 display,供 OB11 at 段 name / 日志渲染使用(此前只有 target=uid)。\n const base = { type: \"at\" as const, target: atUid ?? content };\n const display = content.startsWith(\"@\") ? content.slice(1) : content;\n if (display === \"\") {\n return base;\n }\n return { ...base, display };\n }\n return { type: \"text\", text: content };\n}\n\n/** PIC 元素 → canonical image。 */\nfunction picToCanonical(pic: PicElement | undefined): CanonicalElement {\n if (pic === undefined) {\n return { type: \"image\", path: \"\" };\n }\n const path = pic.picPath ?? pic.picUrl ?? \"\";\n const url = pic.picUrl;\n if (url === undefined) {\n return { type: \"image\", path };\n }\n return { type: \"image\", path, url };\n}\n\n/** FACE 元素 → canonical face。 */\nfunction faceToCanonical(face: FaceElement | undefined): CanonicalElement {\n if (face === undefined) {\n return { type: \"face\", id: \"0\" };\n }\n return { type: \"face\", id: String(face.faceIndex) };\n}\n\n/** PTT 元素 → canonical voice。 */\nfunction pttToCanonical(ptt: PttElement | undefined): CanonicalElement {\n if (ptt === undefined) {\n return { type: \"voice\", path: \"\" };\n }\n return { type: \"voice\", path: ptt.filePath ?? \"\" };\n}\n\n/** VIDEO 元素 → canonical video。 */\nfunction videoToCanonical(video: VideoElement | undefined): CanonicalElement {\n if (video === undefined) {\n return { type: \"video\", path: \"\" };\n }\n const path = video.filePath ?? video.videoUrl ?? \"\";\n const base: Extract<CanonicalElement, { type: \"video\" }> = { type: \"video\", path };\n if (video.videoUrl !== undefined) {\n base.url = video.videoUrl;\n }\n // thumbPath 接收形态不唯一(Map / 序列化平面对象 / 字符串),统一走防御读取\n const thumb = readVideoThumbPath(video.thumbPath);\n if (thumb !== undefined) {\n base.thumbPath = thumb;\n }\n if (video.thumbWidth !== undefined && video.thumbWidth > 0) {\n base.width = video.thumbWidth;\n }\n if (video.thumbHeight !== undefined && video.thumbHeight > 0) {\n base.height = video.thumbHeight;\n }\n if (video.fileTime !== undefined && video.fileTime > 0) {\n base.durationSec = video.fileTime;\n }\n return base;\n}\n\n/** FILE 元素 → canonical file。 */\nfunction fileToCanonical(file: FileElement | undefined): CanonicalElement {\n if (file === undefined) {\n return { type: \"file\", path: \"\" };\n }\n const path = file.filePath ?? \"\";\n const name = file.fileName;\n if (name === undefined) {\n return { type: \"file\", path };\n }\n return { type: \"file\", path, name };\n}\n\n/** REPLY 元素 → canonical reply。 */\nfunction replyToCanonical(reply: ReplyElement | undefined): CanonicalElement {\n if (reply === undefined) {\n return { type: \"reply\", messageId: \"\" };\n }\n return { type: \"reply\", messageId: reply.replayMsgId };\n}\n\n/**\n * ARK 元素 → canonical json(小程序/分享卡片,2026-10-25)。\n *\n * bytesData 是卡片 JSON 字符串,原样透传不解析重组——OB11 json 段 data.data\n * 保持原始载荷(go-cqhttp/NapCat 生态一致),title/url 由下游自行 parse。\n * bytesData 缺失/为空时维持 unknown 兜底:空 json 段无信息量,保留 raw 供探测。\n */\nfunction arkToCanonical(ark: { bytesData: string } | undefined, el: RawElement): CanonicalElement {\n const bytes = ark?.bytesData;\n if (bytes === undefined || bytes === \"\") {\n return { type: \"unknown\", raw: el };\n }\n return { type: \"json\", raw: bytes };\n}\n\n/** 单个 NT 元素 → canonical(接收方向,宽容:无法表达回 unknown,不抛错)。 */\nfunction toCanonicalElement(el: RawElement): CanonicalElement {\n switch (el.elementType) {\n case ElementType.TEXT:\n return textToCanonical(el.textElement);\n case ElementType.PIC:\n return picToCanonical(el.picElement);\n case ElementType.FACE:\n return faceToCanonical(el.faceElement);\n case ElementType.PTT:\n return pttToCanonical(el.pttElement);\n case ElementType.VIDEO:\n return videoToCanonical(el.videoElement);\n case ElementType.FILE:\n return fileToCanonical(el.fileElement);\n case ElementType.REPLY:\n return replyToCanonical(el.replyElement);\n case ElementType.ARK:\n return arkToCanonical(el.arkElement, el);\n default:\n return { type: \"unknown\", raw: el };\n }\n}\n\n/** NT RawMessage → canonical 元素(与 toSendElements 对称,接收方向)。 */\nexport function toCanonicalElements(msg: RawMessage): CanonicalElement[] {\n return msg.elements.map(toCanonicalElement);\n}\n\n/**\n * canonical json → ARK bytesData 字符串:字符串原样透传(OB11 json 段 /\n * satori 发送链给的已是卡片 JSON 文本),其余值 JSON 序列化。\n * 与接收方向 arkToCanonical 对称(bytesData 字段名 2026-10-25 真实消息实测)。\n */\nfunction jsonToArkBytes(raw: unknown): string {\n if (typeof raw === \"string\") {\n return raw;\n }\n try {\n return JSON.stringify(raw) ?? \"\";\n } catch (err) {\n throw kernelError(`json 元素序列化失败: ${String(err)}`, \"INVALID_PARAM\");\n }\n}\n\n/** canonical 元素 → NT 发送元素(text/at/face/image/voice/reply 核心 + json ARK 透传)。 */\nexport function toSendElements(elements: CanonicalElement[]): SendMessageElement[] {\n const out: SendMessageElement[] = [];\n for (const el of elements) {\n switch (el.type) {\n case \"text\":\n out.push({ elementType: ElementType.TEXT, textElement: { content: el.text } });\n break;\n case \"at\":\n if (el.target === \"all\") {\n out.push({\n elementType: ElementType.TEXT,\n textElement: { content: el.display ?? \"@\", atType: 1 },\n });\n } else {\n out.push({\n elementType: ElementType.TEXT,\n textElement: { content: el.display ?? \"@\", atType: 2, atUid: el.target },\n });\n }\n break;\n case \"face\":\n out.push({\n elementType: ElementType.FACE,\n faceElement: { faceIndex: Number(el.id) },\n });\n break;\n case \"image\":\n out.push({ elementType: ElementType.PIC, picElement: { picPath: el.path } });\n break;\n case \"voice\":\n out.push({ elementType: ElementType.PTT, pttElement: { filePath: el.path } });\n break;\n case \"reply\":\n out.push({\n elementType: ElementType.REPLY,\n replyElement: {\n replayMsgId: el.messageId,\n replayMsgSeq: el.messageId,\n replayMsgTime: \"0\",\n },\n });\n break;\n case \"video\":\n // 视频发送必须经 MsgApi.prepareVideoElement 预处理(md5/目标路径/\n // 缩略图 Map 等字段无法从 canonical 直接得到)。canonical → NT\n // 元元素只在 prepareSendElements 内部发生,此处属误用。\n throw kernelError(\n \"视频发送需经 MsgApi 预处理(prepareVideoElement)\",\n \"INVALID_PARAM\",\n );\n case \"json\":\n // ARK 卡片透传(elementType=10):bytesData 装卡片 JSON 字符串,\n // 与接收方向 arkToCanonical 对称;wrapper 对缺省字段容忍(djinni\n // 转换缺省字段为默认值)。发送失败走 KernelError 语义,不崩溃。\n out.push({\n elementType: ElementType.ARK,\n arkElement: { bytesData: jsonToArkBytes(el.raw) },\n });\n break;\n case \"file\":\n case \"forward\":\n case \"xml\":\n case \"unknown\":\n throw kernelError(`发送元素 ${el.type} 暂不支持(P2-2 探测后补)`, \"INVALID_PARAM\");\n default:\n // exhaustive:CanonicalElement 无其他成员\n break;\n }\n }\n return out;\n}\n\n/** 协议无关的规范消息元素。 */\nexport type CanonicalElement =\n | { type: \"text\"; text: string }\n | { type: \"at\"; target: string; display?: string }\n | { type: \"image\"; path: string; url?: string; size?: { width: number; height: number } }\n | { type: \"face\"; id: string }\n | { type: \"voice\"; path: string; durationMs?: number; url?: string }\n | {\n type: \"video\";\n path: string;\n url?: string;\n thumbPath?: string;\n width?: number;\n height?: number;\n durationSec?: number;\n }\n | { type: \"file\"; path: string; name?: string; size?: number }\n | { type: \"reply\"; messageId: string }\n | { type: \"forward\"; messageIds: string[] }\n | { type: \"json\"; raw: unknown }\n | { type: \"xml\"; raw: unknown }\n | { type: \"unknown\"; raw: unknown };\n","/**\n * NodeIKernelGroupService:群服务接口面(自研描述,非移植)\n *\n * 依据:getGroupService() 运行时反射 + wrapper 外部契约(接口签名是 QQ 的外部事实,\n * 自研描述其形状,零复制实现)。\n * 只收录 apis/group 当前需要的方法;其余按需探测后补齐。\n */\nimport type { GeneralCallResult } from \"./msg-service.js\";\n\n/** 群成员角色(QQ wrapper 契约)。 */\nexport const NTGroupMemberRole = {\n UNSPECIFIED: 0,\n STRANGER: 1,\n MEMBER: 2,\n ADMIN: 3,\n OWNER: 4,\n} as const;\nexport type NTGroupMemberRole = (typeof NTGroupMemberRole)[keyof typeof NTGroupMemberRole];\n\n/** 踢人请求项(KickMemberV2Req.kickList 成员,说明书参考,字段值待探测校准)。 */\nexport interface KickMemberInfo {\n optFlag: number;\n optOperate: number;\n optMemberUid: string;\n optBytesMsg: string;\n}\n\n/** 踢人请求(set_group_kick 的 kickMemberV2 参数,说明书参考)。 */\nexport interface KickMemberV2Req {\n groupCode: string;\n kickFlag: number;\n kickList: KickMemberInfo[];\n kickListUids: string[];\n kickMsg: string;\n}\n\n/** @all 剩余次数(getGroupRemainAtTimes 返回)。 */\nexport interface GroupRemainAtTimes {\n canAtAll: boolean;\n remainAtAllCountForUin: number;\n remainAtAllCountForGroup: number;\n atTimesMsg: string;\n canNotAtAllMsg: string;\n}\n\n/** 群通知类型(GroupNotifyMsgType,说明书参考)。 */\nexport const GroupNotifyMsgType = {\n UN_SPECIFIED: 0,\n INVITED_BY_MEMBER: 1, // 邀请入群\n REFUSE_INVITED: 2,\n REFUSED_BY_ADMINI_STRATOR: 3,\n AGREED_TOJOIN_DIRECT: 4,\n INVITED_NEED_ADMINI_STRATOR_PASS: 5,\n AGREED_TO_JOIN_BY_ADMINI_STRATOR: 6,\n REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS: 7, // 申请入群\n SET_ADMIN: 8,\n KICK_MEMBER_NOTIFY_ADMIN: 9,\n KICK_MEMBER_NOTIFY_KICKED: 10,\n MEMBER_LEAVE_NOTIFY_ADMIN: 11,\n CANCEL_ADMIN_NOTIFY_CANCELED: 12,\n CANCEL_ADMIN_NOTIFY_ADMIN: 13,\n TRANSFER_GROUP_NOTIFY_OLDOWNER: 14,\n TRANSFER_GROUP_NOTIFY_ADMIN: 15,\n} as const;\nexport type GroupNotifyMsgType = (typeof GroupNotifyMsgType)[keyof typeof GroupNotifyMsgType];\n\n/** 群通知处理状态(GroupNotifyMsgStatus,说明书参考)。 */\nexport const GroupNotifyMsgStatus = {\n KINIT: 0,\n KUNHANDLE: 1, // 未处理\n KAGREED: 2,\n KREFUSED: 3,\n KIGNORED: 4,\n} as const;\nexport type GroupNotifyMsgStatus = (typeof GroupNotifyMsgStatus)[keyof typeof GroupNotifyMsgStatus];\n\n/** 群请求操作类型(NTGroupRequestOperateTypes,说明书参考)。 */\nexport const NTGroupRequestOperateTypes = {\n KUNSPECIFIED: 0,\n KAGREE: 1,\n KREFUSE: 2,\n KIGNORE: 3,\n KDELETE: 4,\n} as const;\nexport type NTGroupRequestOperateTypes =\n (typeof NTGroupRequestOperateTypes)[keyof typeof NTGroupRequestOperateTypes];\n\n/** 群通知(getSingleScreenNotifies 返回项,说明书参考,待探测校准)。 */\nexport interface GroupNotify {\n seq: string;\n type: GroupNotifyMsgType;\n status: GroupNotifyMsgStatus;\n group?: { groupCode: string; groupName: string };\n user1?: { uid: string; nickName: string };\n user2?: { uid: string; nickName: string };\n actionTime?: string;\n postscript?: string;\n warningTips?: string;\n}\n\n/** 禁言成员(getGroupShutUpMemberList 返回项,说明书参考,待探测校准)。 */\nexport interface ShutUpGroupMember {\n uid: string;\n qid: string;\n uin: string;\n nick: string;\n remark: string;\n cardName: string;\n shutUpTime: number;\n memberSpecialTitle?: string;\n}\n\n/** 群列表项(Group 实体,说明书参考)。 */\nexport interface Group {\n groupCode: string;\n groupName: string;\n memberCount: number;\n maxMember: number;\n remarkName?: string;\n groupOwnerId?: { memberUin: string; memberUid: string };\n memberRole?: number;\n isTop?: boolean;\n [key: string]: unknown;\n}\n\n/** 群详情(getGroupDetailInfo 返回,说明书参考)。 */\nexport interface GroupDetailInfo {\n groupCode: string;\n groupUin: string;\n ownerUin: string;\n ownerUid?: string;\n groupName: string;\n memberNum: number;\n maxMemberNum: number;\n remarkName?: string;\n isTop?: boolean;\n groupCreateTime?: number;\n [key: string]: unknown;\n}\n\n/** 群成员(getAllMemberList result.infos 的 value,说明书参考)。 */\nexport interface GroupMember {\n uid: string;\n uin: string;\n nick: string;\n cardName: string;\n remark: string;\n role: NTGroupMemberRole;\n shutUpTime: number; // 禁言秒数\n joinTime: string;\n lastSpeakTime: string;\n memberSpecialTitle?: string;\n sex?: number;\n age?: number;\n isRobot?: boolean;\n [key: string]: unknown;\n}\n\n/** 群公告(getGroupBulletin 返回项,说明书参考,待探测校准)。 */\nexport interface GroupBulletin {\n /** 公告 id(delete_group_announcement 的 announcement_id)。 */\n announcementId?: string;\n /** 发送者 uid。 */\n senderUid?: string;\n /** 发送时间(秒)。 */\n publishTime?: number;\n /** 文本内容。 */\n content?: string;\n /** 公告图片(url 待实机确认)。 */\n pic?: { url?: string; width?: number; height?: number; id?: string };\n [key: string]: unknown;\n}\n\n/** 群服务:apis/group 用到的核心方法面。 */\nexport interface NodeIKernelGroupService {\n addKernelGroupListener(listener: unknown): number;\n removeKernelGroupListener(listenerId: number): void;\n getGroupList(force: boolean): Promise<GeneralCallResult>;\n getGroupDetailInfo(groupCode: string, groupInfoSource: number): Promise<GeneralCallResult>;\n getAllMemberList(\n groupCode: string,\n forceFetch: boolean,\n ): Promise<{\n errCode: number;\n errMsg: string;\n result: {\n ids: Array<{ uid: string; index: number }>;\n infos: Map<string, GroupMember>;\n finish: boolean;\n };\n }>;\n getMemberInfo(\n groupCode: string,\n uids: string[],\n forceFetch: boolean,\n ): Promise<GeneralCallResult>;\n /** uin → uid(私聊发送等需要,说明书参考)。 */\n getUidByUins(\n uins: string[],\n ): Promise<{ errCode: number; errMsg: string; uids: Map<string, string> }>;\n /** uid → uin。 */\n getUinByUids(\n uids: string[],\n ): Promise<{ errCode: number; errMsg: string; uins: Map<string, string> }>;\n /** 踢人(set_group_kick)。 */\n kickMemberV2(param: KickMemberV2Req): Promise<GeneralCallResult>;\n /** 成员禁言(set_group_ban;timeStamp=0 解除禁言)。 */\n setMemberShutUp(\n groupCode: string,\n memberTimes: Array<{ uid: string; timeStamp: number }>,\n ): Promise<GeneralCallResult>;\n /** 全员禁言(set_group_whole_ban)。 */\n setGroupShutUp(groupCode: string, shutUp: boolean): Promise<GeneralCallResult>;\n /** 设置管理员(set_group_admin;void 语义,乐观处理)。 */\n modifyMemberRole(groupCode: string, uid: string, role: NTGroupMemberRole): void;\n /** 设置群名片(set_group_card;void 语义)。 */\n modifyMemberCardName(groupCode: string, uid: string, cardName: string): void;\n /** 修改群名(set_group_name)。 */\n modifyGroupName(\n groupCode: string,\n groupName: string,\n isNormalMember: boolean,\n ): Promise<GeneralCallResult>;\n /** 退群(set_group_leave)。 */\n quitGroupV2(param: {\n groupCode: string;\n needDeleteLocalMsg: boolean;\n }): Promise<GeneralCallResult>;\n /** 设置精华消息(msgSeq/msgRandom 取自消息本体)。 */\n addGroupEssence(param: {\n groupCode: string;\n msgRandom: number;\n msgSeq: number;\n }): Promise<unknown>;\n /** 取消精华消息。 */\n removeGroupEssence(param: {\n groupCode: string;\n msgRandom: number;\n msgSeq: number;\n }): Promise<unknown>;\n /** @all 剩余次数(get_group_at_all_remain)。 */\n getGroupRemainAtTimes(groupCode: string): Promise<\n Omit<GeneralCallResult, \"result\"> & {\n errCode: number;\n atInfo: GroupRemainAtTimes;\n }\n >;\n /** 单屏通知列表(get_group_system_msg / set_group_add_request 共用;列表可能走 listener,宽松解包)。 */\n getSingleScreenNotifies(\n doubt: boolean,\n startSeq: string,\n count: number,\n ): Promise<GeneralCallResult & { result?: unknown }>;\n /** 禁言成员列表(get_group_shut_list;同 listener 风格,宽松解包)。 */\n getGroupShutUpMemberList(groupCode: string): Promise<GeneralCallResult & { result?: unknown }>;\n /** 处理群请求(set_group_add_request 应答;postscript 空值可能导致失败,默认空格)。 */\n operateSysNotify(\n doubt: boolean,\n operateMsg: {\n operateType: NTGroupRequestOperateTypes;\n targetMsg: {\n seq: string;\n type: GroupNotifyMsgType;\n groupCode: string;\n postscript: string;\n };\n },\n ): Promise<void>;\n /** 群公告列表(get_group_announcements;返回形状待探测校准)。 */\n getGroupBulletin(groupCode: string): Promise<GeneralCallResult & { result?: unknown }>;\n /** 发群公告(send_group_announcement;参数形状待探测校准)。 */\n publishGroupBulletin(param: {\n groupCode: string;\n content: string;\n picId?: string;\n }): Promise<unknown>;\n /** 上传公告图片(image_uri 落盘后调用;返回 picId 待校准)。 */\n uploadGroupBulletinPic(groupCode: string, imagePath: string): Promise<unknown>;\n /** 删群公告(delete_group_announcement;参数待校准)。 */\n deleteGroupBulletin(groupCode: string, announcementId: string): Promise<unknown>;\n /** 专属头衔(set_group_member_special_title;待校准,备选 setGroupIdentitySpec)。 */\n setIdentityTitleInfo(groupCode: string, uid: string, title: string): Promise<unknown>;\n /** 群头像(set_group_avatar;参数待校准;Profile setHeader 同名方法)。 */\n setHeader(groupCode: string, imagePath: string): Promise<unknown>;\n}\n","/**\n * wrapper.node 的 NAPI 接口面(自研描述,非移植)\n *\n * 来源:wrapper.node 在 QQ 定制版 Electron 里通过 napi_define_class / napi_set_named_property\n * 注册的 JS 类(2026-08-05 Ghidra 反编译 FUN_180c6274a / FUN_180c80224 确认类名)。\n * 这些接口是 wrapper.node 对外暴露的外部系统契约——我们自研描述其形状,未复制任何第三方代码。\n *\n * 注意:这里所有对象由 QQ 运行时的 NAPI 层自动构建/转换,业务层禁止触碰 C++ ABI。\n */\n\n/** 平台类型(与 wrapper.node 的 platform 字段对应)。 */\nexport const PlatformType = {\n KUNKNOWN: 0,\n KANDROID: 1,\n KIOS: 2,\n KWINDOWS: 3,\n KMAC: 4,\n KLINUX: 5,\n} as const;\nexport type PlatformType = (typeof PlatformType)[keyof typeof PlatformType];\n\n/** 厂商类型(deviceInfo.vendorType)。 */\nexport const VendorType = {\n KNOSETONIOS: 0,\n KUNSUPPORTANDROIDPUSH: 1,\n KSUPPORTTPNS: 2,\n KSUPPORTHMS: 3,\n KSUPPORTOPPOPUSH: 4,\n KSUPPORTVIVOPUSH: 5,\n KSUPPORTGOOGLEPUSH: 99,\n} as const;\nexport type VendorType = (typeof VendorType)[keyof typeof VendorType];\n\n/** NodeQQNTWrapperUtil:通用工具类(静态服务)。 */\nexport interface NodeQQNTWrapperUtil {\n get(): NodeQQNTWrapperUtil;\n getNTUserDataInfoConfig(): string;\n // 文件/哈希工具(按需补齐,运行时反射枚举为准)\n getFileSize(path: string): Promise<number>;\n fileIsExist(path: string): unknown;\n makeDirByPath(path: string): unknown;\n}\n\n/** engine 初始化配置(initWithDeskTopConfig 第一参,JS 对象直接传)。 */\nexport interface EnginInitDesktopConfig {\n base_path_prefix: string;\n platform_type: PlatformType;\n /** 桌面端应用类型(engine.initWithDeskTopConfig 契约,桌面端固定 4;移动端为其它值)。 */\n app_type: 4;\n app_version: string;\n os_version: string;\n use_xlog: boolean;\n qua: string;\n global_path_config: {\n desktopGlobalPath: string;\n };\n /**\n * 缩略图生成参数(wrapper 契约):maxSide 最长边上限 / minSide 最短边下限 /\n * longLimit 长图最长短边比例上限 / density 像素密度倍数。\n */\n thumb_config: { maxSide: 324; minSide: 48; longLimit: 6; density: 2 };\n}\n\n/** NodeIQQNTWrapperEngine 实例方法。 */\nexport interface NodeIQQNTWrapperEngine {\n initWithDeskTopConfig(config: EnginInitDesktopConfig, adapter: NodeIGlobalAdapter): void;\n initWithMobileConfig(config: unknown, adapter: NodeIGlobalAdapter): void;\n initLog(arg: unknown): void;\n setLogLevel(arg: unknown): void;\n onSendSSOReply(a: unknown, b: unknown, c: unknown, d: unknown, e: unknown): void;\n}\n\n/** NodeIQQNTWrapperEngine 构造器(含静态 get())。 */\nexport type NodeIQQNTWrapperEngineCtor = new () => NodeIQQNTWrapperEngine & {\n get(): NodeIQQNTWrapperEngine;\n};\n\n/** desktopPathConfig 结构。 */\nexport interface DesktopPathConfig {\n account_path: string;\n}\n\n/** rdeliveryConfig 结构。 */\nexport interface RDeliveryConfig {\n appKey: string;\n systemId: number;\n appId: string;\n logicEnvironment: string;\n platform: PlatformType;\n language: string;\n sdkVersion: string;\n userId: string;\n appVersion: string;\n osVersion: string;\n bundleId: string;\n serverUrl: string;\n fixedAfterHitKeys: string[];\n}\n\n/** deviceInfo 结构。 */\nexport interface DeviceInfo {\n guid: string;\n buildVer: string;\n localId: number;\n devName: string;\n devType: string;\n vendorName: string;\n osVer: string;\n vendorOsName: string;\n setMute: boolean;\n vendorType: VendorType;\n}\n\n/** session.init 的完整配置(JS 对象,NAPI 自动转换)。 */\nexport interface WrapperSessionInitConfig {\n selfUin: string;\n selfUid: string;\n desktopPathConfig: DesktopPathConfig;\n clientVer: string;\n a2: string;\n d2: string;\n d2Key: string;\n machineId: string;\n platform: PlatformType;\n platVer: string;\n appid: string;\n rdeliveryConfig: RDeliveryConfig;\n defaultFileDownloadPath: string;\n deviceInfo: DeviceInfo;\n deviceConfig: string;\n}\n\n/** NodeIQQNTWrapperSession 构造器(含静态 get / getNTWrapperSession / create)。 */\nexport type NodeIQQNTWrapperSessionCtor = {\n new (): NodeIQQNTWrapperSession;\n get(): NodeIQQNTWrapperSession;\n getNTWrapperSession(name: string): NodeIQQNTWrapperSession;\n create(): NodeIQQNTWrapperSession;\n};\n\n/** NodeIQQNTWrapperSession:会话主入口(实例方法)。 */\nexport interface NodeIQQNTWrapperSession {\n init(\n config: WrapperSessionInitConfig,\n depends: NodeIDependsAdapter,\n dispatcher: NodeIDispatcherAdapter,\n listener: NodeIKernelSessionListener,\n ): void;\n startNT(sessionId?: number): void;\n close(arg: unknown): void;\n onLine(arg: unknown): void;\n offLine(arg: unknown): void;\n disableIpDirect(arg: unknown): void;\n getAccountPath(arg: unknown): string;\n updateTicket(arg: unknown): void;\n // 60+ getService:运行时反射枚举后补齐\n getMsgService(): unknown;\n getGroupService(): unknown;\n getBuddyService(): unknown;\n getTicketService(): unknown;\n getRichMediaService(): unknown;\n /** 闪传服务(诊断用:调查 rich media transfer failed 时验证初始化触发点)。 */\n getFlashTransferService(): unknown;\n getProfileService(): unknown;\n /** 会话列表(P2 批次二 get_peer_pins 数据源;2026-10-31 探测方法面)。 */\n getRecentContactService(): unknown;\n getProfileLikeService(): unknown;\n getTipOffService(): unknown;\n}\n\n/** NodeIQQNTStartupSessionWrapper:启动会话实例方法。 */\nexport interface NodeIQQNTStartupSessionWrapper {\n stop(): void;\n start(): void;\n createWithModuleList(uk: unknown): unknown;\n getSessionIdList(): Promise<Map<unknown, unknown>>;\n}\n\n/** NodeIQQNTStartupSessionWrapper 构造器(含静态 create)。 */\nexport type NodeIQQNTStartupSessionWrapperCtor = new () => NodeIQQNTStartupSessionWrapper & {\n create(): NodeIQQNTStartupSessionWrapper;\n};\n\n/** NodeIKernelLoginService 实例方法。 */\nexport interface NodeIKernelLoginService {\n addKernelLoginListener(listener: unknown): number;\n removeKernelLoginListener(listener: number): void;\n initConfig(config: unknown): void;\n connect(): void;\n getLoginList(): Promise<{ result: number; LocalLoginInfoList: unknown[] }>;\n quickLoginWithUin(uin: string): Promise<unknown>;\n getQRCodePicture(): boolean;\n getMsfStatus(): number;\n /** 设备指纹 guid(wrapper.node 字符串分析实证,2026-08-12;无 getMachineId)。 */\n getMachineGuid(): string;\n}\n\n/** NodeIKernelLoginService 构造器(含静态 get)。 */\nexport type NodeIKernelLoginServiceCtor = new () => NodeIKernelLoginService & {\n get(): NodeIKernelLoginService;\n};\n\n/** NodeIO3MiscService:O3 杂项服务。 */\nexport interface NodeIO3MiscService {\n get(): NodeIO3MiscService;\n addO3MiscListener(listener: unknown): number;\n}\n\n// adapter 类型(session.init 第二/三参、engine.initWithDeskTopConfig 第二参)\n// 注意:wrapper exports **不含** NodeI*Adapter / NodeI*Listener 构造器(实测 89 键无)。\n// 传普通 JS 对象即可——NAPI 反射读取对象上的方法回调。方法名是 QQ wrapper 的外部契约\n//(2026-08-05 运行时行为实测确认,接口为自研描述)。\nexport interface NodeIGlobalAdapter {\n onLog?(...args: unknown[]): void;\n onGetSrvCalTime?(...args: unknown[]): void;\n onShowErrUITips?(...args: unknown[]): void;\n fixPicImgType?(...args: unknown[]): unknown;\n getAppSetting?(...args: unknown[]): unknown;\n onInstallFinished?(...args: unknown[]): void;\n onUpdateGeneralFlag?(...args: unknown[]): void;\n onGetOfflineMsg?(...args: unknown[]): void;\n}\n\n/** session.init 第二参:依赖回调(普通 JS 对象)。 */\nexport interface NodeIDependsAdapter {\n onMSFStatusChange?(statusType: number, changeReasonType: number): void;\n onMSFSsoError?(code: number, desc: string): void;\n getGroupCode?(...args: unknown[]): unknown;\n}\n\n/** session.init 第三参:请求分发回调(普通 JS 对象)。 */\nexport interface NodeIDispatcherAdapter {\n dispatchRequest?(arg: unknown): void;\n dispatchCall?(arg: unknown): void;\n dispatchCallWithJson?(arg: unknown): void;\n}\n\n/** 登录监听器接口(普通 JS 对象,传给 loginService.addKernelLoginListener)。自研描述。 */\nexport interface IKernelLoginListener {\n onLoginConnected?(...args: unknown[]): void;\n onLoginDisConnected?(...args: unknown[]): void;\n onLoginConnecting?(...args: unknown[]): void;\n onQRCodeGetPicture?(arg: { pngBase64QrcodeData: string; qrcodeUrl: string }): void;\n onQRCodeLoginPollingStarted?(...args: unknown[]): void;\n onQRCodeSessionUserScaned?(...args: unknown[]): void;\n onQRCodeLoginSucceed?(loginResult: { uid: string; uin: string; nick?: string }): void;\n onQRCodeSessionFailed?(errType: number, errCode: number, errMsg: string): void;\n onLoginFailed?(args: unknown): void;\n onLogoutSucceed?(...args: unknown[]): void;\n onLogoutFailed?(...args: unknown[]): void;\n onUserLoggedIn?(userid: string): void;\n onQRCodeSessionQuickLoginFailed?(...args: unknown[]): void;\n onPasswordLoginFailed?(...args: unknown[]): void;\n onQQLoginNumLimited?(...args: unknown[]): void;\n onLoginState?(...args: unknown[]): void;\n}\n\n/**\n * NodeIKernelSessionListener:session.init 第四参监听器(普通 JS 对象)。\n * init 完成信号:以 onOpentelemetryInit(is_init===true) 为准(onSessionInitComplete 为辅)。\n */\nexport interface NodeIKernelSessionListener {\n onNTSessionCreate?(sessionId: string): void;\n onGProSessionCreate?(sessionId: string): void;\n onSessionInitComplete?(result: unknown): void;\n onOpentelemetryInit?(info: { is_init: boolean; is_report: boolean }): void;\n onUserOnlineResult?(result: unknown): void;\n onGetSelfTinyId?(result: unknown): void;\n}\n\n/** wrapper.node 的 NAPI 顶层导出(module.exports)。 */\nexport interface WrapperNodeApi {\n NodeIO3MiscService: NodeIO3MiscService;\n NodeQQNTWrapperUtil: NodeQQNTWrapperUtil;\n /** 构造器:`new wrapper.NodeIQQNTWrapperSession()`。 */\n NodeIQQNTWrapperSession: NodeIQQNTWrapperSessionCtor;\n NodeIQQNTStartupSessionWrapper: NodeIQQNTStartupSessionWrapperCtor;\n /** 构造器:`new wrapper.NodeIQQNTWrapperEngine()`。 */\n NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngineCtor;\n /** 构造器:`new wrapper.NodeIKernelLoginService()`。 */\n NodeIKernelLoginService: NodeIKernelLoginServiceCtor;\n // 注:wrapper exports **不含** NodeI*Adapter / NodeI*Listener 构造器(实测 89 键无)——\n // adapter/listener 一律传普通 JS 对象(见 NodeIGlobalAdapter 等接口)。\n}\n","/**\n * GroupApi:群语义化 API(ADR-009 统一错误语义)\n *\n * 内部解包原生返回:成功返回纯业务值,失败抛 KernelError。\n * 方法面(P2-4 + P2-10):群列表 / 群详情 / 成员列表 / 成员详情 / uin↔uid +\n * 群管操作(踢人 / 禁言 / 管理员 / 名片 / 群名 / 退群 / 精华消息 / @all 剩余)。\n */\nimport { isKernelError, kernelError } from \"../infra/index.js\";\nimport type {\n Group,\n GroupBulletin,\n GroupDetailInfo,\n GroupMember,\n KickMemberV2Req,\n NodeIKernelGroupService,\n NodeIKernelMsgService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\nimport { ChatType, NTGroupMemberRole } from \"../types/index.js\";\nimport { checkLooseResult, unwrap } from \"./result.js\";\n\n/** 群 API:从 session 拿 group service,包装成语义化方法。 */\nexport class GroupApi {\n private readonly service: NodeIKernelGroupService;\n /** 精华消息需要 msg service(按 msgId 拉消息取 msgSeq/msgRandom)。 */\n private readonly msgService: NodeIKernelMsgService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service = session.getGroupService() as unknown as NodeIKernelGroupService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getGroupService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n const msgService = session.getMsgService() as unknown as NodeIKernelMsgService | null;\n if (msgService === null || msgService === undefined) {\n throw kernelError(\"getMsgService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.msgService = msgService;\n }\n\n /**\n * 群列表(force=true 触发原生刷新)。\n *\n * ⚠️ 实测校准(2026-08-08 端到端联调):原生 getGroupList 返回仅\n * `{ result, errMsg }`——**列表数据经 onGroupListUpdate 事件推送**,\n * 不走返回值。消费群列表一律从 `GroupCache.listGroups()` 读(缓存由\n * 事件主动维护);本方法仅作「触发刷新」用,返回值恒为 []。\n */\n async getGroupList(force = false): Promise<Group[]> {\n const raw = await this.service.getGroupList(force);\n if (raw.result !== 0) {\n unwrap(\"getGroupList\", raw.result, raw.errMsg);\n }\n return [];\n }\n\n /** 群详情(groupCode 为群号字符串)。\n * getGroupDetailInfo 返回形状:失败 result=错误码;成功详情可能直接在 result\n * 对象里,也可能在 groupInfo/detailInfo 字段——三种形状都兼容(探测校准)。\n * 2026-08-07 修复:成功(result=0)时不再把 {result:0,errMsg} 原样当详情返回,\n * 而是提取详情字段;形状异常抛 UNKNOWN 而非静默返回垃圾数据。 */\n async getGroupInfo(groupCode: string): Promise<GroupDetailInfo> {\n const raw = await this.service.getGroupDetailInfo(groupCode, 0);\n const { result, errMsg } = raw as { result?: unknown; errMsg: string };\n if (typeof result === \"number\" && result !== 0) {\n unwrap(\"getGroupDetailInfo\", result, errMsg);\n }\n const detail = extractGroupDetail(result, raw);\n if (detail === null) {\n throw kernelError(\n `getGroupDetailInfo 返回形状异常(groupCode=${groupCode}): ${errMsg}`,\n \"UNKNOWN\",\n );\n }\n return detail;\n }\n\n /** 群成员列表(forceFetch=true 强制拉取,默认缓存优先)。 */\n async getGroupMemberList(groupCode: string, forceFetch = false): Promise<GroupMember[]> {\n const raw = await this.service.getAllMemberList(groupCode, forceFetch);\n if (raw.errCode !== 0) {\n throw kernelError(`getAllMemberList 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return [...raw.result.infos.values()];\n }\n\n /** 群成员详情(uids 为空返回空数组)。 */\n async getGroupMemberInfo(groupCode: string, uids: string[]): Promise<GroupMember[]> {\n if (uids.length === 0) {\n return [];\n }\n const raw = await this.service.getMemberInfo(groupCode, uids, true);\n unwrap(\"getMemberInfo\", raw.result, raw.errMsg);\n // 返回形状待探测校准:infos Map<uid, GroupMember>\n const { infos } = raw as unknown as { infos?: Map<string, GroupMember> };\n if (infos instanceof Map) {\n return [...infos.values()];\n }\n return [];\n }\n\n /** uin → uid(私聊发送等需要)。 */\n async uinToUid(uins: string[]): Promise<Map<string, string>> {\n if (uins.length === 0) {\n return new Map();\n }\n const raw = await this.service.getUidByUins(uins);\n if (raw.errCode !== 0) {\n throw kernelError(`getUidByUins 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return raw.uids;\n }\n\n /** uid → uin。 */\n async uidToUin(uids: string[]): Promise<Map<string, string>> {\n if (uids.length === 0) {\n return new Map();\n }\n const raw = await this.service.getUinByUids(uids);\n if (raw.errCode !== 0) {\n throw kernelError(`getUinByUids 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return raw.uins;\n }\n\n /** 群成员角色 → OB11 role 字符串。 */\n static roleToString(role: NTGroupMemberRole): \"owner\" | \"admin\" | \"member\" {\n if (role === NTGroupMemberRole.OWNER) {\n return \"owner\";\n }\n if (role === NTGroupMemberRole.ADMIN) {\n return \"admin\";\n }\n return \"member\";\n }\n\n /**\n * 踢人(set_group_kick)。memberUids 为成员 uid;refuseForever=true 拉黑拒绝再次入群。\n * kickMemberV2 的 kickFlag/kickList 字段值待探测校准(TODO P3 联调)。\n */\n async kickMember(\n groupCode: string,\n memberUids: string[],\n refuseForever: boolean,\n reason = \"\",\n ): Promise<void> {\n if (memberUids.length === 0) {\n throw kernelError(\"kickMember 需要至少一个成员 uid\", \"INVALID_PARAM\");\n }\n // refuseForever=true 拒绝再次入群(optFlag 字段值待探测校准)\n let optFlag = 1;\n if (refuseForever) {\n optFlag = 0;\n }\n const req: KickMemberV2Req = {\n groupCode,\n kickFlag: 0,\n kickList: memberUids.map((uid) => ({\n optFlag,\n optOperate: 0,\n optMemberUid: uid,\n optBytesMsg: \"\",\n })),\n kickListUids: memberUids,\n kickMsg: reason,\n };\n const raw = await this.service.kickMemberV2(req);\n unwrap(\"kickMemberV2\", raw.result, raw.errMsg);\n }\n\n /** 成员禁言(duration 秒,0 解除禁言)。 */\n async setMemberShutUp(\n groupCode: string,\n members: Array<{ uid: string; duration: number }>,\n ): Promise<void> {\n if (members.length === 0) {\n throw kernelError(\"setMemberShutUp 需要至少一个成员\", \"INVALID_PARAM\");\n }\n const raw = await this.service.setMemberShutUp(\n groupCode,\n members.map((m) => ({ uid: m.uid, timeStamp: m.duration })),\n );\n unwrap(\"setMemberShutUp\", raw.result, raw.errMsg);\n }\n\n /** 全员禁言(enable=true 开启全群禁言)。 */\n async setGroupShutUp(groupCode: string, enable: boolean): Promise<void> {\n const raw = await this.service.setGroupShutUp(groupCode, enable);\n unwrap(\"setGroupShutUp\", raw.result, raw.errMsg);\n }\n\n /** 设置管理员(role=ADMIN 设管理,MEMBER 取消;void 语义乐观处理)。 */\n setMemberRole(groupCode: string, uid: string, role: NTGroupMemberRole): void {\n this.service.modifyMemberRole(groupCode, uid, role);\n }\n\n /** 设置群名片(void 语义乐观处理)。 */\n setMemberCardName(groupCode: string, uid: string, cardName: string): void {\n this.service.modifyMemberCardName(groupCode, uid, cardName);\n }\n\n /** 修改群名。 */\n async modifyGroupName(groupCode: string, groupName: string): Promise<void> {\n const raw = await this.service.modifyGroupName(groupCode, groupName, false);\n unwrap(\"modifyGroupName\", raw.result, raw.errMsg);\n }\n\n /** 退群(needDeleteLocalMsg=is_dismiss:是否解散群,仅群主)。 */\n async quitGroup(groupCode: string, needDeleteLocalMsg = false): Promise<void> {\n const raw = await this.service.quitGroupV2({ groupCode, needDeleteLocalMsg });\n unwrap(\"quitGroupV2\", raw.result, raw.errMsg);\n }\n\n /** 设置精华消息(msgId 经 msgService 取 msgSeq/msgRandom)。 */\n async addGroupEssence(groupCode: string, msgId: string): Promise<void> {\n await this.setEssence(groupCode, msgId, true);\n }\n\n /** 取消精华消息。 */\n async removeGroupEssence(groupCode: string, msgId: string): Promise<void> {\n await this.setEssence(groupCode, msgId, false);\n }\n\n /** 精华消息公共流程:按 msgId 拉消息 → add/removeGroupEssence。 */\n private async setEssence(groupCode: string, msgId: string, add: boolean): Promise<void> {\n const peer = { chatType: ChatType.GROUP, peerUid: groupCode };\n const raw = await this.msgService.getMsgsByMsgId(peer, [msgId]);\n unwrap(\"getMsgsByMsgId\", raw.result, raw.errMsg);\n const msg = raw.msgList?.[0];\n if (msg === undefined) {\n throw kernelError(`消息 ${msgId} 不存在`, \"NOT_FOUND\");\n }\n const param = {\n groupCode,\n msgRandom: Number(msg.msgRandom ?? 0),\n msgSeq: Number(msg.msgSeq),\n };\n let label = \"removeGroupEssence\";\n let res: { result?: unknown; errMsg?: unknown } | undefined;\n if (add) {\n label = \"addGroupEssence\";\n res = (await this.service.addGroupEssence(param)) as\n | { result?: unknown; errMsg?: unknown }\n | undefined;\n } else {\n res = (await this.service.removeGroupEssence(param)) as\n | { result?: unknown; errMsg?: unknown }\n | undefined;\n }\n checkLooseResult(label, res);\n }\n\n /**\n * 群公告列表(get_group_announcements;getGroupBulletin 返回形状待探测校准,\n * 兼容 result 数组 / result.bulletins 等宽松提取)。\n */\n async getGroupAnnouncements(groupCode: string): Promise<GroupBulletin[]> {\n let raw: { result?: unknown; errMsg?: unknown };\n try {\n raw = (await this.service.getGroupBulletin(groupCode)) as typeof raw;\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`getGroupBulletin 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n unwrap(\"getGroupBulletin\", toResultNumber(raw.result), raw.errMsg as string | undefined);\n return extractBulletins(raw.result);\n }\n\n /**\n * 发群公告(send_group_announcement;imagePath 提供时先 uploadGroupBulletinPic\n * 再带 picId 发布——两步参数形状均待探测校准)。\n */\n async publishGroupAnnouncement(\n groupCode: string,\n content: string,\n imagePath?: string,\n ): Promise<void> {\n let picId: string | undefined;\n if (imagePath !== undefined && imagePath !== \"\") {\n picId = await this.uploadBulletinPic(groupCode, imagePath);\n }\n try {\n checkLooseResult(\n \"publishGroupBulletin\",\n (await this.service.publishGroupBulletin({\n groupCode,\n content,\n ...(picId !== undefined ? { picId } : {}),\n })) as { result?: unknown } | null | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`publishGroupBulletin 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 删群公告(delete_group_announcement;参数待探测校准)。 */\n async deleteGroupAnnouncement(groupCode: string, announcementId: string): Promise<void> {\n try {\n checkLooseResult(\n \"deleteGroupBulletin\",\n (await this.service.deleteGroupBulletin(groupCode, announcementId)) as\n | { result?: unknown }\n | null\n | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`deleteGroupBulletin 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 专属头衔(set_group_member_special_title;setIdentityTitleInfo 参数待校准)。 */\n async setMemberSpecialTitle(groupCode: string, uid: string, title: string): Promise<void> {\n try {\n checkLooseResult(\n \"setIdentityTitleInfo\",\n (await this.service.setIdentityTitleInfo(groupCode, uid, title)) as\n | { result?: unknown }\n | null\n | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`setIdentityTitleInfo 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 群头像(set_group_avatar;GroupService.setHeader 参数待校准)。 */\n async setGroupAvatar(groupCode: string, imagePath: string): Promise<void> {\n try {\n checkLooseResult(\n \"setHeader\",\n (await this.service.setHeader(groupCode, imagePath)) as\n | { result?: unknown }\n | null\n | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`setHeader(group) 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 公告图片上传(uploadGroupBulletinPic 返回 picId 待校准,失败原样上抛)。 */\n private async uploadBulletinPic(groupCode: string, imagePath: string): Promise<string> {\n const res = (await this.service.uploadGroupBulletinPic(groupCode, imagePath)) as\n | {\n result?: unknown;\n picId?: unknown;\n }\n | null\n | undefined;\n checkLooseResult(\"uploadGroupBulletinPic\", res);\n return typeof res?.picId === \"string\" ? res.picId : \"\";\n }\n\n /** @all 剩余次数(get_group_at_all_remain)。 */\n async getGroupRemainAtTimes(groupCode: string): Promise<{\n canAtAll: boolean;\n remainAtAllCountForUin: number;\n remainAtAllCountForGroup: number;\n }> {\n const raw = await this.service.getGroupRemainAtTimes(groupCode);\n if (raw.errCode !== 0) {\n throw kernelError(`getGroupRemainAtTimes 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return {\n canAtAll: raw.atInfo.canAtAll,\n remainAtAllCountForUin: raw.atInfo.remainAtAllCountForUin,\n remainAtAllCountForGroup: raw.atInfo.remainAtAllCountForGroup,\n };\n }\n}\n\n/** result 字段宽松取数(数字直接用,缺省 0 = 成功语义)。 */\nfunction toResultNumber(result: unknown): number {\n return typeof result === \"number\" ? result : 0;\n}\n\n/** 公告列表宽松提取(result 数组 / { bulletins } / { result: 数组 } 兼容,形状待校准)。 */\nfunction extractBulletins(result: unknown): GroupBulletin[] {\n const candidates = [result];\n if (result !== null && typeof result === \"object\") {\n const rec = result as Record<string, unknown>;\n for (const key of [\"bulletins\", \"announcementList\", \"result\", \"list\"] as const) {\n candidates.push(rec[key]);\n }\n }\n for (const candidate of candidates) {\n if (Array.isArray(candidate)) {\n return candidate.filter(\n (item): item is GroupBulletin => item !== null && typeof item === \"object\",\n );\n }\n }\n return [];\n}\n\n/** 未知异常 → 消息字符串。 */\nfunction errMsgString(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n/**\n * 从 getGroupDetailInfo 返回提取群详情(兼容三种形状):\n * - result 为对象 → 详情直接在其中(最常见)\n * - result 为 0/空 → 从 raw.groupInfo / detailInfo / info 提取\n * 提取不到返回 null(调用方抛 UNKNOWN)。导出供单测。\n */\nexport function extractGroupDetail(result: unknown, raw: unknown): GroupDetailInfo | null {\n if (result !== null && typeof result === \"object\") {\n return result as GroupDetailInfo;\n }\n const detail = raw as {\n groupInfo?: unknown;\n detailInfo?: unknown;\n info?: unknown;\n };\n const candidate = detail.groupInfo ?? detail.detailInfo ?? detail.info;\n if (candidate !== undefined && candidate !== null && typeof candidate === \"object\") {\n return candidate as GroupDetailInfo;\n }\n return null;\n}\n","/**\n * GroupNotifyApi:群通知/禁言列表语义化 API(ADR-009 统一错误语义,P2-13)\n *\n * get_group_system_msg / set_group_add_request / get_group_shut_list 共用。\n * getSingleScreenNotifies / getGroupShutUpMemberList 的列表可能走 listener 回调,\n * 直接调用返回形状待探测校准:兼容 result 数组 / 字段 / 直接数组。\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n GroupMember,\n GroupNotify,\n NodeIKernelGroupService,\n NodeIQQNTWrapperSession,\n NTGroupRequestOperateTypes,\n} from \"../types/index.js\";\nimport { unwrap } from \"./result.js\";\n\n/** 群通知 API:从 session 拿 group service,包装成语义化方法。 */\nexport class GroupNotifyApi {\n private readonly service: NodeIKernelGroupService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service = session.getGroupService() as unknown as NodeIKernelGroupService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getGroupService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n }\n\n /** 单屏通知列表(get_group_system_msg / set_group_add_request 共用)。 */\n async getSingleScreenNotifies(doubt: boolean, count: number): Promise<GroupNotify[]> {\n const raw = await this.service.getSingleScreenNotifies(doubt, \"\", count);\n return extractNotifyList(raw.result);\n }\n\n /** 禁言成员列表(get_group_shut_list)。 */\n async getGroupShutUpMemberList(groupCode: string): Promise<GroupMember[]> {\n const raw = await this.service.getGroupShutUpMemberList(groupCode);\n if (raw.result !== 0) {\n unwrap(\"getGroupShutUpMemberList\", raw.result, raw.errMsg);\n }\n return extractMemberList(raw.result);\n }\n\n /** 处理群请求(set_group_add_request 应答;approve=true 同意 / false 拒绝)。 */\n async handleGroupRequest(\n doubt: boolean,\n notify: GroupNotify,\n operateType: NTGroupRequestOperateTypes,\n reason: string,\n ): Promise<void> {\n const { group, seq, type } = notify;\n let groupCode = \"\";\n if (group !== undefined) {\n ({ groupCode } = group);\n }\n let postscript = reason;\n if (postscript === \"\") {\n postscript = \" \";\n }\n await this.service.operateSysNotify(doubt, {\n operateType,\n targetMsg: {\n seq,\n type,\n groupCode,\n postscript,\n },\n });\n }\n}\n\n/** 从 getSingleScreenNotifies 返回提取通知数组(兼容 result 数组 / notifies 字段)。 */\nexport function extractNotifyList(result: unknown): GroupNotify[] {\n if (Array.isArray(result)) {\n return result as GroupNotify[];\n }\n if (result !== null && typeof result === \"object\") {\n const obj = result as { notifies?: unknown; groupNotifies?: unknown };\n if (Array.isArray(obj.notifies)) {\n return obj.notifies as GroupNotify[];\n }\n if (Array.isArray(obj.groupNotifies)) {\n return obj.groupNotifies as GroupNotify[];\n }\n }\n return [];\n}\n\n/** 从 getGroupShutUpMemberList 返回提取成员数组(兼容 result 数组 / memberList 字段)。 */\nexport function extractMemberList(result: unknown): GroupMember[] {\n if (Array.isArray(result)) {\n return result as unknown as GroupMember[];\n }\n if (result !== null && typeof result === \"object\") {\n const obj = result as { memberList?: unknown; list?: unknown };\n if (Array.isArray(obj.memberList)) {\n return obj.memberList as unknown as GroupMember[];\n }\n if (Array.isArray(obj.list)) {\n return obj.list as unknown as GroupMember[];\n }\n }\n return [];\n}\n","/**\n * MsgApi:消息语义化 API(ADR-009 统一错误语义)\n *\n * 内部解包原生 `{ result, errMsg }`:成功返回纯业务值,失败抛 KernelError。\n * 协议层只维护 `KernelErrorCode → 协议错误码` 映射表,不解析错误逻辑。\n *\n * 方法面(P2-1):发送 / 撤回 / 拉历史 / 标记已读。group/friend 等后续 apis 同构。\n */\nimport { createHash } from \"node:crypto\";\nimport { readFile, stat } from \"node:fs/promises\";\nimport { basename, extname } from \"node:path\";\nimport type { MsgEventChannel } from \"../bridge/msg-bridge.js\";\nimport { isKernelError, kernelError } from \"../infra/index.js\";\nimport type {\n CanonicalElement,\n NodeIKernelMsgService,\n NodeIQQNTWrapperSession,\n NudgeParam,\n Peer,\n PttElement,\n RawElement,\n RawMessage,\n SendMessageElement,\n VideoElement,\n} from \"../types/index.js\";\nimport { ChatType, ElementType, toSendElements } from \"../types/index.js\";\nimport { checkLooseResult, unwrapResult } from \"./result.js\";\n\n/** 发送状态(onMsgInfoListUpdate 事件 msg.sendStatus)。 */\nconst SEND_STATUS = { FAILED: 0, SENDING: 1, SUCCESS: 2, SUCCESS_NO_SEQ: 3 } as const;\n\n/** 发送确认超时(毫秒)。 */\nconst SEND_CONFIRM_TIMEOUT_MS = 15_000;\n\n/**\n * PIC 元素发送契约值(NapCat 同款发送契约值,未逐字段校准)。\n * 下列固定值均为实测调通的 wrapper 发送契约(picType / picSubType /\n * picWidth / picHeight / original / thumbFileSize),只抽常量不改值——改值会破坏图片发送。\n */\nconst PIC_TYPE = 1000;\nconst PIC_SUB_TYPE = 0;\nconst PIC_WIDTH = 0;\nconst PIC_HEIGHT = 0;\nconst PIC_ORIGINAL = true;\nconst PIC_THUMB_FILE_SIZE = 0;\n\n/**\n * PTT 元素发送契约值(NapCat 同款发送契约值,未逐字段校准)。\n * silk v3 协议:下列固定值均为实测调通的 wrapper 发送契约(formatType / voiceType /\n * voiceChangeType / canConvert2Text / playState / autoConvertText / storeID /\n * otherBusinessInfo.aiVoiceType),只抽常量不改值——改值会破坏语音发送。\n */\nconst PTT_FORMAT_TYPE = 1;\nconst PTT_VOICE_TYPE = 1;\nconst PTT_VOICE_CHANGE_TYPE = 0;\nconst PTT_CAN_CONVERT_2_TEXT = true;\nconst PTT_PLAY_STATE = 1;\nconst PTT_AUTO_CONVERT_TEXT = 0;\nconst PTT_STORE_ID = 0;\nconst PTT_AI_VOICE_TYPE = 0;\n/** 假波形数组(占位波形):无真实振幅数据时发送的固定占位,silk v3 协议契约值。 */\nconst PTT_WAVE_AMPLITUDES = [0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17];\n\n/**\n * VIDEO 元素发送契约值(2026-10-17 收向实测字段 + 发送实测调通)。\n *\n * 依据:历史视频消息 `getLatestDbMsgs` 的 videoElement 实测快照(fileFormat /\n * busiType / subBusiType / storeID / original / videoFrom / sourceVideoCodecFormat\n * 均为实测出现的取值)。2026-10-17 18:12 自建宿主实测调通:`sendMsg` 成功\n * (sendStatus 2/3,群内可播放);关键坑是**缩略图目标路径必须用视频 md5**\n * (见 prepareVideoElement 注释),与字段取值无关。\n */\nconst VIDEO_FILE_FORMAT = 0;\nconst VIDEO_BUSI_TYPE = 0;\nconst VIDEO_SUB_BUSI_TYPE = 0;\nconst VIDEO_STORE_ID = 1;\nconst VIDEO_ORIGINAL = true;\nconst VIDEO_FROM = 0;\nconst VIDEO_SOURCE_CODEC_FORMAT = 0;\n/** 视频缩略图在 thumbPath Map 中的 key(收向实测 Map<0, path>)。 */\nconst VIDEO_THUMB_KEY = 0;\n/** getRichMediaFilePathForGuild 的 downloadType:1 = 原文件,2 = 缩略图。 */\nconst RICH_MEDIA_DOWNLOAD_TYPE_ORIGIN = 1;\nconst RICH_MEDIA_DOWNLOAD_TYPE_THUMB = 2;\n\n/** downloadRichMedia 契约值(2026-09-08 B1 实测:downloadType=2 可用 / thumbSize=0)。 */\nconst DOWNLOAD_TYPE_COMMON = 2;\nconst THUMB_SIZE_NONE = 0;\n/** 语音下载轮询参数(300ms × 34 ≈ 10s;返回 void 只能经重拉元素观察完成)。 */\nconst PTT_POLL_INTERVAL_MS = 300;\nconst PTT_POLL_TIMES = 34;\n\n/** sleep(下载轮询间隔)。 */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\n/** 未知异常 → 消息字符串。 */\nfunction errMsgString(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n/** 语音下载完成判定:有路径且(传输完成或末次轮询)才返回元素,否则 null。 */\nfunction pttDownloadResult(latest: PttElement | undefined, isLastPoll: boolean): PttElement | null {\n if (latest === undefined) {\n return null;\n }\n const hasPath = latest.filePath !== undefined && latest.filePath !== \"\";\n const done = latest.transferStatus === 2 || latest.transferStatus === 4;\n return hasPath && (done || isLastPoll) ? latest : null;\n}\n\n/** 收藏表情项 → url(字段名待校准,url/imageUrl/emojiUrl 逐个兜底)。 */\nfunction extractFavEmojiUrls(list: unknown[]): string[] {\n const out: string[] = [];\n for (const item of list) {\n if (item === null || typeof item !== \"object\") {\n continue;\n }\n const rec = item as Record<string, unknown>;\n const url = rec[\"url\"] ?? rec[\"imageUrl\"] ?? rec[\"emojiUrl\"];\n if (typeof url === \"string\" && url !== \"\") {\n out.push(url);\n }\n }\n return out;\n}\n\n/** 读文件 stat(富媒体发送预处理;文件不存在抛 INVALID_PARAM)。 */\nasync function statFile(path: string, label = \"图片\"): Promise<{ size: number }> {\n try {\n return await stat(path);\n } catch {\n throw kernelError(`${label}文件不存在: ${path}`, \"INVALID_PARAM\");\n }\n}\n\n/** 计算文件 md5(十六进制小写)。 */\nasync function hashFile(path: string, label = \"图片\"): Promise<string> {\n try {\n const buf = await readFile(path);\n return createHash(\"md5\").update(buf).digest(\"hex\");\n } catch {\n throw kernelError(`${label}文件读取失败: ${path}`, \"INVALID_PARAM\");\n }\n}\n\n/**\n * 清洗撤回消息 id(防御外部脏参数)。\n *\n * 调用方(IPC / 协议层 / koishi 适配器)可能传 null、非数组、空字符串等——\n * 直接透传会让裸 TypeError 逃逸,或把脏值一路送到 wrapper.node 才失败\n * (原生层只回「无错误详情」,排查困难)。规则:\n * - 非数组 → INVALID_PARAM\n * - 过滤空字符串 / 纯空白 / 非字符串\n * - 清洗后为空 → INVALID_PARAM\n */\nfunction sanitizeRecallMsgIds(msgIds: string[]): string[] {\n if (!Array.isArray(msgIds)) {\n throw kernelError(\"recallMessage 的 msgIds 必须是数组\", \"INVALID_PARAM\");\n }\n const ids = msgIds.filter((id) => typeof id === \"string\" && id.trim() !== \"\");\n if (ids.length === 0) {\n throw kernelError(\"recallMessage 需要至少一个非空 msgId\", \"INVALID_PARAM\");\n }\n return ids;\n}\n\n/** 消息 API:从 session 拿 msg service,包装成语义化方法。 */\nexport class MsgApi {\n private readonly service: NodeIKernelMsgService;\n /** 消息事件通道(sendMsg 后等 onMsgInfoListUpdate 确认发送结果)。 */\n private readonly channel: MsgEventChannel | null;\n /** NodeQQNTWrapperUtil(富媒体发送 copyFile 用;session 拿不到时可为 null)。 */\n private readonly util: { get(): unknown } | null;\n /** 上次生成 msgId 的时间(单调递增,2026-08-07 防同毫秒并发碰撞)。 */\n private lastMsgTime = 0;\n\n constructor(session: NodeIQQNTWrapperSession, channel?: MsgEventChannel, util?: unknown) {\n const service = session.getMsgService() as unknown as NodeIKernelMsgService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getMsgService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n this.channel = channel ?? null;\n // util 传入形态:构造器(NodeQQNTWrapperUtil,含 get())或实例,宽松兼容\n this.util = util !== null && util !== undefined ? (util as { get(): unknown }) : null;\n }\n\n /**\n * 生成 msgId 时间戳(单调递增)。\n * generateMsgUniqueId(chatType, time) 以 time 区分消息——同毫秒并发发送\n * (机器人群发/多会话同时回复)Date.now() 会碰撞,msgId 相同导致 wrapper\n * 拒绝或覆盖。严格单调递增保证进程内唯一。\n */\n private nextMsgTime(): string {\n const now = Date.now();\n if (now > this.lastMsgTime) {\n this.lastMsgTime = now;\n } else {\n this.lastMsgTime += 1;\n }\n return String(this.lastMsgTime);\n }\n\n /**\n * 发送消息:canonical 元素 → NT 发送元素 → sendMsg(NapCat 式)。\n * 返回 NT msgId(雪花 ID)。\n *\n * 2026-08-11 修复(NapCat 式,实测):\n * - sendMsg 第一参传 '0'(固定),msgId 塞 peer.guildId —— 传 msgId 作第一参\n * 时 wrapper 返回 result=5(失败),NapCat 同款调用返回 result=0。\n * - 发送结果以 onMsgInfoListUpdate 事件确认(sendStatus===2 成功):sendMsg\n * 返回值 result 可能为 5 但事件仍成功(异步确认),反之亦然。\n * - ⚠️ 必须先注册事件监听再调 sendMsg(事件可能在 sendMsg 返回前就触发)。\n */\n async sendMessage(target: Peer, elements: CanonicalElement[]): Promise<{ msgId: string }> {\n // NapCat 式富媒体预处理:PIC 元素补 md5/fileName/sourcePath 并放置文件\n // (getRichMediaFilePathForGuild → util.copyFile),否则发送器走未初始化\n // 的 FlashFileUploadService 导致 rich media transfer failed。\n const sendElements = await this.prepareSendElements(elements);\n const msgId = this.service.generateMsgUniqueId(target.chatType, this.nextMsgTime());\n // NapCat 同款:msgId 塞 guildId,第一参 '0'\n const sendPeer: Peer = { ...target, guildId: msgId };\n // 无事件通道(老用法):退化为看返回值\n if (this.channel === null) {\n const raw = await this.service.sendMsg(\"0\", sendPeer, sendElements, new Map());\n unwrapResult(\"sendMsg\", raw);\n return { msgId };\n }\n // 有事件通道:先注册确认监听(NapCat 式,事件可能早于 sendMsg 返回触发),\n // 再调 sendMsg。最终结果以事件 sendStatus 为准(raw.result 非 0 不判失败)。\n const confirm = this.confirmSend(msgId, target);\n // ⚠️ 预消费 confirm 的 rejection(2026-08-22 崩溃根因修复):若下方\n // service.sendMsg 抛错(wrapper 内部异常),confirm 确认监听已注册却无人\n // await,事件回调里 reject 会变成 unhandledRejection,Node 默认抛错退出\n // 直接拖垮整个子进程(实测:sendStatus=0 确认事件触发后进程崩溃、IPC\n // 通道关闭)。先挂 no-op 消费兜底,`await confirm` 仍能正常收到结果\n // (Promise 允许多个消费者),失败语义不变。\n confirm.catch(() => undefined);\n await this.service.sendMsg(\"0\", sendPeer, sendElements, new Map());\n await confirm;\n return { msgId };\n }\n\n /**\n * canonical 元素 → 发送元素,富媒体(PIC/PTT)做 NapCat 式预处理。\n * 2026-08-11 修复(实测):图片发送必须 elementType=2(PIC)+ 完整 picElement\n * (md5HexStr/fileSize/fileName/sourcePath),且文件须经 getRichMediaFilePathForGuild\n * 计算目标路径 + util.copyFile 放置——缺任一环节 sendMsg 返回 rich media transfer failed。\n * 2026-08-12(语音):PTT 同理——只给 filePath 触发 wrapper 内部\n * \"Cannot convert undefined or null to object\"(缺字段转换失败)。\n */\n private async prepareSendElements(elements: CanonicalElement[]): Promise<SendMessageElement[]> {\n const out: SendMessageElement[] = [];\n for (const el of elements) {\n switch (el.type) {\n case \"image\":\n out.push(await this.prepareImageElement(el.path));\n break;\n case \"voice\":\n out.push(await this.preparePttElement(el.path));\n break;\n case \"video\":\n out.push(await this.prepareVideoElement(el));\n break;\n case \"file\":\n out.push(await this.prepareFileElement(el));\n break;\n default:\n out.push(...toSendElements([el]));\n break;\n }\n }\n return out;\n }\n\n /** PIC 元素 NapCat 式预处理:md5 → 目标路径 → copyFile → 完整 picElement。 */\n private async prepareImageElement(path: string): Promise<SendMessageElement> {\n const file = await statFile(path);\n const md5 = await hashFile(path);\n const fileName = basename(path);\n const relPath = await this.placeMediaFile(path, ElementType.PIC, md5, fileName);\n return {\n elementType: ElementType.PIC,\n elementId: \"\",\n picElement: {\n md5HexStr: md5,\n fileSize: String(file.size),\n picWidth: PIC_WIDTH,\n picHeight: PIC_HEIGHT,\n fileName,\n sourcePath: relPath,\n original: PIC_ORIGINAL,\n picType: PIC_TYPE,\n picSubType: PIC_SUB_TYPE,\n fileUuid: \"\",\n fileSubId: \"\",\n thumbFileSize: PIC_THUMB_FILE_SIZE,\n summary: \"\",\n },\n };\n }\n\n /**\n * PTT 元素 NapCat 式预处理:md5 → 目标路径 → copyFile → 完整 pttElement。\n *\n * 2026-08-12(实测依据):只给 filePath 时 wrapper 内部抛\n * \"Cannot convert undefined or null to object\"(缺 md5HexStr/fileSize 等字段),\n * 且发送后进程崩溃重启(supervisor 自动拉起)。完整字段(NapCat 同款,\n * formatType/voiceType/canConvert2Text/waveAmplitudes 等)消除该错误。\n *\n * ⚠️ silk 格式:QQ 语音协议为 silk v3。非 silk → silk 的转码在协议层\n * (adapter,@napuketto/media 的 encodePcmToSilk,2026-08-12 接线)完成,\n * 此处接收的已是 silk 路径,仅做放置。kernel 不 import media(解耦红线)。\n */\n private async preparePttElement(path: string): Promise<SendMessageElement> {\n const file = await statFile(path);\n const md5 = await hashFile(path);\n const fileName = basename(path);\n const relPath = await this.placeMediaFile(path, ElementType.PTT, md5, fileName);\n // 时长估算(NapCat 同款规则:~3KB/s 语音码率,缺 ffprobe 时兜底)\n const duration = Math.max(1, Math.floor(file.size / 1024 / 3));\n return {\n elementType: ElementType.PTT,\n elementId: \"\",\n pttElement: {\n fileName,\n filePath: relPath,\n md5HexStr: md5,\n fileSize: String(file.size),\n duration,\n formatType: PTT_FORMAT_TYPE,\n voiceType: PTT_VOICE_TYPE,\n voiceChangeType: PTT_VOICE_CHANGE_TYPE,\n canConvert2Text: PTT_CAN_CONVERT_2_TEXT,\n waveAmplitudes: PTT_WAVE_AMPLITUDES,\n fileSubId: \"\",\n playState: PTT_PLAY_STATE,\n autoConvertText: PTT_AUTO_CONVERT_TEXT,\n storeID: PTT_STORE_ID,\n otherBusinessInfo: { aiVoiceType: PTT_AI_VOICE_TYPE },\n },\n };\n }\n\n /**\n * VIDEO 元素预处理:md5 → 目标路径 → copyFile → 完整 videoElement。\n *\n * 字段依据:2026-10-17 收向实测(历史视频消息 videoElement 全字段快照)+\n * 发送实测(2026-10-17 18:12 自建宿主发送成功,群内可播放):\n * 1. `thumbPath` 必须是真实 `Map<number, string>`(收向实测 `Map<0, path>`)——\n * 经 JSON/IPC 传参会退化成 `{}`,wrapper 转换失败;\n * 2. **缩略图目标路径用视频 md5**(`Video/<YYYY-MM>/Thumb/<videoMd5>_0.png`):\n * 真实消息实测 videoMd5=`3d27…`、thumbMd5=`46da…`,落盘文件名却是\n * `3d27…_0.png`;此前按 thumbMd5 放置 → native 传输按视频 md5 找不到缩略图\n * → `rich media transfer failed`(20+ 组字段变体都无效的真正原因);\n * 3. `thumbMd5` 是缩略图**内容**的 md5,与文件名无关;`fileTime` 为秒。\n */\n private async prepareVideoElement(\n el: Extract<CanonicalElement, { type: \"video\" }>,\n ): Promise<SendMessageElement> {\n const file = await statFile(el.path, \"视频\");\n const md5 = await hashFile(el.path, \"视频\");\n const fileName = basename(el.path);\n const relPath = await this.placeMediaFile(\n el.path,\n ElementType.VIDEO,\n md5,\n fileName,\n RICH_MEDIA_DOWNLOAD_TYPE_ORIGIN,\n );\n const videoElement: VideoElement = {\n fileName,\n filePath: relPath,\n fileSize: String(file.size),\n videoMd5: md5,\n fileTime: el.durationSec ?? 0,\n fileFormat: VIDEO_FILE_FORMAT,\n busiType: VIDEO_BUSI_TYPE,\n subBusiType: VIDEO_SUB_BUSI_TYPE,\n fileUuid: \"\",\n fileSubId: \"\",\n storeID: VIDEO_STORE_ID,\n original: VIDEO_ORIGINAL,\n videoFrom: VIDEO_FROM,\n sourceVideoCodecFormat: VIDEO_SOURCE_CODEC_FORMAT,\n };\n if (el.thumbPath !== undefined && el.thumbPath !== \"\") {\n const thumbSize = await statFile(el.thumbPath, \"视频缩略图\");\n const thumbMd5 = await hashFile(el.thumbPath, \"视频缩略图\");\n // ⚠️ 目标路径必须用**视频 md5**(不是缩略图内容 md5):2026-10-17 实测\n // 真实视频消息的缩略图文件名为 `Thumb/<videoMd5>_0.png`,而\n // `thumbMd5` 只是缩略图内容的 md5(例:videoMd5=3d27…、thumbMd5=46da…,\n // 实际落盘 `3d27…_0.png`)。此前用 thumbMd5 算路径 → native 传输按视频\n // md5 找不到缩略图 → `rich media transfer failed`。\n const thumbRelPath = await this.placeMediaFile(\n el.thumbPath,\n ElementType.VIDEO,\n md5,\n `${md5}${extname(el.thumbPath)}`,\n RICH_MEDIA_DOWNLOAD_TYPE_THUMB,\n );\n videoElement.thumbMd5 = thumbMd5;\n videoElement.thumbSize = thumbSize.size;\n videoElement.thumbWidth = el.width ?? 0;\n videoElement.thumbHeight = el.height ?? 0;\n videoElement.thumbPath = new Map([[VIDEO_THUMB_KEY, thumbRelPath]]);\n }\n return { elementType: ElementType.VIDEO, elementId: \"\", videoElement };\n }\n\n /**\n * FILE 元素预处理(P2-2 预留口子,upload_private/group_file 落地):\n * pic/ptt/video 同款 NapCat 式链路——md5 → getRichMediaFilePathForGuild(FILE)\n * → copyFile → 完整 fileElement。链路模式已验证(三类媒体同构);fileElement\n * 字段值(fileSize 字符串形态、fileUuid 空串等)待探测校准。\n */\n private async prepareFileElement(\n el: Extract<CanonicalElement, { type: \"file\" }>,\n ): Promise<SendMessageElement> {\n const file = await statFile(el.path, \"文件\");\n const md5 = await hashFile(el.path, \"文件\");\n const fileName = el.name !== undefined && el.name !== \"\" ? el.name : basename(el.path);\n const relPath = await this.placeMediaFile(el.path, ElementType.FILE, md5, fileName);\n return {\n elementType: ElementType.FILE,\n elementId: \"\",\n fileElement: {\n fileName,\n filePath: relPath,\n fileSize: String(file.size),\n fileUuid: \"\",\n },\n };\n }\n\n /**\n * 富媒体文件放置(PIC/PTT/VIDEO 共用):md5 + 文件名 → QQ 内部目标路径\n * (getRichMediaFilePathForGuild)→ util.copyFile 放置 → 返回目标路径。\n *\n * `downloadType`:1 = 原文件(Ori),2 = 缩略图(Thumb,视频缩略图必须)。\n */\n private async placeMediaFile(\n path: string,\n elementType: ElementType,\n md5: string,\n fileName: string,\n downloadType = RICH_MEDIA_DOWNLOAD_TYPE_ORIGIN,\n ): Promise<string> {\n const service = this.service;\n const util = this.util;\n // getRichMediaFilePathForGuild:QQ 内部目标路径(纯文件名,相对数据根)。\n // 固定入参 elementSubType / thumbSize / downloadType 为 NapCat 同款发送契约值\n // (未逐字段校准),发送方向固定,改值会破坏富媒体路径计算。\n const relPath = service.getRichMediaFilePathForGuild({\n md5HexStr: md5,\n fileName,\n elementType,\n elementSubType: 0, // 元素子类型(发送固定 0)\n thumbSize: 0, // 缩略图尺寸(发送固定 0,不预生成缩略图)\n needCreate: true,\n downloadType, // 下载类型:1 = 原文件,2 = 缩略图(视频缩略图必须)\n file_uuid: \"\",\n });\n if (util !== null) {\n const instance = typeof util.get === \"function\" ? util.get() : util;\n const copy = (instance as Record<string, unknown>)[\"copyFile\"];\n if (typeof copy === \"function\") {\n await (copy as (a: string, b: string) => Promise<unknown>).call(\n instance,\n path,\n relPath,\n );\n }\n }\n return relPath;\n }\n\n /** 注册 onMsgInfoListUpdate 确认监听(返回 Promise,resolve 时发送成功)。 */\n private confirmSend(msgId: string, target: Peer): Promise<void> {\n const channel = this.channel;\n if (channel === null) {\n return Promise.resolve();\n }\n return new Promise((resolve, reject) => {\n const off = channel.on(\"Msg/onMsgInfoListUpdate\", (list) => {\n const mine = list.find((m) => m.guildId === msgId);\n if (mine === undefined) {\n return; // 不是本次消息\n }\n if (\n mine.sendStatus === SEND_STATUS.SUCCESS ||\n mine.sendStatus === SEND_STATUS.SUCCESS_NO_SEQ\n ) {\n off();\n resolve();\n } else if (mine.sendStatus === SEND_STATUS.FAILED) {\n off();\n reject(\n kernelError(`sendMsg 失败: sendStatus=${mine.sendStatus}`, \"SEND_FAILED\"),\n );\n }\n // SENDING:继续等\n });\n // 超时兜底\n setTimeout(() => {\n off();\n reject(\n kernelError(\n `sendMsg 等待确认超时(msgId=${msgId}, target=${JSON.stringify(target)})`,\n \"SEND_FAILED\",\n ),\n );\n }, SEND_CONFIRM_TIMEOUT_MS);\n });\n }\n\n /** 撤回消息(群聊管理员 / 私聊 2 分钟内)。 */\n async recallMessage(target: Peer, msgIds: string[]): Promise<void> {\n const raw = await this.service.recallMsg(target, sanitizeRecallMsgIds(msgIds));\n unwrapResult(\"recallMsg\", raw);\n }\n\n /** 拉取历史消息(msgId 为空从最新拉;count 条,时间倒序)。 */\n async fetchMessages(\n target: Peer,\n opts: { count: number; msgId?: string },\n ): Promise<RawMessage[]> {\n const raw = await this.service.getMsgs(target, opts.msgId ?? \"\", opts.count, false);\n unwrapResult(\"getMsgs\", raw);\n return raw.msgList ?? [];\n }\n\n /** 按 msgId 批量拉取消息(get_msg / 精华消息 / ptt 转文字共用)。 */\n async fetchMsgsByMsgId(target: Peer, ids: string[]): Promise<RawMessage[]> {\n if (ids.length === 0) {\n return [];\n }\n const raw = await this.service.getMsgsByMsgId(target, ids);\n unwrapResult(\"getMsgsByMsgId\", raw);\n return raw.msgList ?? [];\n }\n\n /** 消息表情表态(set_msg_emoji_like;like=true 点赞,false 取消)。 */\n async setMsgEmojiLike(\n target: Peer,\n opts: { msgSeq: string; emojiId: string; emojiType: string; like: boolean },\n ): Promise<void> {\n const raw = await this.service.setMsgEmojiLikes(\n target,\n opts.msgSeq,\n opts.emojiId,\n opts.emojiType,\n opts.like,\n );\n unwrapResult(\"setMsgEmojiLikes\", raw);\n }\n\n /**\n * 语音转文字(fetch_ptt_text)。\n * 流程:按 msgId 拉消息 → 找 PTT 元素 → translatePtt2Text(异步转写)\n * → 再拉一次消息读 pttElement.text。\n */\n async fetchPttText(msgId: string, target: Peer): Promise<string> {\n const msgs = await this.fetchMsgsByMsgId(target, [msgId]);\n const ptt = findPttElement(msgs);\n if (ptt === null) {\n throw kernelError(\"消息中不包含语音\", \"NOT_FOUND\");\n }\n const raw = await this.service.translatePtt2Text(msgId, target, ptt);\n unwrapResult(\"translatePtt2Text\", raw);\n // 转写异步完成:再拉一次拿 text\n const after = await this.fetchMsgsByMsgId(target, [msgId]);\n const text = findPttElement(after)?.pttElement?.text;\n if (text === undefined || text === \"\") {\n throw kernelError(\"获取语音转文字结果失败\", \"UNKNOWN\");\n }\n return text;\n }\n\n /** 标记会话已读。 */\n async markRead(target: Peer): Promise<void> {\n const raw = await this.service.setMsgRead(target);\n unwrapResult(\"setMsgRead\", raw);\n }\n\n /**\n * 语音主动下载(get_record 本地未命中兜底,2026-09-08 B1)。\n * downloadRichMedia 返回 void(实测),完成信号 = 重拉消息元素:\n * 轮询(300ms × 34,约 10s)直到 filePath 非空且 transferStatus 进入\n * 已下载态(2/4);超时但 filePath 已有值则尽力返回(磁盘命中判断留给\n * 调用方),完全无路径抛 NOT_FOUND。\n */\n async downloadPtt(msgId: string, target: Peer): Promise<PttElement> {\n const found = await this.pttElementOf(msgId, target);\n if (found === null) {\n throw kernelError(\"消息中不包含语音\", \"NOT_FOUND\");\n }\n await this.service.downloadRichMedia({\n msgId,\n elemId: found.elemId,\n chatType: target.chatType,\n downloadType: DOWNLOAD_TYPE_COMMON,\n thumbSize: THUMB_SIZE_NONE,\n });\n for (let i = 0; i < PTT_POLL_TIMES; i++) {\n await sleep(PTT_POLL_INTERVAL_MS);\n const again = await this.fetchMsgsByMsgId(target, [msgId]);\n const result = pttDownloadResult(\n findPttElement(again)?.pttElement,\n i === PTT_POLL_TIMES - 1,\n );\n if (result !== null) {\n return result;\n }\n }\n throw kernelError(\"语音下载未完成(超时)\", \"TIMEOUT\");\n }\n\n /** 取消息内语音元素(无则 null;附 elemId 供主动下载)。 */\n private async pttElementOf(\n msgId: string,\n target: Peer,\n ): Promise<{ elemId: string; ptt: PttElement } | null> {\n const msgs = await this.fetchMsgsByMsgId(target, [msgId]);\n const el = findPttElement(msgs);\n const ptt = el?.pttElement;\n if (el === null || ptt === undefined) {\n return null;\n }\n return { elemId: el.elementId ?? \"\", ptt };\n }\n\n /** 发送输入状态(set_input_status;eventType=1 输入中,0 停止)。 */\n async setInputStatus(target: Peer, eventType: number): Promise<void> {\n await this.service.sendShowInputStatusReq(target.chatType, eventType, target.peerUid);\n }\n\n /** 戳一戳发送(send_friend/group_nudge;参数形状待探测校准,失败抛 UNKNOWN)。 */\n async sendNudge(target: Peer, opts: { receiverUid: string; callerUid: string }): Promise<void> {\n const param: NudgeParam = {\n receiverUid: opts.receiverUid,\n callerUid: opts.callerUid,\n ...(target.chatType === ChatType.GROUP ? { confId: target.peerUid } : {}),\n };\n try {\n checkLooseResult(\n \"sendNudge\",\n (await this.service.sendNudge(param)) as { result?: unknown } | null | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`sendNudge 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 会话置顶(set_peer_pin;setContactLocalTop 参数待探测校准)。 */\n async setContactTop(target: Peer, isTop: boolean): Promise<void> {\n try {\n checkLooseResult(\n \"setContactLocalTop\",\n (await this.service.setContactLocalTop(target, isTop)) as\n | { result?: unknown }\n | null\n | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`setContactLocalTop 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 收藏表情列表(get_custom_face_url_list;queryFavEmojiByDesc 形状待校准)。 */\n async queryFavEmojis(): Promise<string[]> {\n let raw: { result?: unknown; errMsg?: unknown; emojiInfoList?: unknown };\n try {\n raw = (await this.service.queryFavEmojiByDesc(\"\")) as typeof raw;\n } catch (e) {\n throw kernelError(`queryFavEmojiByDesc 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n checkLooseResult(\"queryFavEmojiByDesc\", raw);\n const list = raw.emojiInfoList;\n if (!Array.isArray(list)) {\n return [];\n }\n return extractFavEmojiUrls(list);\n }\n\n /**\n * 发送合并转发(send_group/private_forward_msg)。\n * buildMultiForwardMsg 组装 MULTI_FORWARD 元素 → 直接作 sendMsg 元素发送。\n */\n async sendForwardMessage(\n target: Peer,\n sourcePeer: Peer,\n srcMsgIds: string[],\n ): Promise<{ msgId: string }> {\n if (srcMsgIds.length === 0) {\n throw kernelError(\"sendForwardMessage 需要至少一条源消息\", \"INVALID_PARAM\");\n }\n const built = await this.service.buildMultiForwardMsg({\n srcMsgIds,\n srcContact: sourcePeer,\n });\n unwrapResult(\"buildMultiForwardMsg\", built);\n const elements = built.rspInfo?.elements;\n if (elements === undefined || elements.length === 0) {\n throw kernelError(\"合并转发组装失败:无元素\", \"UNKNOWN\");\n }\n const msgId = this.service.generateMsgUniqueId(target.chatType, String(Date.now()));\n const raw = await this.service.sendMsg(msgId, target, elements, new Map());\n unwrapResult(\"sendMsg\", raw);\n return { msgId };\n }\n\n /** 获取合并转发内容(get_forward_msg;resId 取自 multiForwardMsgElement)。 */\n async fetchForwardMessage(peer: Peer, msgId: string): Promise<RawMessage[]> {\n const msgs = await this.fetchMsgsByMsgId(peer, [msgId]);\n const forward = findForwardElement(msgs);\n if (forward === null || forward.resId === \"\") {\n throw kernelError(\"消息不包含合并转发内容\", \"NOT_FOUND\");\n }\n const raw = await this.service.getMultiMsg(peer, msgId, forward.resId);\n unwrapResult(\"getMultiMsg\", raw);\n return raw.msgList ?? [];\n }\n\n /** 单条转发(forward_group/friend_single_msg;srcMsgIds 源、dstPeer 目标)。 */\n async forwardSingleMessage(\n sourcePeer: Peer,\n srcMsgIds: string[],\n dstPeer: Peer,\n ): Promise<void> {\n if (srcMsgIds.length === 0) {\n throw kernelError(\"forwardSingleMessage 需要至少一条源消息\", \"INVALID_PARAM\");\n }\n const raw = await this.service.forwardMsg(srcMsgIds, sourcePeer, [dstPeer], undefined);\n unwrapResult(\"forwardMsg\", raw);\n }\n\n /** 设置在线状态(set_online_status;customStatus 为自定义状态)。 */\n async setOnlineStatus(opts: {\n status: number;\n extStatus: number;\n batteryStatus: number;\n customStatus?: { faceId: string; wording: string; faceType: string };\n }): Promise<void> {\n const raw = await this.service.setStatus(opts);\n unwrapResult(\"setStatus\", raw);\n }\n}\n\n/** 在消息列表中找含 pttElement 的元素(找不到返回 null)。 */\nfunction findPttElement(msgs: RawMessage[]): RawElement | null {\n const [first] = msgs;\n if (first === undefined) {\n return null;\n }\n return first.elements.find((el) => el.pttElement !== undefined) ?? null;\n}\n\n/** 在消息列表中找含 multiForwardMsgElement 的元素(找不到返回 null)。 */\nfunction findForwardElement(\n msgs: RawMessage[],\n): { resId: string; fileName: string; xmlContent: string } | null {\n const [first] = msgs;\n if (first === undefined) {\n return null;\n }\n return (\n first.elements.find((el) => el.multiForwardMsgElement !== undefined)\n ?.multiForwardMsgElement ?? null\n );\n}\n","/**\n * 陌生人信息扁平化纯函数(get_stranger_info 的数据映射部分)\n *\n * 2026-08-08 从 profile.ts 拆出:NAPI 会话依赖(ProfileApi)与纯映射分离,\n * 纯函数可独立单测(stranger-info.test.ts 基线锁定宽松取值行为)。\n *\n * 宽松取值:优先 info(uid 完整详情),字段缺失回退 byUin(uin 初查详情),\n * 两份都缺给默认值——字段缺失是 QQ 详情接口的常态,不能抛错。\n */\nimport type { UserDetailInfoByUin } from \"../types/index.js\";\n\n/** 陌生人信息(get_stranger_info 返回)。 */\nexport interface StrangerInfo {\n user_id: number;\n uid: string;\n nickname: string;\n age: number;\n qid: string;\n qq_level: number;\n sex: \"male\" | \"female\" | \"unknown\";\n long_nick: string;\n reg_time: number;\n is_vip: boolean;\n is_years_vip: boolean;\n vip_level: number;\n remark: string;\n status: number;\n login_days: number;\n}\n\n/** 扁平化后的详情视图(双来源字段级回退的产物,仅含 StrangerInfo 需要的字段)。 */\ninterface MergedDetail {\n coreInfo?: { nick?: string; remark?: string } | undefined;\n baseInfo?: { age?: number; qid?: string; sex?: number; longNick?: string } | undefined;\n vasInfo?: { svipFlag?: boolean; yearVipFlag?: boolean; vipLevel?: number } | undefined;\n commonExt?: { qqLevel?: number; regTime?: number } | undefined;\n status?: number | undefined;\n}\n\n/** 宽松取值:undefined 时给默认值(null 也归默认)。 */\nfunction orDefault<T>(value: T | undefined, fallback: T): T {\n return value ?? fallback;\n}\n\n/** 合并两份详情:优先 info(uid 完整详情),字段缺失回退 byUin(uin 初查详情)。 */\nfunction mergeDetail(info: UserDetailInfoByUin, byUin: UserDetailInfoByUin): MergedDetail {\n const infoSimple = info.detail?.simpleInfo;\n const byUinSimple = byUin.detail?.simpleInfo;\n return {\n coreInfo: pickField(infoSimple, byUinSimple, (s) => s?.coreInfo),\n baseInfo: pickField(infoSimple, byUinSimple, (s) => s?.baseInfo),\n vasInfo: pickField(infoSimple, byUinSimple, (s) => s?.vasInfo),\n commonExt: pickField(info.detail, byUin.detail, (d) => d?.commonExt),\n status: pickField(infoSimple, byUinSimple, (s) => s?.status?.status),\n };\n}\n\n/** 字段级回退:primary 的字段取不到回退 fallback 的同名字段(宽松取值)。 */\nfunction pickField<T, R>(\n primary: T | undefined,\n fallback: T | undefined,\n get: (src: T) => R | undefined,\n): R | undefined {\n const fromPrimary = primary !== undefined ? get(primary) : undefined;\n if (fromPrimary !== undefined) {\n return fromPrimary;\n }\n return fallback !== undefined ? get(fallback) : undefined;\n}\n\n/** 扁平化两份详情 → StrangerInfo(宽松取值,字段缺失给默认)。 */\nexport function flattenStrangerInfo(\n uin: string,\n uid: string,\n byUin: UserDetailInfoByUin,\n info: UserDetailInfoByUin,\n): StrangerInfo {\n const d = mergeDetail(info, byUin);\n return {\n user_id: Number(uin),\n uid,\n nickname: orDefault(d.coreInfo?.nick, \"\"),\n age: orDefault(d.baseInfo?.age, 0),\n qid: orDefault(d.baseInfo?.qid, \"\"),\n qq_level: orDefault(d.commonExt?.qqLevel, 0),\n sex: mapSex(d.baseInfo?.sex),\n long_nick: orDefault(d.baseInfo?.longNick, \"\"),\n reg_time: orDefault(d.commonExt?.regTime, 0),\n is_vip: orDefault(d.vasInfo?.svipFlag, false),\n is_years_vip: orDefault(d.vasInfo?.yearVipFlag, false),\n vip_level: orDefault(d.vasInfo?.vipLevel, 0),\n remark: orDefault(d.coreInfo?.remark, \"\"),\n status: orDefault(d.status, 0),\n login_days: 0,\n };\n}\n\n/** 性别数值 → OB11 字符串(1=男 2=女 其余 unknown)。 */\nfunction mapSex(raw: number | undefined): \"male\" | \"female\" | \"unknown\" {\n if (raw === 1) {\n return \"male\";\n }\n if (raw === 2) {\n return \"female\";\n }\n return \"unknown\";\n}\n","/**\n * ProfileApi:资料语义化 API(ADR-009 统一错误语义,P2-14/P2-15)\n *\n * - setLongNick:set_self_longnick\n * - setNickName:set_qq_profile\n * - setHeader:set_qq_avatar\n * - getStrangerInfo:get_stranger_info(uin → uid → 详情扁平化)\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type { NodeIKernelProfileService, NodeIQQNTWrapperSession } from \"../types/index.js\";\nimport { checkLooseResult } from \"./result.js\";\nimport type { StrangerInfo } from \"./stranger-info.js\";\nimport { flattenStrangerInfo } from \"./stranger-info.js\";\n\n/** 资料 API:从 session 拿 profile service,包装成语义化方法。 */\nexport class ProfileApi {\n private readonly service: NodeIKernelProfileService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service = session.getProfileService() as unknown as NodeIKernelProfileService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getProfileService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n }\n\n /** 设置个性签名(set_self_longnick)。 */\n async setLongNick(longNick: string): Promise<void> {\n const res = (await this.service.setLongNick(longNick)) as\n | { result?: unknown; errMsg?: unknown }\n | undefined;\n checkLooseResult(\"setLongNick\", res);\n }\n\n /** 设置昵称(set_qq_profile)。 */\n async setNickName(nickName: string): Promise<void> {\n const res = (await this.service.setNickName(nickName)) as\n | { result?: unknown; errMsg?: unknown }\n | undefined;\n checkLooseResult(\"setNickName\", res);\n }\n\n /** 设置头像(set_qq_avatar;filePath 为本地路径)。 */\n async setHeader(filePath: string): Promise<void> {\n const raw = await this.service.setHeader(filePath);\n if (raw.result !== 0) {\n throw kernelError(`setHeader 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n }\n\n /** 获取陌生人信息(get_stranger_info;uin 为用户 QQ 号)。 */\n async getStrangerInfo(uin: string): Promise<StrangerInfo> {\n const byUin = await this.service.getUserDetailInfoByUin(uin);\n const detailUid = byUin.detail?.uid;\n let uid = uin;\n if (detailUid !== undefined && detailUid !== \"\") {\n uid = detailUid;\n }\n const info = await this.service.getUserDetailInfo(uid);\n return flattenStrangerInfo(uin, uid, byUin, info);\n }\n}\n","/**\n * ProfileLikeApi:资料点赞语义化 API(ADR-009 统一错误语义,P2-14)\n *\n * sendLike:send_like(sourceId=71 赞来源,doLikeCount 次数)。\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type { NodeIKernelProfileLikeService, NodeIQQNTWrapperSession } from \"../types/index.js\";\n\n/** 点赞 API:从 session 拿 profile like service,包装成语义化方法。 */\nexport class ProfileLikeApi {\n private readonly service: NodeIKernelProfileLikeService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service =\n session.getProfileLikeService() as unknown as NodeIKernelProfileLikeService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getProfileLikeService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n }\n\n /** 点赞(send_like;uid 为目标 uid,count 次数)。 */\n async sendLike(uid: string, count = 1): Promise<void> {\n const raw = await this.service.setBuddyProfileLike({\n friendUid: uid,\n sourceId: 71,\n doLikeCount: count,\n doLikeTollCount: 0,\n });\n if (raw.result !== 0) {\n throw kernelError(`点赞失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n }\n}\n","/**\n * RecentContactApi:会话列表语义化 API(P2 批次二,get_peer_pins 数据源)\n *\n * 2026-10-31 实机探测确认 getRecentContactService 有 getRecentContactList /\n * setContactListTop 等方法;返回形状待探测校准(宽松提取)。\n * 置顶数据源:isTop 过滤(ChatType 区分 friend/group 场景)。\n */\nimport { isKernelError, kernelError } from \"../infra/index.js\";\nimport type {\n NodeIKernelRecentContactService,\n NodeIQQNTWrapperSession,\n RecentContact,\n} from \"../types/index.js\";\nimport { ChatType } from \"../types/index.js\";\nimport { checkLooseResult } from \"./result.js\";\n\n/** 会话列表拉取条数(覆盖置顶集合足够大;参数值待探测校准)。 */\nconst RECENT_CONTACT_FETCH_COUNT = 200;\n\n/** 置顶会话(get_peer_pins 数据项):peerUid 按 chatType 归 friend/group。 */\nexport interface PinnedContact {\n /** 会话 id(好友 = uid,群 = 群号)。 */\n peerUid: string;\n /** 场景:friend(C2C)/ group(GROUP);temp 不产出。 */\n scene: \"friend\" | \"group\";\n}\n\n/** 会话列表 API:从 session 拿 recent contact service,包装成语义化方法。 */\nexport class RecentContactApi {\n private readonly service: NodeIKernelRecentContactService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service =\n session.getRecentContactService() as unknown as NodeIKernelRecentContactService | null;\n if (service === null || service === undefined) {\n throw kernelError(\n \"getRecentContactService() 返回空(session 未 init)\",\n \"INVALID_STATE\",\n );\n }\n this.service = service;\n }\n\n /** 置顶会话列表(get_peer_pins;getRecentContactList 返回形状待探测校准)。 */\n async getPinnedContacts(): Promise<PinnedContact[]> {\n let raw: { result?: unknown; errMsg?: unknown };\n try {\n raw = (await this.service.getRecentContactList({\n count: RECENT_CONTACT_FETCH_COUNT,\n })) as typeof raw;\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`getRecentContactList 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n checkLooseResult(\"getRecentContactList\", raw);\n return extractPinnedContacts(raw);\n }\n}\n\n/** 从返回形状提取置顶会话(result 数组 / 顶层 contacts / 嵌套容器兼容,待校准)。 */\nfunction extractPinnedContacts(raw: { result?: unknown }): PinnedContact[] {\n const candidates: unknown[] = [raw.result, raw];\n for (const container of [raw.result, raw]) {\n if (container === null || typeof container !== \"object\") {\n continue;\n }\n const rec = container as Record<string, unknown>;\n for (const key of [\"contacts\", \"data\", \"result\", \"list\"] as const) {\n candidates.push(rec[key]);\n }\n }\n for (const candidate of candidates) {\n if (!Array.isArray(candidate)) {\n continue;\n }\n return candidate.flatMap((item) => pinnedOf(item));\n }\n return [];\n}\n\n/** 单条会话 → 置顶项(isTop 为真且场景可判定才产出)。 */\nfunction pinnedOf(item: unknown): PinnedContact[] {\n if (item === null || typeof item !== \"object\") {\n return [];\n }\n const contact = item as RecentContact;\n if (contact.isTop !== true || typeof contact.peerUid !== \"string\") {\n return [];\n }\n if (contact.chatType === ChatType.GROUP) {\n return [{ peerUid: contact.peerUid, scene: \"group\" }];\n }\n if (contact.chatType === ChatType.C2C) {\n return [{ peerUid: contact.peerUid, scene: \"friend\" }];\n }\n return [];\n}\n\n/** 未知异常 → 消息字符串。 */\nfunction errMsgString(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n","/**\n * RichMediaApi:富媒体语义化 API(ADR-009 统一错误语义,P2-14)\n *\n * - translateWords:translate_en2zh\n * - 群文件:列表 / 空间 / 建夹 / 删除文件 / 删除夹 / 重命名 / 移动 / 转存\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n GetFileListParam,\n GroupFileItemInfo,\n GroupFolderInfo,\n NodeIKernelRichMediaService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\nimport { checkLooseResult, unwrap } from \"./result.js\";\n\n/** 群文件业务类型(deleteGroupFile/moveGroupFile/renameGroupFile 用,说明书参考)。 */\nconst GROUP_FILE_BIZ_TYPE = 102;\n/** 排序方式(按时间)。 */\nconst SORT_TYPE_TIME = 1;\n/** 排序方向(倒序)。 */\nconst SORT_ORDER_DESC = 2;\n/** 起始下标(根目录)。 */\nconst START_INDEX_ROOT = 0;\n/** 不显示在线文档文件夹。 */\nconst HIDE_ONLINEDOC_FOLDER = 0;\n/** 默认文件数量。 */\nconst DEFAULT_FILE_COUNT = 50;\n/** 空间查询文件数量(单条即可)。 */\nconst SPACE_QUERY_FILE_COUNT = 1;\n\n/** 群文件列表条目(fileInfo/folderInfo 取其一)。 */\nexport interface GroupFileListItem {\n fileInfo?: GroupFileItemInfo;\n folderInfo?: GroupFolderInfo;\n}\n\n/** 群文件系统信息。 */\nexport interface GroupFileSystemInfo {\n fileCount: number;\n limitCount: number;\n usedSpace: number;\n totalSpace: number;\n}\n\n/** 富媒体 API:从 session 拿 rich media service,包装成语义化方法。 */\nexport class RichMediaApi {\n private readonly service: NodeIKernelRichMediaService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service =\n session.getRichMediaService() as unknown as NodeIKernelRichMediaService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getRichMediaService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n }\n\n /** 英文单词翻译(translate_en2zh)。 */\n async translateWords(words: string[]): Promise<string[]> {\n const raw = await this.service.translateEnWordToZn(words);\n unwrap(\"translateEnWordToZn\", raw.result, raw.errMsg);\n return raw.words ?? [];\n }\n\n /** 群文件列表(get_group_root_files;parent 缺省根目录)。 */\n async getGroupFileList(\n groupCode: string,\n parentFolderId = \"\",\n fileCount = DEFAULT_FILE_COUNT,\n ): Promise<GroupFileListItem[]> {\n const params: GetFileListParam = {\n sortType: SORT_TYPE_TIME,\n fileCount,\n startIndex: START_INDEX_ROOT,\n sortOrder: SORT_ORDER_DESC,\n showOnlinedocFolder: HIDE_ONLINEDOC_FOLDER,\n };\n const raw = await this.service.getGroupFileList(groupCode, params);\n // 宽松解包(2026-10-31 修正):旧逻辑 result 数字非 0 抛错 + extractFileList(0)\n // 得空——真实返回形状(result 即列表 / 顶层列表 / 嵌套 items)下要么误抛要么恒空。\n checkLooseResult(\"getGroupFileList\", raw);\n const list = extractFileList(raw.result);\n const items = list.length > 0 ? list : extractFileList(raw);\n if (parentFolderId === \"\") {\n return items;\n }\n return items.filter(\n (item) =>\n (item.fileInfo !== undefined && item.fileInfo.parent === parentFolderId) ||\n (item.folderInfo !== undefined && item.folderInfo.folderUid === parentFolderId),\n );\n }\n\n /** 群文件系统信息(get_group_file_system_info;getGroupFileList 的 groupSpaceResult)。 */\n async getGroupFileSystemInfo(groupCode: string): Promise<GroupFileSystemInfo> {\n const params: GetFileListParam = {\n sortType: SORT_TYPE_TIME,\n fileCount: SPACE_QUERY_FILE_COUNT,\n startIndex: START_INDEX_ROOT,\n sortOrder: SORT_ORDER_DESC,\n showOnlinedocFolder: HIDE_ONLINEDOC_FOLDER,\n };\n const raw = await this.service.getGroupFileList(groupCode, params);\n if (raw.result !== 0) {\n unwrap(\"getGroupFileList\", raw.result, raw.errMsg);\n }\n const space = raw.groupSpaceResult;\n if (space === undefined) {\n throw kernelError(\"getGroupFileList 无 groupSpaceResult\", \"UNKNOWN\");\n }\n return {\n fileCount: countGroupFiles(raw.result),\n limitCount: 0,\n usedSpace: space.usedSpace,\n totalSpace: space.totalSpace,\n };\n }\n\n /** 创建群文件夹(create_group_file_folder)。 */\n async createGroupFolder(groupCode: string, folderName: string): Promise<string> {\n const raw = await this.service.createGroupFolder(groupCode, folderName);\n if (raw.result !== 0) {\n unwrap(\"createGroupFolder\", raw.result, raw.errMsg);\n }\n // 返回形状待探测:兼容 folderId / resultWithGroupItem\n const { result } = raw as { result?: unknown };\n if (typeof result === \"string\") {\n return result;\n }\n return \"\";\n }\n\n /** 删除群文件(delete_group_file)。 */\n async deleteGroupFile(groupCode: string, fileIds: string[]): Promise<void> {\n const raw = await this.service.deleteGroupFile(groupCode, [GROUP_FILE_BIZ_TYPE], fileIds);\n if (raw.result !== 0) {\n unwrap(\"deleteGroupFile\", raw.result, raw.errMsg);\n }\n }\n\n /** 删除群文件夹(delete_group_folder)。 */\n async deleteGroupFolder(groupCode: string, folderId: string): Promise<void> {\n const raw = await this.service.deleteGroupFolder(groupCode, folderId);\n if (raw.result !== 0) {\n unwrap(\"deleteGroupFolder\", raw.result, raw.errMsg);\n }\n }\n\n /** 重命名群文件(rename_group_file;参数语义待探测校准)。 */\n async renameGroupFile(groupCode: string, fileId: string, newName: string): Promise<void> {\n await this.service.renameGroupFile(groupCode, GROUP_FILE_BIZ_TYPE, fileId, newName, \"\");\n }\n\n /** 移动群文件(move_group_file;targetFolder 空为根目录)。 */\n async moveGroupFile(\n groupCode: string,\n fileIds: string[],\n targetFolderId: string,\n ): Promise<void> {\n const raw = await this.service.moveGroupFile(\n groupCode,\n [GROUP_FILE_BIZ_TYPE],\n fileIds,\n \"\",\n targetFolderId,\n );\n if (raw.result !== 0) {\n unwrap(\"moveGroupFile\", raw.result, raw.errMsg);\n }\n }\n\n /** 重命名群文件夹(rename_group_folder;形态镜像 renameGroupFile,待校准)。 */\n async renameGroupFolder(groupCode: string, folderId: string, newName: string): Promise<void> {\n await this.service.renameGroupFolder(groupCode, GROUP_FILE_BIZ_TYPE, folderId, newName, \"\");\n }\n\n /**\n * 群文件下载链接(get_group_file_download_url):getGroupFileList 根列表按\n * fileId 查找取 url 字段(字段待实机确认)。已知差异:仅根目录可达——\n * 子目录文件需 parentFolderId 逐层定位,暂不支持。\n */\n async getGroupFileDownloadUrl(groupCode: string, fileId: string): Promise<string> {\n const items = await this.getGroupFileList(groupCode);\n for (const item of items) {\n const info = item.fileInfo;\n if (info !== undefined && info.fileId === fileId) {\n if (typeof info.url === \"string\" && info.url !== \"\") {\n return info.url;\n }\n throw kernelError(\n `群文件 ${fileId} 无下载链接(url 字段缺失,待实机校准)`,\n \"NOT_FOUND\",\n );\n }\n }\n throw kernelError(`群文件 ${fileId} 不在根目录或不存在(仅支持根目录查找)`, \"NOT_FOUND\");\n }\n\n /** 转存群文件(trans_group_file)。 */\n async transGroupFile(groupCode: string, fileId: string): Promise<void> {\n const raw = await this.service.transGroupFile(groupCode, fileId);\n if (raw.result !== 0) {\n unwrap(\"transGroupFile\", raw.result, raw.errMsg);\n }\n }\n}\n\n/** 从 getGroupFileList 返回提取文件列表(兼容数组 / { items } / { fileList })。导出供单测。 */\nexport function extractFileList(result: unknown): GroupFileListItem[] {\n if (Array.isArray(result)) {\n return result as GroupFileListItem[];\n }\n if (result === null || typeof result !== \"object\") {\n return [];\n }\n const obj = result as { items?: unknown; fileList?: unknown; list?: unknown };\n for (const key of [\"items\", \"fileList\", \"list\"] as const) {\n const value = obj[key];\n if (Array.isArray(value)) {\n return value as GroupFileListItem[];\n }\n }\n return [];\n}\n\n/** 统计文件数量(兼容数组 / { items })。 */\nfunction countGroupFiles(result: unknown): number {\n if (Array.isArray(result)) {\n return (result as GroupFileListItem[]).filter((item) => item.fileInfo !== undefined).length;\n }\n return 0;\n}\n","/**\n * TicketApi:票据语义化 API(ADR-009 统一错误语义,P2-13)\n *\n * - getClientKey:TicketService.forceFetchClientKey 解包(get_clientkey)\n * - getCookies:clientKey → ssl.ptlogin2.qq.com/jump 跳转 → 解析 set-cookie 头\n * (fetch 会吞掉 Set-Cookie 头,故用 node:https 手动请求)\n */\nimport { get as httpsGet } from \"node:https\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n ForceFetchClientKeyRetType,\n NodeIKernelTicketService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\n\n/** 跳转请求超时(毫秒)。 */\nconst JUMP_TIMEOUT_MS = 10_000;\n\n/** 票据 API:从 session 拿 ticket service,包装成语义化方法。 */\nexport class TicketApi {\n private readonly service: NodeIKernelTicketService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service = session.getTicketService() as unknown as NodeIKernelTicketService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getTicketService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n }\n\n /** 获取 clientKey(get_clientkey)。 */\n async getClientKey(): Promise<ForceFetchClientKeyRetType> {\n const raw = await this.service.forceFetchClientKey(\"\");\n if (raw.result !== 0 || raw.clientKey === \"\") {\n throw kernelError(`forceFetchClientKey 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return raw;\n }\n\n /** 获取指定域名的 cookies(get_cookies;uin 为机器人 QQ 号)。 */\n async getCookies(domain: string, uin: string): Promise<Record<string, string>> {\n const { clientKey } = await this.getClientKey();\n const u1 = `https://${domain}/${uin}/infocenter`;\n const jumpUrl =\n \"https://ssl.ptlogin2.qq.com/jump?ptlang=1033\" +\n `&clientuin=${encodeURIComponent(uin)}` +\n `&clientkey=${encodeURIComponent(clientKey)}` +\n `&u1=${encodeURIComponent(u1)}` +\n \"&keyindex=19\";\n return fetchJumpCookies(jumpUrl);\n }\n\n /** 计算 bkn(get_csrf_token 与 web 接口共用;skey → bkn 哈希)。 */\n static getBkn(skey: string): string {\n let hash = 5381;\n for (let i = 0; i < skey.length; i += 1) {\n const code = skey.charCodeAt(i);\n hash = hash + (hash << 5) + code;\n }\n return (hash & 0x7f_ff_ff_ff).toString();\n }\n}\n\n/** 请求 jump 地址并解析 set-cookie → dict(fetch 吞 Set-Cookie,故用 node:https)。 */\nfunction fetchJumpCookies(url: string): Promise<Record<string, string>> {\n return new Promise((resolve, reject) => {\n const req = httpsGet(\n url,\n {\n headers: {\n \"user-agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\",\n },\n },\n (res) => {\n const cookies = parseSetCookieHeaders(res.headers[\"set-cookie\"]);\n res.resume();\n res.on(\"end\", () => resolve(cookies));\n },\n );\n req.setTimeout(JUMP_TIMEOUT_MS, () => {\n req.destroy();\n reject(new Error(\"获取 cookies 超时\"));\n });\n req.on(\"error\", (err) => reject(err));\n });\n}\n\n/** 解析 set-cookie 头数组 → { name: value } dict。 */\nfunction parseSetCookieHeaders(raw: string[] | undefined): Record<string, string> {\n const cookies: Record<string, string> = {};\n if (raw === undefined) {\n return cookies;\n }\n for (const line of raw) {\n const [pair] = line.split(\";\");\n if (pair !== undefined) {\n const eq = pair.indexOf(\"=\");\n if (eq > 0) {\n const name = pair.slice(0, eq).trim();\n const value = pair.slice(eq + 1);\n if (name !== \"\") {\n cookies[name] = value;\n }\n }\n }\n }\n return cookies;\n}\n","/**\n * WebApi:群空间 web 接口(P2-15)\n *\n * qun.qq.com 群空间接口需 Cookie(p_skey/skey)+ bkn(skey 哈希)。\n * 复用 TicketApi.getCookies('qun.qq.com') 取 Cookie,TicketApi.getBkn 算 bkn。\n * 零 NapCat 代码:接口契约(URL/参数/返回形状)是 qun.qq.com 外部系统的事实,\n * 自研描述。\n */\nimport { kernelError } from \"../infra/index.js\";\nimport { TicketApi } from \"./ticket.js\";\n\n/** 群空间域名(Cookie 归属)。 */\nconst QUN_DOMAIN = \"qun.qq.com\";\n\n/** 精华消息页大小(分页拉取)。 */\nconst ESSENCE_PAGE_LIMIT = 50;\n\n/** 精华消息最大分页数。 */\nconst ESSENCE_MAX_PAGES = 20;\n\n/** 精华消息项(digest_list 返回 data.msg_list 成员,说明书参考)。 */\nexport interface EssenceMsgItem {\n msg_seq: number;\n msg_random: number;\n sender_uin: string;\n sender_nick: string;\n add_digest_uin: string;\n add_digest_nick: string;\n add_digest_time: number;\n msg_content: Array<{\n msg_type?: number;\n text?: string;\n image_url?: string;\n }>;\n}\n\n/** 群荣誉项(honorlist 返回列表项,说明书参考)。 */\nexport interface HonorListItem {\n uin: string;\n name: string;\n avatar: string;\n desc: string;\n}\n\n/** 群荣誉信息(getGroupHonorInfo 返回)。 */\nexport interface GroupHonorWebInfo {\n group_id: number;\n current_talkative: HonorListItem | null;\n talkative_list: HonorListItem[];\n performer_list: HonorListItem[];\n legend_list: HonorListItem[];\n emotion_list: HonorListItem[];\n strong_newbie_list: HonorListItem[];\n}\n\n/** 群荣誉类型(1=龙王 2=群聊之火 3=群聊炽热 6=快乐源泉)。 */\nexport const WebHonorType = {\n ALL: 0,\n TALKATIVE: 1,\n PERFORMER: 2,\n LEGEND: 3,\n EMOTION: 6,\n} as const;\nexport type WebHonorType = (typeof WebHonorType)[keyof typeof WebHonorType];\n\n/** 群空间 web API(Cookie 经 TicketApi 注入)。 */\nexport class WebApi {\n private readonly getCookies: (domain: string) => Promise<Record<string, string>>;\n\n constructor(opts: { getCookies: (domain: string) => Promise<Record<string, string>> }) {\n this.getCookies = opts.getCookies;\n }\n\n /** 拉取单页精华消息(失败/retcode 非 0 返回 null)。 */\n private async fetchEssencePage(\n groupCode: string,\n cookie: Record<string, string>,\n bkn: string,\n page: number,\n ): Promise<{ items: EssenceMsgItem[]; isEnd: boolean } | null> {\n const url =\n \"https://qun.qq.com/cgi-bin/group_digest/digest_list?\" +\n new URLSearchParams({\n bkn,\n page_start: String(page * ESSENCE_PAGE_LIMIT),\n page_limit: String(ESSENCE_PAGE_LIMIT),\n group_code: groupCode,\n }).toString();\n const ret = await fetchQunJson(url, cookie);\n if (ret === null || ret.retcode !== 0) {\n return null;\n }\n const list = ret.data?.msg_list;\n return {\n items: Array.isArray(list) ? list.filter((item) => item !== null) : [],\n isEnd: ret.data?.is_end === true,\n };\n }\n\n /** 获取群精华消息列表(get_essence_msg_list;分页拉取至 is_end)。 */\n async getEssenceMsgList(groupCode: string): Promise<EssenceMsgItem[]> {\n const cookie = await this.getCookies(QUN_DOMAIN);\n const bkn = TicketApi.getBkn(cookie[\"skey\"] ?? \"\");\n const out: EssenceMsgItem[] = [];\n for (let page = 0; page < ESSENCE_MAX_PAGES; page += 1) {\n const result = await this.fetchEssencePage(groupCode, cookie, bkn, page);\n if (result === null) {\n break;\n }\n out.push(...result.items);\n if (result.isEnd) {\n break;\n }\n }\n return out;\n }\n\n /** 获取群荣誉信息(get_group_honor_info;type 0=全部)。 */\n async getGroupHonorInfo(groupCode: string, type: WebHonorType): Promise<GroupHonorWebInfo> {\n const cookie = await this.getCookies(QUN_DOMAIN);\n const result: GroupHonorWebInfo = {\n group_id: Number(groupCode),\n current_talkative: null,\n talkative_list: [],\n performer_list: [],\n legend_list: [],\n emotion_list: [],\n strong_newbie_list: [],\n };\n await this.fillHonorLists(result, groupCode, cookie, type);\n return result;\n }\n\n /** 按 type 填充荣誉列表(ALL 拉全部,单类型只拉对应类)。 */\n private async fillHonorLists(\n result: GroupHonorWebInfo,\n groupCode: string,\n cookie: Record<string, string>,\n type: WebHonorType,\n ): Promise<void> {\n for (const target of honorTargets(type)) {\n const list = await this.fetchHonorList(groupCode, cookie, target.kind);\n result[target.listKey] = list;\n if (target.kind === WebHonorType.TALKATIVE) {\n const [first] = list;\n if (first !== undefined) {\n result.current_talkative = first;\n }\n }\n }\n }\n\n /** 拉取单类荣誉列表(honorlist 接口,正则提取 __INITIAL_STATE__)。 */\n private async fetchHonorList(\n groupCode: string,\n cookie: Record<string, string>,\n type: number,\n ): Promise<HonorListItem[]> {\n const url =\n \"https://qun.qq.com/interactive/honorlist?\" +\n new URLSearchParams({ gc: groupCode, type: String(type) }).toString();\n const text = await fetchQunText(url, cookie);\n if (text === null) {\n return [];\n }\n return parseHonorList(text, type);\n }\n}\n\n/** honorlist 页面 __INITIAL_STATE__ 提取。 */\nconst INITIAL_STATE_RE = /window\\.__INITIAL_STATE__=(.*?);/;\n\n/** 从 honorlist 页面提取 __INITIAL_STATE__ → 荣誉列表(解析失败返回空)。 */\nexport function parseHonorList(text: string, type: number): HonorListItem[] {\n const match = INITIAL_STATE_RE.exec(text);\n if (match === null || match[1] === undefined) {\n return [];\n }\n let state: { talkativeList?: unknown; actorList?: unknown };\n try {\n state = JSON.parse(match[1]);\n } catch {\n return [];\n }\n const raw = type === WebHonorType.TALKATIVE ? state.talkativeList : state.actorList;\n if (!Array.isArray(raw)) {\n return [];\n }\n const out: HonorListItem[] = [];\n for (const item of raw) {\n if (item !== null && typeof item === \"object\") {\n const obj = item as {\n uin?: unknown;\n name?: unknown;\n avatar?: unknown;\n desc?: unknown;\n };\n out.push({\n uin: String(obj.uin ?? \"\"),\n name: String(obj.name ?? \"\"),\n avatar: String(obj.avatar ?? \"\"),\n desc: String(obj.desc ?? \"\"),\n });\n }\n }\n return out;\n}\n\n/** 请求 qun.qq.com JSON 接口(带 Cookie;失败返回 null)。 */\nasync function fetchQunJson(\n url: string,\n cookie: Record<string, string>,\n): Promise<{ retcode: number; data?: { msg_list?: EssenceMsgItem[]; is_end?: boolean } } | null> {\n try {\n const res = await fetch(url, { headers: { Cookie: cookieToString(cookie) } });\n if (!res.ok) {\n return null;\n }\n const json = (await res.json()) as {\n retcode?: number;\n data?: { msg_list?: unknown; is_end?: unknown };\n };\n return {\n retcode: json.retcode ?? -1,\n data: json.data as { msg_list?: EssenceMsgItem[]; is_end?: boolean },\n };\n } catch (err) {\n throw kernelError(`qun.qq.com 请求失败: ${String(err)}`, \"UNKNOWN\");\n }\n}\n\n/** 请求 qun.qq.com 文本接口(带 Cookie;失败返回 null)。 */\nasync function fetchQunText(url: string, cookie: Record<string, string>): Promise<string | null> {\n try {\n const res = await fetch(url, { headers: { Cookie: cookieToString(cookie) } });\n if (!res.ok) {\n return null;\n }\n return await res.text();\n } catch (err) {\n throw kernelError(`qun.qq.com 请求失败: ${String(err)}`, \"UNKNOWN\");\n }\n}\n\n/** Cookie dict → 头字符串。 */\nfunction cookieToString(cookie: Record<string, string>): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(cookie)) {\n parts.push(`${key}=${value}`);\n }\n return parts.join(\"; \");\n}\n\n/** 荣誉类型 → 目标列表(ALL 拉全部,单类型只拉对应类)。 */\nfunction honorTargets(type: WebHonorType): Array<{\n kind: WebHonorType;\n listKey: \"talkative_list\" | \"performer_list\" | \"legend_list\" | \"emotion_list\";\n}> {\n const all = [\n { kind: WebHonorType.TALKATIVE, listKey: \"talkative_list\" },\n { kind: WebHonorType.PERFORMER, listKey: \"performer_list\" },\n { kind: WebHonorType.LEGEND, listKey: \"legend_list\" },\n { kind: WebHonorType.EMOTION, listKey: \"emotion_list\" },\n ] as const;\n if (type === WebHonorType.ALL) {\n return [...all];\n }\n return all.filter((target) => target.kind === type);\n}\n","/**\n * FriendBridge:好友事件桥(2026-09-08)\n *\n * 职责:把 buddy service 的原生回调(onBuddyReqChange / onBuddyListChange 等)\n * 推入类型化事件通道 `NTEventChannel<BuddyListener, \"Buddy\">`。与 MsgBridge/\n * GroupBridge 同构(每个 Service 只注册一次原生监听,ADR-003)。\n *\n * 方法名证据:wrapper.node(9.9.33-52230)二进制字符串提取;参数形状\n * 待真实事件校准(透传 unknown,订阅方防御性收窄)。\n *\n * 用法:\n * const channel = new NTEventChannel<BuddyListener, \"Buddy\">(\"Buddy\");\n * const bridge = new FriendBridge(session, channel);\n * bridge.register(); // addKernelBuddyListener(普通 JS 对象,NAPI 反射)\n * bridge.unregister(); // 停止时清理\n */\n\nimport type { NTEventChannel } from \"../event-channel.js\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n BuddyListener,\n NodeIKernelBuddyService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\n\n/** 好友事件通道的固定类型(事件名前缀 \"Buddy\")。 */\nexport type BuddyEventChannel = NTEventChannel<BuddyListener, \"Buddy\">;\n\n/**\n * 好友事件桥:注册原生监听 → 回调 emit 到 channel。\n * 无全局单例(ADR-015 推论)——每进程每 session 实例化一份。\n */\nexport class FriendBridge {\n private readonly service: NodeIKernelBuddyService;\n private readonly channel: BuddyEventChannel;\n private listenerId: number | null = null;\n\n constructor(session: NodeIQQNTWrapperSession, channel: BuddyEventChannel) {\n const service = session.getBuddyService() as unknown as NodeIKernelBuddyService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getBuddyService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n this.channel = channel;\n }\n\n /** 注册原生监听(幂等)。 */\n register(): void {\n if (this.listenerId !== null) {\n return;\n }\n // listener 为普通 JS 对象(NAPI 反射读取方法回调)\n const listener: BuddyListener = {\n onBuddyReqChange: (arg) => this.channel.emit(\"Buddy/onBuddyReqChange\", arg),\n onBuddyListChange: (arg) => this.channel.emit(\"Buddy/onBuddyListChange\", arg),\n onBuddyListChangedV2: (arg) => this.channel.emit(\"Buddy/onBuddyListChangedV2\", arg),\n onBuddyDeleted: (arg) => this.channel.emit(\"Buddy/onBuddyDeleted\", arg),\n };\n this.listenerId = this.service.addKernelBuddyListener(listener);\n }\n\n /** 注销原生监听(幂等)。 */\n unregister(): void {\n if (this.listenerId === null) {\n return;\n }\n this.service.removeKernelBuddyListener(this.listenerId);\n this.listenerId = null;\n }\n}\n","/**\n * GroupBridge:群事件桥(P2-17,2026-08-05)\n *\n * 职责:把 group service 的原生回调(onGroupListUpdate / onMemberListChange 等)\n * 推入类型化事件通道 `NTEventChannel<GroupListener, \"Group\">`。每个 Service 只\n * 注册一次原生监听(ADR-003),缓存维护(GroupCache)与协议层都订阅 channel。\n *\n * 用法:\n * const channel = new NTEventChannel<GroupListener, \"Group\">(\"Group\");\n * const bridge = new GroupBridge(session, channel);\n * bridge.register(); // addKernelGroupListener(普通 JS 对象,NAPI 反射)\n * const cache = new GroupCache({ channel, groupApi });\n * cache.register(); // 订阅 channel 主动维护\n * bridge.unregister(); // 停止时清理\n */\n\nimport type { NTEventChannel } from \"../event-channel.js\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n GroupListener,\n NodeIKernelGroupService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\n\n/** 群事件通道的固定类型(事件名前缀 \"Group\")。 */\nexport type GroupEventChannel = NTEventChannel<GroupListener, \"Group\">;\n\n/**\n * 群事件桥:注册原生监听 → 回调 emit 到 channel。\n * 无全局单例(ADR-015 推论)——每进程每 session 实例化一份。\n */\nexport class GroupBridge {\n private readonly service: NodeIKernelGroupService;\n private readonly channel: GroupEventChannel;\n private listenerId: number | null = null;\n\n constructor(session: NodeIQQNTWrapperSession, channel: GroupEventChannel) {\n const service = session.getGroupService() as unknown as NodeIKernelGroupService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getGroupService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n this.channel = channel;\n }\n\n /** 注册原生监听(幂等)。 */\n register(): void {\n if (this.listenerId !== null) {\n return;\n }\n // listener 为普通 JS 对象(NAPI 反射读取方法回调)\n const listener: GroupListener = {\n onGroupListInited: (listEmpty) =>\n this.channel.emit(\"Group/onGroupListInited\", listEmpty),\n onGroupListUpdate: (updateType, groupList) =>\n this.channel.emit(\"Group/onGroupListUpdate\", updateType, groupList),\n onGroupDetailInfoChange: (detailInfo) =>\n this.channel.emit(\"Group/onGroupDetailInfoChange\", detailInfo),\n onMemberListChange: (arg) => this.channel.emit(\"Group/onMemberListChange\", arg),\n onMemberInfoChange: (groupCode, dataSource, members) =>\n this.channel.emit(\"Group/onMemberInfoChange\", groupCode, dataSource, members),\n onGroupNotifiesUpdated: (doubt, notifies) =>\n this.channel.emit(\"Group/onGroupNotifiesUpdated\", doubt, notifies),\n onGroupSingleScreenNotifies: (doubt, seq, notifies) =>\n this.channel.emit(\"Group/onGroupSingleScreenNotifies\", doubt, seq, notifies),\n onShutUpMemberListChanged: (groupCode, members) =>\n this.channel.emit(\"Group/onShutUpMemberListChanged\", groupCode, members),\n // 2026-09-10 接线:群精华列表变化(方法名证据见 listeners/group.ts 注释)\n onGroupEssenceListChange: (arg) =>\n this.channel.emit(\"Group/onGroupEssenceListChange\", arg),\n };\n this.listenerId = this.service.addKernelGroupListener(listener);\n }\n\n /** 注销原生监听(幂等)。 */\n unregister(): void {\n if (this.listenerId === null) {\n return;\n }\n this.service.removeKernelGroupListener(this.listenerId);\n this.listenerId = null;\n }\n}\n","/**\n * MsgBridge:消息事件桥(P2-2,2026-08-05)\n *\n * 职责:把 msg service 的原生回调(onRecvMsg 等)推入类型化事件通道\n * `NTEventChannel<MsgListener, \"Msg\">`。每个 Service 只注册一次原生监听\n * (ADR-003),缓存维护与协议翻译都订阅 channel。\n *\n * 用法:\n * const channel = new NTEventChannel<MsgListener, \"Msg\">(\"Msg\");\n * const bridge = new MsgBridge(session, channel);\n * bridge.register(); // addKernelMsgListener(普通 JS 对象,NAPI 反射)\n * // 协议层订阅:channel.on(\"Msg/onRecvMsg\", handler)\n * bridge.unregister(); // 停止时清理\n */\n\nimport type { NTEventChannel } from \"../event-channel.js\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n MsgListener,\n NodeIKernelMsgService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\n\n/** 消息事件通道的固定类型(事件名前缀 \"Msg\")。 */\nexport type MsgEventChannel = NTEventChannel<MsgListener, \"Msg\">;\n\n/**\n * 消息事件桥:注册原生监听 → 回调 emit 到 channel。\n * 无全局单例(ADR-015 推论)——每进程每 session 实例化一份。\n */\nexport class MsgBridge {\n private readonly service: NodeIKernelMsgService;\n private readonly channel: MsgEventChannel;\n private listenerId: number | null = null;\n\n constructor(session: NodeIQQNTWrapperSession, channel: MsgEventChannel) {\n const service = session.getMsgService() as unknown as NodeIKernelMsgService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getMsgService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n this.channel = channel;\n }\n\n /** 注册原生监听(幂等)。 */\n register(): void {\n if (this.listenerId !== null) {\n return;\n }\n // listener 为普通 JS 对象(NAPI 反射读取方法回调)\n // onRecvMsg 透传原生回调参数(运行时实证为消息数组,2026-08-07)。\n const listener: MsgListener = {\n onRecvMsg: (msgs) => this.channel.emit(\"Msg/onRecvMsg\", msgs),\n onRecvMsgReadReport: (reports) => this.channel.emit(\"Msg/onRecvMsgReadReport\", reports),\n onRecvMsgReceipt: (receipts) => this.channel.emit(\"Msg/onRecvMsgReceipt\", receipts),\n // 2026-08-11 补齐:发送状态更新(sendMsg 结果以此为准,NapCat 同款)\n onMsgInfoListUpdate: (msgs) => this.channel.emit(\"Msg/onMsgInfoListUpdate\", msgs),\n // 2026-09-10 接线:离线文件 / sys msg(方法名证据见 listeners/msg.ts 头注释)\n onRecvOfflineFileMsg: (arg) => this.channel.emit(\"Msg/onRecvOfflineFileMsg\", arg),\n onRecvOnlineFileMsg: (arg) => this.channel.emit(\"Msg/onRecvOnlineFileMsg\", arg),\n onRecvSysMsg: (arg) => this.channel.emit(\"Msg/onRecvSysMsg\", arg),\n };\n this.listenerId = this.service.addKernelMsgListener(listener);\n }\n\n /** 注销原生监听(幂等)。 */\n unregister(): void {\n if (this.listenerId === null) {\n return;\n }\n this.service.removeKernelMsgListener(this.listenerId);\n this.listenerId = null;\n }\n}\n","/**\n * 类型化事件通道(ADR-003)\n *\n * - 事件名约定 `${Service}/${method}`(如 \"Msg/onRecvMsg\"),签名从 Listener 接口编译期推导\n * - 事件名写错 → 编译期报错(类型推导)\n * - `on` 返回 unsubscribe;`waitFor` 注册临时监听 → filter → 超时自动清理(替代魔法字符串方案)\n * - error 兜底:监听器抛异常不打断派发,统一经 `onError` 通知\n *\n * 组合实现而非 extends EventEmitter:EventEmitter.on 返回 `this`,与设计要求的\n * 返回 unsubscribe 冲突;组合更干净,且内核无全局单例(ADR-015 推论)。\n *\n * 类型推导说明:不直接 `ListenerEvents<L, Name>[E]` 索引(泛型下会扩宽到\n * string|number|symbol 索引,索引出 unknown),而是用 Extract 按事件名从\n * `{ name, fn }` 联合精确提取 handler / 参数类型,保证实例化后签名精确。\n */\nimport { EventEmitter } from \"node:events\";\nimport { KernelError } from \"./infra/index.js\";\n\n/** waitFor 默认超时(毫秒)。 */\nconst DEFAULT_WAIT_TIMEOUT_MS = 30_000;\n\n/** Listener 接口形状(宽约束;精确签名由实例化时的具体接口保证)。 */\ntype ListenerShape = Record<string, unknown>;\n\n/** 事件名:`${Service}/${method}`。 */\ntype EventName<L extends ListenerShape, Name extends string> = `${Name}/${keyof L & string}`;\n\n/** 按事件名从 Listener 精确提取 handler 类型(条件类型 infer,泛型安全)。 */\ntype EventHandler<\n L extends ListenerShape,\n Name extends string,\n E extends EventName<L, Name>,\n> = Extract<\n {\n [K in keyof L]: L[K] extends (...args: infer A) => infer R\n ? { name: `${Name}/${K & string}`; fn: (...args: A) => R }\n : never;\n }[keyof L],\n { name: E }\n>[\"fn\"];\n\n/** 按事件名从 Listener 精确提取参数类型。 */\ntype EventArgs<\n L extends ListenerShape,\n Name extends string,\n E extends EventName<L, Name>,\n> = Extract<\n {\n [K in keyof L]: L[K] extends (...args: infer A) => unknown\n ? { name: `${Name}/${K & string}`; args: A }\n : never;\n }[keyof L],\n { name: E }\n>[\"args\"];\n\n/** 从 Listener 接口推导事件映射(文档/调试用):`{ \"Msg/onRecvMsg\": fn }`。 */\nexport type ListenerEvents<L extends ListenerShape, Name extends string> = {\n [K in keyof L as `${Name}/${K & string}`]: L[K];\n};\n\n/**\n * 类型化事件通道。\n *\n * @typeParam L - Listener 接口(探测产物,如 MsgListener)\n * @typeParam Name - Service 名(事件名前缀,如 \"Msg\")\n */\nexport class NTEventChannel<L extends ListenerShape, Name extends string> {\n readonly serviceName: Name;\n\n private readonly emitter = new EventEmitter();\n private readonly errorHandlers = new Set<(err: unknown) => void>();\n private readonly anyHandlers = new Set<(event: string, ...args: unknown[]) => void>();\n\n constructor(serviceName: Name) {\n this.serviceName = serviceName;\n // 单个 Service 多订阅者(缓存维护 + 协议翻译),关闭默认 10 个监听器上限\n this.emitter.setMaxListeners(0);\n }\n\n /**\n * 订阅事件:返回 unsubscribe(只注销该 handler,不影响其他订阅者)。\n * 每个 Service 只注册一次原生监听,缓存维护与协议翻译都订阅这里。\n */\n on<E extends EventName<L, Name>>(event: E, handler: EventHandler<L, Name, E>): () => void {\n this.emitter.addListener(event as string, handler as (...args: unknown[]) => void);\n return () => {\n this.emitter.removeListener(event as string, handler as (...args: unknown[]) => void);\n };\n }\n\n /**\n * 请求-响应桥:注册临时监听 → filter 命中 → resolve;超时自动清理并 reject。\n * 替代魔法字符串事件名方案。\n */\n waitFor<E extends EventName<L, Name>>(\n event: E,\n opts: {\n filter?: (...args: EventArgs<L, Name, E>) => boolean;\n timeout?: number;\n } = {},\n ): Promise<EventArgs<L, Name, E>> {\n const timeoutMs = opts.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;\n return new Promise((resolve, reject) => {\n const handler = (...args: EventArgs<L, Name, E>) => {\n if (opts.filter && !opts.filter(...args)) {\n return; // 不匹配:保持监听,等待下一条\n }\n cleanup();\n resolve(args);\n };\n const timer = setTimeout(() => {\n cleanup();\n reject(new KernelError(`等待事件超时: ${event as string}`, \"TIMEOUT\"));\n }, timeoutMs);\n const cleanup = (): void => {\n clearTimeout(timer);\n this.emitter.removeListener(\n event as string,\n handler as (...args: unknown[]) => void,\n );\n };\n this.emitter.addListener(event as string, handler as (...args: unknown[]) => void);\n });\n }\n\n /**\n * 类型化派发:wrapper-loader 的原生回调经此推入通道。\n * 2026-08-07 修复:原生 EventEmitter.emit 中一个 listener 抛异常会中断后续\n * listener 派发(Node 语义)——改为快照数组逐个 try/catch,保证「单个订阅者\n * 异常不打断派发」(协议翻译层异常不饿死缓存维护等其他订阅者)。\n */\n /**\n * 订阅所有事件(IPC 子进程模式整通道转发用:事件名 `\"${service}/${method}\"`\n * 经 handler 首参透出,args 原样透传)。返回退订函数。\n */\n onAny(handler: (event: string, ...args: unknown[]) => void): () => void {\n this.anyHandlers.add(handler);\n return () => {\n this.anyHandlers.delete(handler);\n };\n }\n\n emit<E extends EventName<L, Name>>(event: E, ...args: EventArgs<L, Name, E>): void {\n // 全事件订阅(IPC 转发):单个订阅者异常不打断派发,统一走 error 兜底\n for (const anyHandler of this.anyHandlers) {\n try {\n anyHandler(event as string, ...args);\n } catch (err) {\n this.notifyError(err);\n }\n }\n const listeners = this.emitter.listeners(event as string);\n for (const listener of listeners) {\n try {\n (listener as (...a: unknown[]) => void)(...args);\n } catch (err) {\n // error 兜底:单个订阅者异常不打断派发,统一通知 onError\n this.notifyError(err);\n }\n }\n }\n\n /** 通知所有错误处理器(单个异常不打断其他处理器)。 */\n private notifyError(err: unknown): void {\n for (const handler of this.errorHandlers) {\n handler(err);\n }\n }\n\n /** 订阅错误通知(监听器抛出的异常 / 其他内部错误)。 */\n onError(handler: (err: unknown) => void): () => void {\n this.errorHandlers.add(handler);\n return () => {\n this.errorHandlers.delete(handler);\n };\n }\n}\n","/**\n * BuddyCache:好友缓存(B2,2026-09-08;仿 GroupCache 的 ADR-008 模式)\n *\n * - 更新:订阅 Buddy/onBuddyListChange 全量快照(T10 实证 = BuddyCategory[],\n * 好友明细在 category.buddyList)维护 uid → BuddyListEntry 快照。\n * - diff:首帧只建 baseline 不发事件(防止启动时对每个好友发 friend_add);\n * 之后快照对比上一帧——新增发布 BuddyCache/onBuddyAdded、消失发布\n * BuddyCache/onBuddyRemoved(归一化事件,协议层翻译保持纯函数)。\n * - 消费:协议翻译层订阅 diff 通道(friend_add notice)或只读快照查询。\n *\n * 无全局单例(ADR-015 推论)——每进程每 session 一份,由装配层持有。\n */\n\nimport type { BuddyEventChannel } from \"../bridge/index.js\";\nimport { NTEventChannel } from \"../event-channel.js\";\nimport type { BuddyCategory, BuddyListEntry } from \"../types/index.js\";\n\n/** BuddyCache diff 事件 listener(归一化「新增/删除」事件)。 */\nexport type BuddyCacheListener = {\n onBuddyAdded: (entry: BuddyListEntry) => void;\n onBuddyRemoved: (entry: BuddyListEntry) => void;\n};\n\n/** BuddyCache diff 事件通道(事件名 BuddyCache/onBuddyAdded 等)。 */\nexport type BuddyCacheEventChannel = NTEventChannel<BuddyCacheListener, \"BuddyCache\">;\n\n/** BuddyCache 构造参数。 */\nexport interface BuddyCacheOptions {\n /** Buddy 事件通道(FriendBridge 注册原生监听后 emit 到这里)。 */\n channel: BuddyEventChannel;\n /** 校准日志(可选:快照形状异常 raw 记录;缺省静默)。 */\n logger?: { warn(obj: unknown, msg?: string): void };\n}\n\n/**\n * 防御性收窄 onBuddyListChange 载荷 → BuddyCategory[](T10 实证形状;\n * 非数组返回 null,调用方打 raw 日志积累校准数据)。\n */\nexport function narrowBuddyCategories(arg: unknown): BuddyCategory[] | null {\n if (!Array.isArray(arg)) {\n return null;\n }\n for (const item of arg) {\n if (item === null || typeof item !== \"object\") {\n return null;\n }\n }\n return arg as BuddyCategory[];\n}\n\n/** 从快照分类拍平好友明细(buddyList 缺失的分类跳过)。 */\nfunction flattenEntries(categories: BuddyCategory[]): BuddyListEntry[] {\n const out: BuddyListEntry[] = [];\n for (const cat of categories) {\n for (const entry of cat.buddyList ?? []) {\n if (typeof entry?.uid === \"string\" && entry.uid !== \"\") {\n out.push(entry);\n }\n }\n }\n return out;\n}\n\n/** 好友缓存:快照维护 + diff 归一化事件。 */\nexport class BuddyCache {\n private readonly channel: BuddyEventChannel;\n private readonly logger: { warn(obj: unknown, msg?: string): void } | undefined;\n\n /** diff 归一化事件通道(协议层订阅 friend_add / IPC 转发消费)。 */\n readonly events: BuddyCacheEventChannel = new NTEventChannel(\"BuddyCache\");\n\n /** 好友快照(uid → entry,onBuddyListChange 全量维护)。 */\n private readonly buddies = new Map<string, BuddyListEntry>();\n\n /** 首帧已建 baseline(此后快照才参与 diff)。 */\n private bootstrapped = false;\n\n private unsubscribes: (() => void) | null = null;\n\n constructor(opts: BuddyCacheOptions) {\n this.channel = opts.channel;\n this.logger = opts.logger;\n }\n\n /** 订阅 Buddy 事件通道(幂等)。 */\n register(): void {\n if (this.unsubscribes !== null) {\n return;\n }\n const unsub = this.channel.on(\"Buddy/onBuddyListChange\", (arg: unknown) => {\n const categories = narrowBuddyCategories(arg);\n if (categories === null) {\n // 未知形状:raw 日志积累校准数据(V2 事件载荷是 boolean,同样落这里)\n this.logger?.warn(\n { raw: JSON.stringify(arg)?.slice(0, 2000) },\n \"BuddyCache: onBuddyListChange 未知载荷形状\",\n );\n return;\n }\n this.applySnapshot(flattenEntries(categories));\n });\n this.unsubscribes = unsub;\n }\n\n /** 退订事件通道(幂等)。 */\n unregister(): void {\n this.unsubscribes?.();\n this.unsubscribes = null;\n this.buddies.clear();\n this.bootstrapped = false;\n }\n\n /** 应用全量快照:首帧建 baseline,之后 diff 发布归一化事件。 */\n private applySnapshot(entries: BuddyListEntry[]): void {\n if (!this.bootstrapped) {\n for (const entry of entries) {\n this.buddies.set(entry.uid, entry);\n }\n this.bootstrapped = true;\n return;\n }\n const next = new Map<string, BuddyListEntry>();\n for (const entry of entries) {\n next.set(entry.uid, entry);\n }\n for (const [uid, entry] of next) {\n if (!this.buddies.has(uid)) {\n this.events.emit(\"BuddyCache/onBuddyAdded\", entry);\n }\n }\n for (const [uid, entry] of this.buddies) {\n if (!next.has(uid)) {\n this.events.emit(\"BuddyCache/onBuddyRemoved\", entry);\n }\n }\n this.buddies.clear();\n for (const [uid, entry] of next) {\n this.buddies.set(uid, entry);\n }\n }\n\n /** 好友快照(只读拷贝)。 */\n listBuddies(): BuddyListEntry[] {\n return [...this.buddies.values()];\n }\n\n /** 单个好友(快照;undefined = 不在缓存)。 */\n getBuddy(uid: string): BuddyListEntry | undefined {\n return this.buddies.get(uid);\n }\n\n /** 快照是否已建(测试/诊断用)。 */\n get baselineReady(): boolean {\n return this.bootstrapped;\n }\n}\n","/**\n * 等待/延迟工具(login 模块共享,2026-08-08 克隆合并)\n */\n\n/** 等待条件满足(轮询,带超时)。 */\nexport function waitFor(\n predicate: () => boolean,\n timeoutMs: number,\n intervalMs = 500,\n): Promise<boolean> {\n return new Promise((resolve) => {\n const started = Date.now();\n const tick = (): void => {\n if (predicate()) {\n resolve(true);\n return;\n }\n if (Date.now() - started > timeoutMs) {\n resolve(false);\n return;\n }\n setTimeout(tick, intervalMs);\n };\n tick();\n });\n}\n\n/** 短延迟。 */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n","/**\n * GroupCache:群/成员缓存(ADR-008,P2-17,2026-08-05)\n *\n * - 更新:订阅 GroupEventChannel 主动维护(群列表/群详情/成员事件 → 缓存 upsert)。\n * - 回填:只读接口查询缺失时经 GroupApi 惰性拉取(in-flight 去重防并发重复拉)。\n * - 消费:协议翻译层只读消费,禁止调 API(翻译 = 纯函数)。\n *\n * 无全局单例(ADR-015 推论)——每进程每 session 实例化一份,由装配层持有。\n */\n\nimport type { GroupApi } from \"../apis/index.js\";\nimport type { GroupEventChannel } from \"../bridge/index.js\";\nimport { waitFor } from \"../login/wait.js\";\nimport type {\n Group,\n GroupDetailInfo,\n GroupMember,\n GroupMemberDataSource,\n GroupMemberListChange,\n} from \"../types/index.js\";\nimport { GroupListUpdateType } from \"../types/index.js\";\n\n/** GroupCache 构造参数。 */\nexport interface GroupCacheOptions {\n /** 群事件通道(GroupBridge 注册原生监听后 emit 到这里)。 */\n channel: GroupEventChannel;\n /** 群 API(惰性回填用)。 */\n groupApi: GroupApi;\n}\n\n/**\n * 群/成员缓存:事件主动维护 + 查询惰性回填。\n * 只读接口 getGroupDetail / getMembers / getMember / getGroup(列表项)。\n */\nexport class GroupCache {\n private readonly channel: GroupEventChannel;\n private readonly groupApi: GroupApi;\n\n /** 群列表项(onGroupListUpdate / getGroupList 回填)。 */\n private readonly groupList = new Map<string, Group>();\n /** 群详情(onGroupDetailInfoChange / getGroupInfo 回填)。 */\n private readonly groupDetails = new Map<string, GroupDetailInfo>();\n /** 群成员:groupCode → (uid → GroupMember)。 */\n private readonly members = new Map<string, Map<string, GroupMember>>();\n /** 惰性回填 in-flight 去重。 */\n private readonly inFlight = new Map<string, Promise<unknown>>();\n\n private unsubscribes: (() => void) | null = null;\n\n constructor(opts: GroupCacheOptions) {\n this.channel = opts.channel;\n this.groupApi = opts.groupApi;\n }\n\n /** 订阅群事件通道(幂等)。 */\n register(): void {\n if (this.unsubscribes !== null) {\n return;\n }\n const unsubs: Array<() => void> = [];\n unsubs.push(\n this.channel.on(\"Group/onGroupListInited\", (listEmpty: boolean) => {\n if (listEmpty) {\n this.groupList.clear();\n }\n }),\n );\n unsubs.push(\n this.channel.on(\n \"Group/onGroupListUpdate\",\n (updateType: GroupListUpdateType, groupList: Group[]) => {\n if (updateType === GroupListUpdateType.ALL) {\n this.groupList.clear();\n }\n for (const group of groupList) {\n this.groupList.set(group.groupCode, group);\n }\n },\n ),\n );\n unsubs.push(\n this.channel.on(\"Group/onGroupDetailInfoChange\", (detailInfo: GroupDetailInfo) => {\n this.groupDetails.set(detailInfo.groupCode, detailInfo);\n }),\n );\n unsubs.push(\n this.channel.on(\"Group/onMemberListChange\", (arg: GroupMemberListChange) => {\n this.upsertMembers(arg.sceneId, arg.infos);\n }),\n );\n unsubs.push(\n this.channel.on(\n \"Group/onMemberInfoChange\",\n (\n _groupCode: string,\n _dataSource: GroupMemberDataSource,\n members: Map<string, GroupMember>,\n ) => {\n this.upsertMembers(_groupCode, members);\n },\n ),\n );\n this.unsubscribes = () => {\n for (const unsub of unsubs) {\n unsub();\n }\n };\n }\n\n /** 退订事件通道(幂等)。 */\n unregister(): void {\n this.unsubscribes?.();\n this.unsubscribes = null;\n this.groupList.clear();\n this.groupDetails.clear();\n this.members.clear();\n this.inFlight.clear();\n }\n\n /** 合并成员到该群缓存(uid → GroupMember)。 */\n private upsertMembers(groupCode: string, infos: Map<string, GroupMember>): void {\n let map = this.members.get(groupCode);\n if (map === undefined) {\n map = new Map();\n this.members.set(groupCode, map);\n }\n for (const [uid, member] of infos) {\n map.set(uid, member);\n }\n }\n\n /** 群列表是否已有缓存(同步判断)。 */\n hasGroup(groupCode: string): boolean {\n return this.groupList.has(groupCode) || this.groupDetails.has(groupCode);\n }\n\n /**\n * 群列表快照(只读,缓存由 onGroupListUpdate 事件主动维护)。\n * 群列表数据不走原生 getGroupList 返回值(实测仅 { result, errMsg },\n * 列表经事件推送)——协议/IPC 层消费列表一律经此方法。\n */\n listGroups(): Group[] {\n return [...this.groupList.values()];\n }\n\n /**\n * 群列表快照(缓存为空时主动触发原生刷新,等待事件回填后返回)。\n *\n * 实测(2026-08-08 端到端联调):onGroupListUpdate 仅在登录后首轮同步\n * 推送(MMKV 已有缓存后不再重复推送)——进程内缓存为空时列表读不到。\n * 触发 getGroupList(true) 会重新拉取并推送事件回填缓存。\n * 返回空数组 = 刷新后仍无数据(超时 5s)。\n */\n async listGroupsRefreshed(): Promise<Group[]> {\n if (this.groupList.size > 0) {\n return [...this.groupList.values()];\n }\n await this.groupApi.getGroupList(true);\n await waitFor(() => this.groupList.size > 0, 5_000, 200);\n return [...this.groupList.values()];\n }\n\n /**\n * 群列表项(只读;缺失惰性回填 getGroupList)。\n * 返回 undefined 表示群不存在。\n */\n async getGroup(groupCode: string): Promise<Group | undefined> {\n const cached = this.groupList.get(groupCode);\n if (cached !== undefined) {\n return cached;\n }\n await this.loadGroupList();\n return this.groupList.get(groupCode);\n }\n\n /**\n * 群详情(只读;缺失惰性回填 getGroupInfo)。\n * getGroupInfo 恒返回对象(失败抛 KernelError),直接缓存。\n */\n async getGroupDetail(groupCode: string): Promise<GroupDetailInfo> {\n const cached = this.groupDetails.get(groupCode);\n if (cached !== undefined) {\n return cached;\n }\n const detail = await this.dedupe<GroupDetailInfo>(`detail:${groupCode}`, () =>\n this.groupApi.getGroupInfo(groupCode),\n );\n this.groupDetails.set(groupCode, detail);\n return detail;\n }\n\n /**\n * 群成员列表(只读;缺失惰性回填 getAllMemberList(forceFetch=true))。\n * 返回空数组表示该群无成员(或拉取失败已由 GroupApi 抛 KernelError)。\n */\n async getMembers(groupCode: string): Promise<GroupMember[]> {\n const map = this.members.get(groupCode);\n if (map !== undefined && map.size > 0) {\n return [...map.values()];\n }\n const members = await this.dedupe<GroupMember[]>(`members:${groupCode}`, () =>\n this.groupApi.getGroupMemberList(groupCode, true),\n );\n const merged = new Map<string, GroupMember>();\n for (const member of members) {\n merged.set(member.uid, member);\n }\n this.members.set(groupCode, merged);\n return members;\n }\n\n /**\n * 群成员(只读;缺失回填:先整群拉取,再按 uid 取;仍无则 getMemberInfo 单查)。\n * 返回 undefined 表示成员不在群。\n */\n async getMember(groupCode: string, uid: string): Promise<GroupMember | undefined> {\n const cached = await this.getCachedMember(groupCode, uid);\n if (cached !== undefined) {\n return cached;\n }\n const members = await this.dedupe<GroupMember[]>(`member:${groupCode}:${uid}`, () =>\n this.groupApi.getGroupMemberInfo(groupCode, [uid]),\n );\n const [member] = members;\n if (member === undefined) {\n return;\n }\n const target = this.members.get(groupCode);\n if (target !== undefined) {\n target.set(member.uid, member);\n }\n return member;\n }\n\n /** 读缓存成员(缺失先整群回填一次)。 */\n private async getCachedMember(\n groupCode: string,\n uid: string,\n ): Promise<GroupMember | undefined> {\n let map = this.members.get(groupCode);\n if (map === undefined || map.size === 0) {\n await this.getMembers(groupCode);\n map = this.members.get(groupCode);\n }\n return map?.get(uid);\n }\n\n /** 惰性回填全量群列表(getGroup 缺失时)。 */\n private async loadGroupList(): Promise<void> {\n const list = await this.dedupe<Group[]>(\"group-list\", () =>\n this.groupApi.getGroupList(false),\n );\n for (const group of list) {\n this.groupList.set(group.groupCode, group);\n }\n }\n\n /** 惰性回填并发去重:同 key 并发只拉一次。 */\n private async dedupe<T>(key: string, loader: () => Promise<T>): Promise<T> {\n const existing = this.inFlight.get(key);\n if (existing !== undefined) {\n return await (existing as Promise<T>);\n }\n const task = loader()\n .catch((err: unknown) => {\n throw err;\n })\n .finally(() => {\n this.inFlight.delete(key);\n });\n this.inFlight.set(key, task);\n return await task;\n }\n}\n","/**\n * CoreContext:只读装配根(ADR-015 推论,无全局单例)\n *\n * 把 logger / paths / wrapper 上下文聚合成单一装配根,由装配层(NapukettoCore)\n * 创建并传递给消费方(协议层 adapter、apis、cache)。\n *\n * - 无全局单例:每账号每进程一份,多账号多进程场景天然隔离(ADR-015)。\n * - 只读消费:字段由装配层填充,消费方只读,不直接修改。\n */\nimport type pino from \"pino\";\nimport type { PathWrapper } from \"./infra/index.js\";\nimport type { LoginResult } from \"./login/index.js\";\nimport type { WrapperContext } from \"./wrapper/wrapper-loader.js\";\n\nexport interface CoreContextOptions {\n /** pino logger(console + 可选文件,由装配层创建)。 */\n logger: pino.Logger;\n /** 账号数据目录布局(config/logs/cache)。 */\n paths: PathWrapper;\n}\n\n/**\n * 装配根:持有进程级共享对象。\n * wrapper / login 由 NapukettoCore 装配后填充,未装配前为 null。\n */\nexport interface CoreContext {\n readonly logger: pino.Logger;\n readonly paths: PathWrapper;\n /** QQ wrapper 上下文(attachWrapper 后填充)。 */\n wrapper: WrapperContext | null;\n /** 登录结果(login 成功后填充)。 */\n login: LoginResult | null;\n}\n\n/** 创建装配根(初始 wrapper/login 为 null)。 */\nexport function createCoreContext(opts: CoreContextOptions): CoreContext {\n return {\n logger: opts.logger,\n paths: opts.paths,\n wrapper: null,\n login: null,\n };\n}\n","/**\n * wrapper NAPI 回调适配器实现(普通 JS 对象,2026-08-05 确认)\n *\n * 事实(实测):wrapper.node 的 exports 89 键里没有 NodeI*Adapter/Listener 构造器,\n * engine.initWithDeskTopConfig / session.init 的 adapter 与 listener 参数一律接受\n * **普通 JS 对象**——NAPI 按方法名反射读取回调(自研实现)。\n *\n * 职责:engine / session 交互所需的全部回调占位与监听器工厂。\n * - GlobalAdapter → engine.initWithDeskTopConfig 第二参(onLog / getAppSetting / ...)\n * - DependsAdapter → session.init 第二参(onMSFStatusChange / onMSFSsoError / getGroupCode)\n * - DispatcherAdapter → session.init 第三参(dispatchRequest / dispatchCall / dispatchCallWithJson)\n * - createSessionListener → 日志版 session 监听器(boot 阶段默认,login 模块可覆盖)\n * - createLoginListener → 登录监听器占位(登录器接入时填充)\n *\n * 这些类的实例本身就是「普通 JS 对象」,可被 NAPI 反射——直接 new 传入即可。\n */\n\nimport type {\n IKernelLoginListener,\n NodeIDependsAdapter,\n NodeIDispatcherAdapter,\n NodeIGlobalAdapter,\n NodeIKernelSessionListener,\n} from \"../types/index.js\";\n\n/** NAPI 回调占位(方法存在即可被反射调用,无副作用)。 */\nconst noop = (): void => undefined;\n\n/** NodeIGlobalAdapter 实现(engine.initWithDeskTopConfig 第二参)。 */\nexport class GlobalAdapter implements NodeIGlobalAdapter {\n onLog = noop;\n onGetSrvCalTime = noop;\n onShowErrUITips = noop;\n fixPicImgType = noop;\n getAppSetting = noop;\n onInstallFinished = noop;\n onUpdateGeneralFlag = noop;\n onGetOfflineMsg = noop;\n}\n\n/** NodeIDependsAdapter 实现(session.init 第二参)。 */\nexport class DependsAdapter implements NodeIDependsAdapter {\n onMSFStatusChange = noop;\n onMSFSsoError = noop;\n getGroupCode = noop;\n}\n\n/** NodeIDispatcherAdapter 实现(session.init 第三参)。 */\nexport class DispatcherAdapter implements NodeIDispatcherAdapter {\n dispatchRequest = noop;\n dispatchCall = noop;\n dispatchCallWithJson = noop;\n}\n\n/** 会话监听器选项。 */\nexport interface SessionListenerOptions {\n /** 事件日志回调(默认静默——boot 阶段事件对用户是噪音;调试时可传回调观察)。 */\n onEvent?: (name: string, ...args: unknown[]) => void;\n}\n\n/**\n * 创建会话监听器(默认静默,boot 阶段默认;login 模块接入后可覆盖)。\n * 回调方法必须存在(NAPI 按方法名反射),但输出与否由调用方决定——\n * onNTSessionCreate / onOpentelemetryInit 等对用户无信息量,默认不打 stdout。\n */\nexport function createSessionListener(\n opts: SessionListenerOptions = {},\n): NodeIKernelSessionListener {\n const { onEvent } = opts;\n const emit = (name: string, ...args: unknown[]): void => {\n onEvent?.(name, ...args);\n };\n return {\n onNTSessionCreate: (sessionId) => emit(\"onNTSessionCreate\", sessionId),\n onGProSessionCreate: (sessionId) => emit(\"onGProSessionCreate\", sessionId),\n onSessionInitComplete: (sessionId) => emit(\"onSessionInitComplete\", sessionId),\n onOpentelemetryInit: (info) => emit(\"onOpentelemetryInit\", info),\n onUserOnlineResult: (result) => emit(\"onUserOnlineResult\", result),\n onGetSelfTinyId: (result) => emit(\"onGetSelfTinyId\", result),\n };\n}\n\n/** 创建登录监听器(占位,登录器接入时填充)。 */\nexport function createLoginListener(): IKernelLoginListener {\n return {};\n}\n","/**\n * 登录连接与快速登录(从 lifecycle.ts 拆分,2026-08-08 FTA 优化)\n *\n * - waitForNetworkConnection:等 MSF 网络连接就绪(getMsfStatus() === 3)\n * - ensureLoginConnected:connect() → 等 onLoginConnected(自建宿主必需)\n * - quickLogin:历史账号快速登录(网络异常 1006511 等就绪后重试;\n * T3 单步超时兜底——底层 promise 永久挂起时按登录失败抛出)\n */\n\nimport { kernelError } from \"../infra/index.js\";\nimport { createLoginListener } from \"../wrapper/wrapper-adapters.js\";\nimport type { WrapperContext } from \"../wrapper/wrapper-loader.js\";\nimport { sleep, waitFor } from \"./wait.js\";\n\n/** 登录结果(QR 或快速登录)。 */\nexport interface LoginResult {\n uin: string;\n uid: string;\n nick: string;\n}\n\n/** 登录列表项(getLoginList 返回,说明书参考)。 */\nexport interface LoginAccountInfo {\n uin: string;\n uid?: string;\n nickName?: string;\n isQuickLogin?: boolean;\n}\n\n/** 登录服务形状(getLoginList / quickLoginWithUin / getMsfStatus,自研描述)。 */\ntype LoginServiceShape = {\n getLoginList(): Promise<{ result: number; LocalLoginInfoList: LoginAccountInfo[] }>;\n quickLoginWithUin(uin: string): Promise<{ result: string; loginErrorInfo: { errMsg: string } }>;\n getMsfStatus(): number;\n};\n\n/**\n * 网络状态(MSF)常量。\n * 3 = 已连接(NapCat waitForNetworkConnection 语义,自研描述)。\n */\nconst MSF_STATUS_CONNECTED = 3;\n\n/** 快速登录网络异常错误码(1006511,P2-1 实测:登录前网络未就绪时报此错)。 */\nconst NETWORK_ERROR_CODE = \"1006511\";\n\n/** 登录连接异常错误提示(自建宿主实测:connect 后未缓冲即 quickLogin 报此错)。 */\nconst CONNECTION_ERROR_HINT = \"登录系统连接异常\";\n\n/** 网络重试最大次数(每次重试前等网络就绪)。 */\nconst NETWORK_RETRY_MAX = 3;\n\n/** 网络就绪等待超时(毫秒)。 */\nconst NETWORK_READY_TIMEOUT_MS = 15_000;\n\n/** 网络就绪轮询间隔(毫秒)。 */\nconst NETWORK_READY_POLL_MS = 1000;\n\n/** 连接稳定缓冲(毫秒,p0-kernel-flow 实证值)。 */\nconst CONNECTION_SETTLE_MS = 3000;\n\n/**\n * 快速登录单步超时(毫秒,T3:软重登挂起兜底)。\n * 服务端已有同账号会话时底层 quickLoginWithUin / getLoginList 永不 settle——\n * 裸 await 会让 core.login 的 catch/qrFallback、loader 的 control.release 全部\n * 永久等待。底层 promise 无法取消:超时后悬挂丢弃即可。\n */\nexport const QUICK_LOGIN_TIMEOUT_MS = 20_000;\n\n/** 快速登录超时文案(按登录失败处理;不含网络错误特征,不触发 1006511 重试叠挂)。 */\nexport const QUICK_LOGIN_TIMEOUT_MESSAGE = \"快速登录超时\";\n\n/**\n * 等待网络连接就绪(loginService.getMsfStatus() === 3)。\n * 参考 NapCat waitForNetworkConnection 思路(自研实现):快速登录在 QQ 刚拉起、\n * 网络栈未初始化时报 1006511 网络异常——等待 MSF 连接后再重试。\n * @returns 是否在超时前就绪。\n */\nexport function waitForNetworkConnection(\n ctx: WrapperContext,\n opts: { timeoutMs?: number } = {},\n): Promise<boolean> {\n const raw = ctx.loginService as unknown as LoginServiceShape | null;\n if (raw === null || typeof raw.getMsfStatus !== \"function\") {\n return Promise.resolve(false);\n }\n return waitFor(\n () => {\n try {\n return raw.getMsfStatus() === MSF_STATUS_CONNECTED;\n } catch {\n return false;\n }\n },\n opts.timeoutMs ?? NETWORK_READY_TIMEOUT_MS,\n NETWORK_READY_POLL_MS,\n );\n}\n\n/** 列出历史登录账号(boot.cjs 启动横幅用,「可用快速登录 of QQ」)。 */\nexport async function listLoginAccounts(ctx: WrapperContext): Promise<LoginAccountInfo[]> {\n const raw = ctx.loginService as unknown as LoginServiceShape | null;\n if (raw === null) {\n throw kernelError(\"loginService 无效(缺 getLoginList)\", \"INVALID_STATE\");\n }\n const list = await raw.getLoginList();\n return list.LocalLoginInfoList;\n}\n\n/**\n * 登录服务连接(自建宿主必需,HANDOVER-V6/V10 p0-kernel-flow 实证顺序):\n * connect() → 等 onLoginConnected。不 connect 则快速登录报「登录系统连接异常」。\n *\n * 对齐 p0-kernel-flow:initConfig → getLoginList → connect + onLoginConnected →\n * quickLoginWithUin。QQ 环境(worker/主进程)下可能已连接——connect 幂等,\n * 监听器超时兜底,不影响原流程。\n */\nasync function ensureLoginConnected(\n ctx: WrapperContext,\n opts: { timeoutMs?: number },\n): Promise<void> {\n const raw = ctx.loginService as unknown as {\n connect?: () => void;\n addKernelLoginListener?: (listener: unknown) => number;\n } | null;\n if (raw === null || typeof raw.connect !== \"function\") {\n return; // 无 connect(老版本 / QQ 主进程自连),跳过\n }\n await waitLoginConnected(raw, opts.timeoutMs);\n // onLoginConnected 后网络栈仍在初始化(p0-kernel-flow 实证:connect → onLoginConnected\n // → 3s 缓冲 → quickLoginWithUin 成功;无缓冲则「登录系统连接异常」)。\n await sleep(CONNECTION_SETTLE_MS);\n}\n\n/** 等 onLoginConnected(注册临时监听 + connect + 超时兜底)。 */\nfunction waitLoginConnected(\n raw: {\n connect?: () => void;\n addKernelLoginListener?: (listener: unknown) => number;\n },\n timeoutMs?: number,\n): Promise<void> {\n return new Promise<void>((resolve) => {\n let settled = false;\n const finish = (): void => {\n if (!settled) {\n settled = true;\n resolve();\n }\n };\n tryRegisterConnectListener(raw, finish);\n setTimeout(finish, timeoutMs ?? NETWORK_READY_TIMEOUT_MS);\n try {\n raw.connect?.();\n } catch {\n finish();\n }\n });\n}\n\n/** 注册临时登录连接监听(失败不阻塞连接)。 */\nfunction tryRegisterConnectListener(\n raw: {\n connect?: () => void;\n addKernelLoginListener?: (listener: unknown) => number;\n },\n finish: () => void,\n): void {\n try {\n // 临时监听等 onLoginConnected;保留不 remove——登录后续回调\n // (onQRCodeLoginSucceed 等)仍可能触发,且进程内只登录一次,无泄漏风险。\n const listener = createLoginListener();\n listener.onLoginConnected = () => {\n finish();\n };\n if (typeof raw.addKernelLoginListener === \"function\") {\n raw.addKernelLoginListener(listener);\n }\n } catch {\n // 监听注册失败不阻塞连接\n }\n}\n\n/**\n * 目标账号选择(2026-08-07 修复:显式 uin 不在列表时**报错**而非静默 fallback——\n * 之前会退到第一个可快速登录账号,导致 `-q <uin>` 实际登成别的号)。\n * 导出供单测(login-connect.test.ts)。\n */\nexport function pickLoginTarget(\n items: LoginAccountInfo[],\n uin: string | undefined,\n): LoginAccountInfo {\n if (uin !== undefined) {\n const target = items.find((i) => i.uin === uin);\n if (target === undefined) {\n const available = items.map((i) => i.uin).join(\"、\");\n throw kernelError(`账号 ${uin} 不在登录列表(可用:${available})`, \"NOT_FOUND\");\n }\n return target;\n }\n // 未指定 uin:优先第一个可快速登录账号,否则列表第一个\n const quick = items.find((i) => i.isQuickLogin);\n if (quick !== undefined) {\n return quick;\n }\n const [first] = items;\n if (first === undefined) {\n throw kernelError(\"无可用登录账号\", \"NOT_LOGIN\");\n }\n return first;\n}\n\n/** 单次快速登录尝试的结果(成功带结果,失败带错误消息)。 */\ntype AttemptResult = { ok: true; result: LoginResult } | { ok: false; errMsg: string };\n\n/**\n * 底层 NAPI promise 超时竞速(T3):promise 先 settle 用其结果,超时先到用\n * onTimeout 产物(可抛错 → 整体 reject)。底层 promise 无法取消,超时后\n * 悬挂丢弃——勿做清理动作引发二次问题;败者的迟到 settle 由 race 吞掉。\n */\nfunction raceWithTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n onTimeout: () => T,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const timeout = new Promise<T>((resolve, reject) => {\n timer = setTimeout(() => {\n try {\n resolve(onTimeout());\n } catch (err) {\n reject(err);\n }\n }, timeoutMs);\n });\n return Promise.race([promise, timeout]).finally(() => {\n if (timer !== null) {\n clearTimeout(timer);\n }\n });\n}\n\n/**\n * 带网络重试的快速登录(P2-1)。\n * 重试语义:仅当失败为网络异常(1006511)且未达上限时,等网络就绪后重试。\n * T3:每次尝试包超时竞速,挂起按登录失败处理(超时文案非网络错误 → 不重试)。\n */\nasync function loginWithNetworkRetry(\n ctx: WrapperContext,\n loginService: LoginServiceShape,\n target: LoginAccountInfo,\n opts: { uin?: string; timeoutMs?: number },\n quickTimeoutMs: number,\n): Promise<LoginResult> {\n let lastErrMsg = \"\";\n for (let attempt = 1; attempt <= NETWORK_RETRY_MAX; attempt += 1) {\n const attemptResult = await raceWithTimeout(\n attemptQuickLogin(loginService, target),\n quickTimeoutMs,\n (): AttemptResult => ({ ok: false, errMsg: QUICK_LOGIN_TIMEOUT_MESSAGE }),\n );\n if (attemptResult.ok) {\n return attemptResult.result;\n }\n lastErrMsg = attemptResult.errMsg;\n const shouldRetry = isNetworkError(lastErrMsg) && attempt < NETWORK_RETRY_MAX;\n if (!shouldRetry) {\n break;\n }\n const ready = await waitForNetworkConnection(ctx, {\n timeoutMs: opts.timeoutMs ?? NETWORK_READY_TIMEOUT_MS,\n });\n if (!ready) {\n break;\n }\n }\n throw kernelError(`快速登录失败: ${lastErrMsg}`, \"NOT_LOGIN\");\n}\n\n/** 单次快速登录尝试(成功带结果,失败带错误消息)。 */\nasync function attemptQuickLogin(\n loginService: LoginServiceShape,\n target: LoginAccountInfo,\n): Promise<AttemptResult> {\n const result = await loginService.quickLoginWithUin(target.uin);\n const { errMsg } = result.loginErrorInfo;\n if (errMsg) {\n return { ok: false, errMsg };\n }\n return {\n ok: true,\n result: {\n uin: target.uin,\n uid: target.uid ?? \"\",\n nick: target.nickName ?? \"\",\n },\n };\n}\n\n/** 判定快速登录失败是否为网络异常(重试语义)。导出供单测。 */\nexport function isNetworkError(errMsg: string): boolean {\n return errMsg.includes(NETWORK_ERROR_CODE) || errMsg.includes(CONNECTION_ERROR_HINT);\n}\n\n/** quickLogin 参数(uin 指定账号;timeoutMs 网络就绪等待;quickLoginTimeoutMs 单步超时)。 */\nexport interface QuickLoginOptions {\n /** 指定快速登录账号(缺省遍历历史列表,优先 isQuickLogin)。 */\n uin?: string;\n /** 网络就绪等待超时(毫秒,P2-1 既有语义),默认 15s。 */\n timeoutMs?: number;\n /** 单步快速登录超时(毫秒,T3 挂起兜底),默认 {@link QUICK_LOGIN_TIMEOUT_MS}。 */\n quickLoginTimeoutMs?: number;\n}\n\n/**\n * 快速登录:遍历历史登录列表尝试。\n * P2-1:失败且错误为网络异常(1006511)时,等网络就绪后重试(最多 NETWORK_RETRY_MAX 次)。\n * T3:getLoginList 与每次 quickLoginWithUin 均包超时竞速(默认 20s)——服务端\n * 已有会话时底层 promise 永久挂起,超时按登录失败(「快速登录失败: 快速登录超时」)\n * 抛出,上层 catch/qrFallback 语义自动生效。\n */\nexport async function quickLogin(\n ctx: WrapperContext,\n opts: QuickLoginOptions = {},\n): Promise<LoginResult> {\n const raw = ctx.loginService as unknown as LoginServiceShape | null;\n if (raw === null) {\n throw kernelError(\"loginService 无效(缺 getLoginList)\", \"INVALID_STATE\");\n }\n const loginService = raw;\n const quickTimeoutMs = opts.quickLoginTimeoutMs ?? QUICK_LOGIN_TIMEOUT_MS;\n // 连接登录服务(自建宿主必需:不 connect 则「登录系统连接异常」)\n await ensureLoginConnected(ctx, {\n ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),\n });\n const list = await raceWithTimeout(loginService.getLoginList(), quickTimeoutMs, () => {\n throw kernelError(`快速登录失败: ${QUICK_LOGIN_TIMEOUT_MESSAGE}`, \"NOT_LOGIN\");\n });\n const items = list.LocalLoginInfoList;\n if (items.length === 0) {\n throw kernelError(\"无历史登录账号\", \"NOT_LOGIN\");\n }\n const target = pickLoginTarget(items, opts.uin);\n return loginWithNetworkRetry(ctx, loginService, target, opts, quickTimeoutMs);\n}\n","/**\n * 完整启动生命周期(wrapper.node 契约流程,2026-08-05 实测确认,自研实现)\n *\n * 启动顺序由 wrapper.node 外部契约决定(engine→login→session→startNT),\n * 运行时实测确认,自研实现:\n * 1. engine.initWithDeskTopConfig(appid/qua/版本)\n * 2. loginService.initConfig + addKernelLoginListener\n * 3. getLoginList() → quickLoginWithUin(uin)(或 QR 登录)\n * 4. 登录成功 → genSessionConfig → session.init(config, 3 adapter, listener) → startNT(0)\n * 5. 等 init 完成信号(onOpentelemetryInit(is_init) 为主,onSessionInitComplete 为辅)\n *\n * 关键(2026-08-05 实测修正):\n * - session 用 `new wrapper.NodeIQQNTWrapperSession()` 创建;exports 不含 NodeI*Adapter/Listener\n * 构造器(89 键实测无)——adapter 与 listener 一律传普通 JS 对象(NAPI 反射读取方法回调)。\n *\n * 模块边界(2026-08-05 解耦;2026-08-08 FTA 优化再拆):\n * - 配置装配(buildEngineConfig / buildLoginConfig / buildSessionConfig)→ wrapper-config.ts\n * - NAPI 回调适配器(GlobalAdapter / DependsAdapter / DispatcherAdapter / listener 工厂)→ wrapper-adapters.ts\n * - 登录连接 + 快速登录(waitForNetworkConnection / quickLogin 等)→ login-connect.ts\n * - 本文件只保留 session 流程编排:就绪等待(waitSessionReady)+ session 初始化(initAndStartSession)。\n */\n\nimport { kernelError } from \"../infra/index.js\";\nimport type { NodeIKernelSessionListener, WrapperSessionInitConfig } from \"../types/index.js\";\nimport { DependsAdapter, DispatcherAdapter } from \"../wrapper/wrapper-adapters.js\";\nimport type { WrapperContext } from \"../wrapper/wrapper-loader.js\";\nimport { waitFor } from \"./wait.js\";\n\nexport type { LoginAccountInfo, LoginResult, QuickLoginOptions } from \"./login-connect.js\";\nexport { listLoginAccounts, quickLogin, waitForNetworkConnection } from \"./login-connect.js\";\n\n/** session init 默认超时(毫秒)。 */\nconst DEFAULT_INIT_TIMEOUT_MS = 15_000;\n\n/** session 就绪等待默认超时(毫秒)。 */\nconst SESSION_READY_TIMEOUT_MS = 30_000;\n\n/** session 就绪轮询间隔(毫秒)。 */\nconst SESSION_READY_POLL_MS = 1000;\n\n/**\n * 等待 QQ 自己完成 session 初始化(framework 复用模式,2026-08-05 修正)。\n *\n * 背景:QQ 9.9.31 实测——session 由 QQ 主进程自己创建并 init(登录后)。\n * 我们不自己 init(重复 init 断言 \"implementation of IQQNTWrapperSession is not valid\"),\n * 也不自己 create(登录前 create() 干扰 QQ,实测 QQ 退出 code=0)。\n * 正确做法:等 QQ 的 session 就绪(getMsgService 非 null = 核心 service 已挂载)。\n */\nexport async function waitSessionReady(\n ctx: WrapperContext,\n opts: { timeoutMs?: number },\n): Promise<void> {\n const { session } = ctx;\n if (session === null || session === undefined) {\n throw kernelError(\"session 未创建\", \"INVALID_STATE\");\n }\n const ok = await waitFor(\n () => {\n try {\n const svc = session.getMsgService();\n return svc !== null && svc !== undefined;\n } catch {\n return false;\n }\n },\n opts.timeoutMs ?? SESSION_READY_TIMEOUT_MS,\n SESSION_READY_POLL_MS,\n );\n if (!ok) {\n throw kernelError(\"等待 QQ session 就绪超时(QQ 未完成 init)\", \"TIMEOUT\");\n }\n}\n\n/** 登录连接 + 快速登录(quickLogin 等)已拆到 login-connect.ts(见文件头 re-export)。 */\n\n/** session 初始化(4 参全为普通 JS 对象,等 init 完成信号)。 */\nexport async function initAndStartSession(\n ctx: WrapperContext,\n config: WrapperSessionInitConfig,\n listener: NodeIKernelSessionListener,\n opts: { timeoutMs?: number },\n): Promise<void> {\n const { session } = ctx;\n if (session === null || session === undefined) {\n throw kernelError(\"session 未创建\", \"INVALID_STATE\");\n }\n\n // adapter / listener 全部用普通 JS 对象(实测 exports 89 键无 NodeI*Adapter/Listener\n // 构造器;NAPI 反射读取对象方法回调,自研实现)。\n const depends = new DependsAdapter();\n const dispatcher = new DispatcherAdapter();\n\n const initComplete = watchInitSignal(listener);\n session.init(config, depends, dispatcher, listener);\n startSessionBestEffort(ctx, session);\n\n const ok = await Promise.race([\n initComplete.then(() => true),\n waitFor(() => false, opts.timeoutMs ?? DEFAULT_INIT_TIMEOUT_MS).then(() => false),\n ]);\n if (!ok) {\n throw kernelError(\"session init 超时\", \"TIMEOUT\");\n }\n}\n\n/** 监听 init 完成信号:onOpentelemetryInit(is_init) 为主,onSessionInitComplete(0) 为辅。 */\nfunction watchInitSignal(listener: NodeIKernelSessionListener): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const onOpentelemetry = listener.onOpentelemetryInit;\n listener.onOpentelemetryInit = (info) => {\n if (info.is_init) {\n resolve();\n }\n if (typeof onOpentelemetry === \"function\") {\n onOpentelemetry(info);\n }\n };\n const onInitComplete = listener.onSessionInitComplete;\n listener.onSessionInitComplete = (r) => {\n if (r === 0 || r === \"0\") {\n resolve();\n } else {\n reject(kernelError(`session init 失败: ${String(r)}`, \"UNKNOWN\"));\n }\n if (typeof onInitComplete === \"function\") {\n onInitComplete(r);\n }\n };\n });\n}\n\n/**\n * 启动会话(best effort,失败不致命):\n * **先 init 后 start(2026-08-07 V9 决定性修正)**。\n * 自建宿主实测(HANDOVER-V9,p0-napcat-min):必须先 session.init 再\n * startupSession.start()——顺序颠倒(先 start 后 init)业务 service 不挂载\n * (getMsgService null);init 后用 startNT(非 startupSession.start)也失败。\n * NapCat initializeSession 同款:有 startupSession 用 start(),否则 startNT(0)。\n */\nfunction startSessionBestEffort(\n ctx: WrapperContext,\n session: NonNullable<WrapperContext[\"session\"]>,\n): void {\n if (startupSessionStart(ctx)) {\n return;\n }\n try {\n session.startNT(0);\n } catch {\n try {\n session.startNT();\n } catch {\n // 无 startNT(9.9.31):忽略,等 init 完成信号\n }\n }\n}\n\n/** 有 startupSession 则 start()(失败不致命),返回是否已处理。 */\nfunction startupSessionStart(ctx: WrapperContext): boolean {\n const { startupSession } = ctx;\n if (startupSession === null || typeof startupSession.start !== \"function\") {\n return false;\n }\n try {\n startupSession.start();\n } catch {\n // start 失败不致命,靠 onOpentelemetryInit/onSessionInitComplete 信号判断\n }\n return true;\n}\n","/**\n * login.ts:QR 登录流程编排 + 状态机 + selfInfo(ADR-010,2026-08-05)\n *\n * 流程(QQ wrapper 登录契约,自研实现):\n * loginService.initConfig(core.login 已做)→ addKernelLoginListener → connect()\n * → 指定账号:quickLoginWithUin;否则 getQRCodePicture() 触发二维码\n * → 回调驱动状态机:未登录 → 扫码中 → 已扫码 → 已登录(onQRCodeLoginSucceed)\n * → 二维码过期(errType=1 errCode=3)→ refresh()\n *\n * 不含 UI 渲染(cli 只做二维码渲染/URL 打印)。\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type { IKernelLoginListener } from \"../types/index.js\";\nimport { createLoginListener } from \"../wrapper/wrapper-adapters.js\";\n\n/** 登录状态。 */\nexport type LoginState = \"idle\" | \"waiting_scan\" | \"scanned\" | \"logged_in\" | \"failed\";\n\n/** 二维码数据(png base64 + 解码 url)。 */\nexport interface QrCodeData {\n pngBase64: string;\n qrcodeUrl: string;\n}\n\n/** selfInfo(登录成功后填写)。 */\nexport interface SelfInfo {\n uin: string;\n uid: string;\n nick: string;\n}\n\n/** 快速登录项。 */\nexport interface LoginListItem {\n uin: string;\n uid?: string;\n nickName?: string;\n isQuickLogin?: boolean;\n}\n\n/** loginService 接口(从 WrapperContext.loginService 获取)。 */\ninterface LoginServiceLike {\n addKernelLoginListener(listener: unknown): number;\n removeKernelLoginListener(listenerId: number): void;\n connect(): void;\n getLoginList(): Promise<{ result: number; LocalLoginInfoList: LoginListItem[] }>;\n quickLoginWithUin(uin: string): Promise<{ result: string; loginErrorInfo: { errMsg: string } }>;\n getQRCodePicture(): boolean;\n}\n\n/** 二维码过期错误码(errType=1 errCode=3,实测)。 */\nconst QR_EXPIRED_ERR_TYPE = 1;\nconst QR_EXPIRED_ERR_CODE = 3;\n\n/** 登录超时上限(毫秒,参考项目 60 次 × 2s = 120s,2026-08-13 用户拍板照搬)。 */\nexport const QR_LOGIN_TIMEOUT_MS = 120_000;\n\n/** 登录超时提示(参考项目文案,前端 failed 态展示)。 */\nexport const QR_LOGIN_TIMEOUT_MESSAGE = \"登录超时,请刷新页面重试\";\n\n/** 二维码过期判定。 */\nfunction isQrCodeExpired(errType: number, errCode: number): boolean {\n return errType === QR_EXPIRED_ERR_TYPE && errCode === QR_EXPIRED_ERR_CODE;\n}\n\n/** 二维码 png data URI 前缀剥离正则。 */\nconst DATA_URI_PREFIX_RE = /^data:image\\/\\w+;base64,/;\n\n/** QR 登录会话(状态机 + 回调订阅)。 */\nexport class QrLoginSession {\n private readonly loginService: LoginServiceLike;\n private state: LoginState = \"idle\";\n private listenerId: number | null = null;\n private self: SelfInfo | null = null;\n private readonly qrCodeHandlers = new Set<(qr: QrCodeData) => void>();\n private readonly stateHandlers = new Set<(state: LoginState) => void>();\n /** 超时定时器(refresh 后启动,登录成功/失败/stop 清理)。 */\n private timeoutTimer: ReturnType<typeof setTimeout> | null = null;\n /** 失败原因(超时等,failed 态透出给上层提示文案)。 */\n private failureReasonValue: string | null = null;\n /** 超时上限(毫秒,默认 QR_LOGIN_TIMEOUT_MS)。 */\n private readonly timeoutMs: number;\n\n constructor(loginService: unknown, options: { timeoutMs?: number } = {}) {\n const svc = loginService as LoginServiceLike | null;\n if (svc === null || typeof svc.addKernelLoginListener !== \"function\") {\n throw kernelError(\"loginService 无效(缺 addKernelLoginListener)\", \"INVALID_STATE\");\n }\n this.loginService = svc;\n this.timeoutMs = options.timeoutMs ?? QR_LOGIN_TIMEOUT_MS;\n }\n\n /** 当前状态。 */\n get currentState(): LoginState {\n return this.state;\n }\n\n /** selfInfo(登录成功后有值)。 */\n get selfInfo(): SelfInfo | null {\n return this.self;\n }\n\n /** 失败原因(超时等;failed 态透出给上层提示文案)。 */\n get failureReason(): string | null {\n return this.failureReasonValue;\n }\n\n /** 订阅二维码(png base64 + url)。返回退订函数。 */\n onQrCode(cb: (qr: QrCodeData) => void): () => void {\n this.qrCodeHandlers.add(cb);\n return () => this.qrCodeHandlers.delete(cb);\n }\n\n /** 订阅状态变化。返回退订函数。 */\n onStateChange(cb: (state: LoginState) => void): () => void {\n this.stateHandlers.add(cb);\n return () => this.stateHandlers.delete(cb);\n }\n\n /** 开始登录:注册监听 → connect → 快速登录(指定账号)或触发二维码。 */\n start(opts: { quickUin?: string } = {}): void {\n if (this.state !== \"idle\") {\n return;\n }\n this.listenerId = this.loginService.addKernelLoginListener(this.buildListener());\n this.loginService.connect();\n if (opts.quickUin !== undefined && opts.quickUin !== \"\") {\n // T3:快速登录可能永久挂起(服务端已有同账号会话)——先启动超时计时,\n // 挂起 → 超时 failed。此前挂起时连兜底计时都不启动(定时器只在 refresh\n // 里重启),ready 态软重登完全无兜底。\n this.restartTimeout();\n this.loginService\n .quickLoginWithUin(opts.quickUin)\n .then((result) => {\n // ⚠️ wrapper 契约(attemptQuickLogin 已实证):快速登录失败是\n // resolve 带 loginErrorInfo.errMsg,**不是 reject**——只挂 .catch\n // 会永不触发 → 无凭据环境(WSL 扫码)永不 refresh → 二维码永不\n // 产生,完全静默阻塞(2026-08-13 WSL 扫码卡死根因)。\n if (result.loginErrorInfo.errMsg !== \"\") {\n this.refreshAfterQuickLogin();\n }\n })\n .catch(() => {\n // reject(异常路径)同样回退二维码\n this.refreshAfterQuickLogin();\n });\n } else {\n this.refresh();\n }\n }\n\n /**\n * 快速登录失败回退二维码。仅 idle 态生效:超时 failed 后悬挂 promise 的\n * 迟到结果(含登录成功后 stop 之后的迟到 settle)不得复活会话/重复出码。\n */\n private refreshAfterQuickLogin(): void {\n if (this.state === \"idle\") {\n this.refresh();\n }\n }\n\n /** 刷新二维码(手动 / 过期自动)。每次出码重启超时计时。 */\n refresh(): void {\n this.failureReasonValue = null;\n this.setState(\"waiting_scan\");\n this.loginService.getQRCodePicture();\n this.restartTimeout();\n }\n\n /** 构建登录监听(普通 JS 对象,NAPI 反射)。 */\n private buildListener(): IKernelLoginListener {\n const listener = createLoginListener();\n listener.onQRCodeGetPicture = ({ pngBase64QrcodeData, qrcodeUrl }) => {\n const realBase64 = pngBase64QrcodeData.replace(DATA_URI_PREFIX_RE, \"\");\n this.emitQrCode({ pngBase64: realBase64, qrcodeUrl });\n };\n listener.onQRCodeLoginPollingStarted = () => {\n this.setState(\"waiting_scan\");\n };\n listener.onQRCodeSessionUserScaned = () => {\n this.setState(\"scanned\");\n };\n listener.onQRCodeLoginSucceed = (result) => {\n this.clearTimeoutTimer();\n this.self = { uin: result.uin, uid: result.uid, nick: result.nick ?? \"\" };\n this.setState(\"logged_in\");\n };\n listener.onQRCodeSessionFailed = (errType, errCode) => {\n if (isQrCodeExpired(errType, errCode)) {\n // 二维码过期 → 自动刷新(2026-08-13 用户拍板保留自动刷新 + 手动刷新按钮)\n this.refresh();\n return;\n }\n this.clearTimeoutTimer();\n this.failureReasonValue = null;\n this.setState(\"failed\");\n };\n listener.onLoginFailed = () => {\n this.clearTimeoutTimer();\n this.failureReasonValue = null;\n this.setState(\"failed\");\n };\n return listener;\n }\n\n /** 停止:注销监听(登录成功后调用,防重复回调)。 */\n stop(): void {\n this.clearTimeoutTimer();\n if (this.listenerId !== null) {\n this.loginService.removeKernelLoginListener(this.listenerId);\n this.listenerId = null;\n }\n }\n\n /** 重启超时计时器:超时未登录 → failed + 超时提示。 */\n private restartTimeout(): void {\n this.clearTimeoutTimer();\n this.timeoutTimer = setTimeout(() => {\n this.timeoutTimer = null;\n this.failureReasonValue = QR_LOGIN_TIMEOUT_MESSAGE;\n this.setState(\"failed\");\n }, this.timeoutMs);\n }\n\n /** 清理超时计时器(幂等)。 */\n private clearTimeoutTimer(): void {\n if (this.timeoutTimer !== null) {\n clearTimeout(this.timeoutTimer);\n this.timeoutTimer = null;\n }\n }\n\n private setState(next: LoginState): void {\n this.state = next;\n for (const cb of this.stateHandlers) {\n cb(next);\n }\n }\n\n private emitQrCode(qr: QrCodeData): void {\n for (const cb of this.qrCodeHandlers) {\n cb(qr);\n }\n }\n}\n","/**\n * QQ 数据根路径解析(P2-1,2026-08-06)\n *\n * 从 `getNTUserDataInfoConfig()` 的返回值中提取 QQ 真实数据根路径:\n * worker(utilityProcess)模式下 loginService 是 new 的,commonPath 必须指向\n * QQ 真实数据路径才能读到历史账号(cli 的 `.napuketto\\default` 读不到,\n * getLoginList 空,P2-1 实测)。\n *\n * 返回值形态可能是 JSON 字符串(对象/数组)或纯路径字符串,此处做宽容解析。\n */\n\nimport { join } from \"node:path\";\n\n/**\n * QQ 全局数据目录(commonPath / desktopGlobalPath = 数据根/nt_qq/global,\n * HANDOVER-V6 自建宿主三要素之三,p0-kernel-flow 实证)。\n *\n * 防御:已含 nt_qq/global 后缀(worker 环境下 getNTUserDataInfoConfig 可能\n * 返回完整路径)则不重复拼接,避免 `nt_qq/global/nt_qq/global` 错位。\n */\nexport function resolveQqGlobalPath(root: string): string {\n const norm = root.replace(/\\\\/g, \"/\");\n if (norm.endsWith(\"/nt_qq/global\")) {\n return root;\n }\n if (norm.endsWith(\"/nt_qq\")) {\n return join(root, \"global\");\n }\n return join(root, \"nt_qq\", \"global\");\n}\n\n/** 从 getNTUserDataInfoConfig 返回值提取数据根(JSON 字符串解析或纯路径)。 */\nexport function extractDataRoot(raw: string): string | null {\n if (raw.startsWith(\"{\") || raw.startsWith(\"[\")) {\n try {\n return findPathInValue(JSON.parse(raw));\n } catch {\n return null;\n }\n }\n return raw;\n}\n\n/** 递归在对象/数组里找含 QQ 数据路径特征的字符串值。 */\nfunction findPathInValue(value: unknown): string | null {\n if (typeof value === \"string\") {\n return isQqDataPath(value) ? value : null;\n }\n if (Array.isArray(value)) {\n return findInArray(value);\n }\n if (typeof value === \"object\" && value !== null) {\n return findInObject(value);\n }\n return null;\n}\n\n/** 判断字符串是否含 QQ 数据路径特征。 */\nfunction isQqDataPath(value: string): boolean {\n return value.includes(\"Tencent Files\") || value.includes(\"nt_qq\");\n}\n\n/** 在数组里递归查找。 */\nfunction findInArray(value: unknown[]): string | null {\n for (const item of value) {\n const found = findPathInValue(item);\n if (found !== null) {\n return found;\n }\n }\n return null;\n}\n\n/** 在对象值里递归查找。 */\nfunction findInObject(value: object): string | null {\n for (const v of Object.values(value)) {\n const found = findPathInValue(v);\n if (found !== null) {\n return found;\n }\n }\n return null;\n}\n","/**\n * wrapper 交互配置装配(纯函数,无状态)\n *\n * 生成 QQ wrapper 交互所需的三份配置:\n * - buildEngineConfig → engine.initWithDeskTopConfig(appid / qua / 版本 / 路径)\n * - buildLoginConfig → loginService.initConfig\n * - buildSessionConfig → session.init(登录成功后调用)\n *\n * 与 lifecycle.ts(流程编排)解耦:本模块只回答「配置长什么样」,不管流程怎么走。\n * 字段为 wrapper.node 外部契约(appid/qua/版本),运行时实测确认,自研描述。\n * appid 唯一来源是 major.node 动态解析(每版本不同);解析失败显式抛 KernelError,\n * 不再静默回退过期 appid(2026-08 硬编码审计:537237765 是 9.9.31 的 appid,该版本\n * 登录服务已被腾讯下线,静默用它会导致扫码「请下载最新版」且极难排查)。\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { hostname, type as osType, version as osVersionString, platform, release } from \"node:os\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n DeviceInfo,\n EnginInitDesktopConfig,\n WrapperSessionInitConfig,\n} from \"../types/index.js\";\nimport { PlatformType as PlatformTypeValue, VendorType } from \"../types/index.js\";\n\n/** app_type=4:桌面端应用类型(engine.initWithDeskTopConfig 契约;移动端为其它值)。 */\nconst APP_TYPE_DESKTOP = 4;\n\n/**\n * 缩略图生成参数(thumb_config,engine 契约):\n * maxSide 最长边像素上限 / minSide 最短边像素下限 / longLimit 长图最长短边比例上限 /\n * density 像素密度倍数。\n */\nconst THUMB_CONFIG = {\n maxSide: 324,\n minSide: 48,\n longLimit: 6,\n density: 2,\n} as const;\n\n/**\n * session.init 的 deviceConfig(wrapper 契约,JSON 字符串字面量):\n * appearance.isSplitViewMode 分屏模式开关,msg 空对象为占位。自建宿主无 UI,保持最小可用。\n */\nconst DEVICE_CONFIG_JSON = '{\"appearance\":{\"isSplitViewMode\":true},\"msg\":{}}';\n\n/** localId=2052:zh-CN(简体中文)locale 码(Windows LCID,deviceInfo.localId 契约)。 */\nconst ZH_CN_LOCALE_ID = 2052;\n\n/** 平台名映射:os.platform() → wrapper devType 契约值(不写死具体构建号)。 */\nconst DEV_TYPE_BY_PLATFORM: Record<string, string> = {\n win32: \"Windows\",\n darwin: \"Mac\",\n linux: \"Linux\",\n android: \"Android\",\n freebsd: \"FreeBSD\",\n openbsd: \"OpenBSD\",\n sunos: \"SunOS\",\n aix: \"AIX\",\n};\n\n/**\n * 系统信息运行时探测(wrapper 契约:platVer / osVersion / devType)。\n * 旧实现写死 \"Windows 10.0.22631\"/\"Windows 10 Pro\"——换机即失真(2026-08 硬编码审计整改)。\n * 探测失败时给通用值兜底,但不写死具体 Windows 构建号。\n */\nfunction systemInfo(): { platVer: string; osVersion: string; devType: string } {\n let devType = \"Unknown\";\n let platVer = \"Unknown\";\n let osVersion = \"Unknown\";\n try {\n devType = DEV_TYPE_BY_PLATFORM[platform()] ?? platform();\n // platVer 契约形如 \"Windows 10.0.22631\" = \"<平台名> <内核版本号>\"。\n platVer = `${devType} ${release()}`;\n // osVersion 优先 os.version()(Windows 返回 \"Windows 11 Pro\" 等),空则用 os.type()。\n osVersion = osVersionString() || osType();\n } catch {\n // 探测失败:保留通用兜底值。\n }\n return { platVer, osVersion, devType };\n}\n\n/** 纯数字串(major.node 提取的 appid 判定)。 */\nconst DIGITS_ONLY_RE = /^\\d+$/;\n\n/**\n * 从 major.node 提取 appid(NapCat parseAppidFromMajorV2 的自研等价实现)。\n * major.node 含 `QQAppId/` 标记后跟数字(腾讯工具链产物,实测确认)。\n * 返回 null 表示解析失败(调用方应显式报错,不再回退硬编码 appid)。\n */\nexport function parseAppidFromMajor(majorPath: string): string | null {\n if (!existsSync(majorPath)) {\n return null;\n }\n let buf: Buffer;\n try {\n buf = readFileSync(majorPath);\n } catch {\n return null;\n }\n return scanAppidMarkers(buf);\n}\n\n/** 扫描 buf 中所有 QQAppId/ 标记,返回第一个纯数字串(无则 null)。 */\nfunction scanAppidMarkers(buf: Buffer): string | null {\n const marker = Buffer.from(\"QQAppId/\", \"utf-8\");\n let pos = 0;\n while (pos < buf.length) {\n const idx = buf.indexOf(marker, pos);\n if (idx === -1) {\n return null;\n }\n const str = readMarkerValue(buf, idx + marker.length);\n if (DIGITS_ONLY_RE.test(str)) {\n return str;\n }\n pos = idx + marker.length + str.length + 1;\n }\n return null;\n}\n\n/** 读取标记后的 C 字符串(到首个 0 字节为止)。 */\nfunction readMarkerValue(buf: Buffer, start: number): string {\n let end = start;\n while (end < buf.length && buf[end] !== 0) {\n end += 1;\n }\n return buf.subarray(start, end).toString(\"utf-8\");\n}\n\n/**\n * 解析 appid / qua(自研,参考 NapCat QQBasicInfoWrapper 思路但独立实现):\n * appid 唯一来源 = major.node 提取(实测 9.9.33-51802 = 537376818)。\n * majorPath 缺省或解析失败时抛 KernelError——不再静默回退过期 appid。\n * majorPath 传 wrapper.node 同目录 major.node。\n */\nexport function resolveAppidQua(\n fullVersion: string,\n majorPath?: string,\n): { appid: string; qua: string } {\n const appid = majorPath !== undefined ? parseAppidFromMajor(majorPath) : null;\n if (appid === null) {\n throw kernelError(\n \"无法从 major.node 解析 appid,请确认 wrapper.node/major.node 完整,或更新 qq-releases.json\",\n \"INVALID_STATE\",\n );\n }\n return {\n appid,\n qua: `V1_WIN_NQ_${fullVersion}_${fullVersion.split(\"-\")[1] ?? \"\"}_GW_B`,\n };\n}\n\n/** 生成 engine 桌面配置(wrapper 契约字段)。majorPath 可选(缺失/解析失败会抛错)。 */\nexport function buildEngineConfig(\n fullVersion: string,\n dataPathGlobal: string,\n majorPath?: string,\n): EnginInitDesktopConfig {\n const { osVersion } = systemInfo();\n const { qua } = resolveAppidQua(fullVersion, majorPath);\n return {\n base_path_prefix: \"\",\n platform_type: PlatformTypeValue.KWINDOWS,\n app_type: APP_TYPE_DESKTOP,\n app_version: fullVersion,\n os_version: osVersion,\n use_xlog: true,\n qua,\n global_path_config: {\n desktopGlobalPath: dataPathGlobal,\n },\n thumb_config: THUMB_CONFIG,\n };\n}\n\n/** 生成登录初始化配置(externalVersion: false 与 NapCat 同款,扫码兼容关键)。 */\nexport function buildLoginConfig(\n appid: string,\n fullVersion: string,\n commonPath: string,\n): Record<string, unknown> {\n const { platVer } = systemInfo();\n return {\n machineId: \"\",\n appid,\n platVer,\n commonPath,\n clientVer: fullVersion,\n hostName: hostname(),\n externalVersion: false,\n };\n}\n\n/** buildSessionConfig 参数。 */\nexport interface SessionConfigOptions {\n appid: string;\n fullVersion: string;\n selfUin: string;\n selfUid: string;\n accountPath: string;\n downloadPath: string;\n /** 设备指纹 guid(来自 loginService.getMachineGuid();缺省空串)。 */\n machineGuid?: string;\n}\n\n/** 生成 session 初始化配置(登录成功后调用)。 */\nexport function buildSessionConfig(options: SessionConfigOptions): WrapperSessionInitConfig {\n const { appid, fullVersion, selfUin, selfUid, accountPath, downloadPath, machineGuid } =\n options;\n const { platVer, osVersion, devType } = systemInfo();\n const deviceInfo: DeviceInfo = {\n // 设备指纹 guid:loginService.getMachineGuid()(wrapper 探测确认,无 getMachineId 方法)。\n guid: machineGuid ?? \"\",\n buildVer: fullVersion,\n localId: ZH_CN_LOCALE_ID,\n devName: hostname(),\n devType,\n vendorName: \"\",\n osVer: osVersion,\n vendorOsName: devType,\n setMute: false,\n vendorType: VendorType.KNOSETONIOS,\n };\n return {\n selfUin,\n selfUid,\n desktopPathConfig: {\n account_path: accountPath,\n },\n clientVer: fullVersion,\n a2: \"\",\n d2: \"\",\n d2Key: \"\",\n machineId: \"\",\n platform: PlatformTypeValue.KWINDOWS,\n platVer,\n appid,\n rdeliveryConfig: {\n appKey: \"\",\n systemId: 0,\n appId: \"\",\n logicEnvironment: \"\",\n platform: PlatformTypeValue.KWINDOWS,\n language: \"\",\n sdkVersion: \"\",\n userId: \"\",\n appVersion: \"\",\n osVersion: \"\",\n bundleId: \"\",\n serverUrl: \"\",\n fixedAfterHitKeys: [\"\"],\n },\n defaultFileDownloadPath: downloadPath,\n deviceInfo,\n deviceConfig: DEVICE_CONFIG_JSON,\n };\n}\n\n/**\n * 读取设备指纹 guid(wrapper.node 字符串分析实证,2026-08-12):\n * NodeIKernelLoginService::getMachineGuid(0 参,返回机器 GUID 字符串)。\n * 无 getMachineId 方法——TODO 里的方法名不存在,等价方法是 getMachineGuid。\n * 失败(无该方法 / 调用抛错 / 返回值非字符串)兜底空串。\n */\nexport function readMachineGuid(loginService: unknown): string {\n try {\n const fn = (loginService as Record<string, unknown> | null | undefined)?.[\"getMachineGuid\"];\n if (typeof fn !== \"function\") {\n return \"\";\n }\n const value: unknown = fn.call(loginService);\n return typeof value === \"string\" ? value : \"\";\n } catch {\n return \"\";\n }\n}\n","/**\n * wrapper 引导(NAPI 范式,2026-08-05 重构,替代旧 koffi 方案)\n *\n * 事实(实测):wrapper.node 不是标准 NAPI self-register 模块,只能在 QQ 定制版\n * Electron 主进程里由 preload 注册。@napuketto/loader 注入 hook DLL 后,boot.cjs\n * 在 QQ 主进程内截获 `process.dlopen` 的 `module.exports`,然后调用本模块初始化。\n *\n * 本模块**不再**使用 koffi / vtable / 内存偏移——所有对象由 QQ 的 NAPI 层自动构建。\n * 业务层拿到的都是真实 JS 对象:engine.initWithDeskTopConfig(config, adapter) 等。\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n EnginInitDesktopConfig,\n NodeIKernelSessionListener,\n NodeIQQNTStartupSessionWrapper,\n NodeIQQNTWrapperEngine,\n NodeIQQNTWrapperSession,\n NodeIQQNTWrapperSessionCtor,\n WrapperNodeApi,\n WrapperSessionInitConfig,\n} from \"../types/index.js\";\nimport { PlatformType } from \"../types/index.js\";\nimport { extractDataRoot, resolveQqGlobalPath } from \"./qq-data-path.js\";\nimport {\n createSessionListener,\n DependsAdapter,\n DispatcherAdapter,\n GlobalAdapter,\n} from \"./wrapper-adapters.js\";\n\n// ---------------------------------------------------------------\n// startNapuketto 内部辅助(非 export,boot 装配用)\n// ---------------------------------------------------------------\n\n/** 默认 engine 配置(KWINDOWS + 版本号,字段待首个真实联调确认)。 */\nfunction defaultEngineConfig(env: BootEnv, qqDataPath?: string): EnginInitDesktopConfig {\n return {\n base_path_prefix: env.dataDir ?? \"\",\n platform_type: PlatformType.KWINDOWS,\n app_type: 4,\n app_version: env.qqVersion ?? \"\",\n os_version: process.platform,\n use_xlog: false,\n qua: \"\",\n global_path_config: {\n desktopGlobalPath: qqDataPath ?? env.dataDir ?? \"\",\n },\n thumb_config: { maxSide: 324, minSide: 48, longLimit: 6, density: 2 },\n };\n}\n\n/**\n * Electron 进程类型(Node 的 process 类型无此字段)。\n * - \"browser\":QQ 主进程(V1 模式)\n * - \"utility\":utilityProcess Worker(路线 B,2026-08-06)\n */\nexport function electronProcessType(): string | undefined {\n return (process as unknown as { type?: string }).type;\n}\n\n/**\n * 解析 QQ 用户数据根(登录数据目录,如 `C:\\Users\\<user>\\Documents\\Tencent Files`)。\n * 优先 NodeQQNTWrapperUtil.getNTUserDataInfoConfig()(QQ 官方 API),回退默认位置。\n *\n * 背景(2026-08-06 P2-1 实测):worker(utilityProcess)模式下 loginService 是\n * `new NodeIKernelLoginService()` 新建的,其 initConfig 的 commonPath 必须指向\n * QQ 真实数据路径才能读到历史账号——cli 的 `.napuketto\\default` 数据目录读不到\n * (getLoginList 返回空 → 只能 QR 登录)。V1 主进程模式 QQ 自己 initConfig 过\n * 正确路径,无需此解析。\n */\nexport function resolveQqUserDataRoot(exports: WrapperNodeApi): string | null {\n return extractUserDataRoot(exports) ?? join(homedir(), \"Documents\", \"Tencent Files\");\n}\n\n/** 从 QQ 官方 API 提取数据根(不可用返回 null,调用方回退默认)。 */\nfunction extractUserDataRoot(exports: WrapperNodeApi): string | null {\n try {\n const util = exports.NodeQQNTWrapperUtil as unknown as {\n get?: () => { getNTUserDataInfoConfig?: () => unknown };\n } | null;\n const raw = util?.get?.().getNTUserDataInfoConfig?.();\n if (typeof raw === \"string\" && raw.trim() !== \"\") {\n return extractDataRoot(raw.trim());\n }\n } catch {\n // 回退默认位置\n }\n return null;\n}\n\n// ---------------------------------------------------------------\n// 导出区(useExportsLast:export 全部在文件末尾)\n// ---------------------------------------------------------------\n\n/** QQ 版本信息(登录握手用)。 */\nexport interface QQVersionContext {\n fullVersion: string;\n buildVersion: string;\n}\n\n/** 已引导的 wrapper 上下文(运行在 QQ Electron 主进程内)。 */\nexport interface WrapperContext {\n /** wrapper.node 的 NAPI 顶层导出。 */\n exports: WrapperNodeApi;\n /** engine 单例(get() 已调用)。 */\n engine: NodeIQQNTWrapperEngine;\n /** 版本信息(供登录握手等使用)。 */\n versionInfo: QQVersionContext;\n /** 会话(QQ 拦截捕获 / createSession 后填充)。 */\n session: NodeIQQNTWrapperSession | null;\n /** 启动会话包装(NapCat 同款:StartupSessionWrapper.create(),start() 替代 startNT)。 */\n startupSession: NodeIQQNTStartupSessionWrapper | null;\n /** QQ 的 loginService 实例(拦截 new 捕获,可为 null)。 */\n loginService: unknown;\n}\n\n/**\n * 从 loader 截获的 NAPI exports 创建 wrapper 上下文。\n * 在 QQ 主进程内调用(boot.cjs → kernel 入口)。\n */\nexport function createWrapper(\n exports: WrapperNodeApi,\n versionInfo: QQVersionContext,\n): WrapperContext {\n const engineMaybe = exports.NodeIQQNTWrapperEngine as unknown as {\n get?: () => NodeIQQNTWrapperEngine;\n } | null;\n if (engineMaybe === null || typeof engineMaybe.get !== \"function\") {\n throw kernelError(\n \"wrapper.node exports 无效(缺少 NodeIQQNTWrapperEngine)\",\n \"INVALID_PARAM\",\n );\n }\n const engine = engineMaybe.get();\n if (!engine || typeof engine.initWithDeskTopConfig !== \"function\") {\n throw kernelError(\"NodeIQQNTWrapperEngine.get() 返回对象无效\", \"INVALID_PARAM\");\n }\n return {\n exports,\n engine,\n versionInfo,\n session: null,\n startupSession: null,\n loginService: null,\n };\n}\n\n/** engine 初始化(wrapper 契约:先 engine 后 session)。config 为普通 JS 对象。 */\nexport function initEngine(ctx: WrapperContext, config: EnginInitDesktopConfig): void {\n ctx.engine.initWithDeskTopConfig(config, new GlobalAdapter());\n}\n\n/**\n * 创建会话(2026-08-06 P2-0 实测修正——NapCat 方式自研等价):\n * 1. StartupSessionWrapper.create()(启动会话包装)\n * 2. getNTWrapperSession(\"nt_1\")(QQ 主 session,带完整 cpp_impl)\n * 3. S.create() 回退\n *\n * 背景:`new NodeIQQNTWrapperSession()` 构造对象缺 cpp_impl,session.init 断言\n * \"implementation of IQQNTWrapperSession is not valid\"(P2-0 实测确认)。\n * NapCat 用 StartupSessionWrapper.create() + getNTWrapperSession('nt_1') 解决——\n * 返回带完整实现的实例。worker(utilityProcess)继承 QQ env 后同样有效。\n */\nexport function createSession(ctx: WrapperContext): NodeIQQNTWrapperSession {\n const S = ctx.exports.NodeIQQNTWrapperSession;\n const session =\n createStartupSession(ctx) ?? getNTWrapperSession(S) ?? createNewSession(S) ?? new S();\n ctx.session = session;\n return session;\n}\n\n/** 步骤 1:StartupSessionWrapper.create()(建立启动 session,失败忽略)。 */\nfunction createStartupSession(ctx: WrapperContext): NodeIQQNTWrapperSession | null {\n const Ssw = ctx.exports.NodeIQQNTStartupSessionWrapper as unknown as\n | {\n create?: () => NodeIQQNTStartupSessionWrapper;\n }\n | undefined;\n if (Ssw === undefined || typeof Ssw.create !== \"function\") {\n return null;\n }\n try {\n ctx.startupSession = Ssw.create();\n } catch {\n // 可选,失败继续\n }\n return null;\n}\n\n/** 步骤 2:getNTWrapperSession(\"nt_1\")(QQ 主 session,带 cpp_impl)。 */\nfunction getNTWrapperSession(S: NodeIQQNTWrapperSessionCtor): NodeIQQNTWrapperSession | null {\n if (typeof S.getNTWrapperSession !== \"function\") {\n return null;\n }\n try {\n return S.getNTWrapperSession(\"nt_1\");\n } catch {\n return null; // 回退\n }\n}\n\n/** 步骤 3:S.create() 回退。 */\nfunction createNewSession(S: NodeIQQNTWrapperSessionCtor): NodeIQQNTWrapperSession | null {\n if (typeof S.create !== \"function\") {\n return null;\n }\n try {\n return S.create();\n } catch {\n return null;\n }\n}\n\n/** session 初始化(4 参全为 JS 对象,NAPI 自动转换)。 */\nexport function initSession(\n ctx: WrapperContext,\n config: WrapperSessionInitConfig,\n listener: NodeIKernelSessionListener,\n): void {\n const session = ctx.session ?? createSession(ctx);\n session.init(config, new DependsAdapter(), new DispatcherAdapter(), listener);\n}\n\n/**\n * 启动会话(有 startupSession 用 start(),否则 startNT(0))。\n *\n * ⚠️ 2026-08-07 V9 修正:单纯 start() 只在「已 init」的 session 上有效。\n * 自建宿主必须走 initAndStartSession(先 session.init 再 startupSession.start())——\n * 先 start 后 init 业务 service 不挂载(HANDOVER-V9 实测)。本函数保留给\n * 已 init 的 session 补启动用;新路径请用 initAndStartSession。\n */\nexport function startSession(ctx: WrapperContext): void {\n const { session, startupSession } = ctx;\n if (session === null) {\n throw kernelError(\"session 未创建,无法启动\", \"INVALID_STATE\");\n }\n if (startupSession !== null && typeof startupSession.start === \"function\") {\n startupSession.start();\n return;\n }\n session.startNT(0);\n}\n\n/**\n * boot 装配入口:createWrapper → initEngine → session。\n *\n * **session 来源(2026-08-05 修正)**:\n * 1. qqSession(boot.cjs 拦截 `new` 窃取的 QQ 已 init session)——尚未落地\n * 2. createSession(`new NodeIQQNTWrapperSession()`)——实测可用,默认路径\n * (getMainSession 实测为空壳:service 全 null + 缺 startNT,仅 probe 探测参考)\n *\n * 由 kernel NapukettoCore(core.ts)装配调用;loader 自建宿主 bootstrap 也直接调用作回退。\n * 返回 WrapperContext;失败抛 KernelError。\n */\nexport function startNapuketto(options: StartNapukettoOptions): WrapperContext {\n const { env, engineConfig, sessionConfig, wrapperExports, qqSession, qqLoginService } = options;\n const versionInfo: QQVersionContext = {\n fullVersion: env?.qqVersion ?? \"\",\n buildVersion: env?.qqVersion ?? \"\",\n };\n const ctx = createWrapper(wrapperExports, versionInfo);\n const hasCapturedSession = captureSession(ctx, qqSession);\n // session 创建时机:**自建宿主(标准 node)必须在 engine init 之前创建**——\n // p0-kernel-flow 决定性顺序(HANDOVER-V6):SSW.create + getNTWrapperSession(\"nt_1\")\n // 在 engine.initWithDeskTopConfig 前;engine 先建 session 后建 → 登录后\n // session.init 的 onOpentelemetryInit 不触发(2026-08-07 自建宿主实测)。\n // 路线 B(utility)与 QQ 主进程(browser)保持 engine 先建原顺序(已验证)。\n const isSelfHost = electronProcessType() === undefined;\n prepareSession(ctx, hasCapturedSession, isSelfHost);\n initEngine(ctx, resolveEngineConfig(ctx, env, engineConfig));\n resolveLoginService(ctx, qqLoginService);\n // session:路线 B(utility)与 QQ 主进程(browser)在 engine 后创建;\n // 自建宿主已在 engine 前创建(isSelfHost 分支),此处跳过避免重复。\n finishSession(ctx, hasCapturedSession, isSelfHost, sessionConfig);\n return ctx;\n}\n\n/** engine 前 session 准备(自建宿主必须先建,顺序决定性)。 */\nfunction prepareSession(ctx: WrapperContext, hasCaptured: boolean, isSelfHost: boolean): void {\n if (!hasCaptured && isSelfHost) {\n createSession(ctx);\n }\n}\n\n/** engine 后收尾:非自建宿主补建 session;配置了 sessionConfig 则 init+start。 */\nfunction finishSession(\n ctx: WrapperContext,\n hasCaptured: boolean,\n isSelfHost: boolean,\n sessionConfig: WrapperSessionInitConfig | undefined,\n): void {\n if (!hasCaptured && !isSelfHost) {\n createSession(ctx);\n }\n if (sessionConfig !== undefined) {\n initSession(ctx, sessionConfig, createSessionListener());\n startSession(ctx);\n }\n}\n\n/** engine 配置:显式覆盖优先,否则默认(含 QQ 数据根 global 路径解析)。 */\nfunction resolveEngineConfig(\n ctx: WrapperContext,\n env: BootEnv | undefined,\n engineConfig: EnginInitDesktopConfig | undefined,\n): EnginInitDesktopConfig {\n if (engineConfig !== undefined) {\n return engineConfig;\n }\n return defaultEngineConfig(env ?? {}, resolveBootQqGlobalPath(ctx));\n}\n\n/** 捕获 QQ 已 init 的 session(boot.cjs 拦截 `new` 窃取),返回是否捕获成功。 */\nfunction captureSession(\n ctx: WrapperContext,\n qqSession: NodeIQQNTWrapperSession | null | undefined,\n): boolean {\n if (qqSession === undefined || qqSession === null) {\n return false;\n }\n ctx.session = qqSession;\n return true;\n}\n\n/**\n * 解析 QQ 真实数据目录的 global 路径(desktopGlobalPath 三要素之三)。\n * worker(utilityProcess)模式 + 自建宿主(标准 node)下必须指向 QQ 真实\n * 数据目录(数据根/nt_qq/global,HANDOVER-V6),否则登录数据读写错位\n * (P2-1 实测 2026-08-06;getLoginList 空)。QQ 主进程(V1)QQ 自己已配置\n * engine,无需解析。\n */\nfunction resolveBootQqGlobalPath(ctx: WrapperContext): string | undefined {\n if (electronProcessType() === \"browser\") {\n return undefined;\n }\n const root = resolveQqUserDataRoot(ctx.exports);\n if (root === null) {\n return undefined;\n }\n return resolveQqGlobalPath(root);\n}\n\n/**\n * 解析 loginService:优先捕获实例,其次单例 get()(自建宿主/worker 下 QQ 环境\n * 创建的实例才完整——`new` 自建实例读不到登录列表(getLoginList 空),\n * HANDOVER-V6 p0-login3 实证),最后 new 回退。\n */\nfunction resolveLoginService(ctx: WrapperContext, qqLoginService: unknown): void {\n if (qqLoginService !== undefined && qqLoginService !== null) {\n ctx.loginService = qqLoginService as WrapperContext[\"loginService\"];\n return;\n }\n ctx.loginService = getOrCreateLoginService(ctx.exports);\n}\n\n/** 单例 get() 优先,缺失回退 new(都失败返回 null)。 */\nfunction getOrCreateLoginService(exports: WrapperNodeApi): unknown {\n try {\n const loginSvcCtor = exports.NodeIKernelLoginService as unknown as {\n get?: () => unknown;\n } | null;\n if (loginSvcCtor !== null && typeof loginSvcCtor.get === \"function\") {\n return loginSvcCtor.get();\n }\n return new exports.NodeIKernelLoginService();\n } catch {\n return null;\n }\n}\n\n/** startNapuketto 的引导环境(由 loader launcher 注入环境变量,boot.cjs 透传)。 */\nexport interface BootEnv {\n /** QQ 版本(如 \"9.9.31-49919\")。 */\n qqVersion?: string;\n /** 用户数据目录(cfgDir 的父级,用于 desktopPathConfig)。 */\n dataDir?: string;\n /** 账号 uin(可选,提供则一步 init+start)。 */\n selfUin?: string;\n /** 账号 uid(可选)。 */\n selfUid?: string;\n}\n\n/** startNapuketto 选项。 */\nexport interface StartNapukettoOptions {\n /** 截获的 wrapper.node NAPI exports(boot.cjs 传入)。 */\n wrapperExports: WrapperNodeApi;\n /** 引导环境(版本/数据目录/账号)。 */\n env?: BootEnv;\n /** 覆盖 engine 配置(联调用)。 */\n engineConfig?: EnginInitDesktopConfig;\n /** 覆盖 session 配置(联调用,提供则自动 init+start)。 */\n sessionConfig?: WrapperSessionInitConfig;\n /** QQ 已 init 的 session(boot.cjs 拦截 new 捕获)。 */\n qqSession?: NodeIQQNTWrapperSession | null;\n /** QQ 的 loginService 实例(boot.cjs 拦截 new 捕获)。 */\n qqLoginService?: unknown;\n}\n","/**\n * NapukettoCore:装配 + 启动(§9 第 6 项,2026-08-05 实现)\n *\n * 装配:paths(建目录)→ logger → CoreContext。\n * 启动:attachWrapper(wrapperExports → startNapuketto)→ login(快速登录 → session.init)。\n *\n * 典型用法(boot.cjs 截获 exports 后):\n * const core = await NapukettoCore.create({ dataRoot, account });\n * core.attachWrapper(wrapperExports, { qqVersion, dataDir });\n * // appid 由 resolveAppidQua 从 major.node 动态解析(版本绑定,不写死)\n * await core.login({ appid });\n *\n * 与手动拼 startNapuketto/quickLogin/initAndStartSession 等价,但把装配状态\n * (ctx)集中管理,协议层直接消费 core.ctx。\n */\n\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { type CoreContext, createCoreContext } from \"./context.js\";\nimport {\n createLogger,\n kernelError,\n type LogFormat,\n type LogLevel,\n type PathOptions,\n PathWrapper,\n type TimestampMode,\n} from \"./infra/index.js\";\nimport {\n type LoginResult,\n type LoginState,\n type QrCodeData,\n QrLoginSession,\n type QuickLoginOptions,\n quickLogin,\n type SelfInfo,\n} from \"./login/index.js\";\nimport type { NodeIQQNTWrapperSession, WrapperNodeApi } from \"./types/index.js\";\nimport { resolveQqGlobalPath } from \"./wrapper/qq-data-path.js\";\nimport { buildLoginConfig } from \"./wrapper/wrapper-config.js\";\nimport {\n type BootEnv,\n electronProcessType,\n resolveQqUserDataRoot,\n startNapuketto,\n type WrapperContext,\n} from \"./wrapper/wrapper-loader.js\";\n\nexport interface NapukettoCoreOptions {\n /** 数据根 / 账号(透传给 PathWrapper,见 ADR-016)。 */\n paths?: PathOptions;\n /** 日志级别,默认 info。 */\n logLevel?: LogLevel | undefined;\n /** 文件日志路径;不传则默认 `logs/napuketto.log`(父目录自动创建)。 */\n logFile?: string | undefined;\n /** 文件目的地级别(可低于控制台级别;缺省同 logLevel)。 */\n logFileLevel?: LogLevel | undefined;\n /** 是否输出 console,默认 true。 */\n consoleLog?: boolean | undefined;\n /** 控制台格式(缺省按 TTY 自动:TTY pretty / pipe single-line)。 */\n logFormat?: LogFormat | undefined;\n /** 时间戳渲染模式,默认 local。 */\n logTimestamp?: TimestampMode | undefined;\n /** 控制台颜色(缺省自动)。 */\n logColorize?: boolean | undefined;\n /** 账号标注(记录结构化 account 字段;快速登录目标账号,登录前已知时传入)。 */\n account?: string | undefined;\n}\n\n/** 登录进度(IPC 子进程模式经 onLoginProgress 转发;koishi 插件展示二维码/状态用)。 */\nexport interface LoginProgress {\n /** 登录状态(QR 状态机)。 */\n state: LoginState;\n /** 二维码数据(state=waiting_scan 时有)。 */\n qr?: QrCodeData;\n /** 登录成功 selfInfo(state=logged_in 时有)。 */\n selfInfo?: SelfInfo;\n /** 失败原因(state=failed 时有;如「登录超时,请刷新页面重试」)。 */\n message?: string;\n}\n\n/** login 参数。 */\nexport interface CoreLoginOptions {\n /** appid(登录握手,由 resolveAppidQua 从 major.node 动态解析,不写死)。 */\n appid: string;\n /** session.init 超时(毫秒),默认 15s。 */\n initTimeoutMs?: number;\n /** 指定快速登录账号(缺省遍历历史列表)。 */\n quickUin?: string;\n /**\n * 单步快速登录超时(毫秒,T3 软重登挂起兜底),默认 20s。服务端已有同账号\n * 会话时底层 quickLoginWithUin/getLoginList 永久挂起,超时按登录失败抛出\n * (qrFallback 可接)。底层 promise 无法取消:超时后悬挂丢弃。\n */\n quickLoginTimeoutMs?: number;\n /** 快速登录失败时回退 QR 登录(二维码写缓存目录),默认 false。 */\n qrFallback?: boolean;\n /** 强制扫码:跳过快速登录,直接走 QR 登录(koishi 强制扫码 / 切账号)。默认 false。 */\n qrOnly?: boolean;\n /** QR 二维码图片保存路径(默认缓存目录 qrcode.png)。 */\n qrCodePath?: string;\n /** 登录进度回调(QR 阶段:二维码数据 + 状态变化)。子进程 IPC 模式转发用。 */\n onLoginProgress?: (progress: LoginProgress) => void;\n}\n\n/**\n * 核心装配器:持有 CoreContext,负责启动/停止编排。\n * 无全局单例(ADR-015 推论)——每账号每进程实例化一份。\n */\nexport class NapukettoCore {\n /** 装配根(协议层 / apis / cache 的消费入口)。 */\n readonly ctx: CoreContext;\n\n /** QR 登录会话句柄(登录期间非空;登录终态后清空)。refreshQr() 据此可达。 */\n private qrSession: QrLoginSession | null = null;\n\n /** 由 create() 装配,不直接 new。 */\n private constructor(ctx: CoreContext) {\n this.ctx = ctx;\n }\n\n /**\n * 装配:paths(建目录)→ logger → CoreContext。\n * 不加载 wrapper / 不登录——启动阶段由 attachWrapper + login 完成。\n */\n static create(opts: NapukettoCoreOptions = {}): NapukettoCore {\n const paths = new PathWrapper(opts.paths);\n paths.ensure();\n // base.name 用 pino 保留字段(logger name):pino-pretty 渲染为 (kernel/pid)\n // 元数据头,不占属性行;cli/loader 同款,三来源可区分。account 未知时\n // 序列化层自然丢弃 undefined,无需条件展开。\n const logger = createLogger({\n level: opts.logLevel ?? \"info\",\n console: opts.consoleLog ?? true,\n file: opts.logFile ?? paths.file(\"logs\", \"napuketto.log\"),\n fileLevel: opts.logFileLevel,\n format: opts.logFormat,\n timestamp: opts.logTimestamp,\n colorize: opts.logColorize,\n base: { name: \"kernel\", account: opts.account },\n });\n return new NapukettoCore(createCoreContext({ logger, paths }));\n }\n\n /**\n * 装配 wrapper:wrapperExports → startNapuketto(engine.init + session 创建),\n * 结果挂到 ctx.wrapper。在 QQ 主进程内调用(boot.cjs 截获 exports 后)。\n * captured:boot.cjs 拦截 `new` 捕获的 QQ 实例(session/loginService,优先使用)。\n */\n attachWrapper(\n wrapperExports: WrapperNodeApi,\n env?: BootEnv,\n captured?: { qqSession?: NodeIQQNTWrapperSession | null; qqLoginService?: unknown },\n ): WrapperContext {\n let wrapper: WrapperContext;\n if (env === undefined) {\n wrapper = startNapuketto({ wrapperExports, ...captured });\n } else {\n wrapper = startNapuketto({ wrapperExports, env, ...captured });\n }\n this.ctx.wrapper = wrapper;\n this.ctx.logger.info({ version: env?.qqVersion }, \"wrapper 装配完成(engine + session)\");\n return wrapper;\n }\n\n /**\n * 登录 + session 初始化(wrapper 契约,2026-08-05 实测确认):\n * loginService.initConfig → 登录(快速/QR)→ **登录成功后** create session + init\n * → 等 onOpentelemetryInit(is_init) 完成信号。成功后填 ctx.login。\n *\n * 关键(实测):登录前 create()/init 均断言失败(session 未 init / 干扰 QQ);\n * 登录后 QQ 自己 init 并触发 listener.onOpentelemetryInit——我们只需在登录后\n * create + init 并监听该信号。\n */\n async login(opts: CoreLoginOptions): Promise<LoginResult> {\n const { wrapper } = this.ctx;\n if (wrapper === null) {\n throw kernelError(\"wrapper 未装配,无法登录\", \"INVALID_STATE\");\n }\n\n // 1. loginService.initConfig(wrapper 流程:addKernelLoginListener 前)\n this.initLoginConfig(wrapper, opts.appid);\n\n // 强制扫码(qrOnly):跳过快速登录,直接 QR 登录(koishi 强制扫码 / 切账号)。\n if (opts.qrOnly === true) {\n const loginResult = await this.loginByQr(opts);\n this.ctx.login = loginResult;\n return loginResult;\n }\n\n // 2. 登录:快速登录(优先)→ QR 回退。登录成功前不碰 session。\n const loginResult = await this.loginQuickFirst(wrapper, opts);\n\n // 3. 登录成功即返回。QQ 登录后重建 session(Proxy 捕获新实例),由 boot.cjs\n // 登录成功后 setSession 替换,再自行等待 session 就绪(waitSessionReady)。\n // 不在 login 内等待——替换发生在 login 返回之后,这里等不到。\n this.ctx.login = loginResult;\n return loginResult;\n }\n\n /** 快速登录优先,失败按 qrFallback 决定是否回退 QR(QR 应直接出码,见 loginByQr 注)。 */\n private async loginQuickFirst(\n wrapper: WrapperContext,\n opts: CoreLoginOptions,\n ): Promise<LoginResult> {\n try {\n const result = await quickLogin(wrapper, buildQuickLoginOptions(opts));\n this.ctx.logger.info({ uin: result.uin, uid: result.uid }, \"快速登录成功\");\n return result;\n } catch (err) {\n if (opts.qrFallback !== true) {\n throw err;\n }\n return await this.loginByQr(opts);\n }\n }\n\n /**\n * loginService.initConfig(worker/自建宿主需指定 QQ 真实数据路径)。\n * worker(utilityProcess)模式 + 自建宿主(标准 node)下 loginService 是\n * new 的,commonPath 必须指向 QQ 真实数据目录(数据根/nt_qq/global,\n * HANDOVER-V6 三要素之三)才能读到历史账号——cli 的 `.napuketto\\default`\n * 读不到(getLoginList 空,P2-1 实测 2026-08-06)。\n * QQ 主进程(V1)模式 QQ 自己 initConfig 过正确路径,无需解析。\n */\n private initLoginConfig(wrapper: WrapperContext, appid: string): void {\n const loginService = wrapper.loginService as {\n initConfig?: (config: unknown) => void;\n } | null;\n if (loginService === null || typeof loginService.initConfig !== \"function\") {\n this.ctx.logger.warn(\"loginService 不可用,跳过 initConfig\");\n return;\n }\n const commonPath = resolveCommonPath(this.ctx, wrapper);\n const loginCfg = buildLoginConfig(appid, wrapper.versionInfo.fullVersion, commonPath);\n loginService.initConfig(loginCfg);\n this.ctx.logger.info({ commonPath }, \"loginService.initConfig OK\");\n }\n\n /**\n * 替换当前 session(登录后 Proxy 捕获的 QQ 新实例替换自己 create 的无效实例)。\n * 在 QQ 主进程内由 boot.cjs 调用(登录成功后)。\n */\n setSession(session: NodeIQQNTWrapperSession): void {\n if (this.ctx.wrapper === null) {\n throw kernelError(\"wrapper 未装配,无法设置 session\", \"INVALID_STATE\");\n }\n this.ctx.wrapper.session = session;\n }\n\n /**\n * 手动刷新二维码(QR 登录期间可用)。返回是否成功触发(false = 当前不在扫码态)。\n * koishi 前端「刷新二维码」按钮经 loader IPC `login.refreshQr` 动作直达。\n */\n refreshQr(): boolean {\n if (this.qrSession === null) {\n return false;\n }\n this.qrSession.refresh();\n return true;\n }\n\n /** QR 登录:QrLoginSession 状态机 + 二维码写缓存目录(无 UI,cli 可打印路径)。 */\n private async loginByQr(opts: CoreLoginOptions): Promise<LoginResult> {\n const { wrapper } = this.ctx;\n if (wrapper === null || wrapper.loginService === null) {\n throw kernelError(\"wrapper/loginService 不可用,无法 QR 登录\", \"INVALID_STATE\");\n }\n const session = new QrLoginSession(wrapper.loginService);\n // 暴露句柄:登录期间外部可经 refreshQr() 手动刷新二维码(koishi 前端刷新按钮)。\n this.qrSession = session;\n const qrPath = opts.qrCodePath ?? this.ctx.paths.file(\"cache\", \"qrcode.png\");\n this.subscribeQrProgress(session, opts, qrPath);\n\n try {\n // 启动 QR 登录(注册监听 → connect → getQRCodePicture)\n // ⚠️ 不传 quickUin(2026-08-13):QR 回退路径下快速登录已在 core.login\n // 失败过一次,这里再传会导致 QrLoginSession 二次快速登录——无凭据环境\n // 直接 resolve 带 errMsg,白等一个周期才出二维码。QR 登录应直接出码。\n session.start();\n\n await waitQrLoggedIn(session);\n const self = requireQrSelfInfo(session);\n return { uin: self.uin, uid: self.uid, nick: self.nick };\n } finally {\n session.stop();\n this.qrSession = null;\n }\n }\n\n /** 订阅 QR 进度:二维码写盘 + 状态机经 onLoginProgress 转发。 */\n private subscribeQrProgress(\n session: QrLoginSession,\n opts: CoreLoginOptions,\n qrPath: string,\n ): void {\n session.onQrCode((qr) => {\n this.ctx.logger.warn(`请扫描二维码登录(保存: ${qrPath} | URL: ${qr.qrcodeUrl})`);\n if (qr.pngBase64 !== \"\") {\n writeQrCodePng(qrPath, qr.pngBase64);\n }\n opts.onLoginProgress?.({ state: \"waiting_scan\", qr });\n });\n session.onStateChange((state) => {\n this.forwardQrState(session, opts, state);\n });\n }\n\n /** QR 状态转发:日志 + onLoginProgress(failed 附 failureReason)。 */\n private forwardQrState(\n session: QrLoginSession,\n opts: CoreLoginOptions,\n state: LoginState,\n ): void {\n this.ctx.logger.info({ state }, \"QR 登录状态\");\n opts.onLoginProgress?.(qrProgressEvent(state, session.failureReason ?? undefined));\n }\n\n /** 停止:日志收尾(资源清理留给 P2 常驻管理)。 */\n stop(): void {\n this.ctx.logger.info(\"NapukettoCore 停止\");\n }\n}\n\n/** 写二维码 png 到磁盘(base64 解码,供 cli/用户扫码)。 */\nfunction writeQrCodePng(filePath: string, pngBase64: string): void {\n try {\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, Buffer.from(pngBase64, \"base64\"));\n } catch {\n // 写失败不阻塞登录(cli 仍可打印 URL)\n }\n}\n\n/** CoreLoginOptions → quickLogin 参数(仅取快速登录相关两键,QR 键不透传)。 */\nfunction buildQuickLoginOptions(opts: CoreLoginOptions): QuickLoginOptions {\n const quickOpts: QuickLoginOptions = {};\n if (opts.quickUin !== undefined) {\n quickOpts.uin = opts.quickUin;\n }\n if (opts.quickLoginTimeoutMs !== undefined) {\n quickOpts.quickLoginTimeoutMs = opts.quickLoginTimeoutMs;\n }\n return quickOpts;\n}\n\n/** QR 登录终态后的 selfInfo 提取(空值按登录失败抛出)。 */\nfunction requireQrSelfInfo(session: QrLoginSession): SelfInfo {\n const self = session.selfInfo;\n if (self === null) {\n throw kernelError(\"QR 登录成功但 selfInfo 为空\", \"INVALID_STATE\");\n }\n return self;\n}\n\n/** QR 进度事件折叠(failed 附 failureReason 为 message,其余不带 message 键)。 */\nfunction qrProgressEvent(state: LoginState, reason: string | undefined): LoginProgress {\n const message = state === \"failed\" ? reason : undefined;\n return { state, ...(message !== undefined ? { message } : {}) };\n}\n\n/** 等待 QR 登录终态:logged_in resolve,failed reject。 */\nfunction waitQrLoggedIn(session: QrLoginSession): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const offState = session.onStateChange((state) => {\n if (state === \"logged_in\") {\n offState();\n resolve();\n } else if (state === \"failed\") {\n offState();\n reject(kernelError(\"QR 登录失败\", \"NOT_LOGIN\"));\n }\n });\n });\n}\n\n/**\n * loginService.initConfig 的 commonPath:\n * worker/自建宿主(非 browser)优先 QQ 真实数据目录(数据根/nt_qq/global),\n * 否则回退账号目录。\n */\nfunction resolveCommonPath(ctx: CoreContext, wrapper: WrapperContext): string {\n if (electronProcessType() !== \"browser\") {\n const root = resolveQqUserDataRoot(wrapper.exports);\n if (root !== null) {\n return resolveQqGlobalPath(root);\n }\n }\n return ctx.paths.accountDir;\n}\n","/**\n * session 解析(2026-08-05,wrapper 机制实测确认后)\n *\n * - getMainSession:startup.create → getSessionIdList → getNTWrapperSession(nt_x)\n * (实测这些返回的 session 是空壳——核心 service 未 init,仅作参考)\n * - 真正可用的 session 是 `new wrapper.NodeIQQNTWrapperSession()` + init(见 lifecycle.ts)\n */\n\nimport type { NodeIQQNTWrapperSession } from \"../types/index.js\";\nimport type { WrapperContext } from \"./wrapper-loader.js\";\n\n/** 从 getSessionIdList 的 Map 提取主 sessionId(nt_ 前缀优先)。 */\nfunction findMainSessionId(ids: Map<unknown, unknown>): string | null {\n return firstStringId(ids, (s) => s.startsWith(\"nt_\")) ?? firstStringId(ids, () => true);\n}\n\n/** 遍历 Map 找第一个满足谓词的字符串 id(值优先,其次键)。 */\nfunction firstStringId(ids: Map<unknown, unknown>, pred: (s: string) => boolean): string | null {\n for (const [k, v] of ids) {\n if (typeof v === \"string\" && pred(v)) {\n return v;\n }\n if (typeof k === \"string\" && pred(k)) {\n return k;\n }\n }\n return null;\n}\n\n/** 通过 getNTWrapperSession 拿主 session(内部辅助)。 */\nfunction resolveMainSession(\n created: { start?: () => void; getSessionIdList?: () => unknown },\n getNTWrapperSession: (id: string) => NodeIQQNTWrapperSession,\n): NodeIQQNTWrapperSession | null {\n created.start?.();\n const ids = typeof created.getSessionIdList === \"function\" ? created.getSessionIdList() : null;\n if (!(ids instanceof Map)) {\n return null;\n }\n const mainId = findMainSessionId(ids);\n if (mainId === null) {\n return null;\n }\n const maybe = getNTWrapperSession(mainId) as NodeIQQNTWrapperSession | null | undefined;\n return maybe !== null && maybe !== undefined && typeof maybe.getMsgService === \"function\"\n ? maybe\n : null;\n}\n\n/**\n * 复用 QQ 已有 session(P1-4:QQ 已登录,直接拿单例避免重复 init)。\n * 优先 `NodeIQQNTWrapperSession.get()`;返回 null 时回退 createSession。\n */\nexport function getExistingSession(ctx: WrapperContext): NodeIQQNTWrapperSession | null {\n const got = trySessionGet(ctx);\n if (got !== null) {\n ctx.session = got;\n return got;\n }\n return null;\n}\n\n/** NodeIQQNTWrapperSession.get() 单例(无效/异常返回 null)。 */\nfunction trySessionGet(ctx: WrapperContext): NodeIQQNTWrapperSession | null {\n try {\n const S = ctx.exports.NodeIQQNTWrapperSession as unknown as {\n get?: () => NodeIQQNTWrapperSession;\n };\n if (typeof S.get !== \"function\") {\n return null;\n }\n const got = S.get();\n return got && typeof got.getMsgService === \"function\" ? got : null;\n } catch {\n return null; // 复用失败,回退 create\n }\n}\n\n/**\n * 复用 QQ 主 session(P1-4 实测链路):\n * `NodeIQQNTStartupSessionWrapper.create()` → `start()` → `getSessionIdList()`(Map\n * {nt:\"nt_3\", gpro:\"gpro_3\"})→ `NodeIQQNTWrapperSession.getNTWrapperSession(\"nt_3\")`。\n * 注:实测这些 session 是空壳(核心 service null),真正可用的是 new + init(lifecycle.ts)。\n */\nexport function getMainSession(ctx: WrapperContext): NodeIQQNTWrapperSession | null {\n try {\n return resolveMainFromStartup(ctx) ?? null;\n } catch {\n return null; // 复用失败,回退 create\n }\n}\n\n/** startup.create → start → getSessionIdList → getNTWrapperSession 链路。 */\nfunction resolveMainFromStartup(ctx: WrapperContext): NodeIQQNTWrapperSession | null {\n const startupRaw = ctx.exports.NodeIQQNTStartupSessionWrapper as unknown as {\n create?: () => { start?: () => void; getSessionIdList?: () => unknown } | null;\n };\n const sessionCtor = ctx.exports.NodeIQQNTWrapperSession as unknown as {\n getNTWrapperSession?: (id: string) => NodeIQQNTWrapperSession;\n };\n if (\n typeof startupRaw.create !== \"function\" ||\n typeof sessionCtor.getNTWrapperSession !== \"function\"\n ) {\n return null;\n }\n const created = startupRaw.create();\n if (!created) {\n return null;\n }\n const getSession = (id: string): NodeIQQNTWrapperSession =>\n (sessionCtor.getNTWrapperSession as (name: string) => NodeIQQNTWrapperSession)(id);\n const session = resolveMainSession(created, getSession);\n if (session !== null) {\n ctx.session = session;\n }\n return session;\n}\n","/**\n * 运行时反射探测的序列化工具(从 probe.ts 拆分,2026-08-08 FTA 优化)\n *\n * - serialize:深度受限序列化(BigInt 转字符串,Map/Set/Promise 展开,循环引用防护)\n * - tryShape:解析 service getter 返回形状\n */\n\n/** 序列化深度上限(循环引用/深层对象防护)。 */\nconst MAX_SERIALIZE_DEPTH = 4;\n/** 数组序列化条数上限。 */\nconst MAX_ARRAY_ITEMS = 20;\n/** 对象键序列化条数上限。 */\nconst MAX_OBJECT_KEYS = 50;\n\n/** 序列化一个值(BigInt 转字符串,Map/Set/Promise 展开,循环引用防护)。 */\nexport function serialize(value: unknown, depth = 0): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value === \"bigint\") return value.toString();\n if (typeof value === \"function\") return `[function ${value.name ?? \"anon\"}]`;\n if (typeof value !== \"object\") return value;\n if (depth > MAX_SERIALIZE_DEPTH) return \"[depth-limit]\";\n try {\n return serializeContainer(value, depth);\n } catch {\n return \"[unserializable]\";\n }\n}\n\n/** 序列化容器对象(数组 / Map / Set / Promise / 普通对象)。 */\nfunction serializeContainer(value: object, depth: number): unknown {\n if (Array.isArray(value)) {\n return serializeArray(value, depth);\n }\n if (value instanceof Map) {\n return serializeMap(value, depth);\n }\n if (value instanceof Set) {\n return serializeSet(value, depth);\n }\n if (typeof (value as Promise<unknown>).then === \"function\") {\n return \"[Promise]\";\n }\n return serializeObject(value, depth);\n}\n\n/** 数组序列化(条数截断)。 */\nfunction serializeArray(value: unknown[], depth: number): unknown[] {\n return value.slice(0, MAX_ARRAY_ITEMS).map((v) => serialize(v, depth + 1));\n}\n\n/** Map 序列化(键转字符串,条目截断)。 */\nfunction serializeMap(value: Map<unknown, unknown>, depth: number): unknown {\n const out: Record<string, unknown> = {};\n let i = 0;\n for (const [k, v] of value) {\n if (i >= MAX_OBJECT_KEYS) break;\n out[String(k)] = serialize(v, depth + 1);\n i += 1;\n }\n return { kind: \"Map\", size: value.size, entries: out };\n}\n\n/** Set 序列化(值截断)。 */\nfunction serializeSet(value: Set<unknown>, depth: number): unknown {\n return {\n kind: \"Set\",\n size: value.size,\n values: [...value].slice(0, MAX_ARRAY_ITEMS).map((v) => serialize(v, depth + 1)),\n };\n}\n\n/** 普通对象序列化(键截断)。 */\nfunction serializeObject(value: object, depth: number): unknown {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value).slice(0, MAX_OBJECT_KEYS)) {\n out[key] = serialize((value as Record<string, unknown>)[key], depth + 1);\n }\n return out;\n}\n\n/** 尝试解析一个 service getter 的返回值形状(深度受限)。 */\nexport function tryShape(session: unknown, getter: string): unknown {\n try {\n const fn = (session as Record<string, unknown>)[getter];\n if (typeof fn !== \"function\") return null;\n const value = fn.call(session);\n if (value === null || value === undefined) return null;\n return serialize(value);\n } catch {\n return \"[error]\";\n }\n}\n","/**\n * 运行时反射探测基础工具(从 probe.ts 拆分,2026-08-08 FTA 优化)\n *\n * - listMethods:反射枚举对象原型链上的方法名(去重、去构造器)\n * - tryCall:安全调用方法拿返回值(失败记录错误,不中断探测)\n */\n\n/** 反射枚举一个对象原型链上的方法名(去重、去构造器)。 */\nexport function listMethods(obj: unknown): string[] {\n if (obj === null || obj === undefined) {\n return [];\n }\n const names = new Set<string>();\n collectPrototypeNames(obj, names);\n collectOwnNames(obj, names);\n return [...names].sort();\n}\n\n/** 收集原型链上的属性名(到 Object/Function.prototype 为止,去构造器)。 */\nfunction collectPrototypeNames(obj: unknown, names: Set<string>): void {\n let proto = Object.getPrototypeOf(obj);\n while (isProbeProto(proto)) {\n for (const name of Object.getOwnPropertyNames(proto)) {\n if (name !== \"constructor\") {\n names.add(name);\n }\n }\n proto = Object.getPrototypeOf(proto);\n }\n}\n\n/** 是否仍需遍历的原型(非 Object/Function.prototype 且非空)。 */\nfunction isProbeProto(proto: object | null): proto is object {\n return proto !== null && proto !== Object.prototype && proto !== Function.prototype;\n}\n\n/** 收集自有属性名(去构造器)。 */\nfunction collectOwnNames(obj: unknown, names: Set<string>): void {\n for (const name of Object.getOwnPropertyNames(obj)) {\n if (name !== \"constructor\") {\n names.add(name);\n }\n }\n}\n\n/** 安全调用:尝试调用方法拿返回值,失败记录错误(不中断探测)。 */\nexport function tryCall(\n obj: unknown,\n name: string,\n): { ok: boolean; value?: unknown; error?: string } {\n try {\n const fn = (obj as Record<string, unknown>)[name];\n if (typeof fn !== \"function\") {\n return { ok: false, error: `not a function (typeof=${typeof fn})` };\n }\n const value = fn.call(obj);\n return { ok: true, value };\n } catch (e) {\n let message = String(e);\n if (e instanceof Error) {\n message = e.message;\n }\n return { ok: false, error: message };\n }\n}\n\n/** 服务 getter 判定(get*Service 命名约定)。 */\nexport function isServiceGetter(name: string): boolean {\n return name.startsWith(\"get\") && name.endsWith(\"Service\");\n}\n","/**\n * wrapper exports 探测(从 probe.ts 拆分,2026-08-08 FTA 优化)\n *\n * - probeExportCtors:关键 export 构造器方法面\n * - probeEngineCalls:engine 关键调用\n * - probeLoginService:LoginService 实例(QQ 已登录凭据入口)\n */\n\nimport type { WrapperContext } from \"../wrapper-loader.js\";\nimport { serialize, tryShape } from \"./probe-serialize.js\";\nimport { listMethods, tryCall } from \"./probe-utils.js\";\n\n/** 探测关键 export 构造器方法(get/create/getNTWrapperSession 等)。 */\nexport function probeExportCtors(ctx: WrapperContext): Record<string, unknown> {\n const keys = [\n \"NodeIQQNTWrapperSession\",\n \"NodeIQQNTStartupSessionWrapper\",\n \"NodeQQNTWrapperUtil\",\n \"NodeIKernelLoginService\",\n \"NodeIQQNTWrapperEngine\",\n ] as const;\n const out: Record<string, unknown> = {};\n for (const key of keys) {\n const ctor = (ctx.exports as unknown as Record<string, unknown>)[key];\n if (ctor !== null && ctor !== undefined) {\n out[key] = { methods: listMethods(ctor), ownKeys: Object.getOwnPropertyNames(ctor) };\n }\n }\n return out;\n}\n\n/** 探测 engine 关键调用(getDeviceInfo / getECDHService / getThirdPartySigService)。 */\nexport function probeEngineCalls(ctx: WrapperContext): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const m of ENGINE_CALL_PROBES) {\n out[m] = probeEngineCall(ctx, m);\n }\n return out;\n}\n\n/** 探测单个 engine 方法调用(成功记录方法面/形状,失败记录错误)。 */\nfunction probeEngineCall(ctx: WrapperContext, m: string): unknown {\n const call = tryCall(ctx.engine, m);\n return call.ok\n ? { value: serialize(call.value), methods: call.value ? listMethods(call.value) : [] }\n : { error: call.error ?? \"null/undefined\" };\n}\n\n/** engine 关键调用候选。 */\nconst ENGINE_CALL_PROBES = [\n \"getDeviceInfo\",\n \"getECDHService\",\n \"getThirdPartySigService\",\n \"readyToShow\",\n] as const;\n\n/** 探测 LoginService 实例(QQ 已登录凭据入口)。 */\nexport function probeLoginService(ctx: WrapperContext): Record<string, unknown> | null {\n const ctor = ctx.exports.NodeIKernelLoginService as unknown as {\n get?: () => unknown;\n } | null;\n if (ctor === null || typeof ctor.get !== \"function\") {\n return null;\n }\n const inst = tryCall(ctor, \"get\");\n if (!(inst.ok && inst.value)) {\n return { get: inst.error ?? \"null/undefined\" };\n }\n return {\n methods: listMethods(inst.value),\n ownKeys: Object.getOwnPropertyNames(inst.value).slice(0, 30),\n ...probeLoginGetters(inst.value),\n };\n}\n\n/** 尝试关键登录信息 getter(无参调用,探测形状)。 */\nfunction probeLoginGetters(inst: unknown): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const m of LOGIN_GETTER_PROBES) {\n if (typeof (inst as Record<string, unknown>)[m] === \"function\") {\n out[m] = tryShape(inst, m);\n }\n }\n return out;\n}\n\n/** 登录信息 getter 候选(无参调用,探测形状)。 */\nconst LOGIN_GETTER_PROBES = [\n \"getAccountInfo\",\n \"getLoginInfo\",\n \"getA2\",\n \"getTicket\",\n \"getUin\",\n \"getUid\",\n \"getSelfUin\",\n] as const;\n","/**\n * 运行时反射探测(ADR-006 落地,P1-4)\n *\n * 注入链路打通后(2026-08-05),wrapper exports 是 QQ 已注册的单例。\n * 本模块在 **QQ 主进程内**反射枚举 session / service 的真实方法名与返回形状,\n * 产出 JSON 到 NAPUTO_CFG_DIR/napuketto-probe.json,作为 types/services/ 的权威来源。\n *\n * 用法:boot.cjs 在 NAPUTO_PROBE=1 时于 startNapuketto 后调用 probeRuntime(ctx)。\n * 产物是研究工具数据,类型层据此补全(自研描述,非移植)。\n *\n * 工具拆分(2026-08-08 FTA 优化):方法枚举/安全调用 → probe-utils.ts;\n * 序列化/形状探测 → probe-serialize.ts;exports/service 探测 → probe-services.ts;\n * 本文件只留 session 探测编排(probeStartup/probeSession/probeRuntime)。\n */\n\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\nimport { getMainSession } from \"../session-resolver.js\";\nimport type { WrapperContext } from \"../wrapper-loader.js\";\nimport { serialize } from \"./probe-serialize.js\";\nimport { probeEngineCalls, probeExportCtors, probeLoginService } from \"./probe-services.js\";\nimport { isServiceGetter, listMethods, tryCall } from \"./probe-utils.js\";\n\n/** 枚举候选 sessionId(nt_0..nt_9 / gpro_0..gpro_9)找 QQ 已 init 的 session。 */\nfunction enumerateSessionIds(ctx: WrapperContext): Record<string, unknown> {\n const S = ctx.exports.NodeIQQNTWrapperSession;\n const enumFn = getNTWrapperSessionFn(ctx);\n if (enumFn === null) {\n return {};\n }\n const out: Record<string, unknown> = {};\n for (let i = 0; i < 10; i += 1) {\n probeSessionId(enumFn, S, `nt_${i}`, out);\n probeSessionId(enumFn, S, `gpro_${i}`, out);\n }\n return out;\n}\n\n/** 探测单个 sessionId(getter 存在性 + getMsgService 调用结果)。 */\nfunction probeSessionId(\n enumFn: (...args: unknown[]) => unknown,\n thisCtx: unknown,\n id: string,\n out: Record<string, unknown>,\n): void {\n try {\n const value = enumFn.call(thisCtx, id);\n if (!value) {\n return;\n }\n out[id] = probeSessionRecord(value);\n } catch {\n // 忽略单次失败\n }\n}\n\n/** 探测单个 session 记录(getter 存在性 + msgService 调用结果 + 方法面)。 */\nfunction probeSessionRecord(value: unknown): Record<string, unknown> {\n const rec = value as Record<string, unknown>;\n const msg = tryMsgService(rec, value);\n return {\n hasMsgServiceGetter: typeof rec[\"getMsgService\"] === \"function\",\n msgServiceOk: msg.service !== null,\n msgServiceMethods: msg.service ? listMethods(msg.service).length : 0,\n msgError: msg.error,\n methods: listMethods(value),\n };\n}\n\n/** 尝试调用 getMsgService(失败记录错误,成功返回实例)。 */\nfunction tryMsgService(\n rec: Record<string, unknown>,\n thisCtx: unknown,\n): { service: unknown; error: string | null } {\n try {\n const svc = (rec[\"getMsgService\"] as () => unknown).call(thisCtx);\n return { service: svc ?? null, error: null };\n } catch (e) {\n return { service: null, error: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/** 取 NodeIQQNTWrapperSession.getNTWrapperSession 函数(无则 null)。 */\nfunction getNTWrapperSessionFn(ctx: WrapperContext): ((...args: unknown[]) => unknown) | null {\n const fn = (ctx.exports.NodeIQQNTWrapperSession as unknown as Record<string, unknown>)[\n \"getNTWrapperSession\"\n ];\n return typeof fn === \"function\" ? (fn as (...args: unknown[]) => unknown) : null;\n}\n\n/** 探测 startup 实例:start() + getSessionIdList(),提取 sessionIds(Map 值或键)。 */\nfunction probeStartupChain(created: unknown): {\n start: Record<string, unknown>;\n createdGetSessionIdList: Record<string, unknown>;\n sessionIds: string[];\n} {\n const started = tryCall(created, \"start\");\n const ids = tryCall(created, \"getSessionIdList\");\n return {\n start: started.ok ? { ok: true } : { error: started.error },\n createdGetSessionIdList: ids.ok ? { value: serialize(ids.value) } : { error: ids.error },\n sessionIds: extractSessionIds(ids),\n };\n}\n\n/** 从 getSessionIdList 结果提取 sessionIds(Map 值或键优先)。 */\nfunction extractSessionIds(ids: { ok: boolean; value?: unknown }): string[] {\n if (!(ids.ok && ids.value instanceof Map)) {\n return [];\n }\n return [...ids.value].map(([k, v]) => sessionIdOfEntry(k, v));\n}\n\n/** 单条 sessionIds 项(Map 值是字符串直接用,否则退化键名)。 */\nfunction sessionIdOfEntry(k: unknown, v: unknown): string {\n return typeof v === \"string\" ? v : String(k);\n}\n\n/** session 实例方法面(空值记 null)。 */\nfunction sessionFace(session: unknown): Record<string, unknown> | null {\n return session !== null && session !== undefined\n ? {\n methods: listMethods(session),\n ownKeys: Object.getOwnPropertyNames(session),\n }\n : null;\n}\n\n/** 未知异常 → 错误消息字符串。 */\nfunction errMsgString(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n/** try getNTWrapperSession(id) → session 方法面(fn 缺失记 null,失败记 error)。 */\nfunction trySessionFace(ctx: WrapperContext, id: string): unknown {\n const fn = getNTWrapperSessionFn(ctx);\n if (fn === null) {\n return null;\n }\n try {\n return sessionFace(fn.call(ctx.exports.NodeIQQNTWrapperSession, id));\n } catch (e) {\n return { error: errMsgString(e) };\n }\n}\n\n/** 用真实 sessionId 取主 session(getNTWrapperSession(nt_x))并记录方法面。 */\nfunction probeSessionById(ctx: WrapperContext, id: string): unknown {\n return trySessionFace(ctx, id);\n}\n\n/** 兜底:候选名字 getNTWrapperSession(无 Map 数据时)。 */\nfunction probeByNameFallback(ctx: WrapperContext): Record<string, unknown> {\n const candidates = [\"main\", \"primary\", \"session1\", \"default\", \"\"];\n const getResults: Record<string, unknown> = {};\n for (const name of candidates) {\n getResults[name === \"\" ? \"<empty>\" : name] = probeSessionByName(ctx, name);\n }\n return getResults;\n}\n\n/** 探测单个候选名(getNTWrapperSession(name),记录方法面或错误)。 */\nfunction probeSessionByName(ctx: WrapperContext, name: string): unknown {\n return trySessionFace(ctx, name);\n}\n\n/** StartupSessionWrapper 可用性判定(缺 create 方法视为不可用)。 */\nfunction startupWrapper(ctx: WrapperContext): { create?: () => unknown } | null {\n const startup = ctx.exports.NodeIQQNTStartupSessionWrapper as unknown as {\n create?: () => unknown;\n } | null;\n return startup === null || typeof startup.create !== \"function\" ? null : startup;\n}\n\n/** create() 调用结果面(成功记实例方法面 + 序列化值,失败记 error)。 */\nfunction createFace(created: {\n ok: boolean;\n value?: unknown;\n error?: string;\n}): Record<string, unknown> {\n return created.ok\n ? {\n methods: created.value ? listMethods(created.value) : [],\n value: serialize(created.value),\n }\n : { error: created.error };\n}\n\n/** create 成功时并入实例链探测结果。 */\nfunction assignCreatedChain(\n out: Record<string, unknown>,\n ctx: WrapperContext,\n created: { ok: boolean; value?: unknown },\n): void {\n if (created.ok && created.value) {\n Object.assign(out, probeCreatedChain(ctx, created.value));\n }\n}\n\n/** 无 Map 数据时并入候选名兜底结果。 */\nfunction assignNameFallback(out: Record<string, unknown>, ctx: WrapperContext): void {\n if (out[\"sessionIds\"] !== undefined) {\n return;\n }\n out[\"getNTWrapperSession\"] = probeByNameFallback(ctx);\n}\n\n/** 探测 startup session 链路:create() → start() → getSessionIdList → getNTWrapperSession。 */\nfunction probeStartup(ctx: WrapperContext): Record<string, unknown> | null {\n const startup = startupWrapper(ctx);\n if (startup === null) {\n return null;\n }\n const out: Record<string, unknown> = { staticMethods: listMethods(startup) };\n const created = tryCall(startup, \"create\");\n out[\"create\"] = createFace(created);\n assignCreatedChain(out, ctx, created);\n out[\"enumSessionIds\"] = enumerateSessionIds(ctx);\n // 无 Map 数据时兜底:候选名字\n assignNameFallback(out, ctx);\n return out;\n}\n\n/** 探测 create 后的实例链:start / getSessionIdList / 主 session。 */\nfunction probeCreatedChain(ctx: WrapperContext, created: unknown): Record<string, unknown> {\n const chain = probeStartupChain(created);\n const out: Record<string, unknown> = {\n start: chain.start,\n createdGetSessionIdList: chain.createdGetSessionIdList,\n sessionIds: chain.sessionIds,\n };\n const mainId = findMainProbeId(chain.sessionIds);\n if (mainId !== undefined) {\n out[\"mainSessionId\"] = mainId;\n out[\"mainSession\"] = probeSessionById(ctx, mainId);\n }\n return out;\n}\n\n/** 选主 sessionId(nt_ 前缀优先,否则第一个)。 */\nfunction findMainProbeId(sessionIds: string[]): string | undefined {\n return sessionIds.find((id) => id.startsWith(\"nt_\")) ?? sessionIds[0];\n}\n\n/** 探测 session 与 service 方法名面。\n * 主目标 = ctx.session 快照(kernel 自建就绪 session),缺省回退 QQ 主 session;\n * 自建宿主下主 session 未被 init(get*Service 恒 null,2026-10-31 实测)。\n * ⚠️ 先快照 ctx.session 再枚举主 session——getMainSession 有 P1-4 遗留副作用\n * (resolveMainFromStartup 把空壳主 session 写回 ctx.session,session-resolver.ts)。\n * ⚠️ names-only:不对服务实例做无参 shape 调用——活 session 上无参调原生方法\n * = 访问违例 0xC0000005(2026-10-31 实测崩溃,tryShape 捕不住原生段错误)。 */\n/** 非空判定(null / undefined 均视为缺省)。 */\nfunction hasValue(value: unknown): boolean {\n return value !== null && value !== undefined;\n}\n\nfunction probeSession(ctx: WrapperContext, persist?: () => void): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n const own = ctx.session;\n const main = tryMainSession(ctx);\n const primary = hasValue(own) ? own : main;\n out[\"sessionSource\"] = hasValue(own) ? \"own-create\" : \"mainSession\";\n out[\"session\"] = sessionFace(primary);\n assignServices(out, primary, persist);\n assignAlternates(out, own, main);\n return out;\n}\n\n/** 并入主目标的服务方法名面(主目标缺省时跳过)。 */\nfunction assignServices(\n out: Record<string, unknown>,\n primary: unknown,\n persist?: () => void,\n): void {\n if (!hasValue(primary)) {\n return;\n }\n out[\"services\"] = collectServiceGetters(primary, persist);\n}\n\n/** 并入落选候选概要(仅主目标为自建 session 时记录主 session 方法数,诊断用)。 */\nfunction assignAlternates(out: Record<string, unknown>, own: unknown, main: unknown): void {\n if (!hasValue(own) || !hasValue(main)) {\n return;\n }\n out[\"sessionAlternates\"] = [\n {\n source: \"mainSession(getNTWrapperSession)\",\n methodCount: listMethods(main).length,\n },\n ];\n}\n\n/** QQ 主 session(getMainSession 有写回 ctx.session 的副作用——调用前须已快照)。 */\nfunction tryMainSession(ctx: WrapperContext): unknown {\n try {\n return getMainSession(ctx);\n } catch {\n return null; // 主 session 枚举失败:只剩 own-create 候选\n }\n}\n\n/** 收集 session 的 get*Service 方法名面(names-only,不做 shape 无参调用)。\n * 每个服务完成即 persist 一次——活 session 上个别 getter 若触发原生崩溃,\n * 文件里留下最后一个成功项,精确定位肇事 getter。 */\nfunction collectServiceGetters(session: unknown, persist?: () => void): Record<string, unknown> {\n const services: Record<string, unknown> = {};\n for (const getter of listMethods(session).filter(isServiceGetter)) {\n services[getter] = probeServiceGetter(session, getter);\n persist?.();\n }\n return services;\n}\n\n/** 探测单个 getter:成功记录方法名面(不调用服务方法),失败记录错误。 */\nfunction probeServiceGetter(session: unknown, getter: string): unknown {\n const call = tryCall(session, getter);\n if (!(call.ok && call.value)) {\n return { error: call.error ?? \"null/undefined\", ok: call.ok };\n }\n return {\n methods: listMethods(call.value),\n ownKeys: Object.getOwnPropertyNames(call.value).slice(0, 20),\n };\n}\n\n/**\n * 探测 wrapper 运行时:dump session 方法与各 service 方法名到 JSON。\n * 返回探测结果对象;同时写文件(NAPUTO_CFG_DIR/<file>,默认 napuketto-probe.json)。\n * 服务枚举逐项增量落盘(persist)——活 session 上原生崩溃时文件保留断点。\n */\nexport function probeRuntime(\n ctx: WrapperContext,\n file = \"napuketto-probe.json\",\n): Record<string, unknown> {\n const result: Record<string, unknown> = {\n timestamp: new Date().toISOString(),\n exports: listMethods(ctx.exports),\n engineMethods: listMethods(ctx.engine),\n exportCtors: probeExportCtors(ctx),\n engineCalls: probeEngineCalls(ctx),\n loginService: probeLoginService(ctx),\n startup: probeStartup(ctx),\n };\n const persist = (): void => writeProbeFile(result, file);\n Object.assign(result, probeSession(ctx, persist));\n writeProbeFile(result, file);\n return result;\n}\n\n/** 探测结果落盘(增量与最终共用;最终写入后记 probeFile 路径)。 */\nfunction writeProbeFile(result: Record<string, unknown>, file: string): void {\n try {\n const dir = process.env[\"NAPUTO_CFG_DIR\"] ?? process.cwd();\n mkdirSync(dir, { recursive: true });\n const full = join(dir, file);\n writeFileSync(full, JSON.stringify(result, null, 2), \"utf8\");\n result[\"probeFile\"] = full;\n } catch (e) {\n result[\"probeFileError\"] = errMsgString(e);\n }\n}\n","/**\n * listener 录制器(批次三前置探测,docs/protocols/milky.md §0.8.1、本包 design.md §10)\n *\n * 动机:无源事件缺的不是方法名而是回调证据——现有 bridge 只注册已知回调名,\n * 原生 listener 回调面之外的派发行为从未被观测。原理:原生 listener 是普通\n * JS 对象 + NAPI 按名反射回调(MsgBridge 模式),用 Proxy get 陷阱构造万能\n * 监听器,原生调什么记什么,不需要预先知道回调名。\n *\n * 开关:NAPUTO_LISTENER_RECORD=1(loader bootstrap 挂起,位置与 runProbePhase 相邻)。\n * 产物:NAPUTO_CFG_DIR/napuketto-listener-record.jsonl(JSONL 追加,注册过程\n * 逐行落盘,原生崩溃保留断点定位肇事服务)。\n *\n * 安全边界:记录函数只序列化入参后回调 onEvent,绝不反向调用原生;录制器与\n * 业务 bridge 并存(addKernelXListener 多监听各自独立 id),不干扰协议链路。\n */\n\nimport { appendFileSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\nimport type { WrapperContext } from \"../wrapper-loader.js\";\nimport { serialize } from \"./probe-serialize.js\";\nimport { listMethods } from \"./probe-utils.js\";\n\n/** 录制事件(JSONL 一行)。 */\nexport interface ListenerRecordEvent {\n /** ISO 时间戳。 */\n ts: string;\n /** 服务简称(Msg / Group / …;LoginService 例外,来自 exports 单例)。 */\n service: string;\n /** 回调名;陷阱观测用合成事件 `<get>` / `<has>` / `<ownKeys>` / `<registered>`。 */\n event: string;\n /** 回调入参(截断序列化)或注册附加信息。 */\n args: unknown[];\n}\n\n/** 录制结果摘要(bootstrap 日志用)。 */\nexport interface ListenerRecordingResult {\n /** jsonl 产物绝对路径。 */\n file: string;\n /** 逐服务注册结果(ok=listenerId / error / null=服务 getter 返回空)。 */\n registered: Record<string, { ok: boolean; listenerId?: number; error?: string | null }>;\n}\n\n/** 默认仪器面:与 5 个无源事件 + sysmsg 样本采集相关的 listener 服务(design.md §10)。 */\nconst INSTRUMENTED_SERVICE_GETTERS = [\n \"getMsgService\",\n \"getGroupService\",\n \"getRecentContactService\",\n \"getBdhUploadService\",\n \"getOnlineStatusService\",\n \"getLockService\",\n \"getFileAssistantService\",\n \"getBuddyService\",\n \"getProfileLikeService\",\n \"getNodeMiscService\",\n] as const;\n\n/** 服务 getter 前缀 / 后缀(getMsgService → Msg)。 */\nconst GETTER_PREFIX_RE = /^get/;\nconst SERVICE_SUFFIX_RE = /Service$/;\n/** listener 注册 / 注销方法名(addKernelMsgListener / 裸 addListener 同规则)。 */\nconst ADD_LISTENER_RE = /^add(Kernel)?\\w*Listener$/;\nconst REMOVE_LISTENER_RE = /^remove(Kernel)?\\w*Listener$/;\n\n/** 服务简称(getMsgService → Msg;getBdhUploadService → BdhUpload)。 */\nfunction serviceShortName(getter: string): string {\n return getter.replace(GETTER_PREFIX_RE, \"\").replace(SERVICE_SUFFIX_RE, \"\");\n}\n\n/** 从服务方法面选 listener 注册方法(addKernelMsgListener / 裸 addListener 同规则)。 */\nfunction findAddMethod(service: unknown): string | null {\n const match = listMethods(service).find((m) => ADD_LISTENER_RE.test(m));\n return match ?? null;\n}\n\n/** 从服务方法面选 listener 注销方法(缺失允许——录制器不主动注销)。 */\nfunction findRemoveMethod(service: unknown): string | null {\n const match = listMethods(service).find((m) => REMOVE_LISTENER_RE.test(m));\n return match ?? null;\n}\n\n/**\n * 构造万能监听器:Proxy get 陷阱对任意字符串属性返回记录函数(同时记录原生\n * 查过的名字,即使从未调用);has / ownKeys 也记录(判定原生按名 Get 还是\n * 枚举发现回调);非字符串键(Symbol,如 util.inspect 触发)放行默认行为。\n */\nexport function createRecordingListener(\n service: string,\n onEvent: (record: ListenerRecordEvent) => void,\n): object {\n const record = (event: string, args: unknown[]): void => {\n onEvent({ ts: new Date().toISOString(), service, event, args });\n };\n // 空目标:所有属性访问走陷阱(ownKeys 为空是刻意的——若原生以枚举发现回调,\n // <ownKeys> 记录立即暴露该行为,fallback 预置已知回调名全集后重录)。\n return new Proxy({} as Record<string, unknown>, {\n get(target, prop, receiver): unknown {\n if (typeof prop !== \"string\") {\n return Reflect.get(target, prop, receiver);\n }\n record(\"<get>\", [prop]);\n return (...args: unknown[]) => {\n record(\n prop,\n [...args].map((a) => serialize(a)),\n );\n };\n },\n has(_target, prop): boolean {\n if (typeof prop === \"string\") {\n record(\"<has>\", [prop]);\n }\n return true;\n },\n ownKeys(): string[] {\n record(\"<ownKeys>\", []);\n return [];\n },\n });\n}\n\n/**\n * 启动 listener 录制:session 十服务 + LoginService(exports 单例)逐服务注册\n * 万能监听器,JSONL 逐行追加。返回摘要;文件路径写入结果(bootstrap 日志用)。\n *\n * ⚠️ 调用时序约束:必须在任何探测(probeRuntime / getMainSession)之前调用——\n * resolveMainFromStartup 会把空壳主 session 写回 ctx.session(session-resolver.ts),\n * 空壳的 service getter 函数仍在但调用返回 null(late probe 61/63 null 的根因)。\n */\nexport function startListenerRecording(\n ctx: WrapperContext,\n file = \"napuketto-listener-record.jsonl\",\n): ListenerRecordingResult {\n const dir = process.env[\"NAPUTO_CFG_DIR\"] ?? process.cwd();\n mkdirSync(dir, { recursive: true });\n const full = join(dir, file);\n // JSONL 语义:本次录制起全新文件(截断),此后逐行 append(崩溃保留断点)\n writeFileSync(full, \"\", \"utf8\");\n const emit = (record: ListenerRecordEvent): void => {\n try {\n appendFileSync(full, `${JSON.stringify(record)}\\n`, \"utf8\");\n } catch {\n // 落盘失败不中断录制(录制器是观测工具,不允许拖垮宿主)\n }\n };\n const registered: ListenerRecordingResult[\"registered\"] = {};\n const session = ctx.session as Record<string, unknown> | null;\n if (session !== null && session !== undefined) {\n for (const getter of INSTRUMENTED_SERVICE_GETTERS) {\n registered[serviceShortName(getter)] = recordService(session, getter, emit);\n }\n }\n registered[\"Login\"] = recordLoginService(ctx, emit);\n return { file: full, registered };\n}\n\n/** 对单个服务注册万能监听器(getter 失败/null/无 add 方法均记录原因)。 */\nfunction recordService(\n session: Record<string, unknown>,\n getter: string,\n emit: (record: ListenerRecordEvent) => void,\n): ListenerRecordingResult[\"registered\"][string] {\n const name = serviceShortName(getter);\n try {\n const fn = session[getter];\n if (typeof fn !== \"function\") {\n return { ok: false, error: `getter 缺失(typeof=${typeof fn})` };\n }\n const service = (fn as () => unknown).call(session);\n if (service === null || service === undefined) {\n return { ok: false, error: \"null/undefined\" };\n }\n return attachRecordingListener(name, service, emit);\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/** LoginService 单例录制(NodeIKernelLoginService.get(),bot_offline 候选源)。 */\nfunction recordLoginService(\n ctx: WrapperContext,\n emit: (record: ListenerRecordEvent) => void,\n): ListenerRecordingResult[\"registered\"][string] {\n const ctor = (ctx.exports as unknown as Record<string, unknown>)[\"NodeIKernelLoginService\"] as\n | { get?: () => unknown }\n | null\n | undefined;\n try {\n if (ctor === null || ctor === undefined || typeof ctor.get !== \"function\") {\n return { ok: false, error: \"NodeIKernelLoginService.get 缺失\" };\n }\n const service = ctor.get();\n if (service === null || service === undefined) {\n return { ok: false, error: \"null/undefined\" };\n }\n return attachRecordingListener(\"Login\", service, emit);\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/** 在服务实例上挂万能监听器(add 方法自动选取;注册事件即行落盘)。 */\nfunction attachRecordingListener(\n name: string,\n service: unknown,\n emit: (record: ListenerRecordEvent) => void,\n): ListenerRecordingResult[\"registered\"][string] {\n const addMethod = findAddMethod(service);\n if (addMethod === null) {\n return { ok: false, error: \"无 add*Listener 方法\" };\n }\n const add = (service as Record<string, unknown>)[addMethod];\n if (typeof add !== \"function\") {\n return { ok: false, error: `${addMethod} 非函数` };\n }\n const listener = createRecordingListener(name, emit);\n const listenerId = (add as (...args: unknown[]) => unknown).call(service, listener) as\n | number\n | null\n | undefined;\n emit({\n ts: new Date().toISOString(),\n service: name,\n event: \"<registered>\",\n args: [\n {\n addMethod,\n removeMethod: findRemoveMethod(service),\n listenerId: serialize(listenerId),\n },\n ],\n });\n return { ok: true, ...(typeof listenerId === \"number\" ? { listenerId } : {}) };\n}\n","/**\n * QQ 安装目录探测(ADR-018)\n *\n * wrapper.node 路径随 QQ 版本变化:`<安装目录>/versions/<版本>/resources/app/wrapper.node`。\n * 加载前必须知道版本(登录握手参数 appid/qua 与版本强相关)。\n *\n * 已实测确认(2026-08-05,QQ 9.9.31-49919):\n * - 目录结构:versions/<9.9.31-49919>/resources/app/wrapper.node\n * - 版本信息在 versions/<版本>/resources/app/package.json(name=qq-chat / version / buildVersion)\n * - wrapper.node 是 C++ ABI 模块(非 N-API),导出 INTSessionShell 等符号\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { kernelError } from \"../infra/index.js\";\n\n/** 从 versions 目录读取版本 package.json(拿 name/version/buildVersion)。 */\nfunction readVersionPackage(\n installDir: string,\n version: string,\n): { name: string; version: string; buildVersion?: string } {\n const pkgPath = join(installDir, \"versions\", version, \"resources\", \"app\", \"package.json\");\n if (!existsSync(pkgPath)) {\n throw kernelError(`QQ 版本目录缺少 package.json: ${pkgPath}`, \"NOT_FOUND\");\n }\n try {\n const raw = JSON.parse(readFileSync(pkgPath, \"utf8\")) as {\n name?: string;\n version?: string;\n buildVersion?: string;\n };\n return {\n name: raw.name ?? \"\",\n version: raw.version ?? \"\",\n ...(raw.buildVersion !== undefined ? { buildVersion: raw.buildVersion } : {}),\n };\n } catch (cause) {\n throw kernelError(`QQ 版本 package.json 解析失败: ${pkgPath}`, \"INVALID_PARAM\", { cause });\n }\n}\n\n/** 解析 wrapper.node 绝对路径(按 ADR-018 目录约定)。 */\nexport function resolveWrapperPath(installDir: string, version: string): string {\n return join(installDir, \"versions\", version, \"resources\", \"app\", \"wrapper.node\");\n}\n\n/**\n * 探测指定版本信息(校验 wrapper.node 存在)。\n * 版本号不匹配目录时抛 NOT_FOUND(明确失败而非静默)。\n */\nexport function resolveQQVersion(installDir: string, version: string): QQVersionInfo {\n const wrapperPath = resolveWrapperPath(installDir, version);\n if (!existsSync(wrapperPath)) {\n throw kernelError(`未找到 wrapper.node: ${wrapperPath}`, \"NOT_FOUND\");\n }\n const pkg = readVersionPackage(installDir, version);\n return {\n fullVersion: pkg.version || version,\n buildVersion: pkg.buildVersion ?? \"\",\n wrapperPath,\n appid: \"\",\n qua: \"\",\n };\n}\n\n/** 扫描 versions 目录,取可加载的版本列表(按目录名倒序,最新在前)。 */\nexport function listQQVersions(installDir: string): string[] {\n const versionsDir = join(installDir, \"versions\");\n if (!existsSync(versionsDir)) {\n return [];\n }\n const dirs = readdirSync(versionsDir).filter((name) => {\n const full = join(versionsDir, name);\n return (\n statSync(full).isDirectory() &&\n existsSync(join(full, \"resources\", \"app\", \"wrapper.node\"))\n );\n });\n return dirs.sort().reverse();\n}\n\n/** QQ 版本信息。 */\nexport interface QQVersionInfo {\n /** 完整版本号,如 \"9.9.31-49919\"。 */\n fullVersion: string;\n /** 构建号,如 \"49919\"。 */\n buildVersion: string;\n /** wrapper.node 绝对路径。 */\n wrapperPath: string;\n /** 登录握手参数(P1 探测脚本产出,暂为空串)。 */\n appid: string;\n /** 登录握手参数(P1 探测脚本产出,暂为空串)。 */\n qua: string;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["stringifyTomlText","parseTomlText","VIDEO_THUMB_KEY","errMsgString","sleep","errMsgString","errMsgString","httpsGet","osVersionString","osType","PlatformTypeValue"],"sources":["../src/infra/errors.ts","../src/infra/config.ts","../src/infra/log-config.ts","../src/infra/log-format.ts","../src/infra/logger.ts","../src/infra/paths.ts","../src/apis/result.ts","../src/apis/friend.ts","../src/types/entities.ts","../src/types/listeners/group.ts","../src/types/services/msg-service.ts","../src/types/message-element.ts","../src/types/services/group-service.ts","../src/types/wrapper.ts","../src/apis/group.ts","../src/apis/group-notify.ts","../src/apis/msg.ts","../src/apis/stranger-info.ts","../src/apis/profile.ts","../src/apis/profile-like.ts","../src/apis/recent-contact.ts","../src/apis/richmedia.ts","../src/apis/ticket.ts","../src/apis/webapi.ts","../src/bridge/friend-bridge.ts","../src/bridge/group-bridge.ts","../src/bridge/msg-bridge.ts","../src/event-channel.ts","../src/cache/buddy-cache.ts","../src/login/wait.ts","../src/cache/group-cache.ts","../src/context.ts","../src/wrapper/wrapper-adapters.ts","../src/login/login-connect.ts","../src/login/lifecycle.ts","../src/login/login.ts","../src/wrapper/qq-data-path.ts","../src/wrapper/wrapper-config.ts","../src/wrapper/wrapper-loader.ts","../src/core.ts","../src/wrapper/session-resolver.ts","../src/wrapper/probe/probe-serialize.ts","../src/wrapper/probe/probe-utils.ts","../src/wrapper/probe/probe-services.ts","../src/wrapper/probe/probe.ts","../src/wrapper/probe/probe-listener.ts","../src/wrapper/wrapper-version.ts"],"sourcesContent":["/**\n * kernel 类型化错误(ADR-009 / ADR-017)\n *\n * apis 层内部解包原生 `{ result, errMsg }`:成功返回纯业务值,失败抛 KernelError。\n * 协议层只需维护 `KernelErrorCode → 协议错误码` 映射表,不解析错误逻辑。\n */\n\n/**\n * 错误码分类。P1 探测真实错误返回后再增删。\n * 当前 INVALID_PARAM 也承担基础设施层(配置校验失败等)的错误语义。\n */\nexport type KernelErrorCode =\n | \"SEND_FAILED\" // 发送失败(原生拒绝)\n | \"PERMISSION_DENIED\" // 无权限(禁言、非管理员等)\n | \"NOT_FOUND\" // 目标不存在(群/成员/消息)\n | \"TIMEOUT\" // 操作超时(含文件预测超时)\n | \"NOT_LOGIN\" // 未登录或已掉线\n | \"INVALID_PARAM\" // 参数非法(原生拒绝 / 配置校验失败)\n | \"INVALID_STATE\" // 状态非法(如 session 未创建即 startNT)\n | \"UNKNOWN\"; // 兜底\n\n/**\n * 全部错误码集合。协议层映射表可据此推导:\n * `Record<(typeof KERNEL_ERROR_CODES)[number], number>`。\n */\nexport const KERNEL_ERROR_CODES = [\n \"SEND_FAILED\",\n \"PERMISSION_DENIED\",\n \"NOT_FOUND\",\n \"TIMEOUT\",\n \"NOT_LOGIN\",\n \"INVALID_PARAM\",\n \"INVALID_STATE\",\n \"UNKNOWN\",\n] as const;\n\n/** kernel 类型化错误:apis / 基础设施层抛出的统一错误形态。 */\nexport class KernelError extends Error {\n readonly code: KernelErrorCode;\n\n constructor(message: string, code: KernelErrorCode = \"UNKNOWN\", options?: ErrorOptions) {\n super(message, options);\n this.name = \"KernelError\";\n this.code = code;\n }\n}\n\n/** 便捷构造:`kernelError('消息不存在', 'NOT_FOUND', { cause })`。 */\nexport function kernelError(\n message: string,\n code: KernelErrorCode = \"UNKNOWN\",\n options?: ErrorOptions,\n): KernelError {\n return new KernelError(message, code, options);\n}\n\n/** 类型守卫:协议层 catch 统一映射时区分 KernelError 与意外错误。 */\nexport function isKernelError(err: unknown): err is KernelError {\n return err instanceof KernelError;\n}\n\n/**\n * 配置校验失败(INVALID_PARAM 子型,2026-10-25 嵌入式宿主契约):仅用于\n * 「配置未就绪」一族——主配置校验/反序列化、内联配置、协议段 seed 校验。\n * code 恒为 INVALID_PARAM,协议层错误码映射零涟漪;退出码分类走 instanceof\n * (CLI/self-host 进程以 EXIT_CONFIG_INVALID 退出),与错误文案完全解耦——\n * 文案是人读的,退出码是宿主消费的机器契约。\n */\nexport class ConfigError extends KernelError {\n constructor(message: string, options?: ErrorOptions) {\n super(message, \"INVALID_PARAM\", options);\n this.name = \"ConfigError\";\n }\n}\n\n/** 便捷构造:`configError('主配置缺少 accounts', { cause })`。 */\nexport function configError(message: string, options?: ErrorOptions): ConfigError {\n return new ConfigError(message, options);\n}\n\n/** 配置校验失败的进程退出码(UNIX sysexits EX_CONFIG 惯例)。 */\nexport const EXIT_CONFIG_INVALID = 78;\n","/**\n * 配置基类(ADR-012)\n *\n * 读文件 → 校验 → 内存对象 → 变更写回。支持 **JSON / TOML** 两种文本格式\n * (按扩展名推断:.toml → TOML(smol-toml),其余 → JSON)。\n *\n * kernel 不依赖 zod:只依赖校验器的最小 `parse` 形状(`ConfigSchema`),\n * 协议包的 zod schema 天然满足;kernel 主配置用手写校验器包装。\n * 协议配置 schema 在各自协议包,见各包 docs/design.md。\n */\nimport { mkdirSync } from \"node:fs\";\nimport { readFile, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, extname } from \"node:path\";\nimport { parse as parseTomlText, stringify as stringifyTomlText } from \"smol-toml\";\nimport { ConfigError } from \"./errors.js\";\n\n/** 配置文本格式。 */\nexport type ConfigFormat = \"json\" | \"toml\";\n\n/** 按扩展名推断格式(.toml → toml,其余 → json)。 */\nfunction inferFormat(path: string): ConfigFormat {\n if (extname(path).toLowerCase() === \".toml\") {\n return \"toml\";\n }\n return \"json\";\n}\n\nfunction isMissingFileError(err: unknown): boolean {\n if (err instanceof Error) {\n return (err as NodeJS.ErrnoException).code === \"ENOENT\";\n }\n return false;\n}\n\n/**\n * 校验器最小接口。zod 的 `ZodType<T>`(`parse(data: unknown): T`)天然满足;\n * 手写校验器包装成该形状即可。\n */\nexport interface ConfigSchema<T> {\n parse: (input: unknown) => T;\n}\n\nexport interface ConfigOptions<T> {\n /** 配置文件绝对路径。 */\n path: string;\n /** 校验器(协议 schema 用 zod;kernel 主配置用手写校验器包装)。 */\n schema: ConfigSchema<T>;\n /** 默认值:文件缺失时生成并落盘;校验以它为兜底。 */\n defaults: T;\n /** 内存初值(已校验对象):load() 时直接使用,跳过文件读写(全局 TOML 分段的装配方式)。 */\n seed?: T | undefined;\n /** 文本格式(缺省按扩展名推断)。 */\n format?: ConfigFormat | undefined;\n}\n\n/**\n * 配置基类。每次 `load()` / `save()` 都经过 schema 校验,\n * 校验失败抛 `KernelError('INVALID_PARAM')`,不静默吞掉。\n * - `load()`:seed 存在 → 直接用 seed;否则读文件(缺失落默认值)。\n * - `save()`:按格式序列化(TOML/JSON)原子写。\n */\nexport class ConfigBase<T> {\n readonly path: string;\n readonly defaults: T;\n\n private readonly schema: ConfigSchema<T>;\n private readonly format: ConfigFormat;\n private readonly seed: T | undefined;\n private value: T;\n\n constructor(opts: ConfigOptions<T>) {\n this.path = opts.path;\n this.defaults = opts.defaults;\n this.schema = opts.schema;\n this.format = opts.format ?? inferFormat(opts.path);\n this.seed = opts.seed;\n this.value = opts.seed ?? opts.defaults;\n }\n\n /** 当前内存值(只读消费;外部不得直接修改返回对象)。 */\n get(): T {\n return this.value;\n }\n\n /**\n * 加载配置:\n * - seed 存在 → 直接用 seed(已校验,不读文件;全局 TOML 分段装配方式);\n * - 文件缺失 → 写入默认值并落盘(首次运行自动生成),返回默认值;\n * - 文件存在 → 读取 + 校验,更新内存,返回。\n */\n async load(): Promise<T> {\n if (this.seed !== undefined) {\n this.value = this.seed;\n return this.value;\n }\n let raw: string;\n try {\n raw = await readFile(this.path, \"utf8\");\n } catch (err) {\n if (isMissingFileError(err)) {\n await this.save(this.defaults);\n return this.value;\n }\n throw new ConfigError(`读取配置文件失败: ${this.path}`, { cause: err });\n }\n this.value = this.parse(raw);\n return this.value;\n }\n\n /**\n * 校验纯文本配置(反序列化 + schema 校验,不读写文件;`config validate`\n * 等只读场景用)。错误包装与 load()/save() 完全一致(ConfigError,\n * 文案含 this.path),保证 validate 与运行时校验逐字一致。\n */\n parseText(raw: string): T {\n return this.parse(raw);\n }\n\n /** 校验并落盘(原子写:临时文件 + rename),随后更新内存。 */\n async save(next: T): Promise<void> {\n const parsed = this.parse(this.serialize(next));\n mkdirSync(dirname(this.path), { recursive: true });\n const tmpPath = `${this.path}.tmp`;\n await writeFile(tmpPath, `${this.serialize(parsed)}\\n`, \"utf8\");\n await rename(tmpPath, this.path);\n this.value = parsed;\n }\n\n /** 对象 → 文本(TOML/JSON)。 */\n private serialize(value: T): string {\n if (this.format === \"toml\") {\n return stringifyTomlText(value as Record<string, unknown>);\n }\n return JSON.stringify(value, null, 4);\n }\n\n /** 文本 → 对象(TOML/JSON)。 */\n private deserialize(raw: string): unknown {\n if (this.format === \"toml\") {\n return parseTomlText(raw);\n }\n return JSON.parse(raw) as unknown;\n }\n\n /** 反序列化 + schema 校验,错误统一包装为 ConfigError(配置未就绪一族)。 */\n private parse(raw: string): T {\n let input: unknown;\n try {\n input = this.deserialize(raw);\n } catch (err) {\n throw new ConfigError(`配置文件 ${this.format.toUpperCase()} 解析失败: ${this.path}`, {\n cause: err,\n });\n }\n try {\n return this.schema.parse(input);\n } catch (err) {\n throw new ConfigError(`配置文件校验失败: ${this.path}`, { cause: err });\n }\n }\n}\n\n/** 解析 TOML 文本 → 对象(boot.cjs / 探测脚本等无 ConfigBase 场景复用)。 */\nexport function parseToml(text: string): Record<string, unknown> {\n return parseTomlText(text);\n}\n\n/** 对象 → TOML 文本(config init 生成全局配置文件等场景)。 */\nexport function stringifyToml(value: Record<string, unknown>): string {\n return stringifyTomlText(value);\n}\n","/**\n * [log] 配置段解析(napuketto.toml 全局段,2026-10-25 日志可配置化)。\n *\n * 统一解析入口:cli(apps/cli)与 loader 引导链共用本函数(经 kernel 导出),\n * 避免两处手写解析漂移。输入为 TOML 解析后的 [log] 段原对象(缺失传 undefined)\n * + 环境变量快照;输出为已解析的 LogConfig(env 覆盖优先于 toml)。\n *\n * 环境变量(优先级高于 toml;非法值忽略——与既有 NAPKETTO_LOG_LEVEL 行为一致):\n * - NAPUKETTO_LOG_LEVEL / NAPKETTO_LOG_LEVEL(兼容旧拼写,NAPUKETTO_ 优先)\n * - NAPUKETTO_LOG_FORMAT(pretty | single-line | json)\n * - NAPUKETTO_NO_BANNER(1/true = 关 banner)\n * - NAPUKETTO_LOG_COLORIZE(1/true/0/false,CLI 顶层进程向子进程传播控制台\n * 渲染决策用;toml 不暴露此键)\n */\nimport { kernelError } from \"./errors.js\";\nimport type { LogLevel } from \"./logger.js\";\n\n/** 控制台输出格式。 */\nexport type LogFormat = \"pretty\" | \"single-line\" | \"json\";\n/** 时间戳渲染模式(见 LogConfig.timestamp)。 */\nexport type TimestampMode = \"local\" | \"iso8601\" | \"elapsed\";\n/** 可按模块覆盖级别的模块名(= logger 来源名)。 */\nexport type LogModuleName = \"cli\" | \"kernel\" | \"loader\" | \"ob11\";\n\n/** pino 标准级别 + silent。 */\nconst LOG_LEVEL_VALUES: readonly LogLevel[] = [\n \"trace\",\n \"debug\",\n \"info\",\n \"warn\",\n \"error\",\n \"fatal\",\n \"silent\",\n];\nconst LOG_FORMAT_VALUES: readonly LogFormat[] = [\"pretty\", \"single-line\", \"json\"];\nconst TIMESTAMP_MODE_VALUES: readonly TimestampMode[] = [\"local\", \"iso8601\", \"elapsed\"];\nconst MODULE_NAME_VALUES: readonly LogModuleName[] = [\"cli\", \"kernel\", \"loader\", \"ob11\"];\n\n/** 独立文件 sink 配置([log.file])。 */\nexport interface LogFileConfig {\n /** 文件路径(相对路径由消费方按数据根展开)。 */\n path: string;\n /** 文件目的地级别(可低于控制台级别)。 */\n level: LogLevel;\n}\n\n/** [log] 段解析结果(env 已覆盖)。 */\nexport interface LogConfig {\n /** 控制台(全局)级别。 */\n level: LogLevel;\n /**\n * 显式配置的输出格式;undefined = 未配置——消费端按 TTY 自动\n * (TTY = pretty 保持终端体验 / pipe = single-line 嵌入式友好),\n * 显式配置值不受 TTY 影响。\n */\n format?: LogFormat;\n /** 是否打印启动 banner(CLI 入口)。 */\n banner: boolean;\n /** 时间戳渲染模式(json 格式恒为 ISO8601,不受此键影响)。 */\n timestamp: TimestampMode;\n /** false = 未绑定频道的群消息接收行不打(需 kurobot 握手拿到绑定表后生效)。 */\n unboundReceive: boolean;\n /** 按模块覆盖全局级别。 */\n levels: Partial<Record<LogModuleName, LogLevel>>;\n /** 独立文件 sink(不配 = 沿用各层缺省文件日志)。 */\n file?: LogFileConfig;\n /**\n * 控制台颜色(env 专用:CLI 顶层进程向 pipe 后面的子进程传播用;\n * undefined = 消费端自动判定 TTY + pretty)。\n */\n colorize?: boolean;\n}\n\n/** 环境变量快照类型(process.env 的结构子集,便于测试注入)。 */\nexport type LogEnvSource = Record<string, string | undefined>;\n\n/** 校验级别字面量;非法值抛 INVALID_PARAM。 */\nfunction requireLevel(value: unknown, key: string): LogLevel {\n if (typeof value !== \"string\" || !(LOG_LEVEL_VALUES as readonly string[]).includes(value)) {\n throw kernelError(\n `主配置 ${key} 必须是日志级别(${LOG_LEVEL_VALUES.join(\" / \")})`,\n \"INVALID_PARAM\",\n );\n }\n return value as LogLevel;\n}\n\n/** 宽松级别解析(env 覆盖用;非法/缺失返回 undefined 不生效)。 */\nfunction optionalLevel(value: string | undefined): LogLevel | undefined {\n if (value === undefined) {\n return undefined;\n }\n return (LOG_LEVEL_VALUES as readonly string[]).includes(value)\n ? (value as LogLevel)\n : undefined;\n}\n\n/** 宽松格式解析(env 覆盖用)。 */\nfunction optionalFormat(value: string | undefined): LogFormat | undefined {\n if (value === undefined) {\n return undefined;\n }\n return (LOG_FORMAT_VALUES as readonly string[]).includes(value)\n ? (value as LogFormat)\n : undefined;\n}\n\n/** 宽松布尔解析(env 用:仅识别 1/true/0/false,其余忽略)。 */\nfunction optionalBool(value: string | undefined): boolean | undefined {\n if (value === \"1\" || value === \"true\") {\n return true;\n }\n if (value === \"0\" || value === \"false\") {\n return false;\n }\n return undefined;\n}\n\n/** 解析 [log.levels] 按模块覆盖表(未知模块名报错,键集封闭防拼写错误)。 */\nfunction parseModuleLevels(raw: unknown): Partial<Record<LogModuleName, LogLevel>> {\n if (raw === undefined) {\n return {};\n }\n if (typeof raw !== \"object\" || raw === null) {\n throw kernelError(\"主配置 log.levels 必须是对象\", \"INVALID_PARAM\");\n }\n const out: Partial<Record<LogModuleName, LogLevel>> = {};\n for (const [key, value] of Object.entries(raw)) {\n if (!(MODULE_NAME_VALUES as readonly string[]).includes(key)) {\n throw kernelError(\n `主配置 log.levels 未知模块 \"${key}\"(合法模块:${MODULE_NAME_VALUES.join(\" / \")})`,\n \"INVALID_PARAM\",\n );\n }\n out[key as LogModuleName] = requireLevel(value, `log.levels.${key}`);\n }\n return out;\n}\n\n/** 解析 [log.file] 独立文件 sink。 */\nfunction parseLogFile(raw: unknown, fallbackLevel: LogLevel): LogFileConfig {\n if (typeof raw !== \"object\" || raw === null) {\n throw kernelError(\"主配置 log.file 必须是对象\", \"INVALID_PARAM\");\n }\n const record = raw as Record<string, unknown>;\n const path = record[\"path\"];\n if (typeof path !== \"string\" || path === \"\") {\n throw kernelError(\"主配置 log.file.path 必须是非空字符串\", \"INVALID_PARAM\");\n }\n const level =\n record[\"level\"] === undefined\n ? fallbackLevel\n : requireLevel(record[\"level\"], \"log.file.level\");\n return { path, level };\n}\n\n/** [log] 解析中间产物(可变草稿:toml 字段与 env 覆盖分步填充)。 */\ninterface LogConfigDraft extends LogConfig {\n /** [log.file] 是否显式配置 level(env 覆盖判断用;不进最终配置面)。 */\n fileLevelExplicit?: boolean;\n}\n\n/** 校验字面量枚举值(非法抛 INVALID_PARAM,报错文案带键名与合法值集)。 */\nfunction requireEnum<T extends string>(value: unknown, key: string, values: readonly T[]): T {\n if (typeof value !== \"string\" || !(values as readonly string[]).includes(value)) {\n throw kernelError(`主配置 ${key} 必须是(${values.join(\" / \")})`, \"INVALID_PARAM\");\n }\n return value as T;\n}\n\n/** 校验布尔字段(undefined 保留原值)。 */\nfunction applyBoolean(\n draft: LogConfigDraft,\n raw: Record<string, unknown>,\n key: \"banner\" | \"unboundReceive\",\n): void {\n if (raw[key] === undefined) {\n return;\n }\n if (typeof raw[key] !== \"boolean\") {\n throw kernelError(`主配置 log.${key} 必须是布尔值`, \"INVALID_PARAM\");\n }\n draft[key] = raw[key] as boolean;\n}\n\n/** toml [log] 段字段填充(level/format/timestamp/banner/unboundReceive)。 */\nfunction applySectionFields(draft: LogConfigDraft, raw: Record<string, unknown>): void {\n if (raw[\"level\"] !== undefined) {\n draft.level = requireLevel(raw[\"level\"], \"log.level\");\n }\n if (raw[\"format\"] !== undefined) {\n draft.format = requireEnum(raw[\"format\"], \"log.format\", LOG_FORMAT_VALUES);\n }\n if (raw[\"timestamp\"] !== undefined) {\n draft.timestamp = requireEnum(raw[\"timestamp\"], \"log.timestamp\", TIMESTAMP_MODE_VALUES);\n }\n applyBoolean(draft, raw, \"banner\");\n applyBoolean(draft, raw, \"unboundReceive\");\n}\n\n/** env 覆盖(优先级高于 toml;非法值忽略):兼容旧拼写 NAPKETTO_LOG_LEVEL。\n * file.level 显式配置时不被 env 全局级别顶掉(缺省跟随全局)。 */\nfunction applyEnvOverrides(draft: LogConfigDraft, env: LogEnvSource): void {\n const envLevel =\n optionalLevel(env[\"NAPUKETTO_LOG_LEVEL\"]) ?? optionalLevel(env[\"NAPKETTO_LOG_LEVEL\"]);\n if (envLevel !== undefined) {\n draft.level = envLevel;\n if (draft.file !== undefined && draft.fileLevelExplicit !== true) {\n draft.file = { ...draft.file, level: envLevel };\n }\n }\n const envFormat = optionalFormat(env[\"NAPUKETTO_LOG_FORMAT\"]);\n if (envFormat !== undefined) {\n draft.format = envFormat;\n }\n if (optionalBool(env[\"NAPUKETTO_NO_BANNER\"]) === true) {\n draft.banner = false;\n }\n const envColorize = optionalBool(env[\"NAPUKETTO_LOG_COLORIZE\"]);\n if (envColorize !== undefined) {\n draft.colorize = envColorize;\n }\n}\n\n/**\n * 解析 [log] 段 + 环境变量覆盖。\n *\n * @param section TOML 解析后的 [log] 段(缺失/undefined = 全缺省)\n * @param env 环境变量快照(默认空对象 = 无 env 覆盖)\n */\nexport function parseLogConfig(section: unknown, env: LogEnvSource = {}): LogConfig {\n const draft: LogConfigDraft = {\n level: \"info\",\n banner: true,\n timestamp: \"local\",\n unboundReceive: true,\n levels: {},\n };\n if (section !== undefined) {\n if (typeof section !== \"object\" || section === null) {\n throw kernelError(\"主配置 log 段必须是对象\", \"INVALID_PARAM\");\n }\n const raw = section as Record<string, unknown>;\n applySectionFields(draft, raw);\n draft.levels = parseModuleLevels(raw[\"levels\"]);\n if (raw[\"file\"] !== undefined) {\n draft.file = parseLogFile(raw[\"file\"], draft.level);\n draft.fileLevelExplicit =\n typeof raw[\"file\"] === \"object\" &&\n raw[\"file\"] !== null &&\n (raw[\"file\"] as Record<string, unknown>)[\"level\"] !== undefined;\n }\n }\n applyEnvOverrides(draft, env);\n // 内部传递字段不进最终配置面\n delete draft.fileLevelExplicit;\n return draft;\n}\n","/**\n * 结构化控制台流(single-line / json 格式,2026-10-25 日志可配置化)。\n *\n * pino-pretty 面向交互终端(多行展开 / JSON 内联渲染),其 singleLine 输出为\n * `msg {\"k\":\"v\"}` 形态且 messageFormat 会与字段追加双重渲染——无法干净满足\n * 嵌入式宿主的「一条记录一行、字段 k=v / NDJSON」契约,故自写轻量渲染流:\n *\n * single-line:[时间] LEVEL (name/pid): 消息 key=value key=value\n * json:NDJSON,每行 {time, level, module, account?, pid, msg, ...fields}\n *\n * 时间渲染(LogConfig.timestamp):local = [HH:MM:ss.l](与 pretty 缺省一致)、\n * iso8601 = 2026-10-25T16:01:33.294+08:00、elapsed = +123.456s(进程内单调秒)。\n * json 的 time 恒为 ISO8601(机器可解析契约,不受 timestamp 键影响)。\n */\nimport process from \"node:process\";\nimport type pino from \"pino\";\nimport type { TimestampMode } from \"./log-config.js\";\n\n/** 级别数值 → 名称(pino 标准映射)。 */\nconst LEVEL_LABELS: Record<number, string> = {\n 10: \"trace\",\n 20: \"debug\",\n 30: \"info\",\n 40: \"warn\",\n 50: \"error\",\n 60: \"fatal\",\n};\n\n/** single-line 颜色(colorize 时仅着色级别与元数据头,正文默认色)。 */\nconst LEVEL_COLORS: Record<string, string> = {\n trace: \"\\u001b[34m\",\n debug: \"\\u001b[36m\",\n info: \"\\u001b[32m\",\n warn: \"\\u001b[33m\",\n error: \"\\u001b[31m\",\n fatal: \"\\u001b[91m\",\n};\nconst DIM = \"\\u001b[2m\";\nconst RESET = \"\\u001b[0m\";\n\n/** 结构化流选项。 */\nexport interface ConsoleFormatStreamOptions {\n /** 输出格式(pretty 走 pino-pretty,不进本流)。 */\n format: \"single-line\" | \"json\";\n /** 时间戳渲染模式。 */\n timestamp: TimestampMode;\n /** single-line 是否着色(json 恒不着色)。 */\n colorize: boolean;\n /** 输出目的地(缺省 process.stdout;测试注入用)。 */\n destination?: NodeJS.WriteStream;\n}\n\n/** 2 位补零。 */\nfunction pad2(n: number): string {\n return String(n).padStart(2, \"0\");\n}\n\n/** 单调墙钟毫秒 → 本地时区 ISO8601(+08:00 形态;new Date(epoch) 构造不受 Date.now 篡改影响)。 */\nfunction isoLocalTime(epochMs: number): string {\n const d = new Date(epochMs);\n const offsetMinutes = -d.getTimezoneOffset();\n const sign = offsetMinutes >= 0 ? \"+\" : \"-\";\n const abs = Math.abs(offsetMinutes);\n return (\n `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}` +\n `T${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}` +\n `.${String(d.getMilliseconds()).padStart(3, \"0\")}` +\n `${sign}${pad2(Math.floor(abs / 60))}:${pad2(abs % 60)}`\n );\n}\n\n/** 单调墙钟毫秒 → 本地时分秒(与 pino-pretty SYS:HH:MM:ss.l 一致的短格式)。 */\nfunction localShortTime(epochMs: number): string {\n const d = new Date(epochMs);\n return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}.${String(\n d.getMilliseconds(),\n ).padStart(3, \"0\")}`;\n}\n\n/** 单调墙钟毫秒 → 进程内相对秒(+123.456s)。 */\nfunction elapsedLabel(epochMs: number, timeOriginMs: number): string {\n return `+${((epochMs - timeOriginMs) / 1000).toFixed(3)}s`;\n}\n\n/** 渲染记录时间(timestamp 模式分发;json 恒 ISO8601)。 */\nfunction renderTime(\n rawTime: unknown,\n mode: TimestampMode,\n forceIso: boolean,\n timeOriginMs: number,\n): string {\n if (typeof rawTime === \"string\") {\n // pretty+elapsed 等场景记录时间已是预渲染字符串,原样透出\n return rawTime;\n }\n const epoch = typeof rawTime === \"number\" ? rawTime : timeOriginMs;\n if (forceIso || mode === \"iso8601\") {\n return isoLocalTime(epoch);\n }\n if (mode === \"elapsed\") {\n return elapsedLabel(epoch, timeOriginMs);\n }\n return localShortTime(epoch);\n}\n\n/** 级别数值 → 标签名。 */\nfunction levelLabel(raw: unknown): string {\n if (typeof raw === \"number\") {\n return LEVEL_LABELS[raw] ?? String(raw);\n }\n return typeof raw === \"string\" ? raw.toUpperCase() : \"LOG\";\n}\n\n/** k=v 内联值:字符串裸输出(含空格时不加引号——宽松人类格式),其余 JSON 序列化。 */\nfunction inlineValue(value: unknown): string {\n if (typeof value === \"string\") {\n return value;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\n/** 结构化输出忽略的标准字段(渲染进头/独立字段,不进 k=v 尾部)。 */\nconst RESERVED_KEYS = new Set([\"pid\", \"hostname\", \"level\", \"time\", \"msg\", \"name\"]);\n\n/** 渲染上下文(流构造时固化,渲染函数共享)。 */\ninterface RenderContext {\n timestamp: TimestampMode;\n timeOriginMs: number;\n useColor: boolean;\n}\n\n/** json 渲染:NDJSON 契约 {time, level, module, account?, pid, msg, ...fields}。 */\nfunction renderJsonLine(record: Record<string, unknown>, ctx: RenderContext): string {\n const { time, level, name, module, account, pid, msg: _msg, ...fields } = record;\n const msg = typeof record[\"msg\"] === \"string\" ? record[\"msg\"] : \"\";\n const jsonLine = {\n time: renderTime(time, ctx.timestamp, true, ctx.timeOriginMs),\n level: levelLabel(level).toLowerCase(),\n ...(module !== undefined\n ? { module: module as string }\n : typeof name === \"string\"\n ? { module: name }\n : {}),\n ...(account !== undefined ? { account: account as string } : {}),\n ...(typeof pid === \"number\" ? { pid } : {}),\n msg,\n ...fields,\n };\n return `${JSON.stringify(jsonLine)}\\n`;\n}\n\n/** single-line 元数据头:(name/pid),无 name 时 (pid)。 */\nfunction renderMeta(record: Record<string, unknown>): string {\n const pid = record[\"pid\"] ?? \"-\";\n const name = record[\"name\"];\n return typeof name === \"string\" ? `(${name}/${pid})` : `(${pid})`;\n}\n\n/** single-line 附加字段:非保留键按 k=v 内联(无字段时空串)。 */\nfunction renderKvFields(record: Record<string, unknown>): string {\n const fields: string[] = [];\n for (const [key, value] of Object.entries(record)) {\n if (!RESERVED_KEYS.has(key)) {\n fields.push(`${key}=${inlineValue(value)}`);\n }\n }\n return fields.length > 0 ? ` ${fields.join(\" \")}` : \"\";\n}\n\n/** single-line 渲染:[时间] LEVEL (name/pid): 消息 k=v k=v(级别大写与 pretty 一致)。 */\nfunction renderSingleLine(record: Record<string, unknown>, ctx: RenderContext): string {\n const label = levelLabel(record[\"level\"]);\n const msg = typeof record[\"msg\"] === \"string\" ? record[\"msg\"] : \"\";\n const time = renderTime(record[\"time\"], ctx.timestamp, false, ctx.timeOriginMs);\n const labelUpper = label.toUpperCase();\n const meta = renderMeta(record);\n const fieldsSuffix = renderKvFields(record);\n if (ctx.useColor) {\n const color = LEVEL_COLORS[label.toLowerCase()] ?? \"\";\n return `${DIM}[${time}]${RESET} ${color}${labelUpper}${RESET} ${DIM}${meta}:${RESET} ${msg}${fieldsSuffix}\\n`;\n }\n return `[${time}] ${labelUpper} ${meta}: ${msg}${fieldsSuffix}\\n`;\n}\n\n/** 创建结构化控制台流(single-line / json)。 */\nexport function createConsoleFormatStream(\n opts: ConsoleFormatStreamOptions,\n): pino.DestinationStream {\n const out = opts.destination ?? process.stdout;\n const ctx: RenderContext = {\n timestamp: opts.timestamp,\n timeOriginMs: performance.timeOrigin,\n useColor: opts.format === \"single-line\" && opts.colorize,\n };\n const render = opts.format === \"json\" ? renderJsonLine : renderSingleLine;\n\n return {\n write(line: string) {\n let record: Record<string, unknown>;\n try {\n record = JSON.parse(line) as Record<string, unknown>;\n } catch {\n // 非法 JSON 行原样透出(不吞日志)\n out.write(`${line}\\n`);\n return;\n }\n out.write(render(record, ctx));\n },\n };\n}\n","/**\n * pino 日志封装(ADR-007)\n *\n * - console(pretty 可读输出 / single-line / json 结构化)+ 可选文件双目的地\n * - 统一 level 控制(控制台与文件可分别设级,2026-10-25 日志可配置化)\n * - 内置 redact(token / 票据等敏感字段不打日志),可追加\n * - 无全局单例(ADR-015 推论):每进程实例化一份,由装配层持有\n *\n * 时间源(2026-10-25 修复):QQNT 原生层会在 onRecvSysMsg 回调窗口内篡改进程\n * Date(实测记录时间冻结为假值、回调外恢复),故时间戳统一取\n * performance.timeOrigin + performance.now() 单调派生墙钟,不受 Date 篡改影响。\n */\nimport { mkdirSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport process from \"node:process\";\nimport pino from \"pino\";\nimport pinoPretty from \"pino-pretty\";\nimport { KernelError } from \"./errors.js\";\nimport type { LogFormat, TimestampMode } from \"./log-config.js\";\nimport { createConsoleFormatStream } from \"./log-format.js\";\n\n/** 默认 redact 路径:顶层 token/票据 + 任意深度的密码/密钥。 */\nconst DEFAULT_REDACT_PATHS = [\"token\", \"ticket\", \"cookie\", \"password\", \"secret\"] as const;\n\n/** 级别数值序(求多目的地最低级别用)。 */\nconst LEVEL_ORDER: readonly pino.LevelWithSilent[] = [\n \"trace\",\n \"debug\",\n \"info\",\n \"warn\",\n \"error\",\n \"fatal\",\n \"silent\",\n];\n\n/** 取两级别中更低(更宽容)的一个。 */\nfunction minLevel(a: pino.LevelWithSilent, b: pino.LevelWithSilent): pino.LevelWithSilent {\n return LEVEL_ORDER.indexOf(a) <= LEVEL_ORDER.indexOf(b) ? a : b;\n}\n\n/** 单调墙钟毫秒(timeOrigin 为进程启动真实 Unix 时间,不受进程内 Date 篡改影响)。 */\nfunction wallMs(): number {\n return performance.timeOrigin + performance.now();\n}\n\n/** 进程内相对秒标签(elapsed 模式,pretty 字符串时间路径用)。 */\nfunction elapsedLabelNow(): string {\n return `+${(performance.now() / 1000).toFixed(3)}s`;\n}\n\nfunction toSingle(streams: Array<{ stream: pino.DestinationStream }>): pino.DestinationStream {\n const [only] = streams;\n if (only === undefined) {\n // 不可达:调用方已保证 streams 非空\n throw new KernelError(\"logger 内部错误:目的地列表为空\", \"UNKNOWN\");\n }\n return only.stream;\n}\n\n/** 日志级别(pino 标准级别 + silent)。 */\nexport type LogLevel = \"trace\" | \"debug\" | \"info\" | \"warn\" | \"error\" | \"fatal\" | \"silent\";\n\n/**\n * 可选属性统一带 `| undefined`(2026-10-25):exactOptionalPropertyTypes 下\n * 纯直传调用点(装配层透传 LogConfig 字段)可写 `format: cfg.format` 一类\n * 直赋值,免去逐字段条件展开(展开链是复杂度热点)。\n */\ntype Optional<T> = T | undefined;\n\nexport interface LoggerOptions {\n /** 控制台级别,默认 `info`。 */\n level?: Optional<LogLevel>;\n /** 是否输出到 console,默认 true。 */\n console?: Optional<boolean>;\n /** 文件日志路径;不传则不写文件(父目录自动创建)。 */\n file?: Optional<string>;\n /** 文件目的地级别(缺省同 level;可低于控制台级别保留排查信息)。 */\n fileLevel?: Optional<LogLevel>;\n /**\n * 输出格式,默认按 TTY 自动:TTY = pretty(终端体验不变)/ pipe = single-line\n * (嵌入式逐行转发友好)。显式指定后不受 TTY 影响。\n */\n format?: Optional<LogFormat>;\n /** 时间戳渲染模式(local/iso8601/elapsed),默认 local。 */\n timestamp?: Optional<TimestampMode>;\n /** 控制台颜色(缺省自动:pretty 且 TTY 才着色)。 */\n colorize?: Optional<boolean>;\n /** 控制台目的地(缺省 process.stdout;测试注入用)。 */\n destination?: Optional<NodeJS.WriteStream>;\n /** 附加基础字段(如 `{ name: 'kernel' }`),自动保留 pid;name 用 pino 保留字段\n * (logger name),pino-pretty 渲染为 `(name/pid)` 元数据头。 */\n base?: Optional<Record<string, unknown>>;\n /** 追加的敏感字段路径(默认内置 token/票据等,见 DEFAULT_REDACT_PATHS)。 */\n redact?: string[];\n}\n\n/**\n * 创建 pino logger:console(pretty/single-line/json)+ 可选文件,统一 level / redact。\n *\n * 文件目的地用同步写入(`sync: true`):日志量级下性能可接受,且进程退出不丢日志,\n * 无需额外的 flush 步骤(对比异步 SonicBoom 的 flushSync 时序陷阱)。\n * 文件目的地恒为 JSON 行(数值时间戳),格式/时间戳渲染仅作用于控制台目的地。\n */\n/** 控制台流构造参数(格式分支共用)。 */\ninterface ConsoleStreamParams {\n format: LogFormat;\n timestampMode: TimestampMode;\n colorize: boolean;\n destination: NodeJS.WriteStream;\n}\n\n/** 构造控制台流:pretty 走 pino-pretty,single-line/json 走结构化渲染流。 */\nfunction buildConsoleStream(params: ConsoleStreamParams): pino.DestinationStream {\n const { format, timestampMode, colorize, destination } = params;\n if (format !== \"pretty\") {\n return createConsoleFormatStream({\n format,\n timestamp: timestampMode,\n colorize,\n destination,\n });\n }\n return pinoPretty({\n colorize,\n // 短格式时间(2026-08-07 用户定稿):完整日期+时区前缀近 60 字符,\n // 每条日志重复占满横向视线;缩短为时分秒+毫秒,文件日志(JSON)\n // 仍保留完整时间戳可精确追溯。\n // ⚠️ 必须带 SYS: 前缀:无前缀按 UTC 渲染(北京 = UTC+8,时间会差 8 小时),\n // SYS: 才用本地系统时间。iso8601 模式用完整日期+时区后缀;elapsed 模式\n // 时间为预渲染字符串(不可解析则 pretty 原样输出),不走 translateTime。\n ...(timestampMode === \"local\" ? { translateTime: \"SYS:HH:MM:ss.l\" as const } : {}),\n ...(timestampMode === \"iso8601\"\n ? { translateTime: \"SYS:yyyy-mm-dd'T'HH:MM:ss.l Z\" as const }\n : {}),\n // 原生多行展开(默认值,显式声明):属性按 key/value 逐行渲染,\n // 由 pino-pretty 自带颜色高亮(key 紫色 / 字符串青色),不手写格式化\n // 函数对抗原生渲染——高频业务日志(如收到消息)在调用点直接传\n // 纯字符串,不传对象,天然单行。\n singleLine: false,\n // ⚠️ 必须显式传 destination=process.stdout:pino-pretty 默认用\n // sonic-boom 直写 fd 1(UTF-8 字节流),在 pnpm start(cmd.exe +\n // 管道 936 转码)链路下中文被按 GBK 解码成乱码、ANSI 残留;\n // 走 process.stdout 的 TTY 路径(WriteConsoleW UTF-16)编码正确。\n destination,\n });\n}\n\n/** 解析后的渲染参数(createLogger 决策折叠,降低主函数复杂度)。 */\ninterface ResolvedRenderParams {\n format: LogFormat;\n timestampMode: TimestampMode;\n colorize: boolean;\n destination: NodeJS.WriteStream;\n}\n\n/** 折叠 LoggerOptions 的渲染缺省:格式按 TTY 自动(TTY pretty / pipe single-line)、\n * 颜色仅在 pretty 且 TTY 时开。 */\nfunction resolveRenderParams(opts: LoggerOptions): ResolvedRenderParams {\n const destination = opts.destination ?? process.stdout;\n const isTty = destination.isTTY === true;\n const format: LogFormat = opts.format ?? (isTty ? \"pretty\" : \"single-line\");\n return {\n format,\n timestampMode: opts.timestamp ?? \"local\",\n colorize: opts.colorize ?? (format === \"pretty\" && isTty),\n destination,\n };\n}\n\nexport function createLogger(opts: LoggerOptions = {}): pino.Logger {\n const consoleLevel = opts.level ?? \"info\";\n const fileLevel = opts.fileLevel ?? consoleLevel;\n const { file, base } = opts;\n const render = resolveRenderParams(opts);\n\n const streams: Array<{ stream: pino.DestinationStream; level: pino.LevelWithSilent }> = [];\n if ((opts.console ?? true) === true) {\n streams.push({ stream: buildConsoleStream(render), level: consoleLevel });\n }\n if (file) {\n mkdirSync(dirname(file), { recursive: true });\n streams.push({ stream: pino.destination({ dest: file, sync: true }), level: fileLevel });\n }\n if (streams.length === 0) {\n throw new KernelError(\"logger 至少需要一个输出目标:console 或 file\", \"INVALID_PARAM\");\n }\n\n // 记录产生门槛 = 全部目的地中最低级别(multistream 再按各自级别分发过滤)\n let gate: pino.LevelWithSilent = \"fatal\";\n for (const entry of streams) {\n gate = minLevel(gate, entry.level);\n }\n\n // 时间戳:local/iso8601 记数值(单调墙钟)交渲染层;elapsed 在 pretty 字符串\n // 时间路径预渲染(pino-pretty 对不可解析字符串时间原样输出)\n const pinoTimestamp =\n render.format === \"pretty\" && render.timestampMode === \"elapsed\"\n ? (): string => `,\"time\":\"${elapsedLabelNow()}\"`\n : (): string => `,\"time\":${wallMs()}`;\n\n const pinoOptions: pino.LoggerOptions = {\n level: gate,\n redact: [...DEFAULT_REDACT_PATHS, ...(opts.redact ?? [])],\n timestamp: pinoTimestamp,\n };\n if (base) {\n pinoOptions.base = { pid: process.pid, ...base };\n }\n\n let stream: pino.DestinationStream | ReturnType<typeof pino.multistream>;\n if (streams.length === 1) {\n stream = toSingle(streams);\n } else {\n stream = pino.multistream(streams);\n }\n\n return pino(pinoOptions, stream);\n}\n","/**\n * 数据目录布局(ADR-016,2026-08-08 修订:数据根默认放项目根)\n *\n * ```\n * <数据根>/<qq号>/ # 每账号独立目录(ADR-015 多账号前提)\n * ├── config/ # 各账号独立配置(历史产物,协议段已走 seed)\n * ├── logs/ # pino 文件日志\n * └── cache/ # 临时文件、媒体缓存\n *\n * <项目根>/napuketto.toml # 全局配置文件(放项目根目录,不入库)\n * <项目根>/.napuketto/ # 数据根(默认值,2026-08-08 用户拍板:直接放项目主目录)\n * ```\n *\n * 数据根优先级:cli `--data-dir`(显式参数)> `NAPKETTO_DATA` 环境变量 >\n * `<项目根>/.napuketto`(默认,部署原因不放用户目录)。\n * 配置文件路径见 `resolveConfigPath`(项目根探测,与数据根解耦)。\n */\nimport { existsSync, mkdirSync, readdirSync, rmSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport process from \"node:process\";\nimport { fileURLToPath } from \"node:url\";\n\n/** 默认数据根目录名(项目根下,2026-08-08 用户拍板:不放用户目录)。 */\nexport const DEFAULT_DATA_ROOT_NAME = \".napuketto\";\n\n/** 全局配置文件名。 */\nexport const MAIN_CONFIG_FILE = \"napuketto.toml\";\n\nexport interface PathOptions {\n /** 显式数据根(cli `--data-dir` 传入,优先级最高)。 */\n dataRoot?: string;\n /** QQ 号,用于多账号隔离;缺省时直接使用数据根本身。 */\n account?: string;\n}\n\n/** `~` 前缀展开为用户主目录(跨平台配置文件写法 `dataDir = \"~/.napuketto\"`)。 */\nfunction expandTilde(p: string): string {\n if (p === \"~\") {\n return homedir();\n }\n if (p.startsWith(\"~/\") || p.startsWith(\"~\\\\\")) {\n return join(homedir(), p.slice(2));\n }\n return p;\n}\n\n/**\n * 项目根查找:入口模块向上找 `pnpm-workspace.yaml`(monorepo 开发场景,即仓库根)→\n * cwd 向上找 `pnpm-workspace.yaml` / `package.json`(发布后用户项目)→ undefined(兜底)。\n */\nfunction resolveProjectRoot(): string | undefined {\n const moduleDir = dirname(fileURLToPath(import.meta.url));\n const moduleRoot = findProjectRoot(moduleDir, \"pnpm-workspace.yaml\");\n if (moduleRoot !== undefined) {\n return moduleRoot;\n }\n return (\n findProjectRoot(process.cwd(), \"pnpm-workspace.yaml\") ??\n findProjectRoot(process.cwd(), \"package.json\")\n );\n}\n\n/**\n * 解析数据根:显式参数 > 环境变量 > `<项目根>/.napuketto`(默认)> 用户目录兜底(裸脚本)。\n * 支持 `~` 前缀(展开为用户主目录)与相对路径(相对 cwd 解析)。\n * 返回绝对路径。独立导出便于 cli / 探测脚本复用。\n */\nexport function resolveDataRoot(dataRoot?: string): string {\n const explicit = dataRoot ?? process.env[\"NAPKETTO_DATA\"];\n if (explicit !== undefined && explicit !== \"\") {\n return resolve(expandTilde(explicit));\n }\n const projectRoot = resolveProjectRoot();\n if (projectRoot !== undefined) {\n return join(projectRoot, DEFAULT_DATA_ROOT_NAME);\n }\n return join(homedir(), DEFAULT_DATA_ROOT_NAME);\n}\n\n/** 向上查找含标志文件的目录(项目根探测:monorepo 根 / 用户项目根)。 */\nfunction findProjectRoot(startDir: string, marker: string): string | undefined {\n let dir = startDir;\n for (;;) {\n if (existsSync(join(dir, marker))) {\n return dir;\n }\n const parent = dirname(dir);\n if (parent === dir) {\n return undefined;\n }\n dir = parent;\n }\n}\n\nexport interface ConfigPathOptions {\n /** 兜底数据根(探测全部失败时用 `<dataRoot>/napuketto.toml`,保持旧行为)。 */\n dataRoot?: string;\n}\n\n/**\n * 全局配置文件路径解析(2026-08-07 用户拍板:**配置文件放项目根目录,\n * 数据仍按数据根组织**——数据根只承载账号目录/日志/缓存/QQ 数据)。\n *\n * 优先级:\n * 1. `NAPKETTO_CONFIG` 环境变量(显式指定完整路径,任意场景可用)\n * 2. 项目根探测(入口模块向上找 pnpm-workspace.yaml → cwd 向上找,见 resolveProjectRoot)\n * 3. 数据根兜底 `<dataRoot>/napuketto.toml`(旧行为兼容)\n *\n * 返回绝对路径。独立导出便于 cli / loader 装配链复用。\n */\nexport function resolveConfigPath(opts: ConfigPathOptions = {}): string {\n const explicit = process.env[\"NAPKETTO_CONFIG\"];\n if (explicit !== undefined && explicit !== \"\") {\n return resolve(explicit);\n }\n const projectRoot = resolveProjectRoot();\n if (projectRoot !== undefined) {\n return join(projectRoot, MAIN_CONFIG_FILE);\n }\n return join(opts.dataRoot ?? resolveDataRoot(), MAIN_CONFIG_FILE);\n}\n\n/**\n * 路径装配器:持有账号数据目录下的全部子目录。\n * 无全局单例(ADR-015 推论)——每账号每进程实例化一份。\n */\nexport class PathWrapper {\n /** 数据根(绝对路径)。 */\n readonly root: string;\n /** 账号数据目录(绝对路径),缺省 account 时等于 root。 */\n readonly accountDir: string;\n /** config 目录(napuketto.json + 各协议配置)。 */\n readonly configDir: string;\n /** logs 目录(pino 文件日志)。 */\n readonly logsDir: string;\n /** cache 目录(临时文件、媒体缓存)。 */\n readonly cacheDir: string;\n\n constructor(opts: PathOptions = {}) {\n const root = resolveDataRoot(opts.dataRoot);\n this.root = root;\n if (opts.account) {\n this.accountDir = join(root, opts.account);\n } else {\n this.accountDir = root;\n }\n this.configDir = join(this.accountDir, \"config\");\n this.logsDir = join(this.accountDir, \"logs\");\n this.cacheDir = join(this.accountDir, \"cache\");\n }\n\n /** 递归创建全部目录,启动装配时调用一次。 */\n ensure(): void {\n for (const dir of [this.configDir, this.logsDir, this.cacheDir]) {\n mkdirSync(dir, { recursive: true });\n }\n }\n\n /** 账号目录内的文件路径(如 `file('config', 'napuketto.json')`)。 */\n file(...segments: string[]): string {\n return join(this.accountDir, ...segments);\n }\n\n /**\n * 清空缓存目录(clean_cache 动作经注入回调消费)。\n * 只删除 cacheDir 下的条目,保留目录本身。\n */\n clearCache(): void {\n let entries: string[];\n try {\n entries = readdirSync(this.cacheDir);\n } catch {\n return; // 目录不存在视为空\n }\n for (const entry of entries) {\n rmSync(join(this.cacheDir, entry), { recursive: true, force: true });\n }\n }\n}\n","/**\n * 原生调用结果解包(ADR-009 统一错误语义)\n *\n * wrapper.node 的 service 方法大多返回 `{ result, errMsg }`:\n * - unwrap:result 为 number 且非 0 → 抛 KernelError\n * - checkLooseResult:宽松形状(result 可能缺失/非数字)→ 仅当 result 为\n * 数字且非 0 时抛错(探测期兼容:某些 API 成功时 result 缺省或为对象)\n * - unwrapResult:result !== 0 → 按 errMsg 内容映射语义错误码(msg 系专用)\n */\n\nimport { type KernelErrorCode, kernelError } from \"../infra/index.js\";\nimport type { GeneralCallResult } from \"../types/index.js\";\n\n/** errMsg 关键词 → 错误码规则(顺序即优先级,命中任一组关键词即映射)。 */\nconst RESULT_CODE_RULES: ReadonlyArray<{ code: KernelErrorCode; keywords: readonly string[] }> = [\n { code: \"NOT_LOGIN\", keywords: [\"未登录\", \"login\"] },\n { code: \"PERMISSION_DENIED\", keywords: [\"无权限\", \"permission\"] },\n { code: \"NOT_FOUND\", keywords: [\"不存在\", \"not found\"] },\n];\n\n/** 按 errMsg 关键词映射错误码(未命中 → UNKNOWN)。 */\nfunction mapResultCode(msg: string): KernelErrorCode {\n for (const rule of RESULT_CODE_RULES) {\n if (rule.keywords.some((k) => msg.includes(k))) {\n return rule.code;\n }\n }\n return \"UNKNOWN\";\n}\n\n/** 原生 result 解包(result 字段非 0 抛 KernelError)。 */\nexport function unwrap(label: string, result: number, errMsg?: string): void {\n if (result === 0) {\n return;\n }\n throw kernelError(`${label} 失败: ${errMsg ?? \"无错误详情\"}`, \"UNKNOWN\");\n}\n\n/** 宽松 result 校验(result 为数字非 0 视为失败;缺失/非数字视为成功)。 */\nexport function checkLooseResult(\n label: string,\n res: { result?: unknown; errMsg?: unknown } | null | undefined,\n): void {\n if (res === undefined || res === null || typeof res.result !== \"number\" || res.result === 0) {\n return;\n }\n throw kernelError(`${label} 失败: ${String(res.errMsg ?? \"\")}`, \"UNKNOWN\");\n}\n\n/** 原生结果解包:result !== 0 抛 KernelError(errMsg 语义映射错误码)。 */\nexport function unwrapResult<T extends GeneralCallResult>(label: string, raw: T): void {\n if (raw.result === 0) {\n return;\n }\n const msg = raw.errMsg || \"无错误详情\";\n // 语义码优先(未登录/无权限/不存在),未命中且为 sendMsg 时兜底 SEND_FAILED\n let code = mapResultCode(msg);\n if (code === \"UNKNOWN\" && label === \"sendMsg\") {\n code = \"SEND_FAILED\";\n }\n throw kernelError(`${label} 失败: ${msg}`, code);\n}\n","/**\n * FriendApi:好友语义化 API(ADR-009 统一错误语义)\n *\n * 内部解包原生返回:成功返回纯业务值,失败抛 KernelError。\n * 方法面(P2-4 + P2-11):好友列表(uid + 昵称/备注)+ 好友请求\n * (getBuddyReq / approvalFriendRequest / setBuddyRemark / delBuddy /\n * getDoubtBuddyReq / approvalDoubtBuddyReq)。\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n BuddyCategory,\n BuddyReq,\n DoubtBuddyReq,\n NodeIKernelBuddyService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\nimport { checkLooseResult, unwrap } from \"./result.js\";\n\n/** 好友条目(uid + 昵称/备注,昵称由 buddy service 缓存补全)。 */\nexport interface Friend {\n uid: string;\n uin: string;\n nickname: string;\n remark: string;\n}\n\n/** 带分类的好友列表项(get_friends_with_category 返回)。 */\nexport interface FriendCategory {\n categoryId: number;\n categorySortId: number;\n categoryName: string;\n categoryMbCount: number;\n onlineCount: number;\n buddyList: Friend[];\n}\n\n/** 可疑好友申请(OB11 结构,get_doubt_friends_add_request 返回)。 */\nexport interface DoubtFriendRequestInfo {\n flag: string;\n uin: number;\n nick?: string;\n source?: string;\n reason?: string;\n msg?: string;\n group_code?: string;\n time?: string;\n type: \"doubt\";\n}\n\n/** FriendApi 构造选项。 */\nexport interface FriendApiOptions {\n /** uid→uin 转换器(通常注入 GroupApi.uidToUin);缺省 uin 退化为 uid。 */\n uidToUin?: (uids: string[]) => Promise<Map<string, string>>;\n}\n\n/** 好友 API:从 session 拿 buddy service,包装成语义化方法。 */\nexport class FriendApi {\n private readonly service: NodeIKernelBuddyService;\n private readonly uidToUin: ((uids: string[]) => Promise<Map<string, string>>) | undefined;\n\n constructor(session: NodeIQQNTWrapperSession, opts: FriendApiOptions = {}) {\n const service = session.getBuddyService() as unknown as NodeIKernelBuddyService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getBuddyService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n this.uidToUin = opts.uidToUin;\n }\n\n /** 好友列表(分类拍平为 Friend[];昵称/备注从 service 缓存读取)。 */\n async getFriendList(): Promise<Friend[]> {\n let categories: BuddyCategory[];\n try {\n categories = await this.service.getBuddyListFromCache(0);\n } catch {\n const raw = await this.service.getBuddyListV2(\"cli\", true, 0);\n if (raw.result !== 0) {\n throw kernelError(`getBuddyListV2 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n categories = raw.data;\n }\n // buddyList(明细,T10 实证)优先,buddyUids(旧描述字段)兜底\n const uids = categories.flatMap(\n (cat) => cat.buddyList?.map((b) => b.uid) ?? cat.buddyUids ?? [],\n );\n let uinMap = new Map<string, string>();\n if (this.uidToUin !== undefined) {\n uinMap = await this.uidToUin(uids);\n }\n const out: Friend[] = [];\n for (const uid of uids) {\n out.push({\n uid,\n uin: uinMap.get(uid) ?? uid,\n nickname: this.service.getBuddyNick(uid),\n remark: this.service.getBuddyRemark(uid),\n });\n }\n return out;\n }\n\n /** 带分类的好友列表(get_friends_with_category,分类保留)。 */\n async getFriendCategories(): Promise<FriendCategory[]> {\n const categories = await this.getBuddyCategories();\n const friendMap = new Map<string, Friend>();\n for (const friend of await this.getFriendList()) {\n friendMap.set(friend.uid, friend);\n }\n return categories.map((cat) => {\n const buddies: Friend[] = [];\n for (const uid of cat.buddyList?.map((b) => b.uid) ?? cat.buddyUids ?? []) {\n const friend = friendMap.get(uid);\n if (friend !== undefined) {\n buddies.push(friend);\n }\n }\n return {\n categoryId: cat.categoryId,\n categorySortId: cat.categorySortId,\n categoryName: cat.categroyName,\n categoryMbCount: cat.categroyMbCount,\n onlineCount: cat.onlineCount,\n buddyList: buddies,\n };\n });\n }\n\n /** 原始分类(getBuddyListV2 / 缓存)。 */\n private async getBuddyCategories(): Promise<BuddyCategory[]> {\n try {\n return await this.service.getBuddyListFromCache(0);\n } catch {\n const raw = await this.service.getBuddyListV2(\"cli\", true, 0);\n if (raw.result !== 0) {\n throw kernelError(`getBuddyListV2 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return raw.data;\n }\n }\n\n /** 判断是否好友。 */\n isBuddy(uid: string): boolean {\n return this.service.isBuddy(uid);\n }\n\n /** 好友申请列表(set_friend_add_request 应答前查找匹配项)。 */\n async getBuddyReqList(): Promise<BuddyReq[]> {\n const raw = await this.service.getBuddyReq();\n unwrap(\"getBuddyReq\", raw.result, raw.errMsg);\n return raw.buddyReqs ?? [];\n }\n\n /** 应答好友申请(accept=true 同意;remark 由调用方另行 setFriendRemark)。 */\n async handleFriendRequest(notify: BuddyReq, accept: boolean): Promise<void> {\n await this.service.approvalFriendRequest({\n friendUid: notify.friendUid,\n reqTime: notify.reqTime,\n accept,\n });\n }\n\n /** 设置好友备注(void 语义乐观处理)。 */\n setFriendRemark(uid: string, remark: string): void {\n this.service.setBuddyRemark({ uid, remark });\n }\n\n /** 删除好友(tempBlock=拉黑,tempBothDel=双向删除)。 */\n async deleteFriend(uid: string, tempBlock = false, tempBothDel = false): Promise<void> {\n const res = (await this.service.delBuddy({\n friendUid: uid,\n tempBlock,\n tempBothDel,\n })) as { result?: unknown; errMsg?: unknown } | undefined;\n checkLooseResult(\"delBuddy\", res);\n }\n\n /** 可疑好友申请列表(get_doubt_friends_add_request;uin 经 uidToUin 转换)。 */\n async getDoubtFriendRequest(count: number): Promise<DoubtFriendRequestInfo[]> {\n const reqId = String(Date.now());\n const raw = await this.service.getDoubtBuddyReq(reqId, count, \"\");\n unwrap(\"getDoubtBuddyReq\", raw.result, raw.errMsg);\n const list: DoubtBuddyReq[] = raw.doubtList ?? [];\n let uinMap = new Map<string, string>();\n if (this.uidToUin !== undefined && list.length > 0) {\n uinMap = await this.uidToUin(list.map((item) => item.uid));\n }\n const out: DoubtFriendRequestInfo[] = [];\n for (const item of list) {\n out.push(this.toDoubtFriendRequestInfo(item, uinMap));\n }\n return out;\n }\n\n /** 单项转换:DoubtBuddyReq → OB11 结构(可选字段条件赋值,exactOptionalPropertyTypes)。 */\n private toDoubtFriendRequestInfo(\n item: DoubtBuddyReq,\n uinMap: Map<string, string>,\n ): DoubtFriendRequestInfo {\n return toDoubtFriendRequestInfo(item, uinMap);\n }\n\n /** 处理可疑好友申请(set_doubt_friends_add_request;uid=flag)。 */\n handleDoubtFriendRequest(uid: string): void {\n this.service.approvalDoubtBuddyReq(uid, \"\", \"\");\n }\n}\n\n/** 可选字段条件赋值(undefined 跳过,兼容 exactOptionalPropertyTypes)。 */\nfunction assignOptional<K extends keyof DoubtFriendRequestInfo>(\n info: DoubtFriendRequestInfo,\n key: K,\n value: DoubtFriendRequestInfo[K] | undefined,\n): void {\n if (value !== undefined) {\n info[key] = value;\n }\n}\n\n/** 单项转换:DoubtBuddyReq → OB11 结构(导出供单测,friend.test.ts)。 */\nexport function toDoubtFriendRequestInfo(\n item: DoubtBuddyReq,\n uinMap: Map<string, string>,\n): DoubtFriendRequestInfo {\n const info: DoubtFriendRequestInfo = {\n flag: item.uid,\n uin: Number(uinMap.get(item.uid) ?? item.uid),\n type: \"doubt\",\n };\n assignOptional(info, \"nick\", item.nick);\n assignOptional(info, \"source\", item.source);\n assignOptional(info, \"reason\", item.reason);\n assignOptional(info, \"msg\", item.msg);\n assignOptional(info, \"group_code\", item.groupCode);\n assignOptional(info, \"time\", item.reqTime);\n return info;\n}\n","/**\n * 实体类型层(运行时探测产物 + 公开资料作说明书,ADR-006)\n *\n * ⚠️ 部分字段按公开资料(wrapper 外部契约)自研描述,待下一次进程内\n * 探测(probe.ts)校准——接口签名是外部系统事实,自研描述其形状。\n */\n\n/** 会话类型(QQ wrapper 外部契约)。 */\nexport const ChatType = {\n C2C: 1, // 私聊\n GROUP: 2, // 群聊\n DISC: 3, // 讨论组\n GUILD: 4, // 频道\n C2C_TEMP: 100, // 临时会话(群内私聊)\n} as const;\nexport type ChatType = (typeof ChatType)[keyof typeof ChatType];\n\n/** 会话对象(peer 目标:群号 / 用户 uid)。 */\nexport interface Peer {\n chatType: ChatType;\n peerUid: string;\n guildId?: string;\n}\n\n/** 文本元素(at 用 atType + atUid 表达)。 */\nexport interface TextElement {\n content: string;\n atType?: number; // 1=全体 2=单人 4=我\n atUid?: string;\n}\n\n/** 图片元素(发送时 picPath 必须;接收含 url)。 */\nexport interface PicElement {\n picPath?: string;\n picUrl?: string;\n picType?: number;\n picWidth?: number;\n picHeight?: number;\n md5?: string;\n fileName?: string;\n sourcePath?: string;\n thumbPath?: string;\n // 发送用完整字段(NapCat 式,2026-08-11 实证:缺任一发送失败)\n md5HexStr?: string;\n fileSize?: string;\n original?: boolean;\n picSubType?: number;\n fileUuid?: string;\n fileSubId?: string;\n thumbFileSize?: number;\n summary?: string;\n}\n\n/** 语音元素(silk)。 */\nexport interface PttElement {\n filePath?: string;\n md5?: string;\n fileName?: string;\n duration?: number;\n /** 语音转文字结果(translatePtt2Text 异步写回)。 */\n text?: string;\n // 发送用完整字段(NapCat 式,2026-08-12 实证:只给 filePath 会触发 wrapper\n // 内部 \"Cannot convert undefined or null to object\"(缺字段转换失败))\n md5HexStr?: string;\n fileSize?: string;\n formatType?: number;\n voiceType?: number;\n voiceChangeType?: number;\n canConvert2Text?: boolean;\n waveAmplitudes?: number[];\n fileSubId?: string;\n playState?: number;\n autoConvertText?: number;\n storeID?: number;\n otherBusinessInfo?: { aiVoiceType: number };\n // 收向完整字段(2026-09-08 B1 实测快照,getLatestDbMsgs 历史消息提取)\n fileId?: number;\n fileUuid?: string;\n translateStatus?: number;\n /** 传输状态:实测 2=收到已下载(数据库态,此时挪走本地文件调 downloadRichMedia 不重下)、4=自己发送已上传。 */\n transferStatus?: number;\n progress?: number;\n invalidState?: number;\n fileBizId?: unknown;\n import_rich_media_context?: unknown;\n isInApplicationDataPath?: boolean | null;\n}\n\n/** 表情元素。 */\nexport interface FaceElement {\n faceIndex: number;\n faceType?: number;\n}\n\n/** 回复元素(引用消息)。 */\nexport interface ReplyElement {\n replayMsgId: string;\n replayMsgSeq?: string;\n replayMsgTime?: string;\n senderUid?: string;\n senderUin?: string;\n}\n\n/** 视频元素(接收 + 发送)。 */\nexport interface VideoElement {\n filePath?: string;\n videoUrl?: string;\n fileName?: string;\n fileSize?: string;\n // 接收字段(2026-10-17 收向实测快照,getLatestDbMsgs 历史视频消息)\n videoMd5?: string;\n thumbMd5?: string;\n /** 时长(秒)。 */\n fileTime?: number;\n thumbSize?: number;\n thumbWidth?: number;\n thumbHeight?: number;\n fileFormat?: number;\n busiType?: number;\n subBusiType?: number;\n /**\n * 缩略图路径。进程内收向实测 `Map<number, string>`(key 0 = Thumb 图片路径);\n * 经 JSON 序列化(IPC 行协议等)后退化为平面对象(`{\"0\": path}`,且无规整的\n * 传输会丢成空对象 `{}`)。接收读取统一走 message-element 的\n * readVideoThumbPath(Map / 平面对象 / 字符串三形态均容忍)。\n * 发送时必须传真实 Map——JSON 序列化会丢成 `{}`,wrapper 转换失败。\n */\n thumbPath?: Map<number, string> | Record<string, string> | string;\n transferStatus?: number;\n progress?: number;\n invalidState?: number;\n fileUuid?: string;\n fileSubId?: string;\n fileBizId?: unknown;\n originVideoMd5?: string;\n import_rich_media_context?: unknown;\n sourceVideoCodecFormat?: number;\n storeID?: number;\n original?: boolean;\n videoFrom?: number;\n isInApplicationDataPath?: boolean | null;\n /** 发送补充字段(PIC/PTT 同款 md5 字段名)。 */\n md5HexStr?: string;\n}\n\n/** 文件元素。 */\nexport interface FileElement {\n filePath?: string;\n fileName?: string;\n fileSize?: string;\n fileUuid?: string;\n}\n\n/** 灰色提示子类型(系统事件类型,QQ wrapper 契约)。 */\nexport const GrayTipSubType = {\n REVOKE: 1, // 撤回\n PROCLAMATION: 2, // 群公告\n GROUP: 4, // 群成员变动(TipGroupElement)\n BUDDY: 5, // 好友变动\n FEED: 6,\n ESSENCE: 7, // 精华消息\n GROUP_NOTIFY: 8, // 群通知\n BUDDY_NOTIFY: 9,\n FILE: 10,\n JSON: 17,\n} as const;\nexport type GrayTipSubType = (typeof GrayTipSubType)[keyof typeof GrayTipSubType];\n\n/** 群成员变动类型(TipGroupElement.type)。 */\nexport const TipGroupElementType = {\n MEMBER_ADD: 1, // 入群\n DISBANDED: 2, // 群解散\n QUIT: 3, // 退群\n CREATED: 4, // 建群\n GROUP_NAME_MODIFIED: 5, // 群名修改\n BLOCK: 6,\n UNBLOCK: 7,\n SHUT_UP: 8, // 禁言\n} as const;\nexport type TipGroupElementType = (typeof TipGroupElementType)[keyof typeof TipGroupElementType];\n\n/** 撤回元素(revokeElement)。 */\nexport interface GrayTipRevokeElement {\n operatorUid: string;\n operatorNick?: string;\n operatorRole?: string;\n isSelfOperate?: boolean;\n wording?: string;\n}\n\n/** AIOP 操作元素(戳一戳等,aioOpGrayTipElement)。 */\nexport interface TipAioOpGrayTipElement {\n operateType?: number;\n peerUid?: string;\n fromGrpCodeOfTmpChat?: string;\n}\n\n/** 群成员变动元素(groupElement 内的 TipGroupElement)。 */\nexport interface TipGroupElement {\n type?: TipGroupElementType;\n groupName?: string;\n memberUid?: string;\n memberNick?: string;\n memberRemark?: string;\n adminUid?: string;\n adminNick?: string;\n adminRemark?: string;\n shutUp?: {\n curTime?: string;\n duration?: string; // 禁言秒数\n admin?: { uid?: string; card?: string; name?: string; role?: number };\n member?: { uid?: string; card?: string; name?: string; role?: number };\n };\n}\n\n/** 灰色提示元素(elementType=8,系统事件载体)。 */\nexport interface GrayTipElement {\n subElementType?: GrayTipSubType;\n revokeElement?: GrayTipRevokeElement;\n groupElement?: TipGroupElement;\n aioOpGrayTipElement?: TipAioOpGrayTipElement;\n xmlElement?: { busiId?: string; content?: string; templId?: string };\n jsonGrayTipElement?: { busiId?: string; jsonStr?: string };\n}\n\n/** 消息元素(RawMessage.elements 成员,elementType + 子元素互斥)。 */\nexport interface RawElement {\n elementType: number;\n elementId?: string;\n textElement?: TextElement;\n picElement?: PicElement;\n pttElement?: PttElement;\n faceElement?: FaceElement;\n replyElement?: ReplyElement;\n videoElement?: VideoElement;\n fileElement?: FileElement;\n grayTipElement?: GrayTipElement;\n /** ARK 卡片元素(elementType=10 ARK,小程序/分享卡片,2026-10-25 B 站卡片实测)。bytesData 为卡片 JSON 字符串;其余字段待探测校准。 */\n arkElement?: { bytesData: string };\n /** 合并转发元素(elementType=16 MULTI_FORWARD)。 */\n multiForwardMsgElement?: {\n resId: string;\n fileName: string;\n xmlContent: string;\n };\n}\n\n/** QQ 消息(RawMessage,说明书参考字段,探测后校准)。 */\nexport interface RawMessage {\n msgId: string; // 雪花 ID\n msgSeq: string; // 消息序列号\n msgTime: string; // 时间戳\n msgRandom?: string;\n msgType: number;\n chatType: ChatType;\n peerUid: string; // 群号 / 用户 uid\n peerUin: string; // 群号 / 用户 QQ 号\n senderUid: string;\n senderUin: string;\n peerName: string;\n sendNickName: string;\n sendMemberName?: string;\n /** 发送状态(0=失败 1=发送中 2=成功 3=成功无seq)。onMsgInfoListUpdate 事件填充。 */\n sendStatus?: number;\n elements: RawElement[];\n records?: RawMessage[];\n guildId?: string;\n [key: string]: unknown;\n}\n","/**\n * Listener 接口层(运行时探测产物 + 公开资料作说明书,ADR-003 / ADR-006)\n *\n * GroupListener:群服务(GroupService)的原生回调监听接口。\n * 签名依据 wrapper 外部契约自研描述(零复制)——事件名约定 `${Service}/${method}`,由 event-channel 编译期推导。\n * 用 type 别名(非 interface):满足 ListenerShape(Record<string, unknown>)约束。\n */\nimport type {\n Group,\n GroupDetailInfo,\n GroupMember,\n GroupNotify,\n ShutUpGroupMember,\n} from \"../services/group-service.js\";\n\n/** 群列表更新类型(说明书参考,值待探测校准)。 */\nexport const GroupListUpdateType = {\n /** 全量更新(groupList 为完整列表)。 */\n ALL: 0,\n /** 增量更新(合并到现有缓存)。 */\n PARTIAL: 1,\n} as const;\nexport type GroupListUpdateType = (typeof GroupListUpdateType)[keyof typeof GroupListUpdateType];\n\n/** 成员数据来源(onMemberInfoChange 第二参,说明书参考)。 */\nexport const GroupMemberDataSource = {\n /** 本地缓存。 */\n CACHE: 0,\n /** 网络拉取。 */\n NETWORK: 1,\n} as const;\nexport type GroupMemberDataSource =\n (typeof GroupMemberDataSource)[keyof typeof GroupMemberDataSource];\n\n/** onMemberListChange 参数(sceneId 为群号 groupCode)。 */\nexport interface GroupMemberListChange {\n sceneId: string;\n ids: string[];\n /** uid → GroupMember。 */\n infos: Map<string, GroupMember>;\n hasPrev: boolean;\n hasNext: boolean;\n hasRobot: boolean;\n}\n\n/** 群服务(GroupService)的原生回调监听接口。 */\nexport type GroupListener = {\n /** 群列表初始化完成(listEmpty:是否无群)。 */\n onGroupListInited: (listEmpty: boolean) => void;\n /** 群列表更新。 */\n onGroupListUpdate: (updateType: GroupListUpdateType, groupList: Group[]) => void;\n /** 群详情变化。 */\n onGroupDetailInfoChange: (detailInfo: GroupDetailInfo) => void;\n /** 群成员列表变化(sceneId=groupCode)。 */\n onMemberListChange: (arg: GroupMemberListChange) => void;\n /** 群成员信息变化(dataSource 区分本地缓存/网络)。 */\n onMemberInfoChange: (\n groupCode: string,\n dataSource: GroupMemberDataSource,\n members: Map<string, GroupMember>,\n ) => void;\n /** 群通知更新(doubt:是否可疑/忽略列表)。 */\n onGroupNotifiesUpdated: (doubt: boolean, notifies: GroupNotify[]) => void;\n /** 群通知单屏(分页 seq)。 */\n onGroupSingleScreenNotifies: (doubt: boolean, seq: string, notifies: GroupNotify[]) => void;\n /** 群禁言成员列表变化。 */\n onShutUpMemberListChanged: (groupCode: string, members: ShutUpGroupMember[]) => void;\n /**\n * 群精华列表变化(OB11 group_essence 候选源,2026-09-10 接线)。\n * 证据:wrapper.node 9.9.33-52230 字符串——小写名位于 listener 反射字符串簇,\n * 与已知 onMemberListChange/onBuddyReqChange 交错(同一字符串表页);\n * API 族旁证 addGroupEssence/getGroupLatestEssenceList。\n * ⚠️ 参数形状待真实事件校准(unknown 透传,订阅方 raw 日志观测)。\n */\n onGroupEssenceListChange: (arg: unknown) => void;\n};\n","/**\n * NodeIKernelMsgService:消息服务接口面(自研描述,非移植)\n *\n * 依据:getMsgService() 运行时反射 + wrapper 外部契约(接口签名是 QQ 的外部事实,\n * 自研描述其形状,零复制实现)。\n * 只收录 apis/msg 当前需要的方法;其余 200+ 方法按需探测后补齐。\n */\nimport type {\n FaceElement,\n FileElement,\n Peer,\n PicElement,\n PttElement,\n RawMessage,\n ReplyElement,\n TextElement,\n VideoElement,\n} from \"../entities.js\";\n\n/** 原生通用返回:result 非 0 即失败(errMsg 附原因)。 */\nexport interface GeneralCallResult {\n result: number;\n errMsg: string;\n}\n\n/** 消息元素类型枚举(QQ wrapper 外部契约,自研描述)。 */\nexport const ElementType = {\n UNKNOWN: 0,\n TEXT: 1,\n PIC: 2,\n FILE: 3,\n PTT: 4,\n VIDEO: 5,\n FACE: 6,\n REPLY: 7,\n GRAY_TIP: 8,\n WALLET: 9,\n ARK: 10,\n MFACE: 11,\n LIVE_GIFT: 12,\n STRUCT_LONG_MSG: 13,\n MARKDOWN: 14,\n GIPHY: 15,\n MULTI_FORWARD: 16,\n INLINE_KEYBOARD: 17,\n INTEXT_GIFT: 18,\n CALENDAR: 19,\n} as const;\nexport type ElementType = (typeof ElementType)[keyof typeof ElementType];\n\n/** 发送消息元素(session 发送用,elementType + 各子元素互斥)。 */\nexport interface SendMessageElement {\n elementType: ElementType;\n elementId?: string;\n textElement?: TextElement;\n faceElement?: FaceElement;\n replyElement?: ReplyElement;\n picElement?: PicElement;\n pttElement?: PttElement;\n videoElement?: VideoElement;\n /** ARK 卡片(小程序/分享卡片);bytesData 为卡片 JSON 字符串(与接收侧 RawElement 同字段名,2026-10-25 实测)。 */\n arkElement?: { bytesData: string };\n /** 文件(upload_private/group_file;字段值待探测校准,pic/ptt 同款发送链)。 */\n fileElement?: FileElement;\n}\n\n/** 戳一戳参数(sendNudge,说明书参考,字段值待探测校准)。 */\nexport interface NudgeParam {\n /** 被戳者 uid。 */\n receiverUid: string;\n /** 发起者 uid(机器人自己)。 */\n callerUid: string;\n /** 会话 id(群场景 = 群号;好友场景缺省)。 */\n confId?: string;\n /** 场景操作码(接收侧 aioOp 同名;发送取值待校准)。 */\n aioOp?: number;\n}\n\n/** 消息服务:apis/msg 用到的核心方法面。 */\nexport interface NodeIKernelMsgService {\n addKernelMsgListener(listener: unknown): number;\n removeKernelMsgListener(listenerId: number): void;\n generateMsgUniqueId(chatType: number, time: string): string;\n sendMsg(\n msgId: string,\n peer: Peer,\n elements: SendMessageElement[],\n map: Map<number, unknown>,\n ): Promise<GeneralCallResult>;\n recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>;\n getMsgs(\n peer: Peer,\n msgId: string,\n count: number,\n queryOrder: boolean,\n ): Promise<GeneralCallResult & { msgList?: RawMessage[] }>;\n /** 按 msgId 批量拉取消息(get_msg / 精华消息 / ptt 转文字共用)。 */\n getMsgsByMsgId(\n peer: Peer,\n ids: string[],\n ): Promise<GeneralCallResult & { msgList?: RawMessage[] }>;\n /**\n * 富媒体主动下载(get_record 语音下载,2026-09-08 B1 实测:单参数对象,\n * resolve undefined——结果只能经重拉消息元素观察;参数残缺时静默 no-op 不抛错;\n * transferStatus=2 已下载态调用为 no-op,详见 kernel design.md §5)。\n */\n downloadRichMedia(param: {\n msgId: string;\n elemId: string;\n chatType: number;\n downloadType: number;\n thumbSize: number;\n }): Promise<void>;\n setMsgRead(peer: Peer): Promise<GeneralCallResult>;\n /** 消息表情表态(set_msg_emoji_like;setOrCancel=true 点赞,false 取消)。 */\n setMsgEmojiLikes(\n peer: Peer,\n msgSeq: string,\n emojiId: string,\n emojiType: string,\n setOrCancel: boolean,\n ): Promise<GeneralCallResult>;\n /** 语音转文字(fetch_ptt_text;异步转写,结果写回 pttElement.text)。 */\n translatePtt2Text(msgId: string, peer: Peer, msgElement: unknown): Promise<GeneralCallResult>;\n /** 输入状态(set_input_status;eventType=1 输入中,0 停止)。 */\n sendShowInputStatusReq(chatType: number, eventType: number, toUid: string): Promise<unknown>;\n /**\n * 富媒体文件目标路径(NapCat 式发送图片前置:md5+文件名 → QQ 内部 Ori/Thumb 路径)。\n * 返回纯文件名(相对 QQ 数据根),需配合 NodeQQNTWrapperUtil.copyFile 放置文件。\n */\n getRichMediaFilePathForGuild(req: {\n md5HexStr: string;\n fileName: string;\n elementType: number;\n elementSubType: number;\n thumbSize: number;\n needCreate: boolean;\n downloadType: number;\n file_uuid: string;\n }): string;\n /** 合并转发组装(send_group/private_forward_msg;返回 MULTI_FORWARD 元素)。 */\n buildMultiForwardMsg(req: {\n srcMsgIds: string[];\n srcContact: Peer;\n }): Promise<GeneralCallResult & { rspInfo?: { elements?: SendMessageElement[] } }>;\n /** 获取合并转发内容(get_forward_msg;resId 取自 multiForwardMsgElement)。 */\n getMultiMsg(\n peer: Peer,\n msgId: string,\n resId: string,\n ): Promise<GeneralCallResult & { msgList?: RawMessage[] }>;\n /** 单条转发(forward_group/friend_single_msg;msgIds 源、dstPeers 目标)。 */\n forwardMsg(\n msgIds: string[],\n peer: Peer,\n dstPeers: Peer[],\n commentElements: unknown,\n ): Promise<GeneralCallResult>;\n /** 设置在线状态(set_online_status;customStatus 为自定义状态)。 */\n setStatus(args: {\n status: number;\n extStatus: number;\n batteryStatus: number;\n customStatus?: {\n faceId: string;\n wording: string;\n faceType: string;\n };\n }): Promise<GeneralCallResult>;\n /** 戳一戳发送(send_friend/group_nudge;参数形状待探测校准)。 */\n sendNudge(param: NudgeParam): Promise<unknown>;\n /** 会话置顶(set_peer_pin;参数待探测校准)。 */\n setContactLocalTop(peer: Peer, isTop: boolean): Promise<unknown>;\n /** 收藏表情查询(get_custom_face_url_list;desc 空串 = 全量,形状待校准)。 */\n queryFavEmojiByDesc(desc: string): Promise<GeneralCallResult & { emojiInfoList?: unknown }>;\n}\n","/**\n * canonical 消息元素模型(协议无关,ADR-008 延伸)\n *\n * 描述 QQ 消息的事实结构,是各协议翻译层的公共中间表示:\n * kernel 只做一次 NT ↔ canonical(toCanonicalElements / toSendElements),\n * 各协议(onebot11/satori)只写薄映射 canonical ↔ 协议格式。\n */\n\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n FaceElement,\n FileElement,\n PicElement,\n PttElement,\n RawElement,\n RawMessage,\n ReplyElement,\n TextElement,\n VideoElement,\n} from \"./entities.js\";\nimport { ElementType, type SendMessageElement } from \"./services/msg-service.js\";\n\n/** 文本 at 类型(QQ wrapper 契约,自研描述)。 */\nconst AtType = { ALL: 1, ONE: 2, ME: 4 } as const;\n\n/**\n * 视频缩略图在 thumbPath 中的 key(收向实测 Map<0, path>;平面对象形态下\n * 数字 key 经序列化变字符串 \"0\"。apis/msg.ts 发送侧有同名常量,types 层\n * 不能反向 import apis(依赖方向),此处独立定义)。\n */\nconst VIDEO_THUMB_KEY = 0;\n\n/**\n * 读视频缩略图路径(接收方向形态不唯一,防御读取):\n * - 进程内为 `Map<0, path>`(收向实测);\n * - 跨进程/JSON 序列化后退化为平面对象 `{\"0\": path}`(`JSON.stringify(Map)`\n * 内容全丢,无规整的传输会得到空对象 `{}`);\n * - 个别形态可能直接给字符串路径(兜底容忍)。\n *\n * 2026-10-24 线上事故:koishi IPC 模式下 thumbPath 到达父进程已是平面对象,\n * 直接 `.get(0)` 抛 TypeError,整条视频消息事件分派失败。\n */\nfunction readVideoThumbPath(thumb: VideoElement[\"thumbPath\"]): string | undefined {\n if (thumb === undefined || thumb === \"\") {\n return undefined;\n }\n if (typeof thumb === \"string\") {\n return thumb;\n }\n if (thumb instanceof Map) {\n const value = thumb.get(VIDEO_THUMB_KEY);\n return value === \"\" ? undefined : value;\n }\n const value = thumb[String(VIDEO_THUMB_KEY)];\n return value === \"\" ? undefined : value;\n}\n\n/** TEXT 元素 → canonical(at 或纯文本)。 */\nfunction textToCanonical(textEl: TextElement | undefined): CanonicalElement {\n if (textEl === undefined) {\n return { type: \"text\", text: \"\" };\n }\n const { atType, atUid, content } = textEl;\n if (atType === AtType.ALL) {\n return { type: \"at\", target: \"all\" };\n }\n if (atType === AtType.ONE || atType === AtType.ME) {\n // 2026-08-07:at 段补 display(显示名)——QQ 的 content 通常是 \"@昵称\",\n // 剥掉 @ 作 display,供 OB11 at 段 name / 日志渲染使用(此前只有 target=uid)。\n const base = { type: \"at\" as const, target: atUid ?? content };\n const display = content.startsWith(\"@\") ? content.slice(1) : content;\n if (display === \"\") {\n return base;\n }\n return { ...base, display };\n }\n return { type: \"text\", text: content };\n}\n\n/** PIC 元素 → canonical image。 */\nfunction picToCanonical(pic: PicElement | undefined): CanonicalElement {\n if (pic === undefined) {\n return { type: \"image\", path: \"\" };\n }\n const path = pic.picPath ?? pic.picUrl ?? \"\";\n const url = pic.picUrl;\n if (url === undefined) {\n return { type: \"image\", path };\n }\n return { type: \"image\", path, url };\n}\n\n/** FACE 元素 → canonical face。 */\nfunction faceToCanonical(face: FaceElement | undefined): CanonicalElement {\n if (face === undefined) {\n return { type: \"face\", id: \"0\" };\n }\n return { type: \"face\", id: String(face.faceIndex) };\n}\n\n/** PTT 元素 → canonical voice。 */\nfunction pttToCanonical(ptt: PttElement | undefined): CanonicalElement {\n if (ptt === undefined) {\n return { type: \"voice\", path: \"\" };\n }\n return { type: \"voice\", path: ptt.filePath ?? \"\" };\n}\n\n/** VIDEO 元素 → canonical video。 */\nfunction videoToCanonical(video: VideoElement | undefined): CanonicalElement {\n if (video === undefined) {\n return { type: \"video\", path: \"\" };\n }\n const path = video.filePath ?? video.videoUrl ?? \"\";\n const base: Extract<CanonicalElement, { type: \"video\" }> = { type: \"video\", path };\n if (video.videoUrl !== undefined) {\n base.url = video.videoUrl;\n }\n // thumbPath 接收形态不唯一(Map / 序列化平面对象 / 字符串),统一走防御读取\n const thumb = readVideoThumbPath(video.thumbPath);\n if (thumb !== undefined) {\n base.thumbPath = thumb;\n }\n if (video.thumbWidth !== undefined && video.thumbWidth > 0) {\n base.width = video.thumbWidth;\n }\n if (video.thumbHeight !== undefined && video.thumbHeight > 0) {\n base.height = video.thumbHeight;\n }\n if (video.fileTime !== undefined && video.fileTime > 0) {\n base.durationSec = video.fileTime;\n }\n return base;\n}\n\n/** FILE 元素 → canonical file。 */\nfunction fileToCanonical(file: FileElement | undefined): CanonicalElement {\n if (file === undefined) {\n return { type: \"file\", path: \"\" };\n }\n const path = file.filePath ?? \"\";\n const name = file.fileName;\n if (name === undefined) {\n return { type: \"file\", path };\n }\n return { type: \"file\", path, name };\n}\n\n/** REPLY 元素 → canonical reply。 */\nfunction replyToCanonical(reply: ReplyElement | undefined): CanonicalElement {\n if (reply === undefined) {\n return { type: \"reply\", messageId: \"\" };\n }\n return { type: \"reply\", messageId: reply.replayMsgId };\n}\n\n/**\n * ARK 元素 → canonical json(小程序/分享卡片,2026-10-25)。\n *\n * bytesData 是卡片 JSON 字符串,原样透传不解析重组——OB11 json 段 data.data\n * 保持原始载荷(go-cqhttp/NapCat 生态一致),title/url 由下游自行 parse。\n * bytesData 缺失/为空时维持 unknown 兜底:空 json 段无信息量,保留 raw 供探测。\n */\nfunction arkToCanonical(ark: { bytesData: string } | undefined, el: RawElement): CanonicalElement {\n const bytes = ark?.bytesData;\n if (bytes === undefined || bytes === \"\") {\n return { type: \"unknown\", raw: el };\n }\n return { type: \"json\", raw: bytes };\n}\n\n/** 单个 NT 元素 → canonical(接收方向,宽容:无法表达回 unknown,不抛错)。 */\nfunction toCanonicalElement(el: RawElement): CanonicalElement {\n switch (el.elementType) {\n case ElementType.TEXT:\n return textToCanonical(el.textElement);\n case ElementType.PIC:\n return picToCanonical(el.picElement);\n case ElementType.FACE:\n return faceToCanonical(el.faceElement);\n case ElementType.PTT:\n return pttToCanonical(el.pttElement);\n case ElementType.VIDEO:\n return videoToCanonical(el.videoElement);\n case ElementType.FILE:\n return fileToCanonical(el.fileElement);\n case ElementType.REPLY:\n return replyToCanonical(el.replyElement);\n case ElementType.ARK:\n return arkToCanonical(el.arkElement, el);\n default:\n return { type: \"unknown\", raw: el };\n }\n}\n\n/** NT RawMessage → canonical 元素(与 toSendElements 对称,接收方向)。 */\nexport function toCanonicalElements(msg: RawMessage): CanonicalElement[] {\n return msg.elements.map(toCanonicalElement);\n}\n\n/**\n * canonical json → ARK bytesData 字符串:字符串原样透传(OB11 json 段 /\n * satori 发送链给的已是卡片 JSON 文本),其余值 JSON 序列化。\n * 与接收方向 arkToCanonical 对称(bytesData 字段名 2026-10-25 真实消息实测)。\n */\nfunction jsonToArkBytes(raw: unknown): string {\n if (typeof raw === \"string\") {\n return raw;\n }\n try {\n return JSON.stringify(raw) ?? \"\";\n } catch (err) {\n throw kernelError(`json 元素序列化失败: ${String(err)}`, \"INVALID_PARAM\");\n }\n}\n\n/** canonical 元素 → NT 发送元素(text/at/face/image/voice/reply 核心 + json ARK 透传)。 */\nexport function toSendElements(elements: CanonicalElement[]): SendMessageElement[] {\n const out: SendMessageElement[] = [];\n for (const el of elements) {\n switch (el.type) {\n case \"text\":\n out.push({ elementType: ElementType.TEXT, textElement: { content: el.text } });\n break;\n case \"at\":\n if (el.target === \"all\") {\n out.push({\n elementType: ElementType.TEXT,\n textElement: { content: el.display ?? \"@\", atType: 1 },\n });\n } else {\n out.push({\n elementType: ElementType.TEXT,\n textElement: { content: el.display ?? \"@\", atType: 2, atUid: el.target },\n });\n }\n break;\n case \"face\":\n out.push({\n elementType: ElementType.FACE,\n faceElement: { faceIndex: Number(el.id) },\n });\n break;\n case \"image\":\n out.push({ elementType: ElementType.PIC, picElement: { picPath: el.path } });\n break;\n case \"voice\":\n out.push({ elementType: ElementType.PTT, pttElement: { filePath: el.path } });\n break;\n case \"reply\":\n out.push({\n elementType: ElementType.REPLY,\n replyElement: {\n replayMsgId: el.messageId,\n replayMsgSeq: el.messageId,\n replayMsgTime: \"0\",\n },\n });\n break;\n case \"video\":\n // 视频发送必须经 MsgApi.prepareVideoElement 预处理(md5/目标路径/\n // 缩略图 Map 等字段无法从 canonical 直接得到)。canonical → NT\n // 元元素只在 prepareSendElements 内部发生,此处属误用。\n throw kernelError(\n \"视频发送需经 MsgApi 预处理(prepareVideoElement)\",\n \"INVALID_PARAM\",\n );\n case \"json\":\n // ARK 卡片透传(elementType=10):bytesData 装卡片 JSON 字符串,\n // 与接收方向 arkToCanonical 对称;wrapper 对缺省字段容忍(djinni\n // 转换缺省字段为默认值)。发送失败走 KernelError 语义,不崩溃。\n out.push({\n elementType: ElementType.ARK,\n arkElement: { bytesData: jsonToArkBytes(el.raw) },\n });\n break;\n case \"file\":\n case \"forward\":\n case \"xml\":\n case \"unknown\":\n throw kernelError(`发送元素 ${el.type} 暂不支持(P2-2 探测后补)`, \"INVALID_PARAM\");\n default:\n // exhaustive:CanonicalElement 无其他成员\n break;\n }\n }\n return out;\n}\n\n/** 协议无关的规范消息元素。 */\nexport type CanonicalElement =\n | { type: \"text\"; text: string }\n | { type: \"at\"; target: string; display?: string }\n | { type: \"image\"; path: string; url?: string; size?: { width: number; height: number } }\n | { type: \"face\"; id: string }\n | { type: \"voice\"; path: string; durationMs?: number; url?: string }\n | {\n type: \"video\";\n path: string;\n url?: string;\n thumbPath?: string;\n width?: number;\n height?: number;\n durationSec?: number;\n }\n | { type: \"file\"; path: string; name?: string; size?: number }\n | { type: \"reply\"; messageId: string }\n | { type: \"forward\"; messageIds: string[] }\n | { type: \"json\"; raw: unknown }\n | { type: \"xml\"; raw: unknown }\n | { type: \"unknown\"; raw: unknown };\n","/**\n * NodeIKernelGroupService:群服务接口面(自研描述,非移植)\n *\n * 依据:getGroupService() 运行时反射 + wrapper 外部契约(接口签名是 QQ 的外部事实,\n * 自研描述其形状,零复制实现)。\n * 只收录 apis/group 当前需要的方法;其余按需探测后补齐。\n */\nimport type { GeneralCallResult } from \"./msg-service.js\";\n\n/** 群成员角色(QQ wrapper 契约)。 */\nexport const NTGroupMemberRole = {\n UNSPECIFIED: 0,\n STRANGER: 1,\n MEMBER: 2,\n ADMIN: 3,\n OWNER: 4,\n} as const;\nexport type NTGroupMemberRole = (typeof NTGroupMemberRole)[keyof typeof NTGroupMemberRole];\n\n/** 踢人请求项(KickMemberV2Req.kickList 成员,说明书参考,字段值待探测校准)。 */\nexport interface KickMemberInfo {\n optFlag: number;\n optOperate: number;\n optMemberUid: string;\n optBytesMsg: string;\n}\n\n/** 踢人请求(set_group_kick 的 kickMemberV2 参数,说明书参考)。 */\nexport interface KickMemberV2Req {\n groupCode: string;\n kickFlag: number;\n kickList: KickMemberInfo[];\n kickListUids: string[];\n kickMsg: string;\n}\n\n/** @all 剩余次数(getGroupRemainAtTimes 返回)。 */\nexport interface GroupRemainAtTimes {\n canAtAll: boolean;\n remainAtAllCountForUin: number;\n remainAtAllCountForGroup: number;\n atTimesMsg: string;\n canNotAtAllMsg: string;\n}\n\n/** 群通知类型(GroupNotifyMsgType,说明书参考)。 */\nexport const GroupNotifyMsgType = {\n UN_SPECIFIED: 0,\n INVITED_BY_MEMBER: 1, // 邀请入群\n REFUSE_INVITED: 2,\n REFUSED_BY_ADMINI_STRATOR: 3,\n AGREED_TOJOIN_DIRECT: 4,\n INVITED_NEED_ADMINI_STRATOR_PASS: 5,\n AGREED_TO_JOIN_BY_ADMINI_STRATOR: 6,\n REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS: 7, // 申请入群\n SET_ADMIN: 8,\n KICK_MEMBER_NOTIFY_ADMIN: 9,\n KICK_MEMBER_NOTIFY_KICKED: 10,\n MEMBER_LEAVE_NOTIFY_ADMIN: 11,\n CANCEL_ADMIN_NOTIFY_CANCELED: 12,\n CANCEL_ADMIN_NOTIFY_ADMIN: 13,\n TRANSFER_GROUP_NOTIFY_OLDOWNER: 14,\n TRANSFER_GROUP_NOTIFY_ADMIN: 15,\n} as const;\nexport type GroupNotifyMsgType = (typeof GroupNotifyMsgType)[keyof typeof GroupNotifyMsgType];\n\n/** 群通知处理状态(GroupNotifyMsgStatus,说明书参考)。 */\nexport const GroupNotifyMsgStatus = {\n KINIT: 0,\n KUNHANDLE: 1, // 未处理\n KAGREED: 2,\n KREFUSED: 3,\n KIGNORED: 4,\n} as const;\nexport type GroupNotifyMsgStatus = (typeof GroupNotifyMsgStatus)[keyof typeof GroupNotifyMsgStatus];\n\n/** 群请求操作类型(NTGroupRequestOperateTypes,说明书参考)。 */\nexport const NTGroupRequestOperateTypes = {\n KUNSPECIFIED: 0,\n KAGREE: 1,\n KREFUSE: 2,\n KIGNORE: 3,\n KDELETE: 4,\n} as const;\nexport type NTGroupRequestOperateTypes =\n (typeof NTGroupRequestOperateTypes)[keyof typeof NTGroupRequestOperateTypes];\n\n/** 群通知(getSingleScreenNotifies 返回项,说明书参考,待探测校准)。 */\nexport interface GroupNotify {\n seq: string;\n type: GroupNotifyMsgType;\n status: GroupNotifyMsgStatus;\n group?: { groupCode: string; groupName: string };\n user1?: { uid: string; nickName: string };\n user2?: { uid: string; nickName: string };\n actionTime?: string;\n postscript?: string;\n warningTips?: string;\n}\n\n/** 禁言成员(getGroupShutUpMemberList 返回项,说明书参考,待探测校准)。 */\nexport interface ShutUpGroupMember {\n uid: string;\n qid: string;\n uin: string;\n nick: string;\n remark: string;\n cardName: string;\n shutUpTime: number;\n memberSpecialTitle?: string;\n}\n\n/** 群列表项(Group 实体,说明书参考)。 */\nexport interface Group {\n groupCode: string;\n groupName: string;\n memberCount: number;\n maxMember: number;\n remarkName?: string;\n groupOwnerId?: { memberUin: string; memberUid: string };\n memberRole?: number;\n isTop?: boolean;\n [key: string]: unknown;\n}\n\n/** 群详情(getGroupDetailInfo 返回,说明书参考)。 */\nexport interface GroupDetailInfo {\n groupCode: string;\n groupUin: string;\n ownerUin: string;\n ownerUid?: string;\n groupName: string;\n memberNum: number;\n maxMemberNum: number;\n remarkName?: string;\n isTop?: boolean;\n groupCreateTime?: number;\n [key: string]: unknown;\n}\n\n/** 群成员(getAllMemberList result.infos 的 value,说明书参考)。 */\nexport interface GroupMember {\n uid: string;\n uin: string;\n nick: string;\n cardName: string;\n remark: string;\n role: NTGroupMemberRole;\n shutUpTime: number; // 禁言秒数\n joinTime: string;\n lastSpeakTime: string;\n memberSpecialTitle?: string;\n sex?: number;\n age?: number;\n isRobot?: boolean;\n [key: string]: unknown;\n}\n\n/** 群公告(getGroupBulletin 返回项,说明书参考,待探测校准)。 */\nexport interface GroupBulletin {\n /** 公告 id(delete_group_announcement 的 announcement_id)。 */\n announcementId?: string;\n /** 发送者 uid。 */\n senderUid?: string;\n /** 发送时间(秒)。 */\n publishTime?: number;\n /** 文本内容。 */\n content?: string;\n /** 公告图片(url 待实机确认)。 */\n pic?: { url?: string; width?: number; height?: number; id?: string };\n [key: string]: unknown;\n}\n\n/** 群服务:apis/group 用到的核心方法面。 */\nexport interface NodeIKernelGroupService {\n addKernelGroupListener(listener: unknown): number;\n removeKernelGroupListener(listenerId: number): void;\n getGroupList(force: boolean): Promise<GeneralCallResult>;\n getGroupDetailInfo(groupCode: string, groupInfoSource: number): Promise<GeneralCallResult>;\n getAllMemberList(\n groupCode: string,\n forceFetch: boolean,\n ): Promise<{\n errCode: number;\n errMsg: string;\n result: {\n ids: Array<{ uid: string; index: number }>;\n infos: Map<string, GroupMember>;\n finish: boolean;\n };\n }>;\n getMemberInfo(\n groupCode: string,\n uids: string[],\n forceFetch: boolean,\n ): Promise<GeneralCallResult>;\n /** uin → uid(私聊发送等需要,说明书参考)。 */\n getUidByUins(\n uins: string[],\n ): Promise<{ errCode: number; errMsg: string; uids: Map<string, string> }>;\n /** uid → uin。 */\n getUinByUids(\n uids: string[],\n ): Promise<{ errCode: number; errMsg: string; uins: Map<string, string> }>;\n /** 踢人(set_group_kick)。 */\n kickMemberV2(param: KickMemberV2Req): Promise<GeneralCallResult>;\n /** 成员禁言(set_group_ban;timeStamp=0 解除禁言)。 */\n setMemberShutUp(\n groupCode: string,\n memberTimes: Array<{ uid: string; timeStamp: number }>,\n ): Promise<GeneralCallResult>;\n /** 全员禁言(set_group_whole_ban)。 */\n setGroupShutUp(groupCode: string, shutUp: boolean): Promise<GeneralCallResult>;\n /** 设置管理员(set_group_admin;void 语义,乐观处理)。 */\n modifyMemberRole(groupCode: string, uid: string, role: NTGroupMemberRole): void;\n /** 设置群名片(set_group_card;void 语义)。 */\n modifyMemberCardName(groupCode: string, uid: string, cardName: string): void;\n /** 修改群名(set_group_name)。 */\n modifyGroupName(\n groupCode: string,\n groupName: string,\n isNormalMember: boolean,\n ): Promise<GeneralCallResult>;\n /** 退群(set_group_leave)。 */\n quitGroupV2(param: {\n groupCode: string;\n needDeleteLocalMsg: boolean;\n }): Promise<GeneralCallResult>;\n /** 设置精华消息(msgSeq/msgRandom 取自消息本体)。 */\n addGroupEssence(param: {\n groupCode: string;\n msgRandom: number;\n msgSeq: number;\n }): Promise<unknown>;\n /** 取消精华消息。 */\n removeGroupEssence(param: {\n groupCode: string;\n msgRandom: number;\n msgSeq: number;\n }): Promise<unknown>;\n /** @all 剩余次数(get_group_at_all_remain)。 */\n getGroupRemainAtTimes(groupCode: string): Promise<\n Omit<GeneralCallResult, \"result\"> & {\n errCode: number;\n atInfo: GroupRemainAtTimes;\n }\n >;\n /** 单屏通知列表(get_group_system_msg / set_group_add_request 共用;列表可能走 listener,宽松解包)。 */\n getSingleScreenNotifies(\n doubt: boolean,\n startSeq: string,\n count: number,\n ): Promise<GeneralCallResult & { result?: unknown }>;\n /** 禁言成员列表(get_group_shut_list;同 listener 风格,宽松解包)。 */\n getGroupShutUpMemberList(groupCode: string): Promise<GeneralCallResult & { result?: unknown }>;\n /** 处理群请求(set_group_add_request 应答;postscript 空值可能导致失败,默认空格)。 */\n operateSysNotify(\n doubt: boolean,\n operateMsg: {\n operateType: NTGroupRequestOperateTypes;\n targetMsg: {\n seq: string;\n type: GroupNotifyMsgType;\n groupCode: string;\n postscript: string;\n };\n },\n ): Promise<void>;\n /** 群公告列表(get_group_announcements;返回形状待探测校准)。 */\n getGroupBulletin(groupCode: string): Promise<GeneralCallResult & { result?: unknown }>;\n /** 发群公告(send_group_announcement;参数形状待探测校准)。 */\n publishGroupBulletin(param: {\n groupCode: string;\n content: string;\n picId?: string;\n }): Promise<unknown>;\n /** 上传公告图片(image_uri 落盘后调用;返回 picId 待校准)。 */\n uploadGroupBulletinPic(groupCode: string, imagePath: string): Promise<unknown>;\n /** 删群公告(delete_group_announcement;参数待校准)。 */\n deleteGroupBulletin(groupCode: string, announcementId: string): Promise<unknown>;\n /** 专属头衔(set_group_member_special_title;待校准,备选 setGroupIdentitySpec)。 */\n setIdentityTitleInfo(groupCode: string, uid: string, title: string): Promise<unknown>;\n /** 群头像(set_group_avatar;参数待校准;Profile setHeader 同名方法)。 */\n setHeader(groupCode: string, imagePath: string): Promise<unknown>;\n}\n","/**\n * wrapper.node 的 NAPI 接口面(自研描述,非移植)\n *\n * 来源:wrapper.node 在 QQ 定制版 Electron 里通过 napi_define_class / napi_set_named_property\n * 注册的 JS 类(2026-08-05 Ghidra 反编译 FUN_180c6274a / FUN_180c80224 确认类名)。\n * 这些接口是 wrapper.node 对外暴露的外部系统契约——我们自研描述其形状,未复制任何第三方代码。\n *\n * 注意:这里所有对象由 QQ 运行时的 NAPI 层自动构建/转换,业务层禁止触碰 C++ ABI。\n */\n\n/** 平台类型(与 wrapper.node 的 platform 字段对应)。 */\nexport const PlatformType = {\n KUNKNOWN: 0,\n KANDROID: 1,\n KIOS: 2,\n KWINDOWS: 3,\n KMAC: 4,\n KLINUX: 5,\n} as const;\nexport type PlatformType = (typeof PlatformType)[keyof typeof PlatformType];\n\n/** 厂商类型(deviceInfo.vendorType)。 */\nexport const VendorType = {\n KNOSETONIOS: 0,\n KUNSUPPORTANDROIDPUSH: 1,\n KSUPPORTTPNS: 2,\n KSUPPORTHMS: 3,\n KSUPPORTOPPOPUSH: 4,\n KSUPPORTVIVOPUSH: 5,\n KSUPPORTGOOGLEPUSH: 99,\n} as const;\nexport type VendorType = (typeof VendorType)[keyof typeof VendorType];\n\n/** NodeQQNTWrapperUtil:通用工具类(静态服务)。 */\nexport interface NodeQQNTWrapperUtil {\n get(): NodeQQNTWrapperUtil;\n getNTUserDataInfoConfig(): string;\n // 文件/哈希工具(按需补齐,运行时反射枚举为准)\n getFileSize(path: string): Promise<number>;\n fileIsExist(path: string): unknown;\n makeDirByPath(path: string): unknown;\n}\n\n/** engine 初始化配置(initWithDeskTopConfig 第一参,JS 对象直接传)。 */\nexport interface EnginInitDesktopConfig {\n base_path_prefix: string;\n platform_type: PlatformType;\n /** 桌面端应用类型(engine.initWithDeskTopConfig 契约,桌面端固定 4;移动端为其它值)。 */\n app_type: 4;\n app_version: string;\n os_version: string;\n use_xlog: boolean;\n qua: string;\n global_path_config: {\n desktopGlobalPath: string;\n };\n /**\n * 缩略图生成参数(wrapper 契约):maxSide 最长边上限 / minSide 最短边下限 /\n * longLimit 长图最长短边比例上限 / density 像素密度倍数。\n */\n thumb_config: { maxSide: 324; minSide: 48; longLimit: 6; density: 2 };\n}\n\n/** NodeIQQNTWrapperEngine 实例方法。 */\nexport interface NodeIQQNTWrapperEngine {\n initWithDeskTopConfig(config: EnginInitDesktopConfig, adapter: NodeIGlobalAdapter): void;\n initWithMobileConfig(config: unknown, adapter: NodeIGlobalAdapter): void;\n initLog(arg: unknown): void;\n setLogLevel(arg: unknown): void;\n onSendSSOReply(a: unknown, b: unknown, c: unknown, d: unknown, e: unknown): void;\n}\n\n/** NodeIQQNTWrapperEngine 构造器(含静态 get())。 */\nexport type NodeIQQNTWrapperEngineCtor = new () => NodeIQQNTWrapperEngine & {\n get(): NodeIQQNTWrapperEngine;\n};\n\n/** desktopPathConfig 结构。 */\nexport interface DesktopPathConfig {\n account_path: string;\n}\n\n/** rdeliveryConfig 结构。 */\nexport interface RDeliveryConfig {\n appKey: string;\n systemId: number;\n appId: string;\n logicEnvironment: string;\n platform: PlatformType;\n language: string;\n sdkVersion: string;\n userId: string;\n appVersion: string;\n osVersion: string;\n bundleId: string;\n serverUrl: string;\n fixedAfterHitKeys: string[];\n}\n\n/** deviceInfo 结构。 */\nexport interface DeviceInfo {\n guid: string;\n buildVer: string;\n localId: number;\n devName: string;\n devType: string;\n vendorName: string;\n osVer: string;\n vendorOsName: string;\n setMute: boolean;\n vendorType: VendorType;\n}\n\n/** session.init 的完整配置(JS 对象,NAPI 自动转换)。 */\nexport interface WrapperSessionInitConfig {\n selfUin: string;\n selfUid: string;\n desktopPathConfig: DesktopPathConfig;\n clientVer: string;\n a2: string;\n d2: string;\n d2Key: string;\n machineId: string;\n platform: PlatformType;\n platVer: string;\n appid: string;\n rdeliveryConfig: RDeliveryConfig;\n defaultFileDownloadPath: string;\n deviceInfo: DeviceInfo;\n deviceConfig: string;\n}\n\n/** NodeIQQNTWrapperSession 构造器(含静态 get / getNTWrapperSession / create)。 */\nexport type NodeIQQNTWrapperSessionCtor = {\n new (): NodeIQQNTWrapperSession;\n get(): NodeIQQNTWrapperSession;\n getNTWrapperSession(name: string): NodeIQQNTWrapperSession;\n create(): NodeIQQNTWrapperSession;\n};\n\n/** NodeIQQNTWrapperSession:会话主入口(实例方法)。 */\nexport interface NodeIQQNTWrapperSession {\n init(\n config: WrapperSessionInitConfig,\n depends: NodeIDependsAdapter,\n dispatcher: NodeIDispatcherAdapter,\n listener: NodeIKernelSessionListener,\n ): void;\n startNT(sessionId?: number): void;\n close(arg: unknown): void;\n onLine(arg: unknown): void;\n offLine(arg: unknown): void;\n disableIpDirect(arg: unknown): void;\n getAccountPath(arg: unknown): string;\n updateTicket(arg: unknown): void;\n // 60+ getService:运行时反射枚举后补齐\n getMsgService(): unknown;\n getGroupService(): unknown;\n getBuddyService(): unknown;\n getTicketService(): unknown;\n getRichMediaService(): unknown;\n /** 闪传服务(诊断用:调查 rich media transfer failed 时验证初始化触发点)。 */\n getFlashTransferService(): unknown;\n getProfileService(): unknown;\n /** 会话列表(P2 批次二 get_peer_pins 数据源;2026-10-31 探测方法面)。 */\n getRecentContactService(): unknown;\n getProfileLikeService(): unknown;\n getTipOffService(): unknown;\n}\n\n/** NodeIQQNTStartupSessionWrapper:启动会话实例方法。 */\nexport interface NodeIQQNTStartupSessionWrapper {\n stop(): void;\n start(): void;\n createWithModuleList(uk: unknown): unknown;\n getSessionIdList(): Promise<Map<unknown, unknown>>;\n}\n\n/** NodeIQQNTStartupSessionWrapper 构造器(含静态 create)。 */\nexport type NodeIQQNTStartupSessionWrapperCtor = new () => NodeIQQNTStartupSessionWrapper & {\n create(): NodeIQQNTStartupSessionWrapper;\n};\n\n/** NodeIKernelLoginService 实例方法。 */\nexport interface NodeIKernelLoginService {\n addKernelLoginListener(listener: unknown): number;\n removeKernelLoginListener(listener: number): void;\n initConfig(config: unknown): void;\n connect(): void;\n getLoginList(): Promise<{ result: number; LocalLoginInfoList: unknown[] }>;\n quickLoginWithUin(uin: string): Promise<unknown>;\n getQRCodePicture(): boolean;\n getMsfStatus(): number;\n /** 设备指纹 guid(wrapper.node 字符串分析实证,2026-08-12;无 getMachineId)。 */\n getMachineGuid(): string;\n}\n\n/** NodeIKernelLoginService 构造器(含静态 get)。 */\nexport type NodeIKernelLoginServiceCtor = new () => NodeIKernelLoginService & {\n get(): NodeIKernelLoginService;\n};\n\n/** NodeIO3MiscService:O3 杂项服务。 */\nexport interface NodeIO3MiscService {\n get(): NodeIO3MiscService;\n addO3MiscListener(listener: unknown): number;\n}\n\n// adapter 类型(session.init 第二/三参、engine.initWithDeskTopConfig 第二参)\n// 注意:wrapper exports **不含** NodeI*Adapter / NodeI*Listener 构造器(实测 89 键无)。\n// 传普通 JS 对象即可——NAPI 反射读取对象上的方法回调。方法名是 QQ wrapper 的外部契约\n//(2026-08-05 运行时行为实测确认,接口为自研描述)。\nexport interface NodeIGlobalAdapter {\n onLog?(...args: unknown[]): void;\n onGetSrvCalTime?(...args: unknown[]): void;\n onShowErrUITips?(...args: unknown[]): void;\n fixPicImgType?(...args: unknown[]): unknown;\n getAppSetting?(...args: unknown[]): unknown;\n onInstallFinished?(...args: unknown[]): void;\n onUpdateGeneralFlag?(...args: unknown[]): void;\n onGetOfflineMsg?(...args: unknown[]): void;\n}\n\n/** session.init 第二参:依赖回调(普通 JS 对象)。 */\nexport interface NodeIDependsAdapter {\n onMSFStatusChange?(statusType: number, changeReasonType: number): void;\n onMSFSsoError?(code: number, desc: string): void;\n getGroupCode?(...args: unknown[]): unknown;\n}\n\n/** session.init 第三参:请求分发回调(普通 JS 对象)。 */\nexport interface NodeIDispatcherAdapter {\n dispatchRequest?(arg: unknown): void;\n dispatchCall?(arg: unknown): void;\n dispatchCallWithJson?(arg: unknown): void;\n}\n\n/** 登录监听器接口(普通 JS 对象,传给 loginService.addKernelLoginListener)。自研描述。 */\nexport interface IKernelLoginListener {\n onLoginConnected?(...args: unknown[]): void;\n onLoginDisConnected?(...args: unknown[]): void;\n onLoginConnecting?(...args: unknown[]): void;\n onQRCodeGetPicture?(arg: { pngBase64QrcodeData: string; qrcodeUrl: string }): void;\n onQRCodeLoginPollingStarted?(...args: unknown[]): void;\n onQRCodeSessionUserScaned?(...args: unknown[]): void;\n onQRCodeLoginSucceed?(loginResult: { uid: string; uin: string; nick?: string }): void;\n onQRCodeSessionFailed?(errType: number, errCode: number, errMsg: string): void;\n onLoginFailed?(args: unknown): void;\n onLogoutSucceed?(...args: unknown[]): void;\n onLogoutFailed?(...args: unknown[]): void;\n onUserLoggedIn?(userid: string): void;\n onQRCodeSessionQuickLoginFailed?(...args: unknown[]): void;\n onPasswordLoginFailed?(...args: unknown[]): void;\n onQQLoginNumLimited?(...args: unknown[]): void;\n onLoginState?(...args: unknown[]): void;\n}\n\n/**\n * NodeIKernelSessionListener:session.init 第四参监听器(普通 JS 对象)。\n * init 完成信号:以 onOpentelemetryInit(is_init===true) 为准(onSessionInitComplete 为辅)。\n */\nexport interface NodeIKernelSessionListener {\n onNTSessionCreate?(sessionId: string): void;\n onGProSessionCreate?(sessionId: string): void;\n onSessionInitComplete?(result: unknown): void;\n onOpentelemetryInit?(info: { is_init: boolean; is_report: boolean }): void;\n onUserOnlineResult?(result: unknown): void;\n onGetSelfTinyId?(result: unknown): void;\n}\n\n/** wrapper.node 的 NAPI 顶层导出(module.exports)。 */\nexport interface WrapperNodeApi {\n NodeIO3MiscService: NodeIO3MiscService;\n NodeQQNTWrapperUtil: NodeQQNTWrapperUtil;\n /** 构造器:`new wrapper.NodeIQQNTWrapperSession()`。 */\n NodeIQQNTWrapperSession: NodeIQQNTWrapperSessionCtor;\n NodeIQQNTStartupSessionWrapper: NodeIQQNTStartupSessionWrapperCtor;\n /** 构造器:`new wrapper.NodeIQQNTWrapperEngine()`。 */\n NodeIQQNTWrapperEngine: NodeIQQNTWrapperEngineCtor;\n /** 构造器:`new wrapper.NodeIKernelLoginService()`。 */\n NodeIKernelLoginService: NodeIKernelLoginServiceCtor;\n // 注:wrapper exports **不含** NodeI*Adapter / NodeI*Listener 构造器(实测 89 键无)——\n // adapter/listener 一律传普通 JS 对象(见 NodeIGlobalAdapter 等接口)。\n}\n","/**\n * GroupApi:群语义化 API(ADR-009 统一错误语义)\n *\n * 内部解包原生返回:成功返回纯业务值,失败抛 KernelError。\n * 方法面(P2-4 + P2-10):群列表 / 群详情 / 成员列表 / 成员详情 / uin↔uid +\n * 群管操作(踢人 / 禁言 / 管理员 / 名片 / 群名 / 退群 / 精华消息 / @all 剩余)。\n */\nimport { isKernelError, kernelError } from \"../infra/index.js\";\nimport type {\n Group,\n GroupBulletin,\n GroupDetailInfo,\n GroupMember,\n KickMemberV2Req,\n NodeIKernelGroupService,\n NodeIKernelMsgService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\nimport { ChatType, NTGroupMemberRole } from \"../types/index.js\";\nimport { checkLooseResult, unwrap } from \"./result.js\";\n\n/** 群 API:从 session 拿 group service,包装成语义化方法。 */\nexport class GroupApi {\n private readonly service: NodeIKernelGroupService;\n /** 精华消息需要 msg service(按 msgId 拉消息取 msgSeq/msgRandom)。 */\n private readonly msgService: NodeIKernelMsgService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service = session.getGroupService() as unknown as NodeIKernelGroupService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getGroupService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n const msgService = session.getMsgService() as unknown as NodeIKernelMsgService | null;\n if (msgService === null || msgService === undefined) {\n throw kernelError(\"getMsgService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.msgService = msgService;\n }\n\n /**\n * 群列表(force=true 触发原生刷新)。\n *\n * ⚠️ 实测校准(2026-08-08 端到端联调):原生 getGroupList 返回仅\n * `{ result, errMsg }`——**列表数据经 onGroupListUpdate 事件推送**,\n * 不走返回值。消费群列表一律从 `GroupCache.listGroups()` 读(缓存由\n * 事件主动维护);本方法仅作「触发刷新」用,返回值恒为 []。\n */\n async getGroupList(force = false): Promise<Group[]> {\n const raw = await this.service.getGroupList(force);\n if (raw.result !== 0) {\n unwrap(\"getGroupList\", raw.result, raw.errMsg);\n }\n return [];\n }\n\n /** 群详情(groupCode 为群号字符串)。\n * getGroupDetailInfo 返回形状:失败 result=错误码;成功详情可能直接在 result\n * 对象里,也可能在 groupInfo/detailInfo 字段——三种形状都兼容(探测校准)。\n * 2026-08-07 修复:成功(result=0)时不再把 {result:0,errMsg} 原样当详情返回,\n * 而是提取详情字段;形状异常抛 UNKNOWN 而非静默返回垃圾数据。 */\n async getGroupInfo(groupCode: string): Promise<GroupDetailInfo> {\n const raw = await this.service.getGroupDetailInfo(groupCode, 0);\n const { result, errMsg } = raw as { result?: unknown; errMsg: string };\n if (typeof result === \"number\" && result !== 0) {\n unwrap(\"getGroupDetailInfo\", result, errMsg);\n }\n const detail = extractGroupDetail(result, raw);\n if (detail === null) {\n throw kernelError(\n `getGroupDetailInfo 返回形状异常(groupCode=${groupCode}): ${errMsg}`,\n \"UNKNOWN\",\n );\n }\n return detail;\n }\n\n /** 群成员列表(forceFetch=true 强制拉取,默认缓存优先)。 */\n async getGroupMemberList(groupCode: string, forceFetch = false): Promise<GroupMember[]> {\n const raw = await this.service.getAllMemberList(groupCode, forceFetch);\n if (raw.errCode !== 0) {\n throw kernelError(`getAllMemberList 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return [...raw.result.infos.values()];\n }\n\n /** 群成员详情(uids 为空返回空数组)。 */\n async getGroupMemberInfo(groupCode: string, uids: string[]): Promise<GroupMember[]> {\n if (uids.length === 0) {\n return [];\n }\n const raw = await this.service.getMemberInfo(groupCode, uids, true);\n unwrap(\"getMemberInfo\", raw.result, raw.errMsg);\n // 返回形状待探测校准:infos Map<uid, GroupMember>\n const { infos } = raw as unknown as { infos?: Map<string, GroupMember> };\n if (infos instanceof Map) {\n return [...infos.values()];\n }\n return [];\n }\n\n /** uin → uid(私聊发送等需要)。 */\n async uinToUid(uins: string[]): Promise<Map<string, string>> {\n if (uins.length === 0) {\n return new Map();\n }\n const raw = await this.service.getUidByUins(uins);\n if (raw.errCode !== 0) {\n throw kernelError(`getUidByUins 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return raw.uids;\n }\n\n /** uid → uin。 */\n async uidToUin(uids: string[]): Promise<Map<string, string>> {\n if (uids.length === 0) {\n return new Map();\n }\n const raw = await this.service.getUinByUids(uids);\n if (raw.errCode !== 0) {\n throw kernelError(`getUinByUids 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return raw.uins;\n }\n\n /** 群成员角色 → OB11 role 字符串。 */\n static roleToString(role: NTGroupMemberRole): \"owner\" | \"admin\" | \"member\" {\n if (role === NTGroupMemberRole.OWNER) {\n return \"owner\";\n }\n if (role === NTGroupMemberRole.ADMIN) {\n return \"admin\";\n }\n return \"member\";\n }\n\n /**\n * 踢人(set_group_kick)。memberUids 为成员 uid;refuseForever=true 拉黑拒绝再次入群。\n * kickMemberV2 的 kickFlag/kickList 字段值待探测校准(TODO P3 联调)。\n */\n async kickMember(\n groupCode: string,\n memberUids: string[],\n refuseForever: boolean,\n reason = \"\",\n ): Promise<void> {\n if (memberUids.length === 0) {\n throw kernelError(\"kickMember 需要至少一个成员 uid\", \"INVALID_PARAM\");\n }\n // refuseForever=true 拒绝再次入群(optFlag 字段值待探测校准)\n let optFlag = 1;\n if (refuseForever) {\n optFlag = 0;\n }\n const req: KickMemberV2Req = {\n groupCode,\n kickFlag: 0,\n kickList: memberUids.map((uid) => ({\n optFlag,\n optOperate: 0,\n optMemberUid: uid,\n optBytesMsg: \"\",\n })),\n kickListUids: memberUids,\n kickMsg: reason,\n };\n const raw = await this.service.kickMemberV2(req);\n unwrap(\"kickMemberV2\", raw.result, raw.errMsg);\n }\n\n /** 成员禁言(duration 秒,0 解除禁言)。 */\n async setMemberShutUp(\n groupCode: string,\n members: Array<{ uid: string; duration: number }>,\n ): Promise<void> {\n if (members.length === 0) {\n throw kernelError(\"setMemberShutUp 需要至少一个成员\", \"INVALID_PARAM\");\n }\n const raw = await this.service.setMemberShutUp(\n groupCode,\n members.map((m) => ({ uid: m.uid, timeStamp: m.duration })),\n );\n unwrap(\"setMemberShutUp\", raw.result, raw.errMsg);\n }\n\n /** 全员禁言(enable=true 开启全群禁言)。 */\n async setGroupShutUp(groupCode: string, enable: boolean): Promise<void> {\n const raw = await this.service.setGroupShutUp(groupCode, enable);\n unwrap(\"setGroupShutUp\", raw.result, raw.errMsg);\n }\n\n /** 设置管理员(role=ADMIN 设管理,MEMBER 取消;void 语义乐观处理)。 */\n setMemberRole(groupCode: string, uid: string, role: NTGroupMemberRole): void {\n this.service.modifyMemberRole(groupCode, uid, role);\n }\n\n /** 设置群名片(void 语义乐观处理)。 */\n setMemberCardName(groupCode: string, uid: string, cardName: string): void {\n this.service.modifyMemberCardName(groupCode, uid, cardName);\n }\n\n /** 修改群名。 */\n async modifyGroupName(groupCode: string, groupName: string): Promise<void> {\n const raw = await this.service.modifyGroupName(groupCode, groupName, false);\n unwrap(\"modifyGroupName\", raw.result, raw.errMsg);\n }\n\n /** 退群(needDeleteLocalMsg=is_dismiss:是否解散群,仅群主)。 */\n async quitGroup(groupCode: string, needDeleteLocalMsg = false): Promise<void> {\n const raw = await this.service.quitGroupV2({ groupCode, needDeleteLocalMsg });\n unwrap(\"quitGroupV2\", raw.result, raw.errMsg);\n }\n\n /** 设置精华消息(msgId 经 msgService 取 msgSeq/msgRandom)。 */\n async addGroupEssence(groupCode: string, msgId: string): Promise<void> {\n await this.setEssence(groupCode, msgId, true);\n }\n\n /** 取消精华消息。 */\n async removeGroupEssence(groupCode: string, msgId: string): Promise<void> {\n await this.setEssence(groupCode, msgId, false);\n }\n\n /** 精华消息公共流程:按 msgId 拉消息 → add/removeGroupEssence。 */\n private async setEssence(groupCode: string, msgId: string, add: boolean): Promise<void> {\n const peer = { chatType: ChatType.GROUP, peerUid: groupCode };\n const raw = await this.msgService.getMsgsByMsgId(peer, [msgId]);\n unwrap(\"getMsgsByMsgId\", raw.result, raw.errMsg);\n const msg = raw.msgList?.[0];\n if (msg === undefined) {\n throw kernelError(`消息 ${msgId} 不存在`, \"NOT_FOUND\");\n }\n const param = {\n groupCode,\n msgRandom: Number(msg.msgRandom ?? 0),\n msgSeq: Number(msg.msgSeq),\n };\n let label = \"removeGroupEssence\";\n let res: { result?: unknown; errMsg?: unknown } | undefined;\n if (add) {\n label = \"addGroupEssence\";\n res = (await this.service.addGroupEssence(param)) as\n | { result?: unknown; errMsg?: unknown }\n | undefined;\n } else {\n res = (await this.service.removeGroupEssence(param)) as\n | { result?: unknown; errMsg?: unknown }\n | undefined;\n }\n checkLooseResult(label, res);\n }\n\n /**\n * 群公告列表(get_group_announcements;getGroupBulletin 返回形状待探测校准,\n * 兼容 result 数组 / result.bulletins 等宽松提取)。\n */\n async getGroupAnnouncements(groupCode: string): Promise<GroupBulletin[]> {\n let raw: { result?: unknown; errMsg?: unknown };\n try {\n raw = (await this.service.getGroupBulletin(groupCode)) as typeof raw;\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`getGroupBulletin 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n unwrap(\"getGroupBulletin\", toResultNumber(raw.result), raw.errMsg as string | undefined);\n return extractBulletins(raw.result);\n }\n\n /**\n * 发群公告(send_group_announcement;imagePath 提供时先 uploadGroupBulletinPic\n * 再带 picId 发布——两步参数形状均待探测校准)。\n */\n async publishGroupAnnouncement(\n groupCode: string,\n content: string,\n imagePath?: string,\n ): Promise<void> {\n let picId: string | undefined;\n if (imagePath !== undefined && imagePath !== \"\") {\n picId = await this.uploadBulletinPic(groupCode, imagePath);\n }\n try {\n checkLooseResult(\n \"publishGroupBulletin\",\n (await this.service.publishGroupBulletin({\n groupCode,\n content,\n ...(picId !== undefined ? { picId } : {}),\n })) as { result?: unknown } | null | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`publishGroupBulletin 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 删群公告(delete_group_announcement;参数待探测校准)。 */\n async deleteGroupAnnouncement(groupCode: string, announcementId: string): Promise<void> {\n try {\n checkLooseResult(\n \"deleteGroupBulletin\",\n (await this.service.deleteGroupBulletin(groupCode, announcementId)) as\n | { result?: unknown }\n | null\n | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`deleteGroupBulletin 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 专属头衔(set_group_member_special_title;setIdentityTitleInfo 参数待校准)。 */\n async setMemberSpecialTitle(groupCode: string, uid: string, title: string): Promise<void> {\n try {\n checkLooseResult(\n \"setIdentityTitleInfo\",\n (await this.service.setIdentityTitleInfo(groupCode, uid, title)) as\n | { result?: unknown }\n | null\n | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`setIdentityTitleInfo 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 群头像(set_group_avatar;GroupService.setHeader 参数待校准)。 */\n async setGroupAvatar(groupCode: string, imagePath: string): Promise<void> {\n try {\n checkLooseResult(\n \"setHeader\",\n (await this.service.setHeader(groupCode, imagePath)) as\n | { result?: unknown }\n | null\n | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`setHeader(group) 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 公告图片上传(uploadGroupBulletinPic 返回 picId 待校准,失败原样上抛)。 */\n private async uploadBulletinPic(groupCode: string, imagePath: string): Promise<string> {\n const res = (await this.service.uploadGroupBulletinPic(groupCode, imagePath)) as\n | {\n result?: unknown;\n picId?: unknown;\n }\n | null\n | undefined;\n checkLooseResult(\"uploadGroupBulletinPic\", res);\n return typeof res?.picId === \"string\" ? res.picId : \"\";\n }\n\n /** @all 剩余次数(get_group_at_all_remain)。 */\n async getGroupRemainAtTimes(groupCode: string): Promise<{\n canAtAll: boolean;\n remainAtAllCountForUin: number;\n remainAtAllCountForGroup: number;\n }> {\n const raw = await this.service.getGroupRemainAtTimes(groupCode);\n if (raw.errCode !== 0) {\n throw kernelError(`getGroupRemainAtTimes 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return {\n canAtAll: raw.atInfo.canAtAll,\n remainAtAllCountForUin: raw.atInfo.remainAtAllCountForUin,\n remainAtAllCountForGroup: raw.atInfo.remainAtAllCountForGroup,\n };\n }\n}\n\n/** result 字段宽松取数(数字直接用,缺省 0 = 成功语义)。 */\nfunction toResultNumber(result: unknown): number {\n return typeof result === \"number\" ? result : 0;\n}\n\n/** 公告列表宽松提取(result 数组 / { bulletins } / { result: 数组 } 兼容,形状待校准)。 */\nfunction extractBulletins(result: unknown): GroupBulletin[] {\n const candidates = [result];\n if (result !== null && typeof result === \"object\") {\n const rec = result as Record<string, unknown>;\n for (const key of [\"bulletins\", \"announcementList\", \"result\", \"list\"] as const) {\n candidates.push(rec[key]);\n }\n }\n for (const candidate of candidates) {\n if (Array.isArray(candidate)) {\n return candidate.filter(\n (item): item is GroupBulletin => item !== null && typeof item === \"object\",\n );\n }\n }\n return [];\n}\n\n/** 未知异常 → 消息字符串。 */\nfunction errMsgString(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n/**\n * 从 getGroupDetailInfo 返回提取群详情(兼容三种形状):\n * - result 为对象 → 详情直接在其中(最常见)\n * - result 为 0/空 → 从 raw.groupInfo / detailInfo / info 提取\n * 提取不到返回 null(调用方抛 UNKNOWN)。导出供单测。\n */\nexport function extractGroupDetail(result: unknown, raw: unknown): GroupDetailInfo | null {\n if (result !== null && typeof result === \"object\") {\n return result as GroupDetailInfo;\n }\n const detail = raw as {\n groupInfo?: unknown;\n detailInfo?: unknown;\n info?: unknown;\n };\n const candidate = detail.groupInfo ?? detail.detailInfo ?? detail.info;\n if (candidate !== undefined && candidate !== null && typeof candidate === \"object\") {\n return candidate as GroupDetailInfo;\n }\n return null;\n}\n","/**\n * GroupNotifyApi:群通知/禁言列表语义化 API(ADR-009 统一错误语义,P2-13)\n *\n * get_group_system_msg / set_group_add_request / get_group_shut_list 共用。\n * getSingleScreenNotifies / getGroupShutUpMemberList 的列表可能走 listener 回调,\n * 直接调用返回形状待探测校准:兼容 result 数组 / 字段 / 直接数组。\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n GroupMember,\n GroupNotify,\n NodeIKernelGroupService,\n NodeIQQNTWrapperSession,\n NTGroupRequestOperateTypes,\n} from \"../types/index.js\";\nimport { unwrap } from \"./result.js\";\n\n/** 群通知 API:从 session 拿 group service,包装成语义化方法。 */\nexport class GroupNotifyApi {\n private readonly service: NodeIKernelGroupService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service = session.getGroupService() as unknown as NodeIKernelGroupService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getGroupService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n }\n\n /** 单屏通知列表(get_group_system_msg / set_group_add_request 共用)。 */\n async getSingleScreenNotifies(doubt: boolean, count: number): Promise<GroupNotify[]> {\n const raw = await this.service.getSingleScreenNotifies(doubt, \"\", count);\n return extractNotifyList(raw.result);\n }\n\n /** 禁言成员列表(get_group_shut_list)。 */\n async getGroupShutUpMemberList(groupCode: string): Promise<GroupMember[]> {\n const raw = await this.service.getGroupShutUpMemberList(groupCode);\n if (raw.result !== 0) {\n unwrap(\"getGroupShutUpMemberList\", raw.result, raw.errMsg);\n }\n return extractMemberList(raw.result);\n }\n\n /** 处理群请求(set_group_add_request 应答;approve=true 同意 / false 拒绝)。 */\n async handleGroupRequest(\n doubt: boolean,\n notify: GroupNotify,\n operateType: NTGroupRequestOperateTypes,\n reason: string,\n ): Promise<void> {\n const { group, seq, type } = notify;\n let groupCode = \"\";\n if (group !== undefined) {\n ({ groupCode } = group);\n }\n let postscript = reason;\n if (postscript === \"\") {\n postscript = \" \";\n }\n await this.service.operateSysNotify(doubt, {\n operateType,\n targetMsg: {\n seq,\n type,\n groupCode,\n postscript,\n },\n });\n }\n}\n\n/** 从 getSingleScreenNotifies 返回提取通知数组(兼容 result 数组 / notifies 字段)。 */\nexport function extractNotifyList(result: unknown): GroupNotify[] {\n if (Array.isArray(result)) {\n return result as GroupNotify[];\n }\n if (result !== null && typeof result === \"object\") {\n const obj = result as { notifies?: unknown; groupNotifies?: unknown };\n if (Array.isArray(obj.notifies)) {\n return obj.notifies as GroupNotify[];\n }\n if (Array.isArray(obj.groupNotifies)) {\n return obj.groupNotifies as GroupNotify[];\n }\n }\n return [];\n}\n\n/** 从 getGroupShutUpMemberList 返回提取成员数组(兼容 result 数组 / memberList 字段)。 */\nexport function extractMemberList(result: unknown): GroupMember[] {\n if (Array.isArray(result)) {\n return result as unknown as GroupMember[];\n }\n if (result !== null && typeof result === \"object\") {\n const obj = result as { memberList?: unknown; list?: unknown };\n if (Array.isArray(obj.memberList)) {\n return obj.memberList as unknown as GroupMember[];\n }\n if (Array.isArray(obj.list)) {\n return obj.list as unknown as GroupMember[];\n }\n }\n return [];\n}\n","/**\n * MsgApi:消息语义化 API(ADR-009 统一错误语义)\n *\n * 内部解包原生 `{ result, errMsg }`:成功返回纯业务值,失败抛 KernelError。\n * 协议层只维护 `KernelErrorCode → 协议错误码` 映射表,不解析错误逻辑。\n *\n * 方法面(P2-1):发送 / 撤回 / 拉历史 / 标记已读。group/friend 等后续 apis 同构。\n */\nimport { createHash } from \"node:crypto\";\nimport { readFile, stat } from \"node:fs/promises\";\nimport { basename, extname } from \"node:path\";\nimport type { MsgEventChannel } from \"../bridge/msg-bridge.js\";\nimport { isKernelError, kernelError } from \"../infra/index.js\";\nimport type {\n CanonicalElement,\n NodeIKernelMsgService,\n NodeIQQNTWrapperSession,\n NudgeParam,\n Peer,\n PttElement,\n RawElement,\n RawMessage,\n SendMessageElement,\n VideoElement,\n} from \"../types/index.js\";\nimport { ChatType, ElementType, toSendElements } from \"../types/index.js\";\nimport { checkLooseResult, unwrapResult } from \"./result.js\";\n\n/** 发送状态(onMsgInfoListUpdate 事件 msg.sendStatus)。 */\nconst SEND_STATUS = { FAILED: 0, SENDING: 1, SUCCESS: 2, SUCCESS_NO_SEQ: 3 } as const;\n\n/** 发送确认超时(毫秒)。 */\nconst SEND_CONFIRM_TIMEOUT_MS = 15_000;\n\n/**\n * PIC 元素发送契约值(NapCat 同款发送契约值,未逐字段校准)。\n * 下列固定值均为实测调通的 wrapper 发送契约(picType / picSubType /\n * picWidth / picHeight / original / thumbFileSize),只抽常量不改值——改值会破坏图片发送。\n */\nconst PIC_TYPE = 1000;\nconst PIC_SUB_TYPE = 0;\nconst PIC_WIDTH = 0;\nconst PIC_HEIGHT = 0;\nconst PIC_ORIGINAL = true;\nconst PIC_THUMB_FILE_SIZE = 0;\n\n/**\n * PTT 元素发送契约值(NapCat 同款发送契约值,未逐字段校准)。\n * silk v3 协议:下列固定值均为实测调通的 wrapper 发送契约(formatType / voiceType /\n * voiceChangeType / canConvert2Text / playState / autoConvertText / storeID /\n * otherBusinessInfo.aiVoiceType),只抽常量不改值——改值会破坏语音发送。\n */\nconst PTT_FORMAT_TYPE = 1;\nconst PTT_VOICE_TYPE = 1;\nconst PTT_VOICE_CHANGE_TYPE = 0;\nconst PTT_CAN_CONVERT_2_TEXT = true;\nconst PTT_PLAY_STATE = 1;\nconst PTT_AUTO_CONVERT_TEXT = 0;\nconst PTT_STORE_ID = 0;\nconst PTT_AI_VOICE_TYPE = 0;\n/** 假波形数组(占位波形):无真实振幅数据时发送的固定占位,silk v3 协议契约值。 */\nconst PTT_WAVE_AMPLITUDES = [0, 18, 9, 23, 16, 17, 16, 15, 44, 17, 24, 20, 14, 15, 17];\n\n/**\n * VIDEO 元素发送契约值(2026-10-17 收向实测字段 + 发送实测调通)。\n *\n * 依据:历史视频消息 `getLatestDbMsgs` 的 videoElement 实测快照(fileFormat /\n * busiType / subBusiType / storeID / original / videoFrom / sourceVideoCodecFormat\n * 均为实测出现的取值)。2026-10-17 18:12 自建宿主实测调通:`sendMsg` 成功\n * (sendStatus 2/3,群内可播放);关键坑是**缩略图目标路径必须用视频 md5**\n * (见 prepareVideoElement 注释),与字段取值无关。\n */\nconst VIDEO_FILE_FORMAT = 0;\nconst VIDEO_BUSI_TYPE = 0;\nconst VIDEO_SUB_BUSI_TYPE = 0;\nconst VIDEO_STORE_ID = 1;\nconst VIDEO_ORIGINAL = true;\nconst VIDEO_FROM = 0;\nconst VIDEO_SOURCE_CODEC_FORMAT = 0;\n/** 视频缩略图在 thumbPath Map 中的 key(收向实测 Map<0, path>)。 */\nconst VIDEO_THUMB_KEY = 0;\n/** getRichMediaFilePathForGuild 的 downloadType:1 = 原文件,2 = 缩略图。 */\nconst RICH_MEDIA_DOWNLOAD_TYPE_ORIGIN = 1;\nconst RICH_MEDIA_DOWNLOAD_TYPE_THUMB = 2;\n\n/** downloadRichMedia 契约值(2026-09-08 B1 实测:downloadType=2 可用 / thumbSize=0)。 */\nconst DOWNLOAD_TYPE_COMMON = 2;\nconst THUMB_SIZE_NONE = 0;\n/** 语音下载轮询参数(300ms × 34 ≈ 10s;返回 void 只能经重拉元素观察完成)。 */\nconst PTT_POLL_INTERVAL_MS = 300;\nconst PTT_POLL_TIMES = 34;\n\n/** sleep(下载轮询间隔)。 */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n\n/** 未知异常 → 消息字符串。 */\nfunction errMsgString(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n/** 语音下载完成判定:有路径且(传输完成或末次轮询)才返回元素,否则 null。 */\nfunction pttDownloadResult(latest: PttElement | undefined, isLastPoll: boolean): PttElement | null {\n if (latest === undefined) {\n return null;\n }\n const hasPath = latest.filePath !== undefined && latest.filePath !== \"\";\n const done = latest.transferStatus === 2 || latest.transferStatus === 4;\n return hasPath && (done || isLastPoll) ? latest : null;\n}\n\n/** 收藏表情项 → url(字段名待校准,url/imageUrl/emojiUrl 逐个兜底)。 */\nfunction extractFavEmojiUrls(list: unknown[]): string[] {\n const out: string[] = [];\n for (const item of list) {\n if (item === null || typeof item !== \"object\") {\n continue;\n }\n const rec = item as Record<string, unknown>;\n const url = rec[\"url\"] ?? rec[\"imageUrl\"] ?? rec[\"emojiUrl\"];\n if (typeof url === \"string\" && url !== \"\") {\n out.push(url);\n }\n }\n return out;\n}\n\n/** 读文件 stat(富媒体发送预处理;文件不存在抛 INVALID_PARAM)。 */\nasync function statFile(path: string, label = \"图片\"): Promise<{ size: number }> {\n try {\n return await stat(path);\n } catch {\n throw kernelError(`${label}文件不存在: ${path}`, \"INVALID_PARAM\");\n }\n}\n\n/** 计算文件 md5(十六进制小写)。 */\nasync function hashFile(path: string, label = \"图片\"): Promise<string> {\n try {\n const buf = await readFile(path);\n return createHash(\"md5\").update(buf).digest(\"hex\");\n } catch {\n throw kernelError(`${label}文件读取失败: ${path}`, \"INVALID_PARAM\");\n }\n}\n\n/**\n * 清洗撤回消息 id(防御外部脏参数)。\n *\n * 调用方(IPC / 协议层 / koishi 适配器)可能传 null、非数组、空字符串等——\n * 直接透传会让裸 TypeError 逃逸,或把脏值一路送到 wrapper.node 才失败\n * (原生层只回「无错误详情」,排查困难)。规则:\n * - 非数组 → INVALID_PARAM\n * - 过滤空字符串 / 纯空白 / 非字符串\n * - 清洗后为空 → INVALID_PARAM\n */\nfunction sanitizeRecallMsgIds(msgIds: string[]): string[] {\n if (!Array.isArray(msgIds)) {\n throw kernelError(\"recallMessage 的 msgIds 必须是数组\", \"INVALID_PARAM\");\n }\n const ids = msgIds.filter((id) => typeof id === \"string\" && id.trim() !== \"\");\n if (ids.length === 0) {\n throw kernelError(\"recallMessage 需要至少一个非空 msgId\", \"INVALID_PARAM\");\n }\n return ids;\n}\n\n/** 消息 API:从 session 拿 msg service,包装成语义化方法。 */\nexport class MsgApi {\n private readonly service: NodeIKernelMsgService;\n /** 消息事件通道(sendMsg 后等 onMsgInfoListUpdate 确认发送结果)。 */\n private readonly channel: MsgEventChannel | null;\n /** NodeQQNTWrapperUtil(富媒体发送 copyFile 用;session 拿不到时可为 null)。 */\n private readonly util: { get(): unknown } | null;\n /** 上次生成 msgId 的时间(单调递增,2026-08-07 防同毫秒并发碰撞)。 */\n private lastMsgTime = 0;\n\n constructor(session: NodeIQQNTWrapperSession, channel?: MsgEventChannel, util?: unknown) {\n const service = session.getMsgService() as unknown as NodeIKernelMsgService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getMsgService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n this.channel = channel ?? null;\n // util 传入形态:构造器(NodeQQNTWrapperUtil,含 get())或实例,宽松兼容\n this.util = util !== null && util !== undefined ? (util as { get(): unknown }) : null;\n }\n\n /**\n * 生成 msgId 时间戳(单调递增)。\n * generateMsgUniqueId(chatType, time) 以 time 区分消息——同毫秒并发发送\n * (机器人群发/多会话同时回复)Date.now() 会碰撞,msgId 相同导致 wrapper\n * 拒绝或覆盖。严格单调递增保证进程内唯一。\n */\n private nextMsgTime(): string {\n const now = Date.now();\n if (now > this.lastMsgTime) {\n this.lastMsgTime = now;\n } else {\n this.lastMsgTime += 1;\n }\n return String(this.lastMsgTime);\n }\n\n /**\n * 发送消息:canonical 元素 → NT 发送元素 → sendMsg(NapCat 式)。\n * 返回 NT msgId(雪花 ID)。\n *\n * 2026-08-11 修复(NapCat 式,实测):\n * - sendMsg 第一参传 '0'(固定),msgId 塞 peer.guildId —— 传 msgId 作第一参\n * 时 wrapper 返回 result=5(失败),NapCat 同款调用返回 result=0。\n * - 发送结果以 onMsgInfoListUpdate 事件确认(sendStatus===2 成功):sendMsg\n * 返回值 result 可能为 5 但事件仍成功(异步确认),反之亦然。\n * - ⚠️ 必须先注册事件监听再调 sendMsg(事件可能在 sendMsg 返回前就触发)。\n */\n async sendMessage(target: Peer, elements: CanonicalElement[]): Promise<{ msgId: string }> {\n // NapCat 式富媒体预处理:PIC 元素补 md5/fileName/sourcePath 并放置文件\n // (getRichMediaFilePathForGuild → util.copyFile),否则发送器走未初始化\n // 的 FlashFileUploadService 导致 rich media transfer failed。\n const sendElements = await this.prepareSendElements(elements);\n const msgId = this.service.generateMsgUniqueId(target.chatType, this.nextMsgTime());\n // NapCat 同款:msgId 塞 guildId,第一参 '0'\n const sendPeer: Peer = { ...target, guildId: msgId };\n // 无事件通道(老用法):退化为看返回值\n if (this.channel === null) {\n const raw = await this.service.sendMsg(\"0\", sendPeer, sendElements, new Map());\n unwrapResult(\"sendMsg\", raw);\n return { msgId };\n }\n // 有事件通道:先注册确认监听(NapCat 式,事件可能早于 sendMsg 返回触发),\n // 再调 sendMsg。最终结果以事件 sendStatus 为准(raw.result 非 0 不判失败)。\n const confirm = this.confirmSend(msgId, target);\n // ⚠️ 预消费 confirm 的 rejection(2026-08-22 崩溃根因修复):若下方\n // service.sendMsg 抛错(wrapper 内部异常),confirm 确认监听已注册却无人\n // await,事件回调里 reject 会变成 unhandledRejection,Node 默认抛错退出\n // 直接拖垮整个子进程(实测:sendStatus=0 确认事件触发后进程崩溃、IPC\n // 通道关闭)。先挂 no-op 消费兜底,`await confirm` 仍能正常收到结果\n // (Promise 允许多个消费者),失败语义不变。\n confirm.catch(() => undefined);\n await this.service.sendMsg(\"0\", sendPeer, sendElements, new Map());\n await confirm;\n return { msgId };\n }\n\n /**\n * canonical 元素 → 发送元素,富媒体(PIC/PTT)做 NapCat 式预处理。\n * 2026-08-11 修复(实测):图片发送必须 elementType=2(PIC)+ 完整 picElement\n * (md5HexStr/fileSize/fileName/sourcePath),且文件须经 getRichMediaFilePathForGuild\n * 计算目标路径 + util.copyFile 放置——缺任一环节 sendMsg 返回 rich media transfer failed。\n * 2026-08-12(语音):PTT 同理——只给 filePath 触发 wrapper 内部\n * \"Cannot convert undefined or null to object\"(缺字段转换失败)。\n */\n private async prepareSendElements(elements: CanonicalElement[]): Promise<SendMessageElement[]> {\n const out: SendMessageElement[] = [];\n for (const el of elements) {\n switch (el.type) {\n case \"image\":\n out.push(await this.prepareImageElement(el.path));\n break;\n case \"voice\":\n out.push(await this.preparePttElement(el.path));\n break;\n case \"video\":\n out.push(await this.prepareVideoElement(el));\n break;\n case \"file\":\n out.push(await this.prepareFileElement(el));\n break;\n default:\n out.push(...toSendElements([el]));\n break;\n }\n }\n return out;\n }\n\n /** PIC 元素 NapCat 式预处理:md5 → 目标路径 → copyFile → 完整 picElement。 */\n private async prepareImageElement(path: string): Promise<SendMessageElement> {\n const file = await statFile(path);\n const md5 = await hashFile(path);\n const fileName = basename(path);\n const relPath = await this.placeMediaFile(path, ElementType.PIC, md5, fileName);\n return {\n elementType: ElementType.PIC,\n elementId: \"\",\n picElement: {\n md5HexStr: md5,\n fileSize: String(file.size),\n picWidth: PIC_WIDTH,\n picHeight: PIC_HEIGHT,\n fileName,\n sourcePath: relPath,\n original: PIC_ORIGINAL,\n picType: PIC_TYPE,\n picSubType: PIC_SUB_TYPE,\n fileUuid: \"\",\n fileSubId: \"\",\n thumbFileSize: PIC_THUMB_FILE_SIZE,\n summary: \"\",\n },\n };\n }\n\n /**\n * PTT 元素 NapCat 式预处理:md5 → 目标路径 → copyFile → 完整 pttElement。\n *\n * 2026-08-12(实测依据):只给 filePath 时 wrapper 内部抛\n * \"Cannot convert undefined or null to object\"(缺 md5HexStr/fileSize 等字段),\n * 且发送后进程崩溃重启(supervisor 自动拉起)。完整字段(NapCat 同款,\n * formatType/voiceType/canConvert2Text/waveAmplitudes 等)消除该错误。\n *\n * ⚠️ silk 格式:QQ 语音协议为 silk v3。非 silk → silk 的转码在协议层\n * (adapter,@napuketto/media 的 encodePcmToSilk,2026-08-12 接线)完成,\n * 此处接收的已是 silk 路径,仅做放置。kernel 不 import media(解耦红线)。\n */\n private async preparePttElement(path: string): Promise<SendMessageElement> {\n const file = await statFile(path);\n const md5 = await hashFile(path);\n const fileName = basename(path);\n const relPath = await this.placeMediaFile(path, ElementType.PTT, md5, fileName);\n // 时长估算(NapCat 同款规则:~3KB/s 语音码率,缺 ffprobe 时兜底)\n const duration = Math.max(1, Math.floor(file.size / 1024 / 3));\n return {\n elementType: ElementType.PTT,\n elementId: \"\",\n pttElement: {\n fileName,\n filePath: relPath,\n md5HexStr: md5,\n fileSize: String(file.size),\n duration,\n formatType: PTT_FORMAT_TYPE,\n voiceType: PTT_VOICE_TYPE,\n voiceChangeType: PTT_VOICE_CHANGE_TYPE,\n canConvert2Text: PTT_CAN_CONVERT_2_TEXT,\n waveAmplitudes: PTT_WAVE_AMPLITUDES,\n fileSubId: \"\",\n playState: PTT_PLAY_STATE,\n autoConvertText: PTT_AUTO_CONVERT_TEXT,\n storeID: PTT_STORE_ID,\n otherBusinessInfo: { aiVoiceType: PTT_AI_VOICE_TYPE },\n },\n };\n }\n\n /**\n * VIDEO 元素预处理:md5 → 目标路径 → copyFile → 完整 videoElement。\n *\n * 字段依据:2026-10-17 收向实测(历史视频消息 videoElement 全字段快照)+\n * 发送实测(2026-10-17 18:12 自建宿主发送成功,群内可播放):\n * 1. `thumbPath` 必须是真实 `Map<number, string>`(收向实测 `Map<0, path>`)——\n * 经 JSON/IPC 传参会退化成 `{}`,wrapper 转换失败;\n * 2. **缩略图目标路径用视频 md5**(`Video/<YYYY-MM>/Thumb/<videoMd5>_0.png`):\n * 真实消息实测 videoMd5=`3d27…`、thumbMd5=`46da…`,落盘文件名却是\n * `3d27…_0.png`;此前按 thumbMd5 放置 → native 传输按视频 md5 找不到缩略图\n * → `rich media transfer failed`(20+ 组字段变体都无效的真正原因);\n * 3. `thumbMd5` 是缩略图**内容**的 md5,与文件名无关;`fileTime` 为秒。\n */\n private async prepareVideoElement(\n el: Extract<CanonicalElement, { type: \"video\" }>,\n ): Promise<SendMessageElement> {\n const file = await statFile(el.path, \"视频\");\n const md5 = await hashFile(el.path, \"视频\");\n const fileName = basename(el.path);\n const relPath = await this.placeMediaFile(\n el.path,\n ElementType.VIDEO,\n md5,\n fileName,\n RICH_MEDIA_DOWNLOAD_TYPE_ORIGIN,\n );\n const videoElement: VideoElement = {\n fileName,\n filePath: relPath,\n fileSize: String(file.size),\n videoMd5: md5,\n fileTime: el.durationSec ?? 0,\n fileFormat: VIDEO_FILE_FORMAT,\n busiType: VIDEO_BUSI_TYPE,\n subBusiType: VIDEO_SUB_BUSI_TYPE,\n fileUuid: \"\",\n fileSubId: \"\",\n storeID: VIDEO_STORE_ID,\n original: VIDEO_ORIGINAL,\n videoFrom: VIDEO_FROM,\n sourceVideoCodecFormat: VIDEO_SOURCE_CODEC_FORMAT,\n };\n if (el.thumbPath !== undefined && el.thumbPath !== \"\") {\n const thumbSize = await statFile(el.thumbPath, \"视频缩略图\");\n const thumbMd5 = await hashFile(el.thumbPath, \"视频缩略图\");\n // ⚠️ 目标路径必须用**视频 md5**(不是缩略图内容 md5):2026-10-17 实测\n // 真实视频消息的缩略图文件名为 `Thumb/<videoMd5>_0.png`,而\n // `thumbMd5` 只是缩略图内容的 md5(例:videoMd5=3d27…、thumbMd5=46da…,\n // 实际落盘 `3d27…_0.png`)。此前用 thumbMd5 算路径 → native 传输按视频\n // md5 找不到缩略图 → `rich media transfer failed`。\n const thumbRelPath = await this.placeMediaFile(\n el.thumbPath,\n ElementType.VIDEO,\n md5,\n `${md5}${extname(el.thumbPath)}`,\n RICH_MEDIA_DOWNLOAD_TYPE_THUMB,\n );\n videoElement.thumbMd5 = thumbMd5;\n videoElement.thumbSize = thumbSize.size;\n videoElement.thumbWidth = el.width ?? 0;\n videoElement.thumbHeight = el.height ?? 0;\n videoElement.thumbPath = new Map([[VIDEO_THUMB_KEY, thumbRelPath]]);\n }\n return { elementType: ElementType.VIDEO, elementId: \"\", videoElement };\n }\n\n /**\n * FILE 元素预处理(P2-2 预留口子,upload_private/group_file 落地):\n * pic/ptt/video 同款 NapCat 式链路——md5 → getRichMediaFilePathForGuild(FILE)\n * → copyFile → 完整 fileElement。链路模式已验证(三类媒体同构);fileElement\n * 字段值(fileSize 字符串形态、fileUuid 空串等)待探测校准。\n */\n private async prepareFileElement(\n el: Extract<CanonicalElement, { type: \"file\" }>,\n ): Promise<SendMessageElement> {\n const file = await statFile(el.path, \"文件\");\n const md5 = await hashFile(el.path, \"文件\");\n const fileName = el.name !== undefined && el.name !== \"\" ? el.name : basename(el.path);\n const relPath = await this.placeMediaFile(el.path, ElementType.FILE, md5, fileName);\n return {\n elementType: ElementType.FILE,\n elementId: \"\",\n fileElement: {\n fileName,\n filePath: relPath,\n fileSize: String(file.size),\n fileUuid: \"\",\n },\n };\n }\n\n /**\n * 富媒体文件放置(PIC/PTT/VIDEO 共用):md5 + 文件名 → QQ 内部目标路径\n * (getRichMediaFilePathForGuild)→ util.copyFile 放置 → 返回目标路径。\n *\n * `downloadType`:1 = 原文件(Ori),2 = 缩略图(Thumb,视频缩略图必须)。\n */\n private async placeMediaFile(\n path: string,\n elementType: ElementType,\n md5: string,\n fileName: string,\n downloadType = RICH_MEDIA_DOWNLOAD_TYPE_ORIGIN,\n ): Promise<string> {\n const service = this.service;\n const util = this.util;\n // getRichMediaFilePathForGuild:QQ 内部目标路径(纯文件名,相对数据根)。\n // 固定入参 elementSubType / thumbSize / downloadType 为 NapCat 同款发送契约值\n // (未逐字段校准),发送方向固定,改值会破坏富媒体路径计算。\n const relPath = service.getRichMediaFilePathForGuild({\n md5HexStr: md5,\n fileName,\n elementType,\n elementSubType: 0, // 元素子类型(发送固定 0)\n thumbSize: 0, // 缩略图尺寸(发送固定 0,不预生成缩略图)\n needCreate: true,\n downloadType, // 下载类型:1 = 原文件,2 = 缩略图(视频缩略图必须)\n file_uuid: \"\",\n });\n if (util !== null) {\n const instance = typeof util.get === \"function\" ? util.get() : util;\n const copy = (instance as Record<string, unknown>)[\"copyFile\"];\n if (typeof copy === \"function\") {\n await (copy as (a: string, b: string) => Promise<unknown>).call(\n instance,\n path,\n relPath,\n );\n }\n }\n return relPath;\n }\n\n /** 注册 onMsgInfoListUpdate 确认监听(返回 Promise,resolve 时发送成功)。 */\n private confirmSend(msgId: string, target: Peer): Promise<void> {\n const channel = this.channel;\n if (channel === null) {\n return Promise.resolve();\n }\n return new Promise((resolve, reject) => {\n const off = channel.on(\"Msg/onMsgInfoListUpdate\", (list) => {\n const mine = list.find((m) => m.guildId === msgId);\n if (mine === undefined) {\n return; // 不是本次消息\n }\n if (\n mine.sendStatus === SEND_STATUS.SUCCESS ||\n mine.sendStatus === SEND_STATUS.SUCCESS_NO_SEQ\n ) {\n off();\n resolve();\n } else if (mine.sendStatus === SEND_STATUS.FAILED) {\n off();\n reject(\n kernelError(`sendMsg 失败: sendStatus=${mine.sendStatus}`, \"SEND_FAILED\"),\n );\n }\n // SENDING:继续等\n });\n // 超时兜底\n setTimeout(() => {\n off();\n reject(\n kernelError(\n `sendMsg 等待确认超时(msgId=${msgId}, target=${JSON.stringify(target)})`,\n \"SEND_FAILED\",\n ),\n );\n }, SEND_CONFIRM_TIMEOUT_MS);\n });\n }\n\n /** 撤回消息(群聊管理员 / 私聊 2 分钟内)。 */\n async recallMessage(target: Peer, msgIds: string[]): Promise<void> {\n const raw = await this.service.recallMsg(target, sanitizeRecallMsgIds(msgIds));\n unwrapResult(\"recallMsg\", raw);\n }\n\n /** 拉取历史消息(msgId 为空从最新拉;count 条,时间倒序)。 */\n async fetchMessages(\n target: Peer,\n opts: { count: number; msgId?: string },\n ): Promise<RawMessage[]> {\n const raw = await this.service.getMsgs(target, opts.msgId ?? \"\", opts.count, false);\n unwrapResult(\"getMsgs\", raw);\n return raw.msgList ?? [];\n }\n\n /** 按 msgId 批量拉取消息(get_msg / 精华消息 / ptt 转文字共用)。 */\n async fetchMsgsByMsgId(target: Peer, ids: string[]): Promise<RawMessage[]> {\n if (ids.length === 0) {\n return [];\n }\n const raw = await this.service.getMsgsByMsgId(target, ids);\n unwrapResult(\"getMsgsByMsgId\", raw);\n return raw.msgList ?? [];\n }\n\n /** 消息表情表态(set_msg_emoji_like;like=true 点赞,false 取消)。 */\n async setMsgEmojiLike(\n target: Peer,\n opts: { msgSeq: string; emojiId: string; emojiType: string; like: boolean },\n ): Promise<void> {\n const raw = await this.service.setMsgEmojiLikes(\n target,\n opts.msgSeq,\n opts.emojiId,\n opts.emojiType,\n opts.like,\n );\n unwrapResult(\"setMsgEmojiLikes\", raw);\n }\n\n /**\n * 语音转文字(fetch_ptt_text)。\n * 流程:按 msgId 拉消息 → 找 PTT 元素 → translatePtt2Text(异步转写)\n * → 再拉一次消息读 pttElement.text。\n */\n async fetchPttText(msgId: string, target: Peer): Promise<string> {\n const msgs = await this.fetchMsgsByMsgId(target, [msgId]);\n const ptt = findPttElement(msgs);\n if (ptt === null) {\n throw kernelError(\"消息中不包含语音\", \"NOT_FOUND\");\n }\n const raw = await this.service.translatePtt2Text(msgId, target, ptt);\n unwrapResult(\"translatePtt2Text\", raw);\n // 转写异步完成:再拉一次拿 text\n const after = await this.fetchMsgsByMsgId(target, [msgId]);\n const text = findPttElement(after)?.pttElement?.text;\n if (text === undefined || text === \"\") {\n throw kernelError(\"获取语音转文字结果失败\", \"UNKNOWN\");\n }\n return text;\n }\n\n /** 标记会话已读。 */\n async markRead(target: Peer): Promise<void> {\n const raw = await this.service.setMsgRead(target);\n unwrapResult(\"setMsgRead\", raw);\n }\n\n /**\n * 语音主动下载(get_record 本地未命中兜底,2026-09-08 B1)。\n * downloadRichMedia 返回 void(实测),完成信号 = 重拉消息元素:\n * 轮询(300ms × 34,约 10s)直到 filePath 非空且 transferStatus 进入\n * 已下载态(2/4);超时但 filePath 已有值则尽力返回(磁盘命中判断留给\n * 调用方),完全无路径抛 NOT_FOUND。\n */\n async downloadPtt(msgId: string, target: Peer): Promise<PttElement> {\n const found = await this.pttElementOf(msgId, target);\n if (found === null) {\n throw kernelError(\"消息中不包含语音\", \"NOT_FOUND\");\n }\n await this.service.downloadRichMedia({\n msgId,\n elemId: found.elemId,\n chatType: target.chatType,\n downloadType: DOWNLOAD_TYPE_COMMON,\n thumbSize: THUMB_SIZE_NONE,\n });\n for (let i = 0; i < PTT_POLL_TIMES; i++) {\n await sleep(PTT_POLL_INTERVAL_MS);\n const again = await this.fetchMsgsByMsgId(target, [msgId]);\n const result = pttDownloadResult(\n findPttElement(again)?.pttElement,\n i === PTT_POLL_TIMES - 1,\n );\n if (result !== null) {\n return result;\n }\n }\n throw kernelError(\"语音下载未完成(超时)\", \"TIMEOUT\");\n }\n\n /** 取消息内语音元素(无则 null;附 elemId 供主动下载)。 */\n private async pttElementOf(\n msgId: string,\n target: Peer,\n ): Promise<{ elemId: string; ptt: PttElement } | null> {\n const msgs = await this.fetchMsgsByMsgId(target, [msgId]);\n const el = findPttElement(msgs);\n const ptt = el?.pttElement;\n if (el === null || ptt === undefined) {\n return null;\n }\n return { elemId: el.elementId ?? \"\", ptt };\n }\n\n /** 发送输入状态(set_input_status;eventType=1 输入中,0 停止)。 */\n async setInputStatus(target: Peer, eventType: number): Promise<void> {\n await this.service.sendShowInputStatusReq(target.chatType, eventType, target.peerUid);\n }\n\n /** 戳一戳发送(send_friend/group_nudge;参数形状待探测校准,失败抛 UNKNOWN)。 */\n async sendNudge(target: Peer, opts: { receiverUid: string; callerUid: string }): Promise<void> {\n const param: NudgeParam = {\n receiverUid: opts.receiverUid,\n callerUid: opts.callerUid,\n ...(target.chatType === ChatType.GROUP ? { confId: target.peerUid } : {}),\n };\n try {\n checkLooseResult(\n \"sendNudge\",\n (await this.service.sendNudge(param)) as { result?: unknown } | null | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`sendNudge 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 会话置顶(set_peer_pin;setContactLocalTop 参数待探测校准)。 */\n async setContactTop(target: Peer, isTop: boolean): Promise<void> {\n try {\n checkLooseResult(\n \"setContactLocalTop\",\n (await this.service.setContactLocalTop(target, isTop)) as\n | { result?: unknown }\n | null\n | undefined,\n );\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`setContactLocalTop 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n }\n\n /** 收藏表情列表(get_custom_face_url_list;queryFavEmojiByDesc 形状待校准)。 */\n async queryFavEmojis(): Promise<string[]> {\n let raw: { result?: unknown; errMsg?: unknown; emojiInfoList?: unknown };\n try {\n raw = (await this.service.queryFavEmojiByDesc(\"\")) as typeof raw;\n } catch (e) {\n throw kernelError(`queryFavEmojiByDesc 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n checkLooseResult(\"queryFavEmojiByDesc\", raw);\n const list = raw.emojiInfoList;\n if (!Array.isArray(list)) {\n return [];\n }\n return extractFavEmojiUrls(list);\n }\n\n /**\n * 发送合并转发(send_group/private_forward_msg)。\n * buildMultiForwardMsg 组装 MULTI_FORWARD 元素 → 直接作 sendMsg 元素发送。\n */\n async sendForwardMessage(\n target: Peer,\n sourcePeer: Peer,\n srcMsgIds: string[],\n ): Promise<{ msgId: string }> {\n if (srcMsgIds.length === 0) {\n throw kernelError(\"sendForwardMessage 需要至少一条源消息\", \"INVALID_PARAM\");\n }\n const built = await this.service.buildMultiForwardMsg({\n srcMsgIds,\n srcContact: sourcePeer,\n });\n unwrapResult(\"buildMultiForwardMsg\", built);\n const elements = built.rspInfo?.elements;\n if (elements === undefined || elements.length === 0) {\n throw kernelError(\"合并转发组装失败:无元素\", \"UNKNOWN\");\n }\n const msgId = this.service.generateMsgUniqueId(target.chatType, String(Date.now()));\n const raw = await this.service.sendMsg(msgId, target, elements, new Map());\n unwrapResult(\"sendMsg\", raw);\n return { msgId };\n }\n\n /** 获取合并转发内容(get_forward_msg;resId 取自 multiForwardMsgElement)。 */\n async fetchForwardMessage(peer: Peer, msgId: string): Promise<RawMessage[]> {\n const msgs = await this.fetchMsgsByMsgId(peer, [msgId]);\n const forward = findForwardElement(msgs);\n if (forward === null || forward.resId === \"\") {\n throw kernelError(\"消息不包含合并转发内容\", \"NOT_FOUND\");\n }\n const raw = await this.service.getMultiMsg(peer, msgId, forward.resId);\n unwrapResult(\"getMultiMsg\", raw);\n return raw.msgList ?? [];\n }\n\n /** 单条转发(forward_group/friend_single_msg;srcMsgIds 源、dstPeer 目标)。 */\n async forwardSingleMessage(\n sourcePeer: Peer,\n srcMsgIds: string[],\n dstPeer: Peer,\n ): Promise<void> {\n if (srcMsgIds.length === 0) {\n throw kernelError(\"forwardSingleMessage 需要至少一条源消息\", \"INVALID_PARAM\");\n }\n const raw = await this.service.forwardMsg(srcMsgIds, sourcePeer, [dstPeer], undefined);\n unwrapResult(\"forwardMsg\", raw);\n }\n\n /** 设置在线状态(set_online_status;customStatus 为自定义状态)。 */\n async setOnlineStatus(opts: {\n status: number;\n extStatus: number;\n batteryStatus: number;\n customStatus?: { faceId: string; wording: string; faceType: string };\n }): Promise<void> {\n const raw = await this.service.setStatus(opts);\n unwrapResult(\"setStatus\", raw);\n }\n}\n\n/** 在消息列表中找含 pttElement 的元素(找不到返回 null)。 */\nfunction findPttElement(msgs: RawMessage[]): RawElement | null {\n const [first] = msgs;\n if (first === undefined) {\n return null;\n }\n return first.elements.find((el) => el.pttElement !== undefined) ?? null;\n}\n\n/** 在消息列表中找含 multiForwardMsgElement 的元素(找不到返回 null)。 */\nfunction findForwardElement(\n msgs: RawMessage[],\n): { resId: string; fileName: string; xmlContent: string } | null {\n const [first] = msgs;\n if (first === undefined) {\n return null;\n }\n return (\n first.elements.find((el) => el.multiForwardMsgElement !== undefined)\n ?.multiForwardMsgElement ?? null\n );\n}\n","/**\n * 陌生人信息扁平化纯函数(get_stranger_info 的数据映射部分)\n *\n * 2026-08-08 从 profile.ts 拆出:NAPI 会话依赖(ProfileApi)与纯映射分离,\n * 纯函数可独立单测(stranger-info.test.ts 基线锁定宽松取值行为)。\n *\n * 宽松取值:优先 info(uid 完整详情),字段缺失回退 byUin(uin 初查详情),\n * 两份都缺给默认值——字段缺失是 QQ 详情接口的常态,不能抛错。\n */\nimport type { UserDetailInfoByUin } from \"../types/index.js\";\n\n/** 陌生人信息(get_stranger_info 返回)。 */\nexport interface StrangerInfo {\n user_id: number;\n uid: string;\n nickname: string;\n age: number;\n qid: string;\n qq_level: number;\n sex: \"male\" | \"female\" | \"unknown\";\n long_nick: string;\n reg_time: number;\n is_vip: boolean;\n is_years_vip: boolean;\n vip_level: number;\n remark: string;\n status: number;\n login_days: number;\n}\n\n/** 扁平化后的详情视图(双来源字段级回退的产物,仅含 StrangerInfo 需要的字段)。 */\ninterface MergedDetail {\n coreInfo?: { nick?: string; remark?: string } | undefined;\n baseInfo?: { age?: number; qid?: string; sex?: number; longNick?: string } | undefined;\n vasInfo?: { svipFlag?: boolean; yearVipFlag?: boolean; vipLevel?: number } | undefined;\n commonExt?: { qqLevel?: number; regTime?: number } | undefined;\n status?: number | undefined;\n}\n\n/** 宽松取值:undefined 时给默认值(null 也归默认)。 */\nfunction orDefault<T>(value: T | undefined, fallback: T): T {\n return value ?? fallback;\n}\n\n/** 合并两份详情:优先 info(uid 完整详情),字段缺失回退 byUin(uin 初查详情)。 */\nfunction mergeDetail(info: UserDetailInfoByUin, byUin: UserDetailInfoByUin): MergedDetail {\n const infoSimple = info.detail?.simpleInfo;\n const byUinSimple = byUin.detail?.simpleInfo;\n return {\n coreInfo: pickField(infoSimple, byUinSimple, (s) => s?.coreInfo),\n baseInfo: pickField(infoSimple, byUinSimple, (s) => s?.baseInfo),\n vasInfo: pickField(infoSimple, byUinSimple, (s) => s?.vasInfo),\n commonExt: pickField(info.detail, byUin.detail, (d) => d?.commonExt),\n status: pickField(infoSimple, byUinSimple, (s) => s?.status?.status),\n };\n}\n\n/** 字段级回退:primary 的字段取不到回退 fallback 的同名字段(宽松取值)。 */\nfunction pickField<T, R>(\n primary: T | undefined,\n fallback: T | undefined,\n get: (src: T) => R | undefined,\n): R | undefined {\n const fromPrimary = primary !== undefined ? get(primary) : undefined;\n if (fromPrimary !== undefined) {\n return fromPrimary;\n }\n return fallback !== undefined ? get(fallback) : undefined;\n}\n\n/** 扁平化两份详情 → StrangerInfo(宽松取值,字段缺失给默认)。 */\nexport function flattenStrangerInfo(\n uin: string,\n uid: string,\n byUin: UserDetailInfoByUin,\n info: UserDetailInfoByUin,\n): StrangerInfo {\n const d = mergeDetail(info, byUin);\n return {\n user_id: Number(uin),\n uid,\n nickname: orDefault(d.coreInfo?.nick, \"\"),\n age: orDefault(d.baseInfo?.age, 0),\n qid: orDefault(d.baseInfo?.qid, \"\"),\n qq_level: orDefault(d.commonExt?.qqLevel, 0),\n sex: mapSex(d.baseInfo?.sex),\n long_nick: orDefault(d.baseInfo?.longNick, \"\"),\n reg_time: orDefault(d.commonExt?.regTime, 0),\n is_vip: orDefault(d.vasInfo?.svipFlag, false),\n is_years_vip: orDefault(d.vasInfo?.yearVipFlag, false),\n vip_level: orDefault(d.vasInfo?.vipLevel, 0),\n remark: orDefault(d.coreInfo?.remark, \"\"),\n status: orDefault(d.status, 0),\n login_days: 0,\n };\n}\n\n/** 性别数值 → OB11 字符串(1=男 2=女 其余 unknown)。 */\nfunction mapSex(raw: number | undefined): \"male\" | \"female\" | \"unknown\" {\n if (raw === 1) {\n return \"male\";\n }\n if (raw === 2) {\n return \"female\";\n }\n return \"unknown\";\n}\n","/**\n * ProfileApi:资料语义化 API(ADR-009 统一错误语义,P2-14/P2-15)\n *\n * - setLongNick:set_self_longnick\n * - setNickName:set_qq_profile\n * - setHeader:set_qq_avatar\n * - getStrangerInfo:get_stranger_info(uin → uid → 详情扁平化)\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type { NodeIKernelProfileService, NodeIQQNTWrapperSession } from \"../types/index.js\";\nimport { checkLooseResult } from \"./result.js\";\nimport type { StrangerInfo } from \"./stranger-info.js\";\nimport { flattenStrangerInfo } from \"./stranger-info.js\";\n\n/** 资料 API:从 session 拿 profile service,包装成语义化方法。 */\nexport class ProfileApi {\n private readonly service: NodeIKernelProfileService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service = session.getProfileService() as unknown as NodeIKernelProfileService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getProfileService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n }\n\n /** 设置个性签名(set_self_longnick)。 */\n async setLongNick(longNick: string): Promise<void> {\n const res = (await this.service.setLongNick(longNick)) as\n | { result?: unknown; errMsg?: unknown }\n | undefined;\n checkLooseResult(\"setLongNick\", res);\n }\n\n /** 设置昵称(set_qq_profile)。 */\n async setNickName(nickName: string): Promise<void> {\n const res = (await this.service.setNickName(nickName)) as\n | { result?: unknown; errMsg?: unknown }\n | undefined;\n checkLooseResult(\"setNickName\", res);\n }\n\n /** 设置头像(set_qq_avatar;filePath 为本地路径)。 */\n async setHeader(filePath: string): Promise<void> {\n const raw = await this.service.setHeader(filePath);\n if (raw.result !== 0) {\n throw kernelError(`setHeader 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n }\n\n /** 获取陌生人信息(get_stranger_info;uin 为用户 QQ 号)。 */\n async getStrangerInfo(uin: string): Promise<StrangerInfo> {\n const byUin = await this.service.getUserDetailInfoByUin(uin);\n const detailUid = byUin.detail?.uid;\n let uid = uin;\n if (detailUid !== undefined && detailUid !== \"\") {\n uid = detailUid;\n }\n const info = await this.service.getUserDetailInfo(uid);\n return flattenStrangerInfo(uin, uid, byUin, info);\n }\n}\n","/**\n * ProfileLikeApi:资料点赞语义化 API(ADR-009 统一错误语义,P2-14)\n *\n * sendLike:send_like(sourceId=71 赞来源,doLikeCount 次数)。\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type { NodeIKernelProfileLikeService, NodeIQQNTWrapperSession } from \"../types/index.js\";\n\n/** 点赞 API:从 session 拿 profile like service,包装成语义化方法。 */\nexport class ProfileLikeApi {\n private readonly service: NodeIKernelProfileLikeService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service =\n session.getProfileLikeService() as unknown as NodeIKernelProfileLikeService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getProfileLikeService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n }\n\n /** 点赞(send_like;uid 为目标 uid,count 次数)。 */\n async sendLike(uid: string, count = 1): Promise<void> {\n const raw = await this.service.setBuddyProfileLike({\n friendUid: uid,\n sourceId: 71,\n doLikeCount: count,\n doLikeTollCount: 0,\n });\n if (raw.result !== 0) {\n throw kernelError(`点赞失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n }\n}\n","/**\n * RecentContactApi:会话列表语义化 API(P2 批次二,get_peer_pins 数据源)\n *\n * 2026-10-31 实机探测确认 getRecentContactService 有 getRecentContactList /\n * setContactListTop 等方法;返回形状待探测校准(宽松提取)。\n * 置顶数据源:isTop 过滤(ChatType 区分 friend/group 场景)。\n */\nimport { isKernelError, kernelError } from \"../infra/index.js\";\nimport type {\n NodeIKernelRecentContactService,\n NodeIQQNTWrapperSession,\n RecentContact,\n} from \"../types/index.js\";\nimport { ChatType } from \"../types/index.js\";\nimport { checkLooseResult } from \"./result.js\";\n\n/** 会话列表拉取条数(覆盖置顶集合足够大;参数值待探测校准)。 */\nconst RECENT_CONTACT_FETCH_COUNT = 200;\n\n/** 置顶会话(get_peer_pins 数据项):peerUid 按 chatType 归 friend/group。 */\nexport interface PinnedContact {\n /** 会话 id(好友 = uid,群 = 群号)。 */\n peerUid: string;\n /** 场景:friend(C2C)/ group(GROUP);temp 不产出。 */\n scene: \"friend\" | \"group\";\n}\n\n/** 会话列表 API:从 session 拿 recent contact service,包装成语义化方法。 */\nexport class RecentContactApi {\n private readonly service: NodeIKernelRecentContactService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service =\n session.getRecentContactService() as unknown as NodeIKernelRecentContactService | null;\n if (service === null || service === undefined) {\n throw kernelError(\n \"getRecentContactService() 返回空(session 未 init)\",\n \"INVALID_STATE\",\n );\n }\n this.service = service;\n }\n\n /** 置顶会话列表(get_peer_pins;getRecentContactList 返回形状待探测校准)。 */\n async getPinnedContacts(): Promise<PinnedContact[]> {\n let raw: { result?: unknown; errMsg?: unknown };\n try {\n raw = (await this.service.getRecentContactList({\n count: RECENT_CONTACT_FETCH_COUNT,\n })) as typeof raw;\n } catch (e) {\n if (isKernelError(e)) {\n throw e;\n }\n throw kernelError(`getRecentContactList 调用失败: ${errMsgString(e)}`, \"UNKNOWN\");\n }\n checkLooseResult(\"getRecentContactList\", raw);\n return extractPinnedContacts(raw);\n }\n}\n\n/** 从返回形状提取置顶会话(result 数组 / 顶层 contacts / 嵌套容器兼容,待校准)。 */\nfunction extractPinnedContacts(raw: { result?: unknown }): PinnedContact[] {\n const candidates: unknown[] = [raw.result, raw];\n for (const container of [raw.result, raw]) {\n if (container === null || typeof container !== \"object\") {\n continue;\n }\n const rec = container as Record<string, unknown>;\n for (const key of [\"contacts\", \"data\", \"result\", \"list\"] as const) {\n candidates.push(rec[key]);\n }\n }\n for (const candidate of candidates) {\n if (!Array.isArray(candidate)) {\n continue;\n }\n return candidate.flatMap((item) => pinnedOf(item));\n }\n return [];\n}\n\n/** 单条会话 → 置顶项(置顶标记为真且场景可判定才产出)。 */\nfunction pinnedOf(item: unknown): PinnedContact[] {\n if (item === null || typeof item !== \"object\") {\n return [];\n }\n const contact = item as RecentContact;\n // 置顶字段实测为 topFlag(1 = 置顶,listener 载荷 2026-11-01 实录);isTop 为兼容形态\n const pinned = contact.isTop === true || contact.topFlag === 1;\n if (!pinned || typeof contact.peerUid !== \"string\") {\n return [];\n }\n if (contact.chatType === ChatType.GROUP) {\n return [{ peerUid: contact.peerUid, scene: \"group\" }];\n }\n if (contact.chatType === ChatType.C2C) {\n return [{ peerUid: contact.peerUid, scene: \"friend\" }];\n }\n return [];\n}\n\n/** 未知异常 → 消息字符串。 */\nfunction errMsgString(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n","/**\n * RichMediaApi:富媒体语义化 API(ADR-009 统一错误语义,P2-14)\n *\n * - translateWords:translate_en2zh\n * - 群文件:列表 / 空间 / 建夹 / 删除文件 / 删除夹 / 重命名 / 移动 / 转存\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n GetFileListParam,\n GroupFileItemInfo,\n GroupFolderInfo,\n NodeIKernelRichMediaService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\nimport { checkLooseResult, unwrap } from \"./result.js\";\n\n/** 群文件业务类型(deleteGroupFile/moveGroupFile/renameGroupFile 用,说明书参考)。 */\nconst GROUP_FILE_BIZ_TYPE = 102;\n/** 排序方式(按时间)。 */\nconst SORT_TYPE_TIME = 1;\n/** 排序方向(倒序)。 */\nconst SORT_ORDER_DESC = 2;\n/** 起始下标(根目录)。 */\nconst START_INDEX_ROOT = 0;\n/** 不显示在线文档文件夹。 */\nconst HIDE_ONLINEDOC_FOLDER = 0;\n/** 默认文件数量。 */\nconst DEFAULT_FILE_COUNT = 50;\n/** 空间查询文件数量(单条即可)。 */\nconst SPACE_QUERY_FILE_COUNT = 1;\n\n/** 群文件列表条目(fileInfo/folderInfo 取其一)。 */\nexport interface GroupFileListItem {\n fileInfo?: GroupFileItemInfo;\n folderInfo?: GroupFolderInfo;\n}\n\n/** 群文件系统信息。 */\nexport interface GroupFileSystemInfo {\n fileCount: number;\n limitCount: number;\n usedSpace: number;\n totalSpace: number;\n}\n\n/** 富媒体 API:从 session 拿 rich media service,包装成语义化方法。 */\nexport class RichMediaApi {\n private readonly service: NodeIKernelRichMediaService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service =\n session.getRichMediaService() as unknown as NodeIKernelRichMediaService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getRichMediaService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n }\n\n /** 英文单词翻译(translate_en2zh)。 */\n async translateWords(words: string[]): Promise<string[]> {\n const raw = await this.service.translateEnWordToZn(words);\n unwrap(\"translateEnWordToZn\", raw.result, raw.errMsg);\n return raw.words ?? [];\n }\n\n /** 群文件列表(get_group_root_files;parent 缺省根目录)。 */\n async getGroupFileList(\n groupCode: string,\n parentFolderId = \"\",\n fileCount = DEFAULT_FILE_COUNT,\n ): Promise<GroupFileListItem[]> {\n const params: GetFileListParam = {\n sortType: SORT_TYPE_TIME,\n fileCount,\n startIndex: START_INDEX_ROOT,\n sortOrder: SORT_ORDER_DESC,\n showOnlinedocFolder: HIDE_ONLINEDOC_FOLDER,\n };\n const raw = await this.service.getGroupFileList(groupCode, params);\n // 宽松解包(2026-10-31 修正):旧逻辑 result 数字非 0 抛错 + extractFileList(0)\n // 得空——真实返回形状(result 即列表 / 顶层列表 / 嵌套 items)下要么误抛要么恒空。\n checkLooseResult(\"getGroupFileList\", raw);\n const list = extractFileList(raw.result);\n const items = list.length > 0 ? list : extractFileList(raw);\n if (parentFolderId === \"\") {\n return items;\n }\n return items.filter(\n (item) =>\n (item.fileInfo !== undefined && item.fileInfo.parent === parentFolderId) ||\n (item.folderInfo !== undefined && item.folderInfo.folderUid === parentFolderId),\n );\n }\n\n /** 群文件系统信息(get_group_file_system_info;getGroupFileList 的 groupSpaceResult)。 */\n async getGroupFileSystemInfo(groupCode: string): Promise<GroupFileSystemInfo> {\n const params: GetFileListParam = {\n sortType: SORT_TYPE_TIME,\n fileCount: SPACE_QUERY_FILE_COUNT,\n startIndex: START_INDEX_ROOT,\n sortOrder: SORT_ORDER_DESC,\n showOnlinedocFolder: HIDE_ONLINEDOC_FOLDER,\n };\n const raw = await this.service.getGroupFileList(groupCode, params);\n if (raw.result !== 0) {\n unwrap(\"getGroupFileList\", raw.result, raw.errMsg);\n }\n const space = raw.groupSpaceResult;\n if (space === undefined) {\n throw kernelError(\"getGroupFileList 无 groupSpaceResult\", \"UNKNOWN\");\n }\n return {\n fileCount: countGroupFiles(raw.result),\n limitCount: 0,\n usedSpace: space.usedSpace,\n totalSpace: space.totalSpace,\n };\n }\n\n /** 创建群文件夹(create_group_file_folder)。 */\n async createGroupFolder(groupCode: string, folderName: string): Promise<string> {\n const raw = await this.service.createGroupFolder(groupCode, folderName);\n if (raw.result !== 0) {\n unwrap(\"createGroupFolder\", raw.result, raw.errMsg);\n }\n // 返回形状待探测:兼容 folderId / resultWithGroupItem\n const { result } = raw as { result?: unknown };\n if (typeof result === \"string\") {\n return result;\n }\n return \"\";\n }\n\n /** 删除群文件(delete_group_file)。 */\n async deleteGroupFile(groupCode: string, fileIds: string[]): Promise<void> {\n const raw = await this.service.deleteGroupFile(groupCode, [GROUP_FILE_BIZ_TYPE], fileIds);\n if (raw.result !== 0) {\n unwrap(\"deleteGroupFile\", raw.result, raw.errMsg);\n }\n }\n\n /** 删除群文件夹(delete_group_folder)。 */\n async deleteGroupFolder(groupCode: string, folderId: string): Promise<void> {\n const raw = await this.service.deleteGroupFolder(groupCode, folderId);\n if (raw.result !== 0) {\n unwrap(\"deleteGroupFolder\", raw.result, raw.errMsg);\n }\n }\n\n /** 重命名群文件(rename_group_file;参数语义待探测校准)。 */\n async renameGroupFile(groupCode: string, fileId: string, newName: string): Promise<void> {\n await this.service.renameGroupFile(groupCode, GROUP_FILE_BIZ_TYPE, fileId, newName, \"\");\n }\n\n /** 移动群文件(move_group_file;targetFolder 空为根目录)。 */\n async moveGroupFile(\n groupCode: string,\n fileIds: string[],\n targetFolderId: string,\n ): Promise<void> {\n const raw = await this.service.moveGroupFile(\n groupCode,\n [GROUP_FILE_BIZ_TYPE],\n fileIds,\n \"\",\n targetFolderId,\n );\n if (raw.result !== 0) {\n unwrap(\"moveGroupFile\", raw.result, raw.errMsg);\n }\n }\n\n /** 重命名群文件夹(rename_group_folder;形态镜像 renameGroupFile,待校准)。 */\n async renameGroupFolder(groupCode: string, folderId: string, newName: string): Promise<void> {\n await this.service.renameGroupFolder(groupCode, GROUP_FILE_BIZ_TYPE, folderId, newName, \"\");\n }\n\n /**\n * 群文件下载链接(get_group_file_download_url):getGroupFileList 根列表按\n * fileId 查找取 url 字段(字段待实机确认)。已知差异:仅根目录可达——\n * 子目录文件需 parentFolderId 逐层定位,暂不支持。\n */\n async getGroupFileDownloadUrl(groupCode: string, fileId: string): Promise<string> {\n const items = await this.getGroupFileList(groupCode);\n for (const item of items) {\n const info = item.fileInfo;\n if (info !== undefined && info.fileId === fileId) {\n if (typeof info.url === \"string\" && info.url !== \"\") {\n return info.url;\n }\n throw kernelError(\n `群文件 ${fileId} 无下载链接(url 字段缺失,待实机校准)`,\n \"NOT_FOUND\",\n );\n }\n }\n throw kernelError(`群文件 ${fileId} 不在根目录或不存在(仅支持根目录查找)`, \"NOT_FOUND\");\n }\n\n /** 转存群文件(trans_group_file)。 */\n async transGroupFile(groupCode: string, fileId: string): Promise<void> {\n const raw = await this.service.transGroupFile(groupCode, fileId);\n if (raw.result !== 0) {\n unwrap(\"transGroupFile\", raw.result, raw.errMsg);\n }\n }\n}\n\n/** 从 getGroupFileList 返回提取文件列表(兼容数组 / { items } / { fileList })。导出供单测。 */\nexport function extractFileList(result: unknown): GroupFileListItem[] {\n if (Array.isArray(result)) {\n return result as GroupFileListItem[];\n }\n if (result === null || typeof result !== \"object\") {\n return [];\n }\n const obj = result as { items?: unknown; fileList?: unknown; list?: unknown };\n for (const key of [\"items\", \"fileList\", \"list\"] as const) {\n const value = obj[key];\n if (Array.isArray(value)) {\n return value as GroupFileListItem[];\n }\n }\n return [];\n}\n\n/** 统计文件数量(兼容数组 / { items })。 */\nfunction countGroupFiles(result: unknown): number {\n if (Array.isArray(result)) {\n return (result as GroupFileListItem[]).filter((item) => item.fileInfo !== undefined).length;\n }\n return 0;\n}\n","/**\n * TicketApi:票据语义化 API(ADR-009 统一错误语义,P2-13)\n *\n * - getClientKey:TicketService.forceFetchClientKey 解包(get_clientkey)\n * - getCookies:clientKey → ssl.ptlogin2.qq.com/jump 跳转 → 解析 set-cookie 头\n * (fetch 会吞掉 Set-Cookie 头,故用 node:https 手动请求)\n */\nimport { get as httpsGet } from \"node:https\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n ForceFetchClientKeyRetType,\n NodeIKernelTicketService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\n\n/** 跳转请求超时(毫秒)。 */\nconst JUMP_TIMEOUT_MS = 10_000;\n\n/** 票据 API:从 session 拿 ticket service,包装成语义化方法。 */\nexport class TicketApi {\n private readonly service: NodeIKernelTicketService;\n\n constructor(session: NodeIQQNTWrapperSession) {\n const service = session.getTicketService() as unknown as NodeIKernelTicketService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getTicketService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n }\n\n /** 获取 clientKey(get_clientkey)。 */\n async getClientKey(): Promise<ForceFetchClientKeyRetType> {\n const raw = await this.service.forceFetchClientKey(\"\");\n if (raw.result !== 0 || raw.clientKey === \"\") {\n throw kernelError(`forceFetchClientKey 失败: ${raw.errMsg}`, \"UNKNOWN\");\n }\n return raw;\n }\n\n /** 获取指定域名的 cookies(get_cookies;uin 为机器人 QQ 号)。 */\n async getCookies(domain: string, uin: string): Promise<Record<string, string>> {\n const { clientKey } = await this.getClientKey();\n const u1 = `https://${domain}/${uin}/infocenter`;\n const jumpUrl =\n \"https://ssl.ptlogin2.qq.com/jump?ptlang=1033\" +\n `&clientuin=${encodeURIComponent(uin)}` +\n `&clientkey=${encodeURIComponent(clientKey)}` +\n `&u1=${encodeURIComponent(u1)}` +\n \"&keyindex=19\";\n return fetchJumpCookies(jumpUrl);\n }\n\n /** 计算 bkn(get_csrf_token 与 web 接口共用;skey → bkn 哈希)。 */\n static getBkn(skey: string): string {\n let hash = 5381;\n for (let i = 0; i < skey.length; i += 1) {\n const code = skey.charCodeAt(i);\n hash = hash + (hash << 5) + code;\n }\n return (hash & 0x7f_ff_ff_ff).toString();\n }\n}\n\n/** 请求 jump 地址并解析 set-cookie → dict(fetch 吞 Set-Cookie,故用 node:https)。 */\nfunction fetchJumpCookies(url: string): Promise<Record<string, string>> {\n return new Promise((resolve, reject) => {\n const req = httpsGet(\n url,\n {\n headers: {\n \"user-agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\",\n },\n },\n (res) => {\n const cookies = parseSetCookieHeaders(res.headers[\"set-cookie\"]);\n res.resume();\n res.on(\"end\", () => resolve(cookies));\n },\n );\n req.setTimeout(JUMP_TIMEOUT_MS, () => {\n req.destroy();\n reject(new Error(\"获取 cookies 超时\"));\n });\n req.on(\"error\", (err) => reject(err));\n });\n}\n\n/** 解析 set-cookie 头数组 → { name: value } dict。 */\nfunction parseSetCookieHeaders(raw: string[] | undefined): Record<string, string> {\n const cookies: Record<string, string> = {};\n if (raw === undefined) {\n return cookies;\n }\n for (const line of raw) {\n const [pair] = line.split(\";\");\n if (pair !== undefined) {\n const eq = pair.indexOf(\"=\");\n if (eq > 0) {\n const name = pair.slice(0, eq).trim();\n const value = pair.slice(eq + 1);\n if (name !== \"\") {\n cookies[name] = value;\n }\n }\n }\n }\n return cookies;\n}\n","/**\n * WebApi:群空间 web 接口(P2-15)\n *\n * qun.qq.com 群空间接口需 Cookie(p_skey/skey)+ bkn(skey 哈希)。\n * 复用 TicketApi.getCookies('qun.qq.com') 取 Cookie,TicketApi.getBkn 算 bkn。\n * 零 NapCat 代码:接口契约(URL/参数/返回形状)是 qun.qq.com 外部系统的事实,\n * 自研描述。\n */\nimport { kernelError } from \"../infra/index.js\";\nimport { TicketApi } from \"./ticket.js\";\n\n/** 群空间域名(Cookie 归属)。 */\nconst QUN_DOMAIN = \"qun.qq.com\";\n\n/** 精华消息页大小(分页拉取)。 */\nconst ESSENCE_PAGE_LIMIT = 50;\n\n/** 精华消息最大分页数。 */\nconst ESSENCE_MAX_PAGES = 20;\n\n/** 精华消息项(digest_list 返回 data.msg_list 成员,说明书参考)。 */\nexport interface EssenceMsgItem {\n msg_seq: number;\n msg_random: number;\n sender_uin: string;\n sender_nick: string;\n add_digest_uin: string;\n add_digest_nick: string;\n add_digest_time: number;\n msg_content: Array<{\n msg_type?: number;\n text?: string;\n image_url?: string;\n }>;\n}\n\n/** 群荣誉项(honorlist 返回列表项,说明书参考)。 */\nexport interface HonorListItem {\n uin: string;\n name: string;\n avatar: string;\n desc: string;\n}\n\n/** 群荣誉信息(getGroupHonorInfo 返回)。 */\nexport interface GroupHonorWebInfo {\n group_id: number;\n current_talkative: HonorListItem | null;\n talkative_list: HonorListItem[];\n performer_list: HonorListItem[];\n legend_list: HonorListItem[];\n emotion_list: HonorListItem[];\n strong_newbie_list: HonorListItem[];\n}\n\n/** 群荣誉类型(1=龙王 2=群聊之火 3=群聊炽热 6=快乐源泉)。 */\nexport const WebHonorType = {\n ALL: 0,\n TALKATIVE: 1,\n PERFORMER: 2,\n LEGEND: 3,\n EMOTION: 6,\n} as const;\nexport type WebHonorType = (typeof WebHonorType)[keyof typeof WebHonorType];\n\n/** 群空间 web API(Cookie 经 TicketApi 注入)。 */\nexport class WebApi {\n private readonly getCookies: (domain: string) => Promise<Record<string, string>>;\n\n constructor(opts: { getCookies: (domain: string) => Promise<Record<string, string>> }) {\n this.getCookies = opts.getCookies;\n }\n\n /** 拉取单页精华消息(失败/retcode 非 0 返回 null)。 */\n private async fetchEssencePage(\n groupCode: string,\n cookie: Record<string, string>,\n bkn: string,\n page: number,\n ): Promise<{ items: EssenceMsgItem[]; isEnd: boolean } | null> {\n const url =\n \"https://qun.qq.com/cgi-bin/group_digest/digest_list?\" +\n new URLSearchParams({\n bkn,\n page_start: String(page * ESSENCE_PAGE_LIMIT),\n page_limit: String(ESSENCE_PAGE_LIMIT),\n group_code: groupCode,\n }).toString();\n const ret = await fetchQunJson(url, cookie);\n if (ret === null || ret.retcode !== 0) {\n return null;\n }\n const list = ret.data?.msg_list;\n return {\n items: Array.isArray(list) ? list.filter((item) => item !== null) : [],\n isEnd: ret.data?.is_end === true,\n };\n }\n\n /** 获取群精华消息列表(get_essence_msg_list;分页拉取至 is_end)。 */\n async getEssenceMsgList(groupCode: string): Promise<EssenceMsgItem[]> {\n const cookie = await this.getCookies(QUN_DOMAIN);\n const bkn = TicketApi.getBkn(cookie[\"skey\"] ?? \"\");\n const out: EssenceMsgItem[] = [];\n for (let page = 0; page < ESSENCE_MAX_PAGES; page += 1) {\n const result = await this.fetchEssencePage(groupCode, cookie, bkn, page);\n if (result === null) {\n break;\n }\n out.push(...result.items);\n if (result.isEnd) {\n break;\n }\n }\n return out;\n }\n\n /** 获取群荣誉信息(get_group_honor_info;type 0=全部)。 */\n async getGroupHonorInfo(groupCode: string, type: WebHonorType): Promise<GroupHonorWebInfo> {\n const cookie = await this.getCookies(QUN_DOMAIN);\n const result: GroupHonorWebInfo = {\n group_id: Number(groupCode),\n current_talkative: null,\n talkative_list: [],\n performer_list: [],\n legend_list: [],\n emotion_list: [],\n strong_newbie_list: [],\n };\n await this.fillHonorLists(result, groupCode, cookie, type);\n return result;\n }\n\n /** 按 type 填充荣誉列表(ALL 拉全部,单类型只拉对应类)。 */\n private async fillHonorLists(\n result: GroupHonorWebInfo,\n groupCode: string,\n cookie: Record<string, string>,\n type: WebHonorType,\n ): Promise<void> {\n for (const target of honorTargets(type)) {\n const list = await this.fetchHonorList(groupCode, cookie, target.kind);\n result[target.listKey] = list;\n if (target.kind === WebHonorType.TALKATIVE) {\n const [first] = list;\n if (first !== undefined) {\n result.current_talkative = first;\n }\n }\n }\n }\n\n /** 拉取单类荣誉列表(honorlist 接口,正则提取 __INITIAL_STATE__)。 */\n private async fetchHonorList(\n groupCode: string,\n cookie: Record<string, string>,\n type: number,\n ): Promise<HonorListItem[]> {\n const url =\n \"https://qun.qq.com/interactive/honorlist?\" +\n new URLSearchParams({ gc: groupCode, type: String(type) }).toString();\n const text = await fetchQunText(url, cookie);\n if (text === null) {\n return [];\n }\n return parseHonorList(text, type);\n }\n}\n\n/** honorlist 页面 __INITIAL_STATE__ 提取。 */\nconst INITIAL_STATE_RE = /window\\.__INITIAL_STATE__=(.*?);/;\n\n/** 从 honorlist 页面提取 __INITIAL_STATE__ → 荣誉列表(解析失败返回空)。 */\nexport function parseHonorList(text: string, type: number): HonorListItem[] {\n const match = INITIAL_STATE_RE.exec(text);\n if (match === null || match[1] === undefined) {\n return [];\n }\n let state: { talkativeList?: unknown; actorList?: unknown };\n try {\n state = JSON.parse(match[1]);\n } catch {\n return [];\n }\n const raw = type === WebHonorType.TALKATIVE ? state.talkativeList : state.actorList;\n if (!Array.isArray(raw)) {\n return [];\n }\n const out: HonorListItem[] = [];\n for (const item of raw) {\n if (item !== null && typeof item === \"object\") {\n const obj = item as {\n uin?: unknown;\n name?: unknown;\n avatar?: unknown;\n desc?: unknown;\n };\n out.push({\n uin: String(obj.uin ?? \"\"),\n name: String(obj.name ?? \"\"),\n avatar: String(obj.avatar ?? \"\"),\n desc: String(obj.desc ?? \"\"),\n });\n }\n }\n return out;\n}\n\n/** 请求 qun.qq.com JSON 接口(带 Cookie;失败返回 null)。 */\nasync function fetchQunJson(\n url: string,\n cookie: Record<string, string>,\n): Promise<{ retcode: number; data?: { msg_list?: EssenceMsgItem[]; is_end?: boolean } } | null> {\n try {\n const res = await fetch(url, { headers: { Cookie: cookieToString(cookie) } });\n if (!res.ok) {\n return null;\n }\n const json = (await res.json()) as {\n retcode?: number;\n data?: { msg_list?: unknown; is_end?: unknown };\n };\n return {\n retcode: json.retcode ?? -1,\n data: json.data as { msg_list?: EssenceMsgItem[]; is_end?: boolean },\n };\n } catch (err) {\n throw kernelError(`qun.qq.com 请求失败: ${String(err)}`, \"UNKNOWN\");\n }\n}\n\n/** 请求 qun.qq.com 文本接口(带 Cookie;失败返回 null)。 */\nasync function fetchQunText(url: string, cookie: Record<string, string>): Promise<string | null> {\n try {\n const res = await fetch(url, { headers: { Cookie: cookieToString(cookie) } });\n if (!res.ok) {\n return null;\n }\n return await res.text();\n } catch (err) {\n throw kernelError(`qun.qq.com 请求失败: ${String(err)}`, \"UNKNOWN\");\n }\n}\n\n/** Cookie dict → 头字符串。 */\nfunction cookieToString(cookie: Record<string, string>): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(cookie)) {\n parts.push(`${key}=${value}`);\n }\n return parts.join(\"; \");\n}\n\n/** 荣誉类型 → 目标列表(ALL 拉全部,单类型只拉对应类)。 */\nfunction honorTargets(type: WebHonorType): Array<{\n kind: WebHonorType;\n listKey: \"talkative_list\" | \"performer_list\" | \"legend_list\" | \"emotion_list\";\n}> {\n const all = [\n { kind: WebHonorType.TALKATIVE, listKey: \"talkative_list\" },\n { kind: WebHonorType.PERFORMER, listKey: \"performer_list\" },\n { kind: WebHonorType.LEGEND, listKey: \"legend_list\" },\n { kind: WebHonorType.EMOTION, listKey: \"emotion_list\" },\n ] as const;\n if (type === WebHonorType.ALL) {\n return [...all];\n }\n return all.filter((target) => target.kind === type);\n}\n","/**\n * FriendBridge:好友事件桥(2026-09-08)\n *\n * 职责:把 buddy service 的原生回调(onBuddyReqChange / onBuddyListChange 等)\n * 推入类型化事件通道 `NTEventChannel<BuddyListener, \"Buddy\">`。与 MsgBridge/\n * GroupBridge 同构(每个 Service 只注册一次原生监听,ADR-003)。\n *\n * 方法名证据:wrapper.node(9.9.33-52230)二进制字符串提取;参数形状\n * 待真实事件校准(透传 unknown,订阅方防御性收窄)。\n *\n * 用法:\n * const channel = new NTEventChannel<BuddyListener, \"Buddy\">(\"Buddy\");\n * const bridge = new FriendBridge(session, channel);\n * bridge.register(); // addKernelBuddyListener(普通 JS 对象,NAPI 反射)\n * bridge.unregister(); // 停止时清理\n */\n\nimport type { NTEventChannel } from \"../event-channel.js\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n BuddyListener,\n NodeIKernelBuddyService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\n\n/** 好友事件通道的固定类型(事件名前缀 \"Buddy\")。 */\nexport type BuddyEventChannel = NTEventChannel<BuddyListener, \"Buddy\">;\n\n/**\n * 好友事件桥:注册原生监听 → 回调 emit 到 channel。\n * 无全局单例(ADR-015 推论)——每进程每 session 实例化一份。\n */\nexport class FriendBridge {\n private readonly service: NodeIKernelBuddyService;\n private readonly channel: BuddyEventChannel;\n private listenerId: number | null = null;\n\n constructor(session: NodeIQQNTWrapperSession, channel: BuddyEventChannel) {\n const service = session.getBuddyService() as unknown as NodeIKernelBuddyService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getBuddyService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n this.channel = channel;\n }\n\n /** 注册原生监听(幂等)。 */\n register(): void {\n if (this.listenerId !== null) {\n return;\n }\n // listener 为普通 JS 对象(NAPI 反射读取方法回调)\n const listener: BuddyListener = {\n onBuddyReqChange: (arg) => this.channel.emit(\"Buddy/onBuddyReqChange\", arg),\n onBuddyListChange: (arg) => this.channel.emit(\"Buddy/onBuddyListChange\", arg),\n onBuddyListChangedV2: (arg) => this.channel.emit(\"Buddy/onBuddyListChangedV2\", arg),\n onBuddyDeleted: (arg) => this.channel.emit(\"Buddy/onBuddyDeleted\", arg),\n };\n this.listenerId = this.service.addKernelBuddyListener(listener);\n }\n\n /** 注销原生监听(幂等)。 */\n unregister(): void {\n if (this.listenerId === null) {\n return;\n }\n this.service.removeKernelBuddyListener(this.listenerId);\n this.listenerId = null;\n }\n}\n","/**\n * GroupBridge:群事件桥(P2-17,2026-08-05)\n *\n * 职责:把 group service 的原生回调(onGroupListUpdate / onMemberListChange 等)\n * 推入类型化事件通道 `NTEventChannel<GroupListener, \"Group\">`。每个 Service 只\n * 注册一次原生监听(ADR-003),缓存维护(GroupCache)与协议层都订阅 channel。\n *\n * 用法:\n * const channel = new NTEventChannel<GroupListener, \"Group\">(\"Group\");\n * const bridge = new GroupBridge(session, channel);\n * bridge.register(); // addKernelGroupListener(普通 JS 对象,NAPI 反射)\n * const cache = new GroupCache({ channel, groupApi });\n * cache.register(); // 订阅 channel 主动维护\n * bridge.unregister(); // 停止时清理\n */\n\nimport type { NTEventChannel } from \"../event-channel.js\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n GroupListener,\n NodeIKernelGroupService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\n\n/** 群事件通道的固定类型(事件名前缀 \"Group\")。 */\nexport type GroupEventChannel = NTEventChannel<GroupListener, \"Group\">;\n\n/**\n * 群事件桥:注册原生监听 → 回调 emit 到 channel。\n * 无全局单例(ADR-015 推论)——每进程每 session 实例化一份。\n */\nexport class GroupBridge {\n private readonly service: NodeIKernelGroupService;\n private readonly channel: GroupEventChannel;\n private listenerId: number | null = null;\n\n constructor(session: NodeIQQNTWrapperSession, channel: GroupEventChannel) {\n const service = session.getGroupService() as unknown as NodeIKernelGroupService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getGroupService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n this.channel = channel;\n }\n\n /** 注册原生监听(幂等)。 */\n register(): void {\n if (this.listenerId !== null) {\n return;\n }\n // listener 为普通 JS 对象(NAPI 反射读取方法回调)\n const listener: GroupListener = {\n onGroupListInited: (listEmpty) =>\n this.channel.emit(\"Group/onGroupListInited\", listEmpty),\n onGroupListUpdate: (updateType, groupList) =>\n this.channel.emit(\"Group/onGroupListUpdate\", updateType, groupList),\n onGroupDetailInfoChange: (detailInfo) =>\n this.channel.emit(\"Group/onGroupDetailInfoChange\", detailInfo),\n onMemberListChange: (arg) => this.channel.emit(\"Group/onMemberListChange\", arg),\n onMemberInfoChange: (groupCode, dataSource, members) =>\n this.channel.emit(\"Group/onMemberInfoChange\", groupCode, dataSource, members),\n onGroupNotifiesUpdated: (doubt, notifies) =>\n this.channel.emit(\"Group/onGroupNotifiesUpdated\", doubt, notifies),\n onGroupSingleScreenNotifies: (doubt, seq, notifies) =>\n this.channel.emit(\"Group/onGroupSingleScreenNotifies\", doubt, seq, notifies),\n onShutUpMemberListChanged: (groupCode, members) =>\n this.channel.emit(\"Group/onShutUpMemberListChanged\", groupCode, members),\n // 2026-09-10 接线:群精华列表变化(方法名证据见 listeners/group.ts 注释)\n onGroupEssenceListChange: (arg) =>\n this.channel.emit(\"Group/onGroupEssenceListChange\", arg),\n };\n this.listenerId = this.service.addKernelGroupListener(listener);\n }\n\n /** 注销原生监听(幂等)。 */\n unregister(): void {\n if (this.listenerId === null) {\n return;\n }\n this.service.removeKernelGroupListener(this.listenerId);\n this.listenerId = null;\n }\n}\n","/**\n * MsgBridge:消息事件桥(P2-2,2026-08-05)\n *\n * 职责:把 msg service 的原生回调(onRecvMsg 等)推入类型化事件通道\n * `NTEventChannel<MsgListener, \"Msg\">`。每个 Service 只注册一次原生监听\n * (ADR-003),缓存维护与协议翻译都订阅 channel。\n *\n * 用法:\n * const channel = new NTEventChannel<MsgListener, \"Msg\">(\"Msg\");\n * const bridge = new MsgBridge(session, channel);\n * bridge.register(); // addKernelMsgListener(普通 JS 对象,NAPI 反射)\n * // 协议层订阅:channel.on(\"Msg/onRecvMsg\", handler)\n * bridge.unregister(); // 停止时清理\n */\n\nimport type { NTEventChannel } from \"../event-channel.js\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n MsgListener,\n NodeIKernelMsgService,\n NodeIQQNTWrapperSession,\n} from \"../types/index.js\";\n\n/** 消息事件通道的固定类型(事件名前缀 \"Msg\")。 */\nexport type MsgEventChannel = NTEventChannel<MsgListener, \"Msg\">;\n\n/**\n * 消息事件桥:注册原生监听 → 回调 emit 到 channel。\n * 无全局单例(ADR-015 推论)——每进程每 session 实例化一份。\n */\nexport class MsgBridge {\n private readonly service: NodeIKernelMsgService;\n private readonly channel: MsgEventChannel;\n private listenerId: number | null = null;\n\n constructor(session: NodeIQQNTWrapperSession, channel: MsgEventChannel) {\n const service = session.getMsgService() as unknown as NodeIKernelMsgService | null;\n if (service === null || service === undefined) {\n throw kernelError(\"getMsgService() 返回空(session 未 init)\", \"INVALID_STATE\");\n }\n this.service = service;\n this.channel = channel;\n }\n\n /** 注册原生监听(幂等)。 */\n register(): void {\n if (this.listenerId !== null) {\n return;\n }\n // listener 为普通 JS 对象(NAPI 反射读取方法回调)\n // onRecvMsg 透传原生回调参数(运行时实证为消息数组,2026-08-07)。\n const listener: MsgListener = {\n onRecvMsg: (msgs) => this.channel.emit(\"Msg/onRecvMsg\", msgs),\n onRecvMsgReadReport: (reports) => this.channel.emit(\"Msg/onRecvMsgReadReport\", reports),\n onRecvMsgReceipt: (receipts) => this.channel.emit(\"Msg/onRecvMsgReceipt\", receipts),\n // 2026-08-11 补齐:发送状态更新(sendMsg 结果以此为准,NapCat 同款)\n onMsgInfoListUpdate: (msgs) => this.channel.emit(\"Msg/onMsgInfoListUpdate\", msgs),\n // 2026-09-10 接线:离线文件 / sys msg(方法名证据见 listeners/msg.ts 头注释)\n onRecvOfflineFileMsg: (arg) => this.channel.emit(\"Msg/onRecvOfflineFileMsg\", arg),\n onRecvOnlineFileMsg: (arg) => this.channel.emit(\"Msg/onRecvOnlineFileMsg\", arg),\n onRecvSysMsg: (arg) => this.channel.emit(\"Msg/onRecvSysMsg\", arg),\n };\n this.listenerId = this.service.addKernelMsgListener(listener);\n }\n\n /** 注销原生监听(幂等)。 */\n unregister(): void {\n if (this.listenerId === null) {\n return;\n }\n this.service.removeKernelMsgListener(this.listenerId);\n this.listenerId = null;\n }\n}\n","/**\n * 类型化事件通道(ADR-003)\n *\n * - 事件名约定 `${Service}/${method}`(如 \"Msg/onRecvMsg\"),签名从 Listener 接口编译期推导\n * - 事件名写错 → 编译期报错(类型推导)\n * - `on` 返回 unsubscribe;`waitFor` 注册临时监听 → filter → 超时自动清理(替代魔法字符串方案)\n * - error 兜底:监听器抛异常不打断派发,统一经 `onError` 通知\n *\n * 组合实现而非 extends EventEmitter:EventEmitter.on 返回 `this`,与设计要求的\n * 返回 unsubscribe 冲突;组合更干净,且内核无全局单例(ADR-015 推论)。\n *\n * 类型推导说明:不直接 `ListenerEvents<L, Name>[E]` 索引(泛型下会扩宽到\n * string|number|symbol 索引,索引出 unknown),而是用 Extract 按事件名从\n * `{ name, fn }` 联合精确提取 handler / 参数类型,保证实例化后签名精确。\n */\nimport { EventEmitter } from \"node:events\";\nimport { KernelError } from \"./infra/index.js\";\n\n/** waitFor 默认超时(毫秒)。 */\nconst DEFAULT_WAIT_TIMEOUT_MS = 30_000;\n\n/** Listener 接口形状(宽约束;精确签名由实例化时的具体接口保证)。 */\ntype ListenerShape = Record<string, unknown>;\n\n/** 事件名:`${Service}/${method}`。 */\ntype EventName<L extends ListenerShape, Name extends string> = `${Name}/${keyof L & string}`;\n\n/** 按事件名从 Listener 精确提取 handler 类型(条件类型 infer,泛型安全)。 */\ntype EventHandler<\n L extends ListenerShape,\n Name extends string,\n E extends EventName<L, Name>,\n> = Extract<\n {\n [K in keyof L]: L[K] extends (...args: infer A) => infer R\n ? { name: `${Name}/${K & string}`; fn: (...args: A) => R }\n : never;\n }[keyof L],\n { name: E }\n>[\"fn\"];\n\n/** 按事件名从 Listener 精确提取参数类型。 */\ntype EventArgs<\n L extends ListenerShape,\n Name extends string,\n E extends EventName<L, Name>,\n> = Extract<\n {\n [K in keyof L]: L[K] extends (...args: infer A) => unknown\n ? { name: `${Name}/${K & string}`; args: A }\n : never;\n }[keyof L],\n { name: E }\n>[\"args\"];\n\n/** 从 Listener 接口推导事件映射(文档/调试用):`{ \"Msg/onRecvMsg\": fn }`。 */\nexport type ListenerEvents<L extends ListenerShape, Name extends string> = {\n [K in keyof L as `${Name}/${K & string}`]: L[K];\n};\n\n/**\n * 类型化事件通道。\n *\n * @typeParam L - Listener 接口(探测产物,如 MsgListener)\n * @typeParam Name - Service 名(事件名前缀,如 \"Msg\")\n */\nexport class NTEventChannel<L extends ListenerShape, Name extends string> {\n readonly serviceName: Name;\n\n private readonly emitter = new EventEmitter();\n private readonly errorHandlers = new Set<(err: unknown) => void>();\n private readonly anyHandlers = new Set<(event: string, ...args: unknown[]) => void>();\n\n constructor(serviceName: Name) {\n this.serviceName = serviceName;\n // 单个 Service 多订阅者(缓存维护 + 协议翻译),关闭默认 10 个监听器上限\n this.emitter.setMaxListeners(0);\n }\n\n /**\n * 订阅事件:返回 unsubscribe(只注销该 handler,不影响其他订阅者)。\n * 每个 Service 只注册一次原生监听,缓存维护与协议翻译都订阅这里。\n */\n on<E extends EventName<L, Name>>(event: E, handler: EventHandler<L, Name, E>): () => void {\n this.emitter.addListener(event as string, handler as (...args: unknown[]) => void);\n return () => {\n this.emitter.removeListener(event as string, handler as (...args: unknown[]) => void);\n };\n }\n\n /**\n * 请求-响应桥:注册临时监听 → filter 命中 → resolve;超时自动清理并 reject。\n * 替代魔法字符串事件名方案。\n */\n waitFor<E extends EventName<L, Name>>(\n event: E,\n opts: {\n filter?: (...args: EventArgs<L, Name, E>) => boolean;\n timeout?: number;\n } = {},\n ): Promise<EventArgs<L, Name, E>> {\n const timeoutMs = opts.timeout ?? DEFAULT_WAIT_TIMEOUT_MS;\n return new Promise((resolve, reject) => {\n const handler = (...args: EventArgs<L, Name, E>) => {\n if (opts.filter && !opts.filter(...args)) {\n return; // 不匹配:保持监听,等待下一条\n }\n cleanup();\n resolve(args);\n };\n const timer = setTimeout(() => {\n cleanup();\n reject(new KernelError(`等待事件超时: ${event as string}`, \"TIMEOUT\"));\n }, timeoutMs);\n const cleanup = (): void => {\n clearTimeout(timer);\n this.emitter.removeListener(\n event as string,\n handler as (...args: unknown[]) => void,\n );\n };\n this.emitter.addListener(event as string, handler as (...args: unknown[]) => void);\n });\n }\n\n /**\n * 类型化派发:wrapper-loader 的原生回调经此推入通道。\n * 2026-08-07 修复:原生 EventEmitter.emit 中一个 listener 抛异常会中断后续\n * listener 派发(Node 语义)——改为快照数组逐个 try/catch,保证「单个订阅者\n * 异常不打断派发」(协议翻译层异常不饿死缓存维护等其他订阅者)。\n */\n /**\n * 订阅所有事件(IPC 子进程模式整通道转发用:事件名 `\"${service}/${method}\"`\n * 经 handler 首参透出,args 原样透传)。返回退订函数。\n */\n onAny(handler: (event: string, ...args: unknown[]) => void): () => void {\n this.anyHandlers.add(handler);\n return () => {\n this.anyHandlers.delete(handler);\n };\n }\n\n emit<E extends EventName<L, Name>>(event: E, ...args: EventArgs<L, Name, E>): void {\n // 全事件订阅(IPC 转发):单个订阅者异常不打断派发,统一走 error 兜底\n for (const anyHandler of this.anyHandlers) {\n try {\n anyHandler(event as string, ...args);\n } catch (err) {\n this.notifyError(err);\n }\n }\n const listeners = this.emitter.listeners(event as string);\n for (const listener of listeners) {\n try {\n (listener as (...a: unknown[]) => void)(...args);\n } catch (err) {\n // error 兜底:单个订阅者异常不打断派发,统一通知 onError\n this.notifyError(err);\n }\n }\n }\n\n /** 通知所有错误处理器(单个异常不打断其他处理器)。 */\n private notifyError(err: unknown): void {\n for (const handler of this.errorHandlers) {\n handler(err);\n }\n }\n\n /** 订阅错误通知(监听器抛出的异常 / 其他内部错误)。 */\n onError(handler: (err: unknown) => void): () => void {\n this.errorHandlers.add(handler);\n return () => {\n this.errorHandlers.delete(handler);\n };\n }\n}\n","/**\n * BuddyCache:好友缓存(B2,2026-09-08;仿 GroupCache 的 ADR-008 模式)\n *\n * - 更新:订阅 Buddy/onBuddyListChange 全量快照(T10 实证 = BuddyCategory[],\n * 好友明细在 category.buddyList)维护 uid → BuddyListEntry 快照。\n * - diff:首帧只建 baseline 不发事件(防止启动时对每个好友发 friend_add);\n * 之后快照对比上一帧——新增发布 BuddyCache/onBuddyAdded、消失发布\n * BuddyCache/onBuddyRemoved(归一化事件,协议层翻译保持纯函数)。\n * - 消费:协议翻译层订阅 diff 通道(friend_add notice)或只读快照查询。\n *\n * 无全局单例(ADR-015 推论)——每进程每 session 一份,由装配层持有。\n */\n\nimport type { BuddyEventChannel } from \"../bridge/index.js\";\nimport { NTEventChannel } from \"../event-channel.js\";\nimport type { BuddyCategory, BuddyListEntry } from \"../types/index.js\";\n\n/** BuddyCache diff 事件 listener(归一化「新增/删除」事件)。 */\nexport type BuddyCacheListener = {\n onBuddyAdded: (entry: BuddyListEntry) => void;\n onBuddyRemoved: (entry: BuddyListEntry) => void;\n};\n\n/** BuddyCache diff 事件通道(事件名 BuddyCache/onBuddyAdded 等)。 */\nexport type BuddyCacheEventChannel = NTEventChannel<BuddyCacheListener, \"BuddyCache\">;\n\n/** BuddyCache 构造参数。 */\nexport interface BuddyCacheOptions {\n /** Buddy 事件通道(FriendBridge 注册原生监听后 emit 到这里)。 */\n channel: BuddyEventChannel;\n /** 校准日志(可选:快照形状异常 raw 记录;缺省静默)。 */\n logger?: { warn(obj: unknown, msg?: string): void };\n}\n\n/**\n * 防御性收窄 onBuddyListChange 载荷 → BuddyCategory[](T10 实证形状;\n * 非数组返回 null,调用方打 raw 日志积累校准数据)。\n */\nexport function narrowBuddyCategories(arg: unknown): BuddyCategory[] | null {\n if (!Array.isArray(arg)) {\n return null;\n }\n for (const item of arg) {\n if (item === null || typeof item !== \"object\") {\n return null;\n }\n }\n return arg as BuddyCategory[];\n}\n\n/** 从快照分类拍平好友明细(buddyList 缺失的分类跳过)。 */\nfunction flattenEntries(categories: BuddyCategory[]): BuddyListEntry[] {\n const out: BuddyListEntry[] = [];\n for (const cat of categories) {\n for (const entry of cat.buddyList ?? []) {\n if (typeof entry?.uid === \"string\" && entry.uid !== \"\") {\n out.push(entry);\n }\n }\n }\n return out;\n}\n\n/** 好友缓存:快照维护 + diff 归一化事件。 */\nexport class BuddyCache {\n private readonly channel: BuddyEventChannel;\n private readonly logger: { warn(obj: unknown, msg?: string): void } | undefined;\n\n /** diff 归一化事件通道(协议层订阅 friend_add / IPC 转发消费)。 */\n readonly events: BuddyCacheEventChannel = new NTEventChannel(\"BuddyCache\");\n\n /** 好友快照(uid → entry,onBuddyListChange 全量维护)。 */\n private readonly buddies = new Map<string, BuddyListEntry>();\n\n /** 首帧已建 baseline(此后快照才参与 diff)。 */\n private bootstrapped = false;\n\n private unsubscribes: (() => void) | null = null;\n\n constructor(opts: BuddyCacheOptions) {\n this.channel = opts.channel;\n this.logger = opts.logger;\n }\n\n /** 订阅 Buddy 事件通道(幂等)。 */\n register(): void {\n if (this.unsubscribes !== null) {\n return;\n }\n const unsub = this.channel.on(\"Buddy/onBuddyListChange\", (arg: unknown) => {\n const categories = narrowBuddyCategories(arg);\n if (categories === null) {\n // 未知形状:raw 日志积累校准数据(V2 事件载荷是 boolean,同样落这里)\n this.logger?.warn(\n { raw: JSON.stringify(arg)?.slice(0, 2000) },\n \"BuddyCache: onBuddyListChange 未知载荷形状\",\n );\n return;\n }\n this.applySnapshot(flattenEntries(categories));\n });\n this.unsubscribes = unsub;\n }\n\n /** 退订事件通道(幂等)。 */\n unregister(): void {\n this.unsubscribes?.();\n this.unsubscribes = null;\n this.buddies.clear();\n this.bootstrapped = false;\n }\n\n /** 应用全量快照:首帧建 baseline,之后 diff 发布归一化事件。 */\n private applySnapshot(entries: BuddyListEntry[]): void {\n if (!this.bootstrapped) {\n for (const entry of entries) {\n this.buddies.set(entry.uid, entry);\n }\n this.bootstrapped = true;\n return;\n }\n const next = new Map<string, BuddyListEntry>();\n for (const entry of entries) {\n next.set(entry.uid, entry);\n }\n for (const [uid, entry] of next) {\n if (!this.buddies.has(uid)) {\n this.events.emit(\"BuddyCache/onBuddyAdded\", entry);\n }\n }\n for (const [uid, entry] of this.buddies) {\n if (!next.has(uid)) {\n this.events.emit(\"BuddyCache/onBuddyRemoved\", entry);\n }\n }\n this.buddies.clear();\n for (const [uid, entry] of next) {\n this.buddies.set(uid, entry);\n }\n }\n\n /** 好友快照(只读拷贝)。 */\n listBuddies(): BuddyListEntry[] {\n return [...this.buddies.values()];\n }\n\n /** 单个好友(快照;undefined = 不在缓存)。 */\n getBuddy(uid: string): BuddyListEntry | undefined {\n return this.buddies.get(uid);\n }\n\n /** 快照是否已建(测试/诊断用)。 */\n get baselineReady(): boolean {\n return this.bootstrapped;\n }\n}\n","/**\n * 等待/延迟工具(login 模块共享,2026-08-08 克隆合并)\n */\n\n/** 等待条件满足(轮询,带超时)。 */\nexport function waitFor(\n predicate: () => boolean,\n timeoutMs: number,\n intervalMs = 500,\n): Promise<boolean> {\n return new Promise((resolve) => {\n const started = Date.now();\n const tick = (): void => {\n if (predicate()) {\n resolve(true);\n return;\n }\n if (Date.now() - started > timeoutMs) {\n resolve(false);\n return;\n }\n setTimeout(tick, intervalMs);\n };\n tick();\n });\n}\n\n/** 短延迟。 */\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ms);\n });\n}\n","/**\n * GroupCache:群/成员缓存(ADR-008,P2-17,2026-08-05)\n *\n * - 更新:订阅 GroupEventChannel 主动维护(群列表/群详情/成员事件 → 缓存 upsert)。\n * - 回填:只读接口查询缺失时经 GroupApi 惰性拉取(in-flight 去重防并发重复拉)。\n * - 消费:协议翻译层只读消费,禁止调 API(翻译 = 纯函数)。\n *\n * 无全局单例(ADR-015 推论)——每进程每 session 实例化一份,由装配层持有。\n */\n\nimport type { GroupApi } from \"../apis/index.js\";\nimport type { GroupEventChannel } from \"../bridge/index.js\";\nimport { waitFor } from \"../login/wait.js\";\nimport type {\n Group,\n GroupDetailInfo,\n GroupMember,\n GroupMemberDataSource,\n GroupMemberListChange,\n} from \"../types/index.js\";\nimport { GroupListUpdateType } from \"../types/index.js\";\n\n/** GroupCache 构造参数。 */\nexport interface GroupCacheOptions {\n /** 群事件通道(GroupBridge 注册原生监听后 emit 到这里)。 */\n channel: GroupEventChannel;\n /** 群 API(惰性回填用)。 */\n groupApi: GroupApi;\n}\n\n/**\n * 群/成员缓存:事件主动维护 + 查询惰性回填。\n * 只读接口 getGroupDetail / getMembers / getMember / getGroup(列表项)。\n */\nexport class GroupCache {\n private readonly channel: GroupEventChannel;\n private readonly groupApi: GroupApi;\n\n /** 群列表项(onGroupListUpdate / getGroupList 回填)。 */\n private readonly groupList = new Map<string, Group>();\n /** 群详情(onGroupDetailInfoChange / getGroupInfo 回填)。 */\n private readonly groupDetails = new Map<string, GroupDetailInfo>();\n /** 群成员:groupCode → (uid → GroupMember)。 */\n private readonly members = new Map<string, Map<string, GroupMember>>();\n /** 惰性回填 in-flight 去重。 */\n private readonly inFlight = new Map<string, Promise<unknown>>();\n\n private unsubscribes: (() => void) | null = null;\n\n constructor(opts: GroupCacheOptions) {\n this.channel = opts.channel;\n this.groupApi = opts.groupApi;\n }\n\n /** 订阅群事件通道(幂等)。 */\n register(): void {\n if (this.unsubscribes !== null) {\n return;\n }\n const unsubs: Array<() => void> = [];\n unsubs.push(\n this.channel.on(\"Group/onGroupListInited\", (listEmpty: boolean) => {\n if (listEmpty) {\n this.groupList.clear();\n }\n }),\n );\n unsubs.push(\n this.channel.on(\n \"Group/onGroupListUpdate\",\n (updateType: GroupListUpdateType, groupList: Group[]) => {\n if (updateType === GroupListUpdateType.ALL) {\n this.groupList.clear();\n }\n for (const group of groupList) {\n this.groupList.set(group.groupCode, group);\n }\n },\n ),\n );\n unsubs.push(\n this.channel.on(\"Group/onGroupDetailInfoChange\", (detailInfo: GroupDetailInfo) => {\n this.groupDetails.set(detailInfo.groupCode, detailInfo);\n }),\n );\n unsubs.push(\n this.channel.on(\"Group/onMemberListChange\", (arg: GroupMemberListChange) => {\n this.upsertMembers(arg.sceneId, arg.infos);\n }),\n );\n unsubs.push(\n this.channel.on(\n \"Group/onMemberInfoChange\",\n (\n _groupCode: string,\n _dataSource: GroupMemberDataSource,\n members: Map<string, GroupMember>,\n ) => {\n this.upsertMembers(_groupCode, members);\n },\n ),\n );\n this.unsubscribes = () => {\n for (const unsub of unsubs) {\n unsub();\n }\n };\n }\n\n /** 退订事件通道(幂等)。 */\n unregister(): void {\n this.unsubscribes?.();\n this.unsubscribes = null;\n this.groupList.clear();\n this.groupDetails.clear();\n this.members.clear();\n this.inFlight.clear();\n }\n\n /** 合并成员到该群缓存(uid → GroupMember)。 */\n private upsertMembers(groupCode: string, infos: Map<string, GroupMember>): void {\n let map = this.members.get(groupCode);\n if (map === undefined) {\n map = new Map();\n this.members.set(groupCode, map);\n }\n for (const [uid, member] of infos) {\n map.set(uid, member);\n }\n }\n\n /** 群列表是否已有缓存(同步判断)。 */\n hasGroup(groupCode: string): boolean {\n return this.groupList.has(groupCode) || this.groupDetails.has(groupCode);\n }\n\n /**\n * 群列表快照(只读,缓存由 onGroupListUpdate 事件主动维护)。\n * 群列表数据不走原生 getGroupList 返回值(实测仅 { result, errMsg },\n * 列表经事件推送)——协议/IPC 层消费列表一律经此方法。\n */\n listGroups(): Group[] {\n return [...this.groupList.values()];\n }\n\n /**\n * 群列表快照(缓存为空时主动触发原生刷新,等待事件回填后返回)。\n *\n * 实测(2026-08-08 端到端联调):onGroupListUpdate 仅在登录后首轮同步\n * 推送(MMKV 已有缓存后不再重复推送)——进程内缓存为空时列表读不到。\n * 触发 getGroupList(true) 会重新拉取并推送事件回填缓存。\n * 返回空数组 = 刷新后仍无数据(超时 5s)。\n */\n async listGroupsRefreshed(): Promise<Group[]> {\n if (this.groupList.size > 0) {\n return [...this.groupList.values()];\n }\n await this.groupApi.getGroupList(true);\n await waitFor(() => this.groupList.size > 0, 5_000, 200);\n return [...this.groupList.values()];\n }\n\n /**\n * 群列表项(只读;缺失惰性回填 getGroupList)。\n * 返回 undefined 表示群不存在。\n */\n async getGroup(groupCode: string): Promise<Group | undefined> {\n const cached = this.groupList.get(groupCode);\n if (cached !== undefined) {\n return cached;\n }\n await this.loadGroupList();\n return this.groupList.get(groupCode);\n }\n\n /**\n * 群详情(只读;缺失惰性回填 getGroupInfo)。\n * getGroupInfo 恒返回对象(失败抛 KernelError),直接缓存。\n */\n async getGroupDetail(groupCode: string): Promise<GroupDetailInfo> {\n const cached = this.groupDetails.get(groupCode);\n if (cached !== undefined) {\n return cached;\n }\n const detail = await this.dedupe<GroupDetailInfo>(`detail:${groupCode}`, () =>\n this.groupApi.getGroupInfo(groupCode),\n );\n this.groupDetails.set(groupCode, detail);\n return detail;\n }\n\n /**\n * 群成员列表(只读;缺失惰性回填 getAllMemberList(forceFetch=true))。\n * 返回空数组表示该群无成员(或拉取失败已由 GroupApi 抛 KernelError)。\n */\n async getMembers(groupCode: string): Promise<GroupMember[]> {\n const map = this.members.get(groupCode);\n if (map !== undefined && map.size > 0) {\n return [...map.values()];\n }\n const members = await this.dedupe<GroupMember[]>(`members:${groupCode}`, () =>\n this.groupApi.getGroupMemberList(groupCode, true),\n );\n const merged = new Map<string, GroupMember>();\n for (const member of members) {\n merged.set(member.uid, member);\n }\n this.members.set(groupCode, merged);\n return members;\n }\n\n /**\n * 群成员(只读;缺失回填:先整群拉取,再按 uid 取;仍无则 getMemberInfo 单查)。\n * 返回 undefined 表示成员不在群。\n */\n async getMember(groupCode: string, uid: string): Promise<GroupMember | undefined> {\n const cached = await this.getCachedMember(groupCode, uid);\n if (cached !== undefined) {\n return cached;\n }\n const members = await this.dedupe<GroupMember[]>(`member:${groupCode}:${uid}`, () =>\n this.groupApi.getGroupMemberInfo(groupCode, [uid]),\n );\n const [member] = members;\n if (member === undefined) {\n return;\n }\n const target = this.members.get(groupCode);\n if (target !== undefined) {\n target.set(member.uid, member);\n }\n return member;\n }\n\n /** 读缓存成员(缺失先整群回填一次)。 */\n private async getCachedMember(\n groupCode: string,\n uid: string,\n ): Promise<GroupMember | undefined> {\n let map = this.members.get(groupCode);\n if (map === undefined || map.size === 0) {\n await this.getMembers(groupCode);\n map = this.members.get(groupCode);\n }\n return map?.get(uid);\n }\n\n /** 惰性回填全量群列表(getGroup 缺失时)。 */\n private async loadGroupList(): Promise<void> {\n const list = await this.dedupe<Group[]>(\"group-list\", () =>\n this.groupApi.getGroupList(false),\n );\n for (const group of list) {\n this.groupList.set(group.groupCode, group);\n }\n }\n\n /** 惰性回填并发去重:同 key 并发只拉一次。 */\n private async dedupe<T>(key: string, loader: () => Promise<T>): Promise<T> {\n const existing = this.inFlight.get(key);\n if (existing !== undefined) {\n return await (existing as Promise<T>);\n }\n const task = loader()\n .catch((err: unknown) => {\n throw err;\n })\n .finally(() => {\n this.inFlight.delete(key);\n });\n this.inFlight.set(key, task);\n return await task;\n }\n}\n","/**\n * CoreContext:只读装配根(ADR-015 推论,无全局单例)\n *\n * 把 logger / paths / wrapper 上下文聚合成单一装配根,由装配层(NapukettoCore)\n * 创建并传递给消费方(协议层 adapter、apis、cache)。\n *\n * - 无全局单例:每账号每进程一份,多账号多进程场景天然隔离(ADR-015)。\n * - 只读消费:字段由装配层填充,消费方只读,不直接修改。\n */\nimport type pino from \"pino\";\nimport type { PathWrapper } from \"./infra/index.js\";\nimport type { LoginResult } from \"./login/index.js\";\nimport type { WrapperContext } from \"./wrapper/wrapper-loader.js\";\n\nexport interface CoreContextOptions {\n /** pino logger(console + 可选文件,由装配层创建)。 */\n logger: pino.Logger;\n /** 账号数据目录布局(config/logs/cache)。 */\n paths: PathWrapper;\n}\n\n/**\n * 装配根:持有进程级共享对象。\n * wrapper / login 由 NapukettoCore 装配后填充,未装配前为 null。\n */\nexport interface CoreContext {\n readonly logger: pino.Logger;\n readonly paths: PathWrapper;\n /** QQ wrapper 上下文(attachWrapper 后填充)。 */\n wrapper: WrapperContext | null;\n /** 登录结果(login 成功后填充)。 */\n login: LoginResult | null;\n}\n\n/** 创建装配根(初始 wrapper/login 为 null)。 */\nexport function createCoreContext(opts: CoreContextOptions): CoreContext {\n return {\n logger: opts.logger,\n paths: opts.paths,\n wrapper: null,\n login: null,\n };\n}\n","/**\n * wrapper NAPI 回调适配器实现(普通 JS 对象,2026-08-05 确认)\n *\n * 事实(实测):wrapper.node 的 exports 89 键里没有 NodeI*Adapter/Listener 构造器,\n * engine.initWithDeskTopConfig / session.init 的 adapter 与 listener 参数一律接受\n * **普通 JS 对象**——NAPI 按方法名反射读取回调(自研实现)。\n *\n * 职责:engine / session 交互所需的全部回调占位与监听器工厂。\n * - GlobalAdapter → engine.initWithDeskTopConfig 第二参(onLog / getAppSetting / ...)\n * - DependsAdapter → session.init 第二参(onMSFStatusChange / onMSFSsoError / getGroupCode)\n * - DispatcherAdapter → session.init 第三参(dispatchRequest / dispatchCall / dispatchCallWithJson)\n * - createSessionListener → 日志版 session 监听器(boot 阶段默认,login 模块可覆盖)\n * - createLoginListener → 登录监听器占位(登录器接入时填充)\n *\n * 这些类的实例本身就是「普通 JS 对象」,可被 NAPI 反射——直接 new 传入即可。\n */\n\nimport type {\n IKernelLoginListener,\n NodeIDependsAdapter,\n NodeIDispatcherAdapter,\n NodeIGlobalAdapter,\n NodeIKernelSessionListener,\n} from \"../types/index.js\";\n\n/** NAPI 回调占位(方法存在即可被反射调用,无副作用)。 */\nconst noop = (): void => undefined;\n\n/** NodeIGlobalAdapter 实现(engine.initWithDeskTopConfig 第二参)。 */\nexport class GlobalAdapter implements NodeIGlobalAdapter {\n onLog = noop;\n onGetSrvCalTime = noop;\n onShowErrUITips = noop;\n fixPicImgType = noop;\n getAppSetting = noop;\n onInstallFinished = noop;\n onUpdateGeneralFlag = noop;\n onGetOfflineMsg = noop;\n}\n\n/** NodeIDependsAdapter 实现(session.init 第二参)。 */\nexport class DependsAdapter implements NodeIDependsAdapter {\n onMSFStatusChange = noop;\n onMSFSsoError = noop;\n getGroupCode = noop;\n}\n\n/** NodeIDispatcherAdapter 实现(session.init 第三参)。 */\nexport class DispatcherAdapter implements NodeIDispatcherAdapter {\n dispatchRequest = noop;\n dispatchCall = noop;\n dispatchCallWithJson = noop;\n}\n\n/** 会话监听器选项。 */\nexport interface SessionListenerOptions {\n /** 事件日志回调(默认静默——boot 阶段事件对用户是噪音;调试时可传回调观察)。 */\n onEvent?: (name: string, ...args: unknown[]) => void;\n}\n\n/**\n * 创建会话监听器(默认静默,boot 阶段默认;login 模块接入后可覆盖)。\n * 回调方法必须存在(NAPI 按方法名反射),但输出与否由调用方决定——\n * onNTSessionCreate / onOpentelemetryInit 等对用户无信息量,默认不打 stdout。\n */\nexport function createSessionListener(\n opts: SessionListenerOptions = {},\n): NodeIKernelSessionListener {\n const { onEvent } = opts;\n const emit = (name: string, ...args: unknown[]): void => {\n onEvent?.(name, ...args);\n };\n return {\n onNTSessionCreate: (sessionId) => emit(\"onNTSessionCreate\", sessionId),\n onGProSessionCreate: (sessionId) => emit(\"onGProSessionCreate\", sessionId),\n onSessionInitComplete: (sessionId) => emit(\"onSessionInitComplete\", sessionId),\n onOpentelemetryInit: (info) => emit(\"onOpentelemetryInit\", info),\n onUserOnlineResult: (result) => emit(\"onUserOnlineResult\", result),\n onGetSelfTinyId: (result) => emit(\"onGetSelfTinyId\", result),\n };\n}\n\n/** 创建登录监听器(占位,登录器接入时填充)。 */\nexport function createLoginListener(): IKernelLoginListener {\n return {};\n}\n","/**\n * 登录连接与快速登录(从 lifecycle.ts 拆分,2026-08-08 FTA 优化)\n *\n * - waitForNetworkConnection:等 MSF 网络连接就绪(getMsfStatus() === 3)\n * - ensureLoginConnected:connect() → 等 onLoginConnected(自建宿主必需)\n * - quickLogin:历史账号快速登录(网络异常 1006511 等就绪后重试;\n * T3 单步超时兜底——底层 promise 永久挂起时按登录失败抛出)\n */\n\nimport { kernelError } from \"../infra/index.js\";\nimport { createLoginListener } from \"../wrapper/wrapper-adapters.js\";\nimport type { WrapperContext } from \"../wrapper/wrapper-loader.js\";\nimport { sleep, waitFor } from \"./wait.js\";\n\n/** 登录结果(QR 或快速登录)。 */\nexport interface LoginResult {\n uin: string;\n uid: string;\n nick: string;\n}\n\n/** 登录列表项(getLoginList 返回,说明书参考)。 */\nexport interface LoginAccountInfo {\n uin: string;\n uid?: string;\n nickName?: string;\n isQuickLogin?: boolean;\n}\n\n/** 登录服务形状(getLoginList / quickLoginWithUin / getMsfStatus,自研描述)。 */\ntype LoginServiceShape = {\n getLoginList(): Promise<{ result: number; LocalLoginInfoList: LoginAccountInfo[] }>;\n quickLoginWithUin(uin: string): Promise<{ result: string; loginErrorInfo: { errMsg: string } }>;\n getMsfStatus(): number;\n};\n\n/**\n * 网络状态(MSF)常量。\n * 3 = 已连接(NapCat waitForNetworkConnection 语义,自研描述)。\n */\nconst MSF_STATUS_CONNECTED = 3;\n\n/** 快速登录网络异常错误码(1006511,P2-1 实测:登录前网络未就绪时报此错)。 */\nconst NETWORK_ERROR_CODE = \"1006511\";\n\n/** 登录连接异常错误提示(自建宿主实测:connect 后未缓冲即 quickLogin 报此错)。 */\nconst CONNECTION_ERROR_HINT = \"登录系统连接异常\";\n\n/** 网络重试最大次数(每次重试前等网络就绪)。 */\nconst NETWORK_RETRY_MAX = 3;\n\n/** 网络就绪等待超时(毫秒)。 */\nconst NETWORK_READY_TIMEOUT_MS = 15_000;\n\n/** 网络就绪轮询间隔(毫秒)。 */\nconst NETWORK_READY_POLL_MS = 1000;\n\n/** 连接稳定缓冲(毫秒,p0-kernel-flow 实证值)。 */\nconst CONNECTION_SETTLE_MS = 3000;\n\n/**\n * 快速登录单步超时(毫秒,T3:软重登挂起兜底)。\n * 服务端已有同账号会话时底层 quickLoginWithUin / getLoginList 永不 settle——\n * 裸 await 会让 core.login 的 catch/qrFallback、loader 的 control.release 全部\n * 永久等待。底层 promise 无法取消:超时后悬挂丢弃即可。\n */\nexport const QUICK_LOGIN_TIMEOUT_MS = 20_000;\n\n/** 快速登录超时文案(按登录失败处理;不含网络错误特征,不触发 1006511 重试叠挂)。 */\nexport const QUICK_LOGIN_TIMEOUT_MESSAGE = \"快速登录超时\";\n\n/**\n * 等待网络连接就绪(loginService.getMsfStatus() === 3)。\n * 参考 NapCat waitForNetworkConnection 思路(自研实现):快速登录在 QQ 刚拉起、\n * 网络栈未初始化时报 1006511 网络异常——等待 MSF 连接后再重试。\n * @returns 是否在超时前就绪。\n */\nexport function waitForNetworkConnection(\n ctx: WrapperContext,\n opts: { timeoutMs?: number } = {},\n): Promise<boolean> {\n const raw = ctx.loginService as unknown as LoginServiceShape | null;\n if (raw === null || typeof raw.getMsfStatus !== \"function\") {\n return Promise.resolve(false);\n }\n return waitFor(\n () => {\n try {\n return raw.getMsfStatus() === MSF_STATUS_CONNECTED;\n } catch {\n return false;\n }\n },\n opts.timeoutMs ?? NETWORK_READY_TIMEOUT_MS,\n NETWORK_READY_POLL_MS,\n );\n}\n\n/** 列出历史登录账号(boot.cjs 启动横幅用,「可用快速登录 of QQ」)。 */\nexport async function listLoginAccounts(ctx: WrapperContext): Promise<LoginAccountInfo[]> {\n const raw = ctx.loginService as unknown as LoginServiceShape | null;\n if (raw === null) {\n throw kernelError(\"loginService 无效(缺 getLoginList)\", \"INVALID_STATE\");\n }\n const list = await raw.getLoginList();\n return list.LocalLoginInfoList;\n}\n\n/**\n * 登录服务连接(自建宿主必需,HANDOVER-V6/V10 p0-kernel-flow 实证顺序):\n * connect() → 等 onLoginConnected。不 connect 则快速登录报「登录系统连接异常」。\n *\n * 对齐 p0-kernel-flow:initConfig → getLoginList → connect + onLoginConnected →\n * quickLoginWithUin。QQ 环境(worker/主进程)下可能已连接——connect 幂等,\n * 监听器超时兜底,不影响原流程。\n */\nasync function ensureLoginConnected(\n ctx: WrapperContext,\n opts: { timeoutMs?: number },\n): Promise<void> {\n const raw = ctx.loginService as unknown as {\n connect?: () => void;\n addKernelLoginListener?: (listener: unknown) => number;\n } | null;\n if (raw === null || typeof raw.connect !== \"function\") {\n return; // 无 connect(老版本 / QQ 主进程自连),跳过\n }\n await waitLoginConnected(raw, opts.timeoutMs);\n // onLoginConnected 后网络栈仍在初始化(p0-kernel-flow 实证:connect → onLoginConnected\n // → 3s 缓冲 → quickLoginWithUin 成功;无缓冲则「登录系统连接异常」)。\n await sleep(CONNECTION_SETTLE_MS);\n}\n\n/** 等 onLoginConnected(注册临时监听 + connect + 超时兜底)。 */\nfunction waitLoginConnected(\n raw: {\n connect?: () => void;\n addKernelLoginListener?: (listener: unknown) => number;\n },\n timeoutMs?: number,\n): Promise<void> {\n return new Promise<void>((resolve) => {\n let settled = false;\n const finish = (): void => {\n if (!settled) {\n settled = true;\n resolve();\n }\n };\n tryRegisterConnectListener(raw, finish);\n setTimeout(finish, timeoutMs ?? NETWORK_READY_TIMEOUT_MS);\n try {\n raw.connect?.();\n } catch {\n finish();\n }\n });\n}\n\n/** 注册临时登录连接监听(失败不阻塞连接)。 */\nfunction tryRegisterConnectListener(\n raw: {\n connect?: () => void;\n addKernelLoginListener?: (listener: unknown) => number;\n },\n finish: () => void,\n): void {\n try {\n // 临时监听等 onLoginConnected;保留不 remove——登录后续回调\n // (onQRCodeLoginSucceed 等)仍可能触发,且进程内只登录一次,无泄漏风险。\n const listener = createLoginListener();\n listener.onLoginConnected = () => {\n finish();\n };\n if (typeof raw.addKernelLoginListener === \"function\") {\n raw.addKernelLoginListener(listener);\n }\n } catch {\n // 监听注册失败不阻塞连接\n }\n}\n\n/**\n * 目标账号选择(2026-08-07 修复:显式 uin 不在列表时**报错**而非静默 fallback——\n * 之前会退到第一个可快速登录账号,导致 `-q <uin>` 实际登成别的号)。\n * 导出供单测(login-connect.test.ts)。\n */\nexport function pickLoginTarget(\n items: LoginAccountInfo[],\n uin: string | undefined,\n): LoginAccountInfo {\n if (uin !== undefined) {\n const target = items.find((i) => i.uin === uin);\n if (target === undefined) {\n const available = items.map((i) => i.uin).join(\"、\");\n throw kernelError(`账号 ${uin} 不在登录列表(可用:${available})`, \"NOT_FOUND\");\n }\n return target;\n }\n // 未指定 uin:优先第一个可快速登录账号,否则列表第一个\n const quick = items.find((i) => i.isQuickLogin);\n if (quick !== undefined) {\n return quick;\n }\n const [first] = items;\n if (first === undefined) {\n throw kernelError(\"无可用登录账号\", \"NOT_LOGIN\");\n }\n return first;\n}\n\n/** 单次快速登录尝试的结果(成功带结果,失败带错误消息)。 */\ntype AttemptResult = { ok: true; result: LoginResult } | { ok: false; errMsg: string };\n\n/**\n * 底层 NAPI promise 超时竞速(T3):promise 先 settle 用其结果,超时先到用\n * onTimeout 产物(可抛错 → 整体 reject)。底层 promise 无法取消,超时后\n * 悬挂丢弃——勿做清理动作引发二次问题;败者的迟到 settle 由 race 吞掉。\n */\nfunction raceWithTimeout<T>(\n promise: Promise<T>,\n timeoutMs: number,\n onTimeout: () => T,\n): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | null = null;\n const timeout = new Promise<T>((resolve, reject) => {\n timer = setTimeout(() => {\n try {\n resolve(onTimeout());\n } catch (err) {\n reject(err);\n }\n }, timeoutMs);\n });\n return Promise.race([promise, timeout]).finally(() => {\n if (timer !== null) {\n clearTimeout(timer);\n }\n });\n}\n\n/**\n * 带网络重试的快速登录(P2-1)。\n * 重试语义:仅当失败为网络异常(1006511)且未达上限时,等网络就绪后重试。\n * T3:每次尝试包超时竞速,挂起按登录失败处理(超时文案非网络错误 → 不重试)。\n */\nasync function loginWithNetworkRetry(\n ctx: WrapperContext,\n loginService: LoginServiceShape,\n target: LoginAccountInfo,\n opts: { uin?: string; timeoutMs?: number },\n quickTimeoutMs: number,\n): Promise<LoginResult> {\n let lastErrMsg = \"\";\n for (let attempt = 1; attempt <= NETWORK_RETRY_MAX; attempt += 1) {\n const attemptResult = await raceWithTimeout(\n attemptQuickLogin(loginService, target),\n quickTimeoutMs,\n (): AttemptResult => ({ ok: false, errMsg: QUICK_LOGIN_TIMEOUT_MESSAGE }),\n );\n if (attemptResult.ok) {\n return attemptResult.result;\n }\n lastErrMsg = attemptResult.errMsg;\n const shouldRetry = isNetworkError(lastErrMsg) && attempt < NETWORK_RETRY_MAX;\n if (!shouldRetry) {\n break;\n }\n const ready = await waitForNetworkConnection(ctx, {\n timeoutMs: opts.timeoutMs ?? NETWORK_READY_TIMEOUT_MS,\n });\n if (!ready) {\n break;\n }\n }\n throw kernelError(`快速登录失败: ${lastErrMsg}`, \"NOT_LOGIN\");\n}\n\n/** 单次快速登录尝试(成功带结果,失败带错误消息)。 */\nasync function attemptQuickLogin(\n loginService: LoginServiceShape,\n target: LoginAccountInfo,\n): Promise<AttemptResult> {\n const result = await loginService.quickLoginWithUin(target.uin);\n const { errMsg } = result.loginErrorInfo;\n if (errMsg) {\n return { ok: false, errMsg };\n }\n return {\n ok: true,\n result: {\n uin: target.uin,\n uid: target.uid ?? \"\",\n nick: target.nickName ?? \"\",\n },\n };\n}\n\n/** 判定快速登录失败是否为网络异常(重试语义)。导出供单测。 */\nexport function isNetworkError(errMsg: string): boolean {\n return errMsg.includes(NETWORK_ERROR_CODE) || errMsg.includes(CONNECTION_ERROR_HINT);\n}\n\n/** quickLogin 参数(uin 指定账号;timeoutMs 网络就绪等待;quickLoginTimeoutMs 单步超时)。 */\nexport interface QuickLoginOptions {\n /** 指定快速登录账号(缺省遍历历史列表,优先 isQuickLogin)。 */\n uin?: string;\n /** 网络就绪等待超时(毫秒,P2-1 既有语义),默认 15s。 */\n timeoutMs?: number;\n /** 单步快速登录超时(毫秒,T3 挂起兜底),默认 {@link QUICK_LOGIN_TIMEOUT_MS}。 */\n quickLoginTimeoutMs?: number;\n}\n\n/**\n * 快速登录:遍历历史登录列表尝试。\n * P2-1:失败且错误为网络异常(1006511)时,等网络就绪后重试(最多 NETWORK_RETRY_MAX 次)。\n * T3:getLoginList 与每次 quickLoginWithUin 均包超时竞速(默认 20s)——服务端\n * 已有会话时底层 promise 永久挂起,超时按登录失败(「快速登录失败: 快速登录超时」)\n * 抛出,上层 catch/qrFallback 语义自动生效。\n */\nexport async function quickLogin(\n ctx: WrapperContext,\n opts: QuickLoginOptions = {},\n): Promise<LoginResult> {\n const raw = ctx.loginService as unknown as LoginServiceShape | null;\n if (raw === null) {\n throw kernelError(\"loginService 无效(缺 getLoginList)\", \"INVALID_STATE\");\n }\n const loginService = raw;\n const quickTimeoutMs = opts.quickLoginTimeoutMs ?? QUICK_LOGIN_TIMEOUT_MS;\n // 连接登录服务(自建宿主必需:不 connect 则「登录系统连接异常」)\n await ensureLoginConnected(ctx, {\n ...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),\n });\n const list = await raceWithTimeout(loginService.getLoginList(), quickTimeoutMs, () => {\n throw kernelError(`快速登录失败: ${QUICK_LOGIN_TIMEOUT_MESSAGE}`, \"NOT_LOGIN\");\n });\n const items = list.LocalLoginInfoList;\n if (items.length === 0) {\n throw kernelError(\"无历史登录账号\", \"NOT_LOGIN\");\n }\n const target = pickLoginTarget(items, opts.uin);\n return loginWithNetworkRetry(ctx, loginService, target, opts, quickTimeoutMs);\n}\n","/**\n * 完整启动生命周期(wrapper.node 契约流程,2026-08-05 实测确认,自研实现)\n *\n * 启动顺序由 wrapper.node 外部契约决定(engine→login→session→startNT),\n * 运行时实测确认,自研实现:\n * 1. engine.initWithDeskTopConfig(appid/qua/版本)\n * 2. loginService.initConfig + addKernelLoginListener\n * 3. getLoginList() → quickLoginWithUin(uin)(或 QR 登录)\n * 4. 登录成功 → genSessionConfig → session.init(config, 3 adapter, listener) → startNT(0)\n * 5. 等 init 完成信号(onOpentelemetryInit(is_init) 为主,onSessionInitComplete 为辅)\n *\n * 关键(2026-08-05 实测修正):\n * - session 用 `new wrapper.NodeIQQNTWrapperSession()` 创建;exports 不含 NodeI*Adapter/Listener\n * 构造器(89 键实测无)——adapter 与 listener 一律传普通 JS 对象(NAPI 反射读取方法回调)。\n *\n * 模块边界(2026-08-05 解耦;2026-08-08 FTA 优化再拆):\n * - 配置装配(buildEngineConfig / buildLoginConfig / buildSessionConfig)→ wrapper-config.ts\n * - NAPI 回调适配器(GlobalAdapter / DependsAdapter / DispatcherAdapter / listener 工厂)→ wrapper-adapters.ts\n * - 登录连接 + 快速登录(waitForNetworkConnection / quickLogin 等)→ login-connect.ts\n * - 本文件只保留 session 流程编排:就绪等待(waitSessionReady)+ session 初始化(initAndStartSession)。\n */\n\nimport { kernelError } from \"../infra/index.js\";\nimport type { NodeIKernelSessionListener, WrapperSessionInitConfig } from \"../types/index.js\";\nimport { DependsAdapter, DispatcherAdapter } from \"../wrapper/wrapper-adapters.js\";\nimport type { WrapperContext } from \"../wrapper/wrapper-loader.js\";\nimport { waitFor } from \"./wait.js\";\n\nexport type { LoginAccountInfo, LoginResult, QuickLoginOptions } from \"./login-connect.js\";\nexport { listLoginAccounts, quickLogin, waitForNetworkConnection } from \"./login-connect.js\";\n\n/** session init 默认超时(毫秒)。 */\nconst DEFAULT_INIT_TIMEOUT_MS = 15_000;\n\n/** session 就绪等待默认超时(毫秒)。 */\nconst SESSION_READY_TIMEOUT_MS = 30_000;\n\n/** session 就绪轮询间隔(毫秒)。 */\nconst SESSION_READY_POLL_MS = 1000;\n\n/**\n * 等待 QQ 自己完成 session 初始化(framework 复用模式,2026-08-05 修正)。\n *\n * 背景:QQ 9.9.31 实测——session 由 QQ 主进程自己创建并 init(登录后)。\n * 我们不自己 init(重复 init 断言 \"implementation of IQQNTWrapperSession is not valid\"),\n * 也不自己 create(登录前 create() 干扰 QQ,实测 QQ 退出 code=0)。\n * 正确做法:等 QQ 的 session 就绪(getMsgService 非 null = 核心 service 已挂载)。\n */\nexport async function waitSessionReady(\n ctx: WrapperContext,\n opts: { timeoutMs?: number },\n): Promise<void> {\n const { session } = ctx;\n if (session === null || session === undefined) {\n throw kernelError(\"session 未创建\", \"INVALID_STATE\");\n }\n const ok = await waitFor(\n () => {\n try {\n const svc = session.getMsgService();\n return svc !== null && svc !== undefined;\n } catch {\n return false;\n }\n },\n opts.timeoutMs ?? SESSION_READY_TIMEOUT_MS,\n SESSION_READY_POLL_MS,\n );\n if (!ok) {\n throw kernelError(\"等待 QQ session 就绪超时(QQ 未完成 init)\", \"TIMEOUT\");\n }\n}\n\n/** 登录连接 + 快速登录(quickLogin 等)已拆到 login-connect.ts(见文件头 re-export)。 */\n\n/** session 初始化(4 参全为普通 JS 对象,等 init 完成信号)。 */\nexport async function initAndStartSession(\n ctx: WrapperContext,\n config: WrapperSessionInitConfig,\n listener: NodeIKernelSessionListener,\n opts: { timeoutMs?: number },\n): Promise<void> {\n const { session } = ctx;\n if (session === null || session === undefined) {\n throw kernelError(\"session 未创建\", \"INVALID_STATE\");\n }\n\n // adapter / listener 全部用普通 JS 对象(实测 exports 89 键无 NodeI*Adapter/Listener\n // 构造器;NAPI 反射读取对象方法回调,自研实现)。\n const depends = new DependsAdapter();\n const dispatcher = new DispatcherAdapter();\n\n const initComplete = watchInitSignal(listener);\n session.init(config, depends, dispatcher, listener);\n startSessionBestEffort(ctx, session);\n\n const ok = await Promise.race([\n initComplete.then(() => true),\n waitFor(() => false, opts.timeoutMs ?? DEFAULT_INIT_TIMEOUT_MS).then(() => false),\n ]);\n if (!ok) {\n throw kernelError(\"session init 超时\", \"TIMEOUT\");\n }\n}\n\n/** 监听 init 完成信号:onOpentelemetryInit(is_init) 为主,onSessionInitComplete(0) 为辅。 */\nfunction watchInitSignal(listener: NodeIKernelSessionListener): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const onOpentelemetry = listener.onOpentelemetryInit;\n listener.onOpentelemetryInit = (info) => {\n if (info.is_init) {\n resolve();\n }\n if (typeof onOpentelemetry === \"function\") {\n onOpentelemetry(info);\n }\n };\n const onInitComplete = listener.onSessionInitComplete;\n listener.onSessionInitComplete = (r) => {\n if (r === 0 || r === \"0\") {\n resolve();\n } else {\n reject(kernelError(`session init 失败: ${String(r)}`, \"UNKNOWN\"));\n }\n if (typeof onInitComplete === \"function\") {\n onInitComplete(r);\n }\n };\n });\n}\n\n/**\n * 启动会话(best effort,失败不致命):\n * **先 init 后 start(2026-08-07 V9 决定性修正)**。\n * 自建宿主实测(HANDOVER-V9,p0-napcat-min):必须先 session.init 再\n * startupSession.start()——顺序颠倒(先 start 后 init)业务 service 不挂载\n * (getMsgService null);init 后用 startNT(非 startupSession.start)也失败。\n * NapCat initializeSession 同款:有 startupSession 用 start(),否则 startNT(0)。\n */\nfunction startSessionBestEffort(\n ctx: WrapperContext,\n session: NonNullable<WrapperContext[\"session\"]>,\n): void {\n if (startupSessionStart(ctx)) {\n return;\n }\n try {\n session.startNT(0);\n } catch {\n try {\n session.startNT();\n } catch {\n // 无 startNT(9.9.31):忽略,等 init 完成信号\n }\n }\n}\n\n/** 有 startupSession 则 start()(失败不致命),返回是否已处理。 */\nfunction startupSessionStart(ctx: WrapperContext): boolean {\n const { startupSession } = ctx;\n if (startupSession === null || typeof startupSession.start !== \"function\") {\n return false;\n }\n try {\n startupSession.start();\n } catch {\n // start 失败不致命,靠 onOpentelemetryInit/onSessionInitComplete 信号判断\n }\n return true;\n}\n","/**\n * login.ts:QR 登录流程编排 + 状态机 + selfInfo(ADR-010,2026-08-05)\n *\n * 流程(QQ wrapper 登录契约,自研实现):\n * loginService.initConfig(core.login 已做)→ addKernelLoginListener → connect()\n * → 指定账号:quickLoginWithUin;否则 getQRCodePicture() 触发二维码\n * → 回调驱动状态机:未登录 → 扫码中 → 已扫码 → 已登录(onQRCodeLoginSucceed)\n * → 二维码过期(errType=1 errCode=3)→ refresh()\n *\n * 不含 UI 渲染(cli 只做二维码渲染/URL 打印)。\n */\nimport { kernelError } from \"../infra/index.js\";\nimport type { IKernelLoginListener } from \"../types/index.js\";\nimport { createLoginListener } from \"../wrapper/wrapper-adapters.js\";\n\n/** 登录状态。 */\nexport type LoginState = \"idle\" | \"waiting_scan\" | \"scanned\" | \"logged_in\" | \"failed\";\n\n/** 二维码数据(png base64 + 解码 url)。 */\nexport interface QrCodeData {\n pngBase64: string;\n qrcodeUrl: string;\n}\n\n/** selfInfo(登录成功后填写)。 */\nexport interface SelfInfo {\n uin: string;\n uid: string;\n nick: string;\n}\n\n/** 快速登录项。 */\nexport interface LoginListItem {\n uin: string;\n uid?: string;\n nickName?: string;\n isQuickLogin?: boolean;\n}\n\n/** loginService 接口(从 WrapperContext.loginService 获取)。 */\ninterface LoginServiceLike {\n addKernelLoginListener(listener: unknown): number;\n removeKernelLoginListener(listenerId: number): void;\n connect(): void;\n getLoginList(): Promise<{ result: number; LocalLoginInfoList: LoginListItem[] }>;\n quickLoginWithUin(uin: string): Promise<{ result: string; loginErrorInfo: { errMsg: string } }>;\n getQRCodePicture(): boolean;\n}\n\n/** 二维码过期错误码(errType=1 errCode=3,实测)。 */\nconst QR_EXPIRED_ERR_TYPE = 1;\nconst QR_EXPIRED_ERR_CODE = 3;\n\n/** 登录超时上限(毫秒,参考项目 60 次 × 2s = 120s,2026-08-13 用户拍板照搬)。 */\nexport const QR_LOGIN_TIMEOUT_MS = 120_000;\n\n/** 登录超时提示(参考项目文案,前端 failed 态展示)。 */\nexport const QR_LOGIN_TIMEOUT_MESSAGE = \"登录超时,请刷新页面重试\";\n\n/** 二维码过期判定。 */\nfunction isQrCodeExpired(errType: number, errCode: number): boolean {\n return errType === QR_EXPIRED_ERR_TYPE && errCode === QR_EXPIRED_ERR_CODE;\n}\n\n/** 二维码 png data URI 前缀剥离正则。 */\nconst DATA_URI_PREFIX_RE = /^data:image\\/\\w+;base64,/;\n\n/** QR 登录会话(状态机 + 回调订阅)。 */\nexport class QrLoginSession {\n private readonly loginService: LoginServiceLike;\n private state: LoginState = \"idle\";\n private listenerId: number | null = null;\n private self: SelfInfo | null = null;\n private readonly qrCodeHandlers = new Set<(qr: QrCodeData) => void>();\n private readonly stateHandlers = new Set<(state: LoginState) => void>();\n /** 超时定时器(refresh 后启动,登录成功/失败/stop 清理)。 */\n private timeoutTimer: ReturnType<typeof setTimeout> | null = null;\n /** 失败原因(超时等,failed 态透出给上层提示文案)。 */\n private failureReasonValue: string | null = null;\n /** 超时上限(毫秒,默认 QR_LOGIN_TIMEOUT_MS)。 */\n private readonly timeoutMs: number;\n\n constructor(loginService: unknown, options: { timeoutMs?: number } = {}) {\n const svc = loginService as LoginServiceLike | null;\n if (svc === null || typeof svc.addKernelLoginListener !== \"function\") {\n throw kernelError(\"loginService 无效(缺 addKernelLoginListener)\", \"INVALID_STATE\");\n }\n this.loginService = svc;\n this.timeoutMs = options.timeoutMs ?? QR_LOGIN_TIMEOUT_MS;\n }\n\n /** 当前状态。 */\n get currentState(): LoginState {\n return this.state;\n }\n\n /** selfInfo(登录成功后有值)。 */\n get selfInfo(): SelfInfo | null {\n return this.self;\n }\n\n /** 失败原因(超时等;failed 态透出给上层提示文案)。 */\n get failureReason(): string | null {\n return this.failureReasonValue;\n }\n\n /** 订阅二维码(png base64 + url)。返回退订函数。 */\n onQrCode(cb: (qr: QrCodeData) => void): () => void {\n this.qrCodeHandlers.add(cb);\n return () => this.qrCodeHandlers.delete(cb);\n }\n\n /** 订阅状态变化。返回退订函数。 */\n onStateChange(cb: (state: LoginState) => void): () => void {\n this.stateHandlers.add(cb);\n return () => this.stateHandlers.delete(cb);\n }\n\n /** 开始登录:注册监听 → connect → 快速登录(指定账号)或触发二维码。 */\n start(opts: { quickUin?: string } = {}): void {\n if (this.state !== \"idle\") {\n return;\n }\n this.listenerId = this.loginService.addKernelLoginListener(this.buildListener());\n this.loginService.connect();\n if (opts.quickUin !== undefined && opts.quickUin !== \"\") {\n // T3:快速登录可能永久挂起(服务端已有同账号会话)——先启动超时计时,\n // 挂起 → 超时 failed。此前挂起时连兜底计时都不启动(定时器只在 refresh\n // 里重启),ready 态软重登完全无兜底。\n this.restartTimeout();\n this.loginService\n .quickLoginWithUin(opts.quickUin)\n .then((result) => {\n // ⚠️ wrapper 契约(attemptQuickLogin 已实证):快速登录失败是\n // resolve 带 loginErrorInfo.errMsg,**不是 reject**——只挂 .catch\n // 会永不触发 → 无凭据环境(WSL 扫码)永不 refresh → 二维码永不\n // 产生,完全静默阻塞(2026-08-13 WSL 扫码卡死根因)。\n if (result.loginErrorInfo.errMsg !== \"\") {\n this.refreshAfterQuickLogin();\n }\n })\n .catch(() => {\n // reject(异常路径)同样回退二维码\n this.refreshAfterQuickLogin();\n });\n } else {\n this.refresh();\n }\n }\n\n /**\n * 快速登录失败回退二维码。仅 idle 态生效:超时 failed 后悬挂 promise 的\n * 迟到结果(含登录成功后 stop 之后的迟到 settle)不得复活会话/重复出码。\n */\n private refreshAfterQuickLogin(): void {\n if (this.state === \"idle\") {\n this.refresh();\n }\n }\n\n /** 刷新二维码(手动 / 过期自动)。每次出码重启超时计时。 */\n refresh(): void {\n this.failureReasonValue = null;\n this.setState(\"waiting_scan\");\n this.loginService.getQRCodePicture();\n this.restartTimeout();\n }\n\n /** 构建登录监听(普通 JS 对象,NAPI 反射)。 */\n private buildListener(): IKernelLoginListener {\n const listener = createLoginListener();\n listener.onQRCodeGetPicture = ({ pngBase64QrcodeData, qrcodeUrl }) => {\n const realBase64 = pngBase64QrcodeData.replace(DATA_URI_PREFIX_RE, \"\");\n this.emitQrCode({ pngBase64: realBase64, qrcodeUrl });\n };\n listener.onQRCodeLoginPollingStarted = () => {\n this.setState(\"waiting_scan\");\n };\n listener.onQRCodeSessionUserScaned = () => {\n this.setState(\"scanned\");\n };\n listener.onQRCodeLoginSucceed = (result) => {\n this.clearTimeoutTimer();\n this.self = { uin: result.uin, uid: result.uid, nick: result.nick ?? \"\" };\n this.setState(\"logged_in\");\n };\n listener.onQRCodeSessionFailed = (errType, errCode) => {\n if (isQrCodeExpired(errType, errCode)) {\n // 二维码过期 → 自动刷新(2026-08-13 用户拍板保留自动刷新 + 手动刷新按钮)\n this.refresh();\n return;\n }\n this.clearTimeoutTimer();\n this.failureReasonValue = null;\n this.setState(\"failed\");\n };\n listener.onLoginFailed = () => {\n this.clearTimeoutTimer();\n this.failureReasonValue = null;\n this.setState(\"failed\");\n };\n return listener;\n }\n\n /** 停止:注销监听(登录成功后调用,防重复回调)。 */\n stop(): void {\n this.clearTimeoutTimer();\n if (this.listenerId !== null) {\n this.loginService.removeKernelLoginListener(this.listenerId);\n this.listenerId = null;\n }\n }\n\n /** 重启超时计时器:超时未登录 → failed + 超时提示。 */\n private restartTimeout(): void {\n this.clearTimeoutTimer();\n this.timeoutTimer = setTimeout(() => {\n this.timeoutTimer = null;\n this.failureReasonValue = QR_LOGIN_TIMEOUT_MESSAGE;\n this.setState(\"failed\");\n }, this.timeoutMs);\n }\n\n /** 清理超时计时器(幂等)。 */\n private clearTimeoutTimer(): void {\n if (this.timeoutTimer !== null) {\n clearTimeout(this.timeoutTimer);\n this.timeoutTimer = null;\n }\n }\n\n private setState(next: LoginState): void {\n this.state = next;\n for (const cb of this.stateHandlers) {\n cb(next);\n }\n }\n\n private emitQrCode(qr: QrCodeData): void {\n for (const cb of this.qrCodeHandlers) {\n cb(qr);\n }\n }\n}\n","/**\n * QQ 数据根路径解析(P2-1,2026-08-06)\n *\n * 从 `getNTUserDataInfoConfig()` 的返回值中提取 QQ 真实数据根路径:\n * worker(utilityProcess)模式下 loginService 是 new 的,commonPath 必须指向\n * QQ 真实数据路径才能读到历史账号(cli 的 `.napuketto\\default` 读不到,\n * getLoginList 空,P2-1 实测)。\n *\n * 返回值形态可能是 JSON 字符串(对象/数组)或纯路径字符串,此处做宽容解析。\n */\n\nimport { join } from \"node:path\";\n\n/**\n * QQ 全局数据目录(commonPath / desktopGlobalPath = 数据根/nt_qq/global,\n * HANDOVER-V6 自建宿主三要素之三,p0-kernel-flow 实证)。\n *\n * 防御:已含 nt_qq/global 后缀(worker 环境下 getNTUserDataInfoConfig 可能\n * 返回完整路径)则不重复拼接,避免 `nt_qq/global/nt_qq/global` 错位。\n */\nexport function resolveQqGlobalPath(root: string): string {\n const norm = root.replace(/\\\\/g, \"/\");\n if (norm.endsWith(\"/nt_qq/global\")) {\n return root;\n }\n if (norm.endsWith(\"/nt_qq\")) {\n return join(root, \"global\");\n }\n return join(root, \"nt_qq\", \"global\");\n}\n\n/** 从 getNTUserDataInfoConfig 返回值提取数据根(JSON 字符串解析或纯路径)。 */\nexport function extractDataRoot(raw: string): string | null {\n if (raw.startsWith(\"{\") || raw.startsWith(\"[\")) {\n try {\n return findPathInValue(JSON.parse(raw));\n } catch {\n return null;\n }\n }\n return raw;\n}\n\n/** 递归在对象/数组里找含 QQ 数据路径特征的字符串值。 */\nfunction findPathInValue(value: unknown): string | null {\n if (typeof value === \"string\") {\n return isQqDataPath(value) ? value : null;\n }\n if (Array.isArray(value)) {\n return findInArray(value);\n }\n if (typeof value === \"object\" && value !== null) {\n return findInObject(value);\n }\n return null;\n}\n\n/** 判断字符串是否含 QQ 数据路径特征。 */\nfunction isQqDataPath(value: string): boolean {\n return value.includes(\"Tencent Files\") || value.includes(\"nt_qq\");\n}\n\n/** 在数组里递归查找。 */\nfunction findInArray(value: unknown[]): string | null {\n for (const item of value) {\n const found = findPathInValue(item);\n if (found !== null) {\n return found;\n }\n }\n return null;\n}\n\n/** 在对象值里递归查找。 */\nfunction findInObject(value: object): string | null {\n for (const v of Object.values(value)) {\n const found = findPathInValue(v);\n if (found !== null) {\n return found;\n }\n }\n return null;\n}\n","/**\n * wrapper 交互配置装配(纯函数,无状态)\n *\n * 生成 QQ wrapper 交互所需的三份配置:\n * - buildEngineConfig → engine.initWithDeskTopConfig(appid / qua / 版本 / 路径)\n * - buildLoginConfig → loginService.initConfig\n * - buildSessionConfig → session.init(登录成功后调用)\n *\n * 与 lifecycle.ts(流程编排)解耦:本模块只回答「配置长什么样」,不管流程怎么走。\n * 字段为 wrapper.node 外部契约(appid/qua/版本),运行时实测确认,自研描述。\n * appid 唯一来源是 major.node 动态解析(每版本不同);解析失败显式抛 KernelError,\n * 不再静默回退过期 appid(2026-08 硬编码审计:537237765 是 9.9.31 的 appid,该版本\n * 登录服务已被腾讯下线,静默用它会导致扫码「请下载最新版」且极难排查)。\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { hostname, type as osType, version as osVersionString, platform, release } from \"node:os\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n DeviceInfo,\n EnginInitDesktopConfig,\n WrapperSessionInitConfig,\n} from \"../types/index.js\";\nimport { PlatformType as PlatformTypeValue, VendorType } from \"../types/index.js\";\n\n/** app_type=4:桌面端应用类型(engine.initWithDeskTopConfig 契约;移动端为其它值)。 */\nconst APP_TYPE_DESKTOP = 4;\n\n/**\n * 缩略图生成参数(thumb_config,engine 契约):\n * maxSide 最长边像素上限 / minSide 最短边像素下限 / longLimit 长图最长短边比例上限 /\n * density 像素密度倍数。\n */\nconst THUMB_CONFIG = {\n maxSide: 324,\n minSide: 48,\n longLimit: 6,\n density: 2,\n} as const;\n\n/**\n * session.init 的 deviceConfig(wrapper 契约,JSON 字符串字面量):\n * appearance.isSplitViewMode 分屏模式开关,msg 空对象为占位。自建宿主无 UI,保持最小可用。\n */\nconst DEVICE_CONFIG_JSON = '{\"appearance\":{\"isSplitViewMode\":true},\"msg\":{}}';\n\n/** localId=2052:zh-CN(简体中文)locale 码(Windows LCID,deviceInfo.localId 契约)。 */\nconst ZH_CN_LOCALE_ID = 2052;\n\n/** 平台名映射:os.platform() → wrapper devType 契约值(不写死具体构建号)。 */\nconst DEV_TYPE_BY_PLATFORM: Record<string, string> = {\n win32: \"Windows\",\n darwin: \"Mac\",\n linux: \"Linux\",\n android: \"Android\",\n freebsd: \"FreeBSD\",\n openbsd: \"OpenBSD\",\n sunos: \"SunOS\",\n aix: \"AIX\",\n};\n\n/**\n * 系统信息运行时探测(wrapper 契约:platVer / osVersion / devType)。\n * 旧实现写死 \"Windows 10.0.22631\"/\"Windows 10 Pro\"——换机即失真(2026-08 硬编码审计整改)。\n * 探测失败时给通用值兜底,但不写死具体 Windows 构建号。\n */\nfunction systemInfo(): { platVer: string; osVersion: string; devType: string } {\n let devType = \"Unknown\";\n let platVer = \"Unknown\";\n let osVersion = \"Unknown\";\n try {\n devType = DEV_TYPE_BY_PLATFORM[platform()] ?? platform();\n // platVer 契约形如 \"Windows 10.0.22631\" = \"<平台名> <内核版本号>\"。\n platVer = `${devType} ${release()}`;\n // osVersion 优先 os.version()(Windows 返回 \"Windows 11 Pro\" 等),空则用 os.type()。\n osVersion = osVersionString() || osType();\n } catch {\n // 探测失败:保留通用兜底值。\n }\n return { platVer, osVersion, devType };\n}\n\n/** 纯数字串(major.node 提取的 appid 判定)。 */\nconst DIGITS_ONLY_RE = /^\\d+$/;\n\n/**\n * 从 major.node 提取 appid(NapCat parseAppidFromMajorV2 的自研等价实现)。\n * major.node 含 `QQAppId/` 标记后跟数字(腾讯工具链产物,实测确认)。\n * 返回 null 表示解析失败(调用方应显式报错,不再回退硬编码 appid)。\n */\nexport function parseAppidFromMajor(majorPath: string): string | null {\n if (!existsSync(majorPath)) {\n return null;\n }\n let buf: Buffer;\n try {\n buf = readFileSync(majorPath);\n } catch {\n return null;\n }\n return scanAppidMarkers(buf);\n}\n\n/** 扫描 buf 中所有 QQAppId/ 标记,返回第一个纯数字串(无则 null)。 */\nfunction scanAppidMarkers(buf: Buffer): string | null {\n const marker = Buffer.from(\"QQAppId/\", \"utf-8\");\n let pos = 0;\n while (pos < buf.length) {\n const idx = buf.indexOf(marker, pos);\n if (idx === -1) {\n return null;\n }\n const str = readMarkerValue(buf, idx + marker.length);\n if (DIGITS_ONLY_RE.test(str)) {\n return str;\n }\n pos = idx + marker.length + str.length + 1;\n }\n return null;\n}\n\n/** 读取标记后的 C 字符串(到首个 0 字节为止)。 */\nfunction readMarkerValue(buf: Buffer, start: number): string {\n let end = start;\n while (end < buf.length && buf[end] !== 0) {\n end += 1;\n }\n return buf.subarray(start, end).toString(\"utf-8\");\n}\n\n/**\n * 解析 appid / qua(自研,参考 NapCat QQBasicInfoWrapper 思路但独立实现):\n * appid 唯一来源 = major.node 提取(实测 9.9.33-51802 = 537376818)。\n * majorPath 缺省或解析失败时抛 KernelError——不再静默回退过期 appid。\n * majorPath 传 wrapper.node 同目录 major.node。\n */\nexport function resolveAppidQua(\n fullVersion: string,\n majorPath?: string,\n): { appid: string; qua: string } {\n const appid = majorPath !== undefined ? parseAppidFromMajor(majorPath) : null;\n if (appid === null) {\n throw kernelError(\n \"无法从 major.node 解析 appid,请确认 wrapper.node/major.node 完整,或更新 qq-releases.json\",\n \"INVALID_STATE\",\n );\n }\n return {\n appid,\n qua: `V1_WIN_NQ_${fullVersion}_${fullVersion.split(\"-\")[1] ?? \"\"}_GW_B`,\n };\n}\n\n/** 生成 engine 桌面配置(wrapper 契约字段)。majorPath 可选(缺失/解析失败会抛错)。 */\nexport function buildEngineConfig(\n fullVersion: string,\n dataPathGlobal: string,\n majorPath?: string,\n): EnginInitDesktopConfig {\n const { osVersion } = systemInfo();\n const { qua } = resolveAppidQua(fullVersion, majorPath);\n return {\n base_path_prefix: \"\",\n platform_type: PlatformTypeValue.KWINDOWS,\n app_type: APP_TYPE_DESKTOP,\n app_version: fullVersion,\n os_version: osVersion,\n use_xlog: true,\n qua,\n global_path_config: {\n desktopGlobalPath: dataPathGlobal,\n },\n thumb_config: THUMB_CONFIG,\n };\n}\n\n/** 生成登录初始化配置(externalVersion: false 与 NapCat 同款,扫码兼容关键)。 */\nexport function buildLoginConfig(\n appid: string,\n fullVersion: string,\n commonPath: string,\n): Record<string, unknown> {\n const { platVer } = systemInfo();\n return {\n machineId: \"\",\n appid,\n platVer,\n commonPath,\n clientVer: fullVersion,\n hostName: hostname(),\n externalVersion: false,\n };\n}\n\n/** buildSessionConfig 参数。 */\nexport interface SessionConfigOptions {\n appid: string;\n fullVersion: string;\n selfUin: string;\n selfUid: string;\n accountPath: string;\n downloadPath: string;\n /** 设备指纹 guid(来自 loginService.getMachineGuid();缺省空串)。 */\n machineGuid?: string;\n}\n\n/** 生成 session 初始化配置(登录成功后调用)。 */\nexport function buildSessionConfig(options: SessionConfigOptions): WrapperSessionInitConfig {\n const { appid, fullVersion, selfUin, selfUid, accountPath, downloadPath, machineGuid } =\n options;\n const { platVer, osVersion, devType } = systemInfo();\n const deviceInfo: DeviceInfo = {\n // 设备指纹 guid:loginService.getMachineGuid()(wrapper 探测确认,无 getMachineId 方法)。\n guid: machineGuid ?? \"\",\n buildVer: fullVersion,\n localId: ZH_CN_LOCALE_ID,\n devName: hostname(),\n devType,\n vendorName: \"\",\n osVer: osVersion,\n vendorOsName: devType,\n setMute: false,\n vendorType: VendorType.KNOSETONIOS,\n };\n return {\n selfUin,\n selfUid,\n desktopPathConfig: {\n account_path: accountPath,\n },\n clientVer: fullVersion,\n a2: \"\",\n d2: \"\",\n d2Key: \"\",\n machineId: \"\",\n platform: PlatformTypeValue.KWINDOWS,\n platVer,\n appid,\n rdeliveryConfig: {\n appKey: \"\",\n systemId: 0,\n appId: \"\",\n logicEnvironment: \"\",\n platform: PlatformTypeValue.KWINDOWS,\n language: \"\",\n sdkVersion: \"\",\n userId: \"\",\n appVersion: \"\",\n osVersion: \"\",\n bundleId: \"\",\n serverUrl: \"\",\n fixedAfterHitKeys: [\"\"],\n },\n defaultFileDownloadPath: downloadPath,\n deviceInfo,\n deviceConfig: DEVICE_CONFIG_JSON,\n };\n}\n\n/**\n * 读取设备指纹 guid(wrapper.node 字符串分析实证,2026-08-12):\n * NodeIKernelLoginService::getMachineGuid(0 参,返回机器 GUID 字符串)。\n * 无 getMachineId 方法——TODO 里的方法名不存在,等价方法是 getMachineGuid。\n * 失败(无该方法 / 调用抛错 / 返回值非字符串)兜底空串。\n */\nexport function readMachineGuid(loginService: unknown): string {\n try {\n const fn = (loginService as Record<string, unknown> | null | undefined)?.[\"getMachineGuid\"];\n if (typeof fn !== \"function\") {\n return \"\";\n }\n const value: unknown = fn.call(loginService);\n return typeof value === \"string\" ? value : \"\";\n } catch {\n return \"\";\n }\n}\n","/**\n * wrapper 引导(NAPI 范式,2026-08-05 重构,替代旧 koffi 方案)\n *\n * 事实(实测):wrapper.node 不是标准 NAPI self-register 模块,只能在 QQ 定制版\n * Electron 主进程里由 preload 注册。@napuketto/loader 注入 hook DLL 后,boot.cjs\n * 在 QQ 主进程内截获 `process.dlopen` 的 `module.exports`,然后调用本模块初始化。\n *\n * 本模块**不再**使用 koffi / vtable / 内存偏移——所有对象由 QQ 的 NAPI 层自动构建。\n * 业务层拿到的都是真实 JS 对象:engine.initWithDeskTopConfig(config, adapter) 等。\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\nimport { kernelError } from \"../infra/index.js\";\nimport type {\n EnginInitDesktopConfig,\n NodeIKernelSessionListener,\n NodeIQQNTStartupSessionWrapper,\n NodeIQQNTWrapperEngine,\n NodeIQQNTWrapperSession,\n NodeIQQNTWrapperSessionCtor,\n WrapperNodeApi,\n WrapperSessionInitConfig,\n} from \"../types/index.js\";\nimport { PlatformType } from \"../types/index.js\";\nimport { extractDataRoot, resolveQqGlobalPath } from \"./qq-data-path.js\";\nimport {\n createSessionListener,\n DependsAdapter,\n DispatcherAdapter,\n GlobalAdapter,\n} from \"./wrapper-adapters.js\";\n\n// ---------------------------------------------------------------\n// startNapuketto 内部辅助(非 export,boot 装配用)\n// ---------------------------------------------------------------\n\n/** 默认 engine 配置(KWINDOWS + 版本号,字段待首个真实联调确认)。 */\nfunction defaultEngineConfig(env: BootEnv, qqDataPath?: string): EnginInitDesktopConfig {\n return {\n base_path_prefix: env.dataDir ?? \"\",\n platform_type: PlatformType.KWINDOWS,\n app_type: 4,\n app_version: env.qqVersion ?? \"\",\n os_version: process.platform,\n use_xlog: false,\n qua: \"\",\n global_path_config: {\n desktopGlobalPath: qqDataPath ?? env.dataDir ?? \"\",\n },\n thumb_config: { maxSide: 324, minSide: 48, longLimit: 6, density: 2 },\n };\n}\n\n/**\n * Electron 进程类型(Node 的 process 类型无此字段)。\n * - \"browser\":QQ 主进程(V1 模式)\n * - \"utility\":utilityProcess Worker(路线 B,2026-08-06)\n */\nexport function electronProcessType(): string | undefined {\n return (process as unknown as { type?: string }).type;\n}\n\n/**\n * 解析 QQ 用户数据根(登录数据目录,如 `C:\\Users\\<user>\\Documents\\Tencent Files`)。\n * 优先 NodeQQNTWrapperUtil.getNTUserDataInfoConfig()(QQ 官方 API),回退默认位置。\n *\n * 背景(2026-08-06 P2-1 实测):worker(utilityProcess)模式下 loginService 是\n * `new NodeIKernelLoginService()` 新建的,其 initConfig 的 commonPath 必须指向\n * QQ 真实数据路径才能读到历史账号——cli 的 `.napuketto\\default` 数据目录读不到\n * (getLoginList 返回空 → 只能 QR 登录)。V1 主进程模式 QQ 自己 initConfig 过\n * 正确路径,无需此解析。\n */\nexport function resolveQqUserDataRoot(exports: WrapperNodeApi): string | null {\n return extractUserDataRoot(exports) ?? join(homedir(), \"Documents\", \"Tencent Files\");\n}\n\n/** 从 QQ 官方 API 提取数据根(不可用返回 null,调用方回退默认)。 */\nfunction extractUserDataRoot(exports: WrapperNodeApi): string | null {\n try {\n const util = exports.NodeQQNTWrapperUtil as unknown as {\n get?: () => { getNTUserDataInfoConfig?: () => unknown };\n } | null;\n const raw = util?.get?.().getNTUserDataInfoConfig?.();\n if (typeof raw === \"string\" && raw.trim() !== \"\") {\n return extractDataRoot(raw.trim());\n }\n } catch {\n // 回退默认位置\n }\n return null;\n}\n\n// ---------------------------------------------------------------\n// 导出区(useExportsLast:export 全部在文件末尾)\n// ---------------------------------------------------------------\n\n/** QQ 版本信息(登录握手用)。 */\nexport interface QQVersionContext {\n fullVersion: string;\n buildVersion: string;\n}\n\n/** 已引导的 wrapper 上下文(运行在 QQ Electron 主进程内)。 */\nexport interface WrapperContext {\n /** wrapper.node 的 NAPI 顶层导出。 */\n exports: WrapperNodeApi;\n /** engine 单例(get() 已调用)。 */\n engine: NodeIQQNTWrapperEngine;\n /** 版本信息(供登录握手等使用)。 */\n versionInfo: QQVersionContext;\n /** 会话(QQ 拦截捕获 / createSession 后填充)。 */\n session: NodeIQQNTWrapperSession | null;\n /** 启动会话包装(NapCat 同款:StartupSessionWrapper.create(),start() 替代 startNT)。 */\n startupSession: NodeIQQNTStartupSessionWrapper | null;\n /** QQ 的 loginService 实例(拦截 new 捕获,可为 null)。 */\n loginService: unknown;\n}\n\n/**\n * 从 loader 截获的 NAPI exports 创建 wrapper 上下文。\n * 在 QQ 主进程内调用(boot.cjs → kernel 入口)。\n */\nexport function createWrapper(\n exports: WrapperNodeApi,\n versionInfo: QQVersionContext,\n): WrapperContext {\n const engineMaybe = exports.NodeIQQNTWrapperEngine as unknown as {\n get?: () => NodeIQQNTWrapperEngine;\n } | null;\n if (engineMaybe === null || typeof engineMaybe.get !== \"function\") {\n throw kernelError(\n \"wrapper.node exports 无效(缺少 NodeIQQNTWrapperEngine)\",\n \"INVALID_PARAM\",\n );\n }\n const engine = engineMaybe.get();\n if (!engine || typeof engine.initWithDeskTopConfig !== \"function\") {\n throw kernelError(\"NodeIQQNTWrapperEngine.get() 返回对象无效\", \"INVALID_PARAM\");\n }\n return {\n exports,\n engine,\n versionInfo,\n session: null,\n startupSession: null,\n loginService: null,\n };\n}\n\n/** engine 初始化(wrapper 契约:先 engine 后 session)。config 为普通 JS 对象。 */\nexport function initEngine(ctx: WrapperContext, config: EnginInitDesktopConfig): void {\n ctx.engine.initWithDeskTopConfig(config, new GlobalAdapter());\n}\n\n/**\n * 创建会话(2026-08-06 P2-0 实测修正——NapCat 方式自研等价):\n * 1. StartupSessionWrapper.create()(启动会话包装)\n * 2. getNTWrapperSession(\"nt_1\")(QQ 主 session,带完整 cpp_impl)\n * 3. S.create() 回退\n *\n * 背景:`new NodeIQQNTWrapperSession()` 构造对象缺 cpp_impl,session.init 断言\n * \"implementation of IQQNTWrapperSession is not valid\"(P2-0 实测确认)。\n * NapCat 用 StartupSessionWrapper.create() + getNTWrapperSession('nt_1') 解决——\n * 返回带完整实现的实例。worker(utilityProcess)继承 QQ env 后同样有效。\n */\nexport function createSession(ctx: WrapperContext): NodeIQQNTWrapperSession {\n const S = ctx.exports.NodeIQQNTWrapperSession;\n const session =\n createStartupSession(ctx) ?? getNTWrapperSession(S) ?? createNewSession(S) ?? new S();\n ctx.session = session;\n return session;\n}\n\n/** 步骤 1:StartupSessionWrapper.create()(建立启动 session,失败忽略)。 */\nfunction createStartupSession(ctx: WrapperContext): NodeIQQNTWrapperSession | null {\n const Ssw = ctx.exports.NodeIQQNTStartupSessionWrapper as unknown as\n | {\n create?: () => NodeIQQNTStartupSessionWrapper;\n }\n | undefined;\n if (Ssw === undefined || typeof Ssw.create !== \"function\") {\n return null;\n }\n try {\n ctx.startupSession = Ssw.create();\n } catch {\n // 可选,失败继续\n }\n return null;\n}\n\n/** 步骤 2:getNTWrapperSession(\"nt_1\")(QQ 主 session,带 cpp_impl)。 */\nfunction getNTWrapperSession(S: NodeIQQNTWrapperSessionCtor): NodeIQQNTWrapperSession | null {\n if (typeof S.getNTWrapperSession !== \"function\") {\n return null;\n }\n try {\n return S.getNTWrapperSession(\"nt_1\");\n } catch {\n return null; // 回退\n }\n}\n\n/** 步骤 3:S.create() 回退。 */\nfunction createNewSession(S: NodeIQQNTWrapperSessionCtor): NodeIQQNTWrapperSession | null {\n if (typeof S.create !== \"function\") {\n return null;\n }\n try {\n return S.create();\n } catch {\n return null;\n }\n}\n\n/** session 初始化(4 参全为 JS 对象,NAPI 自动转换)。 */\nexport function initSession(\n ctx: WrapperContext,\n config: WrapperSessionInitConfig,\n listener: NodeIKernelSessionListener,\n): void {\n const session = ctx.session ?? createSession(ctx);\n session.init(config, new DependsAdapter(), new DispatcherAdapter(), listener);\n}\n\n/**\n * 启动会话(有 startupSession 用 start(),否则 startNT(0))。\n *\n * ⚠️ 2026-08-07 V9 修正:单纯 start() 只在「已 init」的 session 上有效。\n * 自建宿主必须走 initAndStartSession(先 session.init 再 startupSession.start())——\n * 先 start 后 init 业务 service 不挂载(HANDOVER-V9 实测)。本函数保留给\n * 已 init 的 session 补启动用;新路径请用 initAndStartSession。\n */\nexport function startSession(ctx: WrapperContext): void {\n const { session, startupSession } = ctx;\n if (session === null) {\n throw kernelError(\"session 未创建,无法启动\", \"INVALID_STATE\");\n }\n if (startupSession !== null && typeof startupSession.start === \"function\") {\n startupSession.start();\n return;\n }\n session.startNT(0);\n}\n\n/**\n * boot 装配入口:createWrapper → initEngine → session。\n *\n * **session 来源(2026-08-05 修正)**:\n * 1. qqSession(boot.cjs 拦截 `new` 窃取的 QQ 已 init session)——尚未落地\n * 2. createSession(`new NodeIQQNTWrapperSession()`)——实测可用,默认路径\n * (getMainSession 实测为空壳:service 全 null + 缺 startNT,仅 probe 探测参考)\n *\n * 由 kernel NapukettoCore(core.ts)装配调用;loader 自建宿主 bootstrap 也直接调用作回退。\n * 返回 WrapperContext;失败抛 KernelError。\n */\nexport function startNapuketto(options: StartNapukettoOptions): WrapperContext {\n const { env, engineConfig, sessionConfig, wrapperExports, qqSession, qqLoginService } = options;\n const versionInfo: QQVersionContext = {\n fullVersion: env?.qqVersion ?? \"\",\n buildVersion: env?.qqVersion ?? \"\",\n };\n const ctx = createWrapper(wrapperExports, versionInfo);\n const hasCapturedSession = captureSession(ctx, qqSession);\n // session 创建时机:**自建宿主(标准 node)必须在 engine init 之前创建**——\n // p0-kernel-flow 决定性顺序(HANDOVER-V6):SSW.create + getNTWrapperSession(\"nt_1\")\n // 在 engine.initWithDeskTopConfig 前;engine 先建 session 后建 → 登录后\n // session.init 的 onOpentelemetryInit 不触发(2026-08-07 自建宿主实测)。\n // 路线 B(utility)与 QQ 主进程(browser)保持 engine 先建原顺序(已验证)。\n const isSelfHost = electronProcessType() === undefined;\n prepareSession(ctx, hasCapturedSession, isSelfHost);\n initEngine(ctx, resolveEngineConfig(ctx, env, engineConfig));\n resolveLoginService(ctx, qqLoginService);\n // session:路线 B(utility)与 QQ 主进程(browser)在 engine 后创建;\n // 自建宿主已在 engine 前创建(isSelfHost 分支),此处跳过避免重复。\n finishSession(ctx, hasCapturedSession, isSelfHost, sessionConfig);\n return ctx;\n}\n\n/** engine 前 session 准备(自建宿主必须先建,顺序决定性)。 */\nfunction prepareSession(ctx: WrapperContext, hasCaptured: boolean, isSelfHost: boolean): void {\n if (!hasCaptured && isSelfHost) {\n createSession(ctx);\n }\n}\n\n/** engine 后收尾:非自建宿主补建 session;配置了 sessionConfig 则 init+start。 */\nfunction finishSession(\n ctx: WrapperContext,\n hasCaptured: boolean,\n isSelfHost: boolean,\n sessionConfig: WrapperSessionInitConfig | undefined,\n): void {\n if (!hasCaptured && !isSelfHost) {\n createSession(ctx);\n }\n if (sessionConfig !== undefined) {\n initSession(ctx, sessionConfig, createSessionListener());\n startSession(ctx);\n }\n}\n\n/** engine 配置:显式覆盖优先,否则默认(含 QQ 数据根 global 路径解析)。 */\nfunction resolveEngineConfig(\n ctx: WrapperContext,\n env: BootEnv | undefined,\n engineConfig: EnginInitDesktopConfig | undefined,\n): EnginInitDesktopConfig {\n if (engineConfig !== undefined) {\n return engineConfig;\n }\n return defaultEngineConfig(env ?? {}, resolveBootQqGlobalPath(ctx));\n}\n\n/** 捕获 QQ 已 init 的 session(boot.cjs 拦截 `new` 窃取),返回是否捕获成功。 */\nfunction captureSession(\n ctx: WrapperContext,\n qqSession: NodeIQQNTWrapperSession | null | undefined,\n): boolean {\n if (qqSession === undefined || qqSession === null) {\n return false;\n }\n ctx.session = qqSession;\n return true;\n}\n\n/**\n * 解析 QQ 真实数据目录的 global 路径(desktopGlobalPath 三要素之三)。\n * worker(utilityProcess)模式 + 自建宿主(标准 node)下必须指向 QQ 真实\n * 数据目录(数据根/nt_qq/global,HANDOVER-V6),否则登录数据读写错位\n * (P2-1 实测 2026-08-06;getLoginList 空)。QQ 主进程(V1)QQ 自己已配置\n * engine,无需解析。\n */\nfunction resolveBootQqGlobalPath(ctx: WrapperContext): string | undefined {\n if (electronProcessType() === \"browser\") {\n return undefined;\n }\n const root = resolveQqUserDataRoot(ctx.exports);\n if (root === null) {\n return undefined;\n }\n return resolveQqGlobalPath(root);\n}\n\n/**\n * 解析 loginService:优先捕获实例,其次单例 get()(自建宿主/worker 下 QQ 环境\n * 创建的实例才完整——`new` 自建实例读不到登录列表(getLoginList 空),\n * HANDOVER-V6 p0-login3 实证),最后 new 回退。\n */\nfunction resolveLoginService(ctx: WrapperContext, qqLoginService: unknown): void {\n if (qqLoginService !== undefined && qqLoginService !== null) {\n ctx.loginService = qqLoginService as WrapperContext[\"loginService\"];\n return;\n }\n ctx.loginService = getOrCreateLoginService(ctx.exports);\n}\n\n/** 单例 get() 优先,缺失回退 new(都失败返回 null)。 */\nfunction getOrCreateLoginService(exports: WrapperNodeApi): unknown {\n try {\n const loginSvcCtor = exports.NodeIKernelLoginService as unknown as {\n get?: () => unknown;\n } | null;\n if (loginSvcCtor !== null && typeof loginSvcCtor.get === \"function\") {\n return loginSvcCtor.get();\n }\n return new exports.NodeIKernelLoginService();\n } catch {\n return null;\n }\n}\n\n/** startNapuketto 的引导环境(由 loader launcher 注入环境变量,boot.cjs 透传)。 */\nexport interface BootEnv {\n /** QQ 版本(如 \"9.9.31-49919\")。 */\n qqVersion?: string;\n /** 用户数据目录(cfgDir 的父级,用于 desktopPathConfig)。 */\n dataDir?: string;\n /** 账号 uin(可选,提供则一步 init+start)。 */\n selfUin?: string;\n /** 账号 uid(可选)。 */\n selfUid?: string;\n}\n\n/** startNapuketto 选项。 */\nexport interface StartNapukettoOptions {\n /** 截获的 wrapper.node NAPI exports(boot.cjs 传入)。 */\n wrapperExports: WrapperNodeApi;\n /** 引导环境(版本/数据目录/账号)。 */\n env?: BootEnv;\n /** 覆盖 engine 配置(联调用)。 */\n engineConfig?: EnginInitDesktopConfig;\n /** 覆盖 session 配置(联调用,提供则自动 init+start)。 */\n sessionConfig?: WrapperSessionInitConfig;\n /** QQ 已 init 的 session(boot.cjs 拦截 new 捕获)。 */\n qqSession?: NodeIQQNTWrapperSession | null;\n /** QQ 的 loginService 实例(boot.cjs 拦截 new 捕获)。 */\n qqLoginService?: unknown;\n}\n","/**\n * NapukettoCore:装配 + 启动(§9 第 6 项,2026-08-05 实现)\n *\n * 装配:paths(建目录)→ logger → CoreContext。\n * 启动:attachWrapper(wrapperExports → startNapuketto)→ login(快速登录 → session.init)。\n *\n * 典型用法(boot.cjs 截获 exports 后):\n * const core = await NapukettoCore.create({ dataRoot, account });\n * core.attachWrapper(wrapperExports, { qqVersion, dataDir });\n * // appid 由 resolveAppidQua 从 major.node 动态解析(版本绑定,不写死)\n * await core.login({ appid });\n *\n * 与手动拼 startNapuketto/quickLogin/initAndStartSession 等价,但把装配状态\n * (ctx)集中管理,协议层直接消费 core.ctx。\n */\n\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\nimport { type CoreContext, createCoreContext } from \"./context.js\";\nimport {\n createLogger,\n kernelError,\n type LogFormat,\n type LogLevel,\n type PathOptions,\n PathWrapper,\n type TimestampMode,\n} from \"./infra/index.js\";\nimport {\n type LoginResult,\n type LoginState,\n type QrCodeData,\n QrLoginSession,\n type QuickLoginOptions,\n quickLogin,\n type SelfInfo,\n} from \"./login/index.js\";\nimport type { NodeIQQNTWrapperSession, WrapperNodeApi } from \"./types/index.js\";\nimport { resolveQqGlobalPath } from \"./wrapper/qq-data-path.js\";\nimport { buildLoginConfig } from \"./wrapper/wrapper-config.js\";\nimport {\n type BootEnv,\n electronProcessType,\n resolveQqUserDataRoot,\n startNapuketto,\n type WrapperContext,\n} from \"./wrapper/wrapper-loader.js\";\n\nexport interface NapukettoCoreOptions {\n /** 数据根 / 账号(透传给 PathWrapper,见 ADR-016)。 */\n paths?: PathOptions;\n /** 日志级别,默认 info。 */\n logLevel?: LogLevel | undefined;\n /** 文件日志路径;不传则默认 `logs/napuketto.log`(父目录自动创建)。 */\n logFile?: string | undefined;\n /** 文件目的地级别(可低于控制台级别;缺省同 logLevel)。 */\n logFileLevel?: LogLevel | undefined;\n /** 是否输出 console,默认 true。 */\n consoleLog?: boolean | undefined;\n /** 控制台格式(缺省按 TTY 自动:TTY pretty / pipe single-line)。 */\n logFormat?: LogFormat | undefined;\n /** 时间戳渲染模式,默认 local。 */\n logTimestamp?: TimestampMode | undefined;\n /** 控制台颜色(缺省自动)。 */\n logColorize?: boolean | undefined;\n /** 账号标注(记录结构化 account 字段;快速登录目标账号,登录前已知时传入)。 */\n account?: string | undefined;\n}\n\n/** 登录进度(IPC 子进程模式经 onLoginProgress 转发;koishi 插件展示二维码/状态用)。 */\nexport interface LoginProgress {\n /** 登录状态(QR 状态机)。 */\n state: LoginState;\n /** 二维码数据(state=waiting_scan 时有)。 */\n qr?: QrCodeData;\n /** 登录成功 selfInfo(state=logged_in 时有)。 */\n selfInfo?: SelfInfo;\n /** 失败原因(state=failed 时有;如「登录超时,请刷新页面重试」)。 */\n message?: string;\n}\n\n/** login 参数。 */\nexport interface CoreLoginOptions {\n /** appid(登录握手,由 resolveAppidQua 从 major.node 动态解析,不写死)。 */\n appid: string;\n /** session.init 超时(毫秒),默认 15s。 */\n initTimeoutMs?: number;\n /** 指定快速登录账号(缺省遍历历史列表)。 */\n quickUin?: string;\n /**\n * 单步快速登录超时(毫秒,T3 软重登挂起兜底),默认 20s。服务端已有同账号\n * 会话时底层 quickLoginWithUin/getLoginList 永久挂起,超时按登录失败抛出\n * (qrFallback 可接)。底层 promise 无法取消:超时后悬挂丢弃。\n */\n quickLoginTimeoutMs?: number;\n /** 快速登录失败时回退 QR 登录(二维码写缓存目录),默认 false。 */\n qrFallback?: boolean;\n /** 强制扫码:跳过快速登录,直接走 QR 登录(koishi 强制扫码 / 切账号)。默认 false。 */\n qrOnly?: boolean;\n /** QR 二维码图片保存路径(默认缓存目录 qrcode.png)。 */\n qrCodePath?: string;\n /** 登录进度回调(QR 阶段:二维码数据 + 状态变化)。子进程 IPC 模式转发用。 */\n onLoginProgress?: (progress: LoginProgress) => void;\n}\n\n/**\n * 核心装配器:持有 CoreContext,负责启动/停止编排。\n * 无全局单例(ADR-015 推论)——每账号每进程实例化一份。\n */\nexport class NapukettoCore {\n /** 装配根(协议层 / apis / cache 的消费入口)。 */\n readonly ctx: CoreContext;\n\n /** QR 登录会话句柄(登录期间非空;登录终态后清空)。refreshQr() 据此可达。 */\n private qrSession: QrLoginSession | null = null;\n\n /** 由 create() 装配,不直接 new。 */\n private constructor(ctx: CoreContext) {\n this.ctx = ctx;\n }\n\n /**\n * 装配:paths(建目录)→ logger → CoreContext。\n * 不加载 wrapper / 不登录——启动阶段由 attachWrapper + login 完成。\n */\n static create(opts: NapukettoCoreOptions = {}): NapukettoCore {\n const paths = new PathWrapper(opts.paths);\n paths.ensure();\n // base.name 用 pino 保留字段(logger name):pino-pretty 渲染为 (kernel/pid)\n // 元数据头,不占属性行;cli/loader 同款,三来源可区分。account 未知时\n // 序列化层自然丢弃 undefined,无需条件展开。\n const logger = createLogger({\n level: opts.logLevel ?? \"info\",\n console: opts.consoleLog ?? true,\n file: opts.logFile ?? paths.file(\"logs\", \"napuketto.log\"),\n fileLevel: opts.logFileLevel,\n format: opts.logFormat,\n timestamp: opts.logTimestamp,\n colorize: opts.logColorize,\n base: { name: \"kernel\", account: opts.account },\n });\n return new NapukettoCore(createCoreContext({ logger, paths }));\n }\n\n /**\n * 装配 wrapper:wrapperExports → startNapuketto(engine.init + session 创建),\n * 结果挂到 ctx.wrapper。在 QQ 主进程内调用(boot.cjs 截获 exports 后)。\n * captured:boot.cjs 拦截 `new` 捕获的 QQ 实例(session/loginService,优先使用)。\n */\n attachWrapper(\n wrapperExports: WrapperNodeApi,\n env?: BootEnv,\n captured?: { qqSession?: NodeIQQNTWrapperSession | null; qqLoginService?: unknown },\n ): WrapperContext {\n let wrapper: WrapperContext;\n if (env === undefined) {\n wrapper = startNapuketto({ wrapperExports, ...captured });\n } else {\n wrapper = startNapuketto({ wrapperExports, env, ...captured });\n }\n this.ctx.wrapper = wrapper;\n this.ctx.logger.info({ version: env?.qqVersion }, \"wrapper 装配完成(engine + session)\");\n return wrapper;\n }\n\n /**\n * 登录 + session 初始化(wrapper 契约,2026-08-05 实测确认):\n * loginService.initConfig → 登录(快速/QR)→ **登录成功后** create session + init\n * → 等 onOpentelemetryInit(is_init) 完成信号。成功后填 ctx.login。\n *\n * 关键(实测):登录前 create()/init 均断言失败(session 未 init / 干扰 QQ);\n * 登录后 QQ 自己 init 并触发 listener.onOpentelemetryInit——我们只需在登录后\n * create + init 并监听该信号。\n */\n async login(opts: CoreLoginOptions): Promise<LoginResult> {\n const { wrapper } = this.ctx;\n if (wrapper === null) {\n throw kernelError(\"wrapper 未装配,无法登录\", \"INVALID_STATE\");\n }\n\n // 1. loginService.initConfig(wrapper 流程:addKernelLoginListener 前)\n this.initLoginConfig(wrapper, opts.appid);\n\n // 强制扫码(qrOnly):跳过快速登录,直接 QR 登录(koishi 强制扫码 / 切账号)。\n if (opts.qrOnly === true) {\n const loginResult = await this.loginByQr(opts);\n this.ctx.login = loginResult;\n return loginResult;\n }\n\n // 2. 登录:快速登录(优先)→ QR 回退。登录成功前不碰 session。\n const loginResult = await this.loginQuickFirst(wrapper, opts);\n\n // 3. 登录成功即返回。QQ 登录后重建 session(Proxy 捕获新实例),由 boot.cjs\n // 登录成功后 setSession 替换,再自行等待 session 就绪(waitSessionReady)。\n // 不在 login 内等待——替换发生在 login 返回之后,这里等不到。\n this.ctx.login = loginResult;\n return loginResult;\n }\n\n /** 快速登录优先,失败按 qrFallback 决定是否回退 QR(QR 应直接出码,见 loginByQr 注)。 */\n private async loginQuickFirst(\n wrapper: WrapperContext,\n opts: CoreLoginOptions,\n ): Promise<LoginResult> {\n try {\n const result = await quickLogin(wrapper, buildQuickLoginOptions(opts));\n this.ctx.logger.info({ uin: result.uin, uid: result.uid }, \"快速登录成功\");\n return result;\n } catch (err) {\n if (opts.qrFallback !== true) {\n throw err;\n }\n return await this.loginByQr(opts);\n }\n }\n\n /**\n * loginService.initConfig(worker/自建宿主需指定 QQ 真实数据路径)。\n * worker(utilityProcess)模式 + 自建宿主(标准 node)下 loginService 是\n * new 的,commonPath 必须指向 QQ 真实数据目录(数据根/nt_qq/global,\n * HANDOVER-V6 三要素之三)才能读到历史账号——cli 的 `.napuketto\\default`\n * 读不到(getLoginList 空,P2-1 实测 2026-08-06)。\n * QQ 主进程(V1)模式 QQ 自己 initConfig 过正确路径,无需解析。\n */\n private initLoginConfig(wrapper: WrapperContext, appid: string): void {\n const loginService = wrapper.loginService as {\n initConfig?: (config: unknown) => void;\n } | null;\n if (loginService === null || typeof loginService.initConfig !== \"function\") {\n this.ctx.logger.warn(\"loginService 不可用,跳过 initConfig\");\n return;\n }\n const commonPath = resolveCommonPath(this.ctx, wrapper);\n const loginCfg = buildLoginConfig(appid, wrapper.versionInfo.fullVersion, commonPath);\n loginService.initConfig(loginCfg);\n this.ctx.logger.info({ commonPath }, \"loginService.initConfig OK\");\n }\n\n /**\n * 替换当前 session(登录后 Proxy 捕获的 QQ 新实例替换自己 create 的无效实例)。\n * 在 QQ 主进程内由 boot.cjs 调用(登录成功后)。\n */\n setSession(session: NodeIQQNTWrapperSession): void {\n if (this.ctx.wrapper === null) {\n throw kernelError(\"wrapper 未装配,无法设置 session\", \"INVALID_STATE\");\n }\n this.ctx.wrapper.session = session;\n }\n\n /**\n * 手动刷新二维码(QR 登录期间可用)。返回是否成功触发(false = 当前不在扫码态)。\n * koishi 前端「刷新二维码」按钮经 loader IPC `login.refreshQr` 动作直达。\n */\n refreshQr(): boolean {\n if (this.qrSession === null) {\n return false;\n }\n this.qrSession.refresh();\n return true;\n }\n\n /** QR 登录:QrLoginSession 状态机 + 二维码写缓存目录(无 UI,cli 可打印路径)。 */\n private async loginByQr(opts: CoreLoginOptions): Promise<LoginResult> {\n const { wrapper } = this.ctx;\n if (wrapper === null || wrapper.loginService === null) {\n throw kernelError(\"wrapper/loginService 不可用,无法 QR 登录\", \"INVALID_STATE\");\n }\n const session = new QrLoginSession(wrapper.loginService);\n // 暴露句柄:登录期间外部可经 refreshQr() 手动刷新二维码(koishi 前端刷新按钮)。\n this.qrSession = session;\n const qrPath = opts.qrCodePath ?? this.ctx.paths.file(\"cache\", \"qrcode.png\");\n this.subscribeQrProgress(session, opts, qrPath);\n\n try {\n // 启动 QR 登录(注册监听 → connect → getQRCodePicture)\n // ⚠️ 不传 quickUin(2026-08-13):QR 回退路径下快速登录已在 core.login\n // 失败过一次,这里再传会导致 QrLoginSession 二次快速登录——无凭据环境\n // 直接 resolve 带 errMsg,白等一个周期才出二维码。QR 登录应直接出码。\n session.start();\n\n await waitQrLoggedIn(session);\n const self = requireQrSelfInfo(session);\n return { uin: self.uin, uid: self.uid, nick: self.nick };\n } finally {\n session.stop();\n this.qrSession = null;\n }\n }\n\n /** 订阅 QR 进度:二维码写盘 + 状态机经 onLoginProgress 转发。 */\n private subscribeQrProgress(\n session: QrLoginSession,\n opts: CoreLoginOptions,\n qrPath: string,\n ): void {\n session.onQrCode((qr) => {\n this.ctx.logger.warn(`请扫描二维码登录(保存: ${qrPath} | URL: ${qr.qrcodeUrl})`);\n if (qr.pngBase64 !== \"\") {\n writeQrCodePng(qrPath, qr.pngBase64);\n }\n opts.onLoginProgress?.({ state: \"waiting_scan\", qr });\n });\n session.onStateChange((state) => {\n this.forwardQrState(session, opts, state);\n });\n }\n\n /** QR 状态转发:日志 + onLoginProgress(failed 附 failureReason)。 */\n private forwardQrState(\n session: QrLoginSession,\n opts: CoreLoginOptions,\n state: LoginState,\n ): void {\n this.ctx.logger.info({ state }, \"QR 登录状态\");\n opts.onLoginProgress?.(qrProgressEvent(state, session.failureReason ?? undefined));\n }\n\n /** 停止:日志收尾(资源清理留给 P2 常驻管理)。 */\n stop(): void {\n this.ctx.logger.info(\"NapukettoCore 停止\");\n }\n}\n\n/** 写二维码 png 到磁盘(base64 解码,供 cli/用户扫码)。 */\nfunction writeQrCodePng(filePath: string, pngBase64: string): void {\n try {\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, Buffer.from(pngBase64, \"base64\"));\n } catch {\n // 写失败不阻塞登录(cli 仍可打印 URL)\n }\n}\n\n/** CoreLoginOptions → quickLogin 参数(仅取快速登录相关两键,QR 键不透传)。 */\nfunction buildQuickLoginOptions(opts: CoreLoginOptions): QuickLoginOptions {\n const quickOpts: QuickLoginOptions = {};\n if (opts.quickUin !== undefined) {\n quickOpts.uin = opts.quickUin;\n }\n if (opts.quickLoginTimeoutMs !== undefined) {\n quickOpts.quickLoginTimeoutMs = opts.quickLoginTimeoutMs;\n }\n return quickOpts;\n}\n\n/** QR 登录终态后的 selfInfo 提取(空值按登录失败抛出)。 */\nfunction requireQrSelfInfo(session: QrLoginSession): SelfInfo {\n const self = session.selfInfo;\n if (self === null) {\n throw kernelError(\"QR 登录成功但 selfInfo 为空\", \"INVALID_STATE\");\n }\n return self;\n}\n\n/** QR 进度事件折叠(failed 附 failureReason 为 message,其余不带 message 键)。 */\nfunction qrProgressEvent(state: LoginState, reason: string | undefined): LoginProgress {\n const message = state === \"failed\" ? reason : undefined;\n return { state, ...(message !== undefined ? { message } : {}) };\n}\n\n/** 等待 QR 登录终态:logged_in resolve,failed reject。 */\nfunction waitQrLoggedIn(session: QrLoginSession): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const offState = session.onStateChange((state) => {\n if (state === \"logged_in\") {\n offState();\n resolve();\n } else if (state === \"failed\") {\n offState();\n reject(kernelError(\"QR 登录失败\", \"NOT_LOGIN\"));\n }\n });\n });\n}\n\n/**\n * loginService.initConfig 的 commonPath:\n * worker/自建宿主(非 browser)优先 QQ 真实数据目录(数据根/nt_qq/global),\n * 否则回退账号目录。\n */\nfunction resolveCommonPath(ctx: CoreContext, wrapper: WrapperContext): string {\n if (electronProcessType() !== \"browser\") {\n const root = resolveQqUserDataRoot(wrapper.exports);\n if (root !== null) {\n return resolveQqGlobalPath(root);\n }\n }\n return ctx.paths.accountDir;\n}\n","/**\n * session 解析(2026-08-05,wrapper 机制实测确认后)\n *\n * - getMainSession:startup.create → getSessionIdList → getNTWrapperSession(nt_x)\n * (实测这些返回的 session 是空壳——核心 service 未 init,仅作参考)\n * - 真正可用的 session 是 `new wrapper.NodeIQQNTWrapperSession()` + init(见 lifecycle.ts)\n */\n\nimport type { NodeIQQNTWrapperSession } from \"../types/index.js\";\nimport type { WrapperContext } from \"./wrapper-loader.js\";\n\n/** 从 getSessionIdList 的 Map 提取主 sessionId(nt_ 前缀优先)。 */\nfunction findMainSessionId(ids: Map<unknown, unknown>): string | null {\n return firstStringId(ids, (s) => s.startsWith(\"nt_\")) ?? firstStringId(ids, () => true);\n}\n\n/** 遍历 Map 找第一个满足谓词的字符串 id(值优先,其次键)。 */\nfunction firstStringId(ids: Map<unknown, unknown>, pred: (s: string) => boolean): string | null {\n for (const [k, v] of ids) {\n if (typeof v === \"string\" && pred(v)) {\n return v;\n }\n if (typeof k === \"string\" && pred(k)) {\n return k;\n }\n }\n return null;\n}\n\n/** 通过 getNTWrapperSession 拿主 session(内部辅助)。 */\nfunction resolveMainSession(\n created: { start?: () => void; getSessionIdList?: () => unknown },\n getNTWrapperSession: (id: string) => NodeIQQNTWrapperSession,\n): NodeIQQNTWrapperSession | null {\n created.start?.();\n const ids = typeof created.getSessionIdList === \"function\" ? created.getSessionIdList() : null;\n if (!(ids instanceof Map)) {\n return null;\n }\n const mainId = findMainSessionId(ids);\n if (mainId === null) {\n return null;\n }\n const maybe = getNTWrapperSession(mainId) as NodeIQQNTWrapperSession | null | undefined;\n return maybe !== null && maybe !== undefined && typeof maybe.getMsgService === \"function\"\n ? maybe\n : null;\n}\n\n/**\n * 复用 QQ 已有 session(P1-4:QQ 已登录,直接拿单例避免重复 init)。\n * 优先 `NodeIQQNTWrapperSession.get()`;返回 null 时回退 createSession。\n */\nexport function getExistingSession(ctx: WrapperContext): NodeIQQNTWrapperSession | null {\n const got = trySessionGet(ctx);\n if (got !== null) {\n ctx.session = got;\n return got;\n }\n return null;\n}\n\n/** NodeIQQNTWrapperSession.get() 单例(无效/异常返回 null)。 */\nfunction trySessionGet(ctx: WrapperContext): NodeIQQNTWrapperSession | null {\n try {\n const S = ctx.exports.NodeIQQNTWrapperSession as unknown as {\n get?: () => NodeIQQNTWrapperSession;\n };\n if (typeof S.get !== \"function\") {\n return null;\n }\n const got = S.get();\n return got && typeof got.getMsgService === \"function\" ? got : null;\n } catch {\n return null; // 复用失败,回退 create\n }\n}\n\n/**\n * 复用 QQ 主 session(P1-4 实测链路):\n * `NodeIQQNTStartupSessionWrapper.create()` → `start()` → `getSessionIdList()`(Map\n * {nt:\"nt_3\", gpro:\"gpro_3\"})→ `NodeIQQNTWrapperSession.getNTWrapperSession(\"nt_3\")`。\n * 注:实测这些 session 是空壳(核心 service null),真正可用的是 new + init(lifecycle.ts)。\n */\nexport function getMainSession(ctx: WrapperContext): NodeIQQNTWrapperSession | null {\n try {\n return resolveMainFromStartup(ctx) ?? null;\n } catch {\n return null; // 复用失败,回退 create\n }\n}\n\n/** startup.create → start → getSessionIdList → getNTWrapperSession 链路。 */\nfunction resolveMainFromStartup(ctx: WrapperContext): NodeIQQNTWrapperSession | null {\n const startupRaw = ctx.exports.NodeIQQNTStartupSessionWrapper as unknown as {\n create?: () => { start?: () => void; getSessionIdList?: () => unknown } | null;\n };\n const sessionCtor = ctx.exports.NodeIQQNTWrapperSession as unknown as {\n getNTWrapperSession?: (id: string) => NodeIQQNTWrapperSession;\n };\n if (\n typeof startupRaw.create !== \"function\" ||\n typeof sessionCtor.getNTWrapperSession !== \"function\"\n ) {\n return null;\n }\n const created = startupRaw.create();\n if (!created) {\n return null;\n }\n const getSession = (id: string): NodeIQQNTWrapperSession =>\n (sessionCtor.getNTWrapperSession as (name: string) => NodeIQQNTWrapperSession)(id);\n const session = resolveMainSession(created, getSession);\n if (session !== null) {\n ctx.session = session;\n }\n return session;\n}\n","/**\n * 运行时反射探测的序列化工具(从 probe.ts 拆分,2026-08-08 FTA 优化)\n *\n * - serialize:深度受限序列化(BigInt 转字符串,Map/Set/Promise 展开,循环引用防护)\n * - tryShape:解析 service getter 返回形状\n *\n * 字节串保真(2026-11-01 首录教训):onRecvSysMsg 的 wire bytes 与 grayTip 的\n * jsonGrayTipElement 是批次三(sysmsg 识别表 / reaction)的样本来源——TypedArray\n * 与字节形态普通数组转 base64 全量保留(截断上限放宽),数字数组/深对象不再\n * 20 项 / 4 层截断(上限提至 64 / 8)。\n */\n\n/** 序列化深度上限(循环引用/深层对象防护)。 */\nconst MAX_SERIALIZE_DEPTH = 8;\n/** 数组序列化条数上限。 */\nconst MAX_ARRAY_ITEMS = 64;\n/** 对象键序列化条数上限。 */\nconst MAX_OBJECT_KEYS = 50;\n/** 字节串保留上限(超过截断 + truncated 标注)。 */\nconst MAX_BYTES = 8192;\n/** 字节形态普通数组的最短长度(短数字数组按普通数组序列化)。 */\nconst BYTES_ARRAY_MIN = 16;\n\n/** 序列化一个值(BigInt 转字符串,Map/Set/Promise 展开,循环引用防护)。 */\nexport function serialize(value: unknown, depth = 0): unknown {\n if (value === null || value === undefined) return value;\n if (typeof value === \"bigint\") return value.toString();\n if (typeof value === \"function\") return `[function ${value.name ?? \"anon\"}]`;\n if (typeof value !== \"object\") return value;\n if (depth > MAX_SERIALIZE_DEPTH) return \"[depth-limit]\";\n try {\n return serializeContainer(value, depth);\n } catch {\n return \"[unserializable]\";\n }\n}\n\n/** 序列化容器对象(TypedArray / 数组 / Map / Set / Promise / 普通对象)。 */\nfunction serializeContainer(value: object, depth: number): unknown {\n if (ArrayBuffer.isView(value)) {\n return serializeBytes(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));\n }\n if (Array.isArray(value)) {\n if (value.length >= BYTES_ARRAY_MIN && looksLikeBytes(value)) {\n return serializeBytes(Uint8Array.from(value, (b) => (b as number) & 0xff));\n }\n return serializeArray(value, depth);\n }\n if (value instanceof Map) {\n return serializeMap(value, depth);\n }\n if (value instanceof Set) {\n return serializeSet(value, depth);\n }\n if (typeof (value as Promise<unknown>).then === \"function\") {\n return \"[Promise]\";\n }\n return serializeObject(value, depth);\n}\n\n/** 普通数组是否字节形态(全部元素为整数且落 [-128, 255]——原生把字节块转成带符号字节数组)。 */\nfunction looksLikeBytes(value: unknown[]): boolean {\n return value.every(\n (x) => typeof x === \"number\" && Number.isInteger(x) && x >= -128 && x <= 255,\n );\n}\n\n/** 字节块 → { kind: \"bytes\", size, data(base64), truncated? }。 */\nfunction serializeBytes(bytes: Uint8Array): Record<string, unknown> {\n const capped = bytes.length > MAX_BYTES ? bytes.subarray(0, MAX_BYTES) : bytes;\n const out: Record<string, unknown> = {\n kind: \"bytes\",\n size: bytes.length,\n data: Buffer.from(capped).toString(\"base64\"),\n };\n if (capped.length < bytes.length) {\n out[\"truncated\"] = bytes.length - capped.length;\n }\n return out;\n}\n\n/** 数组序列化(条数截断)。 */\nfunction serializeArray(value: unknown[], depth: number): unknown[] {\n return value.slice(0, MAX_ARRAY_ITEMS).map((v) => serialize(v, depth + 1));\n}\n\n/** Map 序列化(键转字符串,条目截断)。 */\nfunction serializeMap(value: Map<unknown, unknown>, depth: number): unknown {\n const out: Record<string, unknown> = {};\n let i = 0;\n for (const [k, v] of value) {\n if (i >= MAX_OBJECT_KEYS) break;\n out[String(k)] = serialize(v, depth + 1);\n i += 1;\n }\n return { kind: \"Map\", size: value.size, entries: out };\n}\n\n/** Set 序列化(值截断)。 */\nfunction serializeSet(value: Set<unknown>, depth: number): unknown {\n return {\n kind: \"Set\",\n size: value.size,\n values: [...value].slice(0, MAX_ARRAY_ITEMS).map((v) => serialize(v, depth + 1)),\n };\n}\n\n/** 普通对象序列化(键截断)。 */\nfunction serializeObject(value: object, depth: number): unknown {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value).slice(0, MAX_OBJECT_KEYS)) {\n out[key] = serialize((value as Record<string, unknown>)[key], depth + 1);\n }\n return out;\n}\n\n/** 尝试解析一个 service getter 的返回值形状(深度受限)。 */\nexport function tryShape(session: unknown, getter: string): unknown {\n try {\n const fn = (session as Record<string, unknown>)[getter];\n if (typeof fn !== \"function\") return null;\n const value = fn.call(session);\n if (value === null || value === undefined) return null;\n return serialize(value);\n } catch {\n return \"[error]\";\n }\n}\n","/**\n * 运行时反射探测基础工具(从 probe.ts 拆分,2026-08-08 FTA 优化)\n *\n * - listMethods:反射枚举对象原型链上的方法名(去重、去构造器)\n * - tryCall:安全调用方法拿返回值(失败记录错误,不中断探测)\n */\n\n/** 反射枚举一个对象原型链上的方法名(去重、去构造器)。 */\nexport function listMethods(obj: unknown): string[] {\n if (obj === null || obj === undefined) {\n return [];\n }\n const names = new Set<string>();\n collectPrototypeNames(obj, names);\n collectOwnNames(obj, names);\n return [...names].sort();\n}\n\n/** 收集原型链上的属性名(到 Object/Function.prototype 为止,去构造器)。 */\nfunction collectPrototypeNames(obj: unknown, names: Set<string>): void {\n let proto = Object.getPrototypeOf(obj);\n while (isProbeProto(proto)) {\n for (const name of Object.getOwnPropertyNames(proto)) {\n if (name !== \"constructor\") {\n names.add(name);\n }\n }\n proto = Object.getPrototypeOf(proto);\n }\n}\n\n/** 是否仍需遍历的原型(非 Object/Function.prototype 且非空)。 */\nfunction isProbeProto(proto: object | null): proto is object {\n return proto !== null && proto !== Object.prototype && proto !== Function.prototype;\n}\n\n/** 收集自有属性名(去构造器)。 */\nfunction collectOwnNames(obj: unknown, names: Set<string>): void {\n for (const name of Object.getOwnPropertyNames(obj)) {\n if (name !== \"constructor\") {\n names.add(name);\n }\n }\n}\n\n/** 安全调用:尝试调用方法拿返回值,失败记录错误(不中断探测)。 */\nexport function tryCall(\n obj: unknown,\n name: string,\n): { ok: boolean; value?: unknown; error?: string } {\n try {\n const fn = (obj as Record<string, unknown>)[name];\n if (typeof fn !== \"function\") {\n return { ok: false, error: `not a function (typeof=${typeof fn})` };\n }\n const value = fn.call(obj);\n return { ok: true, value };\n } catch (e) {\n let message = String(e);\n if (e instanceof Error) {\n message = e.message;\n }\n return { ok: false, error: message };\n }\n}\n\n/** 服务 getter 判定(get*Service 命名约定)。 */\nexport function isServiceGetter(name: string): boolean {\n return name.startsWith(\"get\") && name.endsWith(\"Service\");\n}\n","/**\n * wrapper exports 探测(从 probe.ts 拆分,2026-08-08 FTA 优化)\n *\n * - probeExportCtors:关键 export 构造器方法面\n * - probeEngineCalls:engine 关键调用\n * - probeLoginService:LoginService 实例(QQ 已登录凭据入口)\n */\n\nimport type { WrapperContext } from \"../wrapper-loader.js\";\nimport { serialize, tryShape } from \"./probe-serialize.js\";\nimport { listMethods, tryCall } from \"./probe-utils.js\";\n\n/** 探测关键 export 构造器方法(get/create/getNTWrapperSession 等)。 */\nexport function probeExportCtors(ctx: WrapperContext): Record<string, unknown> {\n const keys = [\n \"NodeIQQNTWrapperSession\",\n \"NodeIQQNTStartupSessionWrapper\",\n \"NodeQQNTWrapperUtil\",\n \"NodeIKernelLoginService\",\n \"NodeIQQNTWrapperEngine\",\n ] as const;\n const out: Record<string, unknown> = {};\n for (const key of keys) {\n const ctor = (ctx.exports as unknown as Record<string, unknown>)[key];\n if (ctor !== null && ctor !== undefined) {\n out[key] = { methods: listMethods(ctor), ownKeys: Object.getOwnPropertyNames(ctor) };\n }\n }\n return out;\n}\n\n/** 探测 engine 关键调用(getDeviceInfo / getECDHService / getThirdPartySigService)。 */\nexport function probeEngineCalls(ctx: WrapperContext): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const m of ENGINE_CALL_PROBES) {\n out[m] = probeEngineCall(ctx, m);\n }\n return out;\n}\n\n/** 探测单个 engine 方法调用(成功记录方法面/形状,失败记录错误)。 */\nfunction probeEngineCall(ctx: WrapperContext, m: string): unknown {\n const call = tryCall(ctx.engine, m);\n return call.ok\n ? { value: serialize(call.value), methods: call.value ? listMethods(call.value) : [] }\n : { error: call.error ?? \"null/undefined\" };\n}\n\n/** engine 关键调用候选。 */\nconst ENGINE_CALL_PROBES = [\n \"getDeviceInfo\",\n \"getECDHService\",\n \"getThirdPartySigService\",\n \"readyToShow\",\n] as const;\n\n/** 探测 LoginService 实例(QQ 已登录凭据入口)。 */\nexport function probeLoginService(ctx: WrapperContext): Record<string, unknown> | null {\n const ctor = ctx.exports.NodeIKernelLoginService as unknown as {\n get?: () => unknown;\n } | null;\n if (ctor === null || typeof ctor.get !== \"function\") {\n return null;\n }\n const inst = tryCall(ctor, \"get\");\n if (!(inst.ok && inst.value)) {\n return { get: inst.error ?? \"null/undefined\" };\n }\n return {\n methods: listMethods(inst.value),\n ownKeys: Object.getOwnPropertyNames(inst.value).slice(0, 30),\n ...probeLoginGetters(inst.value),\n };\n}\n\n/** 尝试关键登录信息 getter(无参调用,探测形状)。 */\nfunction probeLoginGetters(inst: unknown): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const m of LOGIN_GETTER_PROBES) {\n if (typeof (inst as Record<string, unknown>)[m] === \"function\") {\n out[m] = tryShape(inst, m);\n }\n }\n return out;\n}\n\n/** 登录信息 getter 候选(无参调用,探测形状)。 */\nconst LOGIN_GETTER_PROBES = [\n \"getAccountInfo\",\n \"getLoginInfo\",\n \"getA2\",\n \"getTicket\",\n \"getUin\",\n \"getUid\",\n \"getSelfUin\",\n] as const;\n","/**\n * 运行时反射探测(ADR-006 落地,P1-4)\n *\n * 注入链路打通后(2026-08-05),wrapper exports 是 QQ 已注册的单例。\n * 本模块在 **QQ 主进程内**反射枚举 session / service 的真实方法名与返回形状,\n * 产出 JSON 到 NAPUTO_CFG_DIR/napuketto-probe.json,作为 types/services/ 的权威来源。\n *\n * 用法:boot.cjs 在 NAPUTO_PROBE=1 时于 startNapuketto 后调用 probeRuntime(ctx)。\n * 产物是研究工具数据,类型层据此补全(自研描述,非移植)。\n *\n * 工具拆分(2026-08-08 FTA 优化):方法枚举/安全调用 → probe-utils.ts;\n * 序列化/形状探测 → probe-serialize.ts;exports/service 探测 → probe-services.ts;\n * 本文件只留 session 探测编排(probeStartup/probeSession/probeRuntime)。\n */\n\nimport { mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\nimport { getMainSession } from \"../session-resolver.js\";\nimport type { WrapperContext } from \"../wrapper-loader.js\";\nimport { serialize } from \"./probe-serialize.js\";\nimport { probeEngineCalls, probeExportCtors, probeLoginService } from \"./probe-services.js\";\nimport { isServiceGetter, listMethods, tryCall } from \"./probe-utils.js\";\n\n/** 枚举候选 sessionId(nt_0..nt_9 / gpro_0..gpro_9)找 QQ 已 init 的 session。 */\nfunction enumerateSessionIds(ctx: WrapperContext): Record<string, unknown> {\n const S = ctx.exports.NodeIQQNTWrapperSession;\n const enumFn = getNTWrapperSessionFn(ctx);\n if (enumFn === null) {\n return {};\n }\n const out: Record<string, unknown> = {};\n for (let i = 0; i < 10; i += 1) {\n probeSessionId(enumFn, S, `nt_${i}`, out);\n probeSessionId(enumFn, S, `gpro_${i}`, out);\n }\n return out;\n}\n\n/** 探测单个 sessionId(getter 存在性 + getMsgService 调用结果)。 */\nfunction probeSessionId(\n enumFn: (...args: unknown[]) => unknown,\n thisCtx: unknown,\n id: string,\n out: Record<string, unknown>,\n): void {\n try {\n const value = enumFn.call(thisCtx, id);\n if (!value) {\n return;\n }\n out[id] = probeSessionRecord(value);\n } catch {\n // 忽略单次失败\n }\n}\n\n/** 探测单个 session 记录(getter 存在性 + msgService 调用结果 + 方法面)。 */\nfunction probeSessionRecord(value: unknown): Record<string, unknown> {\n const rec = value as Record<string, unknown>;\n const msg = tryMsgService(rec, value);\n return {\n hasMsgServiceGetter: typeof rec[\"getMsgService\"] === \"function\",\n msgServiceOk: msg.service !== null,\n msgServiceMethods: msg.service ? listMethods(msg.service).length : 0,\n msgError: msg.error,\n methods: listMethods(value),\n };\n}\n\n/** 尝试调用 getMsgService(失败记录错误,成功返回实例)。 */\nfunction tryMsgService(\n rec: Record<string, unknown>,\n thisCtx: unknown,\n): { service: unknown; error: string | null } {\n try {\n const svc = (rec[\"getMsgService\"] as () => unknown).call(thisCtx);\n return { service: svc ?? null, error: null };\n } catch (e) {\n return { service: null, error: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/** 取 NodeIQQNTWrapperSession.getNTWrapperSession 函数(无则 null)。 */\nfunction getNTWrapperSessionFn(ctx: WrapperContext): ((...args: unknown[]) => unknown) | null {\n const fn = (ctx.exports.NodeIQQNTWrapperSession as unknown as Record<string, unknown>)[\n \"getNTWrapperSession\"\n ];\n return typeof fn === \"function\" ? (fn as (...args: unknown[]) => unknown) : null;\n}\n\n/** 探测 startup 实例:start() + getSessionIdList(),提取 sessionIds(Map 值或键)。 */\nfunction probeStartupChain(created: unknown): {\n start: Record<string, unknown>;\n createdGetSessionIdList: Record<string, unknown>;\n sessionIds: string[];\n} {\n const started = tryCall(created, \"start\");\n const ids = tryCall(created, \"getSessionIdList\");\n return {\n start: started.ok ? { ok: true } : { error: started.error },\n createdGetSessionIdList: ids.ok ? { value: serialize(ids.value) } : { error: ids.error },\n sessionIds: extractSessionIds(ids),\n };\n}\n\n/** 从 getSessionIdList 结果提取 sessionIds(Map 值或键优先)。 */\nfunction extractSessionIds(ids: { ok: boolean; value?: unknown }): string[] {\n if (!(ids.ok && ids.value instanceof Map)) {\n return [];\n }\n return [...ids.value].map(([k, v]) => sessionIdOfEntry(k, v));\n}\n\n/** 单条 sessionIds 项(Map 值是字符串直接用,否则退化键名)。 */\nfunction sessionIdOfEntry(k: unknown, v: unknown): string {\n return typeof v === \"string\" ? v : String(k);\n}\n\n/** session 实例方法面(空值记 null)。 */\nfunction sessionFace(session: unknown): Record<string, unknown> | null {\n return session !== null && session !== undefined\n ? {\n methods: listMethods(session),\n ownKeys: Object.getOwnPropertyNames(session),\n }\n : null;\n}\n\n/** 未知异常 → 错误消息字符串。 */\nfunction errMsgString(e: unknown): string {\n return e instanceof Error ? e.message : String(e);\n}\n\n/** try getNTWrapperSession(id) → session 方法面(fn 缺失记 null,失败记 error)。 */\nfunction trySessionFace(ctx: WrapperContext, id: string): unknown {\n const fn = getNTWrapperSessionFn(ctx);\n if (fn === null) {\n return null;\n }\n try {\n return sessionFace(fn.call(ctx.exports.NodeIQQNTWrapperSession, id));\n } catch (e) {\n return { error: errMsgString(e) };\n }\n}\n\n/** 用真实 sessionId 取主 session(getNTWrapperSession(nt_x))并记录方法面。 */\nfunction probeSessionById(ctx: WrapperContext, id: string): unknown {\n return trySessionFace(ctx, id);\n}\n\n/** 兜底:候选名字 getNTWrapperSession(无 Map 数据时)。 */\nfunction probeByNameFallback(ctx: WrapperContext): Record<string, unknown> {\n const candidates = [\"main\", \"primary\", \"session1\", \"default\", \"\"];\n const getResults: Record<string, unknown> = {};\n for (const name of candidates) {\n getResults[name === \"\" ? \"<empty>\" : name] = probeSessionByName(ctx, name);\n }\n return getResults;\n}\n\n/** 探测单个候选名(getNTWrapperSession(name),记录方法面或错误)。 */\nfunction probeSessionByName(ctx: WrapperContext, name: string): unknown {\n return trySessionFace(ctx, name);\n}\n\n/** StartupSessionWrapper 可用性判定(缺 create 方法视为不可用)。 */\nfunction startupWrapper(ctx: WrapperContext): { create?: () => unknown } | null {\n const startup = ctx.exports.NodeIQQNTStartupSessionWrapper as unknown as {\n create?: () => unknown;\n } | null;\n return startup === null || typeof startup.create !== \"function\" ? null : startup;\n}\n\n/** create() 调用结果面(成功记实例方法面 + 序列化值,失败记 error)。 */\nfunction createFace(created: {\n ok: boolean;\n value?: unknown;\n error?: string;\n}): Record<string, unknown> {\n return created.ok\n ? {\n methods: created.value ? listMethods(created.value) : [],\n value: serialize(created.value),\n }\n : { error: created.error };\n}\n\n/** create 成功时并入实例链探测结果。 */\nfunction assignCreatedChain(\n out: Record<string, unknown>,\n ctx: WrapperContext,\n created: { ok: boolean; value?: unknown },\n): void {\n if (created.ok && created.value) {\n Object.assign(out, probeCreatedChain(ctx, created.value));\n }\n}\n\n/** 无 Map 数据时并入候选名兜底结果。 */\nfunction assignNameFallback(out: Record<string, unknown>, ctx: WrapperContext): void {\n if (out[\"sessionIds\"] !== undefined) {\n return;\n }\n out[\"getNTWrapperSession\"] = probeByNameFallback(ctx);\n}\n\n/** 探测 startup session 链路:create() → start() → getSessionIdList → getNTWrapperSession。 */\nfunction probeStartup(ctx: WrapperContext): Record<string, unknown> | null {\n const startup = startupWrapper(ctx);\n if (startup === null) {\n return null;\n }\n const out: Record<string, unknown> = { staticMethods: listMethods(startup) };\n const created = tryCall(startup, \"create\");\n out[\"create\"] = createFace(created);\n assignCreatedChain(out, ctx, created);\n out[\"enumSessionIds\"] = enumerateSessionIds(ctx);\n // 无 Map 数据时兜底:候选名字\n assignNameFallback(out, ctx);\n return out;\n}\n\n/** 探测 create 后的实例链:start / getSessionIdList / 主 session。 */\nfunction probeCreatedChain(ctx: WrapperContext, created: unknown): Record<string, unknown> {\n const chain = probeStartupChain(created);\n const out: Record<string, unknown> = {\n start: chain.start,\n createdGetSessionIdList: chain.createdGetSessionIdList,\n sessionIds: chain.sessionIds,\n };\n const mainId = findMainProbeId(chain.sessionIds);\n if (mainId !== undefined) {\n out[\"mainSessionId\"] = mainId;\n out[\"mainSession\"] = probeSessionById(ctx, mainId);\n }\n return out;\n}\n\n/** 选主 sessionId(nt_ 前缀优先,否则第一个)。 */\nfunction findMainProbeId(sessionIds: string[]): string | undefined {\n return sessionIds.find((id) => id.startsWith(\"nt_\")) ?? sessionIds[0];\n}\n\n/** 探测 session 与 service 方法名面。\n * 主目标 = ctx.session 快照(kernel 自建就绪 session),缺省回退 QQ 主 session;\n * 自建宿主下主 session 未被 init(get*Service 恒 null,2026-10-31 实测)。\n * ⚠️ 先快照 ctx.session 再枚举主 session——getMainSession 有 P1-4 遗留副作用\n * (resolveMainFromStartup 把空壳主 session 写回 ctx.session,session-resolver.ts)。\n * ⚠️ names-only:不对服务实例做无参 shape 调用——活 session 上无参调原生方法\n * = 访问违例 0xC0000005(2026-10-31 实测崩溃,tryShape 捕不住原生段错误)。 */\n/** 非空判定(null / undefined 均视为缺省)。 */\nfunction hasValue(value: unknown): boolean {\n return value !== null && value !== undefined;\n}\n\nfunction probeSession(ctx: WrapperContext, persist?: () => void): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n const own = ctx.session;\n const main = tryMainSession(ctx);\n const primary = hasValue(own) ? own : main;\n out[\"sessionSource\"] = hasValue(own) ? \"own-create\" : \"mainSession\";\n out[\"session\"] = sessionFace(primary);\n assignServices(out, primary, persist);\n assignAlternates(out, own, main);\n return out;\n}\n\n/** 并入主目标的服务方法名面(主目标缺省时跳过)。 */\nfunction assignServices(\n out: Record<string, unknown>,\n primary: unknown,\n persist?: () => void,\n): void {\n if (!hasValue(primary)) {\n return;\n }\n out[\"services\"] = collectServiceGetters(primary, persist);\n}\n\n/** 并入落选候选概要(仅主目标为自建 session 时记录主 session 方法数,诊断用)。 */\nfunction assignAlternates(out: Record<string, unknown>, own: unknown, main: unknown): void {\n if (!hasValue(own) || !hasValue(main)) {\n return;\n }\n out[\"sessionAlternates\"] = [\n {\n source: \"mainSession(getNTWrapperSession)\",\n methodCount: listMethods(main).length,\n },\n ];\n}\n\n/** QQ 主 session(getMainSession 有写回 ctx.session 的副作用——调用前须已快照)。 */\nfunction tryMainSession(ctx: WrapperContext): unknown {\n try {\n return getMainSession(ctx);\n } catch {\n return null; // 主 session 枚举失败:只剩 own-create 候选\n }\n}\n\n/** 收集 session 的 get*Service 方法名面(names-only,不做 shape 无参调用)。\n * 每个服务完成即 persist 一次——活 session 上个别 getter 若触发原生崩溃,\n * 文件里留下最后一个成功项,精确定位肇事 getter。 */\nfunction collectServiceGetters(session: unknown, persist?: () => void): Record<string, unknown> {\n const services: Record<string, unknown> = {};\n for (const getter of listMethods(session).filter(isServiceGetter)) {\n services[getter] = probeServiceGetter(session, getter);\n persist?.();\n }\n return services;\n}\n\n/** 探测单个 getter:成功记录方法名面(不调用服务方法),失败记录错误。 */\nfunction probeServiceGetter(session: unknown, getter: string): unknown {\n const call = tryCall(session, getter);\n if (!(call.ok && call.value)) {\n return { error: call.error ?? \"null/undefined\", ok: call.ok };\n }\n return {\n methods: listMethods(call.value),\n ownKeys: Object.getOwnPropertyNames(call.value).slice(0, 20),\n };\n}\n\n/**\n * 探测 wrapper 运行时:dump session 方法与各 service 方法名到 JSON。\n * 返回探测结果对象;同时写文件(NAPUTO_CFG_DIR/<file>,默认 napuketto-probe.json)。\n * 服务枚举逐项增量落盘(persist)——活 session 上原生崩溃时文件保留断点。\n */\nexport function probeRuntime(\n ctx: WrapperContext,\n file = \"napuketto-probe.json\",\n): Record<string, unknown> {\n const result: Record<string, unknown> = {\n timestamp: new Date().toISOString(),\n exports: listMethods(ctx.exports),\n engineMethods: listMethods(ctx.engine),\n exportCtors: probeExportCtors(ctx),\n engineCalls: probeEngineCalls(ctx),\n loginService: probeLoginService(ctx),\n startup: probeStartup(ctx),\n };\n const persist = (): void => writeProbeFile(result, file);\n Object.assign(result, probeSession(ctx, persist));\n writeProbeFile(result, file);\n return result;\n}\n\n/** 探测结果落盘(增量与最终共用;最终写入后记 probeFile 路径)。 */\nfunction writeProbeFile(result: Record<string, unknown>, file: string): void {\n try {\n const dir = process.env[\"NAPUTO_CFG_DIR\"] ?? process.cwd();\n mkdirSync(dir, { recursive: true });\n const full = join(dir, file);\n writeFileSync(full, JSON.stringify(result, null, 2), \"utf8\");\n result[\"probeFile\"] = full;\n } catch (e) {\n result[\"probeFileError\"] = errMsgString(e);\n }\n}\n","/**\n * listener 录制器(批次三前置探测,docs/protocols/milky.md §0.8.1、本包 design.md §10)\n *\n * 动机:无源事件缺的不是方法名而是回调证据——现有 bridge 只注册已知回调名,\n * 原生 listener 回调面之外的派发行为从未被观测。原理:原生 listener 是普通\n * JS 对象 + NAPI 按名反射回调(MsgBridge 模式),用 Proxy get 陷阱构造万能\n * 监听器,原生调什么记什么,不需要预先知道回调名。\n *\n * 开关:NAPUTO_LISTENER_RECORD=1(loader bootstrap 挂起,位置与 runProbePhase 相邻)。\n * 产物:NAPUTO_CFG_DIR/napuketto-listener-record.jsonl(JSONL 追加,注册过程\n * 逐行落盘,原生崩溃保留断点定位肇事服务)。\n *\n * 安全边界:记录函数只序列化入参后回调 onEvent,绝不反向调用原生;录制器与\n * 业务 bridge 并存(addKernelXListener 多监听各自独立 id),不干扰协议链路。\n */\n\nimport { appendFileSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport process from \"node:process\";\nimport type { WrapperContext } from \"../wrapper-loader.js\";\nimport { serialize } from \"./probe-serialize.js\";\nimport { listMethods } from \"./probe-utils.js\";\n\n/** 录制事件(JSONL 一行)。 */\nexport interface ListenerRecordEvent {\n /** ISO 时间戳。 */\n ts: string;\n /** 服务简称(Msg / Group / …;LoginService 例外,来自 exports 单例)。 */\n service: string;\n /** 回调名;陷阱观测用合成事件 `<get>` / `<has>` / `<ownKeys>` / `<registered>`。 */\n event: string;\n /** 回调入参(截断序列化)或注册附加信息。 */\n args: unknown[];\n}\n\n/** 录制结果摘要(bootstrap 日志用)。 */\nexport interface ListenerRecordingResult {\n /** jsonl 产物绝对路径。 */\n file: string;\n /** 逐服务注册结果(ok=listenerId / error / null=服务 getter 返回空)。 */\n registered: Record<string, { ok: boolean; listenerId?: number; error?: string | null }>;\n}\n\n/** 默认仪器面:与 5 个无源事件 + sysmsg 样本采集相关的 listener 服务(design.md §10)。 */\nconst INSTRUMENTED_SERVICE_GETTERS = [\n \"getMsgService\",\n \"getGroupService\",\n \"getRecentContactService\",\n \"getBdhUploadService\",\n \"getOnlineStatusService\",\n \"getLockService\",\n \"getFileAssistantService\",\n \"getBuddyService\",\n \"getProfileLikeService\",\n \"getNodeMiscService\",\n] as const;\n\n/** 服务 getter 前缀 / 后缀(getMsgService → Msg)。 */\nconst GETTER_PREFIX_RE = /^get/;\nconst SERVICE_SUFFIX_RE = /Service$/;\n/** listener 注册 / 注销方法名(addKernelMsgListener / 裸 addListener 同规则)。 */\nconst ADD_LISTENER_RE = /^add(Kernel)?\\w*Listener$/;\nconst REMOVE_LISTENER_RE = /^remove(Kernel)?\\w*Listener$/;\n\n/** 服务简称(getMsgService → Msg;getBdhUploadService → BdhUpload)。 */\nfunction serviceShortName(getter: string): string {\n return getter.replace(GETTER_PREFIX_RE, \"\").replace(SERVICE_SUFFIX_RE, \"\");\n}\n\n/**\n * 从服务方法面选 listener 注册方法:精确名 `addKernel<简称>Listener` 优先——\n * MsgService 另有 addKernelMsgImportToolListener / MsgNotify / MsgQuery /\n * TempChatSig 四个 add*Listener,正则按字母序会误选 ImportTool(2026-11-01\n * 首录实锤:真 listener 没挂上,Msg 回调面零捕获);无精确名回退正则(裸\n * addListener 服务)。\n */\nfunction findAddMethod(service: unknown, shortName: string): string | null {\n const methods = listMethods(service);\n const preferred = `addKernel${shortName}Listener`;\n if (methods.includes(preferred)) {\n return preferred;\n }\n const match = methods.find((m) => ADD_LISTENER_RE.test(m));\n return match ?? null;\n}\n\n/** 从服务方法面选 listener 注销方法(缺失允许——录制器不主动注销)。 */\nfunction findRemoveMethod(service: unknown, shortName: string): string | null {\n const methods = listMethods(service);\n const preferred = `removeKernel${shortName}Listener`;\n if (methods.includes(preferred)) {\n return preferred;\n }\n const match = methods.find((m) => REMOVE_LISTENER_RE.test(m));\n return match ?? null;\n}\n\n/**\n * 构造万能监听器:Proxy get 陷阱对任意字符串属性返回记录函数(同时记录原生\n * 查过的名字,即使从未调用);has / ownKeys 也记录(判定原生按名 Get 还是\n * 枚举发现回调);非字符串键(Symbol,如 util.inspect 触发)放行默认行为。\n */\nexport function createRecordingListener(\n service: string,\n onEvent: (record: ListenerRecordEvent) => void,\n): object {\n const record = (event: string, args: unknown[]): void => {\n onEvent({ ts: new Date().toISOString(), service, event, args });\n };\n // 空目标:所有属性访问走陷阱(ownKeys 为空是刻意的——若原生以枚举发现回调,\n // <ownKeys> 记录立即暴露该行为,fallback 预置已知回调名全集后重录)。\n return new Proxy({} as Record<string, unknown>, {\n get(target, prop, receiver): unknown {\n if (typeof prop !== \"string\") {\n return Reflect.get(target, prop, receiver);\n }\n record(\"<get>\", [prop]);\n return (...args: unknown[]) => {\n record(\n prop,\n [...args].map((a) => serialize(a)),\n );\n };\n },\n has(_target, prop): boolean {\n if (typeof prop === \"string\") {\n record(\"<has>\", [prop]);\n }\n return true;\n },\n ownKeys(): string[] {\n record(\"<ownKeys>\", []);\n return [];\n },\n });\n}\n\n/**\n * 启动 listener 录制:session 十服务 + LoginService(exports 单例)逐服务注册\n * 万能监听器,JSONL 逐行追加。返回摘要;文件路径写入结果(bootstrap 日志用)。\n *\n * ⚠️ 调用时序约束:必须在任何探测(probeRuntime / getMainSession)之前调用——\n * resolveMainFromStartup 会把空壳主 session 写回 ctx.session(session-resolver.ts),\n * 空壳的 service getter 函数仍在但调用返回 null(late probe 61/63 null 的根因)。\n */\nexport function startListenerRecording(\n ctx: WrapperContext,\n file = \"napuketto-listener-record.jsonl\",\n): ListenerRecordingResult {\n const dir = process.env[\"NAPUTO_CFG_DIR\"] ?? process.cwd();\n mkdirSync(dir, { recursive: true });\n const full = join(dir, file);\n // JSONL 语义:本次录制起全新文件(截断),此后逐行 append(崩溃保留断点)\n writeFileSync(full, \"\", \"utf8\");\n const emit = (record: ListenerRecordEvent): void => {\n try {\n appendFileSync(full, `${JSON.stringify(record)}\\n`, \"utf8\");\n } catch {\n // 落盘失败不中断录制(录制器是观测工具,不允许拖垮宿主)\n }\n };\n const registered: ListenerRecordingResult[\"registered\"] = {};\n const session = ctx.session as Record<string, unknown> | null;\n if (session !== null && session !== undefined) {\n for (const getter of INSTRUMENTED_SERVICE_GETTERS) {\n registered[serviceShortName(getter)] = recordService(session, getter, emit);\n }\n }\n registered[\"Login\"] = recordLoginService(ctx, emit);\n return { file: full, registered };\n}\n\n/** 对单个服务注册万能监听器(getter 失败/null/无 add 方法均记录原因)。 */\nfunction recordService(\n session: Record<string, unknown>,\n getter: string,\n emit: (record: ListenerRecordEvent) => void,\n): ListenerRecordingResult[\"registered\"][string] {\n const name = serviceShortName(getter);\n try {\n const fn = session[getter];\n if (typeof fn !== \"function\") {\n return { ok: false, error: `getter 缺失(typeof=${typeof fn})` };\n }\n const service = (fn as () => unknown).call(session);\n if (service === null || service === undefined) {\n return { ok: false, error: \"null/undefined\" };\n }\n return attachRecordingListener(name, service, emit);\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/** LoginService 单例录制(NodeIKernelLoginService.get(),bot_offline 候选源)。 */\nfunction recordLoginService(\n ctx: WrapperContext,\n emit: (record: ListenerRecordEvent) => void,\n): ListenerRecordingResult[\"registered\"][string] {\n const ctor = (ctx.exports as unknown as Record<string, unknown>)[\"NodeIKernelLoginService\"] as\n | { get?: () => unknown }\n | null\n | undefined;\n try {\n if (ctor === null || ctor === undefined || typeof ctor.get !== \"function\") {\n return { ok: false, error: \"NodeIKernelLoginService.get 缺失\" };\n }\n const service = ctor.get();\n if (service === null || service === undefined) {\n return { ok: false, error: \"null/undefined\" };\n }\n return attachRecordingListener(\"Login\", service, emit);\n } catch (e) {\n return { ok: false, error: e instanceof Error ? e.message : String(e) };\n }\n}\n\n/** 在服务实例上挂万能监听器(add 方法自动选取;注册事件即行落盘)。 */\nfunction attachRecordingListener(\n name: string,\n service: unknown,\n emit: (record: ListenerRecordEvent) => void,\n): ListenerRecordingResult[\"registered\"][string] {\n const addMethod = findAddMethod(service, name);\n if (addMethod === null) {\n return { ok: false, error: \"无 add*Listener 方法\" };\n }\n const add = (service as Record<string, unknown>)[addMethod];\n if (typeof add !== \"function\") {\n return { ok: false, error: `${addMethod} 非函数` };\n }\n const listener = createRecordingListener(name, emit);\n const listenerId = (add as (...args: unknown[]) => unknown).call(service, listener) as\n | number\n | null\n | undefined;\n emit({\n ts: new Date().toISOString(),\n service: name,\n event: \"<registered>\",\n args: [\n {\n addMethod,\n removeMethod: findRemoveMethod(service, name),\n listenerId: serialize(listenerId),\n },\n ],\n });\n return { ok: true, ...(typeof listenerId === \"number\" ? { listenerId } : {}) };\n}\n","/**\n * QQ 安装目录探测(ADR-018)\n *\n * wrapper.node 路径随 QQ 版本变化:`<安装目录>/versions/<版本>/resources/app/wrapper.node`。\n * 加载前必须知道版本(登录握手参数 appid/qua 与版本强相关)。\n *\n * 已实测确认(2026-08-05,QQ 9.9.31-49919):\n * - 目录结构:versions/<9.9.31-49919>/resources/app/wrapper.node\n * - 版本信息在 versions/<版本>/resources/app/package.json(name=qq-chat / version / buildVersion)\n * - wrapper.node 是 C++ ABI 模块(非 N-API),导出 INTSessionShell 等符号\n */\n\nimport { existsSync, readdirSync, readFileSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { kernelError } from \"../infra/index.js\";\n\n/** 从 versions 目录读取版本 package.json(拿 name/version/buildVersion)。 */\nfunction readVersionPackage(\n installDir: string,\n version: string,\n): { name: string; version: string; buildVersion?: string } {\n const pkgPath = join(installDir, \"versions\", version, \"resources\", \"app\", \"package.json\");\n if (!existsSync(pkgPath)) {\n throw kernelError(`QQ 版本目录缺少 package.json: ${pkgPath}`, \"NOT_FOUND\");\n }\n try {\n const raw = JSON.parse(readFileSync(pkgPath, \"utf8\")) as {\n name?: string;\n version?: string;\n buildVersion?: string;\n };\n return {\n name: raw.name ?? \"\",\n version: raw.version ?? \"\",\n ...(raw.buildVersion !== undefined ? { buildVersion: raw.buildVersion } : {}),\n };\n } catch (cause) {\n throw kernelError(`QQ 版本 package.json 解析失败: ${pkgPath}`, \"INVALID_PARAM\", { cause });\n }\n}\n\n/** 解析 wrapper.node 绝对路径(按 ADR-018 目录约定)。 */\nexport function resolveWrapperPath(installDir: string, version: string): string {\n return join(installDir, \"versions\", version, \"resources\", \"app\", \"wrapper.node\");\n}\n\n/**\n * 探测指定版本信息(校验 wrapper.node 存在)。\n * 版本号不匹配目录时抛 NOT_FOUND(明确失败而非静默)。\n */\nexport function resolveQQVersion(installDir: string, version: string): QQVersionInfo {\n const wrapperPath = resolveWrapperPath(installDir, version);\n if (!existsSync(wrapperPath)) {\n throw kernelError(`未找到 wrapper.node: ${wrapperPath}`, \"NOT_FOUND\");\n }\n const pkg = readVersionPackage(installDir, version);\n return {\n fullVersion: pkg.version || version,\n buildVersion: pkg.buildVersion ?? \"\",\n wrapperPath,\n appid: \"\",\n qua: \"\",\n };\n}\n\n/** 扫描 versions 目录,取可加载的版本列表(按目录名倒序,最新在前)。 */\nexport function listQQVersions(installDir: string): string[] {\n const versionsDir = join(installDir, \"versions\");\n if (!existsSync(versionsDir)) {\n return [];\n }\n const dirs = readdirSync(versionsDir).filter((name) => {\n const full = join(versionsDir, name);\n return (\n statSync(full).isDirectory() &&\n existsSync(join(full, \"resources\", \"app\", \"wrapper.node\"))\n );\n });\n return dirs.sort().reverse();\n}\n\n/** QQ 版本信息。 */\nexport interface QQVersionInfo {\n /** 完整版本号,如 \"9.9.31-49919\"。 */\n fullVersion: string;\n /** 构建号,如 \"49919\"。 */\n buildVersion: string;\n /** wrapper.node 绝对路径。 */\n wrapperPath: string;\n /** 登录握手参数(P1 探测脚本产出,暂为空串)。 */\n appid: string;\n /** 登录握手参数(P1 探测脚本产出,暂为空串)。 */\n qua: string;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAyBA,MAAa,qBAAqB;CAC9B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAGA,IAAa,cAAb,cAAiC,MAAM;CACnC;CAEA,YAAY,SAAiB,OAAwB,WAAW,SAAwB;EACpF,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;EACZ,KAAK,OAAO;CAChB;AACJ;;AAGA,SAAgB,YACZ,SACA,OAAwB,WACxB,SACW;CACX,OAAO,IAAI,YAAY,SAAS,MAAM,OAAO;AACjD;;AAGA,SAAgB,cAAc,KAAkC;CAC5D,OAAO,eAAe;AAC1B;;;;;;;;AASA,IAAa,cAAb,cAAiC,YAAY;CACzC,YAAY,SAAiB,SAAwB;EACjD,MAAM,SAAS,iBAAiB,OAAO;EACvC,KAAK,OAAO;CAChB;AACJ;;AAGA,SAAgB,YAAY,SAAiB,SAAqC;CAC9E,OAAO,IAAI,YAAY,SAAS,OAAO;AAC3C;;AAGA,MAAa,sBAAsB;;;;;;;;;;;;;;AC7DnC,SAAS,YAAY,MAA4B;CAC7C,IAAI,QAAQ,IAAI,CAAC,CAAC,YAAY,MAAM,SAChC,OAAO;CAEX,OAAO;AACX;AAEA,SAAS,mBAAmB,KAAuB;CAC/C,IAAI,eAAe,OACf,OAAQ,IAA8B,SAAS;CAEnD,OAAO;AACX;;;;;;;AA6BA,IAAa,aAAb,MAA2B;CACvB;CACA;CAEA;CACA;CACA;CACA;CAEA,YAAY,MAAwB;EAChC,KAAK,OAAO,KAAK;EACjB,KAAK,WAAW,KAAK;EACrB,KAAK,SAAS,KAAK;EACnB,KAAK,SAAS,KAAK,UAAU,YAAY,KAAK,IAAI;EAClD,KAAK,OAAO,KAAK;EACjB,KAAK,QAAQ,KAAK,QAAQ,KAAK;CACnC;;CAGA,MAAS;EACL,OAAO,KAAK;CAChB;;;;;;;CAQA,MAAM,OAAmB;EACrB,IAAI,KAAK,SAAS,KAAA,GAAW;GACzB,KAAK,QAAQ,KAAK;GAClB,OAAO,KAAK;EAChB;EACA,IAAI;EACJ,IAAI;GACA,MAAM,MAAM,SAAS,KAAK,MAAM,MAAM;EAC1C,SAAS,KAAK;GACV,IAAI,mBAAmB,GAAG,GAAG;IACzB,MAAM,KAAK,KAAK,KAAK,QAAQ;IAC7B,OAAO,KAAK;GAChB;GACA,MAAM,IAAI,YAAY,aAAa,KAAK,QAAQ,EAAE,OAAO,IAAI,CAAC;EAClE;EACA,KAAK,QAAQ,KAAK,MAAM,GAAG;EAC3B,OAAO,KAAK;CAChB;;;;;;CAOA,UAAU,KAAgB;EACtB,OAAO,KAAK,MAAM,GAAG;CACzB;;CAGA,MAAM,KAAK,MAAwB;EAC/B,MAAM,SAAS,KAAK,MAAM,KAAK,UAAU,IAAI,CAAC;EAC9C,UAAU,QAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EACjD,MAAM,UAAU,GAAG,KAAK,KAAK;EAC7B,MAAM,UAAU,SAAS,GAAG,KAAK,UAAU,MAAM,EAAE,KAAK,MAAM;EAC9D,MAAM,OAAO,SAAS,KAAK,IAAI;EAC/B,KAAK,QAAQ;CACjB;;CAGA,UAAkB,OAAkB;EAChC,IAAI,KAAK,WAAW,QAChB,OAAOA,UAAkB,KAAgC;EAE7D,OAAO,KAAK,UAAU,OAAO,MAAM,CAAC;CACxC;;CAGA,YAAoB,KAAsB;EACtC,IAAI,KAAK,WAAW,QAChB,OAAOC,MAAc,GAAG;EAE5B,OAAO,KAAK,MAAM,GAAG;CACzB;;CAGA,MAAc,KAAgB;EAC1B,IAAI;EACJ,IAAI;GACA,QAAQ,KAAK,YAAY,GAAG;EAChC,SAAS,KAAK;GACV,MAAM,IAAI,YAAY,QAAQ,KAAK,OAAO,YAAY,EAAE,SAAS,KAAK,QAAQ,EAC1E,OAAO,IACX,CAAC;EACL;EACA,IAAI;GACA,OAAO,KAAK,OAAO,MAAM,KAAK;EAClC,SAAS,KAAK;GACV,MAAM,IAAI,YAAY,aAAa,KAAK,QAAQ,EAAE,OAAO,IAAI,CAAC;EAClE;CACJ;AACJ;;AAGA,SAAgB,UAAU,MAAuC;CAC7D,OAAOA,MAAc,IAAI;AAC7B;;AAGA,SAAgB,cAAc,OAAwC;CAClE,OAAOD,UAAkB,KAAK;AAClC;;;;;;;;;;;;;;;;;;ACjJA,MAAM,mBAAwC;CAC1C;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;AACA,MAAM,oBAA0C;CAAC;CAAU;CAAe;AAAM;AAChF,MAAM,wBAAkD;CAAC;CAAS;CAAW;AAAS;AACtF,MAAM,qBAA+C;CAAC;CAAO;CAAU;CAAU;AAAM;;AAyCvF,SAAS,aAAa,OAAgB,KAAuB;CACzD,IAAI,OAAO,UAAU,YAAY,CAAE,iBAAuC,SAAS,KAAK,GACpF,MAAM,YACF,OAAO,IAAI,WAAW,iBAAiB,KAAK,KAAK,EAAE,IACnD,eACJ;CAEJ,OAAO;AACX;;AAGA,SAAS,cAAc,OAAiD;CACpE,IAAI,UAAU,KAAA,GACV;CAEJ,OAAQ,iBAAuC,SAAS,KAAK,IACtD,QACD,KAAA;AACV;;AAGA,SAAS,eAAe,OAAkD;CACtE,IAAI,UAAU,KAAA,GACV;CAEJ,OAAQ,kBAAwC,SAAS,KAAK,IACvD,QACD,KAAA;AACV;;AAGA,SAAS,aAAa,OAAgD;CAClE,IAAI,UAAU,OAAO,UAAU,QAC3B,OAAO;CAEX,IAAI,UAAU,OAAO,UAAU,SAC3B,OAAO;AAGf;;AAGA,SAAS,kBAAkB,KAAwD;CAC/E,IAAI,QAAQ,KAAA,GACR,OAAO,CAAC;CAEZ,IAAI,OAAO,QAAQ,YAAY,QAAQ,MACnC,MAAM,YAAY,wBAAwB,eAAe;CAE7D,MAAM,MAAgD,CAAC;CACvD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG;EAC5C,IAAI,CAAE,mBAAyC,SAAS,GAAG,GACvD,MAAM,YACF,wBAAwB,IAAI,SAAS,mBAAmB,KAAK,KAAK,EAAE,IACpE,eACJ;EAEJ,IAAI,OAAwB,aAAa,OAAO,cAAc,KAAK;CACvE;CACA,OAAO;AACX;;AAGA,SAAS,aAAa,KAAc,eAAwC;CACxE,IAAI,OAAO,QAAQ,YAAY,QAAQ,MACnC,MAAM,YAAY,sBAAsB,eAAe;CAE3D,MAAM,SAAS;CACf,MAAM,OAAO,OAAO;CACpB,IAAI,OAAO,SAAS,YAAY,SAAS,IACrC,MAAM,YAAY,8BAA8B,eAAe;CAMnE,OAAO;EAAE;EAAM,OAHX,OAAO,aAAa,KAAA,IACd,gBACA,aAAa,OAAO,UAAU,gBAAgB;CACnC;AACzB;;AASA,SAAS,YAA8B,OAAgB,KAAa,QAAyB;CACzF,IAAI,OAAO,UAAU,YAAY,CAAE,OAA6B,SAAS,KAAK,GAC1E,MAAM,YAAY,OAAO,IAAI,OAAO,OAAO,KAAK,KAAK,EAAE,IAAI,eAAe;CAE9E,OAAO;AACX;;AAGA,SAAS,aACL,OACA,KACA,KACI;CACJ,IAAI,IAAI,SAAS,KAAA,GACb;CAEJ,IAAI,OAAO,IAAI,SAAS,WACpB,MAAM,YAAY,WAAW,IAAI,UAAU,eAAe;CAE9D,MAAM,OAAO,IAAI;AACrB;;AAGA,SAAS,mBAAmB,OAAuB,KAAoC;CACnF,IAAI,IAAI,aAAa,KAAA,GACjB,MAAM,QAAQ,aAAa,IAAI,UAAU,WAAW;CAExD,IAAI,IAAI,cAAc,KAAA,GAClB,MAAM,SAAS,YAAY,IAAI,WAAW,cAAc,iBAAiB;CAE7E,IAAI,IAAI,iBAAiB,KAAA,GACrB,MAAM,YAAY,YAAY,IAAI,cAAc,iBAAiB,qBAAqB;CAE1F,aAAa,OAAO,KAAK,QAAQ;CACjC,aAAa,OAAO,KAAK,gBAAgB;AAC7C;;;AAIA,SAAS,kBAAkB,OAAuB,KAAyB;CACvE,MAAM,WACF,cAAc,IAAI,sBAAsB,KAAK,cAAc,IAAI,qBAAqB;CACxF,IAAI,aAAa,KAAA,GAAW;EACxB,MAAM,QAAQ;EACd,IAAI,MAAM,SAAS,KAAA,KAAa,MAAM,sBAAsB,MACxD,MAAM,OAAO;GAAE,GAAG,MAAM;GAAM,OAAO;EAAS;CAEtD;CACA,MAAM,YAAY,eAAe,IAAI,uBAAuB;CAC5D,IAAI,cAAc,KAAA,GACd,MAAM,SAAS;CAEnB,IAAI,aAAa,IAAI,sBAAsB,MAAM,MAC7C,MAAM,SAAS;CAEnB,MAAM,cAAc,aAAa,IAAI,yBAAyB;CAC9D,IAAI,gBAAgB,KAAA,GAChB,MAAM,WAAW;AAEzB;;;;;;;AAQA,SAAgB,eAAe,SAAkB,MAAoB,CAAC,GAAc;CAChF,MAAM,QAAwB;EAC1B,OAAO;EACP,QAAQ;EACR,WAAW;EACX,gBAAgB;EAChB,QAAQ,CAAC;CACb;CACA,IAAI,YAAY,KAAA,GAAW;EACvB,IAAI,OAAO,YAAY,YAAY,YAAY,MAC3C,MAAM,YAAY,kBAAkB,eAAe;EAEvD,MAAM,MAAM;EACZ,mBAAmB,OAAO,GAAG;EAC7B,MAAM,SAAS,kBAAkB,IAAI,SAAS;EAC9C,IAAI,IAAI,YAAY,KAAA,GAAW;GAC3B,MAAM,OAAO,aAAa,IAAI,SAAS,MAAM,KAAK;GAClD,MAAM,oBACF,OAAO,IAAI,YAAY,YACvB,IAAI,YAAY,QACf,IAAI,OAAO,CAA6B,aAAa,KAAA;EAC9D;CACJ;CACA,kBAAkB,OAAO,GAAG;CAE5B,OAAO,MAAM;CACb,OAAO;AACX;;;;;;;;;;;;;;;;;;AC9OA,MAAM,eAAuC;CACzC,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACR;;AAGA,MAAM,eAAuC;CACzC,OAAO;CACP,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACP,OAAO;AACX;AACA,MAAM,MAAM;AACZ,MAAM,QAAQ;;AAed,SAAS,KAAK,GAAmB;CAC7B,OAAO,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG;AACpC;;AAGA,SAAS,aAAa,SAAyB;CAC3C,MAAM,IAAI,IAAI,KAAK,OAAO;CAC1B,MAAM,gBAAgB,CAAC,EAAE,kBAAkB;CAC3C,MAAM,OAAO,iBAAiB,IAAI,MAAM;CACxC,MAAM,MAAM,KAAK,IAAI,aAAa;CAClC,OACI,GAAG,EAAE,YAAY,EAAE,GAAG,KAAK,EAAE,SAAS,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,QAAQ,CAAC,EAAA,GAC5D,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,KAAK,EAAE,WAAW,CAAC,EAAE,GAAG,KAAK,EAAE,WAAW,CAAC,EAAA,GACjE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,SAAS,GAAG,GAAG,IAC5C,OAAO,KAAK,KAAK,MAAM,MAAM,EAAE,CAAC,EAAE,GAAG,KAAK,MAAM,EAAE;AAE7D;;AAGA,SAAS,eAAe,SAAyB;CAC7C,MAAM,IAAI,IAAI,KAAK,OAAO;CAC1B,OAAO,GAAG,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,KAAK,EAAE,WAAW,CAAC,EAAE,GAAG,KAAK,EAAE,WAAW,CAAC,EAAE,GAAG,OAC5E,EAAE,gBAAgB,CACtB,CAAC,CAAC,SAAS,GAAG,GAAG;AACrB;;AAGA,SAAS,aAAa,SAAiB,cAA8B;CACjE,OAAO,MAAM,UAAU,gBAAgB,IAAA,CAAM,QAAQ,CAAC,EAAE;AAC5D;;AAGA,SAAS,WACL,SACA,MACA,UACA,cACM;CACN,IAAI,OAAO,YAAY,UAEnB,OAAO;CAEX,MAAM,QAAQ,OAAO,YAAY,WAAW,UAAU;CACtD,IAAI,YAAY,SAAS,WACrB,OAAO,aAAa,KAAK;CAE7B,IAAI,SAAS,WACT,OAAO,aAAa,OAAO,YAAY;CAE3C,OAAO,eAAe,KAAK;AAC/B;;AAGA,SAAS,WAAW,KAAsB;CACtC,IAAI,OAAO,QAAQ,UACf,OAAO,aAAa,QAAQ,OAAO,GAAG;CAE1C,OAAO,OAAO,QAAQ,WAAW,IAAI,YAAY,IAAI;AACzD;;AAGA,SAAS,YAAY,OAAwB;CACzC,IAAI,OAAO,UAAU,UACjB,OAAO;CAEX,OAAO,KAAK,UAAU,KAAK,KAAK;AACpC;;AAGA,MAAM,gCAAgB,IAAI,IAAI;CAAC;CAAO;CAAY;CAAS;CAAQ;CAAO;AAAM,CAAC;;AAUjF,SAAS,eAAe,QAAiC,KAA4B;CACjF,MAAM,EAAE,MAAM,OAAO,MAAM,QAAQ,SAAS,KAAK,KAAK,MAAM,GAAG,WAAW;CAC1E,MAAM,MAAM,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;CAChE,MAAM,WAAW;EACb,MAAM,WAAW,MAAM,IAAI,WAAW,MAAM,IAAI,YAAY;EAC5D,OAAO,WAAW,KAAK,CAAC,CAAC,YAAY;EACrC,GAAI,WAAW,KAAA,IACT,EAAU,OAAiB,IAC3B,OAAO,SAAS,WACd,EAAE,QAAQ,KAAK,IACf,CAAC;EACT,GAAI,YAAY,KAAA,IAAY,EAAW,QAAkB,IAAI,CAAC;EAC9D,GAAI,OAAO,QAAQ,WAAW,EAAE,IAAI,IAAI,CAAC;EACzC;EACA,GAAG;CACP;CACA,OAAO,GAAG,KAAK,UAAU,QAAQ,EAAE;AACvC;;AAGA,SAAS,WAAW,QAAyC;CACzD,MAAM,MAAM,OAAO,UAAU;CAC7B,MAAM,OAAO,OAAO;CACpB,OAAO,OAAO,SAAS,WAAW,IAAI,KAAK,GAAG,IAAI,KAAK,IAAI,IAAI;AACnE;;AAGA,SAAS,eAAe,QAAyC;CAC7D,MAAM,SAAmB,CAAC;CAC1B,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC5C,IAAI,CAAC,cAAc,IAAI,GAAG,GACtB,OAAO,KAAK,GAAG,IAAI,GAAG,YAAY,KAAK,GAAG;CAGlD,OAAO,OAAO,SAAS,IAAI,IAAI,OAAO,KAAK,GAAG,MAAM;AACxD;;AAGA,SAAS,iBAAiB,QAAiC,KAA4B;CACnF,MAAM,QAAQ,WAAW,OAAO,QAAQ;CACxC,MAAM,MAAM,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;CAChE,MAAM,OAAO,WAAW,OAAO,SAAS,IAAI,WAAW,OAAO,IAAI,YAAY;CAC9E,MAAM,aAAa,MAAM,YAAY;CACrC,MAAM,OAAO,WAAW,MAAM;CAC9B,MAAM,eAAe,eAAe,MAAM;CAC1C,IAAI,IAAI,UAAU;EACd,MAAM,QAAQ,aAAa,MAAM,YAAY,MAAM;EACnD,OAAO,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,aAAa,MAAM,GAAG,MAAM,KAAK,GAAG,MAAM,GAAG,MAAM,aAAa;CAC9G;CACA,OAAO,IAAI,KAAK,IAAI,WAAW,GAAG,KAAK,IAAI,MAAM,aAAa;AAClE;;AAGA,SAAgB,0BACZ,MACsB;CACtB,MAAM,MAAM,KAAK,eAAe,QAAQ;CACxC,MAAM,MAAqB;EACvB,WAAW,KAAK;EAChB,cAAc,YAAY;EAC1B,UAAU,KAAK,WAAW,iBAAiB,KAAK;CACpD;CACA,MAAM,SAAS,KAAK,WAAW,SAAS,iBAAiB;CAEzD,OAAO,EACH,MAAM,MAAc;EAChB,IAAI;EACJ,IAAI;GACA,SAAS,KAAK,MAAM,IAAI;EAC5B,QAAQ;GAEJ,IAAI,MAAM,GAAG,KAAK,GAAG;GACrB;EACJ;EACA,IAAI,MAAM,OAAO,QAAQ,GAAG,CAAC;CACjC,EACJ;AACJ;;;;;;;;;;;;;;;;AC3LA,MAAM,uBAAuB;CAAC;CAAS;CAAU;CAAU;CAAY;AAAQ;;AAG/E,MAAM,cAA+C;CACjD;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAGA,SAAS,SAAS,GAAyB,GAA+C;CACtF,OAAO,YAAY,QAAQ,CAAC,KAAK,YAAY,QAAQ,CAAC,IAAI,IAAI;AAClE;;AAGA,SAAS,SAAiB;CACtB,OAAO,YAAY,aAAa,YAAY,IAAI;AACpD;;AAGA,SAAS,kBAA0B;CAC/B,OAAO,KAAK,YAAY,IAAI,IAAI,IAAA,CAAM,QAAQ,CAAC,EAAE;AACrD;AAEA,SAAS,SAAS,SAA4E;CAC1F,MAAM,CAAC,QAAQ;CACf,IAAI,SAAS,KAAA,GAET,MAAM,IAAI,YAAY,uBAAuB,SAAS;CAE1D,OAAO,KAAK;AAChB;;AAuDA,SAAS,mBAAmB,QAAqD;CAC7E,MAAM,EAAE,QAAQ,eAAe,UAAU,gBAAgB;CACzD,IAAI,WAAW,UACX,OAAO,0BAA0B;EAC7B;EACA,WAAW;EACX;EACA;CACJ,CAAC;CAEL,OAAO,WAAW;EACd;EAOA,GAAI,kBAAkB,UAAU,EAAE,eAAe,iBAA0B,IAAI,CAAC;EAChF,GAAI,kBAAkB,YAChB,EAAE,eAAe,gCAAyC,IAC1D,CAAC;EAKP,YAAY;EAKZ;CACJ,CAAC;AACL;;;AAYA,SAAS,oBAAoB,MAA2C;CACpE,MAAM,cAAc,KAAK,eAAe,QAAQ;CAChD,MAAM,QAAQ,YAAY,UAAU;CACpC,MAAM,SAAoB,KAAK,WAAW,QAAQ,WAAW;CAC7D,OAAO;EACH;EACA,eAAe,KAAK,aAAa;EACjC,UAAU,KAAK,aAAa,WAAW,YAAY;EACnD;CACJ;AACJ;AAEA,SAAgB,aAAa,OAAsB,CAAC,GAAgB;CAChE,MAAM,eAAe,KAAK,SAAS;CACnC,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,EAAE,MAAM,SAAS;CACvB,MAAM,SAAS,oBAAoB,IAAI;CAEvC,MAAM,UAAkF,CAAC;CACzF,KAAK,KAAK,WAAW,UAAU,MAC3B,QAAQ,KAAK;EAAE,QAAQ,mBAAmB,MAAM;EAAG,OAAO;CAAa,CAAC;CAE5E,IAAI,MAAM;EACN,UAAU,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;EAC5C,QAAQ,KAAK;GAAE,QAAQ,KAAK,YAAY;IAAE,MAAM;IAAM,MAAM;GAAK,CAAC;GAAG,OAAO;EAAU,CAAC;CAC3F;CACA,IAAI,QAAQ,WAAW,GACnB,MAAM,IAAI,YAAY,oCAAoC,eAAe;CAI7E,IAAI,OAA6B;CACjC,KAAK,MAAM,SAAS,SAChB,OAAO,SAAS,MAAM,MAAM,KAAK;CAKrC,MAAM,gBACF,OAAO,WAAW,YAAY,OAAO,kBAAkB,kBACnC,YAAY,gBAAgB,EAAE,WAC9B,WAAW,OAAO;CAE1C,MAAM,cAAkC;EACpC,OAAO;EACP,QAAQ,CAAC,GAAG,sBAAsB,GAAI,KAAK,UAAU,CAAC,CAAE;EACxD,WAAW;CACf;CACA,IAAI,MACA,YAAY,OAAO;EAAE,KAAK,QAAQ;EAAK,GAAG;CAAK;CAGnD,IAAI;CACJ,IAAI,QAAQ,WAAW,GACnB,SAAS,SAAS,OAAO;MAEzB,SAAS,KAAK,YAAY,OAAO;CAGrC,OAAO,KAAK,aAAa,MAAM;AACnC;;;;;;;;;;;;;;;;;;;;;ACjMA,MAAa,yBAAyB;;AAGtC,MAAa,mBAAmB;;AAUhC,SAAS,YAAY,GAAmB;CACpC,IAAI,MAAM,KACN,OAAO,QAAQ;CAEnB,IAAI,EAAE,WAAW,IAAI,KAAK,EAAE,WAAW,KAAK,GACxC,OAAO,KAAK,QAAQ,GAAG,EAAE,MAAM,CAAC,CAAC;CAErC,OAAO;AACX;;;;;AAMA,SAAS,qBAAyC;CAE9C,MAAM,aAAa,gBADD,QAAQ,cAAc,YAAY,GAAG,CACZ,GAAG,qBAAqB;CACnE,IAAI,eAAe,KAAA,GACf,OAAO;CAEX,OACI,gBAAgB,QAAQ,IAAI,GAAG,qBAAqB,KACpD,gBAAgB,QAAQ,IAAI,GAAG,cAAc;AAErD;;;;;;AAOA,SAAgB,gBAAgB,UAA2B;CACvD,MAAM,WAAW,YAAY,QAAQ,IAAI;CACzC,IAAI,aAAa,KAAA,KAAa,aAAa,IACvC,OAAO,QAAQ,YAAY,QAAQ,CAAC;CAExC,MAAM,cAAc,mBAAmB;CACvC,IAAI,gBAAgB,KAAA,GAChB,OAAO,KAAK,aAAa,sBAAsB;CAEnD,OAAO,KAAK,QAAQ,GAAG,sBAAsB;AACjD;;AAGA,SAAS,gBAAgB,UAAkB,QAAoC;CAC3E,IAAI,MAAM;CACV,SAAS;EACL,IAAI,WAAW,KAAK,KAAK,MAAM,CAAC,GAC5B,OAAO;EAEX,MAAM,SAAS,QAAQ,GAAG;EAC1B,IAAI,WAAW,KACX;EAEJ,MAAM;CACV;AACJ;;;;;;;;;;;;AAkBA,SAAgB,kBAAkB,OAA0B,CAAC,GAAW;CACpE,MAAM,WAAW,QAAQ,IAAI;CAC7B,IAAI,aAAa,KAAA,KAAa,aAAa,IACvC,OAAO,QAAQ,QAAQ;CAE3B,MAAM,cAAc,mBAAmB;CACvC,IAAI,gBAAgB,KAAA,GAChB,OAAO,KAAK,aAAa,gBAAgB;CAE7C,OAAO,KAAK,KAAK,YAAY,gBAAgB,GAAG,gBAAgB;AACpE;;;;;AAMA,IAAa,cAAb,MAAyB;;CAErB;;CAEA;;CAEA;;CAEA;;CAEA;CAEA,YAAY,OAAoB,CAAC,GAAG;EAChC,MAAM,OAAO,gBAAgB,KAAK,QAAQ;EAC1C,KAAK,OAAO;EACZ,IAAI,KAAK,SACL,KAAK,aAAa,KAAK,MAAM,KAAK,OAAO;OAEzC,KAAK,aAAa;EAEtB,KAAK,YAAY,KAAK,KAAK,YAAY,QAAQ;EAC/C,KAAK,UAAU,KAAK,KAAK,YAAY,MAAM;EAC3C,KAAK,WAAW,KAAK,KAAK,YAAY,OAAO;CACjD;;CAGA,SAAe;EACX,KAAK,MAAM,OAAO;GAAC,KAAK;GAAW,KAAK;GAAS,KAAK;EAAQ,GAC1D,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;CAE1C;;CAGA,KAAK,GAAG,UAA4B;EAChC,OAAO,KAAK,KAAK,YAAY,GAAG,QAAQ;CAC5C;;;;;CAMA,aAAmB;EACf,IAAI;EACJ,IAAI;GACA,UAAU,YAAY,KAAK,QAAQ;EACvC,QAAQ;GACJ;EACJ;EACA,KAAK,MAAM,SAAS,SAChB,OAAO,KAAK,KAAK,UAAU,KAAK,GAAG;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;CAE3E;AACJ;;;;;;;;;;;;;ACrKA,MAAM,oBAA2F;CAC7F;EAAE,MAAM;EAAa,UAAU,CAAC,OAAO,OAAO;CAAE;CAChD;EAAE,MAAM;EAAqB,UAAU,CAAC,OAAO,YAAY;CAAE;CAC7D;EAAE,MAAM;EAAa,UAAU,CAAC,OAAO,WAAW;CAAE;AACxD;;AAGA,SAAS,cAAc,KAA8B;CACjD,KAAK,MAAM,QAAQ,mBACf,IAAI,KAAK,SAAS,MAAM,MAAM,IAAI,SAAS,CAAC,CAAC,GACzC,OAAO,KAAK;CAGpB,OAAO;AACX;;AAGA,SAAgB,OAAO,OAAe,QAAgB,QAAuB;CACzE,IAAI,WAAW,GACX;CAEJ,MAAM,YAAY,GAAG,MAAM,OAAO,UAAU,WAAW,SAAS;AACpE;;AAGA,SAAgB,iBACZ,OACA,KACI;CACJ,IAAI,QAAQ,KAAA,KAAa,QAAQ,QAAQ,OAAO,IAAI,WAAW,YAAY,IAAI,WAAW,GACtF;CAEJ,MAAM,YAAY,GAAG,MAAM,OAAO,OAAO,IAAI,UAAU,EAAE,KAAK,SAAS;AAC3E;;AAGA,SAAgB,aAA0C,OAAe,KAAc;CACnF,IAAI,IAAI,WAAW,GACf;CAEJ,MAAM,MAAM,IAAI,UAAU;CAE1B,IAAI,OAAO,cAAc,GAAG;CAC5B,IAAI,SAAS,aAAa,UAAU,WAChC,OAAO;CAEX,MAAM,YAAY,GAAG,MAAM,OAAO,OAAO,IAAI;AACjD;;;;;;;;;;;;ACLA,IAAa,YAAb,MAAuB;CACnB;CACA;CAEA,YAAY,SAAkC,OAAyB,CAAC,GAAG;EACvE,MAAM,UAAU,QAAQ,gBAAgB;EACxC,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,yCAAyC,eAAe;EAE9E,KAAK,UAAU;EACf,KAAK,WAAW,KAAK;CACzB;;CAGA,MAAM,gBAAmC;EACrC,IAAI;EACJ,IAAI;GACA,aAAa,MAAM,KAAK,QAAQ,sBAAsB,CAAC;EAC3D,QAAQ;GACJ,MAAM,MAAM,MAAM,KAAK,QAAQ,eAAe,OAAO,MAAM,CAAC;GAC5D,IAAI,IAAI,WAAW,GACf,MAAM,YAAY,sBAAsB,IAAI,UAAU,SAAS;GAEnE,aAAa,IAAI;EACrB;EAEA,MAAM,OAAO,WAAW,SACnB,QAAQ,IAAI,WAAW,KAAK,MAAM,EAAE,GAAG,KAAK,IAAI,aAAa,CAAC,CACnE;EACA,IAAI,yBAAS,IAAI,IAAoB;EACrC,IAAI,KAAK,aAAa,KAAA,GAClB,SAAS,MAAM,KAAK,SAAS,IAAI;EAErC,MAAM,MAAgB,CAAC;EACvB,KAAK,MAAM,OAAO,MACd,IAAI,KAAK;GACL;GACA,KAAK,OAAO,IAAI,GAAG,KAAK;GACxB,UAAU,KAAK,QAAQ,aAAa,GAAG;GACvC,QAAQ,KAAK,QAAQ,eAAe,GAAG;EAC3C,CAAC;EAEL,OAAO;CACX;;CAGA,MAAM,sBAAiD;EACnD,MAAM,aAAa,MAAM,KAAK,mBAAmB;EACjD,MAAM,4BAAY,IAAI,IAAoB;EAC1C,KAAK,MAAM,UAAU,MAAM,KAAK,cAAc,GAC1C,UAAU,IAAI,OAAO,KAAK,MAAM;EAEpC,OAAO,WAAW,KAAK,QAAQ;GAC3B,MAAM,UAAoB,CAAC;GAC3B,KAAK,MAAM,OAAO,IAAI,WAAW,KAAK,MAAM,EAAE,GAAG,KAAK,IAAI,aAAa,CAAC,GAAG;IACvE,MAAM,SAAS,UAAU,IAAI,GAAG;IAChC,IAAI,WAAW,KAAA,GACX,QAAQ,KAAK,MAAM;GAE3B;GACA,OAAO;IACH,YAAY,IAAI;IAChB,gBAAgB,IAAI;IACpB,cAAc,IAAI;IAClB,iBAAiB,IAAI;IACrB,aAAa,IAAI;IACjB,WAAW;GACf;EACJ,CAAC;CACL;;CAGA,MAAc,qBAA+C;EACzD,IAAI;GACA,OAAO,MAAM,KAAK,QAAQ,sBAAsB,CAAC;EACrD,QAAQ;GACJ,MAAM,MAAM,MAAM,KAAK,QAAQ,eAAe,OAAO,MAAM,CAAC;GAC5D,IAAI,IAAI,WAAW,GACf,MAAM,YAAY,sBAAsB,IAAI,UAAU,SAAS;GAEnE,OAAO,IAAI;EACf;CACJ;;CAGA,QAAQ,KAAsB;EAC1B,OAAO,KAAK,QAAQ,QAAQ,GAAG;CACnC;;CAGA,MAAM,kBAAuC;EACzC,MAAM,MAAM,MAAM,KAAK,QAAQ,YAAY;EAC3C,OAAO,eAAe,IAAI,QAAQ,IAAI,MAAM;EAC5C,OAAO,IAAI,aAAa,CAAC;CAC7B;;CAGA,MAAM,oBAAoB,QAAkB,QAAgC;EACxE,MAAM,KAAK,QAAQ,sBAAsB;GACrC,WAAW,OAAO;GAClB,SAAS,OAAO;GAChB;EACJ,CAAC;CACL;;CAGA,gBAAgB,KAAa,QAAsB;EAC/C,KAAK,QAAQ,eAAe;GAAE;GAAK;EAAO,CAAC;CAC/C;;CAGA,MAAM,aAAa,KAAa,YAAY,OAAO,cAAc,OAAsB;EAMnF,iBAAiB,YAAY,MALV,KAAK,QAAQ,SAAS;GACrC,WAAW;GACX;GACA;EACJ,CAAC,CAC+B;CACpC;;CAGA,MAAM,sBAAsB,OAAkD;EAC1E,MAAM,QAAQ,OAAO,KAAK,IAAI,CAAC;EAC/B,MAAM,MAAM,MAAM,KAAK,QAAQ,iBAAiB,OAAO,OAAO,EAAE;EAChE,OAAO,oBAAoB,IAAI,QAAQ,IAAI,MAAM;EACjD,MAAM,OAAwB,IAAI,aAAa,CAAC;EAChD,IAAI,yBAAS,IAAI,IAAoB;EACrC,IAAI,KAAK,aAAa,KAAA,KAAa,KAAK,SAAS,GAC7C,SAAS,MAAM,KAAK,SAAS,KAAK,KAAK,SAAS,KAAK,GAAG,CAAC;EAE7D,MAAM,MAAgC,CAAC;EACvC,KAAK,MAAM,QAAQ,MACf,IAAI,KAAK,KAAK,yBAAyB,MAAM,MAAM,CAAC;EAExD,OAAO;CACX;;CAGA,yBACI,MACA,QACsB;EACtB,OAAO,yBAAyB,MAAM,MAAM;CAChD;;CAGA,yBAAyB,KAAmB;EACxC,KAAK,QAAQ,sBAAsB,KAAK,IAAI,EAAE;CAClD;AACJ;;AAGA,SAAS,eACL,MACA,KACA,OACI;CACJ,IAAI,UAAU,KAAA,GACV,KAAK,OAAO;AAEpB;;AAGA,SAAgB,yBACZ,MACA,QACsB;CACtB,MAAM,OAA+B;EACjC,MAAM,KAAK;EACX,KAAK,OAAO,OAAO,IAAI,KAAK,GAAG,KAAK,KAAK,GAAG;EAC5C,MAAM;CACV;CACA,eAAe,MAAM,QAAQ,KAAK,IAAI;CACtC,eAAe,MAAM,UAAU,KAAK,MAAM;CAC1C,eAAe,MAAM,UAAU,KAAK,MAAM;CAC1C,eAAe,MAAM,OAAO,KAAK,GAAG;CACpC,eAAe,MAAM,cAAc,KAAK,SAAS;CACjD,eAAe,MAAM,QAAQ,KAAK,OAAO;CACzC,OAAO;AACX;;;;;;;;;;ACnOA,MAAa,WAAW;CACpB,KAAK;CACL,OAAO;CACP,MAAM;CACN,OAAO;CACP,UAAU;AACd;;AA4IA,MAAa,iBAAiB;CAC1B,QAAQ;CACR,cAAc;CACd,OAAO;CACP,OAAO;CACP,MAAM;CACN,SAAS;CACT,cAAc;CACd,cAAc;CACd,MAAM;CACN,MAAM;AACV;;AAIA,MAAa,sBAAsB;CAC/B,YAAY;CACZ,WAAW;CACX,MAAM;CACN,SAAS;CACT,qBAAqB;CACrB,OAAO;CACP,SAAS;CACT,SAAS;AACb;;;;AClKA,MAAa,sBAAsB;;CAE/B,KAAK;;CAEL,SAAS;AACb;;;;ACKA,MAAa,cAAc;CACvB,SAAS;CACT,MAAM;CACN,KAAK;CACL,MAAM;CACN,KAAK;CACL,OAAO;CACP,MAAM;CACN,OAAO;CACP,UAAU;CACV,QAAQ;CACR,KAAK;CACL,OAAO;CACP,WAAW;CACX,iBAAiB;CACjB,UAAU;CACV,OAAO;CACP,eAAe;CACf,iBAAiB;CACjB,aAAa;CACb,UAAU;AACd;;;;;;;;;;;ACxBA,MAAM,SAAS;CAAE,KAAK;CAAG,KAAK;CAAG,IAAI;AAAE;;;;;;AAOvC,MAAME,oBAAkB;;;;;;;;;;;AAYxB,SAAS,mBAAmB,OAAsD;CAC9E,IAAI,UAAU,KAAA,KAAa,UAAU,IACjC;CAEJ,IAAI,OAAO,UAAU,UACjB,OAAO;CAEX,IAAI,iBAAiB,KAAK;EACtB,MAAM,QAAQ,MAAM,IAAIA,iBAAe;EACvC,OAAO,UAAU,KAAK,KAAA,IAAY;CACtC;CACA,MAAM,QAAQ,MAAM,OAAOA,iBAAe;CAC1C,OAAO,UAAU,KAAK,KAAA,IAAY;AACtC;;AAGA,SAAS,gBAAgB,QAAmD;CACxE,IAAI,WAAW,KAAA,GACX,OAAO;EAAE,MAAM;EAAQ,MAAM;CAAG;CAEpC,MAAM,EAAE,QAAQ,OAAO,YAAY;CACnC,IAAI,WAAW,OAAO,KAClB,OAAO;EAAE,MAAM;EAAM,QAAQ;CAAM;CAEvC,IAAI,WAAW,OAAO,OAAO,WAAW,OAAO,IAAI;EAG/C,MAAM,OAAO;GAAE,MAAM;GAAe,QAAQ,SAAS;EAAQ;EAC7D,MAAM,UAAU,QAAQ,WAAW,GAAG,IAAI,QAAQ,MAAM,CAAC,IAAI;EAC7D,IAAI,YAAY,IACZ,OAAO;EAEX,OAAO;GAAE,GAAG;GAAM;EAAQ;CAC9B;CACA,OAAO;EAAE,MAAM;EAAQ,MAAM;CAAQ;AACzC;;AAGA,SAAS,eAAe,KAA+C;CACnE,IAAI,QAAQ,KAAA,GACR,OAAO;EAAE,MAAM;EAAS,MAAM;CAAG;CAErC,MAAM,OAAO,IAAI,WAAW,IAAI,UAAU;CAC1C,MAAM,MAAM,IAAI;CAChB,IAAI,QAAQ,KAAA,GACR,OAAO;EAAE,MAAM;EAAS;CAAK;CAEjC,OAAO;EAAE,MAAM;EAAS;EAAM;CAAI;AACtC;;AAGA,SAAS,gBAAgB,MAAiD;CACtE,IAAI,SAAS,KAAA,GACT,OAAO;EAAE,MAAM;EAAQ,IAAI;CAAI;CAEnC,OAAO;EAAE,MAAM;EAAQ,IAAI,OAAO,KAAK,SAAS;CAAE;AACtD;;AAGA,SAAS,eAAe,KAA+C;CACnE,IAAI,QAAQ,KAAA,GACR,OAAO;EAAE,MAAM;EAAS,MAAM;CAAG;CAErC,OAAO;EAAE,MAAM;EAAS,MAAM,IAAI,YAAY;CAAG;AACrD;;AAGA,SAAS,iBAAiB,OAAmD;CACzE,IAAI,UAAU,KAAA,GACV,OAAO;EAAE,MAAM;EAAS,MAAM;CAAG;CAGrC,MAAM,OAAqD;EAAE,MAAM;EAAS,MAD/D,MAAM,YAAY,MAAM,YAAY;CACgC;CACjF,IAAI,MAAM,aAAa,KAAA,GACnB,KAAK,MAAM,MAAM;CAGrB,MAAM,QAAQ,mBAAmB,MAAM,SAAS;CAChD,IAAI,UAAU,KAAA,GACV,KAAK,YAAY;CAErB,IAAI,MAAM,eAAe,KAAA,KAAa,MAAM,aAAa,GACrD,KAAK,QAAQ,MAAM;CAEvB,IAAI,MAAM,gBAAgB,KAAA,KAAa,MAAM,cAAc,GACvD,KAAK,SAAS,MAAM;CAExB,IAAI,MAAM,aAAa,KAAA,KAAa,MAAM,WAAW,GACjD,KAAK,cAAc,MAAM;CAE7B,OAAO;AACX;;AAGA,SAAS,gBAAgB,MAAiD;CACtE,IAAI,SAAS,KAAA,GACT,OAAO;EAAE,MAAM;EAAQ,MAAM;CAAG;CAEpC,MAAM,OAAO,KAAK,YAAY;CAC9B,MAAM,OAAO,KAAK;CAClB,IAAI,SAAS,KAAA,GACT,OAAO;EAAE,MAAM;EAAQ;CAAK;CAEhC,OAAO;EAAE,MAAM;EAAQ;EAAM;CAAK;AACtC;;AAGA,SAAS,iBAAiB,OAAmD;CACzE,IAAI,UAAU,KAAA,GACV,OAAO;EAAE,MAAM;EAAS,WAAW;CAAG;CAE1C,OAAO;EAAE,MAAM;EAAS,WAAW,MAAM;CAAY;AACzD;;;;;;;;AASA,SAAS,eAAe,KAAwC,IAAkC;CAC9F,MAAM,QAAQ,KAAK;CACnB,IAAI,UAAU,KAAA,KAAa,UAAU,IACjC,OAAO;EAAE,MAAM;EAAW,KAAK;CAAG;CAEtC,OAAO;EAAE,MAAM;EAAQ,KAAK;CAAM;AACtC;;AAGA,SAAS,mBAAmB,IAAkC;CAC1D,QAAQ,GAAG,aAAX;EACI,KAAK,YAAY,MACb,OAAO,gBAAgB,GAAG,WAAW;EACzC,KAAK,YAAY,KACb,OAAO,eAAe,GAAG,UAAU;EACvC,KAAK,YAAY,MACb,OAAO,gBAAgB,GAAG,WAAW;EACzC,KAAK,YAAY,KACb,OAAO,eAAe,GAAG,UAAU;EACvC,KAAK,YAAY,OACb,OAAO,iBAAiB,GAAG,YAAY;EAC3C,KAAK,YAAY,MACb,OAAO,gBAAgB,GAAG,WAAW;EACzC,KAAK,YAAY,OACb,OAAO,iBAAiB,GAAG,YAAY;EAC3C,KAAK,YAAY,KACb,OAAO,eAAe,GAAG,YAAY,EAAE;EAC3C,SACI,OAAO;GAAE,MAAM;GAAW,KAAK;EAAG;CAC1C;AACJ;;AAGA,SAAgB,oBAAoB,KAAqC;CACrE,OAAO,IAAI,SAAS,IAAI,kBAAkB;AAC9C;;;;;;AAOA,SAAS,eAAe,KAAsB;CAC1C,IAAI,OAAO,QAAQ,UACf,OAAO;CAEX,IAAI;EACA,OAAO,KAAK,UAAU,GAAG,KAAK;CAClC,SAAS,KAAK;EACV,MAAM,YAAY,iBAAiB,OAAO,GAAG,KAAK,eAAe;CACrE;AACJ;;AAGA,SAAgB,eAAe,UAAoD;CAC/E,MAAM,MAA4B,CAAC;CACnC,KAAK,MAAM,MAAM,UACb,QAAQ,GAAG,MAAX;EACI,KAAK;GACD,IAAI,KAAK;IAAE,aAAa,YAAY;IAAM,aAAa,EAAE,SAAS,GAAG,KAAK;GAAE,CAAC;GAC7E;EACJ,KAAK;GACD,IAAI,GAAG,WAAW,OACd,IAAI,KAAK;IACL,aAAa,YAAY;IACzB,aAAa;KAAE,SAAS,GAAG,WAAW;KAAK,QAAQ;IAAE;GACzD,CAAC;QAED,IAAI,KAAK;IACL,aAAa,YAAY;IACzB,aAAa;KAAE,SAAS,GAAG,WAAW;KAAK,QAAQ;KAAG,OAAO,GAAG;IAAO;GAC3E,CAAC;GAEL;EACJ,KAAK;GACD,IAAI,KAAK;IACL,aAAa,YAAY;IACzB,aAAa,EAAE,WAAW,OAAO,GAAG,EAAE,EAAE;GAC5C,CAAC;GACD;EACJ,KAAK;GACD,IAAI,KAAK;IAAE,aAAa,YAAY;IAAK,YAAY,EAAE,SAAS,GAAG,KAAK;GAAE,CAAC;GAC3E;EACJ,KAAK;GACD,IAAI,KAAK;IAAE,aAAa,YAAY;IAAK,YAAY,EAAE,UAAU,GAAG,KAAK;GAAE,CAAC;GAC5E;EACJ,KAAK;GACD,IAAI,KAAK;IACL,aAAa,YAAY;IACzB,cAAc;KACV,aAAa,GAAG;KAChB,cAAc,GAAG;KACjB,eAAe;IACnB;GACJ,CAAC;GACD;EACJ,KAAK,SAID,MAAM,YACF,0CACA,eACJ;EACJ,KAAK;GAID,IAAI,KAAK;IACL,aAAa,YAAY;IACzB,YAAY,EAAE,WAAW,eAAe,GAAG,GAAG,EAAE;GACpD,CAAC;GACD;EACJ,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,WACD,MAAM,YAAY,QAAQ,GAAG,KAAK,mBAAmB,eAAe;CAI5E;CAEJ,OAAO;AACX;;;;ACrRA,MAAa,oBAAoB;CAC7B,aAAa;CACb,UAAU;CACV,QAAQ;CACR,OAAO;CACP,OAAO;AACX;;AA8BA,MAAa,qBAAqB;CAC9B,cAAc;CACd,mBAAmB;CACnB,gBAAgB;CAChB,2BAA2B;CAC3B,sBAAsB;CACtB,kCAAkC;CAClC,kCAAkC;CAClC,uCAAuC;CACvC,WAAW;CACX,0BAA0B;CAC1B,2BAA2B;CAC3B,2BAA2B;CAC3B,8BAA8B;CAC9B,2BAA2B;CAC3B,gCAAgC;CAChC,6BAA6B;AACjC;;AAIA,MAAa,uBAAuB;CAChC,OAAO;CACP,WAAW;CACX,SAAS;CACT,UAAU;CACV,UAAU;AACd;;AAIA,MAAa,6BAA6B;CACtC,cAAc;CACd,QAAQ;CACR,SAAS;CACT,SAAS;CACT,SAAS;AACb;;;;;;;;;;;;;ACxEA,MAAa,eAAe;CACxB,UAAU;CACV,UAAU;CACV,MAAM;CACN,UAAU;CACV,MAAM;CACN,QAAQ;AACZ;;AAIA,MAAa,aAAa;CACtB,aAAa;CACb,uBAAuB;CACvB,cAAc;CACd,aAAa;CACb,kBAAkB;CAClB,kBAAkB;CAClB,oBAAoB;AACxB;;;;;;;;;;;ACRA,IAAa,WAAb,MAAsB;CAClB;;CAEA;CAEA,YAAY,SAAkC;EAC1C,MAAM,UAAU,QAAQ,gBAAgB;EACxC,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,yCAAyC,eAAe;EAE9E,KAAK,UAAU;EACf,MAAM,aAAa,QAAQ,cAAc;EACzC,IAAI,eAAe,QAAQ,eAAe,KAAA,GACtC,MAAM,YAAY,uCAAuC,eAAe;EAE5E,KAAK,aAAa;CACtB;;;;;;;;;CAUA,MAAM,aAAa,QAAQ,OAAyB;EAChD,MAAM,MAAM,MAAM,KAAK,QAAQ,aAAa,KAAK;EACjD,IAAI,IAAI,WAAW,GACf,OAAO,gBAAgB,IAAI,QAAQ,IAAI,MAAM;EAEjD,OAAO,CAAC;CACZ;;;;;;CAOA,MAAM,aAAa,WAA6C;EAC5D,MAAM,MAAM,MAAM,KAAK,QAAQ,mBAAmB,WAAW,CAAC;EAC9D,MAAM,EAAE,QAAQ,WAAW;EAC3B,IAAI,OAAO,WAAW,YAAY,WAAW,GACzC,OAAO,sBAAsB,QAAQ,MAAM;EAE/C,MAAM,SAAS,mBAAmB,QAAQ,GAAG;EAC7C,IAAI,WAAW,MACX,MAAM,YACF,uCAAuC,UAAU,KAAK,UACtD,SACJ;EAEJ,OAAO;CACX;;CAGA,MAAM,mBAAmB,WAAmB,aAAa,OAA+B;EACpF,MAAM,MAAM,MAAM,KAAK,QAAQ,iBAAiB,WAAW,UAAU;EACrE,IAAI,IAAI,YAAY,GAChB,MAAM,YAAY,wBAAwB,IAAI,UAAU,SAAS;EAErE,OAAO,CAAC,GAAG,IAAI,OAAO,MAAM,OAAO,CAAC;CACxC;;CAGA,MAAM,mBAAmB,WAAmB,MAAwC;EAChF,IAAI,KAAK,WAAW,GAChB,OAAO,CAAC;EAEZ,MAAM,MAAM,MAAM,KAAK,QAAQ,cAAc,WAAW,MAAM,IAAI;EAClE,OAAO,iBAAiB,IAAI,QAAQ,IAAI,MAAM;EAE9C,MAAM,EAAE,UAAU;EAClB,IAAI,iBAAiB,KACjB,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC;EAE7B,OAAO,CAAC;CACZ;;CAGA,MAAM,SAAS,MAA8C;EACzD,IAAI,KAAK,WAAW,GAChB,uBAAO,IAAI,IAAI;EAEnB,MAAM,MAAM,MAAM,KAAK,QAAQ,aAAa,IAAI;EAChD,IAAI,IAAI,YAAY,GAChB,MAAM,YAAY,oBAAoB,IAAI,UAAU,SAAS;EAEjE,OAAO,IAAI;CACf;;CAGA,MAAM,SAAS,MAA8C;EACzD,IAAI,KAAK,WAAW,GAChB,uBAAO,IAAI,IAAI;EAEnB,MAAM,MAAM,MAAM,KAAK,QAAQ,aAAa,IAAI;EAChD,IAAI,IAAI,YAAY,GAChB,MAAM,YAAY,oBAAoB,IAAI,UAAU,SAAS;EAEjE,OAAO,IAAI;CACf;;CAGA,OAAO,aAAa,MAAuD;EACvE,IAAI,SAAS,kBAAkB,OAC3B,OAAO;EAEX,IAAI,SAAS,kBAAkB,OAC3B,OAAO;EAEX,OAAO;CACX;;;;;CAMA,MAAM,WACF,WACA,YACA,eACA,SAAS,IACI;EACb,IAAI,WAAW,WAAW,GACtB,MAAM,YAAY,2BAA2B,eAAe;EAGhE,IAAI,UAAU;EACd,IAAI,eACA,UAAU;EAEd,MAAM,MAAuB;GACzB;GACA,UAAU;GACV,UAAU,WAAW,KAAK,SAAS;IAC/B;IACA,YAAY;IACZ,cAAc;IACd,aAAa;GACjB,EAAE;GACF,cAAc;GACd,SAAS;EACb;EACA,MAAM,MAAM,MAAM,KAAK,QAAQ,aAAa,GAAG;EAC/C,OAAO,gBAAgB,IAAI,QAAQ,IAAI,MAAM;CACjD;;CAGA,MAAM,gBACF,WACA,SACa;EACb,IAAI,QAAQ,WAAW,GACnB,MAAM,YAAY,4BAA4B,eAAe;EAEjE,MAAM,MAAM,MAAM,KAAK,QAAQ,gBAC3B,WACA,QAAQ,KAAK,OAAO;GAAE,KAAK,EAAE;GAAK,WAAW,EAAE;EAAS,EAAE,CAC9D;EACA,OAAO,mBAAmB,IAAI,QAAQ,IAAI,MAAM;CACpD;;CAGA,MAAM,eAAe,WAAmB,QAAgC;EACpE,MAAM,MAAM,MAAM,KAAK,QAAQ,eAAe,WAAW,MAAM;EAC/D,OAAO,kBAAkB,IAAI,QAAQ,IAAI,MAAM;CACnD;;CAGA,cAAc,WAAmB,KAAa,MAA+B;EACzE,KAAK,QAAQ,iBAAiB,WAAW,KAAK,IAAI;CACtD;;CAGA,kBAAkB,WAAmB,KAAa,UAAwB;EACtE,KAAK,QAAQ,qBAAqB,WAAW,KAAK,QAAQ;CAC9D;;CAGA,MAAM,gBAAgB,WAAmB,WAAkC;EACvE,MAAM,MAAM,MAAM,KAAK,QAAQ,gBAAgB,WAAW,WAAW,KAAK;EAC1E,OAAO,mBAAmB,IAAI,QAAQ,IAAI,MAAM;CACpD;;CAGA,MAAM,UAAU,WAAmB,qBAAqB,OAAsB;EAC1E,MAAM,MAAM,MAAM,KAAK,QAAQ,YAAY;GAAE;GAAW;EAAmB,CAAC;EAC5E,OAAO,eAAe,IAAI,QAAQ,IAAI,MAAM;CAChD;;CAGA,MAAM,gBAAgB,WAAmB,OAA8B;EACnE,MAAM,KAAK,WAAW,WAAW,OAAO,IAAI;CAChD;;CAGA,MAAM,mBAAmB,WAAmB,OAA8B;EACtE,MAAM,KAAK,WAAW,WAAW,OAAO,KAAK;CACjD;;CAGA,MAAc,WAAW,WAAmB,OAAe,KAA6B;EACpF,MAAM,OAAO;GAAE,UAAU,SAAS;GAAO,SAAS;EAAU;EAC5D,MAAM,MAAM,MAAM,KAAK,WAAW,eAAe,MAAM,CAAC,KAAK,CAAC;EAC9D,OAAO,kBAAkB,IAAI,QAAQ,IAAI,MAAM;EAC/C,MAAM,MAAM,IAAI,UAAU;EAC1B,IAAI,QAAQ,KAAA,GACR,MAAM,YAAY,MAAM,MAAM,OAAO,WAAW;EAEpD,MAAM,QAAQ;GACV;GACA,WAAW,OAAO,IAAI,aAAa,CAAC;GACpC,QAAQ,OAAO,IAAI,MAAM;EAC7B;EACA,IAAI,QAAQ;EACZ,IAAI;EACJ,IAAI,KAAK;GACL,QAAQ;GACR,MAAO,MAAM,KAAK,QAAQ,gBAAgB,KAAK;EAGnD,OACI,MAAO,MAAM,KAAK,QAAQ,mBAAmB,KAAK;EAItD,iBAAiB,OAAO,GAAG;CAC/B;;;;;CAMA,MAAM,sBAAsB,WAA6C;EACrE,IAAI;EACJ,IAAI;GACA,MAAO,MAAM,KAAK,QAAQ,iBAAiB,SAAS;EACxD,SAAS,GAAG;GACR,IAAI,cAAc,CAAC,GACf,MAAM;GAEV,MAAM,YAAY,0BAA0BC,eAAa,CAAC,KAAK,SAAS;EAC5E;EACA,OAAO,oBAAoB,eAAe,IAAI,MAAM,GAAG,IAAI,MAA4B;EACvF,OAAO,iBAAiB,IAAI,MAAM;CACtC;;;;;CAMA,MAAM,yBACF,WACA,SACA,WACa;EACb,IAAI;EACJ,IAAI,cAAc,KAAA,KAAa,cAAc,IACzC,QAAQ,MAAM,KAAK,kBAAkB,WAAW,SAAS;EAE7D,IAAI;GACA,iBACI,wBACC,MAAM,KAAK,QAAQ,qBAAqB;IACrC;IACA;IACA,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;GAC3C,CAAC,CACL;EACJ,SAAS,GAAG;GACR,IAAI,cAAc,CAAC,GACf,MAAM;GAEV,MAAM,YAAY,8BAA8BA,eAAa,CAAC,KAAK,SAAS;EAChF;CACJ;;CAGA,MAAM,wBAAwB,WAAmB,gBAAuC;EACpF,IAAI;GACA,iBACI,uBACC,MAAM,KAAK,QAAQ,oBAAoB,WAAW,cAAc,CAIrE;EACJ,SAAS,GAAG;GACR,IAAI,cAAc,CAAC,GACf,MAAM;GAEV,MAAM,YAAY,6BAA6BA,eAAa,CAAC,KAAK,SAAS;EAC/E;CACJ;;CAGA,MAAM,sBAAsB,WAAmB,KAAa,OAA8B;EACtF,IAAI;GACA,iBACI,wBACC,MAAM,KAAK,QAAQ,qBAAqB,WAAW,KAAK,KAAK,CAIlE;EACJ,SAAS,GAAG;GACR,IAAI,cAAc,CAAC,GACf,MAAM;GAEV,MAAM,YAAY,8BAA8BA,eAAa,CAAC,KAAK,SAAS;EAChF;CACJ;;CAGA,MAAM,eAAe,WAAmB,WAAkC;EACtE,IAAI;GACA,iBACI,aACC,MAAM,KAAK,QAAQ,UAAU,WAAW,SAAS,CAItD;EACJ,SAAS,GAAG;GACR,IAAI,cAAc,CAAC,GACf,MAAM;GAEV,MAAM,YAAY,0BAA0BA,eAAa,CAAC,KAAK,SAAS;EAC5E;CACJ;;CAGA,MAAc,kBAAkB,WAAmB,WAAoC;EACnF,MAAM,MAAO,MAAM,KAAK,QAAQ,uBAAuB,WAAW,SAAS;EAO3E,iBAAiB,0BAA0B,GAAG;EAC9C,OAAO,OAAO,KAAK,UAAU,WAAW,IAAI,QAAQ;CACxD;;CAGA,MAAM,sBAAsB,WAIzB;EACC,MAAM,MAAM,MAAM,KAAK,QAAQ,sBAAsB,SAAS;EAC9D,IAAI,IAAI,YAAY,GAChB,MAAM,YAAY,6BAA6B,IAAI,UAAU,SAAS;EAE1E,OAAO;GACH,UAAU,IAAI,OAAO;GACrB,wBAAwB,IAAI,OAAO;GACnC,0BAA0B,IAAI,OAAO;EACzC;CACJ;AACJ;;AAGA,SAAS,eAAe,QAAyB;CAC7C,OAAO,OAAO,WAAW,WAAW,SAAS;AACjD;;AAGA,SAAS,iBAAiB,QAAkC;CACxD,MAAM,aAAa,CAAC,MAAM;CAC1B,IAAI,WAAW,QAAQ,OAAO,WAAW,UAAU;EAC/C,MAAM,MAAM;EACZ,KAAK,MAAM,OAAO;GAAC;GAAa;GAAoB;GAAU;EAAM,GAChE,WAAW,KAAK,IAAI,IAAI;CAEhC;CACA,KAAK,MAAM,aAAa,YACpB,IAAI,MAAM,QAAQ,SAAS,GACvB,OAAO,UAAU,QACZ,SAAgC,SAAS,QAAQ,OAAO,SAAS,QACtE;CAGR,OAAO,CAAC;AACZ;;AAGA,SAASA,eAAa,GAAoB;CACtC,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACpD;;;;;;;AAQA,SAAgB,mBAAmB,QAAiB,KAAsC;CACtF,IAAI,WAAW,QAAQ,OAAO,WAAW,UACrC,OAAO;CAEX,MAAM,SAAS;CAKf,MAAM,YAAY,OAAO,aAAa,OAAO,cAAc,OAAO;CAClE,IAAI,cAAc,KAAA,KAAa,cAAc,QAAQ,OAAO,cAAc,UACtE,OAAO;CAEX,OAAO;AACX;;;;;;;;;;;AChaA,IAAa,iBAAb,MAA4B;CACxB;CAEA,YAAY,SAAkC;EAC1C,MAAM,UAAU,QAAQ,gBAAgB;EACxC,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,yCAAyC,eAAe;EAE9E,KAAK,UAAU;CACnB;;CAGA,MAAM,wBAAwB,OAAgB,OAAuC;EAEjF,OAAO,mBAAkB,MADP,KAAK,QAAQ,wBAAwB,OAAO,IAAI,KAAK,EAAA,CAC1C,MAAM;CACvC;;CAGA,MAAM,yBAAyB,WAA2C;EACtE,MAAM,MAAM,MAAM,KAAK,QAAQ,yBAAyB,SAAS;EACjE,IAAI,IAAI,WAAW,GACf,OAAO,4BAA4B,IAAI,QAAQ,IAAI,MAAM;EAE7D,OAAO,kBAAkB,IAAI,MAAM;CACvC;;CAGA,MAAM,mBACF,OACA,QACA,aACA,QACa;EACb,MAAM,EAAE,OAAO,KAAK,SAAS;EAC7B,IAAI,YAAY;EAChB,IAAI,UAAU,KAAA,GACV,CAAC,CAAE,aAAc;EAErB,IAAI,aAAa;EACjB,IAAI,eAAe,IACf,aAAa;EAEjB,MAAM,KAAK,QAAQ,iBAAiB,OAAO;GACvC;GACA,WAAW;IACP;IACA;IACA;IACA;GACJ;EACJ,CAAC;CACL;AACJ;;AAGA,SAAgB,kBAAkB,QAAgC;CAC9D,IAAI,MAAM,QAAQ,MAAM,GACpB,OAAO;CAEX,IAAI,WAAW,QAAQ,OAAO,WAAW,UAAU;EAC/C,MAAM,MAAM;EACZ,IAAI,MAAM,QAAQ,IAAI,QAAQ,GAC1B,OAAO,IAAI;EAEf,IAAI,MAAM,QAAQ,IAAI,aAAa,GAC/B,OAAO,IAAI;CAEnB;CACA,OAAO,CAAC;AACZ;;AAGA,SAAgB,kBAAkB,QAAgC;CAC9D,IAAI,MAAM,QAAQ,MAAM,GACpB,OAAO;CAEX,IAAI,WAAW,QAAQ,OAAO,WAAW,UAAU;EAC/C,MAAM,MAAM;EACZ,IAAI,MAAM,QAAQ,IAAI,UAAU,GAC5B,OAAO,IAAI;EAEf,IAAI,MAAM,QAAQ,IAAI,IAAI,GACtB,OAAO,IAAI;CAEnB;CACA,OAAO,CAAC;AACZ;;;;;;;;;;;;AC3EA,MAAM,cAAc;CAAE,QAAQ;CAAG,SAAS;CAAG,SAAS;CAAG,gBAAgB;AAAE;;AAG3E,MAAM,0BAA0B;;;;;;AAOhC,MAAM,WAAW;AACjB,MAAM,eAAe;AACrB,MAAM,YAAY;AAClB,MAAM,aAAa;AACnB,MAAM,eAAe;AACrB,MAAM,sBAAsB;;;;;;;AAQ5B,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;AACvB,MAAM,wBAAwB;AAC9B,MAAM,yBAAyB;AAC/B,MAAM,iBAAiB;AACvB,MAAM,wBAAwB;AAC9B,MAAM,eAAe;AACrB,MAAM,oBAAoB;;AAE1B,MAAM,sBAAsB;CAAC;CAAG;CAAI;CAAG;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;AAAE;;;;;;;;;;AAWrF,MAAM,oBAAoB;AAC1B,MAAM,kBAAkB;AACxB,MAAM,sBAAsB;AAC5B,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AACnB,MAAM,4BAA4B;;AAElC,MAAM,kBAAkB;;AAExB,MAAM,kCAAkC;AACxC,MAAM,iCAAiC;;AAGvC,MAAM,uBAAuB;AAC7B,MAAM,kBAAkB;;AAExB,MAAM,uBAAuB;AAC7B,MAAM,iBAAiB;;AAGvB,SAASC,QAAM,IAA2B;CACtC,OAAO,IAAI,SAAS,YAAY;EAC5B,WAAW,SAAS,EAAE;CAC1B,CAAC;AACL;;AAGA,SAASC,eAAa,GAAoB;CACtC,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACpD;;AAGA,SAAS,kBAAkB,QAAgC,YAAwC;CAC/F,IAAI,WAAW,KAAA,GACX,OAAO;CAEX,MAAM,UAAU,OAAO,aAAa,KAAA,KAAa,OAAO,aAAa;CACrE,MAAM,OAAO,OAAO,mBAAmB,KAAK,OAAO,mBAAmB;CACtE,OAAO,YAAY,QAAQ,cAAc,SAAS;AACtD;;AAGA,SAAS,oBAAoB,MAA2B;CACpD,MAAM,MAAgB,CAAC;CACvB,KAAK,MAAM,QAAQ,MAAM;EACrB,IAAI,SAAS,QAAQ,OAAO,SAAS,UACjC;EAEJ,MAAM,MAAM;EACZ,MAAM,MAAM,IAAI,UAAU,IAAI,eAAe,IAAI;EACjD,IAAI,OAAO,QAAQ,YAAY,QAAQ,IACnC,IAAI,KAAK,GAAG;CAEpB;CACA,OAAO;AACX;;AAGA,eAAe,SAAS,MAAc,QAAQ,MAAiC;CAC3E,IAAI;EACA,OAAO,MAAM,KAAK,IAAI;CAC1B,QAAQ;EACJ,MAAM,YAAY,GAAG,MAAM,SAAS,QAAQ,eAAe;CAC/D;AACJ;;AAGA,eAAe,SAAS,MAAc,QAAQ,MAAuB;CACjE,IAAI;EACA,MAAM,MAAM,MAAM,SAAS,IAAI;EAC/B,OAAO,WAAW,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,KAAK;CACrD,QAAQ;EACJ,MAAM,YAAY,GAAG,MAAM,UAAU,QAAQ,eAAe;CAChE;AACJ;;;;;;;;;;;AAYA,SAAS,qBAAqB,QAA4B;CACtD,IAAI,CAAC,MAAM,QAAQ,MAAM,GACrB,MAAM,YAAY,gCAAgC,eAAe;CAErE,MAAM,MAAM,OAAO,QAAQ,OAAO,OAAO,OAAO,YAAY,GAAG,KAAK,MAAM,EAAE;CAC5E,IAAI,IAAI,WAAW,GACf,MAAM,YAAY,gCAAgC,eAAe;CAErE,OAAO;AACX;;AAGA,IAAa,SAAb,MAAoB;CAChB;;CAEA;;CAEA;;CAEA,cAAsB;CAEtB,YAAY,SAAkC,SAA2B,MAAgB;EACrF,MAAM,UAAU,QAAQ,cAAc;EACtC,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,uCAAuC,eAAe;EAE5E,KAAK,UAAU;EACf,KAAK,UAAU,WAAW;EAE1B,KAAK,OAAO,SAAS,QAAQ,SAAS,KAAA,IAAa,OAA8B;CACrF;;;;;;;CAQA,cAA8B;EAC1B,MAAM,MAAM,KAAK,IAAI;EACrB,IAAI,MAAM,KAAK,aACX,KAAK,cAAc;OAEnB,KAAK,eAAe;EAExB,OAAO,OAAO,KAAK,WAAW;CAClC;;;;;;;;;;;;CAaA,MAAM,YAAY,QAAc,UAA0D;EAItF,MAAM,eAAe,MAAM,KAAK,oBAAoB,QAAQ;EAC5D,MAAM,QAAQ,KAAK,QAAQ,oBAAoB,OAAO,UAAU,KAAK,YAAY,CAAC;EAElF,MAAM,WAAiB;GAAE,GAAG;GAAQ,SAAS;EAAM;EAEnD,IAAI,KAAK,YAAY,MAAM;GAEvB,aAAa,WAAW,MADN,KAAK,QAAQ,QAAQ,KAAK,UAAU,8BAAc,IAAI,IAAI,CAAC,CAClD;GAC3B,OAAO,EAAE,MAAM;EACnB;EAGA,MAAM,UAAU,KAAK,YAAY,OAAO,MAAM;EAO9C,QAAQ,YAAY,KAAA,CAAS;EAC7B,MAAM,KAAK,QAAQ,QAAQ,KAAK,UAAU,8BAAc,IAAI,IAAI,CAAC;EACjE,MAAM;EACN,OAAO,EAAE,MAAM;CACnB;;;;;;;;;CAUA,MAAc,oBAAoB,UAA6D;EAC3F,MAAM,MAA4B,CAAC;EACnC,KAAK,MAAM,MAAM,UACb,QAAQ,GAAG,MAAX;GACI,KAAK;IACD,IAAI,KAAK,MAAM,KAAK,oBAAoB,GAAG,IAAI,CAAC;IAChD;GACJ,KAAK;IACD,IAAI,KAAK,MAAM,KAAK,kBAAkB,GAAG,IAAI,CAAC;IAC9C;GACJ,KAAK;IACD,IAAI,KAAK,MAAM,KAAK,oBAAoB,EAAE,CAAC;IAC3C;GACJ,KAAK;IACD,IAAI,KAAK,MAAM,KAAK,mBAAmB,EAAE,CAAC;IAC1C;GACJ,SACI,IAAI,KAAK,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;EAExC;EAEJ,OAAO;CACX;;CAGA,MAAc,oBAAoB,MAA2C;EACzE,MAAM,OAAO,MAAM,SAAS,IAAI;EAChC,MAAM,MAAM,MAAM,SAAS,IAAI;EAC/B,MAAM,WAAW,SAAS,IAAI;EAC9B,MAAM,UAAU,MAAM,KAAK,eAAe,MAAM,YAAY,KAAK,KAAK,QAAQ;EAC9E,OAAO;GACH,aAAa,YAAY;GACzB,WAAW;GACX,YAAY;IACR,WAAW;IACX,UAAU,OAAO,KAAK,IAAI;IAC1B,UAAU;IACV,WAAW;IACX;IACA,YAAY;IACZ,UAAU;IACV,SAAS;IACT,YAAY;IACZ,UAAU;IACV,WAAW;IACX,eAAe;IACf,SAAS;GACb;EACJ;CACJ;;;;;;;;;;;;;CAcA,MAAc,kBAAkB,MAA2C;EACvE,MAAM,OAAO,MAAM,SAAS,IAAI;EAChC,MAAM,MAAM,MAAM,SAAS,IAAI;EAC/B,MAAM,WAAW,SAAS,IAAI;EAC9B,MAAM,UAAU,MAAM,KAAK,eAAe,MAAM,YAAY,KAAK,KAAK,QAAQ;EAE9E,MAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,OAAO,OAAO,CAAC,CAAC;EAC7D,OAAO;GACH,aAAa,YAAY;GACzB,WAAW;GACX,YAAY;IACR;IACA,UAAU;IACV,WAAW;IACX,UAAU,OAAO,KAAK,IAAI;IAC1B;IACA,YAAY;IACZ,WAAW;IACX,iBAAiB;IACjB,iBAAiB;IACjB,gBAAgB;IAChB,WAAW;IACX,WAAW;IACX,iBAAiB;IACjB,SAAS;IACT,mBAAmB,EAAE,aAAa,kBAAkB;GACxD;EACJ;CACJ;;;;;;;;;;;;;;CAeA,MAAc,oBACV,IAC2B;EAC3B,MAAM,OAAO,MAAM,SAAS,GAAG,MAAM,IAAI;EACzC,MAAM,MAAM,MAAM,SAAS,GAAG,MAAM,IAAI;EACxC,MAAM,WAAW,SAAS,GAAG,IAAI;EAQjC,MAAM,eAA6B;GAC/B;GACA,UAAU,MATQ,KAAK,eACvB,GAAG,MACH,YAAY,OACZ,KACA,UACA,+BACJ;GAII,UAAU,OAAO,KAAK,IAAI;GAC1B,UAAU;GACV,UAAU,GAAG,eAAe;GAC5B,YAAY;GACZ,UAAU;GACV,aAAa;GACb,UAAU;GACV,WAAW;GACX,SAAS;GACT,UAAU;GACV,WAAW;GACX,wBAAwB;EAC5B;EACA,IAAI,GAAG,cAAc,KAAA,KAAa,GAAG,cAAc,IAAI;GACnD,MAAM,YAAY,MAAM,SAAS,GAAG,WAAW,OAAO;GACtD,MAAM,WAAW,MAAM,SAAS,GAAG,WAAW,OAAO;GAMrD,MAAM,eAAe,MAAM,KAAK,eAC5B,GAAG,WACH,YAAY,OACZ,KACA,GAAG,MAAM,QAAQ,GAAG,SAAS,KAC7B,8BACJ;GACA,aAAa,WAAW;GACxB,aAAa,YAAY,UAAU;GACnC,aAAa,aAAa,GAAG,SAAS;GACtC,aAAa,cAAc,GAAG,UAAU;GACxC,aAAa,4BAAY,IAAI,IAAI,CAAC,CAAC,iBAAiB,YAAY,CAAC,CAAC;EACtE;EACA,OAAO;GAAE,aAAa,YAAY;GAAO,WAAW;GAAI;EAAa;CACzE;;;;;;;CAQA,MAAc,mBACV,IAC2B;EAC3B,MAAM,OAAO,MAAM,SAAS,GAAG,MAAM,IAAI;EACzC,MAAM,MAAM,MAAM,SAAS,GAAG,MAAM,IAAI;EACxC,MAAM,WAAW,GAAG,SAAS,KAAA,KAAa,GAAG,SAAS,KAAK,GAAG,OAAO,SAAS,GAAG,IAAI;EACrF,MAAM,UAAU,MAAM,KAAK,eAAe,GAAG,MAAM,YAAY,MAAM,KAAK,QAAQ;EAClF,OAAO;GACH,aAAa,YAAY;GACzB,WAAW;GACX,aAAa;IACT;IACA,UAAU;IACV,UAAU,OAAO,KAAK,IAAI;IAC1B,UAAU;GACd;EACJ;CACJ;;;;;;;CAQA,MAAc,eACV,MACA,aACA,KACA,UACA,eAAe,iCACA;EACf,MAAM,UAAU,KAAK;EACrB,MAAM,OAAO,KAAK;EAIlB,MAAM,UAAU,QAAQ,6BAA6B;GACjD,WAAW;GACX;GACA;GACA,gBAAgB;GAChB,WAAW;GACX,YAAY;GACZ;GACA,WAAW;EACf,CAAC;EACD,IAAI,SAAS,MAAM;GACf,MAAM,WAAW,OAAO,KAAK,QAAQ,aAAa,KAAK,IAAI,IAAI;GAC/D,MAAM,OAAQ,SAAqC;GACnD,IAAI,OAAO,SAAS,YAChB,MAAO,KAAoD,KACvD,UACA,MACA,OACJ;EAER;EACA,OAAO;CACX;;CAGA,YAAoB,OAAe,QAA6B;EAC5D,MAAM,UAAU,KAAK;EACrB,IAAI,YAAY,MACZ,OAAO,QAAQ,QAAQ;EAE3B,OAAO,IAAI,SAAS,SAAS,WAAW;GACpC,MAAM,MAAM,QAAQ,GAAG,4BAA4B,SAAS;IACxD,MAAM,OAAO,KAAK,MAAM,MAAM,EAAE,YAAY,KAAK;IACjD,IAAI,SAAS,KAAA,GACT;IAEJ,IACI,KAAK,eAAe,YAAY,WAChC,KAAK,eAAe,YAAY,gBAClC;KACE,IAAI;KACJ,QAAQ;IACZ,OAAO,IAAI,KAAK,eAAe,YAAY,QAAQ;KAC/C,IAAI;KACJ,OACI,YAAY,0BAA0B,KAAK,cAAc,aAAa,CAC1E;IACJ;GAEJ,CAAC;GAED,iBAAiB;IACb,IAAI;IACJ,OACI,YACI,wBAAwB,MAAM,WAAW,KAAK,UAAU,MAAM,EAAE,IAChE,aACJ,CACJ;GACJ,GAAG,uBAAuB;EAC9B,CAAC;CACL;;CAGA,MAAM,cAAc,QAAc,QAAiC;EAE/D,aAAa,aAAa,MADR,KAAK,QAAQ,UAAU,QAAQ,qBAAqB,MAAM,CAAC,CAChD;CACjC;;CAGA,MAAM,cACF,QACA,MACqB;EACrB,MAAM,MAAM,MAAM,KAAK,QAAQ,QAAQ,QAAQ,KAAK,SAAS,IAAI,KAAK,OAAO,KAAK;EAClF,aAAa,WAAW,GAAG;EAC3B,OAAO,IAAI,WAAW,CAAC;CAC3B;;CAGA,MAAM,iBAAiB,QAAc,KAAsC;EACvE,IAAI,IAAI,WAAW,GACf,OAAO,CAAC;EAEZ,MAAM,MAAM,MAAM,KAAK,QAAQ,eAAe,QAAQ,GAAG;EACzD,aAAa,kBAAkB,GAAG;EAClC,OAAO,IAAI,WAAW,CAAC;CAC3B;;CAGA,MAAM,gBACF,QACA,MACa;EAQb,aAAa,oBAAoB,MAPf,KAAK,QAAQ,iBAC3B,QACA,KAAK,QACL,KAAK,SACL,KAAK,WACL,KAAK,IACT,CACoC;CACxC;;;;;;CAOA,MAAM,aAAa,OAAe,QAA+B;EAE7D,MAAM,MAAM,eAAe,MADR,KAAK,iBAAiB,QAAQ,CAAC,KAAK,CAAC,CACzB;EAC/B,IAAI,QAAQ,MACR,MAAM,YAAY,YAAY,WAAW;EAG7C,aAAa,qBAAqB,MADhB,KAAK,QAAQ,kBAAkB,OAAO,QAAQ,GAAG,CAC9B;EAGrC,MAAM,OAAO,eAAe,MADR,KAAK,iBAAiB,QAAQ,CAAC,KAAK,CAAC,CACxB,CAAC,EAAE,YAAY;EAChD,IAAI,SAAS,KAAA,KAAa,SAAS,IAC/B,MAAM,YAAY,eAAe,SAAS;EAE9C,OAAO;CACX;;CAGA,MAAM,SAAS,QAA6B;EAExC,aAAa,cAAc,MADT,KAAK,QAAQ,WAAW,MAAM,CAClB;CAClC;;;;;;;;CASA,MAAM,YAAY,OAAe,QAAmC;EAChE,MAAM,QAAQ,MAAM,KAAK,aAAa,OAAO,MAAM;EACnD,IAAI,UAAU,MACV,MAAM,YAAY,YAAY,WAAW;EAE7C,MAAM,KAAK,QAAQ,kBAAkB;GACjC;GACA,QAAQ,MAAM;GACd,UAAU,OAAO;GACjB,cAAc;GACd,WAAW;EACf,CAAC;EACD,KAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,KAAK;GACrC,MAAMD,QAAM,oBAAoB;GAEhC,MAAM,SAAS,kBACX,eAAe,MAFC,KAAK,iBAAiB,QAAQ,CAAC,KAAK,CAAC,CAEjC,CAAC,EAAE,YACvB,MAAM,EACV;GACA,IAAI,WAAW,MACX,OAAO;EAEf;EACA,MAAM,YAAY,eAAe,SAAS;CAC9C;;CAGA,MAAc,aACV,OACA,QACmD;EAEnD,MAAM,KAAK,eAAe,MADP,KAAK,iBAAiB,QAAQ,CAAC,KAAK,CAAC,CAC1B;EAC9B,MAAM,MAAM,IAAI;EAChB,IAAI,OAAO,QAAQ,QAAQ,KAAA,GACvB,OAAO;EAEX,OAAO;GAAE,QAAQ,GAAG,aAAa;GAAI;EAAI;CAC7C;;CAGA,MAAM,eAAe,QAAc,WAAkC;EACjE,MAAM,KAAK,QAAQ,uBAAuB,OAAO,UAAU,WAAW,OAAO,OAAO;CACxF;;CAGA,MAAM,UAAU,QAAc,MAAiE;EAC3F,MAAM,QAAoB;GACtB,aAAa,KAAK;GAClB,WAAW,KAAK;GAChB,GAAI,OAAO,aAAa,SAAS,QAAQ,EAAE,QAAQ,OAAO,QAAQ,IAAI,CAAC;EAC3E;EACA,IAAI;GACA,iBACI,aACC,MAAM,KAAK,QAAQ,UAAU,KAAK,CACvC;EACJ,SAAS,GAAG;GACR,IAAI,cAAc,CAAC,GACf,MAAM;GAEV,MAAM,YAAY,mBAAmBC,eAAa,CAAC,KAAK,SAAS;EACrE;CACJ;;CAGA,MAAM,cAAc,QAAc,OAA+B;EAC7D,IAAI;GACA,iBACI,sBACC,MAAM,KAAK,QAAQ,mBAAmB,QAAQ,KAAK,CAIxD;EACJ,SAAS,GAAG;GACR,IAAI,cAAc,CAAC,GACf,MAAM;GAEV,MAAM,YAAY,4BAA4BA,eAAa,CAAC,KAAK,SAAS;EAC9E;CACJ;;CAGA,MAAM,iBAAoC;EACtC,IAAI;EACJ,IAAI;GACA,MAAO,MAAM,KAAK,QAAQ,oBAAoB,EAAE;EACpD,SAAS,GAAG;GACR,MAAM,YAAY,6BAA6BA,eAAa,CAAC,KAAK,SAAS;EAC/E;EACA,iBAAiB,uBAAuB,GAAG;EAC3C,MAAM,OAAO,IAAI;EACjB,IAAI,CAAC,MAAM,QAAQ,IAAI,GACnB,OAAO,CAAC;EAEZ,OAAO,oBAAoB,IAAI;CACnC;;;;;CAMA,MAAM,mBACF,QACA,YACA,WAC0B;EAC1B,IAAI,UAAU,WAAW,GACrB,MAAM,YAAY,gCAAgC,eAAe;EAErE,MAAM,QAAQ,MAAM,KAAK,QAAQ,qBAAqB;GAClD;GACA,YAAY;EAChB,CAAC;EACD,aAAa,wBAAwB,KAAK;EAC1C,MAAM,WAAW,MAAM,SAAS;EAChC,IAAI,aAAa,KAAA,KAAa,SAAS,WAAW,GAC9C,MAAM,YAAY,gBAAgB,SAAS;EAE/C,MAAM,QAAQ,KAAK,QAAQ,oBAAoB,OAAO,UAAU,OAAO,KAAK,IAAI,CAAC,CAAC;EAElF,aAAa,WAAW,MADN,KAAK,QAAQ,QAAQ,OAAO,QAAQ,0BAAU,IAAI,IAAI,CAAC,CAC9C;EAC3B,OAAO,EAAE,MAAM;CACnB;;CAGA,MAAM,oBAAoB,MAAY,OAAsC;EAExE,MAAM,UAAU,mBAAmB,MADhB,KAAK,iBAAiB,MAAM,CAAC,KAAK,CAAC,CACf;EACvC,IAAI,YAAY,QAAQ,QAAQ,UAAU,IACtC,MAAM,YAAY,eAAe,WAAW;EAEhD,MAAM,MAAM,MAAM,KAAK,QAAQ,YAAY,MAAM,OAAO,QAAQ,KAAK;EACrE,aAAa,eAAe,GAAG;EAC/B,OAAO,IAAI,WAAW,CAAC;CAC3B;;CAGA,MAAM,qBACF,YACA,WACA,SACa;EACb,IAAI,UAAU,WAAW,GACrB,MAAM,YAAY,kCAAkC,eAAe;EAGvE,aAAa,cAAc,MADT,KAAK,QAAQ,WAAW,WAAW,YAAY,CAAC,OAAO,GAAG,KAAA,CAAS,CACvD;CAClC;;CAGA,MAAM,gBAAgB,MAKJ;EAEd,aAAa,aAAa,MADR,KAAK,QAAQ,UAAU,IAAI,CAChB;CACjC;AACJ;;AAGA,SAAS,eAAe,MAAuC;CAC3D,MAAM,CAAC,SAAS;CAChB,IAAI,UAAU,KAAA,GACV,OAAO;CAEX,OAAO,MAAM,SAAS,MAAM,OAAO,GAAG,eAAe,KAAA,CAAS,KAAK;AACvE;;AAGA,SAAS,mBACL,MAC8D;CAC9D,MAAM,CAAC,SAAS;CAChB,IAAI,UAAU,KAAA,GACV,OAAO;CAEX,OACI,MAAM,SAAS,MAAM,OAAO,GAAG,2BAA2B,KAAA,CAAS,CAAC,EAC9D,0BAA0B;AAExC;;;;ACpuBA,SAAS,UAAa,OAAsB,UAAgB;CACxD,OAAO,SAAS;AACpB;;AAGA,SAAS,YAAY,MAA2B,OAA0C;CACtF,MAAM,aAAa,KAAK,QAAQ;CAChC,MAAM,cAAc,MAAM,QAAQ;CAClC,OAAO;EACH,UAAU,UAAU,YAAY,cAAc,MAAM,GAAG,QAAQ;EAC/D,UAAU,UAAU,YAAY,cAAc,MAAM,GAAG,QAAQ;EAC/D,SAAS,UAAU,YAAY,cAAc,MAAM,GAAG,OAAO;EAC7D,WAAW,UAAU,KAAK,QAAQ,MAAM,SAAS,MAAM,GAAG,SAAS;EACnE,QAAQ,UAAU,YAAY,cAAc,MAAM,GAAG,QAAQ,MAAM;CACvE;AACJ;;AAGA,SAAS,UACL,SACA,UACA,KACa;CACb,MAAM,cAAc,YAAY,KAAA,IAAY,IAAI,OAAO,IAAI,KAAA;CAC3D,IAAI,gBAAgB,KAAA,GAChB,OAAO;CAEX,OAAO,aAAa,KAAA,IAAY,IAAI,QAAQ,IAAI,KAAA;AACpD;;AAGA,SAAgB,oBACZ,KACA,KACA,OACA,MACY;CACZ,MAAM,IAAI,YAAY,MAAM,KAAK;CACjC,OAAO;EACH,SAAS,OAAO,GAAG;EACnB;EACA,UAAU,UAAU,EAAE,UAAU,MAAM,EAAE;EACxC,KAAK,UAAU,EAAE,UAAU,KAAK,CAAC;EACjC,KAAK,UAAU,EAAE,UAAU,KAAK,EAAE;EAClC,UAAU,UAAU,EAAE,WAAW,SAAS,CAAC;EAC3C,KAAK,OAAO,EAAE,UAAU,GAAG;EAC3B,WAAW,UAAU,EAAE,UAAU,UAAU,EAAE;EAC7C,UAAU,UAAU,EAAE,WAAW,SAAS,CAAC;EAC3C,QAAQ,UAAU,EAAE,SAAS,UAAU,KAAK;EAC5C,cAAc,UAAU,EAAE,SAAS,aAAa,KAAK;EACrD,WAAW,UAAU,EAAE,SAAS,UAAU,CAAC;EAC3C,QAAQ,UAAU,EAAE,UAAU,QAAQ,EAAE;EACxC,QAAQ,UAAU,EAAE,QAAQ,CAAC;EAC7B,YAAY;CAChB;AACJ;;AAGA,SAAS,OAAO,KAAwD;CACpE,IAAI,QAAQ,GACR,OAAO;CAEX,IAAI,QAAQ,GACR,OAAO;CAEX,OAAO;AACX;;;;;;;;;;;;AC3FA,IAAa,aAAb,MAAwB;CACpB;CAEA,YAAY,SAAkC;EAC1C,MAAM,UAAU,QAAQ,kBAAkB;EAC1C,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,2CAA2C,eAAe;EAEhF,KAAK,UAAU;CACnB;;CAGA,MAAM,YAAY,UAAiC;EAI/C,iBAAiB,eAAe,MAHb,KAAK,QAAQ,YAAY,QAAQ,CAGjB;CACvC;;CAGA,MAAM,YAAY,UAAiC;EAI/C,iBAAiB,eAAe,MAHb,KAAK,QAAQ,YAAY,QAAQ,CAGjB;CACvC;;CAGA,MAAM,UAAU,UAAiC;EAC7C,MAAM,MAAM,MAAM,KAAK,QAAQ,UAAU,QAAQ;EACjD,IAAI,IAAI,WAAW,GACf,MAAM,YAAY,iBAAiB,IAAI,UAAU,SAAS;CAElE;;CAGA,MAAM,gBAAgB,KAAoC;EACtD,MAAM,QAAQ,MAAM,KAAK,QAAQ,uBAAuB,GAAG;EAC3D,MAAM,YAAY,MAAM,QAAQ;EAChC,IAAI,MAAM;EACV,IAAI,cAAc,KAAA,KAAa,cAAc,IACzC,MAAM;EAEV,MAAM,OAAO,MAAM,KAAK,QAAQ,kBAAkB,GAAG;EACrD,OAAO,oBAAoB,KAAK,KAAK,OAAO,IAAI;CACpD;AACJ;;;;;;;;;ACpDA,IAAa,iBAAb,MAA4B;CACxB;CAEA,YAAY,SAAkC;EAC1C,MAAM,UACF,QAAQ,sBAAsB;EAClC,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,+CAA+C,eAAe;EAEpF,KAAK,UAAU;CACnB;;CAGA,MAAM,SAAS,KAAa,QAAQ,GAAkB;EAClD,MAAM,MAAM,MAAM,KAAK,QAAQ,oBAAoB;GAC/C,WAAW;GACX,UAAU;GACV,aAAa;GACb,iBAAiB;EACrB,CAAC;EACD,IAAI,IAAI,WAAW,GACf,MAAM,YAAY,SAAS,IAAI,UAAU,SAAS;CAE1D;AACJ;;;;;;;;;;;AChBA,MAAM,6BAA6B;;AAWnC,IAAa,mBAAb,MAA8B;CAC1B;CAEA,YAAY,SAAkC;EAC1C,MAAM,UACF,QAAQ,wBAAwB;EACpC,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YACF,iDACA,eACJ;EAEJ,KAAK,UAAU;CACnB;;CAGA,MAAM,oBAA8C;EAChD,IAAI;EACJ,IAAI;GACA,MAAO,MAAM,KAAK,QAAQ,qBAAqB,EAC3C,OAAO,2BACX,CAAC;EACL,SAAS,GAAG;GACR,IAAI,cAAc,CAAC,GACf,MAAM;GAEV,MAAM,YAAY,8BAA8BC,eAAa,CAAC,KAAK,SAAS;EAChF;EACA,iBAAiB,wBAAwB,GAAG;EAC5C,OAAO,sBAAsB,GAAG;CACpC;AACJ;;AAGA,SAAS,sBAAsB,KAA4C;CACvE,MAAM,aAAwB,CAAC,IAAI,QAAQ,GAAG;CAC9C,KAAK,MAAM,aAAa,CAAC,IAAI,QAAQ,GAAG,GAAG;EACvC,IAAI,cAAc,QAAQ,OAAO,cAAc,UAC3C;EAEJ,MAAM,MAAM;EACZ,KAAK,MAAM,OAAO;GAAC;GAAY;GAAQ;GAAU;EAAM,GACnD,WAAW,KAAK,IAAI,IAAI;CAEhC;CACA,KAAK,MAAM,aAAa,YAAY;EAChC,IAAI,CAAC,MAAM,QAAQ,SAAS,GACxB;EAEJ,OAAO,UAAU,SAAS,SAAS,SAAS,IAAI,CAAC;CACrD;CACA,OAAO,CAAC;AACZ;;AAGA,SAAS,SAAS,MAAgC;CAC9C,IAAI,SAAS,QAAQ,OAAO,SAAS,UACjC,OAAO,CAAC;CAEZ,MAAM,UAAU;CAGhB,IAAI,EADW,QAAQ,UAAU,QAAQ,QAAQ,YAAY,MAC9C,OAAO,QAAQ,YAAY,UACtC,OAAO,CAAC;CAEZ,IAAI,QAAQ,aAAa,SAAS,OAC9B,OAAO,CAAC;EAAE,SAAS,QAAQ;EAAS,OAAO;CAAQ,CAAC;CAExD,IAAI,QAAQ,aAAa,SAAS,KAC9B,OAAO,CAAC;EAAE,SAAS,QAAQ;EAAS,OAAO;CAAS,CAAC;CAEzD,OAAO,CAAC;AACZ;;AAGA,SAASA,eAAa,GAAoB;CACtC,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACpD;;;;;;;;;;ACxFA,MAAM,sBAAsB;;AAE5B,MAAM,iBAAiB;;AAEvB,MAAM,kBAAkB;;AAExB,MAAM,mBAAmB;;AAEzB,MAAM,wBAAwB;;AAE9B,MAAM,qBAAqB;;AAE3B,MAAM,yBAAyB;;AAiB/B,IAAa,eAAb,MAA0B;CACtB;CAEA,YAAY,SAAkC;EAC1C,MAAM,UACF,QAAQ,oBAAoB;EAChC,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,6CAA6C,eAAe;EAElF,KAAK,UAAU;CACnB;;CAGA,MAAM,eAAe,OAAoC;EACrD,MAAM,MAAM,MAAM,KAAK,QAAQ,oBAAoB,KAAK;EACxD,OAAO,uBAAuB,IAAI,QAAQ,IAAI,MAAM;EACpD,OAAO,IAAI,SAAS,CAAC;CACzB;;CAGA,MAAM,iBACF,WACA,iBAAiB,IACjB,YAAY,oBACgB;EAC5B,MAAM,SAA2B;GAC7B,UAAU;GACV;GACA,YAAY;GACZ,WAAW;GACX,qBAAqB;EACzB;EACA,MAAM,MAAM,MAAM,KAAK,QAAQ,iBAAiB,WAAW,MAAM;EAGjE,iBAAiB,oBAAoB,GAAG;EACxC,MAAM,OAAO,gBAAgB,IAAI,MAAM;EACvC,MAAM,QAAQ,KAAK,SAAS,IAAI,OAAO,gBAAgB,GAAG;EAC1D,IAAI,mBAAmB,IACnB,OAAO;EAEX,OAAO,MAAM,QACR,SACI,KAAK,aAAa,KAAA,KAAa,KAAK,SAAS,WAAW,kBACxD,KAAK,eAAe,KAAA,KAAa,KAAK,WAAW,cAAc,cACxE;CACJ;;CAGA,MAAM,uBAAuB,WAAiD;EAC1E,MAAM,SAA2B;GAC7B,UAAU;GACV,WAAW;GACX,YAAY;GACZ,WAAW;GACX,qBAAqB;EACzB;EACA,MAAM,MAAM,MAAM,KAAK,QAAQ,iBAAiB,WAAW,MAAM;EACjE,IAAI,IAAI,WAAW,GACf,OAAO,oBAAoB,IAAI,QAAQ,IAAI,MAAM;EAErD,MAAM,QAAQ,IAAI;EAClB,IAAI,UAAU,KAAA,GACV,MAAM,YAAY,uCAAuC,SAAS;EAEtE,OAAO;GACH,WAAW,gBAAgB,IAAI,MAAM;GACrC,YAAY;GACZ,WAAW,MAAM;GACjB,YAAY,MAAM;EACtB;CACJ;;CAGA,MAAM,kBAAkB,WAAmB,YAAqC;EAC5E,MAAM,MAAM,MAAM,KAAK,QAAQ,kBAAkB,WAAW,UAAU;EACtE,IAAI,IAAI,WAAW,GACf,OAAO,qBAAqB,IAAI,QAAQ,IAAI,MAAM;EAGtD,MAAM,EAAE,WAAW;EACnB,IAAI,OAAO,WAAW,UAClB,OAAO;EAEX,OAAO;CACX;;CAGA,MAAM,gBAAgB,WAAmB,SAAkC;EACvE,MAAM,MAAM,MAAM,KAAK,QAAQ,gBAAgB,WAAW,CAAC,mBAAmB,GAAG,OAAO;EACxF,IAAI,IAAI,WAAW,GACf,OAAO,mBAAmB,IAAI,QAAQ,IAAI,MAAM;CAExD;;CAGA,MAAM,kBAAkB,WAAmB,UAAiC;EACxE,MAAM,MAAM,MAAM,KAAK,QAAQ,kBAAkB,WAAW,QAAQ;EACpE,IAAI,IAAI,WAAW,GACf,OAAO,qBAAqB,IAAI,QAAQ,IAAI,MAAM;CAE1D;;CAGA,MAAM,gBAAgB,WAAmB,QAAgB,SAAgC;EACrF,MAAM,KAAK,QAAQ,gBAAgB,WAAW,qBAAqB,QAAQ,SAAS,EAAE;CAC1F;;CAGA,MAAM,cACF,WACA,SACA,gBACa;EACb,MAAM,MAAM,MAAM,KAAK,QAAQ,cAC3B,WACA,CAAC,mBAAmB,GACpB,SACA,IACA,cACJ;EACA,IAAI,IAAI,WAAW,GACf,OAAO,iBAAiB,IAAI,QAAQ,IAAI,MAAM;CAEtD;;CAGA,MAAM,kBAAkB,WAAmB,UAAkB,SAAgC;EACzF,MAAM,KAAK,QAAQ,kBAAkB,WAAW,qBAAqB,UAAU,SAAS,EAAE;CAC9F;;;;;;CAOA,MAAM,wBAAwB,WAAmB,QAAiC;EAC9E,MAAM,QAAQ,MAAM,KAAK,iBAAiB,SAAS;EACnD,KAAK,MAAM,QAAQ,OAAO;GACtB,MAAM,OAAO,KAAK;GAClB,IAAI,SAAS,KAAA,KAAa,KAAK,WAAW,QAAQ;IAC9C,IAAI,OAAO,KAAK,QAAQ,YAAY,KAAK,QAAQ,IAC7C,OAAO,KAAK;IAEhB,MAAM,YACF,OAAO,OAAO,yBACd,WACJ;GACJ;EACJ;EACA,MAAM,YAAY,OAAO,OAAO,uBAAuB,WAAW;CACtE;;CAGA,MAAM,eAAe,WAAmB,QAA+B;EACnE,MAAM,MAAM,MAAM,KAAK,QAAQ,eAAe,WAAW,MAAM;EAC/D,IAAI,IAAI,WAAW,GACf,OAAO,kBAAkB,IAAI,QAAQ,IAAI,MAAM;CAEvD;AACJ;;AAGA,SAAgB,gBAAgB,QAAsC;CAClE,IAAI,MAAM,QAAQ,MAAM,GACpB,OAAO;CAEX,IAAI,WAAW,QAAQ,OAAO,WAAW,UACrC,OAAO,CAAC;CAEZ,MAAM,MAAM;CACZ,KAAK,MAAM,OAAO;EAAC;EAAS;EAAY;CAAM,GAAY;EACtD,MAAM,QAAQ,IAAI;EAClB,IAAI,MAAM,QAAQ,KAAK,GACnB,OAAO;CAEf;CACA,OAAO,CAAC;AACZ;;AAGA,SAAS,gBAAgB,QAAyB;CAC9C,IAAI,MAAM,QAAQ,MAAM,GACpB,OAAQ,OAA+B,QAAQ,SAAS,KAAK,aAAa,KAAA,CAAS,CAAC,CAAC;CAEzF,OAAO;AACX;;;;;;;;;;;ACxNA,MAAM,kBAAkB;;AAGxB,IAAa,YAAb,MAAuB;CACnB;CAEA,YAAY,SAAkC;EAC1C,MAAM,UAAU,QAAQ,iBAAiB;EACzC,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,0CAA0C,eAAe;EAE/E,KAAK,UAAU;CACnB;;CAGA,MAAM,eAAoD;EACtD,MAAM,MAAM,MAAM,KAAK,QAAQ,oBAAoB,EAAE;EACrD,IAAI,IAAI,WAAW,KAAK,IAAI,cAAc,IACtC,MAAM,YAAY,2BAA2B,IAAI,UAAU,SAAS;EAExE,OAAO;CACX;;CAGA,MAAM,WAAW,QAAgB,KAA8C;EAC3E,MAAM,EAAE,cAAc,MAAM,KAAK,aAAa;EAC9C,MAAM,KAAK,WAAW,OAAO,GAAG,IAAI;EAOpC,OAAO,iBAAiB,0DAJN,mBAAmB,GAAG,EAAA,aACtB,mBAAmB,SAAS,EAAA,MACnC,mBAAmB,EAAE,EAAA,aAED;CACnC;;CAGA,OAAO,OAAO,MAAsB;EAChC,IAAI,OAAO;EACX,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;GACrC,MAAM,OAAO,KAAK,WAAW,CAAC;GAC9B,OAAO,QAAQ,QAAQ,KAAK;EAChC;EACA,QAAQ,OAAO,WAAA,CAAe,SAAS;CAC3C;AACJ;;AAGA,SAAS,iBAAiB,KAA8C;CACpE,OAAO,IAAI,SAAS,SAAS,WAAW;EACpC,MAAM,MAAMC,IACR,KACA,EACI,SAAS,EACL,cAAc,+DAClB,EACJ,IACC,QAAQ;GACL,MAAM,UAAU,sBAAsB,IAAI,QAAQ,aAAa;GAC/D,IAAI,OAAO;GACX,IAAI,GAAG,aAAa,QAAQ,OAAO,CAAC;EACxC,CACJ;EACA,IAAI,WAAW,uBAAuB;GAClC,IAAI,QAAQ;GACZ,uBAAO,IAAI,MAAM,eAAe,CAAC;EACrC,CAAC;EACD,IAAI,GAAG,UAAU,QAAQ,OAAO,GAAG,CAAC;CACxC,CAAC;AACL;;AAGA,SAAS,sBAAsB,KAAmD;CAC9E,MAAM,UAAkC,CAAC;CACzC,IAAI,QAAQ,KAAA,GACR,OAAO;CAEX,KAAK,MAAM,QAAQ,KAAK;EACpB,MAAM,CAAC,QAAQ,KAAK,MAAM,GAAG;EAC7B,IAAI,SAAS,KAAA,GAAW;GACpB,MAAM,KAAK,KAAK,QAAQ,GAAG;GAC3B,IAAI,KAAK,GAAG;IACR,MAAM,OAAO,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK;IACpC,MAAM,QAAQ,KAAK,MAAM,KAAK,CAAC;IAC/B,IAAI,SAAS,IACT,QAAQ,QAAQ;GAExB;EACJ;CACJ;CACA,OAAO;AACX;;;;;;;;;;;;AC/FA,MAAM,aAAa;;AAGnB,MAAM,qBAAqB;;AAG3B,MAAM,oBAAoB;;AAsC1B,MAAa,eAAe;CACxB,KAAK;CACL,WAAW;CACX,WAAW;CACX,QAAQ;CACR,SAAS;AACb;;AAIA,IAAa,SAAb,MAAoB;CAChB;CAEA,YAAY,MAA2E;EACnF,KAAK,aAAa,KAAK;CAC3B;;CAGA,MAAc,iBACV,WACA,QACA,KACA,MAC2D;EAS3D,MAAM,MAAM,MAAM,aAPd,yDACA,IAAI,gBAAgB;GAChB;GACA,YAAY,OAAO,OAAO,kBAAkB;GAC5C,YAAY,OAAO,kBAAkB;GACrC,YAAY;EAChB,CAAC,CAAC,CAAC,SAAS,GACoB,MAAM;EAC1C,IAAI,QAAQ,QAAQ,IAAI,YAAY,GAChC,OAAO;EAEX,MAAM,OAAO,IAAI,MAAM;EACvB,OAAO;GACH,OAAO,MAAM,QAAQ,IAAI,IAAI,KAAK,QAAQ,SAAS,SAAS,IAAI,IAAI,CAAC;GACrE,OAAO,IAAI,MAAM,WAAW;EAChC;CACJ;;CAGA,MAAM,kBAAkB,WAA8C;EAClE,MAAM,SAAS,MAAM,KAAK,WAAW,UAAU;EAC/C,MAAM,MAAM,UAAU,OAAO,OAAO,WAAW,EAAE;EACjD,MAAM,MAAwB,CAAC;EAC/B,KAAK,IAAI,OAAO,GAAG,OAAO,mBAAmB,QAAQ,GAAG;GACpD,MAAM,SAAS,MAAM,KAAK,iBAAiB,WAAW,QAAQ,KAAK,IAAI;GACvE,IAAI,WAAW,MACX;GAEJ,IAAI,KAAK,GAAG,OAAO,KAAK;GACxB,IAAI,OAAO,OACP;EAER;EACA,OAAO;CACX;;CAGA,MAAM,kBAAkB,WAAmB,MAAgD;EACvF,MAAM,SAAS,MAAM,KAAK,WAAW,UAAU;EAC/C,MAAM,SAA4B;GAC9B,UAAU,OAAO,SAAS;GAC1B,mBAAmB;GACnB,gBAAgB,CAAC;GACjB,gBAAgB,CAAC;GACjB,aAAa,CAAC;GACd,cAAc,CAAC;GACf,oBAAoB,CAAC;EACzB;EACA,MAAM,KAAK,eAAe,QAAQ,WAAW,QAAQ,IAAI;EACzD,OAAO;CACX;;CAGA,MAAc,eACV,QACA,WACA,QACA,MACa;EACb,KAAK,MAAM,UAAU,aAAa,IAAI,GAAG;GACrC,MAAM,OAAO,MAAM,KAAK,eAAe,WAAW,QAAQ,OAAO,IAAI;GACrE,OAAO,OAAO,WAAW;GACzB,IAAI,OAAO,SAAS,aAAa,WAAW;IACxC,MAAM,CAAC,SAAS;IAChB,IAAI,UAAU,KAAA,GACV,OAAO,oBAAoB;GAEnC;EACJ;CACJ;;CAGA,MAAc,eACV,WACA,QACA,MACwB;EAIxB,MAAM,OAAO,MAAM,aAFf,8CACA,IAAI,gBAAgB;GAAE,IAAI;GAAW,MAAM,OAAO,IAAI;EAAE,CAAC,CAAC,CAAC,SAAS,GACnC,MAAM;EAC3C,IAAI,SAAS,MACT,OAAO,CAAC;EAEZ,OAAO,eAAe,MAAM,IAAI;CACpC;AACJ;;AAGA,MAAM,mBAAmB;;AAGzB,SAAgB,eAAe,MAAc,MAA+B;CACxE,MAAM,QAAQ,iBAAiB,KAAK,IAAI;CACxC,IAAI,UAAU,QAAQ,MAAM,OAAO,KAAA,GAC/B,OAAO,CAAC;CAEZ,IAAI;CACJ,IAAI;EACA,QAAQ,KAAK,MAAM,MAAM,EAAE;CAC/B,QAAQ;EACJ,OAAO,CAAC;CACZ;CACA,MAAM,MAAM,SAAS,aAAa,YAAY,MAAM,gBAAgB,MAAM;CAC1E,IAAI,CAAC,MAAM,QAAQ,GAAG,GAClB,OAAO,CAAC;CAEZ,MAAM,MAAuB,CAAC;CAC9B,KAAK,MAAM,QAAQ,KACf,IAAI,SAAS,QAAQ,OAAO,SAAS,UAAU;EAC3C,MAAM,MAAM;EAMZ,IAAI,KAAK;GACL,KAAK,OAAO,IAAI,OAAO,EAAE;GACzB,MAAM,OAAO,IAAI,QAAQ,EAAE;GAC3B,QAAQ,OAAO,IAAI,UAAU,EAAE;GAC/B,MAAM,OAAO,IAAI,QAAQ,EAAE;EAC/B,CAAC;CACL;CAEJ,OAAO;AACX;;AAGA,eAAe,aACX,KACA,QAC6F;CAC7F,IAAI;EACA,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,SAAS,EAAE,QAAQ,eAAe,MAAM,EAAE,EAAE,CAAC;EAC5E,IAAI,CAAC,IAAI,IACL,OAAO;EAEX,MAAM,OAAQ,MAAM,IAAI,KAAK;EAI7B,OAAO;GACH,SAAS,KAAK,WAAW;GACzB,MAAM,KAAK;EACf;CACJ,SAAS,KAAK;EACV,MAAM,YAAY,oBAAoB,OAAO,GAAG,KAAK,SAAS;CAClE;AACJ;;AAGA,eAAe,aAAa,KAAa,QAAwD;CAC7F,IAAI;EACA,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,SAAS,EAAE,QAAQ,eAAe,MAAM,EAAE,EAAE,CAAC;EAC5E,IAAI,CAAC,IAAI,IACL,OAAO;EAEX,OAAO,MAAM,IAAI,KAAK;CAC1B,SAAS,KAAK;EACV,MAAM,YAAY,oBAAoB,OAAO,GAAG,KAAK,SAAS;CAClE;AACJ;;AAGA,SAAS,eAAe,QAAwC;CAC5D,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC5C,MAAM,KAAK,GAAG,IAAI,GAAG,OAAO;CAEhC,OAAO,MAAM,KAAK,IAAI;AAC1B;;AAGA,SAAS,aAAa,MAGnB;CACC,MAAM,MAAM;EACR;GAAE,MAAM,aAAa;GAAW,SAAS;EAAiB;EAC1D;GAAE,MAAM,aAAa;GAAW,SAAS;EAAiB;EAC1D;GAAE,MAAM,aAAa;GAAQ,SAAS;EAAc;EACpD;GAAE,MAAM,aAAa;GAAS,SAAS;EAAe;CAC1D;CACA,IAAI,SAAS,aAAa,KACtB,OAAO,CAAC,GAAG,GAAG;CAElB,OAAO,IAAI,QAAQ,WAAW,OAAO,SAAS,IAAI;AACtD;;;;;;;AC5OA,IAAa,eAAb,MAA0B;CACtB;CACA;CACA,aAAoC;CAEpC,YAAY,SAAkC,SAA4B;EACtE,MAAM,UAAU,QAAQ,gBAAgB;EACxC,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,yCAAyC,eAAe;EAE9E,KAAK,UAAU;EACf,KAAK,UAAU;CACnB;;CAGA,WAAiB;EACb,IAAI,KAAK,eAAe,MACpB;EAGJ,MAAM,WAA0B;GAC5B,mBAAmB,QAAQ,KAAK,QAAQ,KAAK,0BAA0B,GAAG;GAC1E,oBAAoB,QAAQ,KAAK,QAAQ,KAAK,2BAA2B,GAAG;GAC5E,uBAAuB,QAAQ,KAAK,QAAQ,KAAK,8BAA8B,GAAG;GAClF,iBAAiB,QAAQ,KAAK,QAAQ,KAAK,wBAAwB,GAAG;EAC1E;EACA,KAAK,aAAa,KAAK,QAAQ,uBAAuB,QAAQ;CAClE;;CAGA,aAAmB;EACf,IAAI,KAAK,eAAe,MACpB;EAEJ,KAAK,QAAQ,0BAA0B,KAAK,UAAU;EACtD,KAAK,aAAa;CACtB;AACJ;;;;;;;ACtCA,IAAa,cAAb,MAAyB;CACrB;CACA;CACA,aAAoC;CAEpC,YAAY,SAAkC,SAA4B;EACtE,MAAM,UAAU,QAAQ,gBAAgB;EACxC,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,yCAAyC,eAAe;EAE9E,KAAK,UAAU;EACf,KAAK,UAAU;CACnB;;CAGA,WAAiB;EACb,IAAI,KAAK,eAAe,MACpB;EAGJ,MAAM,WAA0B;GAC5B,oBAAoB,cAChB,KAAK,QAAQ,KAAK,2BAA2B,SAAS;GAC1D,oBAAoB,YAAY,cAC5B,KAAK,QAAQ,KAAK,2BAA2B,YAAY,SAAS;GACtE,0BAA0B,eACtB,KAAK,QAAQ,KAAK,iCAAiC,UAAU;GACjE,qBAAqB,QAAQ,KAAK,QAAQ,KAAK,4BAA4B,GAAG;GAC9E,qBAAqB,WAAW,YAAY,YACxC,KAAK,QAAQ,KAAK,4BAA4B,WAAW,YAAY,OAAO;GAChF,yBAAyB,OAAO,aAC5B,KAAK,QAAQ,KAAK,gCAAgC,OAAO,QAAQ;GACrE,8BAA8B,OAAO,KAAK,aACtC,KAAK,QAAQ,KAAK,qCAAqC,OAAO,KAAK,QAAQ;GAC/E,4BAA4B,WAAW,YACnC,KAAK,QAAQ,KAAK,mCAAmC,WAAW,OAAO;GAE3E,2BAA2B,QACvB,KAAK,QAAQ,KAAK,kCAAkC,GAAG;EAC/D;EACA,KAAK,aAAa,KAAK,QAAQ,uBAAuB,QAAQ;CAClE;;CAGA,aAAmB;EACf,IAAI,KAAK,eAAe,MACpB;EAEJ,KAAK,QAAQ,0BAA0B,KAAK,UAAU;EACtD,KAAK,aAAa;CACtB;AACJ;;;;;;;ACpDA,IAAa,YAAb,MAAuB;CACnB;CACA;CACA,aAAoC;CAEpC,YAAY,SAAkC,SAA0B;EACpE,MAAM,UAAU,QAAQ,cAAc;EACtC,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,uCAAuC,eAAe;EAE5E,KAAK,UAAU;EACf,KAAK,UAAU;CACnB;;CAGA,WAAiB;EACb,IAAI,KAAK,eAAe,MACpB;EAIJ,MAAM,WAAwB;GAC1B,YAAY,SAAS,KAAK,QAAQ,KAAK,iBAAiB,IAAI;GAC5D,sBAAsB,YAAY,KAAK,QAAQ,KAAK,2BAA2B,OAAO;GACtF,mBAAmB,aAAa,KAAK,QAAQ,KAAK,wBAAwB,QAAQ;GAElF,sBAAsB,SAAS,KAAK,QAAQ,KAAK,2BAA2B,IAAI;GAEhF,uBAAuB,QAAQ,KAAK,QAAQ,KAAK,4BAA4B,GAAG;GAChF,sBAAsB,QAAQ,KAAK,QAAQ,KAAK,2BAA2B,GAAG;GAC9E,eAAe,QAAQ,KAAK,QAAQ,KAAK,oBAAoB,GAAG;EACpE;EACA,KAAK,aAAa,KAAK,QAAQ,qBAAqB,QAAQ;CAChE;;CAGA,aAAmB;EACf,IAAI,KAAK,eAAe,MACpB;EAEJ,KAAK,QAAQ,wBAAwB,KAAK,UAAU;EACpD,KAAK,aAAa;CACtB;AACJ;;;;;;;;;;;;;;;;;;;ACtDA,MAAM,0BAA0B;;;;;;;AA+ChC,IAAa,iBAAb,MAA0E;CACtE;CAEA,UAA2B,IAAI,aAAa;CAC5C,gCAAiC,IAAI,IAA4B;CACjE,8BAA+B,IAAI,IAAiD;CAEpF,YAAY,aAAmB;EAC3B,KAAK,cAAc;EAEnB,KAAK,QAAQ,gBAAgB,CAAC;CAClC;;;;;CAMA,GAAiC,OAAU,SAA+C;EACtF,KAAK,QAAQ,YAAY,OAAiB,OAAuC;EACjF,aAAa;GACT,KAAK,QAAQ,eAAe,OAAiB,OAAuC;EACxF;CACJ;;;;;CAMA,QACI,OACA,OAGI,CAAC,GACyB;EAC9B,MAAM,YAAY,KAAK,WAAW;EAClC,OAAO,IAAI,SAAS,SAAS,WAAW;GACpC,MAAM,WAAW,GAAG,SAAgC;IAChD,IAAI,KAAK,UAAU,CAAC,KAAK,OAAO,GAAG,IAAI,GACnC;IAEJ,QAAQ;IACR,QAAQ,IAAI;GAChB;GACA,MAAM,QAAQ,iBAAiB;IAC3B,QAAQ;IACR,OAAO,IAAI,YAAY,WAAW,SAAmB,SAAS,CAAC;GACnE,GAAG,SAAS;GACZ,MAAM,gBAAsB;IACxB,aAAa,KAAK;IAClB,KAAK,QAAQ,eACT,OACA,OACJ;GACJ;GACA,KAAK,QAAQ,YAAY,OAAiB,OAAuC;EACrF,CAAC;CACL;;;;;;;;;;;CAYA,MAAM,SAAkE;EACpE,KAAK,YAAY,IAAI,OAAO;EAC5B,aAAa;GACT,KAAK,YAAY,OAAO,OAAO;EACnC;CACJ;CAEA,KAAmC,OAAU,GAAG,MAAmC;EAE/E,KAAK,MAAM,cAAc,KAAK,aAC1B,IAAI;GACA,WAAW,OAAiB,GAAG,IAAI;EACvC,SAAS,KAAK;GACV,KAAK,YAAY,GAAG;EACxB;EAEJ,MAAM,YAAY,KAAK,QAAQ,UAAU,KAAe;EACxD,KAAK,MAAM,YAAY,WACnB,IAAI;GACA,SAAwC,GAAG,IAAI;EACnD,SAAS,KAAK;GAEV,KAAK,YAAY,GAAG;EACxB;CAER;;CAGA,YAAoB,KAAoB;EACpC,KAAK,MAAM,WAAW,KAAK,eACvB,QAAQ,GAAG;CAEnB;;CAGA,QAAQ,SAA6C;EACjD,KAAK,cAAc,IAAI,OAAO;EAC9B,aAAa;GACT,KAAK,cAAc,OAAO,OAAO;EACrC;CACJ;AACJ;;;;;;;AC1IA,SAAgB,sBAAsB,KAAsC;CACxE,IAAI,CAAC,MAAM,QAAQ,GAAG,GAClB,OAAO;CAEX,KAAK,MAAM,QAAQ,KACf,IAAI,SAAS,QAAQ,OAAO,SAAS,UACjC,OAAO;CAGf,OAAO;AACX;;AAGA,SAAS,eAAe,YAA+C;CACnE,MAAM,MAAwB,CAAC;CAC/B,KAAK,MAAM,OAAO,YACd,KAAK,MAAM,SAAS,IAAI,aAAa,CAAC,GAClC,IAAI,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,IAChD,IAAI,KAAK,KAAK;CAI1B,OAAO;AACX;;AAGA,IAAa,aAAb,MAAwB;CACpB;CACA;;CAGA,SAA0C,IAAI,eAAe,YAAY;;CAGzE,0BAA2B,IAAI,IAA4B;;CAG3D,eAAuB;CAEvB,eAA4C;CAE5C,YAAY,MAAyB;EACjC,KAAK,UAAU,KAAK;EACpB,KAAK,SAAS,KAAK;CACvB;;CAGA,WAAiB;EACb,IAAI,KAAK,iBAAiB,MACtB;EAEJ,MAAM,QAAQ,KAAK,QAAQ,GAAG,4BAA4B,QAAiB;GACvE,MAAM,aAAa,sBAAsB,GAAG;GAC5C,IAAI,eAAe,MAAM;IAErB,KAAK,QAAQ,KACT,EAAE,KAAK,KAAK,UAAU,GAAG,CAAC,EAAE,MAAM,GAAG,GAAI,EAAE,GAC3C,sCACJ;IACA;GACJ;GACA,KAAK,cAAc,eAAe,UAAU,CAAC;EACjD,CAAC;EACD,KAAK,eAAe;CACxB;;CAGA,aAAmB;EACf,KAAK,eAAe;EACpB,KAAK,eAAe;EACpB,KAAK,QAAQ,MAAM;EACnB,KAAK,eAAe;CACxB;;CAGA,cAAsB,SAAiC;EACnD,IAAI,CAAC,KAAK,cAAc;GACpB,KAAK,MAAM,SAAS,SAChB,KAAK,QAAQ,IAAI,MAAM,KAAK,KAAK;GAErC,KAAK,eAAe;GACpB;EACJ;EACA,MAAM,uBAAO,IAAI,IAA4B;EAC7C,KAAK,MAAM,SAAS,SAChB,KAAK,IAAI,MAAM,KAAK,KAAK;EAE7B,KAAK,MAAM,CAAC,KAAK,UAAU,MACvB,IAAI,CAAC,KAAK,QAAQ,IAAI,GAAG,GACrB,KAAK,OAAO,KAAK,2BAA2B,KAAK;EAGzD,KAAK,MAAM,CAAC,KAAK,UAAU,KAAK,SAC5B,IAAI,CAAC,KAAK,IAAI,GAAG,GACb,KAAK,OAAO,KAAK,6BAA6B,KAAK;EAG3D,KAAK,QAAQ,MAAM;EACnB,KAAK,MAAM,CAAC,KAAK,UAAU,MACvB,KAAK,QAAQ,IAAI,KAAK,KAAK;CAEnC;;CAGA,cAAgC;EAC5B,OAAO,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC;CACpC;;CAGA,SAAS,KAAyC;EAC9C,OAAO,KAAK,QAAQ,IAAI,GAAG;CAC/B;;CAGA,IAAI,gBAAyB;EACzB,OAAO,KAAK;CAChB;AACJ;;;;;;;ACtJA,SAAgB,QACZ,WACA,WACA,aAAa,KACG;CAChB,OAAO,IAAI,SAAS,YAAY;EAC5B,MAAM,UAAU,KAAK,IAAI;EACzB,MAAM,aAAmB;GACrB,IAAI,UAAU,GAAG;IACb,QAAQ,IAAI;IACZ;GACJ;GACA,IAAI,KAAK,IAAI,IAAI,UAAU,WAAW;IAClC,QAAQ,KAAK;IACb;GACJ;GACA,WAAW,MAAM,UAAU;EAC/B;EACA,KAAK;CACT,CAAC;AACL;;AAGA,SAAgB,MAAM,IAA2B;CAC7C,OAAO,IAAI,SAAS,YAAY;EAC5B,WAAW,SAAS,EAAE;CAC1B,CAAC;AACL;;;;;;;ACEA,IAAa,aAAb,MAAwB;CACpB;CACA;;CAGA,4BAA6B,IAAI,IAAmB;;CAEpD,+BAAgC,IAAI,IAA6B;;CAEjE,0BAA2B,IAAI,IAAsC;;CAErE,2BAA4B,IAAI,IAA8B;CAE9D,eAA4C;CAE5C,YAAY,MAAyB;EACjC,KAAK,UAAU,KAAK;EACpB,KAAK,WAAW,KAAK;CACzB;;CAGA,WAAiB;EACb,IAAI,KAAK,iBAAiB,MACtB;EAEJ,MAAM,SAA4B,CAAC;EACnC,OAAO,KACH,KAAK,QAAQ,GAAG,4BAA4B,cAAuB;GAC/D,IAAI,WACA,KAAK,UAAU,MAAM;EAE7B,CAAC,CACL;EACA,OAAO,KACH,KAAK,QAAQ,GACT,4BACC,YAAiC,cAAuB;GACrD,IAAI,eAAe,oBAAoB,KACnC,KAAK,UAAU,MAAM;GAEzB,KAAK,MAAM,SAAS,WAChB,KAAK,UAAU,IAAI,MAAM,WAAW,KAAK;EAEjD,CACJ,CACJ;EACA,OAAO,KACH,KAAK,QAAQ,GAAG,kCAAkC,eAAgC;GAC9E,KAAK,aAAa,IAAI,WAAW,WAAW,UAAU;EAC1D,CAAC,CACL;EACA,OAAO,KACH,KAAK,QAAQ,GAAG,6BAA6B,QAA+B;GACxE,KAAK,cAAc,IAAI,SAAS,IAAI,KAAK;EAC7C,CAAC,CACL;EACA,OAAO,KACH,KAAK,QAAQ,GACT,6BAEI,YACA,aACA,YACC;GACD,KAAK,cAAc,YAAY,OAAO;EAC1C,CACJ,CACJ;EACA,KAAK,qBAAqB;GACtB,KAAK,MAAM,SAAS,QAChB,MAAM;EAEd;CACJ;;CAGA,aAAmB;EACf,KAAK,eAAe;EACpB,KAAK,eAAe;EACpB,KAAK,UAAU,MAAM;EACrB,KAAK,aAAa,MAAM;EACxB,KAAK,QAAQ,MAAM;EACnB,KAAK,SAAS,MAAM;CACxB;;CAGA,cAAsB,WAAmB,OAAuC;EAC5E,IAAI,MAAM,KAAK,QAAQ,IAAI,SAAS;EACpC,IAAI,QAAQ,KAAA,GAAW;GACnB,sBAAM,IAAI,IAAI;GACd,KAAK,QAAQ,IAAI,WAAW,GAAG;EACnC;EACA,KAAK,MAAM,CAAC,KAAK,WAAW,OACxB,IAAI,IAAI,KAAK,MAAM;CAE3B;;CAGA,SAAS,WAA4B;EACjC,OAAO,KAAK,UAAU,IAAI,SAAS,KAAK,KAAK,aAAa,IAAI,SAAS;CAC3E;;;;;;CAOA,aAAsB;EAClB,OAAO,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC;CACtC;;;;;;;;;CAUA,MAAM,sBAAwC;EAC1C,IAAI,KAAK,UAAU,OAAO,GACtB,OAAO,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC;EAEtC,MAAM,KAAK,SAAS,aAAa,IAAI;EACrC,MAAM,cAAc,KAAK,UAAU,OAAO,GAAG,KAAO,GAAG;EACvD,OAAO,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC;CACtC;;;;;CAMA,MAAM,SAAS,WAA+C;EAC1D,MAAM,SAAS,KAAK,UAAU,IAAI,SAAS;EAC3C,IAAI,WAAW,KAAA,GACX,OAAO;EAEX,MAAM,KAAK,cAAc;EACzB,OAAO,KAAK,UAAU,IAAI,SAAS;CACvC;;;;;CAMA,MAAM,eAAe,WAA6C;EAC9D,MAAM,SAAS,KAAK,aAAa,IAAI,SAAS;EAC9C,IAAI,WAAW,KAAA,GACX,OAAO;EAEX,MAAM,SAAS,MAAM,KAAK,OAAwB,UAAU,mBACxD,KAAK,SAAS,aAAa,SAAS,CACxC;EACA,KAAK,aAAa,IAAI,WAAW,MAAM;EACvC,OAAO;CACX;;;;;CAMA,MAAM,WAAW,WAA2C;EACxD,MAAM,MAAM,KAAK,QAAQ,IAAI,SAAS;EACtC,IAAI,QAAQ,KAAA,KAAa,IAAI,OAAO,GAChC,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC;EAE3B,MAAM,UAAU,MAAM,KAAK,OAAsB,WAAW,mBACxD,KAAK,SAAS,mBAAmB,WAAW,IAAI,CACpD;EACA,MAAM,yBAAS,IAAI,IAAyB;EAC5C,KAAK,MAAM,UAAU,SACjB,OAAO,IAAI,OAAO,KAAK,MAAM;EAEjC,KAAK,QAAQ,IAAI,WAAW,MAAM;EAClC,OAAO;CACX;;;;;CAMA,MAAM,UAAU,WAAmB,KAA+C;EAC9E,MAAM,SAAS,MAAM,KAAK,gBAAgB,WAAW,GAAG;EACxD,IAAI,WAAW,KAAA,GACX,OAAO;EAKX,MAAM,CAAC,UAAU,MAHK,KAAK,OAAsB,UAAU,UAAU,GAAG,aACpE,KAAK,SAAS,mBAAmB,WAAW,CAAC,GAAG,CAAC,CACrD;EAEA,IAAI,WAAW,KAAA,GACX;EAEJ,MAAM,SAAS,KAAK,QAAQ,IAAI,SAAS;EACzC,IAAI,WAAW,KAAA,GACX,OAAO,IAAI,OAAO,KAAK,MAAM;EAEjC,OAAO;CACX;;CAGA,MAAc,gBACV,WACA,KACgC;EAChC,IAAI,MAAM,KAAK,QAAQ,IAAI,SAAS;EACpC,IAAI,QAAQ,KAAA,KAAa,IAAI,SAAS,GAAG;GACrC,MAAM,KAAK,WAAW,SAAS;GAC/B,MAAM,KAAK,QAAQ,IAAI,SAAS;EACpC;EACA,OAAO,KAAK,IAAI,GAAG;CACvB;;CAGA,MAAc,gBAA+B;EACzC,MAAM,OAAO,MAAM,KAAK,OAAgB,oBACpC,KAAK,SAAS,aAAa,KAAK,CACpC;EACA,KAAK,MAAM,SAAS,MAChB,KAAK,UAAU,IAAI,MAAM,WAAW,KAAK;CAEjD;;CAGA,MAAc,OAAU,KAAa,QAAsC;EACvE,MAAM,WAAW,KAAK,SAAS,IAAI,GAAG;EACtC,IAAI,aAAa,KAAA,GACb,OAAO,MAAO;EAElB,MAAM,OAAO,OAAO,CAAC,CAChB,OAAO,QAAiB;GACrB,MAAM;EACV,CAAC,CAAC,CACD,cAAc;GACX,KAAK,SAAS,OAAO,GAAG;EAC5B,CAAC;EACL,KAAK,SAAS,IAAI,KAAK,IAAI;EAC3B,OAAO,MAAM;CACjB;AACJ;;;;AC9OA,SAAgB,kBAAkB,MAAuC;CACrE,OAAO;EACH,QAAQ,KAAK;EACb,OAAO,KAAK;EACZ,SAAS;EACT,OAAO;CACX;AACJ;;;;AChBA,MAAM,aAAmB,KAAA;;AAGzB,IAAa,gBAAb,MAAyD;CACrD,QAAQ;CACR,kBAAkB;CAClB,kBAAkB;CAClB,gBAAgB;CAChB,gBAAgB;CAChB,oBAAoB;CACpB,sBAAsB;CACtB,kBAAkB;AACtB;;AAGA,IAAa,iBAAb,MAA2D;CACvD,oBAAoB;CACpB,gBAAgB;CAChB,eAAe;AACnB;;AAGA,IAAa,oBAAb,MAAiE;CAC7D,kBAAkB;CAClB,eAAe;CACf,uBAAuB;AAC3B;;;;;;AAaA,SAAgB,sBACZ,OAA+B,CAAC,GACN;CAC1B,MAAM,EAAE,YAAY;CACpB,MAAM,QAAQ,MAAc,GAAG,SAA0B;EACrD,UAAU,MAAM,GAAG,IAAI;CAC3B;CACA,OAAO;EACH,oBAAoB,cAAc,KAAK,qBAAqB,SAAS;EACrE,sBAAsB,cAAc,KAAK,uBAAuB,SAAS;EACzE,wBAAwB,cAAc,KAAK,yBAAyB,SAAS;EAC7E,sBAAsB,SAAS,KAAK,uBAAuB,IAAI;EAC/D,qBAAqB,WAAW,KAAK,sBAAsB,MAAM;EACjE,kBAAkB,WAAW,KAAK,mBAAmB,MAAM;CAC/D;AACJ;;AAGA,SAAgB,sBAA4C;CACxD,OAAO,CAAC;AACZ;;;;;;;;;;;;;;;AC7CA,MAAM,uBAAuB;;AAG7B,MAAM,qBAAqB;;AAG3B,MAAM,wBAAwB;;AAG9B,MAAM,oBAAoB;;AAG1B,MAAM,2BAA2B;;AAGjC,MAAM,wBAAwB;;AAG9B,MAAM,uBAAuB;;AAW7B,MAAa,8BAA8B;;;;;;;AAQ3C,SAAgB,yBACZ,KACA,OAA+B,CAAC,GAChB;CAChB,MAAM,MAAM,IAAI;CAChB,IAAI,QAAQ,QAAQ,OAAO,IAAI,iBAAiB,YAC5C,OAAO,QAAQ,QAAQ,KAAK;CAEhC,OAAO,cACG;EACF,IAAI;GACA,OAAO,IAAI,aAAa,MAAM;EAClC,QAAQ;GACJ,OAAO;EACX;CACJ,GACA,KAAK,aAAa,0BAClB,qBACJ;AACJ;;AAGA,eAAsB,kBAAkB,KAAkD;CACtF,MAAM,MAAM,IAAI;CAChB,IAAI,QAAQ,MACR,MAAM,YAAY,mCAAmC,eAAe;CAGxE,QAAO,MADY,IAAI,aAAa,EAAA,CACxB;AAChB;;;;;;;;;AAUA,eAAe,qBACX,KACA,MACa;CACb,MAAM,MAAM,IAAI;CAIhB,IAAI,QAAQ,QAAQ,OAAO,IAAI,YAAY,YACvC;CAEJ,MAAM,mBAAmB,KAAK,KAAK,SAAS;CAG5C,MAAM,MAAM,oBAAoB;AACpC;;AAGA,SAAS,mBACL,KAIA,WACa;CACb,OAAO,IAAI,SAAe,YAAY;EAClC,IAAI,UAAU;EACd,MAAM,eAAqB;GACvB,IAAI,CAAC,SAAS;IACV,UAAU;IACV,QAAQ;GACZ;EACJ;EACA,2BAA2B,KAAK,MAAM;EACtC,WAAW,QAAQ,aAAa,wBAAwB;EACxD,IAAI;GACA,IAAI,UAAU;EAClB,QAAQ;GACJ,OAAO;EACX;CACJ,CAAC;AACL;;AAGA,SAAS,2BACL,KAIA,QACI;CACJ,IAAI;EAGA,MAAM,WAAW,oBAAoB;EACrC,SAAS,yBAAyB;GAC9B,OAAO;EACX;EACA,IAAI,OAAO,IAAI,2BAA2B,YACtC,IAAI,uBAAuB,QAAQ;CAE3C,QAAQ,CAER;AACJ;;;;;;AAOA,SAAgB,gBACZ,OACA,KACgB;CAChB,IAAI,QAAQ,KAAA,GAAW;EACnB,MAAM,SAAS,MAAM,MAAM,MAAM,EAAE,QAAQ,GAAG;EAC9C,IAAI,WAAW,KAAA,GAEX,MAAM,YAAY,MAAM,IAAI,aADV,MAAM,KAAK,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,GACN,EAAU,IAAI,WAAW;EAEtE,OAAO;CACX;CAEA,MAAM,QAAQ,MAAM,MAAM,MAAM,EAAE,YAAY;CAC9C,IAAI,UAAU,KAAA,GACV,OAAO;CAEX,MAAM,CAAC,SAAS;CAChB,IAAI,UAAU,KAAA,GACV,MAAM,YAAY,WAAW,WAAW;CAE5C,OAAO;AACX;;;;;;AAUA,SAAS,gBACL,SACA,WACA,WACU;CACV,IAAI,QAA8C;CAClD,MAAM,UAAU,IAAI,SAAY,SAAS,WAAW;EAChD,QAAQ,iBAAiB;GACrB,IAAI;IACA,QAAQ,UAAU,CAAC;GACvB,SAAS,KAAK;IACV,OAAO,GAAG;GACd;EACJ,GAAG,SAAS;CAChB,CAAC;CACD,OAAO,QAAQ,KAAK,CAAC,SAAS,OAAO,CAAC,CAAC,CAAC,cAAc;EAClD,IAAI,UAAU,MACV,aAAa,KAAK;CAE1B,CAAC;AACL;;;;;;AAOA,eAAe,sBACX,KACA,cACA,QACA,MACA,gBACoB;CACpB,IAAI,aAAa;CACjB,KAAK,IAAI,UAAU,GAAG,WAAW,mBAAmB,WAAW,GAAG;EAC9D,MAAM,gBAAgB,MAAM,gBACxB,kBAAkB,cAAc,MAAM,GACtC,uBACsB;GAAE,IAAI;GAAO,QAAQ;EAA4B,EAC3E;EACA,IAAI,cAAc,IACd,OAAO,cAAc;EAEzB,aAAa,cAAc;EAE3B,IAAI,EADgB,eAAe,UAAU,KAAK,UAAU,oBAExD;EAKJ,IAAI,CAAC,MAHe,yBAAyB,KAAK,EAC9C,WAAW,KAAK,aAAa,yBACjC,CAAC,GAEG;CAER;CACA,MAAM,YAAY,WAAW,cAAc,WAAW;AAC1D;;AAGA,eAAe,kBACX,cACA,QACsB;CAEtB,MAAM,EAAE,YAAW,MADE,aAAa,kBAAkB,OAAO,GAAG,EAAA,CACpC;CAC1B,IAAI,QACA,OAAO;EAAE,IAAI;EAAO;CAAO;CAE/B,OAAO;EACH,IAAI;EACJ,QAAQ;GACJ,KAAK,OAAO;GACZ,KAAK,OAAO,OAAO;GACnB,MAAM,OAAO,YAAY;EAC7B;CACJ;AACJ;;AAGA,SAAgB,eAAe,QAAyB;CACpD,OAAO,OAAO,SAAS,kBAAkB,KAAK,OAAO,SAAS,qBAAqB;AACvF;;;;;;;;AAmBA,eAAsB,WAClB,KACA,OAA0B,CAAC,GACP;CACpB,MAAM,MAAM,IAAI;CAChB,IAAI,QAAQ,MACR,MAAM,YAAY,mCAAmC,eAAe;CAExE,MAAM,eAAe;CACrB,MAAM,iBAAiB,KAAK,uBAAA;CAE5B,MAAM,qBAAqB,KAAK,EAC5B,GAAI,KAAK,cAAc,KAAA,IAAY,EAAE,WAAW,KAAK,UAAU,IAAI,CAAC,EACxE,CAAC;CAID,MAAM,SAAQ,MAHK,gBAAgB,aAAa,aAAa,GAAG,sBAAsB;EAClF,MAAM,YAAY,WAAW,+BAA+B,WAAW;CAC3E,CAAC,EAAA,CACkB;CACnB,IAAI,MAAM,WAAW,GACjB,MAAM,YAAY,WAAW,WAAW;CAG5C,OAAO,sBAAsB,KAAK,cADnB,gBAAgB,OAAO,KAAK,GACU,GAAG,MAAM,cAAc;AAChF;;;;;;;;;;;;;;;;;;;;;;;;;ACvTA,MAAM,0BAA0B;;AAGhC,MAAM,2BAA2B;;AAGjC,MAAM,wBAAwB;;;;;;;;;AAU9B,eAAsB,iBAClB,KACA,MACa;CACb,MAAM,EAAE,YAAY;CACpB,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,eAAe,eAAe;CAcpD,IAAI,CAAC,MAZY,cACP;EACF,IAAI;GACA,MAAM,MAAM,QAAQ,cAAc;GAClC,OAAO,QAAQ,QAAQ,QAAQ,KAAA;EACnC,QAAQ;GACJ,OAAO;EACX;CACJ,GACA,KAAK,aAAa,0BAClB,qBACJ,GAEI,MAAM,YAAY,mCAAmC,SAAS;AAEtE;;;AAKA,eAAsB,oBAClB,KACA,QACA,UACA,MACa;CACb,MAAM,EAAE,YAAY;CACpB,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,MAAM,YAAY,eAAe,eAAe;CAKpD,MAAM,UAAU,IAAI,eAAe;CACnC,MAAM,aAAa,IAAI,kBAAkB;CAEzC,MAAM,eAAe,gBAAgB,QAAQ;CAC7C,QAAQ,KAAK,QAAQ,SAAS,YAAY,QAAQ;CAClD,uBAAuB,KAAK,OAAO;CAMnC,IAAI,CAAC,MAJY,QAAQ,KAAK,CAC1B,aAAa,WAAW,IAAI,GAC5B,cAAc,OAAO,KAAK,aAAa,uBAAuB,CAAC,CAAC,WAAW,KAAK,CACpF,CAAC,GAEG,MAAM,YAAY,mBAAmB,SAAS;AAEtD;;AAGA,SAAS,gBAAgB,UAAqD;CAC1E,OAAO,IAAI,SAAe,SAAS,WAAW;EAC1C,MAAM,kBAAkB,SAAS;EACjC,SAAS,uBAAuB,SAAS;GACrC,IAAI,KAAK,SACL,QAAQ;GAEZ,IAAI,OAAO,oBAAoB,YAC3B,gBAAgB,IAAI;EAE5B;EACA,MAAM,iBAAiB,SAAS;EAChC,SAAS,yBAAyB,MAAM;GACpC,IAAI,MAAM,KAAK,MAAM,KACjB,QAAQ;QAER,OAAO,YAAY,oBAAoB,OAAO,CAAC,KAAK,SAAS,CAAC;GAElE,IAAI,OAAO,mBAAmB,YAC1B,eAAe,CAAC;EAExB;CACJ,CAAC;AACL;;;;;;;;;AAUA,SAAS,uBACL,KACA,SACI;CACJ,IAAI,oBAAoB,GAAG,GACvB;CAEJ,IAAI;EACA,QAAQ,QAAQ,CAAC;CACrB,QAAQ;EACJ,IAAI;GACA,QAAQ,QAAQ;EACpB,QAAQ,CAER;CACJ;AACJ;;AAGA,SAAS,oBAAoB,KAA8B;CACvD,MAAM,EAAE,mBAAmB;CAC3B,IAAI,mBAAmB,QAAQ,OAAO,eAAe,UAAU,YAC3D,OAAO;CAEX,IAAI;EACA,eAAe,MAAM;CACzB,QAAQ,CAER;CACA,OAAO;AACX;;;;;;;;;;;;;;;ACvHA,MAAM,sBAAsB;AAC5B,MAAM,sBAAsB;;AAM5B,MAAa,2BAA2B;;AAGxC,SAAS,gBAAgB,SAAiB,SAA0B;CAChE,OAAO,YAAY,uBAAuB,YAAY;AAC1D;;AAGA,MAAM,qBAAqB;;AAG3B,IAAa,iBAAb,MAA4B;CACxB;CACA,QAA4B;CAC5B,aAAoC;CACpC,OAAgC;CAChC,iCAAkC,IAAI,IAA8B;CACpE,gCAAiC,IAAI,IAAiC;;CAEtE,eAA6D;;CAE7D,qBAA4C;;CAE5C;CAEA,YAAY,cAAuB,UAAkC,CAAC,GAAG;EACrE,MAAM,MAAM;EACZ,IAAI,QAAQ,QAAQ,OAAO,IAAI,2BAA2B,YACtD,MAAM,YAAY,6CAA6C,eAAe;EAElF,KAAK,eAAe;EACpB,KAAK,YAAY,QAAQ,aAAA;CAC7B;;CAGA,IAAI,eAA2B;EAC3B,OAAO,KAAK;CAChB;;CAGA,IAAI,WAA4B;EAC5B,OAAO,KAAK;CAChB;;CAGA,IAAI,gBAA+B;EAC/B,OAAO,KAAK;CAChB;;CAGA,SAAS,IAA0C;EAC/C,KAAK,eAAe,IAAI,EAAE;EAC1B,aAAa,KAAK,eAAe,OAAO,EAAE;CAC9C;;CAGA,cAAc,IAA6C;EACvD,KAAK,cAAc,IAAI,EAAE;EACzB,aAAa,KAAK,cAAc,OAAO,EAAE;CAC7C;;CAGA,MAAM,OAA8B,CAAC,GAAS;EAC1C,IAAI,KAAK,UAAU,QACf;EAEJ,KAAK,aAAa,KAAK,aAAa,uBAAuB,KAAK,cAAc,CAAC;EAC/E,KAAK,aAAa,QAAQ;EAC1B,IAAI,KAAK,aAAa,KAAA,KAAa,KAAK,aAAa,IAAI;GAIrD,KAAK,eAAe;GACpB,KAAK,aACA,kBAAkB,KAAK,QAAQ,CAAC,CAChC,MAAM,WAAW;IAKd,IAAI,OAAO,eAAe,WAAW,IACjC,KAAK,uBAAuB;GAEpC,CAAC,CAAC,CACD,YAAY;IAET,KAAK,uBAAuB;GAChC,CAAC;EACT,OACI,KAAK,QAAQ;CAErB;;;;;CAMA,yBAAuC;EACnC,IAAI,KAAK,UAAU,QACf,KAAK,QAAQ;CAErB;;CAGA,UAAgB;EACZ,KAAK,qBAAqB;EAC1B,KAAK,SAAS,cAAc;EAC5B,KAAK,aAAa,iBAAiB;EACnC,KAAK,eAAe;CACxB;;CAGA,gBAA8C;EAC1C,MAAM,WAAW,oBAAoB;EACrC,SAAS,sBAAsB,EAAE,qBAAqB,gBAAgB;GAClE,MAAM,aAAa,oBAAoB,QAAQ,oBAAoB,EAAE;GACrE,KAAK,WAAW;IAAE,WAAW;IAAY;GAAU,CAAC;EACxD;EACA,SAAS,oCAAoC;GACzC,KAAK,SAAS,cAAc;EAChC;EACA,SAAS,kCAAkC;GACvC,KAAK,SAAS,SAAS;EAC3B;EACA,SAAS,wBAAwB,WAAW;GACxC,KAAK,kBAAkB;GACvB,KAAK,OAAO;IAAE,KAAK,OAAO;IAAK,KAAK,OAAO;IAAK,MAAM,OAAO,QAAQ;GAAG;GACxE,KAAK,SAAS,WAAW;EAC7B;EACA,SAAS,yBAAyB,SAAS,YAAY;GACnD,IAAI,gBAAgB,SAAS,OAAO,GAAG;IAEnC,KAAK,QAAQ;IACb;GACJ;GACA,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,SAAS,QAAQ;EAC1B;EACA,SAAS,sBAAsB;GAC3B,KAAK,kBAAkB;GACvB,KAAK,qBAAqB;GAC1B,KAAK,SAAS,QAAQ;EAC1B;EACA,OAAO;CACX;;CAGA,OAAa;EACT,KAAK,kBAAkB;EACvB,IAAI,KAAK,eAAe,MAAM;GAC1B,KAAK,aAAa,0BAA0B,KAAK,UAAU;GAC3D,KAAK,aAAa;EACtB;CACJ;;CAGA,iBAA+B;EAC3B,KAAK,kBAAkB;EACvB,KAAK,eAAe,iBAAiB;GACjC,KAAK,eAAe;GACpB,KAAK,qBAAqB;GAC1B,KAAK,SAAS,QAAQ;EAC1B,GAAG,KAAK,SAAS;CACrB;;CAGA,oBAAkC;EAC9B,IAAI,KAAK,iBAAiB,MAAM;GAC5B,aAAa,KAAK,YAAY;GAC9B,KAAK,eAAe;EACxB;CACJ;CAEA,SAAiB,MAAwB;EACrC,KAAK,QAAQ;EACb,KAAK,MAAM,MAAM,KAAK,eAClB,GAAG,IAAI;CAEf;CAEA,WAAmB,IAAsB;EACrC,KAAK,MAAM,MAAM,KAAK,gBAClB,GAAG,EAAE;CAEb;AACJ;;;;;;;;;;;;;;;;;;;;AC/NA,SAAgB,oBAAoB,MAAsB;CACtD,MAAM,OAAO,KAAK,QAAQ,OAAO,GAAG;CACpC,IAAI,KAAK,SAAS,eAAe,GAC7B,OAAO;CAEX,IAAI,KAAK,SAAS,QAAQ,GACtB,OAAO,KAAK,MAAM,QAAQ;CAE9B,OAAO,KAAK,MAAM,SAAS,QAAQ;AACvC;;AAGA,SAAgB,gBAAgB,KAA4B;CACxD,IAAI,IAAI,WAAW,GAAG,KAAK,IAAI,WAAW,GAAG,GACzC,IAAI;EACA,OAAO,gBAAgB,KAAK,MAAM,GAAG,CAAC;CAC1C,QAAQ;EACJ,OAAO;CACX;CAEJ,OAAO;AACX;;AAGA,SAAS,gBAAgB,OAA+B;CACpD,IAAI,OAAO,UAAU,UACjB,OAAO,aAAa,KAAK,IAAI,QAAQ;CAEzC,IAAI,MAAM,QAAQ,KAAK,GACnB,OAAO,YAAY,KAAK;CAE5B,IAAI,OAAO,UAAU,YAAY,UAAU,MACvC,OAAO,aAAa,KAAK;CAE7B,OAAO;AACX;;AAGA,SAAS,aAAa,OAAwB;CAC1C,OAAO,MAAM,SAAS,eAAe,KAAK,MAAM,SAAS,OAAO;AACpE;;AAGA,SAAS,YAAY,OAAiC;CAClD,KAAK,MAAM,QAAQ,OAAO;EACtB,MAAM,QAAQ,gBAAgB,IAAI;EAClC,IAAI,UAAU,MACV,OAAO;CAEf;CACA,OAAO;AACX;;AAGA,SAAS,aAAa,OAA8B;CAChD,KAAK,MAAM,KAAK,OAAO,OAAO,KAAK,GAAG;EAClC,MAAM,QAAQ,gBAAgB,CAAC;EAC/B,IAAI,UAAU,MACV,OAAO;CAEf;CACA,OAAO;AACX;;;;;;;;;;;;;;;;;;ACxDA,MAAM,mBAAmB;;;;;;AAOzB,MAAM,eAAe;CACjB,SAAS;CACT,SAAS;CACT,WAAW;CACX,SAAS;AACb;;;;;AAMA,MAAM,qBAAqB;;AAG3B,MAAM,kBAAkB;;AAGxB,MAAM,uBAA+C;CACjD,OAAO;CACP,QAAQ;CACR,OAAO;CACP,SAAS;CACT,SAAS;CACT,SAAS;CACT,OAAO;CACP,KAAK;AACT;;;;;;AAOA,SAAS,aAAsE;CAC3E,IAAI,UAAU;CACd,IAAI,UAAU;CACd,IAAI,YAAY;CAChB,IAAI;EACA,UAAU,qBAAqB,SAAS,MAAM,SAAS;EAEvD,UAAU,GAAG,QAAQ,GAAG,QAAQ;EAEhC,YAAYC,QAAgB,KAAKC,KAAO;CAC5C,QAAQ,CAER;CACA,OAAO;EAAE;EAAS;EAAW;CAAQ;AACzC;;AAGA,MAAM,iBAAiB;;;;;;AAOvB,SAAgB,oBAAoB,WAAkC;CAClE,IAAI,CAAC,WAAW,SAAS,GACrB,OAAO;CAEX,IAAI;CACJ,IAAI;EACA,MAAM,aAAa,SAAS;CAChC,QAAQ;EACJ,OAAO;CACX;CACA,OAAO,iBAAiB,GAAG;AAC/B;;AAGA,SAAS,iBAAiB,KAA4B;CAClD,MAAM,SAAS,OAAO,KAAK,YAAY,OAAO;CAC9C,IAAI,MAAM;CACV,OAAO,MAAM,IAAI,QAAQ;EACrB,MAAM,MAAM,IAAI,QAAQ,QAAQ,GAAG;EACnC,IAAI,QAAQ,IACR,OAAO;EAEX,MAAM,MAAM,gBAAgB,KAAK,MAAM,OAAO,MAAM;EACpD,IAAI,eAAe,KAAK,GAAG,GACvB,OAAO;EAEX,MAAM,MAAM,OAAO,SAAS,IAAI,SAAS;CAC7C;CACA,OAAO;AACX;;AAGA,SAAS,gBAAgB,KAAa,OAAuB;CACzD,IAAI,MAAM;CACV,OAAO,MAAM,IAAI,UAAU,IAAI,SAAS,GACpC,OAAO;CAEX,OAAO,IAAI,SAAS,OAAO,GAAG,CAAC,CAAC,SAAS,OAAO;AACpD;;;;;;;AAQA,SAAgB,gBACZ,aACA,WAC8B;CAC9B,MAAM,QAAQ,cAAc,KAAA,IAAY,oBAAoB,SAAS,IAAI;CACzE,IAAI,UAAU,MACV,MAAM,YACF,+EACA,eACJ;CAEJ,OAAO;EACH;EACA,KAAK,aAAa,YAAY,GAAG,YAAY,MAAM,GAAG,CAAC,CAAC,MAAM,GAAG;CACrE;AACJ;;AAGA,SAAgB,kBACZ,aACA,gBACA,WACsB;CACtB,MAAM,EAAE,cAAc,WAAW;CACjC,MAAM,EAAE,QAAQ,gBAAgB,aAAa,SAAS;CACtD,OAAO;EACH,kBAAkB;EAClB,eAAeC,aAAkB;EACjC,UAAU;EACV,aAAa;EACb,YAAY;EACZ,UAAU;EACV;EACA,oBAAoB,EAChB,mBAAmB,eACvB;EACA,cAAc;CAClB;AACJ;;AAGA,SAAgB,iBACZ,OACA,aACA,YACuB;CACvB,MAAM,EAAE,YAAY,WAAW;CAC/B,OAAO;EACH,WAAW;EACX;EACA;EACA;EACA,WAAW;EACX,UAAU,SAAS;EACnB,iBAAiB;CACrB;AACJ;;AAeA,SAAgB,mBAAmB,SAAyD;CACxF,MAAM,EAAE,OAAO,aAAa,SAAS,SAAS,aAAa,cAAc,gBACrE;CACJ,MAAM,EAAE,SAAS,WAAW,YAAY,WAAW;CACnD,MAAM,aAAyB;EAE3B,MAAM,eAAe;EACrB,UAAU;EACV,SAAS;EACT,SAAS,SAAS;EAClB;EACA,YAAY;EACZ,OAAO;EACP,cAAc;EACd,SAAS;EACT,YAAY,WAAW;CAC3B;CACA,OAAO;EACH;EACA;EACA,mBAAmB,EACf,cAAc,YAClB;EACA,WAAW;EACX,IAAI;EACJ,IAAI;EACJ,OAAO;EACP,WAAW;EACX,UAAUA,aAAkB;EAC5B;EACA;EACA,iBAAiB;GACb,QAAQ;GACR,UAAU;GACV,OAAO;GACP,kBAAkB;GAClB,UAAUA,aAAkB;GAC5B,UAAU;GACV,YAAY;GACZ,QAAQ;GACR,YAAY;GACZ,WAAW;GACX,UAAU;GACV,WAAW;GACX,mBAAmB,CAAC,EAAE;EAC1B;EACA,yBAAyB;EACzB;EACA,cAAc;CAClB;AACJ;;;;;;;AAQA,SAAgB,gBAAgB,cAA+B;CAC3D,IAAI;EACA,MAAM,KAAM,eAA8D;EAC1E,IAAI,OAAO,OAAO,YACd,OAAO;EAEX,MAAM,QAAiB,GAAG,KAAK,YAAY;EAC3C,OAAO,OAAO,UAAU,WAAW,QAAQ;CAC/C,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;;;;;;;;;;AC7OA,SAAS,oBAAoB,KAAc,YAA6C;CACpF,OAAO;EACH,kBAAkB,IAAI,WAAW;EACjC,eAAe,aAAa;EAC5B,UAAU;EACV,aAAa,IAAI,aAAa;EAC9B,YAAY,QAAQ;EACpB,UAAU;EACV,KAAK;EACL,oBAAoB,EAChB,mBAAmB,cAAc,IAAI,WAAW,GACpD;EACA,cAAc;GAAE,SAAS;GAAK,SAAS;GAAI,WAAW;GAAG,SAAS;EAAE;CACxE;AACJ;;;;;;AAOA,SAAgB,sBAA0C;CACtD,OAAQ,QAAyC;AACrD;;;;;;;;;;;AAYA,SAAgB,sBAAsB,SAAwC;CAC1E,OAAO,oBAAoB,OAAO,KAAK,KAAK,QAAQ,GAAG,aAAa,eAAe;AACvF;;AAGA,SAAS,oBAAoB,SAAwC;CACjE,IAAI;EAIA,MAAM,MAHO,QAAQ,qBAGH,MAAM,CAAC,CAAC,0BAA0B;EACpD,IAAI,OAAO,QAAQ,YAAY,IAAI,KAAK,MAAM,IAC1C,OAAO,gBAAgB,IAAI,KAAK,CAAC;CAEzC,QAAQ,CAER;CACA,OAAO;AACX;;;;;AAgCA,SAAgB,cACZ,SACA,aACc;CACd,MAAM,cAAc,QAAQ;CAG5B,IAAI,gBAAgB,QAAQ,OAAO,YAAY,QAAQ,YACnD,MAAM,YACF,sDACA,eACJ;CAEJ,MAAM,SAAS,YAAY,IAAI;CAC/B,IAAI,CAAC,UAAU,OAAO,OAAO,0BAA0B,YACnD,MAAM,YAAY,uCAAuC,eAAe;CAE5E,OAAO;EACH;EACA;EACA;EACA,SAAS;EACT,gBAAgB;EAChB,cAAc;CAClB;AACJ;;AAGA,SAAgB,WAAW,KAAqB,QAAsC;CAClF,IAAI,OAAO,sBAAsB,QAAQ,IAAI,cAAc,CAAC;AAChE;;;;;;;;;;;;AAaA,SAAgB,cAAc,KAA8C;CACxE,MAAM,IAAI,IAAI,QAAQ;CACtB,MAAM,UACF,qBAAqB,GAAG,KAAK,oBAAoB,CAAC,KAAK,iBAAiB,CAAC,KAAK,IAAI,EAAE;CACxF,IAAI,UAAU;CACd,OAAO;AACX;;AAGA,SAAS,qBAAqB,KAAqD;CAC/E,MAAM,MAAM,IAAI,QAAQ;CAKxB,IAAI,QAAQ,KAAA,KAAa,OAAO,IAAI,WAAW,YAC3C,OAAO;CAEX,IAAI;EACA,IAAI,iBAAiB,IAAI,OAAO;CACpC,QAAQ,CAER;CACA,OAAO;AACX;;AAGA,SAAS,oBAAoB,GAAgE;CACzF,IAAI,OAAO,EAAE,wBAAwB,YACjC,OAAO;CAEX,IAAI;EACA,OAAO,EAAE,oBAAoB,MAAM;CACvC,QAAQ;EACJ,OAAO;CACX;AACJ;;AAGA,SAAS,iBAAiB,GAAgE;CACtF,IAAI,OAAO,EAAE,WAAW,YACpB,OAAO;CAEX,IAAI;EACA,OAAO,EAAE,OAAO;CACpB,QAAQ;EACJ,OAAO;CACX;AACJ;;AAGA,SAAgB,YACZ,KACA,QACA,UACI;CAEJ,CADgB,IAAI,WAAW,cAAc,GAAG,EAAA,CACxC,KAAK,QAAQ,IAAI,eAAe,GAAG,IAAI,kBAAkB,GAAG,QAAQ;AAChF;;;;;;;;;AAUA,SAAgB,aAAa,KAA2B;CACpD,MAAM,EAAE,SAAS,mBAAmB;CACpC,IAAI,YAAY,MACZ,MAAM,YAAY,oBAAoB,eAAe;CAEzD,IAAI,mBAAmB,QAAQ,OAAO,eAAe,UAAU,YAAY;EACvE,eAAe,MAAM;EACrB;CACJ;CACA,QAAQ,QAAQ,CAAC;AACrB;;;;;;;;;;;;AAaA,SAAgB,eAAe,SAAgD;CAC3E,MAAM,EAAE,KAAK,cAAc,eAAe,gBAAgB,WAAW,mBAAmB;CAKxF,MAAM,MAAM,cAAc,gBAAgB;EAHtC,aAAa,KAAK,aAAa;EAC/B,cAAc,KAAK,aAAa;CAEgB,CAAC;CACrD,MAAM,qBAAqB,eAAe,KAAK,SAAS;CAMxD,MAAM,aAAa,oBAAoB,MAAM,KAAA;CAC7C,eAAe,KAAK,oBAAoB,UAAU;CAClD,WAAW,KAAK,oBAAoB,KAAK,KAAK,YAAY,CAAC;CAC3D,oBAAoB,KAAK,cAAc;CAGvC,cAAc,KAAK,oBAAoB,YAAY,aAAa;CAChE,OAAO;AACX;;AAGA,SAAS,eAAe,KAAqB,aAAsB,YAA2B;CAC1F,IAAI,CAAC,eAAe,YAChB,cAAc,GAAG;AAEzB;;AAGA,SAAS,cACL,KACA,aACA,YACA,eACI;CACJ,IAAI,CAAC,eAAe,CAAC,YACjB,cAAc,GAAG;CAErB,IAAI,kBAAkB,KAAA,GAAW;EAC7B,YAAY,KAAK,eAAe,sBAAsB,CAAC;EACvD,aAAa,GAAG;CACpB;AACJ;;AAGA,SAAS,oBACL,KACA,KACA,cACsB;CACtB,IAAI,iBAAiB,KAAA,GACjB,OAAO;CAEX,OAAO,oBAAoB,OAAO,CAAC,GAAG,wBAAwB,GAAG,CAAC;AACtE;;AAGA,SAAS,eACL,KACA,WACO;CACP,IAAI,cAAc,KAAA,KAAa,cAAc,MACzC,OAAO;CAEX,IAAI,UAAU;CACd,OAAO;AACX;;;;;;;;AASA,SAAS,wBAAwB,KAAyC;CACtE,IAAI,oBAAoB,MAAM,WAC1B;CAEJ,MAAM,OAAO,sBAAsB,IAAI,OAAO;CAC9C,IAAI,SAAS,MACT;CAEJ,OAAO,oBAAoB,IAAI;AACnC;;;;;;AAOA,SAAS,oBAAoB,KAAqB,gBAA+B;CAC7E,IAAI,mBAAmB,KAAA,KAAa,mBAAmB,MAAM;EACzD,IAAI,eAAe;EACnB;CACJ;CACA,IAAI,eAAe,wBAAwB,IAAI,OAAO;AAC1D;;AAGA,SAAS,wBAAwB,SAAkC;CAC/D,IAAI;EACA,MAAM,eAAe,QAAQ;EAG7B,IAAI,iBAAiB,QAAQ,OAAO,aAAa,QAAQ,YACrD,OAAO,aAAa,IAAI;EAE5B,OAAO,IAAI,QAAQ,wBAAwB;CAC/C,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;;;;;;;;;;;;;;;;;;ACvQA,IAAa,gBAAb,MAAa,cAAc;;CAEvB;;CAGA,YAA2C;;CAG3C,YAAoB,KAAkB;EAClC,KAAK,MAAM;CACf;;;;;CAMA,OAAO,OAAO,OAA6B,CAAC,GAAkB;EAC1D,MAAM,QAAQ,IAAI,YAAY,KAAK,KAAK;EACxC,MAAM,OAAO;EAIb,MAAM,SAAS,aAAa;GACxB,OAAO,KAAK,YAAY;GACxB,SAAS,KAAK,cAAc;GAC5B,MAAM,KAAK,WAAW,MAAM,KAAK,QAAQ,eAAe;GACxD,WAAW,KAAK;GAChB,QAAQ,KAAK;GACb,WAAW,KAAK;GAChB,UAAU,KAAK;GACf,MAAM;IAAE,MAAM;IAAU,SAAS,KAAK;GAAQ;EAClD,CAAC;EACD,OAAO,IAAI,cAAc,kBAAkB;GAAE;GAAQ;EAAM,CAAC,CAAC;CACjE;;;;;;CAOA,cACI,gBACA,KACA,UACc;EACd,IAAI;EACJ,IAAI,QAAQ,KAAA,GACR,UAAU,eAAe;GAAE;GAAgB,GAAG;EAAS,CAAC;OAExD,UAAU,eAAe;GAAE;GAAgB;GAAK,GAAG;EAAS,CAAC;EAEjE,KAAK,IAAI,UAAU;EACnB,KAAK,IAAI,OAAO,KAAK,EAAE,SAAS,KAAK,UAAU,GAAG,gCAAgC;EAClF,OAAO;CACX;;;;;;;;;;CAWA,MAAM,MAAM,MAA8C;EACtD,MAAM,EAAE,YAAY,KAAK;EACzB,IAAI,YAAY,MACZ,MAAM,YAAY,oBAAoB,eAAe;EAIzD,KAAK,gBAAgB,SAAS,KAAK,KAAK;EAGxC,IAAI,KAAK,WAAW,MAAM;GACtB,MAAM,cAAc,MAAM,KAAK,UAAU,IAAI;GAC7C,KAAK,IAAI,QAAQ;GACjB,OAAO;EACX;EAGA,MAAM,cAAc,MAAM,KAAK,gBAAgB,SAAS,IAAI;EAK5D,KAAK,IAAI,QAAQ;EACjB,OAAO;CACX;;CAGA,MAAc,gBACV,SACA,MACoB;EACpB,IAAI;GACA,MAAM,SAAS,MAAM,WAAW,SAAS,uBAAuB,IAAI,CAAC;GACrE,KAAK,IAAI,OAAO,KAAK;IAAE,KAAK,OAAO;IAAK,KAAK,OAAO;GAAI,GAAG,QAAQ;GACnE,OAAO;EACX,SAAS,KAAK;GACV,IAAI,KAAK,eAAe,MACpB,MAAM;GAEV,OAAO,MAAM,KAAK,UAAU,IAAI;EACpC;CACJ;;;;;;;;;CAUA,gBAAwB,SAAyB,OAAqB;EAClE,MAAM,eAAe,QAAQ;EAG7B,IAAI,iBAAiB,QAAQ,OAAO,aAAa,eAAe,YAAY;GACxE,KAAK,IAAI,OAAO,KAAK,gCAAgC;GACrD;EACJ;EACA,MAAM,aAAa,kBAAkB,KAAK,KAAK,OAAO;EACtD,MAAM,WAAW,iBAAiB,OAAO,QAAQ,YAAY,aAAa,UAAU;EACpF,aAAa,WAAW,QAAQ;EAChC,KAAK,IAAI,OAAO,KAAK,EAAE,WAAW,GAAG,4BAA4B;CACrE;;;;;CAMA,WAAW,SAAwC;EAC/C,IAAI,KAAK,IAAI,YAAY,MACrB,MAAM,YAAY,4BAA4B,eAAe;EAEjE,KAAK,IAAI,QAAQ,UAAU;CAC/B;;;;;CAMA,YAAqB;EACjB,IAAI,KAAK,cAAc,MACnB,OAAO;EAEX,KAAK,UAAU,QAAQ;EACvB,OAAO;CACX;;CAGA,MAAc,UAAU,MAA8C;EAClE,MAAM,EAAE,YAAY,KAAK;EACzB,IAAI,YAAY,QAAQ,QAAQ,iBAAiB,MAC7C,MAAM,YAAY,qCAAqC,eAAe;EAE1E,MAAM,UAAU,IAAI,eAAe,QAAQ,YAAY;EAEvD,KAAK,YAAY;EACjB,MAAM,SAAS,KAAK,cAAc,KAAK,IAAI,MAAM,KAAK,SAAS,YAAY;EAC3E,KAAK,oBAAoB,SAAS,MAAM,MAAM;EAE9C,IAAI;GAKA,QAAQ,MAAM;GAEd,MAAM,eAAe,OAAO;GAC5B,MAAM,OAAO,kBAAkB,OAAO;GACtC,OAAO;IAAE,KAAK,KAAK;IAAK,KAAK,KAAK;IAAK,MAAM,KAAK;GAAK;EAC3D,UAAU;GACN,QAAQ,KAAK;GACb,KAAK,YAAY;EACrB;CACJ;;CAGA,oBACI,SACA,MACA,QACI;EACJ,QAAQ,UAAU,OAAO;GACrB,KAAK,IAAI,OAAO,KAAK,gBAAgB,OAAO,UAAU,GAAG,UAAU,EAAE;GACrE,IAAI,GAAG,cAAc,IACjB,eAAe,QAAQ,GAAG,SAAS;GAEvC,KAAK,kBAAkB;IAAE,OAAO;IAAgB;GAAG,CAAC;EACxD,CAAC;EACD,QAAQ,eAAe,UAAU;GAC7B,KAAK,eAAe,SAAS,MAAM,KAAK;EAC5C,CAAC;CACL;;CAGA,eACI,SACA,MACA,OACI;EACJ,KAAK,IAAI,OAAO,KAAK,EAAE,MAAM,GAAG,SAAS;EACzC,KAAK,kBAAkB,gBAAgB,OAAO,QAAQ,iBAAiB,KAAA,CAAS,CAAC;CACrF;;CAGA,OAAa;EACT,KAAK,IAAI,OAAO,KAAK,kBAAkB;CAC3C;AACJ;;AAGA,SAAS,eAAe,UAAkB,WAAyB;CAC/D,IAAI;EACA,UAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;EAChD,cAAc,UAAU,OAAO,KAAK,WAAW,QAAQ,CAAC;CAC5D,QAAQ,CAER;AACJ;;AAGA,SAAS,uBAAuB,MAA2C;CACvE,MAAM,YAA+B,CAAC;CACtC,IAAI,KAAK,aAAa,KAAA,GAClB,UAAU,MAAM,KAAK;CAEzB,IAAI,KAAK,wBAAwB,KAAA,GAC7B,UAAU,sBAAsB,KAAK;CAEzC,OAAO;AACX;;AAGA,SAAS,kBAAkB,SAAmC;CAC1D,MAAM,OAAO,QAAQ;CACrB,IAAI,SAAS,MACT,MAAM,YAAY,wBAAwB,eAAe;CAE7D,OAAO;AACX;;AAGA,SAAS,gBAAgB,OAAmB,QAA2C;CACnF,MAAM,UAAU,UAAU,WAAW,SAAS,KAAA;CAC9C,OAAO;EAAE;EAAO,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;CAAG;AAClE;;AAGA,SAAS,eAAe,SAAwC;CAC5D,OAAO,IAAI,SAAe,SAAS,WAAW;EAC1C,MAAM,WAAW,QAAQ,eAAe,UAAU;GAC9C,IAAI,UAAU,aAAa;IACvB,SAAS;IACT,QAAQ;GACZ,OAAO,IAAI,UAAU,UAAU;IAC3B,SAAS;IACT,OAAO,YAAY,WAAW,WAAW,CAAC;GAC9C;EACJ,CAAC;CACL,CAAC;AACL;;;;;;AAOA,SAAS,kBAAkB,KAAkB,SAAiC;CAC1E,IAAI,oBAAoB,MAAM,WAAW;EACrC,MAAM,OAAO,sBAAsB,QAAQ,OAAO;EAClD,IAAI,SAAS,MACT,OAAO,oBAAoB,IAAI;CAEvC;CACA,OAAO,IAAI,MAAM;AACrB;;;;ACzXA,SAAS,kBAAkB,KAA2C;CAClE,OAAO,cAAc,MAAM,MAAM,EAAE,WAAW,KAAK,CAAC,KAAK,cAAc,WAAW,IAAI;AAC1F;;AAGA,SAAS,cAAc,KAA4B,MAA6C;CAC5F,KAAK,MAAM,CAAC,GAAG,MAAM,KAAK;EACtB,IAAI,OAAO,MAAM,YAAY,KAAK,CAAC,GAC/B,OAAO;EAEX,IAAI,OAAO,MAAM,YAAY,KAAK,CAAC,GAC/B,OAAO;CAEf;CACA,OAAO;AACX;;AAGA,SAAS,mBACL,SACA,qBAC8B;CAC9B,QAAQ,QAAQ;CAChB,MAAM,MAAM,OAAO,QAAQ,qBAAqB,aAAa,QAAQ,iBAAiB,IAAI;CAC1F,IAAI,EAAE,eAAe,MACjB,OAAO;CAEX,MAAM,SAAS,kBAAkB,GAAG;CACpC,IAAI,WAAW,MACX,OAAO;CAEX,MAAM,QAAQ,oBAAoB,MAAM;CACxC,OAAO,UAAU,QAAQ,UAAU,KAAA,KAAa,OAAO,MAAM,kBAAkB,aACzE,QACA;AACV;;;;;AAMA,SAAgB,mBAAmB,KAAqD;CACpF,MAAM,MAAM,cAAc,GAAG;CAC7B,IAAI,QAAQ,MAAM;EACd,IAAI,UAAU;EACd,OAAO;CACX;CACA,OAAO;AACX;;AAGA,SAAS,cAAc,KAAqD;CACxE,IAAI;EACA,MAAM,IAAI,IAAI,QAAQ;EAGtB,IAAI,OAAO,EAAE,QAAQ,YACjB,OAAO;EAEX,MAAM,MAAM,EAAE,IAAI;EAClB,OAAO,OAAO,OAAO,IAAI,kBAAkB,aAAa,MAAM;CAClE,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;;;AAQA,SAAgB,eAAe,KAAqD;CAChF,IAAI;EACA,OAAO,uBAAuB,GAAG,KAAK;CAC1C,QAAQ;EACJ,OAAO;CACX;AACJ;;AAGA,SAAS,uBAAuB,KAAqD;CACjF,MAAM,aAAa,IAAI,QAAQ;CAG/B,MAAM,cAAc,IAAI,QAAQ;CAGhC,IACI,OAAO,WAAW,WAAW,cAC7B,OAAO,YAAY,wBAAwB,YAE3C,OAAO;CAEX,MAAM,UAAU,WAAW,OAAO;CAClC,IAAI,CAAC,SACD,OAAO;CAEX,MAAM,cAAc,OACf,YAAY,oBAAkE,EAAE;CACrF,MAAM,UAAU,mBAAmB,SAAS,UAAU;CACtD,IAAI,YAAY,MACZ,IAAI,UAAU;CAElB,OAAO;AACX;;;;;;;;;;;;;;;ACxGA,MAAM,sBAAsB;;AAE5B,MAAM,kBAAkB;;AAExB,MAAM,kBAAkB;;AAExB,MAAM,YAAY;;AAElB,MAAM,kBAAkB;;AAGxB,SAAgB,UAAU,OAAgB,QAAQ,GAAY;CAC1D,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IAAI,OAAO,UAAU,UAAU,OAAO,MAAM,SAAS;CACrD,IAAI,OAAO,UAAU,YAAY,OAAO,aAAa,MAAM,QAAQ,OAAO;CAC1E,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI,QAAQ,qBAAqB,OAAO;CACxC,IAAI;EACA,OAAO,mBAAmB,OAAO,KAAK;CAC1C,QAAQ;EACJ,OAAO;CACX;AACJ;;AAGA,SAAS,mBAAmB,OAAe,OAAwB;CAC/D,IAAI,YAAY,OAAO,KAAK,GACxB,OAAO,eAAe,IAAI,WAAW,MAAM,QAAQ,MAAM,YAAY,MAAM,UAAU,CAAC;CAE1F,IAAI,MAAM,QAAQ,KAAK,GAAG;EACtB,IAAI,MAAM,UAAU,mBAAmB,eAAe,KAAK,GACvD,OAAO,eAAe,WAAW,KAAK,QAAQ,MAAO,IAAe,GAAI,CAAC;EAE7E,OAAO,eAAe,OAAO,KAAK;CACtC;CACA,IAAI,iBAAiB,KACjB,OAAO,aAAa,OAAO,KAAK;CAEpC,IAAI,iBAAiB,KACjB,OAAO,aAAa,OAAO,KAAK;CAEpC,IAAI,OAAQ,MAA2B,SAAS,YAC5C,OAAO;CAEX,OAAO,gBAAgB,OAAO,KAAK;AACvC;;AAGA,SAAS,eAAe,OAA2B;CAC/C,OAAO,MAAM,OACR,MAAM,OAAO,MAAM,YAAY,OAAO,UAAU,CAAC,KAAK,KAAK,QAAQ,KAAK,GAC7E;AACJ;;AAGA,SAAS,eAAe,OAA4C;CAChE,MAAM,SAAS,MAAM,SAAS,YAAY,MAAM,SAAS,GAAG,SAAS,IAAI;CACzE,MAAM,MAA+B;EACjC,MAAM;EACN,MAAM,MAAM;EACZ,MAAM,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,QAAQ;CAC/C;CACA,IAAI,OAAO,SAAS,MAAM,QACtB,IAAI,eAAe,MAAM,SAAS,OAAO;CAE7C,OAAO;AACX;;AAGA,SAAS,eAAe,OAAkB,OAA0B;CAChE,OAAO,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,KAAK,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC;AAC7E;;AAGA,SAAS,aAAa,OAA8B,OAAwB;CACxE,MAAM,MAA+B,CAAC;CACtC,IAAI,IAAI;CACR,KAAK,MAAM,CAAC,GAAG,MAAM,OAAO;EACxB,IAAI,KAAK,iBAAiB;EAC1B,IAAI,OAAO,CAAC,KAAK,UAAU,GAAG,QAAQ,CAAC;EACvC,KAAK;CACT;CACA,OAAO;EAAE,MAAM;EAAO,MAAM,MAAM;EAAM,SAAS;CAAI;AACzD;;AAGA,SAAS,aAAa,OAAqB,OAAwB;CAC/D,OAAO;EACH,MAAM;EACN,MAAM,MAAM;EACZ,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,GAAG,eAAe,CAAC,CAAC,KAAK,MAAM,UAAU,GAAG,QAAQ,CAAC,CAAC;CACnF;AACJ;;AAGA,SAAS,gBAAgB,OAAe,OAAwB;CAC5D,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,CAAC,CAAC,MAAM,GAAG,eAAe,GACzD,IAAI,OAAO,UAAW,MAAkC,MAAM,QAAQ,CAAC;CAE3E,OAAO;AACX;;AAGA,SAAgB,SAAS,SAAkB,QAAyB;CAChE,IAAI;EACA,MAAM,KAAM,QAAoC;EAChD,IAAI,OAAO,OAAO,YAAY,OAAO;EACrC,MAAM,QAAQ,GAAG,KAAK,OAAO;EAC7B,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;EAClD,OAAO,UAAU,KAAK;CAC1B,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;;;;;;ACvHA,SAAgB,YAAY,KAAwB;CAChD,IAAI,QAAQ,QAAQ,QAAQ,KAAA,GACxB,OAAO,CAAC;CAEZ,MAAM,wBAAQ,IAAI,IAAY;CAC9B,sBAAsB,KAAK,KAAK;CAChC,gBAAgB,KAAK,KAAK;CAC1B,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK;AAC3B;;AAGA,SAAS,sBAAsB,KAAc,OAA0B;CACnE,IAAI,QAAQ,OAAO,eAAe,GAAG;CACrC,OAAO,aAAa,KAAK,GAAG;EACxB,KAAK,MAAM,QAAQ,OAAO,oBAAoB,KAAK,GAC/C,IAAI,SAAS,eACT,MAAM,IAAI,IAAI;EAGtB,QAAQ,OAAO,eAAe,KAAK;CACvC;AACJ;;AAGA,SAAS,aAAa,OAAuC;CACzD,OAAO,UAAU,QAAQ,UAAU,OAAO,aAAa,UAAU,SAAS;AAC9E;;AAGA,SAAS,gBAAgB,KAAc,OAA0B;CAC7D,KAAK,MAAM,QAAQ,OAAO,oBAAoB,GAAG,GAC7C,IAAI,SAAS,eACT,MAAM,IAAI,IAAI;AAG1B;;AAGA,SAAgB,QACZ,KACA,MACgD;CAChD,IAAI;EACA,MAAM,KAAM,IAAgC;EAC5C,IAAI,OAAO,OAAO,YACd,OAAO;GAAE,IAAI;GAAO,OAAO,0BAA0B,OAAO,GAAG;EAAG;EAGtE,OAAO;GAAE,IAAI;GAAM,OADL,GAAG,KAAK,GACC;EAAE;CAC7B,SAAS,GAAG;EACR,IAAI,UAAU,OAAO,CAAC;EACtB,IAAI,aAAa,OACb,UAAU,EAAE;EAEhB,OAAO;GAAE,IAAI;GAAO,OAAO;EAAQ;CACvC;AACJ;;AAGA,SAAgB,gBAAgB,MAAuB;CACnD,OAAO,KAAK,WAAW,KAAK,KAAK,KAAK,SAAS,SAAS;AAC5D;;;;ACxDA,SAAgB,iBAAiB,KAA8C;CAC3E,MAAM,OAAO;EACT;EACA;EACA;EACA;EACA;CACJ;CACA,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,OAAO,MAAM;EACpB,MAAM,OAAQ,IAAI,QAA+C;EACjE,IAAI,SAAS,QAAQ,SAAS,KAAA,GAC1B,IAAI,OAAO;GAAE,SAAS,YAAY,IAAI;GAAG,SAAS,OAAO,oBAAoB,IAAI;EAAE;CAE3F;CACA,OAAO;AACX;;AAGA,SAAgB,iBAAiB,KAA8C;CAC3E,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,KAAK,oBACZ,IAAI,KAAK,gBAAgB,KAAK,CAAC;CAEnC,OAAO;AACX;;AAGA,SAAS,gBAAgB,KAAqB,GAAoB;CAC9D,MAAM,OAAO,QAAQ,IAAI,QAAQ,CAAC;CAClC,OAAO,KAAK,KACN;EAAE,OAAO,UAAU,KAAK,KAAK;EAAG,SAAS,KAAK,QAAQ,YAAY,KAAK,KAAK,IAAI,CAAC;CAAE,IACnF,EAAE,OAAO,KAAK,SAAS,iBAAiB;AAClD;;AAGA,MAAM,qBAAqB;CACvB;CACA;CACA;CACA;AACJ;;AAGA,SAAgB,kBAAkB,KAAqD;CACnF,MAAM,OAAO,IAAI,QAAQ;CAGzB,IAAI,SAAS,QAAQ,OAAO,KAAK,QAAQ,YACrC,OAAO;CAEX,MAAM,OAAO,QAAQ,MAAM,KAAK;CAChC,IAAI,EAAE,KAAK,MAAM,KAAK,QAClB,OAAO,EAAE,KAAK,KAAK,SAAS,iBAAiB;CAEjD,OAAO;EACH,SAAS,YAAY,KAAK,KAAK;EAC/B,SAAS,OAAO,oBAAoB,KAAK,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;EAC3D,GAAG,kBAAkB,KAAK,KAAK;CACnC;AACJ;;AAGA,SAAS,kBAAkB,MAAwC;CAC/D,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,KAAK,qBACZ,IAAI,OAAQ,KAAiC,OAAO,YAChD,IAAI,KAAK,SAAS,MAAM,CAAC;CAGjC,OAAO;AACX;;AAGA,MAAM,sBAAsB;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;;;;;;;;;;;;;;;;;ACtEA,SAAS,oBAAoB,KAA8C;CACvE,MAAM,IAAI,IAAI,QAAQ;CACtB,MAAM,SAAS,sBAAsB,GAAG;CACxC,IAAI,WAAW,MACX,OAAO,CAAC;CAEZ,MAAM,MAA+B,CAAC;CACtC,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK,GAAG;EAC5B,eAAe,QAAQ,GAAG,MAAM,KAAK,GAAG;EACxC,eAAe,QAAQ,GAAG,QAAQ,KAAK,GAAG;CAC9C;CACA,OAAO;AACX;;AAGA,SAAS,eACL,QACA,SACA,IACA,KACI;CACJ,IAAI;EACA,MAAM,QAAQ,OAAO,KAAK,SAAS,EAAE;EACrC,IAAI,CAAC,OACD;EAEJ,IAAI,MAAM,mBAAmB,KAAK;CACtC,QAAQ,CAER;AACJ;;AAGA,SAAS,mBAAmB,OAAyC;CACjE,MAAM,MAAM;CACZ,MAAM,MAAM,cAAc,KAAK,KAAK;CACpC,OAAO;EACH,qBAAqB,OAAO,IAAI,qBAAqB;EACrD,cAAc,IAAI,YAAY;EAC9B,mBAAmB,IAAI,UAAU,YAAY,IAAI,OAAO,CAAC,CAAC,SAAS;EACnE,UAAU,IAAI;EACd,SAAS,YAAY,KAAK;CAC9B;AACJ;;AAGA,SAAS,cACL,KACA,SAC0C;CAC1C,IAAI;EAEA,OAAO;GAAE,SADI,IAAI,gBAAgB,CAAmB,KAAK,OACrC,KAAK;GAAM,OAAO;EAAK;CAC/C,SAAS,GAAG;EACR,OAAO;GAAE,SAAS;GAAM,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;EAAE;CAC9E;AACJ;;AAGA,SAAS,sBAAsB,KAA+D;CAC1F,MAAM,KAAM,IAAI,QAAQ,wBACpB;CAEJ,OAAO,OAAO,OAAO,aAAc,KAAyC;AAChF;;AAGA,SAAS,kBAAkB,SAIzB;CACE,MAAM,UAAU,QAAQ,SAAS,OAAO;CACxC,MAAM,MAAM,QAAQ,SAAS,kBAAkB;CAC/C,OAAO;EACH,OAAO,QAAQ,KAAK,EAAE,IAAI,KAAK,IAAI,EAAE,OAAO,QAAQ,MAAM;EAC1D,yBAAyB,IAAI,KAAK,EAAE,OAAO,UAAU,IAAI,KAAK,EAAE,IAAI,EAAE,OAAO,IAAI,MAAM;EACvF,YAAY,kBAAkB,GAAG;CACrC;AACJ;;AAGA,SAAS,kBAAkB,KAAiD;CACxE,IAAI,EAAE,IAAI,MAAM,IAAI,iBAAiB,MACjC,OAAO,CAAC;CAEZ,OAAO,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,iBAAiB,GAAG,CAAC,CAAC;AAChE;;AAGA,SAAS,iBAAiB,GAAY,GAAoB;CACtD,OAAO,OAAO,MAAM,WAAW,IAAI,OAAO,CAAC;AAC/C;;AAGA,SAAS,YAAY,SAAkD;CACnE,OAAO,YAAY,QAAQ,YAAY,KAAA,IACjC;EACI,SAAS,YAAY,OAAO;EAC5B,SAAS,OAAO,oBAAoB,OAAO;CAC/C,IACA;AACV;;AAGA,SAAS,aAAa,GAAoB;CACtC,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;AACpD;;AAGA,SAAS,eAAe,KAAqB,IAAqB;CAC9D,MAAM,KAAK,sBAAsB,GAAG;CACpC,IAAI,OAAO,MACP,OAAO;CAEX,IAAI;EACA,OAAO,YAAY,GAAG,KAAK,IAAI,QAAQ,yBAAyB,EAAE,CAAC;CACvE,SAAS,GAAG;EACR,OAAO,EAAE,OAAO,aAAa,CAAC,EAAE;CACpC;AACJ;;AAGA,SAAS,iBAAiB,KAAqB,IAAqB;CAChE,OAAO,eAAe,KAAK,EAAE;AACjC;;AAGA,SAAS,oBAAoB,KAA8C;CACvE,MAAM,aAAa;EAAC;EAAQ;EAAW;EAAY;EAAW;CAAE;CAChE,MAAM,aAAsC,CAAC;CAC7C,KAAK,MAAM,QAAQ,YACf,WAAW,SAAS,KAAK,YAAY,QAAQ,mBAAmB,KAAK,IAAI;CAE7E,OAAO;AACX;;AAGA,SAAS,mBAAmB,KAAqB,MAAuB;CACpE,OAAO,eAAe,KAAK,IAAI;AACnC;;AAGA,SAAS,eAAe,KAAwD;CAC5E,MAAM,UAAU,IAAI,QAAQ;CAG5B,OAAO,YAAY,QAAQ,OAAO,QAAQ,WAAW,aAAa,OAAO;AAC7E;;AAGA,SAAS,WAAW,SAIQ;CACxB,OAAO,QAAQ,KACT;EACI,SAAS,QAAQ,QAAQ,YAAY,QAAQ,KAAK,IAAI,CAAC;EACvD,OAAO,UAAU,QAAQ,KAAK;CAClC,IACA,EAAE,OAAO,QAAQ,MAAM;AACjC;;AAGA,SAAS,mBACL,KACA,KACA,SACI;CACJ,IAAI,QAAQ,MAAM,QAAQ,OACtB,OAAO,OAAO,KAAK,kBAAkB,KAAK,QAAQ,KAAK,CAAC;AAEhE;;AAGA,SAAS,mBAAmB,KAA8B,KAA2B;CACjF,IAAI,IAAI,kBAAkB,KAAA,GACtB;CAEJ,IAAI,yBAAyB,oBAAoB,GAAG;AACxD;;AAGA,SAAS,aAAa,KAAqD;CACvE,MAAM,UAAU,eAAe,GAAG;CAClC,IAAI,YAAY,MACZ,OAAO;CAEX,MAAM,MAA+B,EAAE,eAAe,YAAY,OAAO,EAAE;CAC3E,MAAM,UAAU,QAAQ,SAAS,QAAQ;CACzC,IAAI,YAAY,WAAW,OAAO;CAClC,mBAAmB,KAAK,KAAK,OAAO;CACpC,IAAI,oBAAoB,oBAAoB,GAAG;CAE/C,mBAAmB,KAAK,GAAG;CAC3B,OAAO;AACX;;AAGA,SAAS,kBAAkB,KAAqB,SAA2C;CACvF,MAAM,QAAQ,kBAAkB,OAAO;CACvC,MAAM,MAA+B;EACjC,OAAO,MAAM;EACb,yBAAyB,MAAM;EAC/B,YAAY,MAAM;CACtB;CACA,MAAM,SAAS,gBAAgB,MAAM,UAAU;CAC/C,IAAI,WAAW,KAAA,GAAW;EACtB,IAAI,mBAAmB;EACvB,IAAI,iBAAiB,iBAAiB,KAAK,MAAM;CACrD;CACA,OAAO;AACX;;AAGA,SAAS,gBAAgB,YAA0C;CAC/D,OAAO,WAAW,MAAM,OAAO,GAAG,WAAW,KAAK,CAAC,KAAK,WAAW;AACvE;;;;;;;;;AAUA,SAAS,SAAS,OAAyB;CACvC,OAAO,UAAU,QAAQ,UAAU,KAAA;AACvC;AAEA,SAAS,aAAa,KAAqB,SAA+C;CACtF,MAAM,MAA+B,CAAC;CACtC,MAAM,MAAM,IAAI;CAChB,MAAM,OAAO,eAAe,GAAG;CAC/B,MAAM,UAAU,SAAS,GAAG,IAAI,MAAM;CACtC,IAAI,mBAAmB,SAAS,GAAG,IAAI,eAAe;CACtD,IAAI,aAAa,YAAY,OAAO;CACpC,eAAe,KAAK,SAAS,OAAO;CACpC,iBAAiB,KAAK,KAAK,IAAI;CAC/B,OAAO;AACX;;AAGA,SAAS,eACL,KACA,SACA,SACI;CACJ,IAAI,CAAC,SAAS,OAAO,GACjB;CAEJ,IAAI,cAAc,sBAAsB,SAAS,OAAO;AAC5D;;AAGA,SAAS,iBAAiB,KAA8B,KAAc,MAAqB;CACvF,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,GAChC;CAEJ,IAAI,uBAAuB,CACvB;EACI,QAAQ;EACR,aAAa,YAAY,IAAI,CAAC,CAAC;CACnC,CACJ;AACJ;;AAGA,SAAS,eAAe,KAA8B;CAClD,IAAI;EACA,OAAO,eAAe,GAAG;CAC7B,QAAQ;EACJ,OAAO;CACX;AACJ;;;;AAKA,SAAS,sBAAsB,SAAkB,SAA+C;CAC5F,MAAM,WAAoC,CAAC;CAC3C,KAAK,MAAM,UAAU,YAAY,OAAO,CAAC,CAAC,OAAO,eAAe,GAAG;EAC/D,SAAS,UAAU,mBAAmB,SAAS,MAAM;EACrD,UAAU;CACd;CACA,OAAO;AACX;;AAGA,SAAS,mBAAmB,SAAkB,QAAyB;CACnE,MAAM,OAAO,QAAQ,SAAS,MAAM;CACpC,IAAI,EAAE,KAAK,MAAM,KAAK,QAClB,OAAO;EAAE,OAAO,KAAK,SAAS;EAAkB,IAAI,KAAK;CAAG;CAEhE,OAAO;EACH,SAAS,YAAY,KAAK,KAAK;EAC/B,SAAS,OAAO,oBAAoB,KAAK,KAAK,CAAC,CAAC,MAAM,GAAG,EAAE;CAC/D;AACJ;;;;;;AAOA,SAAgB,aACZ,KACA,OAAO,wBACgB;CACvB,MAAM,SAAkC;EACpC,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EAClC,SAAS,YAAY,IAAI,OAAO;EAChC,eAAe,YAAY,IAAI,MAAM;EACrC,aAAa,iBAAiB,GAAG;EACjC,aAAa,iBAAiB,GAAG;EACjC,cAAc,kBAAkB,GAAG;EACnC,SAAS,aAAa,GAAG;CAC7B;CACA,MAAM,gBAAsB,eAAe,QAAQ,IAAI;CACvD,OAAO,OAAO,QAAQ,aAAa,KAAK,OAAO,CAAC;CAChD,eAAe,QAAQ,IAAI;CAC3B,OAAO;AACX;;AAGA,SAAS,eAAe,QAAiC,MAAoB;CACzE,IAAI;EACA,MAAM,MAAM,QAAQ,IAAI,qBAAqB,QAAQ,IAAI;EACzD,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;EAClC,MAAM,OAAO,KAAK,KAAK,IAAI;EAC3B,cAAc,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,GAAG,MAAM;EAC3D,OAAO,eAAe;CAC1B,SAAS,GAAG;EACR,OAAO,oBAAoB,aAAa,CAAC;CAC7C;AACJ;;;;;;;;;;;;;;;;;;;AC9TA,MAAM,+BAA+B;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ;;AAGA,MAAM,mBAAmB;AACzB,MAAM,oBAAoB;;AAE1B,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;;AAG3B,SAAS,iBAAiB,QAAwB;CAC9C,OAAO,OAAO,QAAQ,kBAAkB,EAAE,CAAC,CAAC,QAAQ,mBAAmB,EAAE;AAC7E;;;;;;;;AASA,SAAS,cAAc,SAAkB,WAAkC;CACvE,MAAM,UAAU,YAAY,OAAO;CACnC,MAAM,YAAY,YAAY,UAAU;CACxC,IAAI,QAAQ,SAAS,SAAS,GAC1B,OAAO;CAGX,OADc,QAAQ,MAAM,MAAM,gBAAgB,KAAK,CAAC,CAC7C,KAAK;AACpB;;AAGA,SAAS,iBAAiB,SAAkB,WAAkC;CAC1E,MAAM,UAAU,YAAY,OAAO;CACnC,MAAM,YAAY,eAAe,UAAU;CAC3C,IAAI,QAAQ,SAAS,SAAS,GAC1B,OAAO;CAGX,OADc,QAAQ,MAAM,MAAM,mBAAmB,KAAK,CAAC,CAChD,KAAK;AACpB;;;;;;AAOA,SAAgB,wBACZ,SACA,SACM;CACN,MAAM,UAAU,OAAe,SAA0B;EACrD,QAAQ;GAAE,qBAAI,IAAI,KAAK,EAAA,CAAE,YAAY;GAAG;GAAS;GAAO;EAAK,CAAC;CAClE;CAGA,OAAO,IAAI,MAAM,CAAC,GAA8B;EAC5C,IAAI,QAAQ,MAAM,UAAmB;GACjC,IAAI,OAAO,SAAS,UAChB,OAAO,QAAQ,IAAI,QAAQ,MAAM,QAAQ;GAE7C,OAAO,SAAS,CAAC,IAAI,CAAC;GACtB,QAAQ,GAAG,SAAoB;IAC3B,OACI,MACA,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,MAAM,UAAU,CAAC,CAAC,CACrC;GACJ;EACJ;EACA,IAAI,SAAS,MAAe;GACxB,IAAI,OAAO,SAAS,UAChB,OAAO,SAAS,CAAC,IAAI,CAAC;GAE1B,OAAO;EACX;EACA,UAAoB;GAChB,OAAO,aAAa,CAAC,CAAC;GACtB,OAAO,CAAC;EACZ;CACJ,CAAC;AACL;;;;;;;;;AAUA,SAAgB,uBACZ,KACA,OAAO,mCACgB;CACvB,MAAM,MAAM,QAAQ,IAAI,qBAAqB,QAAQ,IAAI;CACzD,UAAU,KAAK,EAAE,WAAW,KAAK,CAAC;CAClC,MAAM,OAAO,KAAK,KAAK,IAAI;CAE3B,cAAc,MAAM,IAAI,MAAM;CAC9B,MAAM,QAAQ,WAAsC;EAChD,IAAI;GACA,eAAe,MAAM,GAAG,KAAK,UAAU,MAAM,EAAE,KAAK,MAAM;EAC9D,QAAQ,CAER;CACJ;CACA,MAAM,aAAoD,CAAC;CAC3D,MAAM,UAAU,IAAI;CACpB,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,KAAK,MAAM,UAAU,8BACjB,WAAW,iBAAiB,MAAM,KAAK,cAAc,SAAS,QAAQ,IAAI;CAGlF,WAAW,WAAW,mBAAmB,KAAK,IAAI;CAClD,OAAO;EAAE,MAAM;EAAM;CAAW;AACpC;;AAGA,SAAS,cACL,SACA,QACA,MAC6C;CAC7C,MAAM,OAAO,iBAAiB,MAAM;CACpC,IAAI;EACA,MAAM,KAAK,QAAQ;EACnB,IAAI,OAAO,OAAO,YACd,OAAO;GAAE,IAAI;GAAO,OAAO,oBAAoB,OAAO,GAAG;EAAG;EAEhE,MAAM,UAAW,GAAqB,KAAK,OAAO;EAClD,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,OAAO;GAAE,IAAI;GAAO,OAAO;EAAiB;EAEhD,OAAO,wBAAwB,MAAM,SAAS,IAAI;CACtD,SAAS,GAAG;EACR,OAAO;GAAE,IAAI;GAAO,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;EAAE;CAC1E;AACJ;;AAGA,SAAS,mBACL,KACA,MAC6C;CAC7C,MAAM,OAAQ,IAAI,QAA+C;CAIjE,IAAI;EACA,IAAI,SAAS,QAAQ,SAAS,KAAA,KAAa,OAAO,KAAK,QAAQ,YAC3D,OAAO;GAAE,IAAI;GAAO,OAAO;EAAiC;EAEhE,MAAM,UAAU,KAAK,IAAI;EACzB,IAAI,YAAY,QAAQ,YAAY,KAAA,GAChC,OAAO;GAAE,IAAI;GAAO,OAAO;EAAiB;EAEhD,OAAO,wBAAwB,SAAS,SAAS,IAAI;CACzD,SAAS,GAAG;EACR,OAAO;GAAE,IAAI;GAAO,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC;EAAE;CAC1E;AACJ;;AAGA,SAAS,wBACL,MACA,SACA,MAC6C;CAC7C,MAAM,YAAY,cAAc,SAAS,IAAI;CAC7C,IAAI,cAAc,MACd,OAAO;EAAE,IAAI;EAAO,OAAO;CAAoB;CAEnD,MAAM,MAAO,QAAoC;CACjD,IAAI,OAAO,QAAQ,YACf,OAAO;EAAE,IAAI;EAAO,OAAO,GAAG,UAAU;CAAM;CAElD,MAAM,WAAW,wBAAwB,MAAM,IAAI;CACnD,MAAM,aAAc,IAAwC,KAAK,SAAS,QAAQ;CAIlF,KAAK;EACD,qBAAI,IAAI,KAAK,EAAA,CAAE,YAAY;EAC3B,SAAS;EACT,OAAO;EACP,MAAM,CACF;GACI;GACA,cAAc,iBAAiB,SAAS,IAAI;GAC5C,YAAY,UAAU,UAAU;EACpC,CACJ;CACJ,CAAC;CACD,OAAO;EAAE,IAAI;EAAM,GAAI,OAAO,eAAe,WAAW,EAAE,WAAW,IAAI,CAAC;CAAG;AACjF;;;;;;;;;;;;;;;ACxOA,SAAS,mBACL,YACA,SACwD;CACxD,MAAM,UAAU,KAAK,YAAY,YAAY,SAAS,aAAa,OAAO,cAAc;CACxF,IAAI,CAAC,WAAW,OAAO,GACnB,MAAM,YAAY,2BAA2B,WAAW,WAAW;CAEvE,IAAI;EACA,MAAM,MAAM,KAAK,MAAM,aAAa,SAAS,MAAM,CAAC;EAKpD,OAAO;GACH,MAAM,IAAI,QAAQ;GAClB,SAAS,IAAI,WAAW;GACxB,GAAI,IAAI,iBAAiB,KAAA,IAAY,EAAE,cAAc,IAAI,aAAa,IAAI,CAAC;EAC/E;CACJ,SAAS,OAAO;EACZ,MAAM,YAAY,4BAA4B,WAAW,iBAAiB,EAAE,MAAM,CAAC;CACvF;AACJ;;AAGA,SAAgB,mBAAmB,YAAoB,SAAyB;CAC5E,OAAO,KAAK,YAAY,YAAY,SAAS,aAAa,OAAO,cAAc;AACnF;;;;;AAMA,SAAgB,iBAAiB,YAAoB,SAAgC;CACjF,MAAM,cAAc,mBAAmB,YAAY,OAAO;CAC1D,IAAI,CAAC,WAAW,WAAW,GACvB,MAAM,YAAY,qBAAqB,eAAe,WAAW;CAErE,MAAM,MAAM,mBAAmB,YAAY,OAAO;CAClD,OAAO;EACH,aAAa,IAAI,WAAW;EAC5B,cAAc,IAAI,gBAAgB;EAClC;EACA,OAAO;EACP,KAAK;CACT;AACJ;;AAGA,SAAgB,eAAe,YAA8B;CACzD,MAAM,cAAc,KAAK,YAAY,UAAU;CAC/C,IAAI,CAAC,WAAW,WAAW,GACvB,OAAO,CAAC;CASZ,OAPa,YAAY,WAAW,CAAC,CAAC,QAAQ,SAAS;EACnD,MAAM,OAAO,KAAK,aAAa,IAAI;EACnC,OACI,SAAS,IAAI,CAAC,CAAC,YAAY,KAC3B,WAAW,KAAK,MAAM,aAAa,OAAO,cAAc,CAAC;CAEjE,CACU,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ;AAC/B"}