@openclaw/qqbot 2026.6.8-beta.2 → 2026.6.9-beta.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.
package/dist/api.js CHANGED
@@ -1,8 +1,8 @@
1
- import { t as qqbotPlugin } from "./channel-M0rnT4bN.js";
1
+ import { t as qqbotPlugin } from "./channel-91Y8ZcpJ.js";
2
2
  import { a as resolveQQBotAccount, i as resolveDefaultQQBotAccountId, n as applyQQBotAccountConfig, r as listQQBotAccountIds, t as DEFAULT_ACCOUNT_ID } from "./config-ZEfgeoL4.js";
3
3
  import { t as qqbotSetupPlugin } from "./channel.setup-CQ_DFfPx.js";
4
4
  import { t as getFrameworkCommands } from "./slash-commands-impl-DVApdSop.js";
5
5
  import { n as registerRemindTool, r as registerChannelTool, t as registerQQBotTools } from "./tools-2d9t-N1b.js";
6
- import { t as registerQQBotFull } from "./channel-entry-CfzBEypJ.js";
7
- import { C as getMessageReplyStats, D as setOutboundAudioPort, E as OUTBOUND_ERROR_CODES, S as getMessageReplyConfig, T as DEFAULT_MEDIA_SEND_ERROR, _ as sendVideoMsg, a as sendText, b as MESSAGE_REPLY_LIMIT, f as buildMediaTarget, g as sendPhoto, h as sendDocument, i as sendProactiveMessage, m as resolveOutboundMediaPath, n as sendCronMessage, p as parseTarget, r as sendMedia, v as sendVoice, w as recordMessageReply, x as checkMessageReplyLimit, y as resolveUserFacingMediaError } from "./outbound-C9wV892v.js";
6
+ import { t as registerQQBotFull } from "./channel-entry-D_eHXHMg.js";
7
+ import { C as getMessageReplyConfig, D as OUTBOUND_ERROR_CODES, E as DEFAULT_MEDIA_SEND_ERROR, O as setOutboundAudioPort, S as checkMessageReplyLimit, T as recordMessageReply, _ as sendVideoMsg, a as sendText, b as resolveUserFacingMediaError, f as buildMediaTarget, g as sendPhoto, h as sendDocument, i as sendProactiveMessage, m as resolveOutboundMediaPath, n as sendCronMessage, p as parseTarget, r as sendMedia, v as sendVoice, w as getMessageReplyStats, x as MESSAGE_REPLY_LIMIT } from "./outbound-DN8kX1G3.js";
8
8
  export { DEFAULT_ACCOUNT_ID, DEFAULT_MEDIA_SEND_ERROR, MESSAGE_REPLY_LIMIT, OUTBOUND_ERROR_CODES, applyQQBotAccountConfig, buildMediaTarget, checkMessageReplyLimit, getFrameworkCommands, getMessageReplyConfig, getMessageReplyStats, listQQBotAccountIds, parseTarget, qqbotPlugin, qqbotSetupPlugin, recordMessageReply, registerChannelTool, registerQQBotFull, registerQQBotTools, registerRemindTool, resolveDefaultQQBotAccountId, resolveOutboundMediaPath, resolveQQBotAccount, resolveUserFacingMediaError, sendCronMessage, sendDocument, sendMedia, sendPhoto, sendProactiveMessage, sendText, sendVideoMsg, sendVoice, setOutboundAudioPort };
@@ -296,6 +296,9 @@ function canResolveTarget(request) {
296
296
  bundledFallback: true
297
297
  })?.id != null;
298
298
  }
299
+ function resolveNativeDeliveryState(params) {
300
+ return isNativeDeliveryEnabled(params) ? { kind: "enabled" } : { kind: "disabled" };
301
+ }
299
302
  function createQQBotApprovalCapability() {
300
303
  return createChannelApprovalCapability({
301
304
  authorizeActorAction: ({ cfg, accountId, senderId, approvalKind }) => authorizeQQBotApprovalAction({
@@ -304,18 +307,8 @@ function createQQBotApprovalCapability() {
304
307
  senderId,
305
308
  approvalKind
306
309
  }),
307
- getActionAvailabilityState: ({ cfg, accountId }) => {
308
- return isNativeDeliveryEnabled({
309
- cfg,
310
- accountId
311
- }) ? { kind: "enabled" } : { kind: "disabled" };
312
- },
313
- getExecInitiatingSurfaceState: ({ cfg, accountId }) => {
314
- return isNativeDeliveryEnabled({
315
- cfg,
316
- accountId
317
- }) ? { kind: "enabled" } : { kind: "disabled" };
318
- },
310
+ getActionAvailabilityState: resolveNativeDeliveryState,
311
+ getExecInitiatingSurfaceState: resolveNativeDeliveryState,
319
312
  describeExecApprovalSetup: ({ accountId }) => {
320
313
  return `QQBot native exec approvals are enabled by default. To restrict who can approve, configure \`${accountId && accountId !== "default" ? `channels.qqbot.accounts.${accountId}` : "channels.qqbot"}.execApprovals.approvers\` with QQ user OpenIDs.`;
321
314
  },
@@ -377,7 +370,7 @@ function createQQBotApprovalCapability() {
377
370
  },
378
371
  load: async () => {
379
372
  ensurePlatformAdapter();
380
- return (await import("./handler-runtime-BZCDG_mN.js")).qqbotApprovalNativeRuntime;
373
+ return (await import("./handler-runtime-BzeYyTXb.js")).qqbotApprovalNativeRuntime;
381
374
  }
382
375
  })
383
376
  });
@@ -649,6 +642,412 @@ function clearAccountCredentials(cfg, accountId) {
649
642
  };
650
643
  }
651
644
  //#endregion
645
+ //#region extensions/qqbot/src/engine/messaging/markdown-table-chunking.ts
646
+ const QQBOT_MARKDOWN_SAFE_CHUNK_BYTE_LIMIT = 3600;
647
+ function chunkQQBotMarkdownText(text, limit, baseChunker) {
648
+ const chunker = createQQBotMarkdownChunker(baseChunker);
649
+ return [...chunker.chunkText(text, limit), ...chunker.flushPendingText(limit)];
650
+ }
651
+ function createQQBotMarkdownChunker(baseChunker) {
652
+ const state = new QQBotMarkdownChunkingState(baseChunker);
653
+ return {
654
+ chunkText: (text, limit) => state.chunkText(text, limit),
655
+ flushPendingText: (limit) => state.flushPendingText(limit)
656
+ };
657
+ }
658
+ var QQBotMarkdownChunkingState = class {
659
+ constructor(baseChunker) {
660
+ this.baseChunker = baseChunker;
661
+ this.activeTable = null;
662
+ this.pendingHeaderLine = null;
663
+ this.pendingHeaderCells = [];
664
+ this.tableLines = [];
665
+ this.textLines = [];
666
+ this.pendingRowFragment = null;
667
+ this.activeFence = null;
668
+ this.pendingTextFenceOpenLine = null;
669
+ this.pendingFenceLineFragment = null;
670
+ }
671
+ chunkText(text, limit) {
672
+ if (!text) return [];
673
+ if (limit <= 0) return this.baseChunker(text, limit);
674
+ const chunkLimit = resolveQQBotMarkdownChunkLimit(limit);
675
+ const chunks = [];
676
+ const textWithPendingRow = this.consumePendingRowPrefix(text);
677
+ const textWithPendingFenceLine = this.consumePendingFenceLinePrefix(textWithPendingRow);
678
+ const hasTrailingNewline = textWithPendingFenceLine.endsWith("\n");
679
+ const lines = textWithPendingFenceLine.split("\n");
680
+ for (const [index, line] of lines.entries()) {
681
+ const isTrailingSplitLine = index === lines.length - 1 && line === "";
682
+ this.consumeLine(line, {
683
+ limit: chunkLimit,
684
+ chunks,
685
+ hasTrailingNewline,
686
+ isTrailingSplitLine,
687
+ isLastLine: index === lines.length - 1
688
+ });
689
+ }
690
+ this.flushText(chunks, chunkLimit);
691
+ this.flushTable(chunks);
692
+ return chunks;
693
+ }
694
+ flushPendingText(limit) {
695
+ const chunkLimit = resolveQQBotMarkdownChunkLimit(limit);
696
+ const chunks = [];
697
+ this.flushPendingRowFragment(chunks, chunkLimit);
698
+ this.flushPendingFenceLineFragment();
699
+ this.flushPendingHeaderAsText();
700
+ this.flushText(chunks, chunkLimit);
701
+ this.flushTable(chunks);
702
+ return chunks;
703
+ }
704
+ consumeLine(line, params) {
705
+ const fence = parseFenceLine(line);
706
+ if (fence) {
707
+ this.endTable(params.chunks);
708
+ if (!this.activeFence) {
709
+ this.pushTextLine(line);
710
+ this.activeFence = fence;
711
+ this.clearPendingTableHeader();
712
+ return;
713
+ }
714
+ if (isClosingFenceLine(line, this.activeFence)) {
715
+ this.pushFenceTextLine(line);
716
+ this.activeFence = null;
717
+ this.clearPendingTableHeader();
718
+ return;
719
+ }
720
+ this.pushFenceTextLine(line);
721
+ this.clearPendingTableHeader();
722
+ return;
723
+ }
724
+ if (this.activeFence) {
725
+ if (params.isLastLine && !params.hasTrailingNewline) {
726
+ this.pendingFenceLineFragment = mergeFenceLineFragments(this.pendingFenceLineFragment, line);
727
+ return;
728
+ }
729
+ this.pushFenceTextLine(line);
730
+ return;
731
+ }
732
+ if (isIncompleteTableRowFragment(line) || this.activeTable && isShortTableRowLine(line, this.activeTable)) {
733
+ if (params.isLastLine) {
734
+ this.flushText(params.chunks, params.limit);
735
+ this.pendingRowFragment = mergeRowFragments(this.pendingRowFragment, line);
736
+ return;
737
+ }
738
+ this.pushTextLine(renderMalformedPipeLineAsText(line));
739
+ return;
740
+ }
741
+ if (this.pendingHeaderLine && isTableSeparatorLine(line)) {
742
+ this.flushText(params.chunks, params.limit);
743
+ this.activeTable = {
744
+ header: this.pendingHeaderLine,
745
+ separator: line,
746
+ cells: this.pendingHeaderCells
747
+ };
748
+ this.pendingHeaderLine = null;
749
+ this.pendingHeaderCells = [];
750
+ this.ensureTableHeader();
751
+ return;
752
+ }
753
+ if (isTableRowLine(line) && this.activeTable && !isTableSeparatorLine(line)) {
754
+ this.flushText(params.chunks, params.limit);
755
+ this.appendTableRow(line, params.limit, params.chunks);
756
+ return;
757
+ }
758
+ if (this.activeTable) {
759
+ if (!line.trim() && params.isTrailingSplitLine) return;
760
+ this.endTable(params.chunks);
761
+ }
762
+ if (isTableRowLine(line) && !isTableSeparatorLine(line)) {
763
+ this.flushText(params.chunks, params.limit);
764
+ this.pendingHeaderLine = line;
765
+ this.pendingHeaderCells = splitTableCells(line);
766
+ return;
767
+ }
768
+ this.flushPendingHeaderAsText();
769
+ this.pushTextLine(line);
770
+ }
771
+ pushTextLine(line) {
772
+ this.textLines.push(line);
773
+ }
774
+ pushFenceTextLine(line) {
775
+ if (this.textLines.length === 0 && this.activeFence) this.pendingTextFenceOpenLine = this.activeFence.openLine;
776
+ this.textLines.push(line);
777
+ }
778
+ appendTableRow(line, limit, chunks) {
779
+ if (utf8ByteLength([
780
+ this.activeTable.header,
781
+ this.activeTable.separator,
782
+ line
783
+ ].join("\n")) > limit) {
784
+ this.dropHeaderOnlyTableChunk();
785
+ this.flushTable(chunks);
786
+ this.pushOversizedTableRow(line, limit, chunks);
787
+ return;
788
+ }
789
+ this.ensureTableHeader();
790
+ if (utf8ByteLength([...this.tableLines, line].join("\n")) <= limit) {
791
+ this.tableLines.push(line);
792
+ return;
793
+ }
794
+ this.flushTable(chunks);
795
+ this.ensureTableHeader();
796
+ this.tableLines.push(line);
797
+ }
798
+ pushOversizedTableRow(line, limit, chunks) {
799
+ pushBaseChunks(chunks, renderTableRowAsFields(this.activeTable.cells, splitTableCells(line)), limit, this.baseChunker);
800
+ }
801
+ ensureTableHeader() {
802
+ if (this.tableLines.length > 0 || !this.activeTable) return;
803
+ this.tableLines.push(this.activeTable.header, this.activeTable.separator);
804
+ }
805
+ flushText(chunks, limit) {
806
+ if (this.textLines.length === 0) return;
807
+ if (this.flushFenceText(chunks, limit)) return;
808
+ let text = this.textLines.join("\n");
809
+ this.textLines = [];
810
+ if (this.pendingTextFenceOpenLine) {
811
+ text = `${this.pendingTextFenceOpenLine}\n${text}`;
812
+ this.pendingTextFenceOpenLine = null;
813
+ }
814
+ if (this.activeFence) text = `${text}\n${this.activeFence.closeLine}`;
815
+ if (!text) return;
816
+ pushBaseChunks(chunks, text, limit, this.baseChunker);
817
+ }
818
+ flushFenceText(chunks, limit) {
819
+ const pendingFenceOpenLine = this.pendingTextFenceOpenLine;
820
+ const firstLineFence = pendingFenceOpenLine ? null : parseFenceLine(this.textLines[0] ?? "");
821
+ const fence = pendingFenceOpenLine ? parseFenceLine(pendingFenceOpenLine) : firstLineFence;
822
+ if (!fence) return false;
823
+ const bodyLines = pendingFenceOpenLine ? [...this.textLines] : this.textLines.slice(1);
824
+ this.textLines = [];
825
+ this.pendingTextFenceOpenLine = null;
826
+ if (bodyLines.length > 0 && isClosingFenceLine(bodyLines[bodyLines.length - 1], fence)) bodyLines.pop();
827
+ if (this.activeFence && bodyLines.length === 0) return true;
828
+ pushFenceLineChunks({
829
+ chunks,
830
+ openLine: fence.openLine,
831
+ closeLine: fence.closeLine,
832
+ bodyLines,
833
+ limit,
834
+ baseChunker: this.baseChunker
835
+ });
836
+ return true;
837
+ }
838
+ consumePendingFenceLinePrefix(text) {
839
+ if (!this.pendingFenceLineFragment) return text;
840
+ const separator = shouldJoinFenceLineFragments(this.pendingFenceLineFragment, text) ? "" : "\n";
841
+ const merged = `${this.pendingFenceLineFragment}${separator}${text}`;
842
+ this.pendingFenceLineFragment = null;
843
+ return merged;
844
+ }
845
+ flushPendingFenceLineFragment() {
846
+ if (!this.pendingFenceLineFragment) return;
847
+ this.pushFenceTextLine(this.pendingFenceLineFragment);
848
+ this.pendingFenceLineFragment = null;
849
+ }
850
+ flushPendingHeaderAsText() {
851
+ if (!this.pendingHeaderLine) return;
852
+ this.pushTextLine(this.pendingHeaderLine);
853
+ this.pendingHeaderLine = null;
854
+ this.pendingHeaderCells = [];
855
+ }
856
+ clearPendingTableHeader() {
857
+ this.pendingHeaderLine = null;
858
+ this.pendingHeaderCells = [];
859
+ }
860
+ consumePendingRowPrefix(text) {
861
+ if (!this.pendingRowFragment) return text;
862
+ const separator = this.pendingRowFragment.trimEnd().endsWith("|") && text && !/^[\s|]/.test(text) ? " " : "";
863
+ const merged = `${this.pendingRowFragment}${separator}${text}`;
864
+ this.pendingRowFragment = null;
865
+ return merged;
866
+ }
867
+ flushPendingRowFragment(chunks, limit) {
868
+ if (!this.pendingRowFragment) return;
869
+ const fragment = this.pendingRowFragment;
870
+ this.pendingRowFragment = null;
871
+ pushBaseChunks(chunks, this.activeTable ? renderTableRowAsFields(this.activeTable.cells, splitPartialTableCells(fragment)) : renderMalformedPipeLineAsText(fragment), limit, this.baseChunker);
872
+ }
873
+ flushTable(chunks) {
874
+ if (this.tableLines.length === 0) return;
875
+ chunks.push(this.tableLines.join("\n"));
876
+ this.tableLines = [];
877
+ }
878
+ dropHeaderOnlyTableChunk() {
879
+ if (this.activeTable && this.tableLines.length === 2 && this.tableLines[0] === this.activeTable.header && this.tableLines[1] === this.activeTable.separator) this.tableLines = [];
880
+ }
881
+ endTable(chunks) {
882
+ this.flushTable(chunks);
883
+ this.activeTable = null;
884
+ }
885
+ };
886
+ function isTableRowLine(line) {
887
+ const trimmed = line.trim();
888
+ return trimmed.startsWith("|") && trimmed.endsWith("|") && splitTableCells(trimmed).length >= 2;
889
+ }
890
+ function resolveQQBotMarkdownChunkLimit(limit) {
891
+ return Math.min(limit, QQBOT_MARKDOWN_SAFE_CHUNK_BYTE_LIMIT);
892
+ }
893
+ function pushBaseChunks(chunks, text, byteLimit, baseChunker) {
894
+ for (const chunk of baseChunker(text, byteLimit)) {
895
+ if (!chunk) continue;
896
+ if (utf8ByteLength(chunk) <= byteLimit) {
897
+ chunks.push(chunk);
898
+ continue;
899
+ }
900
+ chunks.push(...splitByUtf8ByteLimit(chunk, byteLimit));
901
+ }
902
+ }
903
+ function splitByUtf8ByteLimit(text, byteLimit) {
904
+ if (!text) return [];
905
+ const chunks = [];
906
+ let current = "";
907
+ let currentBytes = 0;
908
+ for (const char of text) {
909
+ const charBytes = utf8ByteLength(char);
910
+ if (current && currentBytes + charBytes > byteLimit) {
911
+ chunks.push(current);
912
+ current = "";
913
+ currentBytes = 0;
914
+ }
915
+ current += char;
916
+ currentBytes += charBytes;
917
+ }
918
+ if (current) chunks.push(current);
919
+ return chunks;
920
+ }
921
+ function utf8ByteLength(text) {
922
+ return Buffer.byteLength(text, "utf8");
923
+ }
924
+ function isIncompleteTableRowFragment(line) {
925
+ const trimmed = line.trim();
926
+ return trimmed.startsWith("|") && !trimmed.endsWith("|") && splitPartialTableCells(trimmed).length >= 2;
927
+ }
928
+ function isShortTableRowLine(line, table) {
929
+ if (!isTableRowLine(line) || isTableSeparatorLine(line)) return false;
930
+ return splitTableCells(line).length < table.cells.length;
931
+ }
932
+ function isTableSeparatorLine(line) {
933
+ if (!isTableRowLine(line)) return false;
934
+ const cells = splitTableCells(line);
935
+ return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell.trim()));
936
+ }
937
+ function splitTableCells(line) {
938
+ return line.trim().slice(1, -1).split("|").map((cell) => cell.trim());
939
+ }
940
+ function splitPartialTableCells(line) {
941
+ return line.trim().replace(/^\|/, "").split("|").map((cell) => cell.trim()).filter((cell) => cell.length > 0);
942
+ }
943
+ function mergeRowFragments(pending, next) {
944
+ return pending ? `${pending}${next}` : next;
945
+ }
946
+ function mergeFenceLineFragments(pending, next) {
947
+ return pending ? `${pending}${next}` : next;
948
+ }
949
+ function shouldJoinFenceLineFragments(pending, next) {
950
+ if (!next || next.startsWith("\n")) return true;
951
+ const trimmedPending = pending.trimEnd();
952
+ const trimmedNext = next.trimStart();
953
+ if (!trimmedPending || !trimmedNext) return true;
954
+ if (/\d\.$/.test(trimmedPending) && /^\d/.test(trimmedNext)) return true;
955
+ if (/[.([{:,+\-*/%=&|^<>\\]$/.test(trimmedPending)) return true;
956
+ return hasUnclosedQuote(trimmedPending) || hasUnclosedDelimiter(trimmedPending);
957
+ }
958
+ function hasUnclosedQuote(line) {
959
+ let single = false;
960
+ let double = false;
961
+ let escaped = false;
962
+ for (const char of line) {
963
+ if (escaped) {
964
+ escaped = false;
965
+ continue;
966
+ }
967
+ if (char === "\\") {
968
+ escaped = true;
969
+ continue;
970
+ }
971
+ if (char === "'" && !double) {
972
+ single = !single;
973
+ continue;
974
+ }
975
+ if (char === "\"" && !single) double = !double;
976
+ }
977
+ return single || double;
978
+ }
979
+ function hasUnclosedDelimiter(line) {
980
+ const stack = [];
981
+ const pairs = {
982
+ "(": ")",
983
+ "[": "]",
984
+ "{": "}"
985
+ };
986
+ const closers = new Set(Object.values(pairs));
987
+ for (const char of line) {
988
+ if (pairs[char]) {
989
+ stack.push(pairs[char]);
990
+ continue;
991
+ }
992
+ if (closers.has(char)) {
993
+ if (stack.at(-1) === char) stack.pop();
994
+ }
995
+ }
996
+ return stack.length > 0;
997
+ }
998
+ function renderMalformedPipeLineAsText(line) {
999
+ return splitPartialTableCells(line).join(" ");
1000
+ }
1001
+ function renderTableRowAsFields(headers, cells) {
1002
+ return cells.map((cell, index) => {
1003
+ const header = headers[index]?.trim();
1004
+ return header ? `${header}: ${cell}` : cell;
1005
+ }).join("\n");
1006
+ }
1007
+ function pushFenceLineChunks(params) {
1008
+ const { chunks, openLine, closeLine, bodyLines, limit, baseChunker } = params;
1009
+ let currentLines = [];
1010
+ const render = (lines) => [
1011
+ openLine,
1012
+ ...lines,
1013
+ closeLine
1014
+ ].join("\n");
1015
+ const flushCurrent = () => {
1016
+ if (currentLines.length === 0) return;
1017
+ chunks.push(render(currentLines));
1018
+ currentLines = [];
1019
+ };
1020
+ for (const line of bodyLines) {
1021
+ const candidate = [...currentLines, line];
1022
+ if (utf8ByteLength(render(candidate)) <= limit) {
1023
+ currentLines = candidate;
1024
+ continue;
1025
+ }
1026
+ flushCurrent();
1027
+ const singleLineChunk = render([line]);
1028
+ if (utf8ByteLength(singleLineChunk) <= limit) {
1029
+ currentLines = [line];
1030
+ continue;
1031
+ }
1032
+ pushBaseChunks(chunks, singleLineChunk, limit, baseChunker);
1033
+ }
1034
+ if (currentLines.length > 0 || bodyLines.length === 0) chunks.push(render(currentLines));
1035
+ }
1036
+ function parseFenceLine(line) {
1037
+ const match = line.match(/^(\s*)(`{3,}|~{3,})/);
1038
+ if (!match?.[2]) return null;
1039
+ return {
1040
+ openLine: line,
1041
+ closeLine: `${match[1] ?? ""}${match[2]}`,
1042
+ marker: match[2]
1043
+ };
1044
+ }
1045
+ function isClosingFenceLine(line, fence) {
1046
+ const markerChar = fence.marker[0] === "`" ? "`" : "~";
1047
+ const match = line.match(/^(\s*)(`{3,}|~{3,})\s*$/);
1048
+ return Boolean(match?.[2] && match[2][0] === markerChar && match[2].length >= fence.marker.length);
1049
+ }
1050
+ //#endregion
652
1051
  //#region extensions/qqbot/src/group-policy.ts
653
1052
  function resolveQQBotGroupToolPolicy(params) {
654
1053
  return resolveChannelGroupToolsPolicy({
@@ -667,12 +1066,12 @@ function resolveQQBotGroupToolPolicy(params) {
667
1066
  //#region extensions/qqbot/src/channel.ts
668
1067
  let gatewayModulePromise;
669
1068
  function loadGatewayModule() {
670
- gatewayModulePromise ??= import("./gateway-CElyH-CJ.js");
1069
+ gatewayModulePromise ??= import("./gateway-CwDoxaZF.js");
671
1070
  return gatewayModulePromise;
672
1071
  }
673
1072
  let outboundMessagingModulePromise;
674
1073
  function loadOutboundMessagingModule() {
675
- outboundMessagingModulePromise ??= import("./outbound-C9wV892v.js").then((n) => n.t);
1074
+ outboundMessagingModulePromise ??= import("./outbound-DN8kX1G3.js").then((n) => n.t);
676
1075
  return outboundMessagingModulePromise;
677
1076
  }
678
1077
  function createQQBotSendReceipt(params) {
@@ -822,7 +1221,7 @@ const qqbotPlugin = {
822
1221
  },
823
1222
  outbound: {
824
1223
  deliveryMode: "direct",
825
- chunker: (text, limit) => getQQBotRuntime().channel.text.chunkMarkdownText(text, limit),
1224
+ chunker: (text, limit) => chunkQQBotMarkdownText(text, limit, getQQBotRuntime().channel.text.chunkMarkdownText),
826
1225
  chunkerMode: "markdown",
827
1226
  textChunkLimit: 5e3,
828
1227
  sanitizeText: ({ text }) => sanitizeAssistantVisibleText(text),
@@ -950,4 +1349,4 @@ const qqbotPlugin = {
950
1349
  }
951
1350
  };
952
1351
  //#endregion
953
- export { authorizeQQBotApprovalAction as a, resolveQQBotExecApprovalConfig as c, buildExecApprovalText as d, buildPluginApprovalText as f, toGatewayAccount as i, shouldHandleQQBotExecApprovalRequest as l, resolveApprovalTarget as m, buildQQBotStateKey as n, isQQBotExecApprovalClientEnabled as o, parseApprovalButtonData as p, openQQBotSyncKeyedStore as r, matchesQQBotApprovalAccount as s, qqbotPlugin as t, buildApprovalKeyboard as u };
1352
+ export { toGatewayAccount as a, matchesQQBotApprovalAccount as c, buildApprovalKeyboard as d, buildExecApprovalText as f, resolveApprovalTarget as h, openQQBotSyncKeyedStore as i, resolveQQBotExecApprovalConfig as l, parseApprovalButtonData as m, createQQBotMarkdownChunker as n, authorizeQQBotApprovalAction as o, buildPluginApprovalText as p, buildQQBotStateKey as r, isQQBotExecApprovalClientEnabled as s, qqbotPlugin as t, shouldHandleQQBotExecApprovalRequest as u };
@@ -1,7 +1,7 @@
1
1
  import { a as resolveQQBotAccount } from "./config-ZEfgeoL4.js";
2
2
  import { t as getFrameworkCommands } from "./slash-commands-impl-DVApdSop.js";
3
3
  import { t as registerQQBotTools } from "./tools-2d9t-N1b.js";
4
- import { h as sendDocument } from "./outbound-C9wV892v.js";
4
+ import { h as sendDocument } from "./outbound-DN8kX1G3.js";
5
5
  //#region extensions/qqbot/src/bridge/commands/framework-context-adapter.ts
6
6
  /**
7
7
  * Default queue snapshot used for framework-registered commands.
@@ -1,2 +1,2 @@
1
- import { t as registerQQBotFull } from "./channel-entry-CfzBEypJ.js";
1
+ import { t as registerQQBotFull } from "./channel-entry-D_eHXHMg.js";
2
2
  export { registerQQBotFull };
@@ -1,2 +1,2 @@
1
- import { t as qqbotPlugin } from "./channel-M0rnT4bN.js";
1
+ import { t as qqbotPlugin } from "./channel-91Y8ZcpJ.js";
2
2
  export { qqbotPlugin };
@@ -1,14 +1,14 @@
1
1
  import { C as getNextMsgSeq, D as downloadFile, F as StreamInputMode, I as StreamInputState, M as getMaxUploadSize, P as StreamContentType, T as openLocalFile, a as createRawInputNotifyFn, b as stopBackgroundTokenRefresh, c as getMessageApi, d as initSender, f as onMessageSent, g as sendText, h as sendMedia, i as clearTokenCache, j as getImageMimeType, k as formatFileSize, l as getPluginUserAgent, m as sendInputNotify, n as acknowledgeInteraction, o as getAccessToken, p as registerAccount, r as buildDeliveryTarget, s as getGatewayUrl, t as accountToCreds, u as initApiConfig, x as withTokenRetry, y as startBackgroundTokenRefresh } from "./sender-DIMG7jHz.js";
2
2
  import { c as getPlatformAdapter, i as normalizeOptionalString$1, n as normalizeLowercaseStringOrEmpty$1, o as readStringField, s as sanitizeFileName, t as asOptionalObjectRecord } from "./string-normalize-R_0cKO7Q.js";
3
3
  import { c as setBridgeLogger, o as ensurePlatformAdapter } from "./config-schema-BFLNZ8Nf.js";
4
- import { a as authorizeQQBotApprovalAction, i as toGatewayAccount, n as buildQQBotStateKey, p as parseApprovalButtonData, r as openQQBotSyncKeyedStore } from "./channel-M0rnT4bN.js";
4
+ import { a as toGatewayAccount, i as openQQBotSyncKeyedStore, m as parseApprovalButtonData, n as createQQBotMarkdownChunker, o as authorizeQQBotApprovalAction, r as buildQQBotStateKey } from "./channel-91Y8ZcpJ.js";
5
5
  import { d as resolveAccountBase } from "./config-ZEfgeoL4.js";
6
6
  import { a as formatErrorMessage, i as formatDuration, n as debugLog, r as debugWarn, t as debugError } from "./log-SDfMMBWe.js";
7
7
  import { n as getQQBotRuntimeForEngine, t as getQQBotRuntime } from "./runtime-B9UoQ5NI.js";
8
- import { a as getHomeDir, c as getQQBotMediaDir, d as isLocalPath, f as isWindows, i as checkSilkWasmAvailable, m as resolveQQBotPayloadLocalFilePath, o as getQQBotDataDir, p as normalizePath, s as getQQBotDataPath, u as getTempDir } from "./target-parser-BdCUmxK7.js";
8
+ import { a as getHomeDir, c as getQQBotMediaDir, d as isLocalPath, f as isWindows, i as checkSilkWasmAvailable, o as getQQBotDataDir, p as normalizePath, s as getQQBotDataPath, u as getTempDir } from "./target-parser-BdCUmxK7.js";
9
9
  import { a as matchSlashCommand, i as initCommands, n as getFrameworkVersion, r as getPluginVersion } from "./slash-commands-impl-DVApdSop.js";
10
10
  import { n as runWithRequestContext } from "./request-context-Bm7PTBD1.js";
11
- import { D as setOutboundAudioPort, T as DEFAULT_MEDIA_SEND_ERROR, _ as sendVideoMsg, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, l as isMediaPayload, o as decodeMediaPath, r as sendMedia$1, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, y as resolveUserFacingMediaError } from "./outbound-C9wV892v.js";
11
+ import { E as DEFAULT_MEDIA_SEND_ERROR, O as setOutboundAudioPort, _ as sendVideoMsg, b as resolveUserFacingMediaError, c as isCronReminderPayload, d as normalizeMediaTags, g as sendPhoto, h as sendDocument, l as isMediaPayload, o as decodeMediaPath, r as sendMedia$1, s as encodePayloadForCron, u as parseQQBotPayload, v as sendVoice, y as resolveTrustedOutboundMediaPath } from "./outbound-DN8kX1G3.js";
12
12
  import { asBoolean, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
13
13
  import { isImplicitSameChatApprovalAuthorization } from "openclaw/plugin-sdk/approval-auth-runtime";
14
14
  import * as fs$1 from "node:fs";
@@ -3997,7 +3997,7 @@ function formatMediaTypeLabel(mediaType) {
3997
3997
  return mediaType[0].toUpperCase() + mediaType.slice(1);
3998
3998
  }
3999
3999
  function validateStructuredPayloadLocalPath(ctx, payloadPath, mediaType) {
4000
- const allowedPath = resolveQQBotPayloadLocalFilePath(payloadPath);
4000
+ const allowedPath = resolveTrustedOutboundMediaPath(payloadPath);
4001
4001
  if (allowedPath) return allowedPath;
4002
4002
  ctx.log?.error(`Blocked ${mediaType} payload local path outside QQ Bot media storage`);
4003
4003
  return null;
@@ -5870,6 +5870,7 @@ async function dispatchOutbound(inbound, deps) {
5870
5870
  if (!hasResponse) reject(/* @__PURE__ */ new Error("Response timeout"));
5871
5871
  }, responseTimeoutMs);
5872
5872
  });
5873
+ const markdownChunker = createQQBotMarkdownChunker((text, limit) => runtime.channel.text.chunkMarkdownText(text, limit));
5873
5874
  const deliverDeps = {
5874
5875
  mediaSender: {
5875
5876
  sendPhoto: (target, imageUrl) => sendPhoto(target, imageUrl),
@@ -5878,7 +5879,30 @@ async function dispatchOutbound(inbound, deps) {
5878
5879
  sendDocument: (target, filePath) => sendDocument(target, filePath),
5879
5880
  sendMedia: (opts) => sendMedia$1(opts)
5880
5881
  },
5881
- chunkText: (text, limit) => runtime.channel.text.chunkMarkdownText(text, limit)
5882
+ chunkText: (text, limit) => markdownChunker.chunkText(text, limit)
5883
+ };
5884
+ const flushPendingMarkdownText = async () => {
5885
+ const pendingChunks = markdownChunker.flushPendingText(TEXT_CHUNK_LIMIT);
5886
+ if (pendingChunks.length === 0) return;
5887
+ const passthroughDeps = {
5888
+ ...deliverDeps,
5889
+ chunkText: (text) => [text]
5890
+ };
5891
+ for (const chunk of pendingChunks) {
5892
+ await sendTextOnlyReply(chunk, {
5893
+ type: event.type,
5894
+ senderId: event.senderId,
5895
+ messageId: event.messageId,
5896
+ channelId: event.channelId,
5897
+ groupOpenid: event.groupOpenid,
5898
+ msgIdx: event.msgIdx
5899
+ }, {
5900
+ account,
5901
+ qualifiedTarget,
5902
+ log
5903
+ }, sendWithRetry, () => void 0, passthroughDeps);
5904
+ recordOutbound();
5905
+ }
5882
5906
  };
5883
5907
  const replyDeps = { tts: {
5884
5908
  textToSpeech: (params) => runtime.tts.textToSpeech(params),
@@ -6140,6 +6164,7 @@ async function dispatchOutbound(inbound, deps) {
6140
6164
  toolFallbackSent = true;
6141
6165
  await sendToolFallback();
6142
6166
  }
6167
+ await flushPendingMarkdownText();
6143
6168
  if (streamingController && !streamingController.isTerminalPhase) try {
6144
6169
  streamingController.markFullyComplete();
6145
6170
  await streamingController.onIdle();
@@ -1,6 +1,6 @@
1
1
  import { c as getMessageApi, t as accountToCreds } from "./sender-DIMG7jHz.js";
2
2
  import { o as ensurePlatformAdapter, s as getBridgeLogger } from "./config-schema-BFLNZ8Nf.js";
3
- import { c as resolveQQBotExecApprovalConfig, d as buildExecApprovalText, f as buildPluginApprovalText, l as shouldHandleQQBotExecApprovalRequest, m as resolveApprovalTarget, o as isQQBotExecApprovalClientEnabled, s as matchesQQBotApprovalAccount, u as buildApprovalKeyboard } from "./channel-M0rnT4bN.js";
3
+ import { c as matchesQQBotApprovalAccount, d as buildApprovalKeyboard, f as buildExecApprovalText, h as resolveApprovalTarget, l as resolveQQBotExecApprovalConfig, p as buildPluginApprovalText, s as isQQBotExecApprovalClientEnabled, u as shouldHandleQQBotExecApprovalRequest } from "./channel-91Y8ZcpJ.js";
4
4
  import { a as resolveQQBotAccount } from "./config-ZEfgeoL4.js";
5
5
  import { resolveApprovalRequestSessionConversation } from "openclaw/plugin-sdk/approval-native-runtime";
6
6
  import { createChannelApprovalNativeRuntimeAdapter } from "openclaw/plugin-sdk/approval-handler-runtime";
@@ -5,6 +5,7 @@ import { c as getQQBotMediaDir, d as isLocalPath, m as resolveQQBotPayloadLocalF
5
5
  import { pathExistsSync, resolveLocalPathFromRootsSync } from "openclaw/plugin-sdk/security-runtime";
6
6
  import path from "node:path";
7
7
  import { getFileExtension } from "openclaw/plugin-sdk/media-mime";
8
+ import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/sandbox";
8
9
  //#region extensions/qqbot/src/engine/messaging/outbound-audio-port.ts
9
10
  let outboundAudioPort = null;
10
11
  /**
@@ -193,6 +194,47 @@ function buildFileTooLargeResult(fileType, fileSize) {
193
194
  };
194
195
  }
195
196
  //#endregion
197
+ //#region extensions/qqbot/src/engine/messaging/trusted-media-path.ts
198
+ let cachedTrustedTmpRoot;
199
+ function trustedOpenClawTmpRoot() {
200
+ if (cachedTrustedTmpRoot === void 0) try {
201
+ cachedTrustedTmpRoot = resolvePreferredOpenClawTmpDir();
202
+ } catch {
203
+ return null;
204
+ }
205
+ return cachedTrustedTmpRoot;
206
+ }
207
+ /**
208
+ * Resolve a local outbound media path against every trusted root, returning the
209
+ * canonical path or null when it sits outside all of them.
210
+ *
211
+ * QQBot is the only channel that root-sandboxes outbound local files, and the
212
+ * same check runs at three sites (`resolveOutboundMediaPath`, the voice send
213
+ * re-check, and structured-payload validation), so they must all agree or a file
214
+ * accepted at one gate is rejected at the next. Beyond the QQ Bot media storage
215
+ * roots, this also trusts OpenClaw's permission-hardened temp root, where
216
+ * framework scratch media is written (e.g. cron auto-TTS voice files). Core
217
+ * already treats that temp root as a sanctioned media root (`buildMediaLocalRoots`);
218
+ * without it here, auto-routed sends are dropped and cron delivery silently loses
219
+ * the message.
220
+ *
221
+ * `allowMissing` lets callers accept a not-yet-flushed temp file (e.g. TTS still
222
+ * writing) under the temp root; existence is then enforced later by the voice
223
+ * send re-check before upload.
224
+ */
225
+ function resolveTrustedOutboundMediaPath(p, options = {}) {
226
+ const storageRootPath = resolveQQBotPayloadLocalFilePath(p);
227
+ if (storageRootPath) return storageRootPath;
228
+ const tmpRoot = trustedOpenClawTmpRoot();
229
+ if (!tmpRoot) return null;
230
+ return resolveLocalPathFromRootsSync({
231
+ filePath: p,
232
+ roots: [tmpRoot],
233
+ label: "OpenClaw temp media root",
234
+ allowMissing: options.allowMissing === true
235
+ })?.path ?? null;
236
+ }
237
+ //#endregion
196
238
  //#region extensions/qqbot/src/engine/messaging/outbound-media-send.ts
197
239
  /**
198
240
  * Low-level outbound media sends (photo, voice, video, document) and path resolution.
@@ -252,7 +294,7 @@ function resolveOutboundMediaPath(rawPath, mediaKind, options = {}) {
252
294
  ok: true,
253
295
  mediaPath: normalizedPath
254
296
  };
255
- const allowedPath = resolveQQBotPayloadLocalFilePath(normalizedPath);
297
+ const allowedPath = resolveTrustedOutboundMediaPath(normalizedPath, { allowMissing: options.allowMissingLocalPath });
256
298
  if (allowedPath) return {
257
299
  ok: true,
258
300
  mediaPath: allowedPath
@@ -462,7 +504,7 @@ async function sendVoiceFromLocal(ctx, mediaPath, directUploadFormats, transcode
462
504
  error: "Voice generate failed"
463
505
  };
464
506
  if (fileSize > getMaxUploadSize(3)) return buildFileTooLargeResult(3, fileSize);
465
- const safeMediaPath = resolveQQBotPayloadLocalFilePath(mediaPath);
507
+ const safeMediaPath = resolveTrustedOutboundMediaPath(mediaPath);
466
508
  if (!safeMediaPath) {
467
509
  debugWarn(`sendVoice: blocked local voice path outside QQ Bot media storage`);
468
510
  return {
@@ -1368,4 +1410,4 @@ async function sendCronMessage(account, to, message) {
1368
1410
  });
1369
1411
  }
1370
1412
  //#endregion
1371
- export { getMessageReplyStats as C, setOutboundAudioPort as D, OUTBOUND_ERROR_CODES as E, getMessageReplyConfig as S, DEFAULT_MEDIA_SEND_ERROR as T, sendVideoMsg as _, sendText as a, MESSAGE_REPLY_LIMIT as b, isCronReminderPayload as c, normalizeMediaTags as d, buildMediaTarget as f, sendPhoto as g, sendDocument as h, sendProactiveMessage as i, isMediaPayload as l, resolveOutboundMediaPath as m, sendCronMessage as n, decodeMediaPath as o, parseTarget as p, sendMedia as r, encodePayloadForCron as s, outbound_exports as t, parseQQBotPayload as u, sendVoice as v, recordMessageReply as w, checkMessageReplyLimit as x, resolveUserFacingMediaError as y };
1413
+ export { getMessageReplyConfig as C, OUTBOUND_ERROR_CODES as D, DEFAULT_MEDIA_SEND_ERROR as E, setOutboundAudioPort as O, checkMessageReplyLimit as S, recordMessageReply as T, sendVideoMsg as _, sendText as a, resolveUserFacingMediaError as b, isCronReminderPayload as c, normalizeMediaTags as d, buildMediaTarget as f, sendPhoto as g, sendDocument as h, sendProactiveMessage as i, isMediaPayload as l, resolveOutboundMediaPath as m, sendCronMessage as n, decodeMediaPath as o, parseTarget as p, sendMedia as r, encodePayloadForCron as s, outbound_exports as t, parseQQBotPayload as u, sendVoice as v, getMessageReplyStats as w, MESSAGE_REPLY_LIMIT as x, resolveTrustedOutboundMediaPath as y };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@openclaw/qqbot",
3
- "version": "2026.6.8-beta.2",
3
+ "version": "2026.6.9-beta.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@openclaw/qqbot",
9
- "version": "2026.6.8-beta.2",
9
+ "version": "2026.6.9-beta.1",
10
10
  "dependencies": {
11
11
  "@tencent-connect/qqbot-connector": "1.1.0",
12
12
  "mpg123-decoder": "1.0.3",
@@ -15,7 +15,7 @@
15
15
  "zod": "4.4.3"
16
16
  },
17
17
  "peerDependencies": {
18
- "openclaw": ">=2026.6.8-beta.2"
18
+ "openclaw": ">=2026.6.9-beta.1"
19
19
  },
20
20
  "peerDependenciesMeta": {
21
21
  "openclaw": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/qqbot",
3
- "version": "2026.6.8-beta.2",
3
+ "version": "2026.6.9-beta.1",
4
4
  "private": false,
5
5
  "description": "OpenClaw QQ Bot channel plugin for group and direct-message workflows.",
6
6
  "repository": {
@@ -16,7 +16,7 @@
16
16
  "zod": "4.4.3"
17
17
  },
18
18
  "peerDependencies": {
19
- "openclaw": ">=2026.6.8-beta.2"
19
+ "openclaw": ">=2026.6.9-beta.1"
20
20
  },
21
21
  "peerDependenciesMeta": {
22
22
  "openclaw": {
@@ -45,10 +45,10 @@
45
45
  "minHostVersion": ">=2026.4.10"
46
46
  },
47
47
  "compat": {
48
- "pluginApi": ">=2026.6.8-beta.2"
48
+ "pluginApi": ">=2026.6.9-beta.1"
49
49
  },
50
50
  "build": {
51
- "openclawVersion": "2026.6.8-beta.2"
51
+ "openclawVersion": "2026.6.9-beta.1"
52
52
  },
53
53
  "release": {
54
54
  "publishToClawHub": true,