mioku 1.0.0 → 1.0.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.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { buildSpawnPlan, commandExists, resolveCommand, runCommand, runCommandInherit, version } from "./exec-CUvS9y6X.js";
1
+ import { buildSpawnPlan, commandExists, resolveCommand, runCommand, runCommandInherit, version } from "./exec-DmpZrf3S.js";
2
2
  import { createRequire } from "node:module";
3
3
  import * as fs$5 from "node:fs";
4
4
  import * as fs$4 from "node:fs";
@@ -517,6 +517,12 @@ const segment = {
517
517
  json(data) {
518
518
  return new MessageSegmentImpl("json", { data: typeof data === "string" ? data : JSON.stringify(data) });
519
519
  },
520
+ markdown(content) {
521
+ return new MessageSegmentImpl("markdown", { content });
522
+ },
523
+ button(options) {
524
+ return new MessageSegmentImpl("button", { ...options });
525
+ },
520
526
  raw(type, data, attachment) {
521
527
  return new MessageSegmentImpl(type, data, attachment);
522
528
  }
@@ -862,6 +868,7 @@ var AdapterContextImpl = class {
862
868
  #logger;
863
869
  #emit;
864
870
  #pendingStarts = new Set();
871
+ #crossDedup;
865
872
  constructor(options) {
866
873
  this.#state = options.state;
867
874
  this.#bots = options.bots;
@@ -870,6 +877,7 @@ var AdapterContextImpl = class {
870
877
  this.#capabilities = options.capabilities;
871
878
  this.#logger = options.logger;
872
879
  this.#emit = options.emit;
880
+ this.#crossDedup = options.crossDedup ?? null;
873
881
  }
874
882
  registerBot(bot) {
875
883
  return this.#bots.register(bot);
@@ -933,6 +941,20 @@ var AdapterContextImpl = class {
933
941
  };
934
942
  }
935
943
  async dispatch(event) {
944
+ if (event.kind === "message") {
945
+ const senderId = event.user_id;
946
+ if (senderId != null) {
947
+ const senderBot = this.#bots.pick(senderId);
948
+ if (senderBot && senderBot.bot_id !== event.self_id) {
949
+ this.#logger.debug(`拦截来自其他已连接 bot 的消息: sender=${senderId} self=${event.self_id}`);
950
+ return;
951
+ }
952
+ }
953
+ if (this.#crossDedup?.isDuplicate(event)) {
954
+ this.#logger.debug(`丢弃跨适配器重复消息: adapter=${event.identity?.adapter ?? ""} sender=${event.user_id ?? ""}`);
955
+ return;
956
+ }
957
+ }
936
958
  await this.#bus.dispatch(event);
937
959
  }
938
960
  listen(route, handler) {
@@ -2414,6 +2436,9 @@ const toInstance = (bot, status) => {
2414
2436
  connected_at: bot.connected_at,
2415
2437
  impl: status?.impl,
2416
2438
  version: status?.version,
2439
+ protocol: status?.protocol,
2440
+ platform: status?.platform,
2441
+ platformVersion: status?.platform_version,
2417
2442
  stats: normalizeStats(status?.stats, data),
2418
2443
  data
2419
2444
  };
@@ -2440,6 +2465,7 @@ const buildAdapterReport = async (options) => {
2440
2465
  return {
2441
2466
  name,
2442
2467
  version: known?.version,
2468
+ impl: known?.impl,
2443
2469
  loaded: known != null,
2444
2470
  instances: paired.filter((entry) => entry.adapter === name).map((entry) => entry.instance)
2445
2471
  };
@@ -2464,8 +2490,11 @@ const formatStats = (stats) => {
2464
2490
  return segments.join(" · ");
2465
2491
  };
2466
2492
  const formatInstance = (instance) => {
2467
- const impl = [instance.impl, instance.version].filter(Boolean).join("/");
2468
- const tags = [impl, instance.online ? "" : "🔴 离线"].filter(Boolean);
2493
+ const tags = [
2494
+ [instance.impl, instance.version].filter(Boolean).join("/"),
2495
+ instance.platform ? `${instance.platform}${instance.platformVersion ? ` v${instance.platformVersion}` : ""}` : "",
2496
+ instance.online ? "" : "🔴 离线"
2497
+ ].filter(Boolean);
2469
2498
  const lines = [`👤 ${instance.nickname || "(未命名)"} (${instance.bot_id})${tags.length ? ` · ${tags.join(" · ")}` : ""}`];
2470
2499
  const uptime = instance.online && instance.connected_at != null ? `⏳ 已连接 ${prettyMs(Date.now() - instance.connected_at, {
2471
2500
  hideYear: true,
@@ -2482,9 +2511,16 @@ const summarize = (entry) => {
2482
2511
  if (online === total) return `🟢 ${localNum(total)} 实例`;
2483
2512
  return `${online === 0 ? "🔴" : "🟡"} ${localNum(online)}/${localNum(total)} 实例在线`;
2484
2513
  };
2514
+ /** `名称@包版本[/实现库 v版本]` */
2515
+ const formatAdapterName = (entry) => {
2516
+ const base = `${entry.name}${entry.version ? `@${entry.version}` : ""}`;
2517
+ if (!entry.impl) return base;
2518
+ const impl = `${entry.impl.name}${entry.impl.version ? ` v${entry.impl.version}` : ""}`;
2519
+ return `${base}/${impl}`;
2520
+ };
2485
2521
  const formatAdapterReport = (report) => {
2486
2522
  if (!report.adapters.length) return "〓 🔌 mioku 适配器 〓\n(未加载任何适配器)";
2487
- const lines = report.adapters.flatMap((entry) => [`🔌 ${entry.name}${entry.version ? `/${entry.version}` : ""} · ${summarize(entry)}`, ...entry.instances.flatMap(formatInstance)]);
2523
+ const lines = report.adapters.flatMap((entry) => [`🔌 ${formatAdapterName(entry)} · ${summarize(entry)}`, ...entry.instances.flatMap(formatInstance)]);
2488
2524
  return ["〓 🔌 mioku 适配器 〓", ...lines].join("\n");
2489
2525
  };
2490
2526
 
@@ -4027,19 +4063,21 @@ const core = definePlugin({
4027
4063
  const collectBots = () => ctx.bots;
4028
4064
  /** 已加载的适配器优先,再补上只在 bot 上出现过的适配器名 */
4029
4065
  const collectAdapters = () => {
4030
- const list = ctx.adapters.map((adapter) => ({
4031
- name: adapter.name,
4032
- version: adapter.version
4033
- }));
4066
+ const describe = (name, adapter) => ({
4067
+ name,
4068
+ version: adapter?.version,
4069
+ impl: adapter?.impl ? {
4070
+ name: adapter.impl.name,
4071
+ version: adapter.impl.version
4072
+ } : void 0
4073
+ });
4074
+ const list = ctx.adapters.map((adapter) => describe(adapter.name, adapter));
4034
4075
  const seen = new Set(list.map((adapter) => adapter.name));
4035
4076
  for (const bot of ctx.bots) {
4036
4077
  const name = String(bot.adapter);
4037
4078
  if (seen.has(name)) continue;
4038
4079
  seen.add(name);
4039
- list.push({
4040
- name,
4041
- version: ctx.getAdapter(name)?.version
4042
- });
4080
+ list.push(describe(name, ctx.getAdapter(name)));
4043
4081
  }
4044
4082
  return list;
4045
4083
  };
@@ -4693,6 +4731,96 @@ const loadLocalPlugin = async (jiti, name, resolvedPath) => {
4693
4731
  return await loadFromEntry(jiti, entry, name);
4694
4732
  };
4695
4733
 
4734
+ //#endregion
4735
+ //#region src/runtime/cross-adapter-dedup.ts
4736
+ const BUCKET_SLACK = 1;
4737
+ const PRUNE_INTERVAL = 128;
4738
+ const TTL_MS = 15e3;
4739
+ const MAX_SIZE = 4096;
4740
+ const MAX_SEGMENTS = 16;
4741
+ const MAX_TEXT = 256;
4742
+ /**
4743
+ * 把消息段序列化为跨适配器稳定的内容指纹:
4744
+ * 文本/@/表情取内容,媒体段只取类型(url/文件名跨协议不稳定)
4745
+ */
4746
+ const contentFingerprintOf = (event) => {
4747
+ const parts = [];
4748
+ for (const seg of event.message) {
4749
+ if (parts.length >= MAX_SEGMENTS) break;
4750
+ if (seg.type === "reply") continue;
4751
+ const data = seg.data ?? {};
4752
+ if (seg.type === "text") {
4753
+ const text$1 = typeof data.text === "string" ? data.text : "";
4754
+ parts.push(`t:${text$1.slice(0, MAX_TEXT)}`);
4755
+ } else if (seg.type === "at") {
4756
+ const target = data.qq ?? data.target;
4757
+ parts.push(`a:${target == null ? "" : String(target)}`);
4758
+ } else if (seg.type === "face") parts.push(`f:${data.id == null ? "" : String(data.id)}`);
4759
+ else parts.push(seg.type);
4760
+ }
4761
+ return parts.join("|");
4762
+ };
4763
+ const scopeKeyOf = (event, content) => [
4764
+ event.self_id ?? "",
4765
+ event.message_type ?? "",
4766
+ event.group_id ?? event.user_id ?? "",
4767
+ event.user_id ?? "",
4768
+ content
4769
+ ].join("|");
4770
+ /**
4771
+ * 跨适配器消息去重(L2)
4772
+ */
4773
+ var CrossAdapterMessageDeduplicator = class {
4774
+ #entries = new Map();
4775
+ #inserts = 0;
4776
+ #dropped = 0;
4777
+ /** 本次运行以来被丢弃的跨适配器重复消息数 */
4778
+ get dropped() {
4779
+ return this.#dropped;
4780
+ }
4781
+ isDuplicate(event) {
4782
+ if (event.kind !== "message") return false;
4783
+ const messageEvent = event;
4784
+ const adapter = messageEvent.identity?.adapter ?? "";
4785
+ const now = Date.now();
4786
+ const bucket = Math.floor((messageEvent.time ?? now) / 1e3);
4787
+ const key = scopeKeyOf(messageEvent, contentFingerprintOf(messageEvent));
4788
+ const existing = this.#entries.get(key);
4789
+ if (existing) {
4790
+ if (Math.abs(existing.bucket - bucket) <= BUCKET_SLACK) {
4791
+ if (existing.adapter !== adapter) {
4792
+ this.#dropped++;
4793
+ return true;
4794
+ }
4795
+ existing.bucket = bucket;
4796
+ existing.at = now;
4797
+ return false;
4798
+ }
4799
+ existing.bucket = bucket;
4800
+ existing.at = now;
4801
+ existing.adapter = adapter;
4802
+ return false;
4803
+ }
4804
+ this.#entries.set(key, {
4805
+ adapter,
4806
+ bucket,
4807
+ at: now
4808
+ });
4809
+ this.#inserts++;
4810
+ if (this.#inserts >= PRUNE_INTERVAL || this.#entries.size > MAX_SIZE) {
4811
+ this.#inserts = 0;
4812
+ this.#prune(now);
4813
+ }
4814
+ return false;
4815
+ }
4816
+ #prune(now) {
4817
+ for (const [key, entry] of this.#entries) if (now - entry.at > TTL_MS) this.#entries.delete(key);
4818
+ }
4819
+ clear() {
4820
+ this.#entries.clear();
4821
+ }
4822
+ };
4823
+
4696
4824
  //#endregion
4697
4825
  //#region src/runtime/runtime.ts
4698
4826
  const BUILTIN_PLUGINS$1 = [...BUILTIN_PLUGINS];
@@ -4713,6 +4841,7 @@ var MiokuRuntime = class {
4713
4841
  #enabledPlugins = new Map();
4714
4842
  #driverFactory;
4715
4843
  #builtinPlugins;
4844
+ #crossDedup;
4716
4845
  #started = false;
4717
4846
  #stopped = false;
4718
4847
  constructor(options) {
@@ -4729,6 +4858,7 @@ var MiokuRuntime = class {
4729
4858
  });
4730
4859
  this.#bots = new BotRegistry();
4731
4860
  this.#capabilities = new CapabilityRegistry();
4861
+ this.#crossDedup = options.dedup?.crossAdapter === false ? null : new CrossAdapterMessageDeduplicator();
4732
4862
  }
4733
4863
  get cwd() {
4734
4864
  return this.#cwd;
@@ -4849,7 +4979,8 @@ var MiokuRuntime = class {
4849
4979
  driver: this.#driver,
4850
4980
  capabilities: this.#capabilities,
4851
4981
  logger: this.#logger.child({ adapter: state.definition.name }),
4852
- emit: (event) => this.#emitLifecycle(event)
4982
+ emit: (event) => this.#emitLifecycle(event),
4983
+ crossDedup: this.#crossDedup
4853
4984
  });
4854
4985
  }
4855
4986
  async #loadPlugin(plugin, type) {
@@ -5199,7 +5330,8 @@ const startRuntime = async (options = {}) => {
5199
5330
  const runtime = createRuntime({
5200
5331
  cwd,
5201
5332
  logger,
5202
- builtinPlugins: getBuiltinPlugins()
5333
+ builtinPlugins: getBuiltinPlugins(),
5334
+ dedup: { crossAdapter: botConfig.dedup?.cross_adapter ?? true }
5203
5335
  });
5204
5336
  const pluginDir = path$1.resolve(cwd, botConfig.plugins_dir ?? "plugins");
5205
5337
  logger.info(colors$1.dim("=".repeat(40)));