openclaw-plugin-yuanbao 2.7.1 → 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/index.js CHANGED
@@ -8,12 +8,20 @@ import { logUploadCommandDefinition } from './src/commands/log-upload.js';
8
8
  import { initEnv } from './src/utils/get-env.js';
9
9
  import { initBuiltinStickers } from './src/sticker/init-builtin-stickers.js';
10
10
  import pluginManifest from './openclaw.plugin.json' with { type: 'json' };
11
+ function patchCommandQueueState() {
12
+ const key = Symbol.for('openclaw.commandQueueState');
13
+ const state = globalThis[key];
14
+ if (state && !state.activeTaskWaiters) {
15
+ state.activeTaskWaiters = new Set();
16
+ }
17
+ }
11
18
  const plugin = {
12
19
  id: pluginManifest.id,
13
20
  name: pluginManifest.name,
14
21
  description: pluginManifest.description,
15
22
  configSchema: emptyPluginConfigSchema(),
16
23
  register(api) {
24
+ patchCommandQueueState();
17
25
  initEnv(api);
18
26
  initLogger(api);
19
27
  setYuanbaoRuntime(api.runtime);
@@ -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.7.1",
5
+ "version": "2.8.0",
6
6
  "channels": [
7
7
  "yuanbao"
8
8
  ],
@@ -91,7 +91,7 @@ export function resolveYuanbaoAccount(params) {
91
91
  : 100;
92
92
  const disableBlockStreaming = merged.disableBlockStreaming !== undefined ? merged.disableBlockStreaming : false;
93
93
  const requireMention = merged.requireMention !== undefined ? merged.requireMention : true;
94
- const fallbackReply = merged.fallbackReply?.trim() || '暂时无法解答,你可以换个问题问问我哦';
94
+ const fallbackReply = merged.fallbackReply?.trim();
95
95
  const markdownHintEnabled = merged.markdownHintEnabled !== false;
96
96
  const configured = Boolean(appKey && appSecret);
97
97
  if (!configured && Boolean(yuanbaoConfig)) {
@@ -167,7 +167,8 @@ export const yuanbaoPlugin = {
167
167
  blockStreamingChunkMaxChars: 3000,
168
168
  blockStreamingCoalesceDefaults: {
169
169
  minChars: 2800,
170
- idleMs: 5000,
170
+ idleMs: 1000,
171
+ joiner: '',
171
172
  },
172
173
  },
173
174
  outbound: {
@@ -176,10 +177,10 @@ export const yuanbaoPlugin = {
176
177
  textChunkLimit: 3000,
177
178
  chunker: (text, limit) => getYuanbaoRuntime()?.channel.text.chunkMarkdownText(text, limit) ?? [text],
178
179
  sendText: async (params) => {
179
- const slog = createLog('channel.utbound');
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
184
  slog.info('sendText', { accountId, to });
184
185
  const wsClient = getActiveWsClient(account.accountId);
185
186
  if (!wsClient) {
@@ -202,10 +203,10 @@ export const yuanbaoPlugin = {
202
203
  return toChannelResult(await sendTextToTarget(account, to, text, wsClient));
203
204
  },
204
205
  sendMedia: async (params) => {
205
- const slog = createLog('channel.outbound');
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
210
  const wsClient = getActiveWsClient(account.accountId);
210
211
  slog.info('sendMedia', { accountId, to, mediaUrl, text });
211
212
  if (!wsClient) {
@@ -269,7 +270,7 @@ export const yuanbaoPlugin = {
269
270
  gateway: {
270
271
  startAccount: async (ctx) => {
271
272
  const { account } = ctx;
272
- const slog = createLog('gateway', ctx.log);
273
+ const slog = createLog('gateway');
273
274
  slog.debug('启动账号', account);
274
275
  if (!account.configured) {
275
276
  slog.warn('yuanbao not configured; skipping');
@@ -295,7 +296,6 @@ export const yuanbaoPlugin = {
295
296
  account,
296
297
  config: ctx.cfg,
297
298
  abortSignal: ctx.abortSignal,
298
- log: ctx.log,
299
299
  runtime: getYuanbaoRuntime(),
300
300
  statusSink: patch => ctx.setStatus({ accountId: ctx.accountId, ...patch }),
301
301
  });
@@ -72,7 +72,6 @@ export async function sendDM(to, text, opts) {
72
72
  account,
73
73
  config: {},
74
74
  core: {},
75
- log: { info: () => { }, warn: () => { }, error: () => { }, verbose: () => { } },
76
75
  wsClient,
77
76
  };
78
77
  const result = await sendYuanbaoMessage({
@@ -8,7 +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
11
  export interface LogSink {
13
12
  info?: (msg: string) => void;
14
13
  warn?: (msg: string) => void;
@@ -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 verboseEnabled = false;
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,12 +71,11 @@ export const logger = {
52
71
  getActiveLogger().debug(message, meta);
53
72
  },
54
73
  };
55
- export function isVerbose() {
56
- return verboseEnabled;
57
- }
58
74
  export function formatLog(module, msg, data) {
59
75
  const prefix = module ? `${LOG_PREFIX}[${module}]` : LOG_PREFIX;
60
- return data !== undefined ? `${prefix} ${msg} ${sanitize(data)}` : `${prefix} ${msg}`;
76
+ if (data === undefined)
77
+ return `${prefix} ${msg}`;
78
+ return `${prefix} ${msg} ${sanitize(data)}`;
61
79
  }
62
80
  export function createLog(module, sink) {
63
81
  const target = sink ?? logger;
@@ -71,21 +89,6 @@ export function createLog(module, sink) {
71
89
  debug: (msg, data) => (target.debug ?? target.verbose)?.(fmt(msg, data)),
72
90
  };
73
91
  }
74
- const OMIT_KEYS = new Set(['msg_body']);
75
- const SENSITIVE_KEYS = new Set([
76
- 'token',
77
- 'signature',
78
- 'app_key',
79
- 'appkey',
80
- 'appsecret',
81
- 'app_secret',
82
- 'secret',
83
- 'password',
84
- 'x-token',
85
- 'user_input',
86
- 'cloud_custom_data',
87
- 'model_output',
88
- ]);
89
92
  function maskValue(value) {
90
93
  if (value.length < 8)
91
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', ctx.log);
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', ctx.log);
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', ctx.log).info('@消息', { text: customContent?.text, userId: customContent?.user_id, isAtBot: resData.isAtBot });
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 (text.length) {
80
- const trailing = text.trim();
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 && text.trim()) {
86
- items.push(...resolveAtMentions(text.trim(), groupCode, memberInst));
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', ctx.log);
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', ctx.log);
339
+ const glog = createLog('inbound');
340
340
  setGroupCode(groupCode);
341
341
  if (outboundSender && fromAccount === outboundSender) {
342
342
  glog.info('跳过机器人自身消息', { groupCode, fromAccount });
@@ -34,7 +34,7 @@ async function shouldAttachReplyRef(params) {
34
34
  }
35
35
  export async function sendYuanbaoMessageBody(params) {
36
36
  const { toAccount, msgBody, fromAccount, ctx } = params;
37
- const log = createLog('outbound', ctx?.log);
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', ctx?.log);
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', ctx.log);
156
+ const rlog = createLog('outbound');
157
157
  if (ctx.abortSignal?.aborted) {
158
158
  rlog.warn(`[${account.accountId}] 回复已中止,跳过执行`);
159
159
  return;
@@ -165,7 +165,6 @@ export async function executeReply(params) {
165
165
  : null;
166
166
  const collectedTexts = [];
167
167
  let hasFinalInfo = false;
168
- let prevDeliverKind = null;
169
168
  const dispatchReply = () => core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
170
169
  ctx: ctxPayload,
171
170
  cfg: replyRuntime.config,
@@ -174,17 +173,6 @@ export async function executeReply(params) {
174
173
  onAgentRunStart: () => {
175
174
  session?.emitReplyHeartbeat(WS_HEARTBEAT.RUNNING);
176
175
  },
177
- onAssistantMessageStart: async () => {
178
- session?.emitReplyHeartbeat(WS_HEARTBEAT.RUNNING);
179
- rlog.info('[OpenClaw] onAssistantMessageStart');
180
- try {
181
- if (session)
182
- await session.drainNow();
183
- }
184
- catch (err) {
185
- rlog.error('[OpenClaw] onAssistantMessageStart drainNow 失败,跳过', { error: String(err) });
186
- }
187
- },
188
176
  onToolStart: async () => {
189
177
  rlog.info('[OpenClaw] onToolStart');
190
178
  try {
@@ -203,6 +191,10 @@ export async function executeReply(params) {
203
191
  return;
204
192
  }
205
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
+ }
206
198
  if (payload.isCompactionNotice) {
207
199
  rlog.info('[deliver] CompactionNotice', { text: payload.text });
208
200
  return;
@@ -213,13 +205,10 @@ export async function executeReply(params) {
213
205
  if (info.kind === 'final') {
214
206
  hasFinalInfo = true;
215
207
  }
216
- const prevKind = prevDeliverKind;
217
- prevDeliverKind = info.kind;
218
208
  const text = payload.text ?? '';
219
209
  if (session) {
220
210
  if (text.trim()) {
221
- const isAfterToolCall = info.kind === 'block' && prevKind !== null && prevKind !== 'block';
222
- await session.push({ type: 'text', text: isAfterToolCall ? `\n\n${text}` : text });
211
+ await session.push({ type: 'text', text });
223
212
  }
224
213
  const mediaUrls = payload.mediaUrls ?? [];
225
214
  for (const mediaUrl of mediaUrls) {
@@ -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
  };
@@ -74,6 +74,8 @@ export declare function getOutboundQueue(accountId: string): OutboundQueueManage
74
74
  export declare function destroyOutboundQueue(accountId: string): void;
75
75
  export declare function endsWithTableRow(text: string): boolean;
76
76
  export declare function hasUnclosedFence(text: string): boolean;
77
+ export declare function startsWithBlockElement(text: string): boolean;
78
+ export declare function inferBlockSeparator(buffer: string, incoming: string): string;
77
79
  export type AtomicBlock = {
78
80
  start: number;
79
81
  end: number;
@@ -303,7 +303,7 @@ function createMergeOnFlushSession(callbacks, msgId, onComplete, log, heartbeatM
303
303
  if (aborted)
304
304
  return hasSentContent;
305
305
  if (collectedTexts.length > 0) {
306
- const merged = stripOuterMarkdownFence(collectedTexts.join('\n\n'));
306
+ const merged = stripOuterMarkdownFence(collectedTexts.join(''));
307
307
  collectedTexts.length = 0;
308
308
  if (merged.trim()) {
309
309
  const result = await sendText(merged);
@@ -377,6 +377,36 @@ export function hasUnclosedFence(text) {
377
377
  }
378
378
  return inFence;
379
379
  }
380
+ export function startsWithBlockElement(text) {
381
+ const firstLine = (text.trimStart().split('\n')[0] ?? '').trimStart();
382
+ return /^#{1,6}\s/.test(firstLine)
383
+ || firstLine.startsWith('---')
384
+ || firstLine.startsWith('***')
385
+ || firstLine.startsWith('___')
386
+ || firstLine.startsWith('> ')
387
+ || firstLine.startsWith('```')
388
+ || /^[*\-+]\s/.test(firstLine)
389
+ || /^\d+[.)]\s/.test(firstLine)
390
+ || firstLine.startsWith('|');
391
+ }
392
+ export function inferBlockSeparator(buffer, incoming) {
393
+ if (hasUnclosedFence(buffer))
394
+ return '';
395
+ if (buffer.endsWith('\n\n'))
396
+ return '';
397
+ const lastLine = (buffer.trimEnd().split('\n')
398
+ .at(-1) ?? '').trim();
399
+ const firstLine = (incoming.trimStart().split('\n')[0] ?? '').trimStart();
400
+ if (lastLine.startsWith('|') && !firstLine.startsWith('|')
401
+ && firstLine.endsWith('|')) {
402
+ return ' ';
403
+ }
404
+ if (lastLine.startsWith('|') && firstLine.startsWith('|'))
405
+ return '\n';
406
+ if (startsWithBlockElement(incoming))
407
+ return '\n\n';
408
+ return '';
409
+ }
380
410
  const DIAGRAM_LANGUAGES = new Set([
381
411
  'mermaid', 'plantuml', 'sequence', 'flowchart',
382
412
  'gantt', 'classdiagram', 'statediagram', 'erdiagram',
@@ -396,7 +426,7 @@ export function extractAtomicBlocks(text) {
396
426
  const isTableLine = (line) => line.trim().startsWith('|');
397
427
  const isTableSeparator = (line) => /^\|[\s|:-]+\|$/.test(line.trim());
398
428
  const flushTable = () => {
399
- if (tableStart !== -1 && tableHasSep && tableEnd !== -1) {
429
+ if (tableStart !== -1 && tableEnd !== -1 && (tableHasSep || tableLineCount >= 2)) {
400
430
  blocks.push({ start: tableStart, end: tableEnd, kind: 'table' });
401
431
  }
402
432
  tableStart = -1;
@@ -582,14 +612,8 @@ function createMergeTextSession(callbacks, msgId, sessionKey, onComplete, log, o
582
612
  if (!item.text.trim())
583
613
  return;
584
614
  if (textBuffer) {
585
- const lines = textBuffer.trimEnd().split('\n');
586
- const lastLineOfBuffer = (lines[lines.length - 1] ?? '').trim();
587
- const incomingStartsWithTableRow = item.text.trimStart().startsWith('|');
588
- const needsSeparator = !hasUnclosedFence(textBuffer)
589
- && incomingStartsWithTableRow
590
- && !textBuffer.endsWith('\n')
591
- && !lastLineOfBuffer.startsWith('|');
592
- textBuffer = mergeBlockStreamingFences(needsSeparator ? `${textBuffer}\n\n` : textBuffer, item.text);
615
+ const separator = inferBlockSeparator(textBuffer, item.text);
616
+ textBuffer = mergeBlockStreamingFences(separator ? `${textBuffer}${separator}` : textBuffer, item.text);
593
617
  }
594
618
  else {
595
619
  textBuffer = item.text;
@@ -32,6 +32,7 @@ export type YuanbaoConfig = YuanbaoAccountConfig & {
32
32
  accounts?: Record<string, YuanbaoAccountConfig>;
33
33
  defaultAccount?: string;
34
34
  routeEnv?: string;
35
+ debug?: boolean;
35
36
  };
36
37
  export type ResolvedYuanbaoAccount = {
37
38
  accountId: string;
@@ -134,6 +135,12 @@ export type CloudCustomData = {
134
135
  source_group?: string;
135
136
  [key: string]: unknown;
136
137
  };
138
+ export type YuanbaoBufferedDeliverPayload = {
139
+ text?: string;
140
+ mediaUrls?: string[];
141
+ isCompactionNotice: boolean;
142
+ isReasoning: boolean;
143
+ };
137
144
  export type YuanbaoSendMsgRequest = {
138
145
  sync_other_machine?: number;
139
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 { Log, CosUploadConfig } from './request.js';
3
- export declare function apiGetUploadInfo(account: ResolvedYuanbaoAccount, fileName: string, fileId: string, log?: Log): Promise<CosUploadConfig>;
4
- export declare function apiGetDownloadUrl(account: ResolvedYuanbaoAccount, resourceId: string, log?: Log): Promise<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, log) {
5
- const data = await yuanbaoPost(account, UPLOAD_INFO_PATH, { fileName, fileId, docFrom: 'localDoc', docOpenId: '' }, log);
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, log) {
12
- const data = await yuanbaoGet(account, DOWNLOAD_INFO_PATH, { resourceId }, log);
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, log?: Log): Promise<SignTokenData>;
48
- export declare function forceRefreshSignToken(account: ResolvedYuanbaoAccount, log?: Log): Promise<SignTokenData>;
49
- export declare function getAuthHeaders(account: ResolvedYuanbaoAccount, log?: Log): Promise<AuthHeaders>;
50
- export declare function yuanbaoPost<T>(account: ResolvedYuanbaoAccount, path: string, body: unknown, log?: Log): Promise<T>;
51
- export declare function yuanbaoGet<T>(account: ResolvedYuanbaoAccount, path: string, params?: Record<string, string>, log?: Log): Promise<T>;
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, log) {
63
- const mlog = createLog('http', log);
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, log) {
110
- const mlog = createLog('http', log);
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, log);
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, log);
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, log) {
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', log);
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, log);
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, log);
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, log) {
185
- const flog = createLog('http', log);
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, log);
189
+ return getSignToken(account);
190
190
  }
191
- export async function getAuthHeaders(account, log) {
192
- const data = await getSignToken(account, log);
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, log) {
211
- const plog = createLog('http', log);
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, log);
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, log);
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, log) {
241
- const glog = createLog('http', log);
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, log);
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, log);
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', params.log);
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, log?: GatewayLog): InboundResult | null;
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, log, runtime, statusSink } = params;
10
- const gwlog = createLog('ws', log);
11
- const auth = await resolveWsAuth(account, log);
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, log, runtime, client, statusSink, abortSignal });
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, log);
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, log) {
98
- const mlog = createLog('ws', log);
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, log);
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, pushType) {
154
+ function decodeFromProtobuf(rawData) {
161
155
  const decoded = decodeInboundMessage(rawData);
162
156
  if (!decoded || !hasValidMsgFields(decoded))
163
157
  return null;
164
- createLog('ws').debug(`[${pushType}] WS 推送事件解析`, { ...decoded });
158
+ createLog('ws').debug('[Protobuf] WS 推送事件解析', { ...decoded });
165
159
  return { msg: decoded, chatType: inferChatType(decoded) };
166
160
  }
167
- function decodeFromRawDataJson(rawData, pushType) {
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(`[${pushType}] WS 推送事件解析`, { ...msg });
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, log) {
203
+ export function wsPushToInboundMessage(pushEvent) {
210
204
  if (pushEvent.connData && pushEvent.connData.length > 0) {
211
- createLog('ws', log).debug(`[${pushEvent.type}] WS 推送事件解析 type=connData (connData.length=${pushEvent.connData.length})`);
212
- const pushType = String(pushEvent.type ?? '');
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 pushType = String(pushEvent.type ?? 'rawData');
219
- createLog('ws', log).debug(`[${pushType}] WS 推送事件解析`);
220
- const result = decodeFromProtobuf(pushEvent.rawData, pushType)
221
- ?? decodeFromRawDataJson(pushEvent.rawData, pushType);
222
- if (result)
223
- return result;
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', log).debug(`[${pushEvent.type || 'content'}] WS 推送事件解析, type=content`, { content: pushEvent.content });
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, log: gwLog, runtime, client, statusSink, abortSignal } = params;
234
- const dlog = createLog('ws', gwLog);
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, gwLog);
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,
@@ -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.7.1",
5
+ "version": "2.8.0",
6
6
  "channels": [
7
7
  "yuanbao"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-plugin-yuanbao",
3
- "version": "2.7.1",
3
+ "version": "2.8.0",
4
4
  "type": "module",
5
5
  "description": "Tencent YuanBao intelligent bot channel plugin",
6
6
  "license": "MIT",