openclaw-plugin-yuanbao 2.7.2 → 2.9.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 (42) hide show
  1. package/dist/openclaw.plugin.json +1 -1
  2. package/dist/src/channel.js +73 -38
  3. package/dist/src/commands/upgrade/upgrade.js +55 -9
  4. package/dist/src/commands/upgrade/utils.d.ts +3 -1
  5. package/dist/src/commands/upgrade/utils.js +11 -9
  6. package/dist/src/config-schema.js +0 -7
  7. package/dist/src/directory-adapter.d.ts +2 -0
  8. package/dist/src/directory-adapter.js +58 -0
  9. package/dist/src/dm/send-dm.js +0 -1
  10. package/dist/src/logger.d.ts +2 -7
  11. package/dist/src/logger.js +25 -49
  12. package/dist/src/media.d.ts +1 -0
  13. package/dist/src/media.js +134 -4
  14. package/dist/src/message-handler/callbacks/recall.js +2 -2
  15. package/dist/src/message-handler/context.d.ts +0 -6
  16. package/dist/src/message-handler/handlers/custom.js +1 -1
  17. package/dist/src/message-handler/handlers/index.js +7 -4
  18. package/dist/src/message-handler/inbound.js +5 -4
  19. package/dist/src/message-handler/outbound.js +8 -4
  20. package/dist/src/message-tool/action-runtime.js +0 -1
  21. package/dist/src/message-tool/hints.js +3 -3
  22. package/dist/src/messaging-adapter.d.ts +4 -0
  23. package/dist/src/messaging-adapter.js +31 -0
  24. package/dist/src/module/member.d.ts +5 -0
  25. package/dist/src/module/member.js +48 -0
  26. package/dist/src/outbound-queue.d.ts +0 -12
  27. package/dist/src/outbound-queue.js +1 -179
  28. package/dist/src/types.d.ts +7 -1
  29. package/dist/src/utils/markdown-stream.d.ts +14 -0
  30. package/dist/src/utils/markdown-stream.js +242 -0
  31. package/dist/src/utils/markdown-table-sanitize.d.ts +1 -0
  32. package/dist/src/utils/markdown-table-sanitize.js +89 -0
  33. package/dist/src/yuanbao-server/http/main.d.ts +3 -3
  34. package/dist/src/yuanbao-server/http/main.js +4 -4
  35. package/dist/src/yuanbao-server/http/request.d.ts +5 -5
  36. package/dist/src/yuanbao-server/http/request.js +23 -23
  37. package/dist/src/yuanbao-server/ws/client.d.ts +0 -2
  38. package/dist/src/yuanbao-server/ws/client.js +1 -1
  39. package/dist/src/yuanbao-server/ws/gateway.d.ts +1 -8
  40. package/dist/src/yuanbao-server/ws/gateway.js +25 -39
  41. package/openclaw.plugin.json +1 -1
  42. 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.7.2",
5
+ "version": "2.9.0",
6
6
  "channels": [
7
7
  "yuanbao"
8
8
  ],
@@ -1,16 +1,19 @@
1
1
  import { DEFAULT_ACCOUNT_ID, deleteAccountFromConfigSection, setAccountEnabledInConfigSection, } from 'openclaw/plugin-sdk/core';
2
2
  import { formatPairingApproveHint } from 'openclaw/plugin-sdk/mattermost';
3
- import { listYuanbaoAccountIds, resolveDefaultYuanbaoAccountId, resolveYuanbaoAccount } from './accounts.js';
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, setDebugBotIds } from './logger.js';
6
+ import { createLog } from './logger.js';
7
7
  import { yuanbaoSetupAdapter } from './setup.js';
8
- import { startYuanbaoWsGateway, getActiveWsClient } from './yuanbao-server/ws/index.js';
8
+ import { startYuanbaoWsGateway, getActiveWsClient, } from './yuanbao-server/ws/index.js';
9
9
  import { getYuanbaoRuntime } from './runtime.js';
10
- import { sendYuanbaoMessage, sendYuanbaoGroupMessage } from './message-handler/index.js';
11
- import { initOutboundQueue, destroyOutboundQueue, getOutboundQueue } from './outbound-queue.js';
10
+ import { sendYuanbaoMessage, sendYuanbaoGroupMessage, } from './message-handler/index.js';
11
+ import { initOutboundQueue, destroyOutboundQueue, getOutboundQueue, } from './outbound-queue.js';
12
12
  import { ChatType, getGroupCode, parseTarget } from './targets.js';
13
- import { buildMessageToolHints, yuanbaoMessageActions } from './message-tool/index.js';
13
+ import { buildMessageToolHints, yuanbaoMessageActions, } from './message-tool/index.js';
14
+ import { validateMediaBeforeQueue } from './media.js';
15
+ import { yuanbaoMessagingAdapter } from './messaging-adapter.js';
16
+ import { yuanbaoDirectoryAdapter } from './directory-adapter.js';
14
17
  function toChannelResult(result) {
15
18
  return {
16
19
  channel: 'yuanbao',
@@ -35,15 +38,32 @@ async function sendTextToTarget(account, target, text, wsClient) {
35
38
  account,
36
39
  config: {},
37
40
  core: {},
38
- log: { info: () => { }, warn: () => { }, error: () => { }, verbose: () => { } },
41
+ log: {
42
+ info: () => { },
43
+ warn: () => { },
44
+ error: () => { },
45
+ verbose: () => { },
46
+ },
39
47
  wsClient,
40
48
  }
41
49
  : undefined;
42
50
  const { chatType, target: targetId } = parseTarget(target, account.accountId);
43
51
  if (chatType === ChatType.GROUP) {
44
- return sendYuanbaoGroupMessage({ account, groupCode: targetId, text, fromAccount: account.botId, ctx: minCtx });
52
+ return sendYuanbaoGroupMessage({
53
+ account,
54
+ groupCode: targetId,
55
+ text,
56
+ fromAccount: account.botId,
57
+ ctx: minCtx,
58
+ });
45
59
  }
46
- return sendYuanbaoMessage({ account, toAccount: targetId, text, fromAccount: account.botId, ctx: minCtx });
60
+ return sendYuanbaoMessage({
61
+ account,
62
+ toAccount: targetId,
63
+ text,
64
+ fromAccount: account.botId,
65
+ ctx: minCtx,
66
+ });
47
67
  }
48
68
  const meta = {
49
69
  id: 'yuanbao',
@@ -57,12 +77,6 @@ const meta = {
57
77
  order: 85,
58
78
  quickstartAllowFrom: true,
59
79
  };
60
- function normalizeYuanbaoMessagingTarget(raw) {
61
- const trimmed = raw.trim();
62
- if (!trimmed)
63
- return undefined;
64
- return trimmed.replace(/^(yuanbao):/i, '').trim() || undefined;
65
- }
66
80
  export const yuanbaoPlugin = {
67
81
  id: 'yuanbao',
68
82
  meta,
@@ -117,7 +131,10 @@ export const yuanbaoPlugin = {
117
131
  tokenStatus: account.configured ? 'available' : 'missing',
118
132
  }),
119
133
  resolveAllowFrom: ({ cfg, accountId }) => {
120
- const account = resolveYuanbaoAccount({ cfg: cfg, accountId });
134
+ const account = resolveYuanbaoAccount({
135
+ cfg: cfg,
136
+ accountId,
137
+ });
121
138
  return (account.config.dm?.allowFrom ?? []).map(entry => String(entry));
122
139
  },
123
140
  formatAllowFrom: ({ allowFrom }) => allowFrom
@@ -129,7 +146,9 @@ export const yuanbaoPlugin = {
129
146
  resolveDmPolicy: ({ cfg, accountId, account }) => {
130
147
  const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
131
148
  const useAccountPath = Boolean(cfg.channels?.yuanbao?.accounts?.[resolvedAccountId]);
132
- const basePath = useAccountPath ? `channels.yuanbao.accounts.${resolvedAccountId}.` : 'channels.yuanbao.';
149
+ const basePath = useAccountPath
150
+ ? `channels.yuanbao.accounts.${resolvedAccountId}.`
151
+ : 'channels.yuanbao.';
133
152
  const policy = account.config.dm?.policy ?? 'open';
134
153
  const rawAllowFrom = (account.config.dm?.allowFrom ?? []).map(entry => String(entry));
135
154
  const allowFrom = policy === 'open' && !rawAllowFrom.includes('*')
@@ -151,13 +170,8 @@ export const yuanbaoPlugin = {
151
170
  threading: {
152
171
  resolveReplyToMode: () => 'all',
153
172
  },
154
- messaging: {
155
- normalizeTarget: normalizeYuanbaoMessagingTarget,
156
- targetResolver: {
157
- looksLikeId: raw => Boolean(raw.trim()),
158
- hint: '<userid> or group:<groupcode>',
159
- },
160
- },
173
+ messaging: yuanbaoMessagingAdapter,
174
+ directory: yuanbaoDirectoryAdapter,
161
175
  agentPrompt: {
162
176
  messageToolHints() {
163
177
  return buildMessageToolHints();
@@ -175,16 +189,23 @@ export const yuanbaoPlugin = {
175
189
  deliveryMode: 'direct',
176
190
  chunkerMode: 'markdown',
177
191
  textChunkLimit: 3000,
178
- chunker: (text, limit) => getYuanbaoRuntime()?.channel.text.chunkMarkdownText(text, limit) ?? [text],
192
+ chunker: (text, limit) => getYuanbaoRuntime()?.channel.text.chunkMarkdownText(text, limit) ?? [
193
+ text,
194
+ ],
179
195
  sendText: async (params) => {
180
196
  const { cfg, accountId, to: _to, text } = params;
181
197
  const to = _to.replace(/^yuanbao:/, '');
182
198
  const account = resolveYuanbaoAccount({ cfg, accountId: accountId ?? undefined });
183
- const slog = createLog('channel.outbound', undefined, { botId: account.botId });
199
+ const slog = createLog('channel.outbound');
184
200
  slog.info('sendText', { accountId, to });
185
201
  const wsClient = getActiveWsClient(account.accountId);
186
202
  if (!wsClient) {
187
- return { channel: 'yuanbao', ok: false, messageId: '', error: new Error(`WebSocket client not connected for account ${account.accountId}`) };
203
+ return {
204
+ channel: 'yuanbao',
205
+ ok: false,
206
+ messageId: '',
207
+ error: new Error(`WebSocket client not connected for account ${account.accountId}`),
208
+ };
188
209
  }
189
210
  const queueManager = getOutboundQueue(account.accountId);
190
211
  if (queueManager) {
@@ -203,18 +224,28 @@ export const yuanbaoPlugin = {
203
224
  return toChannelResult(await sendTextToTarget(account, to, text, wsClient));
204
225
  },
205
226
  sendMedia: async (params) => {
206
- const { cfg, accountId, to: _to, mediaUrl, text, mediaLocalRoots } = params;
227
+ const { cfg, accountId, to: _to, mediaUrl, text, mediaLocalRoots, } = params;
207
228
  const to = _to.replace(/^yuanbao:/, '');
208
229
  const account = resolveYuanbaoAccount({ cfg, accountId: accountId ?? undefined });
209
- const slog = createLog('channel.outbound', undefined, { botId: account.botId });
230
+ const slog = createLog('channel.outbound');
210
231
  const wsClient = getActiveWsClient(account.accountId);
211
232
  slog.info('sendMedia', { accountId, to, mediaUrl, text });
212
233
  if (!wsClient) {
213
- return { channel: 'yuanbao', ok: false, messageId: '', error: new Error(`WebSocket client not connected for account ${account.accountId}`) };
234
+ return {
235
+ channel: 'yuanbao',
236
+ ok: false,
237
+ messageId: '',
238
+ error: new Error(`WebSocket client not connected for account ${account.accountId}`),
239
+ };
214
240
  }
215
241
  if (!mediaUrl) {
216
242
  return { channel: 'yuanbao', ok: true, messageId: '' };
217
243
  }
244
+ const validationError = validateMediaBeforeQueue(mediaUrl);
245
+ if (validationError) {
246
+ slog.error(`sendMedia 前置校验失败: ${validationError}`, { accountId, to, mediaUrl });
247
+ return { channel: 'yuanbao', ok: false, messageId: '', error: new Error(validationError) };
248
+ }
218
249
  const queueManager = getOutboundQueue(account.accountId);
219
250
  if (queueManager) {
220
251
  const { chatType, target, sessionKey } = parseTarget(to, account.accountId);
@@ -232,7 +263,12 @@ export const yuanbaoPlugin = {
232
263
  await session.flush();
233
264
  return { channel: 'yuanbao', ok: true, messageId: '' };
234
265
  }
235
- return { channel: 'yuanbao', ok: false, messageId: '', error: new Error('No session found') };
266
+ return {
267
+ channel: 'yuanbao',
268
+ ok: false,
269
+ messageId: '',
270
+ error: new Error('No session found'),
271
+ };
236
272
  },
237
273
  },
238
274
  status: {
@@ -270,15 +306,15 @@ export const yuanbaoPlugin = {
270
306
  gateway: {
271
307
  startAccount: async (ctx) => {
272
308
  const { account } = ctx;
273
- const yuanbaoTopConfig = ctx.cfg.channels?.yuanbao;
274
- if (yuanbaoTopConfig?.debugBotIds?.length) {
275
- setDebugBotIds(yuanbaoTopConfig.debugBotIds);
276
- }
277
- const slog = createLog('gateway', ctx.log, { botId: account.botId });
309
+ const slog = createLog('gateway');
278
310
  slog.debug('启动账号', account);
279
311
  if (!account.configured) {
280
312
  slog.warn('yuanbao not configured; skipping');
281
- ctx.setStatus({ accountId: account.accountId, running: false, configured: false });
313
+ ctx.setStatus({
314
+ accountId: account.accountId,
315
+ running: false,
316
+ configured: false,
317
+ });
282
318
  return;
283
319
  }
284
320
  slog.info('使用 WebSocket 模式连接');
@@ -300,7 +336,6 @@ export const yuanbaoPlugin = {
300
336
  account,
301
337
  config: ctx.cfg,
302
338
  abortSignal: ctx.abortSignal,
303
- log: ctx.log,
304
339
  runtime: getYuanbaoRuntime(),
305
340
  statusSink: patch => ctx.setStatus({ accountId: ctx.accountId, ...patch }),
306
341
  });
@@ -5,8 +5,32 @@ const INSTALL_SCRIPT_TIMEOUT_MS = 5 * 60 * 1000;
5
5
  var MessageEnum;
6
6
  (function (MessageEnum) {
7
7
  MessageEnum["REPAIR_BOT_CONFIG_GUIDE"] = "\u274C \u5347\u7EA7\u5931\u8D25\uFF0C\u8BF7\u524D\u5F80 Bot \u7BA1\u7406\u9875\u9762\u4F7F\u7528\u300C\u4FEE\u590D Bot \u914D\u7F6E\u300D\u529F\u80FD\u4FEE\u590D\u3002";
8
- MessageEnum["AUTO_UPGRADE_FAILED_FALLBACK"] = "\u274C \u5347\u7EA7\u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF0C\u5143\u5B9D\u521B\u5EFA\u7684 Bot \u53EF\u524D\u5F80\u300CBot \u8BBE\u7F6E\u300D\u70B9\u51FB\u300C\u66F4\u65B0\u63D2\u4EF6\u300D\u8FDB\u884C\u5347\u7EA7\u3002";
8
+ MessageEnum["AUTO_UPGRADE_FAILED_FALLBACK"] = "\u274C \u5347\u7EA7\u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF0C\u5143\u5B9D\u521B\u5EFA\u7684 Bot \u53EF\u524D\u5F80\u300CBot \u8BBE\u7F6E\u300D\u70B9\u51FB\u300C\u66F4\u65B0\u63D2\u4EF6\u300D\u8FDB\u884C\u5347\u7EA7\uFF0C\u975E\u5143\u5B9D\u521B\u5EFA\u7684Bot\uFF0C\u8BF7\u524D\u5F80\u5404\u81EA\u5E73\u53F0\u624B\u52A8\u66F4\u65B0\u3002";
9
+ MessageEnum["RATE_LIMITED"] = "\u274C \u5347\u7EA7\u5931\u8D25\uFF0C\u5F53\u524D\u670D\u52A1\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u3002\u5143\u5B9D\u521B\u5EFA\u7684 Bot \u53EF\u524D\u5F80\u300CBot \u8BBE\u7F6E\u300D\u70B9\u51FB\u300C\u66F4\u65B0\u63D2\u4EF6\u300D\u8FDB\u884C\u5347\u7EA7\u3002";
9
10
  })(MessageEnum || (MessageEnum = {}));
11
+ async function verifyVersionAfterFailedCommand(params) {
12
+ const { currentVersion, targetVersion, commandResult, commandName } = params;
13
+ const installedVersion = await readInstalledVersion(PLUGIN_ID);
14
+ const upgraded = !!installedVersion && ((targetVersion != null && installedVersion === targetVersion)
15
+ || (targetVersion == null && currentVersion != null && installedVersion !== currentVersion));
16
+ if (upgraded) {
17
+ log.warn(`${commandName} 安装命令执行异常,但版本已更新成功`, {
18
+ currentVersion,
19
+ targetVersion,
20
+ installedVersion,
21
+ error: commandResult.error,
22
+ });
23
+ return { upgraded: true, installedVersion };
24
+ }
25
+ log.error(`${commandName} 执行失败`, {
26
+ currentVersion,
27
+ targetVersion,
28
+ installedVersion: installedVersion ?? '(读取失败)',
29
+ error: commandResult.error,
30
+ rateLimited: !!commandResult.rateLimited,
31
+ });
32
+ return { upgraded: false, installedVersion, rateLimited: !!commandResult.rateLimited };
33
+ }
10
34
  async function runSpecifiedVersionFlow(params) {
11
35
  const { targetVersion: _targetVersion, currentVersion, config, onProgress, } = params;
12
36
  const hasTargetVersion = !!_targetVersion;
@@ -85,12 +109,19 @@ async function runSpecifiedVersionFlow(params) {
85
109
  };
86
110
  }
87
111
  if (!installResult.ok) {
88
- log.error('指定版本安装失败:安装步骤失败', { targetVersion, error: installResult.error });
89
- return {
90
- ok: false,
91
- error: installResult.error ?? '插件安装失败',
92
- message: MessageEnum.REPAIR_BOT_CONFIG_GUIDE,
93
- };
112
+ const verify = await verifyVersionAfterFailedCommand({
113
+ currentVersion,
114
+ targetVersion: targetVersion ?? null,
115
+ commandResult: installResult,
116
+ commandName: 'plugins install',
117
+ });
118
+ if (!verify.upgraded) {
119
+ return {
120
+ ok: false,
121
+ error: installResult.error ?? '插件安装失败',
122
+ message: verify.rateLimited ? MessageEnum.RATE_LIMITED : MessageEnum.REPAIR_BOT_CONFIG_GUIDE,
123
+ };
124
+ }
94
125
  }
95
126
  log.info('指定版本安装流程完成', { targetVersion, hasSnapshot: !!restoreSnapshotJson });
96
127
  await onProgress?.(currentVersion
@@ -116,8 +147,23 @@ async function runRegularUpgradeFlow(params) {
116
147
  commandName: 'plugins update',
117
148
  });
118
149
  if (!updateResult.ok) {
119
- log.warn('更新命令执行失败', { error: updateResult.error, ...(updateResult.stderr ? { stderr: updateResult.stderr } : {}) });
120
- return { ok: false, error: updateResult.error ?? '常规升级失败' };
150
+ const verify = await verifyVersionAfterFailedCommand({
151
+ currentVersion,
152
+ targetVersion: latestStableVersion,
153
+ commandResult: updateResult,
154
+ commandName: 'plugins update',
155
+ });
156
+ if (verify.upgraded) {
157
+ await onProgress?.(latestStableVersion
158
+ ? `✅ 更新成功!**元宝 Bot 插件**已从 v${currentVersion} 升级至 v${latestStableVersion}`
159
+ : `✅ 更新成功!**元宝 Bot 插件**已更新至 v${verify.installedVersion}`);
160
+ return { ok: true };
161
+ }
162
+ return {
163
+ ok: false,
164
+ error: updateResult.error ?? '常规升级失败',
165
+ message: verify.rateLimited ? MessageEnum.RATE_LIMITED : undefined,
166
+ };
121
167
  }
122
168
  if (updateResult.stdout?.includes('No install record')) {
123
169
  return { ok: false, error: updateResult.error ?? '常规升级失败,需要重新安装', needToInstall: true };
@@ -19,4 +19,6 @@ export declare function runOpenClawCommandWithRetry(params: {
19
19
  nextAttempt: number;
20
20
  maxAttempts: number;
21
21
  }) => Promise<void>;
22
- }): Promise<Awaited<ReturnType<typeof runOpenClawCommand>>>;
22
+ }): Promise<Awaited<ReturnType<typeof runOpenClawCommand>> & {
23
+ rateLimited?: boolean;
24
+ }>;
@@ -131,18 +131,16 @@ export async function isPublishedVersionOnNpm(version) {
131
131
  }
132
132
  export async function readInstalledVersion(pluginId) {
133
133
  log.info('读取已安装版本', { pluginId });
134
- const result = await runOpenClawCommand(['plugins', 'list']);
134
+ const result = await runOpenClawCommand(['plugins', 'info', pluginId]);
135
135
  if (!result.ok) {
136
- log.warn('openclaw plugins list 执行失败', { summary: result.error, ...(result.stderr ? { stderr: result.stderr } : {}) });
136
+ log.warn('openclaw plugins info 执行失败', { summary: result.error, ...(result.stderr ? { stderr: result.stderr } : {}) });
137
137
  return null;
138
138
  }
139
139
  for (const line of (result.stdout ?? '').split('\n')) {
140
- if (line.toLowerCase().includes(pluginId.toLowerCase())) {
141
- const match = line.match(/(\d+\.\d+\.\d+(?:-[\w.]+)?)/);
142
- if (match) {
143
- log.info('已安装版本', { pluginId, version: match[1] });
144
- return match[1];
145
- }
140
+ const match = line.match(/^Version:\s*(\d+\.\d+\.\d+(?:-[\w.]+)?)/i);
141
+ if (match) {
142
+ log.info('已安装版本', { pluginId, version: match[1] });
143
+ return match[1];
146
144
  }
147
145
  }
148
146
  log.warn('未检测到已安装版本', { pluginId });
@@ -199,8 +197,12 @@ export async function runOpenClawCommandWithRetry(params) {
199
197
  if (lastResult.ok)
200
198
  return lastResult;
201
199
  const retriable = isRateLimitPluginCommandError(lastResult);
202
- if (!retriable || attempt >= PLUGIN_CMD_RETRY_MAX_ATTEMPTS)
200
+ if (!retriable || attempt >= PLUGIN_CMD_RETRY_MAX_ATTEMPTS) {
201
+ if (retriable) {
202
+ return { ...lastResult, rateLimited: true };
203
+ }
203
204
  break;
205
+ }
204
206
  const nextAttempt = attempt + 1;
205
207
  const retryDelayMs = PLUGIN_CMD_RETRY_DELAY_MS * attempt;
206
208
  log.warn('openclaw plugin 命令触发限频,准备重试', {
@@ -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
  },
@@ -0,0 +1,2 @@
1
+ import type { ChannelPlugin } from 'openclaw/plugin-sdk';
2
+ export declare const yuanbaoDirectoryAdapter: NonNullable<ChannelPlugin<any>['directory']>;
@@ -0,0 +1,58 @@
1
+ import { getMember } from './module/member.js';
2
+ function toUserEntry(u) {
3
+ return { kind: 'user', id: u.userId, name: u.nickName };
4
+ }
5
+ function toGroupEntry(g) {
6
+ return { kind: 'group', id: g };
7
+ }
8
+ function applyLimit(arr, limit) {
9
+ return limit != null && limit > 0 ? arr.slice(0, limit) : arr;
10
+ }
11
+ export const yuanbaoDirectoryAdapter = {
12
+ async listPeers({ accountId, query, limit }) {
13
+ const member = getMember(accountId ?? 'default');
14
+ if (query?.trim()) {
15
+ const q = query.trim().toLowerCase();
16
+ const seen = new Set();
17
+ const entries = [];
18
+ const collect = (users) => {
19
+ for (const u of users) {
20
+ if (!seen.has(u.userId) && u.nickName.toLowerCase().includes(q)) {
21
+ seen.add(u.userId);
22
+ entries.push(toUserEntry(u));
23
+ }
24
+ }
25
+ };
26
+ collect(member.listC2cUsers());
27
+ for (const code of member.listGroupCodes()) {
28
+ collect(member.lookupUsers(code));
29
+ }
30
+ return applyLimit(entries, limit);
31
+ }
32
+ const seen = new Set();
33
+ const entries = [];
34
+ const collect = (users) => {
35
+ for (const u of users) {
36
+ if (!seen.has(u.userId)) {
37
+ seen.add(u.userId);
38
+ entries.push(toUserEntry(u));
39
+ }
40
+ }
41
+ };
42
+ collect(member.listC2cUsers());
43
+ for (const code of member.listGroupCodes()) {
44
+ collect(member.lookupUsers(code));
45
+ }
46
+ return applyLimit(entries, limit);
47
+ },
48
+ async listGroups({ accountId, limit }) {
49
+ const member = getMember(accountId ?? 'default');
50
+ const codes = member.listGroupCodes();
51
+ return applyLimit(codes.map(toGroupEntry), limit);
52
+ },
53
+ async listGroupMembers({ accountId, groupId, limit }) {
54
+ const member = getMember(accountId ?? 'default');
55
+ const users = await member.queryMembers(groupId);
56
+ return applyLimit(users.map(toUserEntry), limit);
57
+ },
58
+ };
@@ -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,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>, skipSanitize?: boolean): string;
28
- export declare function createLog(module: string, sink?: LogSink, options?: {
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;
@@ -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,44 +71,16 @@ export const logger = {
52
71
  getActiveLogger().debug(message, meta);
53
72
  },
54
73
  };
55
- export function isVerbose() {
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
- const serialized = skipSanitize ? JSON.stringify(data) : sanitize(data);
86
- return `${prefix} ${msg} ${serialized}`;
78
+ return `${prefix} ${msg} ${sanitize(data)}`;
87
79
  }
88
- export function createLog(module, sink, options) {
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, skipSanitize);
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 '***';
@@ -23,6 +23,7 @@ export declare function parseImageSize(buf: Buffer): {
23
23
  width: number;
24
24
  height: number;
25
25
  } | undefined;
26
+ export declare function validateMediaBeforeQueue(url: string): string | null;
26
27
  export declare function downloadMediaForYuanbao(url: string, maxMb?: number, account?: ResolvedYuanbaoAccount): Promise<MediaFile>;
27
28
  export declare function uploadMediaToCos(mediaFile: MediaFile, account: ResolvedYuanbaoAccount, onProgress?: (percent: number) => void): Promise<MediaUploadResult>;
28
29
  export declare function downloadAndUploadMedia(mediaUrl: string, core: PluginRuntime, account: ResolvedYuanbaoAccount, mediaLocalRoots?: string[], onProgress?: (percent: number) => void): Promise<MediaUploadResult>;