mioku 1.0.0 → 1.0.1

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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const require_exec = require('../exec-gpip41w9.cjs');
2
+ const require_exec = require('../exec-B2aL-jrd.cjs');
3
3
  const node_fs = require_exec.__toESM(require("node:fs"));
4
4
  const node_path = require_exec.__toESM(require("node:path"));
5
5
  const mri = require_exec.__toESM(require("mri"));
package/dist/cli/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { clearCommandCache, commandExists, resolveCommand, runCommandInherit, version } from "../exec-CUvS9y6X.js";
2
+ import { clearCommandCache, commandExists, resolveCommand, runCommandInherit, version } from "../exec-DGQCm_XV.js";
3
3
  import fs, { readFileSync } from "node:fs";
4
4
  import path from "node:path";
5
5
  import mri from "mri";
@@ -27,7 +27,7 @@ const node_os = __toESM(require("node:os"));
27
27
  const node_child_process = __toESM(require("node:child_process"));
28
28
 
29
29
  //#region package.json
30
- var version = "1.0.0";
30
+ var version = "1.0.1";
31
31
 
32
32
  //#endregion
33
33
  //#region src/internal/exec.ts
@@ -229,4 +229,4 @@ Object.defineProperty(exports, 'version', {
229
229
  return version;
230
230
  }
231
231
  });
232
- //# sourceMappingURL=exec-gpip41w9.cjs.map
232
+ //# sourceMappingURL=exec-B2aL-jrd.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"exec-gpip41w9.cjs","names":["path","candidate: string","command: string","resolved: string | null","resolved: string","arg: string","args: string[]","options: { cwd?: string; env?: NodeJS.ProcessEnv }","options: { cwd?: string }"],"sources":["../package.json","../src/internal/exec.ts"],"sourcesContent":["{\n \"name\": \"mioku\",\n \"type\": \"module\",\n \"version\": \"1.0.0\",\n \"packageManager\": \"bun@1.2.0\",\n \"description\": \"Mioku - A plugin-based QQ bot framework\",\n \"keywords\": [\n \"onebot\",\n \"framework\",\n \"bot\",\n \"mioku\"\n ],\n \"bin\": {\n \"mioku\": \"./dist/cli/index.js\"\n },\n \"engines\": {\n \"node\": \">= 22.18.0\"\n },\n \"homepage\": \"https://github.com/mioku-lab/mioku#readme\",\n \"files\": [\n \"dist\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/mioku-lab/mioku/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/mioku-lab/mioku.git\",\n \"directory\": \"packages/mioku\"\n },\n \"scripts\": {\n \"dev\": \"tsdown -w\",\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"exports\": {\n \".\": {\n \"require\": \"./dist/index.cjs\",\n \"import\": \"./dist/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"author\": \"Jerryplusy <jerryplusy@outlook.com>\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"napcat-sdk\": \"^0.16.0\",\n \"consola\": \"^3.4.2\",\n \"dayjs\": \"^1.11.19\",\n \"dedent\": \"^1.7.1\",\n \"filesize\": \"^11.0.13\",\n \"inquirer\": \"^10.0.0\",\n \"jiti\": \"^2.6.1\",\n \"lowdb\": \"^7.0.1\",\n \"mri\": \"^1.2.0\",\n \"node-cron\": \"^4.2.1\",\n \"pretty-ms\": \"^9.3.0\",\n \"string2argv\": \"^1.0.2\",\n \"lodash\": \"^4.17.21\"\n },\n \"devDependencies\": {\n \"@types/lodash\": \"^4.17.0\",\n \"@types/node\": \"^22.0.0\",\n \"tsdown\": \"^0.11.0\",\n \"typescript\": \"^5.8.0\"\n }\n}\n","import { spawn, spawnSync } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nconst isWindows = process.platform === \"win32\";\n\nconst resolveCache = new Map<string, string | null>();\n\nfunction windowsExtensions(): string[] {\n const raw = process.env.PATHEXT || \".COM;.EXE;.BAT;.CMD\";\n return raw\n .split(\");\")\n .map((ext) => ext.trim())\n .filter(Boolean);\n}\n\n// PATH is a snapshot taken at process start, so a package manager installed\n// mid-run won't be on it. These are the standard install prefixes for bun and\n// for npm's global bin, which is where `npm i -g bun` actually lands.\nfunction searchDirs(): string[] {\n const raw = process.env.PATH || process.env.Path || \"\";\n const dirs = raw.split(path.delimiter).map((d) => d.trim()).filter(Boolean);\n const home = os.homedir();\n\n const extra = isWindows\n ? [\n path.join(home, \".bun\", \"bin\"),\n path.join(process.env.APPDATA || \"\", \"npm\"),\n path.join(process.env.ProgramFiles || \"\", \"nodejs\"),\n ]\n : [\n path.join(home, \".bun\", \"bin\"),\n \"/usr/local/bin\",\n \"/opt/homebrew/bin\",\n path.join(home, \".npm-global\", \"bin\"),\n ];\n\n return [...dirs, ...extra.filter((dir) => dir && !dir.endsWith(path.sep))];\n}\n\nfunction isExecutableFile(candidate: string): boolean {\n try {\n return fs.statSync(candidate).isFile();\n } catch {\n return false;\n }\n}\n\n/** 在 PATH 及常见安装目录中解析命令的完整路径,结果带缓存 */\nexport function resolveCommand(command: string): string | null {\n if (resolveCache.has(command)) return resolveCache.get(command) ?? null;\n\n let resolved: string | null = null;\n\n if (command.includes(\"/\") || command.includes(path.sep)) {\n resolved = isExecutableFile(command) ? path.resolve(command) : null;\n } else {\n const exts = isWindows ? windowsExtensions() : [\"\"];\n const hasKnownExt =\n isWindows && exts.some((ext) => command.toLowerCase().endsWith(ext.toLowerCase()));\n\n outer: for (const dir of searchDirs()) {\n if (hasKnownExt) {\n const direct = path.join(dir, command);\n if (isExecutableFile(direct)) {\n resolved = direct;\n break;\n }\n continue;\n }\n for (const ext of exts) {\n const candidate = path.join(dir, command + ext);\n if (isExecutableFile(candidate)) {\n resolved = candidate;\n break outer;\n }\n }\n }\n }\n\n resolveCache.set(command, resolved);\n return resolved;\n}\n\nexport function clearCommandCache(): void {\n resolveCache.clear();\n}\n\nfunction needsCmdShell(resolved: string): boolean {\n if (!isWindows) return false;\n const ext = path.extname(resolved).toLowerCase();\n return ext === \".cmd\" || ext === \".bat\";\n}\n\n// cmd.exe argument quoting: escape embedded quotes and any run of backslashes\n// that precedes a quote, then wrap the whole thing so metacharacters inside\n// (&, |, <, >, ^) are inert.\nfunction quoteForCmd(arg: string): string {\n if (arg === \"\") return '\"\"';\n const escaped = arg\n .replace(/(\\\\*)\"/g, '$1$1\\\\\"')\n .replace(/(\\\\+)$/, \"$1$1\");\n return `\"${escaped}\"`;\n}\n\nexport interface SpawnPlan {\n file: string;\n args: string[];\n windowsVerbatimArguments?: boolean;\n}\n\n// Windows can't CreateProcess a .cmd/.bat shim directly (npm installs bun as\n// bun.cmd), so those get routed through cmd.exe with hand-quoted arguments\n// rather than shell: true, which would leave the args unquoted.\n/** 生成跨平台的 spawn 参数,Windows 下 .cmd/.bat 会改经 cmd.exe 执行 */\nexport function buildSpawnPlan(command: string, args: string[]): SpawnPlan {\n const resolved = resolveCommand(command);\n\n if (!resolved) {\n return { file: command, args };\n }\n\n if (!needsCmdShell(resolved)) {\n return { file: resolved, args };\n }\n\n const comspec = process.env.ComSpec || process.env.COMSPEC || \"cmd.exe\";\n const line = [resolved, ...args].map(quoteForCmd).join(\" \");\n return {\n file: comspec,\n args: [\"/d\", \"/s\", \"/c\", `\"${line}\"`],\n windowsVerbatimArguments: true,\n };\n}\n\nexport interface RunCommandResult {\n stdout: string;\n stderr: string;\n code: number;\n}\n\n/** 执行命令并收集 stdout/stderr 与退出码,失败不抛异常 */\nexport function runCommand(\n command: string,\n args: string[],\n options: { cwd?: string; env?: NodeJS.ProcessEnv } = {},\n): Promise<RunCommandResult> {\n const plan = buildSpawnPlan(command, args);\n\n return new Promise((resolve) => {\n const child = spawn(plan.file, plan.args, {\n cwd: options.cwd,\n env: options.env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n windowsVerbatimArguments: plan.windowsVerbatimArguments,\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout?.on(\"data\", (chunk) => {\n stdout += String(chunk);\n });\n child.stderr?.on(\"data\", (chunk) => {\n stderr += String(chunk);\n });\n\n child.on(\"close\", (code) => {\n resolve({ stdout, stderr, code: code ?? 1 });\n });\n child.on(\"error\", (error) => {\n resolve({\n stdout,\n stderr: `${stderr}\\n${error.message}`.trim(),\n code: 1,\n });\n });\n });\n}\n\n/** 同步执行命令并继承当前终端的输入输出,非零退出码时抛错 */\nexport function runCommandInherit(\n command: string,\n args: string[],\n options: { cwd?: string } = {},\n): void {\n const plan = buildSpawnPlan(command, args);\n const result = spawnSync(plan.file, plan.args, {\n cwd: options.cwd,\n stdio: \"inherit\",\n windowsVerbatimArguments: plan.windowsVerbatimArguments,\n });\n\n if (result.error) throw result.error;\n if (result.status !== 0) {\n throw new Error(`${command} 退出码 ${result.status}`);\n }\n}\n\n/** 判断命令是否可用 */\nexport function commandExists(command: string): boolean {\n return resolveCommand(command) !== null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"exec-B2aL-jrd.cjs","names":["path","candidate: string","command: string","resolved: string | null","resolved: string","arg: string","args: string[]","options: { cwd?: string; env?: NodeJS.ProcessEnv }","options: { cwd?: string }"],"sources":["../package.json","../src/internal/exec.ts"],"sourcesContent":["{\n \"name\": \"mioku\",\n \"type\": \"module\",\n \"version\": \"1.0.1\",\n \"packageManager\": \"bun@1.2.0\",\n \"description\": \"Mioku - A plugin-based QQ bot framework\",\n \"keywords\": [\n \"onebot\",\n \"framework\",\n \"bot\",\n \"mioku\"\n ],\n \"bin\": {\n \"mioku\": \"./dist/cli/index.js\"\n },\n \"engines\": {\n \"node\": \">= 22.18.0\"\n },\n \"homepage\": \"https://github.com/mioku-lab/mioku#readme\",\n \"files\": [\n \"dist\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/mioku-lab/mioku/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/mioku-lab/mioku.git\",\n \"directory\": \"packages/mioku\"\n },\n \"scripts\": {\n \"dev\": \"tsdown -w\",\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"exports\": {\n \".\": {\n \"require\": \"./dist/index.cjs\",\n \"import\": \"./dist/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"author\": \"Jerryplusy <jerryplusy@outlook.com>\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"napcat-sdk\": \"^0.16.0\",\n \"consola\": \"^3.4.2\",\n \"dayjs\": \"^1.11.19\",\n \"dedent\": \"^1.7.1\",\n \"filesize\": \"^11.0.13\",\n \"inquirer\": \"^10.0.0\",\n \"jiti\": \"^2.6.1\",\n \"lowdb\": \"^7.0.1\",\n \"mri\": \"^1.2.0\",\n \"node-cron\": \"^4.2.1\",\n \"pretty-ms\": \"^9.3.0\",\n \"string2argv\": \"^1.0.2\",\n \"lodash\": \"^4.17.21\"\n },\n \"devDependencies\": {\n \"@types/lodash\": \"^4.17.0\",\n \"@types/node\": \"^22.0.0\",\n \"tsdown\": \"^0.11.0\",\n \"typescript\": \"^5.8.0\"\n }\n}\n","import { spawn, spawnSync } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nconst isWindows = process.platform === \"win32\";\n\nconst resolveCache = new Map<string, string | null>();\n\nfunction windowsExtensions(): string[] {\n const raw = process.env.PATHEXT || \".COM;.EXE;.BAT;.CMD\";\n return raw\n .split(\");\")\n .map((ext) => ext.trim())\n .filter(Boolean);\n}\n\n// PATH is a snapshot taken at process start, so a package manager installed\n// mid-run won't be on it. These are the standard install prefixes for bun and\n// for npm's global bin, which is where `npm i -g bun` actually lands.\nfunction searchDirs(): string[] {\n const raw = process.env.PATH || process.env.Path || \"\";\n const dirs = raw.split(path.delimiter).map((d) => d.trim()).filter(Boolean);\n const home = os.homedir();\n\n const extra = isWindows\n ? [\n path.join(home, \".bun\", \"bin\"),\n path.join(process.env.APPDATA || \"\", \"npm\"),\n path.join(process.env.ProgramFiles || \"\", \"nodejs\"),\n ]\n : [\n path.join(home, \".bun\", \"bin\"),\n \"/usr/local/bin\",\n \"/opt/homebrew/bin\",\n path.join(home, \".npm-global\", \"bin\"),\n ];\n\n return [...dirs, ...extra.filter((dir) => dir && !dir.endsWith(path.sep))];\n}\n\nfunction isExecutableFile(candidate: string): boolean {\n try {\n return fs.statSync(candidate).isFile();\n } catch {\n return false;\n }\n}\n\n/** 在 PATH 及常见安装目录中解析命令的完整路径,结果带缓存 */\nexport function resolveCommand(command: string): string | null {\n if (resolveCache.has(command)) return resolveCache.get(command) ?? null;\n\n let resolved: string | null = null;\n\n if (command.includes(\"/\") || command.includes(path.sep)) {\n resolved = isExecutableFile(command) ? path.resolve(command) : null;\n } else {\n const exts = isWindows ? windowsExtensions() : [\"\"];\n const hasKnownExt =\n isWindows && exts.some((ext) => command.toLowerCase().endsWith(ext.toLowerCase()));\n\n outer: for (const dir of searchDirs()) {\n if (hasKnownExt) {\n const direct = path.join(dir, command);\n if (isExecutableFile(direct)) {\n resolved = direct;\n break;\n }\n continue;\n }\n for (const ext of exts) {\n const candidate = path.join(dir, command + ext);\n if (isExecutableFile(candidate)) {\n resolved = candidate;\n break outer;\n }\n }\n }\n }\n\n resolveCache.set(command, resolved);\n return resolved;\n}\n\nexport function clearCommandCache(): void {\n resolveCache.clear();\n}\n\nfunction needsCmdShell(resolved: string): boolean {\n if (!isWindows) return false;\n const ext = path.extname(resolved).toLowerCase();\n return ext === \".cmd\" || ext === \".bat\";\n}\n\n// cmd.exe argument quoting: escape embedded quotes and any run of backslashes\n// that precedes a quote, then wrap the whole thing so metacharacters inside\n// (&, |, <, >, ^) are inert.\nfunction quoteForCmd(arg: string): string {\n if (arg === \"\") return '\"\"';\n const escaped = arg\n .replace(/(\\\\*)\"/g, '$1$1\\\\\"')\n .replace(/(\\\\+)$/, \"$1$1\");\n return `\"${escaped}\"`;\n}\n\nexport interface SpawnPlan {\n file: string;\n args: string[];\n windowsVerbatimArguments?: boolean;\n}\n\n// Windows can't CreateProcess a .cmd/.bat shim directly (npm installs bun as\n// bun.cmd), so those get routed through cmd.exe with hand-quoted arguments\n// rather than shell: true, which would leave the args unquoted.\n/** 生成跨平台的 spawn 参数,Windows 下 .cmd/.bat 会改经 cmd.exe 执行 */\nexport function buildSpawnPlan(command: string, args: string[]): SpawnPlan {\n const resolved = resolveCommand(command);\n\n if (!resolved) {\n return { file: command, args };\n }\n\n if (!needsCmdShell(resolved)) {\n return { file: resolved, args };\n }\n\n const comspec = process.env.ComSpec || process.env.COMSPEC || \"cmd.exe\";\n const line = [resolved, ...args].map(quoteForCmd).join(\" \");\n return {\n file: comspec,\n args: [\"/d\", \"/s\", \"/c\", `\"${line}\"`],\n windowsVerbatimArguments: true,\n };\n}\n\nexport interface RunCommandResult {\n stdout: string;\n stderr: string;\n code: number;\n}\n\n/** 执行命令并收集 stdout/stderr 与退出码,失败不抛异常 */\nexport function runCommand(\n command: string,\n args: string[],\n options: { cwd?: string; env?: NodeJS.ProcessEnv } = {},\n): Promise<RunCommandResult> {\n const plan = buildSpawnPlan(command, args);\n\n return new Promise((resolve) => {\n const child = spawn(plan.file, plan.args, {\n cwd: options.cwd,\n env: options.env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n windowsVerbatimArguments: plan.windowsVerbatimArguments,\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout?.on(\"data\", (chunk) => {\n stdout += String(chunk);\n });\n child.stderr?.on(\"data\", (chunk) => {\n stderr += String(chunk);\n });\n\n child.on(\"close\", (code) => {\n resolve({ stdout, stderr, code: code ?? 1 });\n });\n child.on(\"error\", (error) => {\n resolve({\n stdout,\n stderr: `${stderr}\\n${error.message}`.trim(),\n code: 1,\n });\n });\n });\n}\n\n/** 同步执行命令并继承当前终端的输入输出,非零退出码时抛错 */\nexport function runCommandInherit(\n command: string,\n args: string[],\n options: { cwd?: string } = {},\n): void {\n const plan = buildSpawnPlan(command, args);\n const result = spawnSync(plan.file, plan.args, {\n cwd: options.cwd,\n stdio: \"inherit\",\n windowsVerbatimArguments: plan.windowsVerbatimArguments,\n });\n\n if (result.error) throw result.error;\n if (result.status !== 0) {\n throw new Error(`${command} 退出码 ${result.status}`);\n }\n}\n\n/** 判断命令是否可用 */\nexport function commandExists(command: string): boolean {\n return resolveCommand(command) !== null;\n}\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ import * as os$1 from "node:os";
4
4
  import { spawn, spawnSync } from "node:child_process";
5
5
 
6
6
  //#region package.json
7
- var version = "1.0.0";
7
+ var version = "1.0.1";
8
8
 
9
9
  //#endregion
10
10
  //#region src/internal/exec.ts
@@ -159,4 +159,4 @@ function commandExists(command) {
159
159
 
160
160
  //#endregion
161
161
  export { buildSpawnPlan, clearCommandCache, commandExists, resolveCommand, runCommand, runCommandInherit, version };
162
- //# sourceMappingURL=exec-CUvS9y6X.js.map
162
+ //# sourceMappingURL=exec-DGQCm_XV.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"exec-CUvS9y6X.js","names":["path","candidate: string","command: string","resolved: string | null","resolved: string","arg: string","args: string[]","options: { cwd?: string; env?: NodeJS.ProcessEnv }","options: { cwd?: string }"],"sources":["../package.json","../src/internal/exec.ts"],"sourcesContent":["{\n \"name\": \"mioku\",\n \"type\": \"module\",\n \"version\": \"1.0.0\",\n \"packageManager\": \"bun@1.2.0\",\n \"description\": \"Mioku - A plugin-based QQ bot framework\",\n \"keywords\": [\n \"onebot\",\n \"framework\",\n \"bot\",\n \"mioku\"\n ],\n \"bin\": {\n \"mioku\": \"./dist/cli/index.js\"\n },\n \"engines\": {\n \"node\": \">= 22.18.0\"\n },\n \"homepage\": \"https://github.com/mioku-lab/mioku#readme\",\n \"files\": [\n \"dist\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/mioku-lab/mioku/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/mioku-lab/mioku.git\",\n \"directory\": \"packages/mioku\"\n },\n \"scripts\": {\n \"dev\": \"tsdown -w\",\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"exports\": {\n \".\": {\n \"require\": \"./dist/index.cjs\",\n \"import\": \"./dist/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"author\": \"Jerryplusy <jerryplusy@outlook.com>\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"napcat-sdk\": \"^0.16.0\",\n \"consola\": \"^3.4.2\",\n \"dayjs\": \"^1.11.19\",\n \"dedent\": \"^1.7.1\",\n \"filesize\": \"^11.0.13\",\n \"inquirer\": \"^10.0.0\",\n \"jiti\": \"^2.6.1\",\n \"lowdb\": \"^7.0.1\",\n \"mri\": \"^1.2.0\",\n \"node-cron\": \"^4.2.1\",\n \"pretty-ms\": \"^9.3.0\",\n \"string2argv\": \"^1.0.2\",\n \"lodash\": \"^4.17.21\"\n },\n \"devDependencies\": {\n \"@types/lodash\": \"^4.17.0\",\n \"@types/node\": \"^22.0.0\",\n \"tsdown\": \"^0.11.0\",\n \"typescript\": \"^5.8.0\"\n }\n}\n","import { spawn, spawnSync } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nconst isWindows = process.platform === \"win32\";\n\nconst resolveCache = new Map<string, string | null>();\n\nfunction windowsExtensions(): string[] {\n const raw = process.env.PATHEXT || \".COM;.EXE;.BAT;.CMD\";\n return raw\n .split(\");\")\n .map((ext) => ext.trim())\n .filter(Boolean);\n}\n\n// PATH is a snapshot taken at process start, so a package manager installed\n// mid-run won't be on it. These are the standard install prefixes for bun and\n// for npm's global bin, which is where `npm i -g bun` actually lands.\nfunction searchDirs(): string[] {\n const raw = process.env.PATH || process.env.Path || \"\";\n const dirs = raw.split(path.delimiter).map((d) => d.trim()).filter(Boolean);\n const home = os.homedir();\n\n const extra = isWindows\n ? [\n path.join(home, \".bun\", \"bin\"),\n path.join(process.env.APPDATA || \"\", \"npm\"),\n path.join(process.env.ProgramFiles || \"\", \"nodejs\"),\n ]\n : [\n path.join(home, \".bun\", \"bin\"),\n \"/usr/local/bin\",\n \"/opt/homebrew/bin\",\n path.join(home, \".npm-global\", \"bin\"),\n ];\n\n return [...dirs, ...extra.filter((dir) => dir && !dir.endsWith(path.sep))];\n}\n\nfunction isExecutableFile(candidate: string): boolean {\n try {\n return fs.statSync(candidate).isFile();\n } catch {\n return false;\n }\n}\n\n/** 在 PATH 及常见安装目录中解析命令的完整路径,结果带缓存 */\nexport function resolveCommand(command: string): string | null {\n if (resolveCache.has(command)) return resolveCache.get(command) ?? null;\n\n let resolved: string | null = null;\n\n if (command.includes(\"/\") || command.includes(path.sep)) {\n resolved = isExecutableFile(command) ? path.resolve(command) : null;\n } else {\n const exts = isWindows ? windowsExtensions() : [\"\"];\n const hasKnownExt =\n isWindows && exts.some((ext) => command.toLowerCase().endsWith(ext.toLowerCase()));\n\n outer: for (const dir of searchDirs()) {\n if (hasKnownExt) {\n const direct = path.join(dir, command);\n if (isExecutableFile(direct)) {\n resolved = direct;\n break;\n }\n continue;\n }\n for (const ext of exts) {\n const candidate = path.join(dir, command + ext);\n if (isExecutableFile(candidate)) {\n resolved = candidate;\n break outer;\n }\n }\n }\n }\n\n resolveCache.set(command, resolved);\n return resolved;\n}\n\nexport function clearCommandCache(): void {\n resolveCache.clear();\n}\n\nfunction needsCmdShell(resolved: string): boolean {\n if (!isWindows) return false;\n const ext = path.extname(resolved).toLowerCase();\n return ext === \".cmd\" || ext === \".bat\";\n}\n\n// cmd.exe argument quoting: escape embedded quotes and any run of backslashes\n// that precedes a quote, then wrap the whole thing so metacharacters inside\n// (&, |, <, >, ^) are inert.\nfunction quoteForCmd(arg: string): string {\n if (arg === \"\") return '\"\"';\n const escaped = arg\n .replace(/(\\\\*)\"/g, '$1$1\\\\\"')\n .replace(/(\\\\+)$/, \"$1$1\");\n return `\"${escaped}\"`;\n}\n\nexport interface SpawnPlan {\n file: string;\n args: string[];\n windowsVerbatimArguments?: boolean;\n}\n\n// Windows can't CreateProcess a .cmd/.bat shim directly (npm installs bun as\n// bun.cmd), so those get routed through cmd.exe with hand-quoted arguments\n// rather than shell: true, which would leave the args unquoted.\n/** 生成跨平台的 spawn 参数,Windows 下 .cmd/.bat 会改经 cmd.exe 执行 */\nexport function buildSpawnPlan(command: string, args: string[]): SpawnPlan {\n const resolved = resolveCommand(command);\n\n if (!resolved) {\n return { file: command, args };\n }\n\n if (!needsCmdShell(resolved)) {\n return { file: resolved, args };\n }\n\n const comspec = process.env.ComSpec || process.env.COMSPEC || \"cmd.exe\";\n const line = [resolved, ...args].map(quoteForCmd).join(\" \");\n return {\n file: comspec,\n args: [\"/d\", \"/s\", \"/c\", `\"${line}\"`],\n windowsVerbatimArguments: true,\n };\n}\n\nexport interface RunCommandResult {\n stdout: string;\n stderr: string;\n code: number;\n}\n\n/** 执行命令并收集 stdout/stderr 与退出码,失败不抛异常 */\nexport function runCommand(\n command: string,\n args: string[],\n options: { cwd?: string; env?: NodeJS.ProcessEnv } = {},\n): Promise<RunCommandResult> {\n const plan = buildSpawnPlan(command, args);\n\n return new Promise((resolve) => {\n const child = spawn(plan.file, plan.args, {\n cwd: options.cwd,\n env: options.env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n windowsVerbatimArguments: plan.windowsVerbatimArguments,\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout?.on(\"data\", (chunk) => {\n stdout += String(chunk);\n });\n child.stderr?.on(\"data\", (chunk) => {\n stderr += String(chunk);\n });\n\n child.on(\"close\", (code) => {\n resolve({ stdout, stderr, code: code ?? 1 });\n });\n child.on(\"error\", (error) => {\n resolve({\n stdout,\n stderr: `${stderr}\\n${error.message}`.trim(),\n code: 1,\n });\n });\n });\n}\n\n/** 同步执行命令并继承当前终端的输入输出,非零退出码时抛错 */\nexport function runCommandInherit(\n command: string,\n args: string[],\n options: { cwd?: string } = {},\n): void {\n const plan = buildSpawnPlan(command, args);\n const result = spawnSync(plan.file, plan.args, {\n cwd: options.cwd,\n stdio: \"inherit\",\n windowsVerbatimArguments: plan.windowsVerbatimArguments,\n });\n\n if (result.error) throw result.error;\n if (result.status !== 0) {\n throw new Error(`${command} 退出码 ${result.status}`);\n }\n}\n\n/** 判断命令是否可用 */\nexport function commandExists(command: string): boolean {\n return resolveCommand(command) !== null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"exec-DGQCm_XV.js","names":["path","candidate: string","command: string","resolved: string | null","resolved: string","arg: string","args: string[]","options: { cwd?: string; env?: NodeJS.ProcessEnv }","options: { cwd?: string }"],"sources":["../package.json","../src/internal/exec.ts"],"sourcesContent":["{\n \"name\": \"mioku\",\n \"type\": \"module\",\n \"version\": \"1.0.1\",\n \"packageManager\": \"bun@1.2.0\",\n \"description\": \"Mioku - A plugin-based QQ bot framework\",\n \"keywords\": [\n \"onebot\",\n \"framework\",\n \"bot\",\n \"mioku\"\n ],\n \"bin\": {\n \"mioku\": \"./dist/cli/index.js\"\n },\n \"engines\": {\n \"node\": \">= 22.18.0\"\n },\n \"homepage\": \"https://github.com/mioku-lab/mioku#readme\",\n \"files\": [\n \"dist\"\n ],\n \"bugs\": {\n \"url\": \"https://github.com/mioku-lab/mioku/issues\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/mioku-lab/mioku.git\",\n \"directory\": \"packages/mioku\"\n },\n \"scripts\": {\n \"dev\": \"tsdown -w\",\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"exports\": {\n \".\": {\n \"require\": \"./dist/index.cjs\",\n \"import\": \"./dist/index.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"author\": \"Jerryplusy <jerryplusy@outlook.com>\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"napcat-sdk\": \"^0.16.0\",\n \"consola\": \"^3.4.2\",\n \"dayjs\": \"^1.11.19\",\n \"dedent\": \"^1.7.1\",\n \"filesize\": \"^11.0.13\",\n \"inquirer\": \"^10.0.0\",\n \"jiti\": \"^2.6.1\",\n \"lowdb\": \"^7.0.1\",\n \"mri\": \"^1.2.0\",\n \"node-cron\": \"^4.2.1\",\n \"pretty-ms\": \"^9.3.0\",\n \"string2argv\": \"^1.0.2\",\n \"lodash\": \"^4.17.21\"\n },\n \"devDependencies\": {\n \"@types/lodash\": \"^4.17.0\",\n \"@types/node\": \"^22.0.0\",\n \"tsdown\": \"^0.11.0\",\n \"typescript\": \"^5.8.0\"\n }\n}\n","import { spawn, spawnSync } from \"node:child_process\";\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\n\nconst isWindows = process.platform === \"win32\";\n\nconst resolveCache = new Map<string, string | null>();\n\nfunction windowsExtensions(): string[] {\n const raw = process.env.PATHEXT || \".COM;.EXE;.BAT;.CMD\";\n return raw\n .split(\");\")\n .map((ext) => ext.trim())\n .filter(Boolean);\n}\n\n// PATH is a snapshot taken at process start, so a package manager installed\n// mid-run won't be on it. These are the standard install prefixes for bun and\n// for npm's global bin, which is where `npm i -g bun` actually lands.\nfunction searchDirs(): string[] {\n const raw = process.env.PATH || process.env.Path || \"\";\n const dirs = raw.split(path.delimiter).map((d) => d.trim()).filter(Boolean);\n const home = os.homedir();\n\n const extra = isWindows\n ? [\n path.join(home, \".bun\", \"bin\"),\n path.join(process.env.APPDATA || \"\", \"npm\"),\n path.join(process.env.ProgramFiles || \"\", \"nodejs\"),\n ]\n : [\n path.join(home, \".bun\", \"bin\"),\n \"/usr/local/bin\",\n \"/opt/homebrew/bin\",\n path.join(home, \".npm-global\", \"bin\"),\n ];\n\n return [...dirs, ...extra.filter((dir) => dir && !dir.endsWith(path.sep))];\n}\n\nfunction isExecutableFile(candidate: string): boolean {\n try {\n return fs.statSync(candidate).isFile();\n } catch {\n return false;\n }\n}\n\n/** 在 PATH 及常见安装目录中解析命令的完整路径,结果带缓存 */\nexport function resolveCommand(command: string): string | null {\n if (resolveCache.has(command)) return resolveCache.get(command) ?? null;\n\n let resolved: string | null = null;\n\n if (command.includes(\"/\") || command.includes(path.sep)) {\n resolved = isExecutableFile(command) ? path.resolve(command) : null;\n } else {\n const exts = isWindows ? windowsExtensions() : [\"\"];\n const hasKnownExt =\n isWindows && exts.some((ext) => command.toLowerCase().endsWith(ext.toLowerCase()));\n\n outer: for (const dir of searchDirs()) {\n if (hasKnownExt) {\n const direct = path.join(dir, command);\n if (isExecutableFile(direct)) {\n resolved = direct;\n break;\n }\n continue;\n }\n for (const ext of exts) {\n const candidate = path.join(dir, command + ext);\n if (isExecutableFile(candidate)) {\n resolved = candidate;\n break outer;\n }\n }\n }\n }\n\n resolveCache.set(command, resolved);\n return resolved;\n}\n\nexport function clearCommandCache(): void {\n resolveCache.clear();\n}\n\nfunction needsCmdShell(resolved: string): boolean {\n if (!isWindows) return false;\n const ext = path.extname(resolved).toLowerCase();\n return ext === \".cmd\" || ext === \".bat\";\n}\n\n// cmd.exe argument quoting: escape embedded quotes and any run of backslashes\n// that precedes a quote, then wrap the whole thing so metacharacters inside\n// (&, |, <, >, ^) are inert.\nfunction quoteForCmd(arg: string): string {\n if (arg === \"\") return '\"\"';\n const escaped = arg\n .replace(/(\\\\*)\"/g, '$1$1\\\\\"')\n .replace(/(\\\\+)$/, \"$1$1\");\n return `\"${escaped}\"`;\n}\n\nexport interface SpawnPlan {\n file: string;\n args: string[];\n windowsVerbatimArguments?: boolean;\n}\n\n// Windows can't CreateProcess a .cmd/.bat shim directly (npm installs bun as\n// bun.cmd), so those get routed through cmd.exe with hand-quoted arguments\n// rather than shell: true, which would leave the args unquoted.\n/** 生成跨平台的 spawn 参数,Windows 下 .cmd/.bat 会改经 cmd.exe 执行 */\nexport function buildSpawnPlan(command: string, args: string[]): SpawnPlan {\n const resolved = resolveCommand(command);\n\n if (!resolved) {\n return { file: command, args };\n }\n\n if (!needsCmdShell(resolved)) {\n return { file: resolved, args };\n }\n\n const comspec = process.env.ComSpec || process.env.COMSPEC || \"cmd.exe\";\n const line = [resolved, ...args].map(quoteForCmd).join(\" \");\n return {\n file: comspec,\n args: [\"/d\", \"/s\", \"/c\", `\"${line}\"`],\n windowsVerbatimArguments: true,\n };\n}\n\nexport interface RunCommandResult {\n stdout: string;\n stderr: string;\n code: number;\n}\n\n/** 执行命令并收集 stdout/stderr 与退出码,失败不抛异常 */\nexport function runCommand(\n command: string,\n args: string[],\n options: { cwd?: string; env?: NodeJS.ProcessEnv } = {},\n): Promise<RunCommandResult> {\n const plan = buildSpawnPlan(command, args);\n\n return new Promise((resolve) => {\n const child = spawn(plan.file, plan.args, {\n cwd: options.cwd,\n env: options.env,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n windowsVerbatimArguments: plan.windowsVerbatimArguments,\n });\n\n let stdout = \"\";\n let stderr = \"\";\n\n child.stdout?.on(\"data\", (chunk) => {\n stdout += String(chunk);\n });\n child.stderr?.on(\"data\", (chunk) => {\n stderr += String(chunk);\n });\n\n child.on(\"close\", (code) => {\n resolve({ stdout, stderr, code: code ?? 1 });\n });\n child.on(\"error\", (error) => {\n resolve({\n stdout,\n stderr: `${stderr}\\n${error.message}`.trim(),\n code: 1,\n });\n });\n });\n}\n\n/** 同步执行命令并继承当前终端的输入输出,非零退出码时抛错 */\nexport function runCommandInherit(\n command: string,\n args: string[],\n options: { cwd?: string } = {},\n): void {\n const plan = buildSpawnPlan(command, args);\n const result = spawnSync(plan.file, plan.args, {\n cwd: options.cwd,\n stdio: \"inherit\",\n windowsVerbatimArguments: plan.windowsVerbatimArguments,\n });\n\n if (result.error) throw result.error;\n if (result.status !== 0) {\n throw new Error(`${command} 退出码 ${result.status}`);\n }\n}\n\n/** 判断命令是否可用 */\nexport function commandExists(command: string): boolean {\n return resolveCommand(command) !== null;\n}\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_exec = require('./exec-gpip41w9.cjs');
1
+ const require_exec = require('./exec-B2aL-jrd.cjs');
2
2
  const node_fs = require_exec.__toESM(require("node:fs"));
3
3
  const node_path = require_exec.__toESM(require("node:path"));
4
4
  const node_url = require_exec.__toESM(require("node:url"));
@@ -502,6 +502,12 @@ const segment = {
502
502
  json(data) {
503
503
  return new MessageSegmentImpl("json", { data: typeof data === "string" ? data : JSON.stringify(data) });
504
504
  },
505
+ markdown(content) {
506
+ return new MessageSegmentImpl("markdown", { content });
507
+ },
508
+ button(options) {
509
+ return new MessageSegmentImpl("button", { ...options });
510
+ },
505
511
  raw(type, data, attachment) {
506
512
  return new MessageSegmentImpl(type, data, attachment);
507
513
  }
@@ -847,6 +853,7 @@ var AdapterContextImpl = class {
847
853
  #logger;
848
854
  #emit;
849
855
  #pendingStarts = new Set();
856
+ #crossDedup;
850
857
  constructor(options) {
851
858
  this.#state = options.state;
852
859
  this.#bots = options.bots;
@@ -855,6 +862,7 @@ var AdapterContextImpl = class {
855
862
  this.#capabilities = options.capabilities;
856
863
  this.#logger = options.logger;
857
864
  this.#emit = options.emit;
865
+ this.#crossDedup = options.crossDedup ?? null;
858
866
  }
859
867
  registerBot(bot) {
860
868
  return this.#bots.register(bot);
@@ -918,6 +926,20 @@ var AdapterContextImpl = class {
918
926
  };
919
927
  }
920
928
  async dispatch(event) {
929
+ if (event.kind === "message") {
930
+ const senderId = event.user_id;
931
+ if (senderId != null) {
932
+ const senderBot = this.#bots.pick(senderId);
933
+ if (senderBot && senderBot.bot_id !== event.self_id) {
934
+ this.#logger.debug(`拦截来自其他已连接 bot 的消息: sender=${senderId} self=${event.self_id}`);
935
+ return;
936
+ }
937
+ }
938
+ if (this.#crossDedup?.isDuplicate(event)) {
939
+ this.#logger.debug(`丢弃跨适配器重复消息: adapter=${event.identity?.adapter ?? ""} sender=${event.user_id ?? ""}`);
940
+ return;
941
+ }
942
+ }
921
943
  await this.#bus.dispatch(event);
922
944
  }
923
945
  listen(route, handler) {
@@ -2399,6 +2421,9 @@ const toInstance = (bot, status) => {
2399
2421
  connected_at: bot.connected_at,
2400
2422
  impl: status?.impl,
2401
2423
  version: status?.version,
2424
+ protocol: status?.protocol,
2425
+ platform: status?.platform,
2426
+ platformVersion: status?.platform_version,
2402
2427
  stats: normalizeStats(status?.stats, data),
2403
2428
  data
2404
2429
  };
@@ -2425,6 +2450,7 @@ const buildAdapterReport = async (options) => {
2425
2450
  return {
2426
2451
  name,
2427
2452
  version: known?.version,
2453
+ impl: known?.impl,
2428
2454
  loaded: known != null,
2429
2455
  instances: paired.filter((entry) => entry.adapter === name).map((entry) => entry.instance)
2430
2456
  };
@@ -2449,8 +2475,11 @@ const formatStats = (stats) => {
2449
2475
  return segments.join(" · ");
2450
2476
  };
2451
2477
  const formatInstance = (instance) => {
2452
- const impl = [instance.impl, instance.version].filter(Boolean).join("/");
2453
- const tags = [impl, instance.online ? "" : "🔴 离线"].filter(Boolean);
2478
+ const tags = [
2479
+ [instance.impl, instance.version].filter(Boolean).join("/"),
2480
+ instance.platform ? `${instance.platform}${instance.platformVersion ? ` v${instance.platformVersion}` : ""}` : "",
2481
+ instance.online ? "" : "🔴 离线"
2482
+ ].filter(Boolean);
2454
2483
  const lines = [`👤 ${instance.nickname || "(未命名)"} (${instance.bot_id})${tags.length ? ` · ${tags.join(" · ")}` : ""}`];
2455
2484
  const uptime = instance.online && instance.connected_at != null ? `⏳ 已连接 ${(0, pretty_ms.default)(Date.now() - instance.connected_at, {
2456
2485
  hideYear: true,
@@ -2467,9 +2496,16 @@ const summarize = (entry) => {
2467
2496
  if (online === total) return `🟢 ${localNum(total)} 实例`;
2468
2497
  return `${online === 0 ? "🔴" : "🟡"} ${localNum(online)}/${localNum(total)} 实例在线`;
2469
2498
  };
2499
+ /** `名称@包版本[/实现库 v版本]` */
2500
+ const formatAdapterName = (entry) => {
2501
+ const base = `${entry.name}${entry.version ? `@${entry.version}` : ""}`;
2502
+ if (!entry.impl) return base;
2503
+ const impl = `${entry.impl.name}${entry.impl.version ? ` v${entry.impl.version}` : ""}`;
2504
+ return `${base}/${impl}`;
2505
+ };
2470
2506
  const formatAdapterReport = (report) => {
2471
2507
  if (!report.adapters.length) return "〓 🔌 mioku 适配器 〓\n(未加载任何适配器)";
2472
- const lines = report.adapters.flatMap((entry) => [`🔌 ${entry.name}${entry.version ? `/${entry.version}` : ""} · ${summarize(entry)}`, ...entry.instances.flatMap(formatInstance)]);
2508
+ const lines = report.adapters.flatMap((entry) => [`🔌 ${formatAdapterName(entry)} · ${summarize(entry)}`, ...entry.instances.flatMap(formatInstance)]);
2473
2509
  return ["〓 🔌 mioku 适配器 〓", ...lines].join("\n");
2474
2510
  };
2475
2511
 
@@ -4012,19 +4048,21 @@ const core = definePlugin({
4012
4048
  const collectBots = () => ctx.bots;
4013
4049
  /** 已加载的适配器优先,再补上只在 bot 上出现过的适配器名 */
4014
4050
  const collectAdapters = () => {
4015
- const list = ctx.adapters.map((adapter) => ({
4016
- name: adapter.name,
4017
- version: adapter.version
4018
- }));
4051
+ const describe = (name, adapter) => ({
4052
+ name,
4053
+ version: adapter?.version,
4054
+ impl: adapter?.impl ? {
4055
+ name: adapter.impl.name,
4056
+ version: adapter.impl.version
4057
+ } : void 0
4058
+ });
4059
+ const list = ctx.adapters.map((adapter) => describe(adapter.name, adapter));
4019
4060
  const seen = new Set(list.map((adapter) => adapter.name));
4020
4061
  for (const bot of ctx.bots) {
4021
4062
  const name = String(bot.adapter);
4022
4063
  if (seen.has(name)) continue;
4023
4064
  seen.add(name);
4024
- list.push({
4025
- name,
4026
- version: ctx.getAdapter(name)?.version
4027
- });
4065
+ list.push(describe(name, ctx.getAdapter(name)));
4028
4066
  }
4029
4067
  return list;
4030
4068
  };
@@ -4678,6 +4716,96 @@ const loadLocalPlugin = async (jiti$1, name, resolvedPath) => {
4678
4716
  return await loadFromEntry(jiti$1, entry, name);
4679
4717
  };
4680
4718
 
4719
+ //#endregion
4720
+ //#region src/runtime/cross-adapter-dedup.ts
4721
+ const BUCKET_SLACK = 1;
4722
+ const PRUNE_INTERVAL = 128;
4723
+ const TTL_MS = 15e3;
4724
+ const MAX_SIZE = 4096;
4725
+ const MAX_SEGMENTS = 16;
4726
+ const MAX_TEXT = 256;
4727
+ /**
4728
+ * 把消息段序列化为跨适配器稳定的内容指纹:
4729
+ * 文本/@/表情取内容,媒体段只取类型(url/文件名跨协议不稳定)
4730
+ */
4731
+ const contentFingerprintOf = (event) => {
4732
+ const parts = [];
4733
+ for (const seg of event.message) {
4734
+ if (parts.length >= MAX_SEGMENTS) break;
4735
+ if (seg.type === "reply") continue;
4736
+ const data = seg.data ?? {};
4737
+ if (seg.type === "text") {
4738
+ const text$1 = typeof data.text === "string" ? data.text : "";
4739
+ parts.push(`t:${text$1.slice(0, MAX_TEXT)}`);
4740
+ } else if (seg.type === "at") {
4741
+ const target = data.qq ?? data.target;
4742
+ parts.push(`a:${target == null ? "" : String(target)}`);
4743
+ } else if (seg.type === "face") parts.push(`f:${data.id == null ? "" : String(data.id)}`);
4744
+ else parts.push(seg.type);
4745
+ }
4746
+ return parts.join("|");
4747
+ };
4748
+ const scopeKeyOf = (event, content) => [
4749
+ event.self_id ?? "",
4750
+ event.message_type ?? "",
4751
+ event.group_id ?? event.user_id ?? "",
4752
+ event.user_id ?? "",
4753
+ content
4754
+ ].join("|");
4755
+ /**
4756
+ * 跨适配器消息去重(L2)
4757
+ */
4758
+ var CrossAdapterMessageDeduplicator = class {
4759
+ #entries = new Map();
4760
+ #inserts = 0;
4761
+ #dropped = 0;
4762
+ /** 本次运行以来被丢弃的跨适配器重复消息数 */
4763
+ get dropped() {
4764
+ return this.#dropped;
4765
+ }
4766
+ isDuplicate(event) {
4767
+ if (event.kind !== "message") return false;
4768
+ const messageEvent = event;
4769
+ const adapter = messageEvent.identity?.adapter ?? "";
4770
+ const now = Date.now();
4771
+ const bucket = Math.floor((messageEvent.time ?? now) / 1e3);
4772
+ const key = scopeKeyOf(messageEvent, contentFingerprintOf(messageEvent));
4773
+ const existing = this.#entries.get(key);
4774
+ if (existing) {
4775
+ if (Math.abs(existing.bucket - bucket) <= BUCKET_SLACK) {
4776
+ if (existing.adapter !== adapter) {
4777
+ this.#dropped++;
4778
+ return true;
4779
+ }
4780
+ existing.bucket = bucket;
4781
+ existing.at = now;
4782
+ return false;
4783
+ }
4784
+ existing.bucket = bucket;
4785
+ existing.at = now;
4786
+ existing.adapter = adapter;
4787
+ return false;
4788
+ }
4789
+ this.#entries.set(key, {
4790
+ adapter,
4791
+ bucket,
4792
+ at: now
4793
+ });
4794
+ this.#inserts++;
4795
+ if (this.#inserts >= PRUNE_INTERVAL || this.#entries.size > MAX_SIZE) {
4796
+ this.#inserts = 0;
4797
+ this.#prune(now);
4798
+ }
4799
+ return false;
4800
+ }
4801
+ #prune(now) {
4802
+ for (const [key, entry] of this.#entries) if (now - entry.at > TTL_MS) this.#entries.delete(key);
4803
+ }
4804
+ clear() {
4805
+ this.#entries.clear();
4806
+ }
4807
+ };
4808
+
4681
4809
  //#endregion
4682
4810
  //#region src/runtime/runtime.ts
4683
4811
  const BUILTIN_PLUGINS$1 = [...BUILTIN_PLUGINS];
@@ -4698,6 +4826,7 @@ var MiokuRuntime = class {
4698
4826
  #enabledPlugins = new Map();
4699
4827
  #driverFactory;
4700
4828
  #builtinPlugins;
4829
+ #crossDedup;
4701
4830
  #started = false;
4702
4831
  #stopped = false;
4703
4832
  constructor(options) {
@@ -4714,6 +4843,7 @@ var MiokuRuntime = class {
4714
4843
  });
4715
4844
  this.#bots = new BotRegistry();
4716
4845
  this.#capabilities = new CapabilityRegistry();
4846
+ this.#crossDedup = options.dedup?.crossAdapter === false ? null : new CrossAdapterMessageDeduplicator();
4717
4847
  }
4718
4848
  get cwd() {
4719
4849
  return this.#cwd;
@@ -4834,7 +4964,8 @@ var MiokuRuntime = class {
4834
4964
  driver: this.#driver,
4835
4965
  capabilities: this.#capabilities,
4836
4966
  logger: this.#logger.child({ adapter: state.definition.name }),
4837
- emit: (event) => this.#emitLifecycle(event)
4967
+ emit: (event) => this.#emitLifecycle(event),
4968
+ crossDedup: this.#crossDedup
4838
4969
  });
4839
4970
  }
4840
4971
  async #loadPlugin(plugin, type) {
@@ -5184,7 +5315,8 @@ const startRuntime = async (options = {}) => {
5184
5315
  const runtime = createRuntime({
5185
5316
  cwd,
5186
5317
  logger,
5187
- builtinPlugins: getBuiltinPlugins()
5318
+ builtinPlugins: getBuiltinPlugins(),
5319
+ dedup: { crossAdapter: botConfig.dedup?.cross_adapter ?? true }
5188
5320
  });
5189
5321
  const pluginDir = node_path.default.resolve(cwd, botConfig.plugins_dir ?? "plugins");
5190
5322
  logger.info(consola_utils.colors.dim("=".repeat(40)));