openclaw-plugin-yuanbao 2.7.2 → 2.8.0
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/openclaw.plugin.json +1 -1
- package/dist/src/channel.js +4 -9
- package/dist/src/config-schema.js +0 -7
- package/dist/src/dm/send-dm.js +0 -1
- package/dist/src/logger.d.ts +2 -7
- package/dist/src/logger.js +25 -49
- package/dist/src/message-handler/callbacks/recall.js +2 -2
- package/dist/src/message-handler/context.d.ts +0 -6
- package/dist/src/message-handler/handlers/custom.js +1 -1
- package/dist/src/message-handler/handlers/index.js +6 -4
- package/dist/src/message-handler/inbound.js +2 -2
- package/dist/src/message-handler/outbound.js +8 -4
- package/dist/src/message-tool/action-runtime.js +0 -1
- package/dist/src/types.d.ts +7 -1
- package/dist/src/utils/markdown-table-sanitize.d.ts +1 -0
- package/dist/src/utils/markdown-table-sanitize.js +89 -0
- package/dist/src/yuanbao-server/http/main.d.ts +3 -3
- package/dist/src/yuanbao-server/http/main.js +4 -4
- package/dist/src/yuanbao-server/http/request.d.ts +5 -5
- package/dist/src/yuanbao-server/http/request.js +23 -23
- package/dist/src/yuanbao-server/ws/client.d.ts +0 -2
- package/dist/src/yuanbao-server/ws/client.js +1 -1
- package/dist/src/yuanbao-server/ws/gateway.d.ts +1 -8
- package/dist/src/yuanbao-server/ws/gateway.js +25 -39
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/src/channel.js
CHANGED
|
@@ -3,7 +3,7 @@ import { formatPairingApproveHint } from 'openclaw/plugin-sdk/mattermost';
|
|
|
3
3
|
import { listYuanbaoAccountIds, resolveDefaultYuanbaoAccountId, resolveYuanbaoAccount } from './accounts.js';
|
|
4
4
|
import { yuanbaoConfigSchema } from './config-schema.js';
|
|
5
5
|
import { yuanbaoOnboardingAdapter } from './onboarding.js';
|
|
6
|
-
import { createLog
|
|
6
|
+
import { createLog } from './logger.js';
|
|
7
7
|
import { yuanbaoSetupAdapter } from './setup.js';
|
|
8
8
|
import { startYuanbaoWsGateway, getActiveWsClient } from './yuanbao-server/ws/index.js';
|
|
9
9
|
import { getYuanbaoRuntime } from './runtime.js';
|
|
@@ -180,7 +180,7 @@ export const yuanbaoPlugin = {
|
|
|
180
180
|
const { cfg, accountId, to: _to, text } = params;
|
|
181
181
|
const to = _to.replace(/^yuanbao:/, '');
|
|
182
182
|
const account = resolveYuanbaoAccount({ cfg, accountId: accountId ?? undefined });
|
|
183
|
-
const slog = createLog('channel.outbound'
|
|
183
|
+
const slog = createLog('channel.outbound');
|
|
184
184
|
slog.info('sendText', { accountId, to });
|
|
185
185
|
const wsClient = getActiveWsClient(account.accountId);
|
|
186
186
|
if (!wsClient) {
|
|
@@ -206,7 +206,7 @@ export const yuanbaoPlugin = {
|
|
|
206
206
|
const { cfg, accountId, to: _to, mediaUrl, text, mediaLocalRoots } = params;
|
|
207
207
|
const to = _to.replace(/^yuanbao:/, '');
|
|
208
208
|
const account = resolveYuanbaoAccount({ cfg, accountId: accountId ?? undefined });
|
|
209
|
-
const slog = createLog('channel.outbound'
|
|
209
|
+
const slog = createLog('channel.outbound');
|
|
210
210
|
const wsClient = getActiveWsClient(account.accountId);
|
|
211
211
|
slog.info('sendMedia', { accountId, to, mediaUrl, text });
|
|
212
212
|
if (!wsClient) {
|
|
@@ -270,11 +270,7 @@ export const yuanbaoPlugin = {
|
|
|
270
270
|
gateway: {
|
|
271
271
|
startAccount: async (ctx) => {
|
|
272
272
|
const { account } = ctx;
|
|
273
|
-
const
|
|
274
|
-
if (yuanbaoTopConfig?.debugBotIds?.length) {
|
|
275
|
-
setDebugBotIds(yuanbaoTopConfig.debugBotIds);
|
|
276
|
-
}
|
|
277
|
-
const slog = createLog('gateway', ctx.log, { botId: account.botId });
|
|
273
|
+
const slog = createLog('gateway');
|
|
278
274
|
slog.debug('启动账号', account);
|
|
279
275
|
if (!account.configured) {
|
|
280
276
|
slog.warn('yuanbao not configured; skipping');
|
|
@@ -300,7 +296,6 @@ export const yuanbaoPlugin = {
|
|
|
300
296
|
account,
|
|
301
297
|
config: ctx.cfg,
|
|
302
298
|
abortSignal: ctx.abortSignal,
|
|
303
|
-
log: ctx.log,
|
|
304
299
|
runtime: getYuanbaoRuntime(),
|
|
305
300
|
statusSink: patch => ctx.setStatus({ accountId: ctx.accountId, ...patch }),
|
|
306
301
|
});
|
|
@@ -98,13 +98,6 @@ export const yuanbaoConfigSchema = {
|
|
|
98
98
|
description: '开启后在系统提示词中自动注入指令,防止模型用代码块包裹整个 Markdown 回复',
|
|
99
99
|
default: true,
|
|
100
100
|
},
|
|
101
|
-
debugBotIds: {
|
|
102
|
-
type: 'array',
|
|
103
|
-
items: { type: 'string' },
|
|
104
|
-
title: '调试白名单 Bot ID',
|
|
105
|
-
description: '白名单内的 Bot ID 日志输出不做脱敏处理,方便开发调试。填写 bot 的 IM 用户 ID',
|
|
106
|
-
default: [],
|
|
107
|
-
},
|
|
108
101
|
},
|
|
109
102
|
additionalProperties: false,
|
|
110
103
|
},
|
package/dist/src/dm/send-dm.js
CHANGED
package/dist/src/logger.d.ts
CHANGED
|
@@ -8,9 +8,6 @@ export interface PluginLogger {
|
|
|
8
8
|
}
|
|
9
9
|
export declare function initLogger(api: OpenClawPluginApi): void;
|
|
10
10
|
export declare const logger: PluginLogger;
|
|
11
|
-
export declare function isVerbose(): boolean;
|
|
12
|
-
export declare function setDebugBotIds(ids: string[]): void;
|
|
13
|
-
export declare function isDebugBotId(botId?: string): boolean;
|
|
14
11
|
export interface LogSink {
|
|
15
12
|
info?: (msg: string) => void;
|
|
16
13
|
warn?: (msg: string) => void;
|
|
@@ -24,10 +21,8 @@ export interface ModuleLog {
|
|
|
24
21
|
error(msg: string, data?: Record<string, unknown>): void;
|
|
25
22
|
debug(msg: string, data?: Record<string, unknown>): void;
|
|
26
23
|
}
|
|
27
|
-
export declare function formatLog(module: string, msg: string, data?: Record<string, unknown
|
|
28
|
-
export declare function createLog(module: string, sink?: LogSink
|
|
29
|
-
botId?: string;
|
|
30
|
-
}): ModuleLog;
|
|
24
|
+
export declare function formatLog(module: string, msg: string, data?: Record<string, unknown>): string;
|
|
25
|
+
export declare function createLog(module: string, sink?: LogSink): ModuleLog;
|
|
31
26
|
export declare function sanitize(value: unknown): string;
|
|
32
27
|
export declare function logSimple(level: 'info' | 'warn' | 'error', message: string): void;
|
|
33
28
|
export declare function logDebug(message: string): void;
|
package/dist/src/logger.js
CHANGED
|
@@ -2,7 +2,22 @@ import { getPluginVersion } from './utils/get-env.js';
|
|
|
2
2
|
export const LOG_PREFIX = `[yuanbao:${getPluginVersion()}]`;
|
|
3
3
|
let childLogger = null;
|
|
4
4
|
let initialized = false;
|
|
5
|
-
let
|
|
5
|
+
let debugMode = false;
|
|
6
|
+
const SANITIZE_RULES = {
|
|
7
|
+
always: {
|
|
8
|
+
omit: [],
|
|
9
|
+
mask: [
|
|
10
|
+
'token', 'signature', 'app_key', 'appkey',
|
|
11
|
+
'appsecret', 'app_secret', 'secret', 'password', 'x-token',
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
debug: {
|
|
15
|
+
omit: ['msg_body'],
|
|
16
|
+
mask: ['user_input', 'cloud_custom_data', 'model_output'],
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
let OMIT_KEYS = new Set([...SANITIZE_RULES.always.omit, ...SANITIZE_RULES.debug.omit]);
|
|
20
|
+
let SENSITIVE_KEYS = new Set([...SANITIZE_RULES.always.mask, ...SANITIZE_RULES.debug.mask]);
|
|
6
21
|
const fallbackLogger = {
|
|
7
22
|
info(message, meta) {
|
|
8
23
|
console.log(`${LOG_PREFIX} ${message}`, meta ?? '');
|
|
@@ -19,8 +34,12 @@ const fallbackLogger = {
|
|
|
19
34
|
};
|
|
20
35
|
export function initLogger(api) {
|
|
21
36
|
try {
|
|
37
|
+
debugMode = api.config.channels?.yuanbao?.debug === true;
|
|
38
|
+
if (debugMode) {
|
|
39
|
+
OMIT_KEYS = new Set(SANITIZE_RULES.always.omit);
|
|
40
|
+
SENSITIVE_KEYS = new Set(SANITIZE_RULES.always.mask);
|
|
41
|
+
}
|
|
22
42
|
childLogger = api.runtime.logging.getChildLogger({ plugin: 'yuanbao' });
|
|
23
|
-
verboseEnabled = api.runtime.logging.shouldLogVerbose?.() ?? false;
|
|
24
43
|
initialized = true;
|
|
25
44
|
}
|
|
26
45
|
catch (err) {
|
|
@@ -52,44 +71,16 @@ export const logger = {
|
|
|
52
71
|
getActiveLogger().debug(message, meta);
|
|
53
72
|
},
|
|
54
73
|
};
|
|
55
|
-
export function
|
|
56
|
-
return verboseEnabled;
|
|
57
|
-
}
|
|
58
|
-
function parseEnvDebugBotIds() {
|
|
59
|
-
const raw = process.env.YUANBAO_DEBUG_BOT_IDS;
|
|
60
|
-
if (!raw)
|
|
61
|
-
return [];
|
|
62
|
-
return raw.split(',').map(s => s.trim())
|
|
63
|
-
.filter(Boolean);
|
|
64
|
-
}
|
|
65
|
-
const debugBotIds = new Set(parseEnvDebugBotIds());
|
|
66
|
-
export function setDebugBotIds(ids) {
|
|
67
|
-
debugBotIds.clear();
|
|
68
|
-
for (const id of parseEnvDebugBotIds())
|
|
69
|
-
debugBotIds.add(id);
|
|
70
|
-
for (const id of ids) {
|
|
71
|
-
const trimmed = id.trim();
|
|
72
|
-
if (trimmed)
|
|
73
|
-
debugBotIds.add(trimmed);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
export function isDebugBotId(botId) {
|
|
77
|
-
if (!botId)
|
|
78
|
-
return false;
|
|
79
|
-
return debugBotIds.has(botId);
|
|
80
|
-
}
|
|
81
|
-
export function formatLog(module, msg, data, skipSanitize) {
|
|
74
|
+
export function formatLog(module, msg, data) {
|
|
82
75
|
const prefix = module ? `${LOG_PREFIX}[${module}]` : LOG_PREFIX;
|
|
83
76
|
if (data === undefined)
|
|
84
77
|
return `${prefix} ${msg}`;
|
|
85
|
-
|
|
86
|
-
return `${prefix} ${msg} ${serialized}`;
|
|
78
|
+
return `${prefix} ${msg} ${sanitize(data)}`;
|
|
87
79
|
}
|
|
88
|
-
export function createLog(module, sink
|
|
80
|
+
export function createLog(module, sink) {
|
|
89
81
|
const target = sink ?? logger;
|
|
90
|
-
const skipSanitize = isDebugBotId(options?.botId);
|
|
91
82
|
function fmt(msg, data) {
|
|
92
|
-
return formatLog(module, msg, data
|
|
83
|
+
return formatLog(module, msg, data);
|
|
93
84
|
}
|
|
94
85
|
return {
|
|
95
86
|
info: (msg, data) => target.info?.(fmt(msg, data)),
|
|
@@ -98,21 +89,6 @@ export function createLog(module, sink, options) {
|
|
|
98
89
|
debug: (msg, data) => (target.debug ?? target.verbose)?.(fmt(msg, data)),
|
|
99
90
|
};
|
|
100
91
|
}
|
|
101
|
-
const OMIT_KEYS = new Set(['msg_body']);
|
|
102
|
-
const SENSITIVE_KEYS = new Set([
|
|
103
|
-
'token',
|
|
104
|
-
'signature',
|
|
105
|
-
'app_key',
|
|
106
|
-
'appkey',
|
|
107
|
-
'appsecret',
|
|
108
|
-
'app_secret',
|
|
109
|
-
'secret',
|
|
110
|
-
'password',
|
|
111
|
-
'x-token',
|
|
112
|
-
'user_input',
|
|
113
|
-
'cloud_custom_data',
|
|
114
|
-
'model_output',
|
|
115
|
-
]);
|
|
116
92
|
function maskValue(value) {
|
|
117
93
|
if (value.length < 8)
|
|
118
94
|
return '***';
|
|
@@ -13,7 +13,7 @@ function enqueueRecallSystemEvent(params) {
|
|
|
13
13
|
}
|
|
14
14
|
export function handleGroupRecall(ctx, msg) {
|
|
15
15
|
const { core, account } = ctx;
|
|
16
|
-
const log = createLog('recall'
|
|
16
|
+
const log = createLog('recall');
|
|
17
17
|
const groupCode = msg.group_code?.trim() || 'unknown';
|
|
18
18
|
const seqList = msg.recall_msg_seq_list;
|
|
19
19
|
if (!seqList || seqList.length === 0) {
|
|
@@ -57,7 +57,7 @@ export function handleGroupRecall(ctx, msg) {
|
|
|
57
57
|
}
|
|
58
58
|
export function handleC2CRecall(ctx, msg) {
|
|
59
59
|
const { core, account } = ctx;
|
|
60
|
-
const log = createLog('recall'
|
|
60
|
+
const log = createLog('recall');
|
|
61
61
|
const fromAccount = msg.from_account?.trim() || 'unknown';
|
|
62
62
|
const seqList = msg.msg_id
|
|
63
63
|
? [{ msg_id: msg.msg_id, msg_seq: msg.msg_seq }]
|
|
@@ -7,12 +7,6 @@ export type MessageHandlerContext = {
|
|
|
7
7
|
account: ResolvedYuanbaoAccount;
|
|
8
8
|
config: OpenClawConfig;
|
|
9
9
|
core: PluginRuntime;
|
|
10
|
-
log: {
|
|
11
|
-
info: (msg: string) => void;
|
|
12
|
-
warn: (msg: string) => void;
|
|
13
|
-
error: (msg: string) => void;
|
|
14
|
-
verbose: (msg: string) => void;
|
|
15
|
-
};
|
|
16
10
|
statusSink?: (patch: {
|
|
17
11
|
lastInboundAt?: number;
|
|
18
12
|
lastOutboundAt?: number;
|
|
@@ -22,7 +22,7 @@ export const customHandler = {
|
|
|
22
22
|
if (!resData.isAtBot) {
|
|
23
23
|
resData.isAtBot = isAtBotSelf;
|
|
24
24
|
}
|
|
25
|
-
createLog('custom'
|
|
25
|
+
createLog('custom').info('@消息', { text: customContent?.text, userId: customContent?.user_id, isAtBot: resData.isAtBot });
|
|
26
26
|
if (!isAtBotSelf && customContent?.user_id) {
|
|
27
27
|
resData.mentions.push({
|
|
28
28
|
userId: customContent.user_id,
|
|
@@ -5,6 +5,7 @@ import { soundHandler } from './sound.js';
|
|
|
5
5
|
import { fileHandler } from './file.js';
|
|
6
6
|
import { videoHandler } from './video.js';
|
|
7
7
|
import { faceHandler } from './face.js';
|
|
8
|
+
import { sanitizePipeTables } from '../../utils/markdown-table-sanitize.js';
|
|
8
9
|
const handlerList = [
|
|
9
10
|
textHandler,
|
|
10
11
|
customHandler,
|
|
@@ -75,15 +76,16 @@ function resolveAtMentions(text, groupCode, memberInst) {
|
|
|
75
76
|
export function prepareOutboundContent(text, groupCode, memberInst) {
|
|
76
77
|
if (!text)
|
|
77
78
|
return [];
|
|
79
|
+
const sanitizedText = sanitizePipeTables(text);
|
|
78
80
|
const items = [];
|
|
79
|
-
if (
|
|
80
|
-
const trailing =
|
|
81
|
+
if (sanitizedText.length) {
|
|
82
|
+
const trailing = sanitizedText.trim();
|
|
81
83
|
if (trailing) {
|
|
82
84
|
items.push(...resolveAtMentions(trailing, groupCode, memberInst));
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
|
-
if (items.length === 0 &&
|
|
86
|
-
items.push(...resolveAtMentions(
|
|
87
|
+
if (items.length === 0 && sanitizedText.trim()) {
|
|
88
|
+
items.push(...resolveAtMentions(sanitizedText.trim(), groupCode, memberInst));
|
|
87
89
|
}
|
|
88
90
|
return items;
|
|
89
91
|
}
|
|
@@ -122,7 +122,7 @@ async function handleC2CMessage(params) {
|
|
|
122
122
|
const fromAccount = msg.from_account?.trim() || 'unknown';
|
|
123
123
|
const senderNickname = msg.sender_nickname?.trim() || undefined;
|
|
124
124
|
const outboundSender = resolveOutboundSenderAccount(account);
|
|
125
|
-
const log = createLog('inbound'
|
|
125
|
+
const log = createLog('inbound');
|
|
126
126
|
if (outboundSender && fromAccount === outboundSender) {
|
|
127
127
|
log.info(`跳过机器人自身消息 <- ${fromAccount}`);
|
|
128
128
|
return;
|
|
@@ -336,7 +336,7 @@ async function handleGroupMessage(params) {
|
|
|
336
336
|
const fromAccount = msg.from_account?.trim() || 'unknown';
|
|
337
337
|
const senderNickname = msg.sender_nickname?.trim() || undefined;
|
|
338
338
|
const outboundSender = resolveOutboundSenderAccount(account);
|
|
339
|
-
const glog = createLog('inbound'
|
|
339
|
+
const glog = createLog('inbound');
|
|
340
340
|
setGroupCode(groupCode);
|
|
341
341
|
if (outboundSender && fromAccount === outboundSender) {
|
|
342
342
|
glog.info('跳过机器人自身消息', { groupCode, fromAccount });
|
|
@@ -33,8 +33,8 @@ async function shouldAttachReplyRef(params) {
|
|
|
33
33
|
return true;
|
|
34
34
|
}
|
|
35
35
|
export async function sendYuanbaoMessageBody(params) {
|
|
36
|
-
const {
|
|
37
|
-
const log = createLog('outbound'
|
|
36
|
+
const { toAccount, msgBody, fromAccount, ctx } = params;
|
|
37
|
+
const log = createLog('outbound');
|
|
38
38
|
if (!ctx?.wsClient) {
|
|
39
39
|
log.error('发送失败: WebSocket 客户端不可用');
|
|
40
40
|
return { ok: false, error: 'wsClient not available' };
|
|
@@ -77,7 +77,7 @@ export async function sendYuanbaoMessage(params) {
|
|
|
77
77
|
}
|
|
78
78
|
export async function sendYuanbaoGroupMessageBody(params) {
|
|
79
79
|
const { account, groupCode, msgBody, fromAccount, refMsgId, refFromAccount, ctx } = params;
|
|
80
|
-
const log = createLog('outbound'
|
|
80
|
+
const log = createLog('outbound');
|
|
81
81
|
if (!ctx?.wsClient) {
|
|
82
82
|
log.error('发送群消息失败: WebSocket 客户端不可用');
|
|
83
83
|
return { ok: false, error: 'wsClient not available' };
|
|
@@ -153,7 +153,7 @@ export async function sendMsgBodyDirect(params) {
|
|
|
153
153
|
}
|
|
154
154
|
export async function executeReply(params) {
|
|
155
155
|
const { transport, ctx, account, core, replyRuntime, splitFinalText, overflowPolicy, ctxPayload, sessionKey, appendText, } = params;
|
|
156
|
-
const rlog = createLog('outbound'
|
|
156
|
+
const rlog = createLog('outbound');
|
|
157
157
|
if (ctx.abortSignal?.aborted) {
|
|
158
158
|
rlog.warn(`[${account.accountId}] 回复已中止,跳过执行`);
|
|
159
159
|
return;
|
|
@@ -191,6 +191,10 @@ export async function executeReply(params) {
|
|
|
191
191
|
return;
|
|
192
192
|
}
|
|
193
193
|
rlog.info('[deliver] 收到回复数据', { kind: info.kind, model_output: payload.text });
|
|
194
|
+
if (payload.isReasoning) {
|
|
195
|
+
rlog.info('[deliver] Reasoning', { text: payload.text });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
194
198
|
if (payload.isCompactionNotice) {
|
|
195
199
|
rlog.info('[deliver] CompactionNotice', { text: payload.text });
|
|
196
200
|
return;
|
|
@@ -55,7 +55,6 @@ export async function handleYuanbaoAction(action, params, context) {
|
|
|
55
55
|
account: context.account,
|
|
56
56
|
config: context.config,
|
|
57
57
|
core: getYuanbaoRuntime(),
|
|
58
|
-
log: { info: () => { }, warn: () => { }, error: () => { }, verbose: () => { } },
|
|
59
58
|
wsClient: context.wsClient,
|
|
60
59
|
groupCode: getGroupCode(),
|
|
61
60
|
};
|
package/dist/src/types.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export type YuanbaoConfig = YuanbaoAccountConfig & {
|
|
|
32
32
|
accounts?: Record<string, YuanbaoAccountConfig>;
|
|
33
33
|
defaultAccount?: string;
|
|
34
34
|
routeEnv?: string;
|
|
35
|
-
|
|
35
|
+
debug?: boolean;
|
|
36
36
|
};
|
|
37
37
|
export type ResolvedYuanbaoAccount = {
|
|
38
38
|
accountId: string;
|
|
@@ -135,6 +135,12 @@ export type CloudCustomData = {
|
|
|
135
135
|
source_group?: string;
|
|
136
136
|
[key: string]: unknown;
|
|
137
137
|
};
|
|
138
|
+
export type YuanbaoBufferedDeliverPayload = {
|
|
139
|
+
text?: string;
|
|
140
|
+
mediaUrls?: string[];
|
|
141
|
+
isCompactionNotice: boolean;
|
|
142
|
+
isReasoning: boolean;
|
|
143
|
+
};
|
|
138
144
|
export type YuanbaoSendMsgRequest = {
|
|
139
145
|
sync_other_machine?: number;
|
|
140
146
|
from_account?: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function sanitizePipeTables(text: string): string;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
function findTableRegions(lines) {
|
|
2
|
+
const regions = [];
|
|
3
|
+
let groupStart = -1;
|
|
4
|
+
let lastPipeLine = -1;
|
|
5
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6
|
+
const line = lines[i];
|
|
7
|
+
const hasPipe = line.includes('|');
|
|
8
|
+
const isBlank = line.trim() === '';
|
|
9
|
+
if (hasPipe) {
|
|
10
|
+
if (groupStart < 0)
|
|
11
|
+
groupStart = i;
|
|
12
|
+
lastPipeLine = i;
|
|
13
|
+
}
|
|
14
|
+
else if (isBlank) {
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
if (groupStart >= 0) {
|
|
18
|
+
regions.push({ startLine: groupStart, endLine: lastPipeLine });
|
|
19
|
+
groupStart = -1;
|
|
20
|
+
lastPipeLine = -1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (groupStart >= 0) {
|
|
25
|
+
regions.push({ startLine: groupStart, endLine: lastPipeLine });
|
|
26
|
+
}
|
|
27
|
+
return regions;
|
|
28
|
+
}
|
|
29
|
+
const SEPARATOR_RE = /\|[\s]*:?-{2,}:?[\s]*(?:\|[\s]*:?-{2,}:?[\s]*)+\|/;
|
|
30
|
+
function findSeparatorInFlat(flat) {
|
|
31
|
+
const m = SEPARATOR_RE.exec(flat);
|
|
32
|
+
if (!m)
|
|
33
|
+
return null;
|
|
34
|
+
const pipeCount = (m[0].match(/\|/g) || []).length;
|
|
35
|
+
if (pipeCount < 2)
|
|
36
|
+
return null;
|
|
37
|
+
return { numColumns: pipeCount - 1 };
|
|
38
|
+
}
|
|
39
|
+
function healRegion(regionLines) {
|
|
40
|
+
if (!regionLines.some(l => l.trim() === ''))
|
|
41
|
+
return null;
|
|
42
|
+
const flat = regionLines.join('').replace(/\n/g, '');
|
|
43
|
+
if (!findSeparatorInFlat(flat))
|
|
44
|
+
return null;
|
|
45
|
+
const nonBlank = regionLines.filter(l => l.trim() !== '');
|
|
46
|
+
const result = [];
|
|
47
|
+
let acc = '';
|
|
48
|
+
for (const line of nonBlank) {
|
|
49
|
+
if (!acc) {
|
|
50
|
+
acc = line;
|
|
51
|
+
}
|
|
52
|
+
else if (acc.trimEnd().endsWith('|') && line.trimStart().startsWith('|')) {
|
|
53
|
+
result.push(acc);
|
|
54
|
+
acc = line;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
acc += line;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (acc)
|
|
61
|
+
result.push(acc);
|
|
62
|
+
return result.join('\n');
|
|
63
|
+
}
|
|
64
|
+
export function sanitizePipeTables(text) {
|
|
65
|
+
if (!text)
|
|
66
|
+
return text;
|
|
67
|
+
if (!text.includes('|'))
|
|
68
|
+
return text;
|
|
69
|
+
if (!text.includes('\n'))
|
|
70
|
+
return text;
|
|
71
|
+
const pipeCount = (text.match(/\|/g) || []).length;
|
|
72
|
+
if (pipeCount < 3)
|
|
73
|
+
return text;
|
|
74
|
+
const lines = text.split('\n');
|
|
75
|
+
const regions = findTableRegions(lines);
|
|
76
|
+
if (regions.length === 0) {
|
|
77
|
+
return text;
|
|
78
|
+
}
|
|
79
|
+
for (let ri = regions.length - 1; ri >= 0; ri--) {
|
|
80
|
+
const region = regions[ri];
|
|
81
|
+
const regionLines = lines.slice(region.startLine, region.endLine + 1);
|
|
82
|
+
const healed = healRegion(regionLines);
|
|
83
|
+
if (healed !== null) {
|
|
84
|
+
const healedLines = healed.split('\n');
|
|
85
|
+
lines.splice(region.startLine, region.endLine - region.startLine + 1, ...healedLines);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return lines.join('\n');
|
|
89
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ResolvedYuanbaoAccount } from '../../types.js';
|
|
2
|
-
import type {
|
|
3
|
-
export declare function apiGetUploadInfo(account: ResolvedYuanbaoAccount, fileName: string, fileId: string
|
|
4
|
-
export declare function apiGetDownloadUrl(account: ResolvedYuanbaoAccount, resourceId: string
|
|
2
|
+
import type { CosUploadConfig } from './request.js';
|
|
3
|
+
export declare function apiGetUploadInfo(account: ResolvedYuanbaoAccount, fileName: string, fileId: string): Promise<CosUploadConfig>;
|
|
4
|
+
export declare function apiGetDownloadUrl(account: ResolvedYuanbaoAccount, resourceId: string): Promise<string>;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { yuanbaoPost, yuanbaoGet } from './request.js';
|
|
2
2
|
const UPLOAD_INFO_PATH = '/api/resource/genUploadInfo';
|
|
3
3
|
const DOWNLOAD_INFO_PATH = '/api/resource/v1/download';
|
|
4
|
-
export async function apiGetUploadInfo(account, fileName, fileId
|
|
5
|
-
const data = await yuanbaoPost(account, UPLOAD_INFO_PATH, { fileName, fileId, docFrom: 'localDoc', docOpenId: '' }
|
|
4
|
+
export async function apiGetUploadInfo(account, fileName, fileId) {
|
|
5
|
+
const data = await yuanbaoPost(account, UPLOAD_INFO_PATH, { fileName, fileId, docFrom: 'localDoc', docOpenId: '' });
|
|
6
6
|
if (!data.bucketName || !data.location) {
|
|
7
7
|
throw new Error(`[yuanbao-api] genUploadInfo 配置不完整: ${JSON.stringify(data)}`);
|
|
8
8
|
}
|
|
9
9
|
return data;
|
|
10
10
|
}
|
|
11
|
-
export async function apiGetDownloadUrl(account, resourceId
|
|
12
|
-
const data = await yuanbaoGet(account, DOWNLOAD_INFO_PATH, { resourceId }
|
|
11
|
+
export async function apiGetDownloadUrl(account, resourceId) {
|
|
12
|
+
const data = await yuanbaoGet(account, DOWNLOAD_INFO_PATH, { resourceId });
|
|
13
13
|
const downloadUrl = data.url ?? data.realUrl;
|
|
14
14
|
if (!downloadUrl) {
|
|
15
15
|
throw new Error(`[yuanbao-api] resource/v1/download 未返回有效 URL: ${JSON.stringify(data)}`);
|
|
@@ -44,8 +44,8 @@ export declare function getTokenStatus(accountId: string): {
|
|
|
44
44
|
};
|
|
45
45
|
export declare function getCachedBotId(accountId: string): string | undefined;
|
|
46
46
|
export declare function verifySignature(expected: string, actual: string): boolean;
|
|
47
|
-
export declare function getSignToken(account: ResolvedYuanbaoAccount
|
|
48
|
-
export declare function forceRefreshSignToken(account: ResolvedYuanbaoAccount
|
|
49
|
-
export declare function getAuthHeaders(account: ResolvedYuanbaoAccount
|
|
50
|
-
export declare function yuanbaoPost<T>(account: ResolvedYuanbaoAccount, path: string, body: unknown
|
|
51
|
-
export declare function yuanbaoGet<T>(account: ResolvedYuanbaoAccount, path: string, params?: Record<string, string
|
|
47
|
+
export declare function getSignToken(account: ResolvedYuanbaoAccount): Promise<SignTokenData>;
|
|
48
|
+
export declare function forceRefreshSignToken(account: ResolvedYuanbaoAccount): Promise<SignTokenData>;
|
|
49
|
+
export declare function getAuthHeaders(account: ResolvedYuanbaoAccount): Promise<AuthHeaders>;
|
|
50
|
+
export declare function yuanbaoPost<T>(account: ResolvedYuanbaoAccount, path: string, body: unknown): Promise<T>;
|
|
51
|
+
export declare function yuanbaoGet<T>(account: ResolvedYuanbaoAccount, path: string, params?: Record<string, string>): Promise<T>;
|
|
@@ -59,8 +59,8 @@ export function verifySignature(expected, actual) {
|
|
|
59
59
|
}
|
|
60
60
|
return timingSafeEqual(expectedBuf, actualBuf);
|
|
61
61
|
}
|
|
62
|
-
async function doFetchSignToken(account
|
|
63
|
-
const mlog = createLog('http'
|
|
62
|
+
async function doFetchSignToken(account) {
|
|
63
|
+
const mlog = createLog('http');
|
|
64
64
|
const { appKey, appSecret, apiDomain } = account;
|
|
65
65
|
if (!appKey || !appSecret)
|
|
66
66
|
throw new Error('签票失败: 缺少 appKey 或 appSecret');
|
|
@@ -106,8 +106,8 @@ async function doFetchSignToken(account, log) {
|
|
|
106
106
|
}
|
|
107
107
|
throw new Error('签票失败: 超过最大重试次数');
|
|
108
108
|
}
|
|
109
|
-
function scheduleTokenRefresh(account, durationSec
|
|
110
|
-
const mlog = createLog('http'
|
|
109
|
+
function scheduleTokenRefresh(account, durationSec) {
|
|
110
|
+
const mlog = createLog('http');
|
|
111
111
|
const existing = tokenRefreshTimers.get(account.accountId);
|
|
112
112
|
if (existing) {
|
|
113
113
|
clearTimeout(existing);
|
|
@@ -122,7 +122,7 @@ function scheduleTokenRefresh(account, durationSec, log) {
|
|
|
122
122
|
tokenRefreshTimers.delete(account.accountId);
|
|
123
123
|
try {
|
|
124
124
|
mlog.info(`[${account.accountId}][token-timer]定时刷新触发,开始重新签票`);
|
|
125
|
-
await forceRefreshSignToken(account
|
|
125
|
+
await forceRefreshSignToken(account);
|
|
126
126
|
mlog.info(`[${account.accountId}][token-timer]定时刷新完成`);
|
|
127
127
|
}
|
|
128
128
|
catch (err) {
|
|
@@ -130,7 +130,7 @@ function scheduleTokenRefresh(account, durationSec, log) {
|
|
|
130
130
|
const retryTimer = setTimeout(async () => {
|
|
131
131
|
tokenRefreshTimers.delete(account.accountId);
|
|
132
132
|
try {
|
|
133
|
-
await forceRefreshSignToken(account
|
|
133
|
+
await forceRefreshSignToken(account);
|
|
134
134
|
mlog.info(`[${account.accountId}][token-timer]定时刷新重试成功`);
|
|
135
135
|
}
|
|
136
136
|
catch (retryErr) {
|
|
@@ -142,7 +142,7 @@ function scheduleTokenRefresh(account, durationSec, log) {
|
|
|
142
142
|
}, refreshAfterMs);
|
|
143
143
|
tokenRefreshTimers.set(account.accountId, timer);
|
|
144
144
|
}
|
|
145
|
-
export async function getSignToken(account
|
|
145
|
+
export async function getSignToken(account) {
|
|
146
146
|
if (account.token) {
|
|
147
147
|
return {
|
|
148
148
|
bot_id: account.botId || '',
|
|
@@ -152,7 +152,7 @@ export async function getSignToken(account, log) {
|
|
|
152
152
|
token: account.token,
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
|
-
const tlog = createLog('http'
|
|
155
|
+
const tlog = createLog('http');
|
|
156
156
|
const cached = tokenCacheMap.get(account.accountId);
|
|
157
157
|
if (cached && cached.expiresAt > Date.now()) {
|
|
158
158
|
const remainSec = Math.round((cached.expiresAt - Date.now()) / 1000);
|
|
@@ -166,11 +166,11 @@ export async function getSignToken(account, log) {
|
|
|
166
166
|
}
|
|
167
167
|
fetchPromise = (async () => {
|
|
168
168
|
try {
|
|
169
|
-
const data = await doFetchSignToken(account
|
|
169
|
+
const data = await doFetchSignToken(account);
|
|
170
170
|
const ttlMs = data.duration > 0 ? data.duration * 1000 : 0;
|
|
171
171
|
if (ttlMs > 0) {
|
|
172
172
|
tokenCacheMap.set(account.accountId, { data, expiresAt: Date.now() + ttlMs });
|
|
173
|
-
scheduleTokenRefresh(account, data.duration
|
|
173
|
+
scheduleTokenRefresh(account, data.duration);
|
|
174
174
|
}
|
|
175
175
|
return data;
|
|
176
176
|
}
|
|
@@ -181,15 +181,15 @@ export async function getSignToken(account, log) {
|
|
|
181
181
|
tokenFetchPromises.set(account.accountId, fetchPromise);
|
|
182
182
|
return fetchPromise;
|
|
183
183
|
}
|
|
184
|
-
export async function forceRefreshSignToken(account
|
|
185
|
-
const flog = createLog('http'
|
|
184
|
+
export async function forceRefreshSignToken(account) {
|
|
185
|
+
const flog = createLog('http');
|
|
186
186
|
flog.warn(`[${account.accountId}][force-refresh]清除缓存并重新签票`);
|
|
187
187
|
clearSignTokenCache(account.accountId);
|
|
188
188
|
tokenFetchPromises.delete(account.accountId);
|
|
189
|
-
return getSignToken(account
|
|
189
|
+
return getSignToken(account);
|
|
190
190
|
}
|
|
191
|
-
export async function getAuthHeaders(account
|
|
192
|
-
const data = await getSignToken(account
|
|
191
|
+
export async function getAuthHeaders(account) {
|
|
192
|
+
const data = await getSignToken(account);
|
|
193
193
|
if (data.bot_id && !account.botId) {
|
|
194
194
|
account.botId = data.bot_id;
|
|
195
195
|
}
|
|
@@ -207,11 +207,11 @@ export async function getAuthHeaders(account, log) {
|
|
|
207
207
|
}
|
|
208
208
|
return authHeaders;
|
|
209
209
|
}
|
|
210
|
-
export async function yuanbaoPost(account, path, body
|
|
211
|
-
const plog = createLog('http'
|
|
210
|
+
export async function yuanbaoPost(account, path, body) {
|
|
211
|
+
const plog = createLog('http');
|
|
212
212
|
const url = `https://${account.apiDomain}${path}`;
|
|
213
213
|
for (let attempt = 0; attempt <= HTTP_AUTH_RETRY_MAX; attempt++) {
|
|
214
|
-
const authHeaders = await getAuthHeaders(account
|
|
214
|
+
const authHeaders = await getAuthHeaders(account);
|
|
215
215
|
const response = await fetch(url, {
|
|
216
216
|
method: 'POST',
|
|
217
217
|
headers: {
|
|
@@ -222,7 +222,7 @@ export async function yuanbaoPost(account, path, body, log) {
|
|
|
222
222
|
});
|
|
223
223
|
if (response.status === 401 && attempt < HTTP_AUTH_RETRY_MAX) {
|
|
224
224
|
plog.warn(`[post][${account.accountId}] ${path} 收到 401,刷新 token 后重试 (attempt=${attempt + 1})`);
|
|
225
|
-
await forceRefreshSignToken(account
|
|
225
|
+
await forceRefreshSignToken(account);
|
|
226
226
|
continue;
|
|
227
227
|
}
|
|
228
228
|
if (!response.ok) {
|
|
@@ -237,11 +237,11 @@ export async function yuanbaoPost(account, path, body, log) {
|
|
|
237
237
|
}
|
|
238
238
|
throw new Error(`[yuanbao-api][POST] ${path} 401 重试次数已耗尽`);
|
|
239
239
|
}
|
|
240
|
-
export async function yuanbaoGet(account, path, params
|
|
241
|
-
const glog = createLog('http'
|
|
240
|
+
export async function yuanbaoGet(account, path, params) {
|
|
241
|
+
const glog = createLog('http');
|
|
242
242
|
const url = `https://${account.apiDomain}${path}${params ? `?${new URLSearchParams(params).toString()}` : ''}`;
|
|
243
243
|
for (let attempt = 0; attempt <= HTTP_AUTH_RETRY_MAX; attempt++) {
|
|
244
|
-
const authHeaders = await getAuthHeaders(account
|
|
244
|
+
const authHeaders = await getAuthHeaders(account);
|
|
245
245
|
const response = await fetch(url, {
|
|
246
246
|
method: 'GET',
|
|
247
247
|
headers: {
|
|
@@ -251,7 +251,7 @@ export async function yuanbaoGet(account, path, params, log) {
|
|
|
251
251
|
});
|
|
252
252
|
if (response.status === 401 && attempt < HTTP_AUTH_RETRY_MAX) {
|
|
253
253
|
glog.warn(`[get][${account.accountId}] ${path} 收到 401,刷新 token 后重试 (attempt=${attempt + 1})`);
|
|
254
|
-
await forceRefreshSignToken(account
|
|
254
|
+
await forceRefreshSignToken(account);
|
|
255
255
|
continue;
|
|
256
256
|
}
|
|
257
257
|
if (!response.ok) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { WsClientCallbacks, WsClientConfig, WsClientState, WsConnectionConfig, WsSendMessageResponse, WsSendC2CMessageData, WsSendGroupMessageData, WsSendPrivateHeartbeatData, WsSendGroupHeartbeatData, WsHeartbeatResponse, WsQueryGroupInfoData, WsQueryGroupInfoResponse, WsGetGroupMemberListData, WsGetGroupMemberListResponse } from './types.js';
|
|
2
|
-
import type { LogSink } from '../../logger.js';
|
|
3
2
|
export declare const BIZ_CMD: {
|
|
4
3
|
readonly SendC2CMessage: "send_c2c_message";
|
|
5
4
|
readonly SendGroupMessage: "send_group_message";
|
|
@@ -30,7 +29,6 @@ export declare class YuanbaoWsClient {
|
|
|
30
29
|
connection: WsConnectionConfig;
|
|
31
30
|
config?: WsClientConfig;
|
|
32
31
|
callbacks?: WsClientCallbacks;
|
|
33
|
-
log?: LogSink;
|
|
34
32
|
});
|
|
35
33
|
updateAuth(auth: WsConnectionConfig['auth']): void;
|
|
36
34
|
connect(): void;
|
|
@@ -57,7 +57,7 @@ export class YuanbaoWsClient {
|
|
|
57
57
|
disposed = false;
|
|
58
58
|
pendingRequests = new Map();
|
|
59
59
|
constructor(params) {
|
|
60
|
-
this.log = createLog('ws'
|
|
60
|
+
this.log = createLog('ws');
|
|
61
61
|
this.log.info('初始化 WebSocket 客户端', { connection: params.connection, config: params.config });
|
|
62
62
|
this.connectionConfig = params.connection;
|
|
63
63
|
this.clientConfig = {
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import type { OpenClawConfig, PluginRuntime } from 'openclaw/plugin-sdk';
|
|
2
2
|
import type { ResolvedYuanbaoAccount, YuanbaoInboundMessage } from '../../types.js';
|
|
3
3
|
import type { WsPushEvent } from './types.js';
|
|
4
|
-
type GatewayLog = {
|
|
5
|
-
info?: (msg: string) => void;
|
|
6
|
-
warn?: (msg: string) => void;
|
|
7
|
-
error?: (msg: string) => void;
|
|
8
|
-
debug?: (msg: string) => void;
|
|
9
|
-
};
|
|
10
4
|
type GatewayStatusPatch = Record<string, unknown>;
|
|
11
5
|
export type StartWsGatewayParams = {
|
|
12
6
|
account: ResolvedYuanbaoAccount;
|
|
13
7
|
config: OpenClawConfig;
|
|
14
8
|
abortSignal: AbortSignal;
|
|
15
|
-
log?: GatewayLog;
|
|
16
9
|
runtime?: PluginRuntime;
|
|
17
10
|
statusSink?: (patch: GatewayStatusPatch) => void;
|
|
18
11
|
};
|
|
@@ -21,5 +14,5 @@ type InboundResult = {
|
|
|
21
14
|
msg: YuanbaoInboundMessage;
|
|
22
15
|
chatType: 'c2c' | 'group';
|
|
23
16
|
};
|
|
24
|
-
export declare function wsPushToInboundMessage(pushEvent: WsPushEvent
|
|
17
|
+
export declare function wsPushToInboundMessage(pushEvent: WsPushEvent): InboundResult | null;
|
|
25
18
|
export {};
|
|
@@ -6,9 +6,9 @@ import { setActiveWsClient } from './runtime.js';
|
|
|
6
6
|
import { decodeInboundMessage } from './biz-codec.js';
|
|
7
7
|
import { getSignToken, forceRefreshSignToken } from '../api.js';
|
|
8
8
|
export async function startYuanbaoWsGateway(params) {
|
|
9
|
-
const { account, config, abortSignal,
|
|
10
|
-
const gwlog = createLog('ws'
|
|
11
|
-
const auth = await resolveWsAuth(account
|
|
9
|
+
const { account, config, abortSignal, runtime, statusSink } = params;
|
|
10
|
+
const gwlog = createLog('ws');
|
|
11
|
+
const auth = await resolveWsAuth(account);
|
|
12
12
|
const client = new YuanbaoWsClient({
|
|
13
13
|
connection: {
|
|
14
14
|
gatewayUrl: account.wsGatewayUrl,
|
|
@@ -29,7 +29,7 @@ export async function startYuanbaoWsGateway(params) {
|
|
|
29
29
|
},
|
|
30
30
|
onDispatch: (pushEvent) => {
|
|
31
31
|
gwlog.debug(`[${account.accountId}] WS 推送: cmd=${pushEvent.cmd}, type=${pushEvent.type}`);
|
|
32
|
-
handleWsDispatchEvent({ account, config, pushEvent,
|
|
32
|
+
handleWsDispatchEvent({ account, config, pushEvent, runtime, client, statusSink, abortSignal });
|
|
33
33
|
},
|
|
34
34
|
onStateChange: (state) => {
|
|
35
35
|
gwlog.info(`[${account.accountId}] WS 状态: ${state}`);
|
|
@@ -52,7 +52,7 @@ export async function startYuanbaoWsGateway(params) {
|
|
|
52
52
|
},
|
|
53
53
|
onAuthFailed: async (code) => {
|
|
54
54
|
gwlog.warn(`[${account.accountId}] 收到 onAuthFailed 回调(code=${code}),开始刷新 token`);
|
|
55
|
-
const tokenData = await forceRefreshSignToken(account
|
|
55
|
+
const tokenData = await forceRefreshSignToken(account);
|
|
56
56
|
const uid = tokenData.bot_id || account.botId || '';
|
|
57
57
|
if (tokenData.bot_id) {
|
|
58
58
|
account.botId = tokenData.bot_id;
|
|
@@ -66,12 +66,6 @@ export async function startYuanbaoWsGateway(params) {
|
|
|
66
66
|
};
|
|
67
67
|
},
|
|
68
68
|
},
|
|
69
|
-
log: {
|
|
70
|
-
info: msg => log?.info?.(msg),
|
|
71
|
-
warn: msg => log?.warn?.(msg),
|
|
72
|
-
error: msg => log?.error?.(msg),
|
|
73
|
-
debug: msg => log?.debug?.(msg),
|
|
74
|
-
},
|
|
75
69
|
});
|
|
76
70
|
client.connect();
|
|
77
71
|
setActiveWsClient(account.accountId, client);
|
|
@@ -94,8 +88,8 @@ export async function startYuanbaoWsGateway(params) {
|
|
|
94
88
|
abortSignal.addEventListener('abort', onAbort, { once: true });
|
|
95
89
|
});
|
|
96
90
|
}
|
|
97
|
-
async function resolveWsAuth(account
|
|
98
|
-
const mlog = createLog('ws'
|
|
91
|
+
async function resolveWsAuth(account) {
|
|
92
|
+
const mlog = createLog('ws');
|
|
99
93
|
mlog.info(`[${account.accountId}] resolveWsAuth 入参:`, {
|
|
100
94
|
botId: account.botId,
|
|
101
95
|
token: account.token,
|
|
@@ -115,7 +109,7 @@ async function resolveWsAuth(account, log) {
|
|
|
115
109
|
routeEnv: account.config?.routeEnv,
|
|
116
110
|
};
|
|
117
111
|
}
|
|
118
|
-
const tokenData = await getSignToken(account
|
|
112
|
+
const tokenData = await getSignToken(account);
|
|
119
113
|
const uid = tokenData.bot_id || account.botId || '';
|
|
120
114
|
if (tokenData.bot_id) {
|
|
121
115
|
account.botId = tokenData.bot_id;
|
|
@@ -157,14 +151,14 @@ function inferChatType(msg) {
|
|
|
157
151
|
function hasValidMsgFields(msg) {
|
|
158
152
|
return Boolean(msg.callback_command || msg.from_account || msg.msg_body);
|
|
159
153
|
}
|
|
160
|
-
function decodeFromProtobuf(rawData
|
|
154
|
+
function decodeFromProtobuf(rawData) {
|
|
161
155
|
const decoded = decodeInboundMessage(rawData);
|
|
162
156
|
if (!decoded || !hasValidMsgFields(decoded))
|
|
163
157
|
return null;
|
|
164
|
-
createLog('ws').debug(
|
|
158
|
+
createLog('ws').debug('[Protobuf] WS 推送事件解析', { ...decoded });
|
|
165
159
|
return { msg: decoded, chatType: inferChatType(decoded) };
|
|
166
160
|
}
|
|
167
|
-
function decodeFromRawDataJson(rawData
|
|
161
|
+
function decodeFromRawDataJson(rawData) {
|
|
168
162
|
try {
|
|
169
163
|
const rawJson = JSON.parse(new TextDecoder().decode(rawData));
|
|
170
164
|
if (!rawJson || !hasValidMsgFields(rawJson))
|
|
@@ -173,7 +167,7 @@ function decodeFromRawDataJson(rawData, pushType) {
|
|
|
173
167
|
if (!msg.trace_id) {
|
|
174
168
|
msg.trace_id = rawJson.log_ext?.trace_id;
|
|
175
169
|
}
|
|
176
|
-
createLog('ws').info(
|
|
170
|
+
createLog('ws').info('[Json] WS 推送事件解析', { ...msg });
|
|
177
171
|
return { msg, chatType: inferChatType(msg) };
|
|
178
172
|
}
|
|
179
173
|
catch {
|
|
@@ -206,34 +200,32 @@ function decodeFromContent(pushEvent) {
|
|
|
206
200
|
chatType,
|
|
207
201
|
};
|
|
208
202
|
}
|
|
209
|
-
export function wsPushToInboundMessage(pushEvent
|
|
203
|
+
export function wsPushToInboundMessage(pushEvent) {
|
|
210
204
|
if (pushEvent.connData && pushEvent.connData.length > 0) {
|
|
211
|
-
createLog('ws'
|
|
212
|
-
const
|
|
213
|
-
const result = decodeFromProtobuf(pushEvent.connData, pushType);
|
|
205
|
+
createLog('ws').debug('[connData] WS 推送事件解析');
|
|
206
|
+
const result = decodeFromProtobuf(pushEvent.connData);
|
|
214
207
|
if (result)
|
|
215
208
|
return result;
|
|
216
209
|
}
|
|
217
210
|
if (pushEvent.rawData && pushEvent.rawData.length > 0) {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
createLog('ws', log).warn(`[${pushType}] WS 推送事件解析失败`);
|
|
211
|
+
const jsonResult = decodeFromRawDataJson(pushEvent.rawData);
|
|
212
|
+
if (jsonResult) {
|
|
213
|
+
createLog('ws').debug('[rawData] WS 推送事件解析', jsonResult);
|
|
214
|
+
return jsonResult;
|
|
215
|
+
}
|
|
216
|
+
createLog('ws').warn('[rawData] WS 推送事件解析失败');
|
|
225
217
|
}
|
|
226
218
|
if (pushEvent.content) {
|
|
227
|
-
createLog('ws'
|
|
219
|
+
createLog('ws').debug('[content] WS 推送事件解析, type=content', { content: pushEvent.content });
|
|
228
220
|
return decodeFromContent(pushEvent);
|
|
229
221
|
}
|
|
230
222
|
return null;
|
|
231
223
|
}
|
|
232
224
|
function handleWsDispatchEvent(params) {
|
|
233
|
-
const { account, config, pushEvent,
|
|
234
|
-
const dlog = createLog('ws'
|
|
225
|
+
const { account, config, pushEvent, runtime, client, statusSink, abortSignal } = params;
|
|
226
|
+
const dlog = createLog('ws');
|
|
235
227
|
dlog.debug(`[${account.accountId}][dispatch] cmd=${pushEvent.cmd}, module=${pushEvent.module}, msgId=${pushEvent.msgId}`);
|
|
236
|
-
const converted = wsPushToInboundMessage(pushEvent
|
|
228
|
+
const converted = wsPushToInboundMessage(pushEvent);
|
|
237
229
|
if (!converted) {
|
|
238
230
|
dlog.debug(`[${account.accountId}][dispatch] cmd=${pushEvent.cmd} (非消息事件,跳过)`);
|
|
239
231
|
return;
|
|
@@ -258,12 +250,6 @@ function handleWsDispatchEvent(params) {
|
|
|
258
250
|
account,
|
|
259
251
|
config,
|
|
260
252
|
core: runtime,
|
|
261
|
-
log: {
|
|
262
|
-
info: (m) => gwLog?.info?.(m),
|
|
263
|
-
warn: (m) => gwLog?.warn?.(m),
|
|
264
|
-
error: (m) => gwLog?.error?.(m),
|
|
265
|
-
verbose: (m) => gwLog?.debug?.(m),
|
|
266
|
-
},
|
|
267
253
|
statusSink: statusSink,
|
|
268
254
|
wsClient: client,
|
|
269
255
|
traceContext,
|
package/openclaw.plugin.json
CHANGED