koishi-plugin-chatluna-toolbox 0.0.4 → 0.0.6

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.
Files changed (2) hide show
  1. package/lib/index.js +309 -288
  2. package/package.json +1 -6
package/lib/index.js CHANGED
@@ -31,6 +31,7 @@ __export(index_exports, {
31
31
  apply: () => apply,
32
32
  callOneBotAPI: () => callOneBotAPI,
33
33
  createBotInfoProvider: () => createBotInfoProvider,
34
+ createCharacterTempXmlRuntime: () => createCharacterTempXmlRuntime,
34
35
  createDeleteMessageTool: () => createDeleteMessageTool,
35
36
  createGroupInfoProvider: () => createGroupInfoProvider,
36
37
  createLogger: () => createLogger,
@@ -40,12 +41,12 @@ __export(index_exports, {
40
41
  createSetMsgEmojiTool: () => createSetMsgEmojiTool,
41
42
  createSetProfileTool: () => createSetProfileTool,
42
43
  createUserInfoProvider: () => createUserInfoProvider,
43
- createXmlInterceptor: () => createXmlInterceptor,
44
+ createXmlProcessor: () => createXmlProcessor,
44
45
  ensureOneBotSession: () => ensureOneBotSession,
45
46
  getSession: () => getSession,
46
47
  inject: () => inject,
47
48
  name: () => name,
48
- parseXmlActions: () => parseXmlActions,
49
+ parseSelfClosingXmlTags: () => parseSelfClosingXmlTags,
49
50
  registerNativeTools: () => registerNativeTools,
50
51
  registerVariables: () => registerVariables,
51
52
  registerXmlTools: () => registerXmlTools,
@@ -64,22 +65,22 @@ var import_koishi = require("koishi");
64
65
  var NativeToolsSchema = import_koishi.Schema.object({
65
66
  enableNapCatProtocol: import_koishi.Schema.boolean().default(true).description("\u542F\u7528 NapCat OneBot \u534F\u8BAE\uFF08\u4E0E LLBot \u4E8C\u9009\u4E00\uFF09"),
66
67
  enableLlbotProtocol: import_koishi.Schema.boolean().default(false).description("\u542F\u7528 LLBot OneBot \u534F\u8BAE\uFF08\u4E0E NapCat \u4E8C\u9009\u4E00\uFF09"),
67
- enablePokeTool: import_koishi.Schema.boolean().default(false).description("\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u6233\u4E00\u6233\uFF08\u5EFA\u8BAE\u4E0E XML\u5DE5\u5177 \u4E8C\u9009\u4E00\uFF09"),
68
+ enablePokeTool: import_koishi.Schema.boolean().default(false).description("\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u6233\u4E00\u6233\uFF08\u4E0E XML\u5DE5\u5177 \u4E8C\u9009\u4E00\uFF09"),
68
69
  pokeToolName: import_koishi.Schema.string().default("poke_user").description("ChatLuna \u5DE5\u5177\u540D\u79F0\uFF1A\u6233\u4E00\u6233"),
69
70
  enableSetSelfProfileTool: import_koishi.Schema.boolean().default(false).description("\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u4FEE\u6539\u81EA\u8EAB\u8D26\u6237\u4FE1\u606F"),
70
71
  setSelfProfileToolName: import_koishi.Schema.string().default("set_self_profile").description("ChatLuna \u5DE5\u5177\u540D\u79F0\uFF1A\u4FEE\u6539\u81EA\u8EAB\u8D26\u6237\u4FE1\u606F\uFF08\u652F\u6301\u6635\u79F0/\u7B7E\u540D/\u6027\u522B\uFF09"),
71
72
  enableSetGroupCardTool: import_koishi.Schema.boolean().default(false).description("\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u4FEE\u6539\u7FA4\u6210\u5458\u6635\u79F0"),
72
73
  setGroupCardToolName: import_koishi.Schema.string().default("set_group_card").description("ChatLuna \u5DE5\u5177\u540D\u79F0\uFF1A\u4FEE\u6539\u7FA4\u6210\u5458\u6635\u79F0"),
73
- enableSetMsgEmojiTool: import_koishi.Schema.boolean().default(false).description("\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u7ED9\u6D88\u606F\u6DFB\u52A0\u8868\u60C5\uFF08\u9700 chatluna-character \u5F00\u542F enableMessageId\uFF0C\u5EFA\u8BAE\u4E0E XML\u5DE5\u5177 \u4E8C\u9009\u4E00\uFF09"),
74
+ enableSetMsgEmojiTool: import_koishi.Schema.boolean().default(false).description("\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u7ED9\u6D88\u606F\u6DFB\u52A0\u8868\u60C5\uFF08\u9700 chatluna-character \u5F00\u542F enableMessageId\uFF0C\u4E0E XML\u5DE5\u5177 \u4E8C\u9009\u4E00\uFF0C\u8868\u60C5\u5BF9\u7167\u8868\uFF1Ahttps://bot.q.qq.com/wiki/develop/pythonsdk/model/emoji.html \uFF09"),
74
75
  setMsgEmojiToolName: import_koishi.Schema.string().default("set_msg_emoji").description("ChatLuna \u5DE5\u5177\u540D\u79F0\uFF1A\u7ED9\u6D88\u606F\u6DFB\u52A0\u8868\u60C5"),
75
- enableDeleteMessageTool: import_koishi.Schema.boolean().default(false).description("\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u64A4\u56DE\u6D88\u606F\uFF08\u9700 chatluna-character \u5F00\u542F enableMessageId\uFF0C\u5EFA\u8BAE\u4E0E XML\u5DE5\u5177 \u4E8C\u9009\u4E00\uFF09"),
76
+ enableDeleteMessageTool: import_koishi.Schema.boolean().default(false).description("\u6CE8\u518C ChatLuna \u5DE5\u5177\uFF1A\u64A4\u56DE\u6D88\u606F\uFF08\u9700 chatluna-character \u5F00\u542F enableMessageId\uFF0C\u4E0E XML\u5DE5\u5177 \u4E8C\u9009\u4E00\uFF09"),
76
77
  deleteMessageToolName: import_koishi.Schema.string().default("delete_msg").description("ChatLuna \u5DE5\u5177\u540D\u79F0\uFF1A\u64A4\u56DE\u6D88\u606F")
77
78
  }).description("\u539F\u751F\u5DE5\u5177");
78
79
 
79
80
  // src/schema/xml-tools.ts
80
81
  var import_koishi2 = require("koishi");
81
82
  var DEFAULT_XML_REFERENCE_PROMPT = `## \u52A8\u4F5C\u6307\u4EE4
82
- \u4F60\u53EF\u4EE5\u6839\u636E\u9700\u8981\u521B\u5EFA\u4E00\u4E2A\u72EC\u7ACB\u7684 <actions> \u5143\u7D20\u3002\u5B83\u7528\u4E8E\u6267\u884C\u975E\u8BED\u8A00\u7684\u7CFB\u7EDF\u6307\u4EE4\u3002\u5982\u679C\u4E0D\u9700\u8981\u6267\u884C\u4EFB\u4F55\u52A8\u4F5C\uFF0C\u8BF7\u7701\u7565\u6B64\u5143\u7D20\u3002
83
+ \u4F60\u53EF\u4EE5\u6839\u636E\u9700\u8981\u5728\u6A21\u578B\u56DE\u590D\u4E2D\u8F93\u51FA\u4E00\u4E2A\u72EC\u7ACB\u7684 <actions> \u5143\u7D20\u3002\u5B83\u7528\u4E8E\u6267\u884C\u975E\u8BED\u8A00\u7684\u7CFB\u7EDF\u6307\u4EE4\u3002\u5982\u679C\u4E0D\u9700\u8981\u6267\u884C\u4EFB\u4F55\u52A8\u4F5C\uFF0C\u8BF7\u7701\u7565\u6B64\u5143\u7D20\u3002
83
84
  1. \u6233\u4E00\u6233: <poke id=""/>
84
85
  - id: user_id
85
86
  - \u9002\u7528\u573A\u666F:
@@ -111,10 +112,12 @@ var DEFAULT_XML_REFERENCE_PROMPT = `## \u52A8\u4F5C\u6307\u4EE4
111
112
  </actions>
112
113
  \`\`\``;
113
114
  var XmlToolsSchema = import_koishi2.Schema.object({
114
- enablePokeXmlTool: import_koishi2.Schema.boolean().default(false).description("\u542F\u7528 XML \u5F62\u5F0F\u7684\u6233\u4E00\u6233\u8C03\u7528"),
115
- enableEmojiXmlTool: import_koishi2.Schema.boolean().default(false).description("\u542F\u7528 XML \u5F62\u5F0F\u7684\u6D88\u606F\u8868\u60C5\u8C03\u7528"),
116
- enableDeleteXmlTool: import_koishi2.Schema.boolean().default(false).description("\u542F\u7528 XML \u5F62\u5F0F\u7684\u6D88\u606F\u64A4\u56DE\u8C03\u7528"),
117
- referencePrompt: import_koishi2.Schema.string().role("textarea").default(DEFAULT_XML_REFERENCE_PROMPT).description("\u53C2\u8003\u63D0\u793A\u8BCD")
115
+ enablePokeXmlTool: import_koishi2.Schema.boolean().default(false).description("\u542F\u7528 XML \u5F62\u5F0F\u7684\u6233\u4E00\u6233\u8C03\u7528\uFF08\u4E0E \u539F\u751F\u5DE5\u5177 \u4E8C\u9009\u4E00\uFF09"),
116
+ enableEmojiXmlTool: import_koishi2.Schema.boolean().default(false).description(
117
+ "\u542F\u7528 XML \u5F62\u5F0F\u7684\u6D88\u606F\u8868\u60C5\u8C03\u7528\uFF0C\uFF08\u9700 chatluna-character \u5F00\u542F enableMessageId\uFF0C\u4E0E \u539F\u751F\u5DE5\u5177 \u4E8C\u9009\u4E00\uFF0Cemoji_id \u5BF9\u7167\u8868\uFF1Ahttps://bot.q.qq.com/wiki/develop/pythonsdk/model/emoji.html \uFF09"
118
+ ),
119
+ enableDeleteXmlTool: import_koishi2.Schema.boolean().default(false).description("\u542F\u7528 XML \u5F62\u5F0F\u7684\u6D88\u606F\u64A4\u56DE\u8C03\u7528\uFF08\u9700 chatluna-character \u5F00\u542F enableMessageId\uFF0C\u4E0E \u539F\u751F\u5DE5\u5177 \u4E8C\u9009\u4E00\uFF09"),
120
+ referencePrompt: import_koishi2.Schema.string().role("textarea").default(DEFAULT_XML_REFERENCE_PROMPT).description("\u6A21\u578B\u56DE\u590D XML \u53C2\u8003\u63D0\u793A\u8BCD\uFF0C\u81EA\u884C\u5199\u5165\u63D0\u793A\u8BCD\u4E2D\uFF0C\u4E0D\u4F1A\u81EA\u52A8\u6CE8\u5165")
118
121
  }).description("XML \u5DE5\u5177");
119
122
 
120
123
  // src/schema/variables.ts
@@ -600,311 +603,315 @@ function registerNativeTools(deps) {
600
603
  }
601
604
  }
602
605
 
603
- // src/features/xml-tools/interceptor.ts
604
- function createXmlInterceptor(deps) {
605
- const { ctx, config, log, onResponse } = deps;
606
- const sessionMap = /* @__PURE__ */ new Map();
607
- let currentGuildId = null;
608
- let lastSession = null;
609
- const pendingSessions = [];
610
- let responseSeq = 0;
611
- const processedResponses = /* @__PURE__ */ new Set();
612
- let monitorHandle = null;
613
- let fastRetryHandle = null;
614
- let startupHandle = null;
615
- const responseRetryHandles = /* @__PURE__ */ new Map();
616
- let activeService = null;
617
- let activeLogger = null;
618
- let originalDebug = null;
619
- let collectorBound = false;
620
- const restore = () => {
621
- if (activeLogger && originalDebug) {
622
- activeLogger.debug = originalDebug;
623
- }
624
- activeLogger = null;
625
- originalDebug = null;
626
- };
627
- const isActive = () => {
628
- const characterService = ctx.chatluna_character;
629
- const debugFn = characterService?.logger?.debug;
630
- return Boolean(debugFn?.[RAW_INTERCEPTOR_TAG]);
631
- };
632
- const resolveSession = () => {
633
- while (pendingSessions.length > 100) pendingSessions.shift();
634
- const now = Date.now();
635
- while (pendingSessions.length > 0 && now - pendingSessions[0].timestamp > 12e3) {
636
- pendingSessions.shift();
637
- }
638
- if (pendingSessions.length === 0) {
639
- return (currentGuildId ? sessionMap.get(currentGuildId) : null) || lastSession || null;
640
- }
641
- const contextKeys = /* @__PURE__ */ new Set();
642
- for (let i = 0; i < pendingSessions.length; i++) {
643
- contextKeys.add(pendingSessions[i].key || "__null__");
606
+ // src/features/xml-tools/parser.ts
607
+ function parseSelfClosingXmlTags(text, tagName) {
608
+ const tags = Array.from(
609
+ text.matchAll(new RegExp(`<${tagName}\\b([^>]*)\\/>`, "gi"))
610
+ );
611
+ if (!tags.length) return [];
612
+ return tags.map((tag) => {
613
+ const attrText = String(tag[1] || "");
614
+ const attrs = {};
615
+ for (const pair of attrText.matchAll(/([a-zA-Z_][\w-]*)="([^"]*)"/g)) {
616
+ attrs[pair[1]] = pair[2];
644
617
  }
645
- let resolved = pendingSessions[pendingSessions.length - 1];
646
- if (currentGuildId) {
647
- for (let i = pendingSessions.length - 1; i >= 0; i--) {
648
- if (pendingSessions[i].key === currentGuildId) {
649
- resolved = pendingSessions[i];
650
- break;
618
+ return attrs;
619
+ });
620
+ }
621
+
622
+ // src/features/xml-tools/processor.ts
623
+ function createXmlProcessor(deps) {
624
+ const {
625
+ config,
626
+ protocol,
627
+ log,
628
+ sendPoke: runPoke = sendPoke,
629
+ sendMsgEmoji: runEmoji = sendMsgEmoji,
630
+ sendDeleteMessage: runDelete = sendDeleteMessage
631
+ } = deps;
632
+ return async ({ response, session }) => {
633
+ const content = String(response || "").trim();
634
+ if (!content) return false;
635
+ const pokeTags = parseSelfClosingXmlTags(content, "poke");
636
+ const emojiTags = parseSelfClosingXmlTags(content, "emoji");
637
+ const deleteTags = parseSelfClosingXmlTags(content, "delete");
638
+ let handled = false;
639
+ if (config.enablePokeXmlTool && pokeTags.length > 0) {
640
+ if (!session) {
641
+ log?.("warn", "\u68C0\u6D4B\u5230\u6233\u4E00\u6233\u6807\u8BB0\u4F46\u7F3A\u5C11\u4F1A\u8BDD\u4E0A\u4E0B\u6587");
642
+ } else {
643
+ const userIds = pokeTags.map((attrs) => String(attrs.id || "").trim()).filter(Boolean);
644
+ if (userIds.length > 0) handled = true;
645
+ for (const userId of userIds) {
646
+ try {
647
+ await runPoke({ session, userId, protocol, log });
648
+ } catch (error) {
649
+ log?.("warn", "XML \u89E6\u53D1 poke \u5931\u8D25", error);
650
+ }
651
651
  }
652
652
  }
653
653
  }
654
- pendingSessions.length = 0;
655
- if (contextKeys.size > 1 && config.debugLogging) {
656
- log?.("warn", "\u68C0\u6D4B\u5230\u591A\u4E2A\u4F1A\u8BDD\u4E0A\u4E0B\u6587\uFF0C\u5DF2\u4F7F\u7528\u6700\u8FD1\u4F1A\u8BDD\u7EE7\u7EED\u6267\u884C XML", {
657
- contextCount: contextKeys.size,
658
- preferredGuildId: currentGuildId,
659
- resolvedGuildId: resolved.key
660
- });
661
- }
662
- return resolved.key ? sessionMap.get(resolved.key) || resolved.session : resolved.session;
663
- };
664
- const markProcessed = (responseId) => {
665
- processedResponses.add(responseId);
666
- if (processedResponses.size > 100) {
667
- const first = processedResponses.values().next().value;
668
- if (first) processedResponses.delete(first);
669
- }
670
- };
671
- const runResponse = (responseId, response, session) => {
672
- const handled = onResponse(response, session);
673
- if (handled) {
674
- markProcessed(responseId);
675
- const retryHandle = responseRetryHandles.get(responseId);
676
- if (retryHandle) {
677
- retryHandle();
678
- responseRetryHandles.delete(responseId);
654
+ if (config.enableEmojiXmlTool && emojiTags.length > 0) {
655
+ if (!session) {
656
+ log?.("warn", "\u68C0\u6D4B\u5230\u8868\u60C5\u6807\u8BB0\u4F46\u7F3A\u5C11\u4F1A\u8BDD\u4E0A\u4E0B\u6587");
657
+ } else {
658
+ const items = emojiTags.map((attrs) => ({
659
+ messageId: String(attrs.message_id || "").trim(),
660
+ emojiId: String(attrs.emoji_id || "").trim()
661
+ })).filter((item) => item.messageId && item.emojiId);
662
+ if (items.length > 0) handled = true;
663
+ for (const item of items) {
664
+ try {
665
+ await runEmoji({
666
+ session,
667
+ messageId: item.messageId,
668
+ emojiId: item.emojiId,
669
+ protocol,
670
+ log
671
+ });
672
+ } catch (error) {
673
+ log?.("warn", "XML \u89E6\u53D1\u8868\u60C5\u5931\u8D25", error);
674
+ }
675
+ }
679
676
  }
680
677
  }
681
- return handled;
682
- };
683
- const retryResponse = (responseId, response) => {
684
- if (responseRetryHandles.has(responseId) || processedResponses.has(responseId))
685
- return;
686
- const handle = ctx.setTimeout(() => {
687
- responseRetryHandles.delete(responseId);
688
- if (processedResponses.has(responseId)) return;
689
- const session = resolveSession();
678
+ if (config.enableDeleteXmlTool && deleteTags.length > 0) {
690
679
  if (!session) {
691
- if (config.debugLogging) {
692
- log?.("warn", "\u62E6\u622A\u5230\u539F\u59CB\u8F93\u51FA\u4F46\u7F3A\u5C11\u4F1A\u8BDD\u4E0A\u4E0B\u6587\uFF0CXML \u5DE5\u5177\u4E0D\u4F1A\u6267\u884C", {
693
- length: response.length
694
- });
680
+ log?.("warn", "\u68C0\u6D4B\u5230\u64A4\u56DE\u6807\u8BB0\u4F46\u7F3A\u5C11\u4F1A\u8BDD\u4E0A\u4E0B\u6587");
681
+ } else {
682
+ const messageIds = deleteTags.map((attrs) => String(attrs.message_id || "").trim()).filter(Boolean);
683
+ if (messageIds.length > 0) handled = true;
684
+ for (const messageId of messageIds) {
685
+ try {
686
+ await runDelete({ session, messageId, log });
687
+ } catch (error) {
688
+ log?.("warn", "XML \u89E6\u53D1\u64A4\u56DE\u5931\u8D25", error);
689
+ }
695
690
  }
696
- runResponse(responseId, response, null);
697
- return;
698
691
  }
699
- runResponse(responseId, response, session);
700
- }, 200);
701
- responseRetryHandles.set(responseId, handle);
702
- };
703
- const attach = () => {
704
- const characterService = ctx.chatluna_character;
705
- if (!characterService) return false;
706
- if (activeService !== characterService) {
707
- activeService = characterService;
708
- collectorBound = false;
709
- }
710
- if (!collectorBound && typeof characterService.collect === "function") {
711
- characterService.collect(async (session) => {
712
- const guildId = session?.guildId || session?.channelId || session?.userId || null;
713
- currentGuildId = guildId;
714
- if (guildId) sessionMap.set(guildId, session);
715
- lastSession = session;
716
- pendingSessions.push({ key: guildId, session, timestamp: Date.now() });
717
- });
718
- collectorBound = true;
719
692
  }
720
- const loggerService = characterService.logger;
721
- if (!loggerService || typeof loggerService.debug !== "function")
722
- return false;
723
- const tagged = loggerService.debug;
724
- if (!tagged[RAW_INTERCEPTOR_TAG]) {
725
- restore();
726
- const raw = loggerService.debug.bind(loggerService);
727
- const wrapped = (...args) => {
728
- raw(...args);
729
- const message = args[0];
730
- if (typeof message !== "string") return;
731
- const prefix = ["model response: ", "model response:\n"].find(
732
- (item) => message.startsWith(item)
733
- );
734
- if (!prefix) return;
735
- const response = message.substring(prefix.length);
736
- if (!response) return;
737
- const responseId = `${Date.now()}:${responseSeq++}`;
738
- const session = resolveSession();
739
- if (!session) {
740
- retryResponse(responseId, response);
741
- return;
693
+ return handled;
694
+ };
695
+ }
696
+
697
+ // src/features/xml-tools/temp-runtime.ts
698
+ var GET_TEMP_TAG = /* @__PURE__ */ Symbol.for("chatlunaToolboxXmlGetTempRuntime");
699
+ var GET_TEMP_ORIGINAL = /* @__PURE__ */ Symbol.for("chatlunaToolboxXmlOriginalGetTemp");
700
+ var GET_TEMP_LISTENERS = /* @__PURE__ */ Symbol.for("chatlunaToolboxXmlGetTempListeners");
701
+ var PUSH_DISPATCHER = /* @__PURE__ */ Symbol.for("chatlunaToolboxXmlPushDispatcher");
702
+ function getMessageType(message) {
703
+ if (!message) return "";
704
+ if (typeof message._getType === "function") {
705
+ return String(message._getType() || "").trim().toLowerCase();
706
+ }
707
+ return String(message.type || message.role || "").trim().toLowerCase();
708
+ }
709
+ function isAssistantMessage(message) {
710
+ const type = getMessageType(message);
711
+ return type === "ai" || type === "assistant";
712
+ }
713
+ function extractText(value) {
714
+ if (typeof value === "string") return value;
715
+ if (value == null) return "";
716
+ if (Array.isArray(value)) {
717
+ return value.map((item) => extractText(item)).join("");
718
+ }
719
+ if (typeof value !== "object") return "";
720
+ const record = value;
721
+ if (typeof record.text === "string") return record.text;
722
+ if (record.content !== void 0 && record.content !== value) {
723
+ return extractText(record.content);
724
+ }
725
+ if (Array.isArray(record.children)) return extractText(record.children);
726
+ if (typeof record.attrs === "object" && record.attrs) {
727
+ const attrs = record.attrs;
728
+ if (typeof attrs.content === "string") return attrs.content;
729
+ if (typeof attrs.text === "string") return attrs.text;
730
+ }
731
+ return "";
732
+ }
733
+ function getResponseText(message) {
734
+ if (!isAssistantMessage(message)) return "";
735
+ return extractText(message?.content ?? message?.text).trim();
736
+ }
737
+ function getDispatcher(messages) {
738
+ return messages[PUSH_DISPATCHER] ?? null;
739
+ }
740
+ function setDispatcher(messages, dispatcher) {
741
+ const record = messages;
742
+ if (!dispatcher) {
743
+ delete record[PUSH_DISPATCHER];
744
+ return;
745
+ }
746
+ Object.defineProperty(record, PUSH_DISPATCHER, {
747
+ value: dispatcher,
748
+ configurable: true,
749
+ enumerable: false,
750
+ writable: true
751
+ });
752
+ }
753
+ function attachDispatcher(messages, log) {
754
+ const existing = getDispatcher(messages);
755
+ if (existing) return existing;
756
+ const listeners = /* @__PURE__ */ new Set();
757
+ const processedMessages = /* @__PURE__ */ new WeakSet();
758
+ const originalPush = messages.push;
759
+ const patchedPush = function patchedPush2(...items) {
760
+ const result = originalPush.apply(this, items);
761
+ for (const item of items) {
762
+ if (!item || typeof item !== "object") continue;
763
+ const message = item;
764
+ if (!isAssistantMessage(message)) continue;
765
+ if (processedMessages.has(item)) continue;
766
+ const response = getResponseText(message);
767
+ if (!response) continue;
768
+ processedMessages.add(item);
769
+ for (const listener of Array.from(listeners)) {
770
+ try {
771
+ listener(message);
772
+ } catch (error) {
773
+ log?.("warn", "\u5904\u7406 XML completionMessages \u76D1\u542C\u5668\u5931\u8D25", error);
742
774
  }
743
- runResponse(responseId, response, session);
744
- };
745
- wrapped[RAW_INTERCEPTOR_TAG] = true;
746
- loggerService.debug = wrapped;
747
- activeLogger = loggerService;
748
- originalDebug = raw;
775
+ }
749
776
  }
750
- return true;
777
+ return result;
751
778
  };
752
- const stopFastRetry = () => {
753
- if (!fastRetryHandle) return;
754
- fastRetryHandle();
755
- fastRetryHandle = null;
779
+ const dispatcher = {
780
+ messages,
781
+ originalPush,
782
+ patchedPush,
783
+ listeners,
784
+ processedMessages
756
785
  };
757
- const startFastRetry = () => {
758
- if (fastRetryHandle) return;
759
- fastRetryHandle = ctx.setInterval(() => {
760
- if (isActive()) {
761
- stopFastRetry();
762
- return;
763
- }
764
- const ready = attach();
765
- if (ready) {
766
- log?.("info", "\u539F\u59CB\u8F93\u51FA\u62E6\u622A\u5DF2\u6062\u590D");
767
- stopFastRetry();
786
+ messages.push = patchedPush;
787
+ setDispatcher(messages, dispatcher);
788
+ return dispatcher;
789
+ }
790
+ function restoreDispatcher(dispatcher) {
791
+ if (dispatcher.messages.push === dispatcher.patchedPush) {
792
+ dispatcher.messages.push = dispatcher.originalPush;
793
+ }
794
+ setDispatcher(dispatcher.messages, null);
795
+ }
796
+ function registerGetTempListener(service, listener) {
797
+ const getTemp = service.getTemp;
798
+ if (typeof getTemp !== "function") return null;
799
+ const serviceRecord = service;
800
+ let listeners = serviceRecord[GET_TEMP_LISTENERS];
801
+ if (!listeners) {
802
+ listeners = /* @__PURE__ */ new Set();
803
+ serviceRecord[GET_TEMP_LISTENERS] = listeners;
804
+ }
805
+ if (!serviceRecord[GET_TEMP_TAG]) {
806
+ Object.defineProperty(serviceRecord, GET_TEMP_ORIGINAL, {
807
+ value: getTemp,
808
+ configurable: true,
809
+ enumerable: false,
810
+ writable: true
811
+ });
812
+ service.getTemp = async (...args) => {
813
+ const originalGetTemp = serviceRecord[GET_TEMP_ORIGINAL];
814
+ const temp = await originalGetTemp?.apply(
815
+ service,
816
+ args
817
+ );
818
+ const activeListeners = serviceRecord[GET_TEMP_LISTENERS];
819
+ const session = args[0] && typeof args[0] === "object" ? args[0] : null;
820
+ if (temp && activeListeners?.size) {
821
+ for (const handler of Array.from(activeListeners)) {
822
+ handler(temp, session);
823
+ }
768
824
  }
769
- }, 3e3);
825
+ return temp;
826
+ };
827
+ serviceRecord[GET_TEMP_TAG] = true;
828
+ }
829
+ listeners.add(listener);
830
+ return () => {
831
+ const currentListeners = serviceRecord[GET_TEMP_LISTENERS];
832
+ currentListeners?.delete(listener);
833
+ if (currentListeners?.size) return;
834
+ const originalGetTemp = serviceRecord[GET_TEMP_ORIGINAL];
835
+ if (originalGetTemp) service.getTemp = originalGetTemp;
836
+ delete serviceRecord[GET_TEMP_ORIGINAL];
837
+ delete serviceRecord[GET_TEMP_TAG];
838
+ delete serviceRecord[GET_TEMP_LISTENERS];
770
839
  };
771
- const ensureActive = () => {
772
- if (isActive()) {
773
- stopFastRetry();
774
- return;
775
- }
776
- const ready = attach();
777
- if (!ready) {
778
- startFastRetry();
779
- }
840
+ }
841
+ function createCharacterTempXmlRuntime(params) {
842
+ const { getCharacterService, processModelResponse, log } = params;
843
+ let attachedDispatchers = [];
844
+ let stopListening = null;
845
+ const compactAttachedDispatchers = () => {
846
+ attachedDispatchers = attachedDispatchers.filter(
847
+ (entry) => Boolean(entry.ref.deref())
848
+ );
849
+ };
850
+ const hasAttachedListener = (dispatcher) => {
851
+ compactAttachedDispatchers();
852
+ return attachedDispatchers.some(
853
+ (entry) => entry.ref.deref() === dispatcher
854
+ );
780
855
  };
781
- const startMonitor = () => {
782
- if (monitorHandle) return;
783
- monitorHandle = ctx.setInterval(() => {
784
- ensureActive();
785
- }, 5e3);
856
+ const detachDispatcherListener = (entry) => {
857
+ const dispatcher = entry.ref.deref();
858
+ if (!dispatcher) return;
859
+ dispatcher.listeners.delete(entry.listener);
860
+ if (dispatcher.listeners.size === 0) restoreDispatcher(dispatcher);
786
861
  };
787
- const stopMonitor = () => {
788
- if (!monitorHandle) return;
789
- monitorHandle();
790
- monitorHandle = null;
862
+ const handleTemp = (temp, session) => {
863
+ const messages = temp.completionMessages;
864
+ if (!Array.isArray(messages) || typeof messages.push !== "function") return;
865
+ const dispatcher = attachDispatcher(messages, log);
866
+ if (hasAttachedListener(dispatcher)) return;
867
+ const listener = (message) => {
868
+ const response = getResponseText(message);
869
+ if (!response) return;
870
+ void processModelResponse({ response, session }).catch((error) => {
871
+ log?.("warn", "\u5904\u7406 XML \u6A21\u578B\u54CD\u5E94\u5931\u8D25", error);
872
+ });
873
+ };
874
+ dispatcher.listeners.add(listener);
875
+ attachedDispatchers.push({ ref: new WeakRef(dispatcher), listener });
791
876
  };
792
877
  return {
793
878
  start: () => {
794
- const delay = 3e3;
795
- if (startupHandle) return;
796
- startupHandle = ctx.setTimeout(() => {
797
- startupHandle = null;
798
- const ready = attach();
799
- if (ready) {
800
- log?.("info", "\u5DF2\u542F\u7528\u539F\u59CB\u8F93\u51FA\u62E6\u622A\u6A21\u5F0F");
801
- } else {
802
- log?.("warn", "chatluna_character \u670D\u52A1\u4E0D\u53EF\u7528\uFF0C\u5C06\u6BCF3\u79D2\u91CD\u8BD5\u4E00\u6B21");
803
- startFastRetry();
879
+ if (stopListening) return true;
880
+ const service = getCharacterService();
881
+ if (!service) return false;
882
+ const detach = registerGetTempListener(service, handleTemp);
883
+ if (!detach) return false;
884
+ stopListening = () => {
885
+ compactAttachedDispatchers();
886
+ for (const entry of attachedDispatchers) {
887
+ detachDispatcherListener(entry);
804
888
  }
805
- startMonitor();
806
- }, delay);
889
+ attachedDispatchers = [];
890
+ detach();
891
+ stopListening = null;
892
+ };
893
+ return true;
807
894
  },
808
895
  stop: () => {
809
- if (startupHandle) {
810
- startupHandle();
811
- startupHandle = null;
812
- }
813
- stopFastRetry();
814
- stopMonitor();
815
- for (const handle of responseRetryHandles.values()) handle();
816
- responseRetryHandles.clear();
817
- processedResponses.clear();
818
- restore();
819
- sessionMap.clear();
820
- pendingSessions.length = 0;
821
- currentGuildId = null;
822
- lastSession = null;
823
- activeService = null;
824
- }
825
- };
826
- }
827
-
828
- // src/features/xml-tools/parser.ts
829
- function unique(values) {
830
- return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean)));
831
- }
832
- function parseXmlActions(response) {
833
- const pokeMatches = Array.from(response.matchAll(/<poke\s+id="([^"]+)"\s*\/>/gi));
834
- const emojiMatches = Array.from(
835
- response.matchAll(/<emoji\s+message_id="([^"]+)"\s+emoji_id="([^"]+)"\s*\/>/gi)
836
- );
837
- const deleteMatches = Array.from(response.matchAll(/<delete\s+message_id="([^"]+)"\s*\/?>/gi));
838
- return {
839
- pokeUserIds: unique(pokeMatches.map((match) => String(match[1] || ""))),
840
- emojis: emojiMatches.map((match) => ({
841
- messageId: String(match[1] || "").trim(),
842
- emojiId: String(match[2] || "").trim()
843
- })).filter((item) => item.messageId && item.emojiId),
844
- deleteMessageIds: unique(deleteMatches.map((match) => String(match[1] || "")))
896
+ stopListening?.();
897
+ },
898
+ isActive: () => Boolean(stopListening)
845
899
  };
846
900
  }
847
901
 
848
902
  // src/features/xml-tools/register.ts
849
903
  function registerXmlTools(deps) {
850
904
  const { ctx, config, protocol, log } = deps;
851
- const interceptor = createXmlInterceptor({
852
- ctx,
853
- config,
854
- log,
855
- onResponse: (response, session) => {
856
- const actions = parseXmlActions(response);
857
- let handled = false;
858
- if (config.enablePokeXmlTool && actions.pokeUserIds.length > 0) {
859
- if (!session) {
860
- log?.("warn", "\u68C0\u6D4B\u5230\u6233\u4E00\u6233\u6807\u8BB0\u4F46\u7F3A\u5C11\u4F1A\u8BDD\u4E0A\u4E0B\u6587");
861
- } else {
862
- handled = true;
863
- for (const userId of actions.pokeUserIds) {
864
- void sendPoke({ session, userId, protocol, log }).catch((error) => {
865
- log?.("warn", "XML \u89E6\u53D1 poke \u5931\u8D25", error);
866
- });
867
- }
868
- }
869
- }
870
- if (config.enableEmojiXmlTool && actions.emojis.length > 0) {
871
- if (!session) {
872
- log?.("warn", "\u68C0\u6D4B\u5230\u8868\u60C5\u6807\u8BB0\u4F46\u7F3A\u5C11\u4F1A\u8BDD\u4E0A\u4E0B\u6587");
873
- } else {
874
- handled = true;
875
- for (const item of actions.emojis) {
876
- void sendMsgEmoji({
877
- session,
878
- messageId: item.messageId,
879
- emojiId: item.emojiId,
880
- protocol,
881
- log
882
- }).catch((error) => {
883
- log?.("warn", "XML \u89E6\u53D1\u8868\u60C5\u5931\u8D25", error);
884
- });
885
- }
886
- }
887
- }
888
- if (config.enableDeleteXmlTool && actions.deleteMessageIds.length > 0) {
889
- if (!session) {
890
- log?.("warn", "\u68C0\u6D4B\u5230\u64A4\u56DE\u6807\u8BB0\u4F46\u7F3A\u5C11\u4F1A\u8BDD\u4E0A\u4E0B\u6587");
891
- } else {
892
- handled = true;
893
- for (const messageId of actions.deleteMessageIds) {
894
- void sendDeleteMessage({ session, messageId, log }).catch(
895
- (error) => {
896
- log?.("warn", "XML \u89E6\u53D1\u64A4\u56DE\u5931\u8D25", error);
897
- }
898
- );
899
- }
900
- }
901
- }
902
- return handled;
903
- }
905
+ const processor = createXmlProcessor({ config, protocol, log });
906
+ const runtime = createCharacterTempXmlRuntime({
907
+ getCharacterService: () => ctx.chatluna_character,
908
+ processModelResponse: processor,
909
+ log
904
910
  });
905
911
  return {
906
- start: () => interceptor.start(),
907
- stop: () => interceptor.stop()
912
+ start: () => runtime.start(),
913
+ stop: () => runtime.stop(),
914
+ isActive: () => runtime.isActive()
908
915
  };
909
916
  }
910
917
 
@@ -1509,22 +1516,35 @@ function apply(ctx, config) {
1509
1516
  };
1510
1517
  const log = createLogger(ctx, config);
1511
1518
  let xmlRuntime = null;
1519
+ let characterCtx = null;
1512
1520
  const initializeServices = async () => {
1513
1521
  log("info", "toolbox \u521D\u59CB\u5316\u5F00\u59CB");
1514
1522
  registerVariables({ ctx, config, log });
1515
1523
  const protocol = resolveOneBotProtocol(config, log);
1516
1524
  registerNativeTools({ ctx, config, plugin, protocol, log });
1517
- if (config.enablePokeXmlTool || config.enableEmojiXmlTool || config.enableDeleteXmlTool) {
1518
- xmlRuntime = registerXmlTools({ ctx, config, protocol, log });
1519
- xmlRuntime.start();
1525
+ xmlRuntime = registerXmlTools({ ctx, config, protocol, log });
1526
+ if (characterCtx && xmlRuntime.start()) {
1520
1527
  log("info", "XML \u5DE5\u5177\u5DF2\u542F\u7528");
1521
1528
  }
1522
1529
  log("info", "toolbox \u521D\u59CB\u5316\u5B8C\u6210");
1523
1530
  };
1524
1531
  const dispose = () => {
1532
+ characterCtx = null;
1525
1533
  xmlRuntime?.stop();
1526
1534
  xmlRuntime = null;
1527
1535
  };
1536
+ if (config.enablePokeXmlTool || config.enableEmojiXmlTool || config.enableDeleteXmlTool) {
1537
+ ctx.inject(["chatluna_character"], (innerCtx) => {
1538
+ characterCtx = innerCtx;
1539
+ if (!xmlRuntime) return;
1540
+ const started = xmlRuntime.start();
1541
+ if (started) log("info", "XML \u5DE5\u5177\u5DF2\u542F\u7528");
1542
+ innerCtx.on("dispose", () => {
1543
+ if (characterCtx === innerCtx) characterCtx = null;
1544
+ xmlRuntime?.stop();
1545
+ });
1546
+ });
1547
+ }
1528
1548
  ctx.on("dispose", dispose);
1529
1549
  if (ctx.root.lifecycle.isActive) {
1530
1550
  void initializeServices();
@@ -1547,6 +1567,7 @@ function apply(ctx, config) {
1547
1567
  apply,
1548
1568
  callOneBotAPI,
1549
1569
  createBotInfoProvider,
1570
+ createCharacterTempXmlRuntime,
1550
1571
  createDeleteMessageTool,
1551
1572
  createGroupInfoProvider,
1552
1573
  createLogger,
@@ -1556,12 +1577,12 @@ function apply(ctx, config) {
1556
1577
  createSetMsgEmojiTool,
1557
1578
  createSetProfileTool,
1558
1579
  createUserInfoProvider,
1559
- createXmlInterceptor,
1580
+ createXmlProcessor,
1560
1581
  ensureOneBotSession,
1561
1582
  getSession,
1562
1583
  inject,
1563
1584
  name,
1564
- parseXmlActions,
1585
+ parseSelfClosingXmlTags,
1565
1586
  registerNativeTools,
1566
1587
  registerVariables,
1567
1588
  registerXmlTools,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chatluna-toolbox",
3
3
  "description": "为 ChatLuna 提供更多原生工具、XML 工具与变量。",
4
- "version": "0.0.4",
4
+ "version": "0.0.6",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -18,11 +18,6 @@
18
18
  "plugin",
19
19
  "chatluna"
20
20
  ],
21
- "koishi": {
22
- "description": {
23
- "zh": "ChatLuna 工具箱插件"
24
- }
25
- },
26
21
  "peerDependencies": {
27
22
  "koishi": "^4.18.7",
28
23
  "koishi-plugin-chatluna": "*",