Skip to content

Commit 6b04521

Browse files
authored
Merge branch 'main' into fix/gateway-tools-invoke-rpc
2 parents 2fe48ba + 7d7b610 commit 6b04521

20 files changed

Lines changed: 122 additions & 356 deletions

File tree

extensions/qqbot/src/engine/config/credentials.ts

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -74,47 +74,3 @@ export function clearAccountCredentials(
7474

7575
return { nextCfg, cleared, changed };
7676
}
77-
78-
// ---- Setup: clear a single credential field ----
79-
80-
export type CredentialField = "appId" | "clientSecret";
81-
82-
/**
83-
* Clear a single credential field from a QQBot account config.
84-
*
85-
* Used by setup flows when switching to env-backed credential resolution.
86-
* Returns a new config with the specified field removed.
87-
*/
88-
export function clearCredentialField(
89-
cfg: Record<string, unknown>,
90-
accountId: string,
91-
field: CredentialField,
92-
): Record<string, unknown> {
93-
const next = { ...cfg };
94-
const channels = asRecord(cfg.channels);
95-
const qqbot = { ...asRecord(channels?.qqbot) };
96-
97-
const clearField = (entry: Record<string, unknown>) => {
98-
if (field === "appId") {
99-
delete entry.appId;
100-
return;
101-
}
102-
delete entry.clientSecret;
103-
delete entry.clientSecretFile;
104-
};
105-
106-
if (accountId === DEFAULT_ACCOUNT_ID) {
107-
clearField(qqbot);
108-
} else {
109-
const accounts = { ...(qqbot.accounts as Record<string, Record<string, unknown>> | undefined) };
110-
if (accounts[accountId]) {
111-
const entry = { ...accounts[accountId] };
112-
clearField(entry);
113-
accounts[accountId] = entry;
114-
qqbot.accounts = accounts;
115-
}
116-
}
117-
118-
next.channels = { ...channels, qqbot };
119-
return next;
120-
}

extensions/qqbot/src/engine/messaging/media-source.ts

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -213,43 +213,3 @@ export async function normalizeSource(
213213
mime: raw.mime,
214214
};
215215
}
216-
217-
// ============ Materialization helpers ============
218-
219-
/**
220-
* Read a {@link MediaSource} into the `{ url?, fileData?, fileName? }` shape
221-
* expected by {@link MediaApi.uploadMedia} today (one-shot upload path).
222-
*
223-
* Chunked upload (future) should bypass this helper and feed the uploader
224-
* directly from the `localPath` / `buffer` branch.
225-
*/
226-
export async function materializeForOneShotUpload(
227-
source: MediaSource,
228-
): Promise<{ url?: string; fileData?: string; fileName?: string }> {
229-
switch (source.kind) {
230-
case "url":
231-
return { url: source.url };
232-
case "base64":
233-
return { fileData: source.data };
234-
case "localPath": {
235-
const opened = await openLocalFile(source.path);
236-
try {
237-
const buf = await opened.handle.readFile();
238-
return { fileData: buf.toString("base64") };
239-
} finally {
240-
await opened.close();
241-
}
242-
}
243-
case "buffer":
244-
return {
245-
fileData: source.buffer.toString("base64"),
246-
fileName: source.fileName,
247-
};
248-
default: {
249-
const _exhaustive: never = source;
250-
throw new Error(
251-
`materializeForOneShotUpload: unsupported MediaSource kind: ${JSON.stringify(_exhaustive)}`,
252-
);
253-
}
254-
}
255-
}

extensions/qqbot/src/engine/messaging/media-type-detect.ts

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,6 @@
99
/** Supported media kind for QQ Bot outbound routing. */
1010
export type MediaKind = "image" | "voice" | "video" | "file";
1111

12-
/** Display labels for media kinds. */
13-
export const MEDIA_KIND_LABELS: Record<MediaKind | "media", string> = {
14-
image: "Image",
15-
voice: "Voice",
16-
video: "Video",
17-
file: "File",
18-
media: "Media",
19-
};
20-
2112
const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"]);
2213
const VIDEO_EXTENSIONS = new Set([".mp4", ".mov", ".avi", ".mkv", ".webm", ".flv", ".wmv"]);
2314
const AUDIO_EXTENSIONS = new Set([
@@ -77,46 +68,3 @@ export function isAudioFile(filePath: string, mimeType?: string): boolean {
7768
}
7869
return AUDIO_EXTENSIONS.has(getCleanExtension(filePath));
7970
}
80-
81-
/**
82-
* Auto-detect the media kind from a file path and optional MIME type.
83-
*
84-
* Priority: audio → video → image → file (default).
85-
*/
86-
export function detectMediaKind(filePath: string, mimeType?: string): MediaKind {
87-
if (isAudioFile(filePath, mimeType)) {
88-
return "voice";
89-
}
90-
if (isVideoFile(filePath, mimeType)) {
91-
return "video";
92-
}
93-
if (isImageFile(filePath, mimeType)) {
94-
return "image";
95-
}
96-
return "file";
97-
}
98-
99-
/** Return true when the source is a remote HTTP(S) URL. */
100-
export function isHttpSource(source: string): boolean {
101-
return source.startsWith("http://") || source.startsWith("https://");
102-
}
103-
104-
/** Return true when the source is a Base64 data URL. */
105-
export function isDataSource(source: string): boolean {
106-
return source.startsWith("data:");
107-
}
108-
109-
/** Return true when the source is a remote URL or data URL. */
110-
export function isRemoteOrDataSource(source: string): boolean {
111-
return isHttpSource(source) || isDataSource(source);
112-
}
113-
114-
/** Common MIME type mapping for image extensions. */
115-
export const IMAGE_MIME_TYPES: Record<string, string> = {
116-
".jpg": "image/jpeg",
117-
".jpeg": "image/jpeg",
118-
".png": "image/png",
119-
".gif": "image/gif",
120-
".webp": "image/webp",
121-
".bmp": "image/bmp",
122-
};

extensions/qqbot/src/engine/messaging/streaming-media-send.ts

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -363,47 +363,6 @@ export function splitByMediaTags(
363363
};
364364
}
365365

366-
/**
367-
* 从文本中解析出完整的发送队列(含标签前后的纯文本)
368-
*
369-
* 与 splitByMediaTags 的区别:
370-
* - splitByMediaTags 分为 before / queue / after 三段(供流式模式的中断-恢复)
371-
* - parseMediaTagsToSendQueue 返回一个扁平的完整队列(供普通模式按顺序发送)
372-
*
373-
* 适用于 gateway.ts deliver 回调和 outbound.ts sendText。
374-
*/
375-
export function parseMediaTagsToSendQueue(
376-
text: string,
377-
log?: {
378-
info?: (msg: string) => void;
379-
debug?: (msg: string) => void;
380-
error?: (msg: string) => void;
381-
},
382-
): { hasMediaTags: boolean; sendQueue: SendQueueItem[] } {
383-
const split = splitByMediaTags(text, log);
384-
385-
if (!split.hasMediaTags) {
386-
return { hasMediaTags: false, sendQueue: [] };
387-
}
388-
389-
const sendQueue: SendQueueItem[] = [];
390-
391-
// 标签前的文本
392-
if (split.textBeforeFirstTag) {
393-
sendQueue.push({ type: "text", content: split.textBeforeFirstTag });
394-
}
395-
396-
// 媒体队列(含标签间文本)
397-
sendQueue.push(...split.mediaQueue);
398-
399-
// 标签后的文本
400-
if (split.textAfterLastTag) {
401-
sendQueue.push({ type: "text", content: split.textAfterLastTag });
402-
}
403-
404-
return { hasMediaTags: true, sendQueue };
405-
}
406-
407366
// ============ 发送队列执行 ============
408367

409368
/**
@@ -527,17 +486,6 @@ export async function executeSendQueue(
527486
}
528487
}
529488

530-
/**
531-
* 从文本中剥离所有媒体标签(用于最终显示)
532-
*/
533-
export function stripMediaTags(text: string): string {
534-
const regex = createMediaTagRegex();
535-
return text
536-
.replace(regex, "")
537-
.replace(/\n{3,}/g, "\n\n")
538-
.trim();
539-
}
540-
541489
/**
542490
* 检测文本中是否有未闭合的媒体标签,如果有则截断到安全位置。
543491
*

extensions/qqbot/src/engine/utils/file-utils.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,17 +107,6 @@ export async function fileExistsAsync(filePath: string): Promise<boolean> {
107107
}
108108
}
109109

110-
/** Get file size asynchronously. */
111-
export async function getFileSizeAsync(filePath: string): Promise<number> {
112-
const stat = await fs.promises.stat(filePath);
113-
return stat.size;
114-
}
115-
116-
/** Return true when a file should be treated as large. */
117-
export function isLargeFile(sizeBytes: number): boolean {
118-
return sizeBytes >= LARGE_FILE_THRESHOLD;
119-
}
120-
121110
/** Format a byte count into a human-readable size string. */
122111
export function formatFileSize(bytes: number): string {
123112
if (bytes < 1024) {

extensions/qqbot/src/engine/utils/image-size.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -247,12 +247,3 @@ export function formatQQBotMarkdownImage(url: string, size: ImageSize | null): s
247247
export function hasQQBotImageSize(markdownImage: string): boolean {
248248
return /!\[#\d+px\s+#\d+px\]/.test(markdownImage);
249249
}
250-
251-
/** Extract width and height from QQBot markdown image syntax: `![#Wpx #Hpx](url)`. */
252-
export function extractQQBotImageSize(markdownImage: string): ImageSize | null {
253-
const match = markdownImage.match(/!\[#(\d+)px\s+#(\d+)px\]/);
254-
if (match) {
255-
return { width: Number.parseInt(match[1], 10), height: Number.parseInt(match[2], 10) };
256-
}
257-
return null;
258-
}

extensions/qqbot/src/engine/utils/platform.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -185,12 +185,6 @@ function testExecutable(cmd: string, args: string[]): Promise<boolean> {
185185
});
186186
}
187187

188-
/** Reset ffmpeg detection state, mainly for tests. */
189-
export function resetFfmpegCache(): void {
190-
_ffmpegPath = undefined;
191-
_ffmpegCheckPromise = null;
192-
}
193-
194188
// ---- silk-wasm detection ----
195189

196190
let _silkWasmAvailable: boolean | null = null;
@@ -273,14 +267,6 @@ export function isLocalPath(p: string): boolean {
273267
return false;
274268
}
275269

276-
/** Looser local-path heuristic used for markdown-extracted paths. */
277-
export function looksLikeLocalPath(p: string): boolean {
278-
if (isLocalPath(p)) {
279-
return true;
280-
}
281-
return /^(?:Users|home|tmp|var|private|[A-Z]:)/i.test(p);
282-
}
283-
284270
// ---- QQBot media path resolution ----
285271

286272
function isPathWithinRoot(candidate: string, root: string): boolean {

extensions/qqbot/src/engine/utils/request-context.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,3 @@ export function runWithRequestContext<T>(ctx: RequestContext, fn: () => T): T {
5858
export function getRequestContext(): RequestContext | undefined {
5959
return store.getStore();
6060
}
61-
62-
/**
63-
* Convenience accessor for the current request's fully qualified
64-
* delivery target.
65-
*/
66-
export function getRequestTarget(): string | undefined {
67-
return store.getStore()?.target;
68-
}
69-
70-
/**
71-
* Convenience accessor for the current request's account ID.
72-
*/
73-
export function getRequestAccountId(): string | undefined {
74-
return store.getStore()?.accountId;
75-
}

extensions/qqbot/src/engine/utils/upload-cache.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,3 @@ export function setCachedFileInfo(
9494
`[upload-cache] Cache SET: key=${key.slice(0, 40)}..., ttl=${effectiveTtl}s, uuid=${fileUuid}`,
9595
);
9696
}
97-
98-
/** Return cache stats for diagnostics. */
99-
export function getUploadCacheStats(): { size: number; maxSize: number } {
100-
return { size: cache.size, maxSize: MAX_CACHE_SIZE };
101-
}
102-
103-
/** Clear the upload cache. */
104-
export function clearUploadCache(): void {
105-
cache.clear();
106-
debugLog(`[upload-cache] Cache cleared`);
107-
}

extensions/slack/src/monitor/channel-config.ts

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ import {
44
resolveChannelEntryMatchWithFallback,
55
type ChannelMatchSource,
66
} from "openclaw/plugin-sdk/channel-targets";
7-
import type { SlackReactionNotificationMode } from "openclaw/plugin-sdk/config-types";
87
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime";
98
import type { SlackMessageEvent } from "../types.js";
10-
import { allowListMatches, normalizeAllowListLower, normalizeSlackSlug } from "./allow-list.js";
9+
import { normalizeSlackSlug } from "./allow-list.js";
1110

1211
export type SlackChannelConfigResolved = {
1312
allowed: boolean;
@@ -40,41 +39,6 @@ function firstDefined<T>(...values: Array<T | undefined>) {
4039
return undefined;
4140
}
4241

43-
export function shouldEmitSlackReactionNotification(params: {
44-
mode: SlackReactionNotificationMode | undefined;
45-
botId?: string | null;
46-
messageAuthorId?: string | null;
47-
userId: string;
48-
userName?: string | null;
49-
allowlist?: Array<string | number> | null;
50-
allowNameMatching?: boolean;
51-
}) {
52-
const { mode, botId, messageAuthorId, userId, userName, allowlist } = params;
53-
const effectiveMode = mode ?? "own";
54-
if (effectiveMode === "off") {
55-
return false;
56-
}
57-
if (effectiveMode === "own") {
58-
if (!botId || !messageAuthorId) {
59-
return false;
60-
}
61-
return messageAuthorId === botId;
62-
}
63-
if (effectiveMode === "allowlist") {
64-
if (!Array.isArray(allowlist) || allowlist.length === 0) {
65-
return false;
66-
}
67-
const users = normalizeAllowListLower(allowlist);
68-
return allowListMatches({
69-
allowList: users,
70-
id: userId,
71-
name: userName ?? undefined,
72-
allowNameMatching: params.allowNameMatching,
73-
});
74-
}
75-
return true;
76-
}
77-
7842
export function resolveSlackChannelLabel(params: { channelId?: string; channelName?: string }) {
7943
const channelName = params.channelName?.trim();
8044
if (channelName) {

0 commit comments

Comments
 (0)