openclaw-plugin-yuanbao 2.3.1 → 2.5.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.
Files changed (34) hide show
  1. package/dist/openclaw.plugin.json +1 -1
  2. package/dist/src/channel.js +51 -39
  3. package/dist/src/config-schema.js +2 -2
  4. package/dist/src/dm/directory-cache.d.ts +2 -3
  5. package/dist/src/dm/directory.d.ts +2 -3
  6. package/dist/src/dm/directory.js +4 -6
  7. package/dist/src/dm/handle-action.js +1 -1
  8. package/dist/src/dm/send-dm.js +1 -1
  9. package/dist/src/logger.js +1 -0
  10. package/dist/src/media.d.ts +1 -0
  11. package/dist/src/media.js +1 -1
  12. package/dist/src/message-handler/context.d.ts +2 -0
  13. package/dist/src/message-handler/inbound.js +18 -101
  14. package/dist/src/message-handler/outbound.d.ts +2 -0
  15. package/dist/src/message-handler/outbound.js +49 -27
  16. package/dist/src/message-tool/action-runtime.js +20 -3
  17. package/dist/src/message-tool/hints.js +3 -0
  18. package/dist/src/module/log-upload/index.js +8 -11
  19. package/dist/src/outbound-queue.d.ts +15 -2
  20. package/dist/src/outbound-queue.js +167 -25
  21. package/dist/src/sticker/sticker-sender.d.ts +2 -0
  22. package/dist/src/sticker/sticker-sender.js +2 -1
  23. package/dist/src/targets.d.ts +13 -0
  24. package/dist/src/targets.js +40 -0
  25. package/dist/src/tools/member.js +1 -0
  26. package/dist/src/trace/context.d.ts +13 -0
  27. package/dist/src/trace/context.js +64 -0
  28. package/dist/src/types.d.ts +8 -0
  29. package/dist/src/yuanbao-server/ws/biz-codec.js +24 -1
  30. package/dist/src/yuanbao-server/ws/gateway.js +18 -2
  31. package/dist/src/yuanbao-server/ws/proto/biz.json +28 -0
  32. package/dist/src/yuanbao-server/ws/types.d.ts +4 -0
  33. package/openclaw.plugin.json +1 -1
  34. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  "id": "openclaw-plugin-yuanbao",
3
3
  "name": "元宝 Bot",
4
4
  "description": "Tencent YuanBao intelligent bot channel plugin",
5
- "version": "2.3.1",
5
+ "version": "2.5.0",
6
6
  "channels": [
7
7
  "yuanbao"
8
8
  ],
@@ -1,14 +1,15 @@
1
- import { DEFAULT_ACCOUNT_ID, deleteAccountFromConfigSection, setAccountEnabledInConfigSection, formatPairingApproveHint } from 'openclaw/plugin-sdk/core';
1
+ import { DEFAULT_ACCOUNT_ID, deleteAccountFromConfigSection, setAccountEnabledInConfigSection, } from 'openclaw/plugin-sdk/core';
2
+ import { formatPairingApproveHint } from 'openclaw/plugin-sdk/mattermost';
2
3
  import { listYuanbaoAccountIds, resolveDefaultYuanbaoAccountId, resolveYuanbaoAccount } from './accounts.js';
3
4
  import { yuanbaoConfigSchema } from './config-schema.js';
4
5
  import { yuanbaoOnboardingAdapter } from './onboarding.js';
5
- import { createLog, logger } from './logger.js';
6
+ import { createLog } from './logger.js';
6
7
  import { yuanbaoSetupAdapter } from './setup.js';
7
8
  import { startYuanbaoWsGateway, getActiveWsClient } from './yuanbao-server/ws/index.js';
8
9
  import { getYuanbaoRuntime } from './runtime.js';
9
10
  import { sendYuanbaoMessage, sendYuanbaoGroupMessage } from './message-handler/index.js';
10
11
  import { initOutboundQueue, destroyOutboundQueue, getOutboundQueue } from './outbound-queue.js';
11
- import { parseTarget, sendDM, } from './dm/index.js';
12
+ import { ChatType, getGroupCode, parseTarget } from './targets.js';
12
13
  import { buildMessageToolHints, yuanbaoMessageActions } from './message-tool/index.js';
13
14
  function toChannelResult(result) {
14
15
  return {
@@ -18,6 +19,16 @@ function toChannelResult(result) {
18
19
  error: result.error ? new Error(result.error) : undefined,
19
20
  };
20
21
  }
22
+ function buildMinCtx(account, wsClient) {
23
+ return {
24
+ account,
25
+ config: account.config,
26
+ core: {},
27
+ log: { info: () => { }, warn: () => { }, error: () => { }, verbose: () => { } },
28
+ wsClient,
29
+ groupCode: getGroupCode(),
30
+ };
31
+ }
21
32
  async function sendTextToTarget(account, target, text, wsClient) {
22
33
  const minCtx = wsClient
23
34
  ? {
@@ -28,13 +39,11 @@ async function sendTextToTarget(account, target, text, wsClient) {
28
39
  wsClient,
29
40
  }
30
41
  : undefined;
31
- if (target.startsWith('group:')) {
32
- return sendYuanbaoGroupMessage({ account, groupCode: target.slice('group:'.length), text, fromAccount: account.botId, ctx: minCtx });
42
+ const { chatType, target: targetId } = parseTarget(target, account.accountId);
43
+ if (chatType === ChatType.GROUP) {
44
+ return sendYuanbaoGroupMessage({ account, groupCode: targetId, text, fromAccount: account.botId, ctx: minCtx });
33
45
  }
34
- if (target.startsWith('direct:')) {
35
- return sendYuanbaoMessage({ account, toAccount: target.slice('direct:'.length), text, fromAccount: account.botId, ctx: minCtx });
36
- }
37
- return sendYuanbaoMessage({ account, toAccount: target, text, fromAccount: account.botId, ctx: minCtx });
46
+ return sendYuanbaoMessage({ account, toAccount: targetId, text, fromAccount: account.botId, ctx: minCtx });
38
47
  }
39
48
  const meta = {
40
49
  id: 'yuanbao',
@@ -157,8 +166,8 @@ export const yuanbaoPlugin = {
157
166
  streaming: {
158
167
  blockStreamingChunkMaxChars: 3000,
159
168
  blockStreamingCoalesceDefaults: {
160
- minChars: 2500,
161
- idleMs: 15000,
169
+ minChars: 2800,
170
+ idleMs: 5000,
162
171
  },
163
172
  },
164
173
  outbound: {
@@ -166,42 +175,39 @@ export const yuanbaoPlugin = {
166
175
  chunkerMode: 'markdown',
167
176
  textChunkLimit: 3000,
168
177
  chunker: (text, limit) => getYuanbaoRuntime()?.channel.text.chunkMarkdownText(text, limit) ?? [text],
169
- sendText: async ({ cfg, to: _to, text, accountId }) => {
178
+ sendText: async (params) => {
179
+ const slog = createLog('channel.utbound');
180
+ const { cfg, accountId, to: _to, text } = params;
170
181
  const to = _to.replace(/^yuanbao:/, '');
171
182
  const account = resolveYuanbaoAccount({ cfg, accountId: accountId ?? undefined });
172
- const dmTarget = parseTarget(to);
173
- if (dmTarget?.kind === 'user') {
174
- const dmResult = await sendDM(to, text, { account });
175
- return {
176
- channel: 'yuanbao',
177
- ok: dmResult.ok,
178
- messageId: dmResult.messageId ?? '',
179
- error: dmResult.error
180
- ? new Error('detail' in dmResult.error ? dmResult.error.detail : dmResult.error.kind)
181
- : undefined,
182
- };
183
- }
183
+ slog.info('sendText', { accountId, to });
184
184
  const wsClient = getActiveWsClient(account.accountId);
185
185
  if (!wsClient) {
186
186
  return { channel: 'yuanbao', ok: false, messageId: '', error: new Error(`WebSocket client not connected for account ${account.accountId}`) };
187
187
  }
188
188
  const queueManager = getOutboundQueue(account.accountId);
189
189
  if (queueManager) {
190
- const safeTo = to.replace(/^user:/, 'direct:');
191
- const session = queueManager.getSession(safeTo);
192
- if (session) {
193
- await session.push({ type: 'text', text });
194
- return { channel: 'yuanbao', ok: true, messageId: '' };
195
- }
196
- logger.debug(`sendText: 未找到已有 session, to: ${to}`);
190
+ const { chatType, target, sessionKey } = parseTarget(to, account.accountId);
191
+ const session = queueManager.getOrCreateSession(sessionKey, {
192
+ chatType,
193
+ account,
194
+ target,
195
+ fromAccount: account.botId,
196
+ ctx: buildMinCtx(account, wsClient),
197
+ });
198
+ await session.push({ type: 'text', text });
199
+ await session.flush();
200
+ return { channel: 'yuanbao', ok: true, messageId: '' };
197
201
  }
198
202
  return toChannelResult(await sendTextToTarget(account, to, text, wsClient));
199
203
  },
200
- sendMedia: async ({ cfg, accountId, to: _to, mediaUrl, text, mediaLocalRoots }) => {
204
+ sendMedia: async (params) => {
205
+ const slog = createLog('channel.outbound');
206
+ const { cfg, accountId, to: _to, mediaUrl, text, mediaLocalRoots } = params;
201
207
  const to = _to.replace(/^yuanbao:/, '');
202
208
  const account = resolveYuanbaoAccount({ cfg, accountId: accountId ?? undefined });
203
209
  const wsClient = getActiveWsClient(account.accountId);
204
- logger.info('sendMedia', { to, mediaUrl, text, accountId });
210
+ slog.info('sendMedia', { accountId, to, mediaUrl, text });
205
211
  if (!wsClient) {
206
212
  return { channel: 'yuanbao', ok: false, messageId: '', error: new Error(`WebSocket client not connected for account ${account.accountId}`) };
207
213
  }
@@ -210,14 +216,20 @@ export const yuanbaoPlugin = {
210
216
  }
211
217
  const queueManager = getOutboundQueue(account.accountId);
212
218
  if (queueManager) {
213
- const safeTo = to.replace(/^user:/, 'direct:');
214
- const session = queueManager.getSession(safeTo);
215
- if (session) {
219
+ const { chatType, target, sessionKey } = parseTarget(to, account.accountId);
220
+ const session = queueManager.getOrCreateSession(sessionKey, {
221
+ chatType,
222
+ account,
223
+ target,
224
+ fromAccount: account.botId,
225
+ ctx: buildMinCtx(account, wsClient),
226
+ });
227
+ if (text?.trim()) {
216
228
  await session.push({ type: 'text', text });
217
- await session.push({ type: 'media', mediaUrl, mediaLocalRoots });
218
- return { channel: 'yuanbao', ok: true, messageId: '' };
219
229
  }
220
- logger.debug(`sendMedia: 未找到已有 session, to: ${to}`);
230
+ await session.push({ type: 'media', mediaUrl, mediaLocalRoots });
231
+ await session.flush();
232
+ return { channel: 'yuanbao', ok: true, messageId: '' };
221
233
  }
222
234
  return { channel: 'yuanbao', ok: false, messageId: '', error: new Error('No session found') };
223
235
  },
@@ -44,7 +44,7 @@ export const yuanbaoConfigSchema = {
44
44
  title: '消息聚合最小字符数',
45
45
  description: 'merge-text 策略下,缓冲区积累到此字符数后触发发送',
46
46
  minimum: 1,
47
- default: 2500,
47
+ default: 2800,
48
48
  },
49
49
  maxChars: {
50
50
  type: 'integer',
@@ -58,7 +58,7 @@ export const yuanbaoConfigSchema = {
58
58
  title: '空闲自动发送超时 (ms)',
59
59
  description: 'merge-text 策略下,超过该时长无新内容时自动发送缓冲区',
60
60
  minimum: 0,
61
- default: 15000,
61
+ default: 5000,
62
62
  },
63
63
  mediaMaxMb: {
64
64
  type: 'number',
@@ -1,7 +1,6 @@
1
1
  export interface CachedUserEntry {
2
- id: string;
3
- name?: string;
4
- handle?: string;
2
+ userId: string;
3
+ nickName?: string;
5
4
  }
6
5
  export declare function getCachedMember(key: string): CachedUserEntry | undefined;
7
6
  export declare function cacheMember(key: string, entry: CachedUserEntry): void;
@@ -1,9 +1,8 @@
1
1
  import type { CachedUserEntry } from './directory-cache.js';
2
2
  export interface DirectoryEntry {
3
3
  kind: 'user' | 'group';
4
- id: string;
5
- name?: string;
6
- handle?: string;
4
+ userId: string;
5
+ nickName: string;
7
6
  }
8
7
  export declare function resolveUsername(nameOrHandle: string, accountId: string, groupCode?: string): CachedUserEntry | null;
9
8
  export declare function listKnownPeers(accountId: string): DirectoryEntry[];
@@ -19,9 +19,8 @@ export function resolveUsername(nameOrHandle, accountId, groupCode = '') {
19
19
  || u.userId.toLowerCase() === query.toLowerCase());
20
20
  const best = exactMatch ?? results[0];
21
21
  const entry = {
22
- id: best.userId,
23
- name: best.nickName,
24
- handle: best.nickName,
22
+ userId: best.userId,
23
+ nickName: best.nickName,
25
24
  };
26
25
  cacheMember(query, entry);
27
26
  cacheMember(best.nickName, entry);
@@ -44,9 +43,8 @@ export function listKnownPeers(accountId) {
44
43
  seen.add(u.userId);
45
44
  entries.push({
46
45
  kind: 'user',
47
- id: u.userId,
48
- name: u.nickName,
49
- handle: u.nickName,
46
+ userId: u.userId,
47
+ nickName: u.nickName,
50
48
  });
51
49
  }
52
50
  }
@@ -52,7 +52,7 @@ export async function handleAction(ctx) {
52
52
  });
53
53
  }
54
54
  if (target.kind === 'user') {
55
- console.log('handleAction DM', ctx);
55
+ log.info('处理 DM 发送', { to, targetId: target.id, senderId: ctx.requesterSenderId });
56
56
  const senderId = ctx.requesterSenderId ?? '';
57
57
  const accessResult = enforceDMAccess(senderId, target.id, message.length, DEFAULT_DM_ACCESS_POLICY);
58
58
  if (!accessResult.allowed) {
@@ -53,7 +53,7 @@ export async function sendDM(to, text, opts) {
53
53
  },
54
54
  };
55
55
  }
56
- userId = resolved.id;
56
+ userId = resolved.userId;
57
57
  }
58
58
  const wsClient = opts.ctx?.wsClient ?? getActiveWsClient(account.accountId);
59
59
  if (!wsClient) {
@@ -84,6 +84,7 @@ const SENSITIVE_KEYS = new Set([
84
84
  'x-token',
85
85
  'user_input',
86
86
  'cloud_custom_data',
87
+ 'model_output',
87
88
  ]);
88
89
  function maskValue(value) {
89
90
  if (value.length < 8)
@@ -43,6 +43,7 @@ export declare function buildFileMsgBody(params: {
43
43
  url: string;
44
44
  filename: string;
45
45
  size?: number;
46
+ uuid?: string;
46
47
  }): Array<{
47
48
  msg_type: string;
48
49
  msg_content: Record<string, unknown>;
package/dist/src/media.js CHANGED
@@ -343,7 +343,7 @@ export function buildFileMsgBody(params) {
343
343
  {
344
344
  msg_type: 'TIMFileElem',
345
345
  msg_content: {
346
- uuid: params.filename,
346
+ uuid: params.uuid ?? params.filename,
347
347
  file_name: params.filename,
348
348
  file_size: params.size ?? 0,
349
349
  url: params.url,
@@ -1,6 +1,7 @@
1
1
  import type { OpenClawConfig, PluginRuntime } from 'openclaw/plugin-sdk';
2
2
  import type { ResolvedYuanbaoAccount } from '../types.js';
3
3
  import type { YuanbaoWsClient } from '../yuanbao-server/ws/index.js';
4
+ import type { YuanbaoTraceContext } from '../trace/context.js';
4
5
  export type MessageHandlerContext = {
5
6
  groupCode?: string;
6
7
  account: ResolvedYuanbaoAccount;
@@ -17,6 +18,7 @@ export type MessageHandlerContext = {
17
18
  lastOutboundAt?: number;
18
19
  }) => void;
19
20
  wsClient: YuanbaoWsClient;
21
+ traceContext?: YuanbaoTraceContext;
20
22
  abortSignal?: AbortSignal;
21
23
  };
22
24
  export declare const YUANBAO_FINAL_TEXT_CHUNK_LIMIT = 3000;
@@ -1,19 +1,18 @@
1
1
  import { recordPendingHistoryEntryIfEnabled, buildPendingHistoryContextFromMap, clearHistoryEntriesIfEnabled, resolveControlCommandGate, } from 'openclaw/plugin-sdk/mattermost';
2
- import { downloadMediasToLocalFiles, downloadAndUploadMedia, guessMimeType, buildImageMsgBody, buildFileMsgBody } from '../media.js';
2
+ import { downloadMediasToLocalFiles } from '../media.js';
3
3
  import { resolveOutboundSenderAccount, rewriteSlashCommand, YUANBAO_FINAL_TEXT_CHUNK_LIMIT, } from './context.js';
4
4
  import { extractTextFromMsgBody } from './extract.js';
5
- import { sendYuanbaoMessage, sendYuanbaoMessageBody, sendYuanbaoGroupMessage, sendYuanbaoGroupMessageBody, sendMsgBodyDirect, executeReply, } from './outbound.js';
5
+ import { sendYuanbaoMessage, sendYuanbaoMessageBody, sendYuanbaoGroupMessageBody, executeReply, } from './outbound.js';
6
6
  import { buildOutboundMsgBody, prepareOutboundContent } from './handlers/index.js';
7
7
  import { parseQuoteFromCloudCustomData, formatQuoteContext } from './quote.js';
8
8
  import { getMember } from '../module/member.js';
9
- import { createLog, logger } from '../logger.js';
9
+ import { createLog } from '../logger.js';
10
10
  import { getOutboundQueue } from '../outbound-queue.js';
11
11
  import { UPGRADE_COMMAND_NAMES } from '../commands/upgrade.js';
12
- import { sendStickerYuanbao } from '../sticker/sticker-sender.js';
13
- import { getCachedSticker } from '../sticker/sticker-cache.js';
14
12
  import { dispatchSystemCallback } from './system-callbacks.js';
15
13
  import { chatHistories, chatMediaHistories, recordMediaHistory, } from './chat-history.js';
16
14
  import './callbacks/recall.js';
15
+ import { setGroupCode } from '../targets.js';
17
16
  const conversationQueues = new Map();
18
17
  function enqueueForConversation(key, task) {
19
18
  const prev = conversationQueues.get(key) ?? Promise.resolve();
@@ -114,6 +113,7 @@ async function handleC2CMessage(params) {
114
113
  const { core, config, account } = ctx;
115
114
  if (msg.private_from_group_code) {
116
115
  ctx.groupCode = msg.private_from_group_code;
116
+ setGroupCode(ctx.groupCode);
117
117
  }
118
118
  const fromAccount = msg.from_account?.trim() || 'unknown';
119
119
  const senderNickname = msg.sender_nickname?.trim() || undefined;
@@ -125,7 +125,6 @@ async function handleC2CMessage(params) {
125
125
  }
126
126
  const { rawBody, medias } = extractTextFromMsgBody(ctx, msg.msg_body);
127
127
  log.info(`收到消息 <- ${fromAccount}${senderNickname ? `(${senderNickname})` : ''}, msgKey: ${msg.msg_key}`);
128
- log.debug('消息内容', { user_input: rawBody });
129
128
  const quoteInfo = parseQuoteFromCloudCustomData(msg.cloud_custom_data);
130
129
  if (quoteInfo) {
131
130
  log.info(`检测到引用消息, 引用来自: ${quoteInfo.sender_nickname || quoteInfo.sender_id || 'unknown'}`);
@@ -178,7 +177,7 @@ async function handleC2CMessage(params) {
178
177
  await sendYuanbaoMessage({
179
178
  account,
180
179
  toAccount: fromAccount,
181
- text: '📦 正在导出 OpenClaw 日志并打包,请稍候...',
180
+ text: '📦 正在导出问题日志并压缩打包发送,请稍后...',
182
181
  fromAccount: outboundSender,
183
182
  ctx,
184
183
  });
@@ -220,6 +219,7 @@ async function handleC2CMessage(params) {
220
219
  envelope: envelopeOptions,
221
220
  body: bodyWithQuote,
222
221
  });
222
+ log.debug(`[msg-trace] inject c2c inbound context: traceId=${ctx.traceContext?.traceId ?? '(none)'}, traceparent=${ctx.traceContext?.traceparent ?? '(none)'}, seqId=${ctx.traceContext?.seqId ?? '(none)'}, from=${fromAccount}`);
223
223
  const ctxPayload = core.channel.reply.finalizeInboundContext({
224
224
  Body: body,
225
225
  RawBody: bodyWithQuote,
@@ -235,6 +235,9 @@ async function handleC2CMessage(params) {
235
235
  Provider: 'yuanbao',
236
236
  Surface: 'yuanbao',
237
237
  MessageSid: msg.msg_id,
238
+ TraceId: ctx.traceContext?.traceId,
239
+ Traceparent: ctx.traceContext?.traceparent,
240
+ SeqId: ctx.traceContext?.seqId,
238
241
  OriginatingChannel: 'yuanbao',
239
242
  OriginatingTo: `yuanbao:direct:${fromAccount}`,
240
243
  CommandAuthorized: commandAuthorized,
@@ -249,11 +252,7 @@ async function handleC2CMessage(params) {
249
252
  log.error('failed updating session meta', { error: String(err) });
250
253
  },
251
254
  });
252
- const tableMode = core.channel.text.resolveMarkdownTableMode({
253
- cfg: config,
254
- channel: 'yuanbao',
255
- accountId: account.accountId,
256
- });
255
+ const tableMode = 'off';
257
256
  const finalTextChunkLimit = core.channel.text.resolveTextChunkLimit(config, 'yuanbao', account.accountId, {
258
257
  fallbackLimit: YUANBAO_FINAL_TEXT_CHUNK_LIMIT,
259
258
  });
@@ -285,38 +284,6 @@ async function handleC2CMessage(params) {
285
284
  const msgId = msg.msg_id ?? String(msg.msg_seq ?? '');
286
285
  const queueManager = getOutboundQueue(account.accountId);
287
286
  if (queueManager) {
288
- const sendMediaOverride = async (url, fallbackText, mediaType, mediaLocalRoots) => {
289
- if (mediaType === 'sticker') {
290
- const sticker = getCachedSticker(url);
291
- if (!sticker) {
292
- return { ok: false, error: `sticker not found: ${url}` };
293
- }
294
- return sendStickerYuanbao({
295
- account,
296
- config,
297
- wsClient: ctx.wsClient,
298
- toAccount: fromAccount,
299
- sticker,
300
- core,
301
- });
302
- }
303
- try {
304
- logger.info('sendMediaOverride', { url, mediaLocalRoots });
305
- const uploadResult = await downloadAndUploadMedia(url, core, account, mediaLocalRoots);
306
- const mime = guessMimeType(uploadResult.filename);
307
- const msgBody = mime.startsWith('image/')
308
- ? buildImageMsgBody({ url: uploadResult.url, filename: uploadResult.filename, size: uploadResult.size, uuid: uploadResult.uuid, imageInfo: uploadResult.imageInfo })
309
- : buildFileMsgBody({ url: uploadResult.url, filename: uploadResult.filename, size: uploadResult.size });
310
- const result = await sendMsgBodyDirect({ account, config, target: fromAccount, msgBody: msgBody, wsClient: ctx.wsClient, core });
311
- return { ok: result.ok, error: result.error };
312
- }
313
- catch (err) {
314
- const errMsg = err instanceof Error ? err.message : String(err);
315
- log.error(`sendMediaOverride 失败: ${errMsg}`);
316
- const fallback = fallbackText ? `${fallbackText}\n${url}` : url;
317
- return sendYuanbaoMessage({ account, toAccount: fromAccount, text: fallback, fromAccount: outboundSender, ctx });
318
- }
319
- };
320
287
  queueManager.registerSession(outboundSessionKey, {
321
288
  msgId,
322
289
  chatType: 'c2c',
@@ -325,7 +292,6 @@ async function handleC2CMessage(params) {
325
292
  toAccount: fromAccount,
326
293
  fromAccount: outboundSender,
327
294
  ctx,
328
- sendMediaOverride,
329
295
  mergeOnFlush: account.disableBlockStreaming,
330
296
  });
331
297
  log.debug(`[${outboundSessionKey}] 出站队列 session 已注册,msgId: ${msgId}`);
@@ -356,19 +322,13 @@ async function handleGroupMessage(params) {
356
322
  const senderNickname = msg.sender_nickname?.trim() || undefined;
357
323
  const outboundSender = resolveOutboundSenderAccount(account);
358
324
  const glog = createLog('inbound', ctx.log);
325
+ setGroupCode(groupCode);
359
326
  if (outboundSender && fromAccount === outboundSender) {
360
327
  glog.info('跳过机器人自身消息', { groupCode, fromAccount });
361
328
  return;
362
329
  }
363
330
  const { rawBody, isAtBot, medias, mentions } = extractTextFromMsgBody(ctx, msg.msg_body);
364
- glog.info('收到群消息', {
365
- user_input: rawBody,
366
- groupCode,
367
- fromAccount,
368
- senderNickname,
369
- msgSeq: msg.msg_seq,
370
- isAtBot,
371
- });
331
+ glog.info(`收到群消息 <- group:${groupCode}, from: ${fromAccount}${senderNickname ? `(${senderNickname})` : ''}, msgSeq: ${msg.msg_seq}, isAtBot: ${isAtBot}`);
372
332
  const quoteInfo = parseQuoteFromCloudCustomData(msg.cloud_custom_data);
373
333
  if (quoteInfo) {
374
334
  glog.info(`群消息检测到引用消息, 引用来自: ${quoteInfo.sender_nickname || quoteInfo.sender_id || 'unknown'}`);
@@ -534,6 +494,7 @@ async function handleGroupMessage(params) {
534
494
  historyLimit,
535
495
  envelopeOptions,
536
496
  });
497
+ glog.debug(`[msg-trace] inject group inbound context: traceId=${ctx.traceContext?.traceId ?? '(none)'}, traceparent=${ctx.traceContext?.traceparent ?? '(none)'}, seqId=${ctx.traceContext?.seqId ?? '(none)'}, groupCode=${groupCode}`);
537
498
  const ctxPayload = core.channel.reply.finalizeInboundContext({
538
499
  Body: combinedBody,
539
500
  BodyForAgent: bodyWithQuote,
@@ -552,6 +513,9 @@ async function handleGroupMessage(params) {
552
513
  Provider: 'yuanbao',
553
514
  Surface: 'yuanbao',
554
515
  MessageSid: msg.msg_id ?? String(msg.msg_seq ?? ''),
516
+ TraceId: ctx.traceContext?.traceId,
517
+ Traceparent: ctx.traceContext?.traceparent,
518
+ SeqId: ctx.traceContext?.seqId,
555
519
  OriginatingChannel: 'yuanbao',
556
520
  OriginatingTo: `yuanbao:group:${groupCode}`,
557
521
  CommandAuthorized: commandAuthorized,
@@ -566,11 +530,7 @@ async function handleGroupMessage(params) {
566
530
  glog.error('failed updating group session meta', { error: String(err) });
567
531
  },
568
532
  });
569
- const tableMode = core.channel.text.resolveMarkdownTableMode({
570
- cfg: config,
571
- channel: 'yuanbao',
572
- accountId: account.accountId,
573
- });
533
+ const tableMode = 'off';
574
534
  const finalTextChunkLimit = core.channel.text.resolveTextChunkLimit(config, 'yuanbao', account.accountId, {
575
535
  fallbackLimit: YUANBAO_FINAL_TEXT_CHUNK_LIMIT,
576
536
  });
@@ -609,48 +569,6 @@ async function handleGroupMessage(params) {
609
569
  const groupMsgId = msg.msg_id ?? String(msg.msg_seq ?? '');
610
570
  const groupQueueManager = getOutboundQueue(account.accountId);
611
571
  if (groupQueueManager) {
612
- const groupSendMediaOverride = async (url, fallbackText, mediaType, mediaLocalRoots) => {
613
- if (mediaType === 'sticker') {
614
- const sticker = await getCachedSticker(url);
615
- if (!sticker) {
616
- return { ok: false, error: `sticker not found: ${url}` };
617
- }
618
- return sendStickerYuanbao({ account, config, wsClient: ctx.wsClient, toAccount: `group:${groupCode}`, sticker, refMsgId, core });
619
- }
620
- try {
621
- logger.info('groupSendMediaOverride', { url, mediaLocalRoots });
622
- const uploadResult = await downloadAndUploadMedia(url, core, account, mediaLocalRoots);
623
- const mime = guessMimeType(uploadResult.filename);
624
- const msgBody = mime.startsWith('image/')
625
- ? buildImageMsgBody({ url: uploadResult.url, filename: uploadResult.filename, size: uploadResult.size, uuid: uploadResult.uuid, imageInfo: uploadResult.imageInfo })
626
- : buildFileMsgBody({ url: uploadResult.url, filename: uploadResult.filename, size: uploadResult.size });
627
- const result = await sendMsgBodyDirect({
628
- account,
629
- config,
630
- target: `group:${groupCode}`,
631
- msgBody: msgBody,
632
- wsClient: ctx.wsClient,
633
- core,
634
- refMsgId,
635
- refFromAccount: fromAccount,
636
- });
637
- return { ok: result.ok, error: result.error };
638
- }
639
- catch (err) {
640
- const errMsg = err instanceof Error ? err.message : String(err);
641
- glog.error(`群 sendMediaOverride 失败: ${errMsg}`);
642
- const fallback = fallbackText ? `${fallbackText}\n${url}` : url;
643
- return sendYuanbaoGroupMessage({
644
- account,
645
- groupCode,
646
- text: fallback,
647
- fromAccount: outboundSender,
648
- refMsgId,
649
- refFromAccount: fromAccount,
650
- ctx,
651
- });
652
- }
653
- };
654
572
  groupQueueManager.registerSession(outboundGroupSessionKey, {
655
573
  msgId: groupMsgId,
656
574
  chatType: 'group',
@@ -661,7 +579,6 @@ async function handleGroupMessage(params) {
661
579
  refMsgId,
662
580
  refFromAccount: fromAccount,
663
581
  ctx,
664
- sendMediaOverride: groupSendMediaOverride,
665
582
  mergeOnFlush: account.disableBlockStreaming,
666
583
  });
667
584
  glog.debug(`[${outboundGroupSessionKey}] 群出站队列 session 已注册,msgId: ${groupMsgId}`);
@@ -4,6 +4,7 @@ import type { ResolvedYuanbaoAccount, YuanbaoMsgBodyElement } from '../types.js'
4
4
  import type { YuanbaoWsClient } from '../yuanbao-server/ws/index.js';
5
5
  import type { OutboundContentItem } from './handlers/index.js';
6
6
  import type { MessageHandlerContext } from './context.js';
7
+ import type { YuanbaoTraceContext } from '../trace/context.js';
7
8
  export declare function sendYuanbaoMessageBody(params: {
8
9
  account: ResolvedYuanbaoAccount;
9
10
  toAccount: string;
@@ -63,6 +64,7 @@ export declare function sendMsgBodyDirect(params: {
63
64
  refFromAccount?: string;
64
65
  wsClient: YuanbaoWsClient;
65
66
  core: PluginRuntime;
67
+ traceContext?: YuanbaoTraceContext;
66
68
  }): Promise<{
67
69
  ok: boolean;
68
70
  messageId?: string;