openclaw-plugin-yuanbao 2.9.1 → 2.10.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 (36) hide show
  1. package/dist/index.js +3 -0
  2. package/dist/openclaw.plugin.json +1 -1
  3. package/dist/src/accounts.js +5 -0
  4. package/dist/src/commands/slash-commands/index.d.ts +27 -0
  5. package/dist/src/commands/slash-commands/index.js +116 -0
  6. package/dist/src/config-schema.d.ts +7 -0
  7. package/dist/src/config-schema.js +20 -0
  8. package/dist/src/message-handler/extract.js +1 -1
  9. package/dist/src/message-handler/handlers/{custom.d.ts → custom/index.d.ts} +1 -1
  10. package/dist/src/message-handler/handlers/custom/index.js +63 -0
  11. package/dist/src/message-handler/handlers/custom/link-card.d.ts +2 -0
  12. package/dist/src/message-handler/handlers/custom/link-card.js +64 -0
  13. package/dist/src/message-handler/handlers/index.d.ts +1 -1
  14. package/dist/src/message-handler/handlers/index.js +2 -2
  15. package/dist/src/message-handler/handlers/types.d.ts +1 -0
  16. package/dist/src/message-handler/inbound.js +67 -17
  17. package/dist/src/message-handler/outbound.d.ts +1 -1
  18. package/dist/src/message-handler/outbound.js +41 -2
  19. package/dist/src/outbound-queue.d.ts +1 -0
  20. package/dist/src/outbound-queue.js +20 -4
  21. package/dist/src/types.d.ts +10 -0
  22. package/dist/src/types.js +5 -0
  23. package/dist/src/yuanbao-server/http/request.d.ts +2 -1
  24. package/dist/src/yuanbao-server/http/request.js +1 -1
  25. package/dist/src/yuanbao-server/ws/biz-codec.d.ts +5 -1
  26. package/dist/src/yuanbao-server/ws/biz-codec.js +20 -0
  27. package/dist/src/yuanbao-server/ws/client.d.ts +3 -1
  28. package/dist/src/yuanbao-server/ws/client.js +9 -1
  29. package/dist/src/yuanbao-server/ws/gateway.js +38 -1
  30. package/dist/src/yuanbao-server/ws/index.d.ts +2 -2
  31. package/dist/src/yuanbao-server/ws/index.js +1 -1
  32. package/dist/src/yuanbao-server/ws/proto/biz.json +69 -0
  33. package/dist/src/yuanbao-server/ws/types.d.ts +19 -0
  34. package/openclaw.plugin.json +1 -1
  35. package/package.json +1 -1
  36. package/dist/src/message-handler/handlers/custom.js +0 -52
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { initLogger } from './src/logger.js';
5
5
  import { registerTools } from './src/tools/index.js';
6
6
  import { yuanbaoUpgradeCommand, yuanbaobotUpgradeCommand } from './src/commands/upgrade/index.js';
7
7
  import { logUploadCommandDefinition } from './src/commands/log-upload.js';
8
+ import { registerPluginCommand } from './src/commands/slash-commands/index.js';
8
9
  import { initEnv } from './src/utils/get-env.js';
9
10
  import { initBuiltinStickers } from './src/sticker/init-builtin-stickers.js';
10
11
  import pluginManifest from './openclaw.plugin.json' with { type: 'json' };
@@ -29,7 +30,9 @@ const plugin = {
29
30
  registerTools(api);
30
31
  api.registerCommand(yuanbaoUpgradeCommand);
31
32
  api.registerCommand(yuanbaobotUpgradeCommand);
33
+ registerPluginCommand(yuanbaobotUpgradeCommand.name, yuanbaobotUpgradeCommand.description);
32
34
  api.registerCommand(logUploadCommandDefinition);
35
+ registerPluginCommand(logUploadCommandDefinition.name, logUploadCommandDefinition.description);
33
36
  initBuiltinStickers();
34
37
  },
35
38
  };
@@ -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.9.1",
5
+ "version": "2.10.0",
6
6
  "channels": [
7
7
  "yuanbao"
8
8
  ],
@@ -2,6 +2,7 @@ import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk/core';
2
2
  import { normalizeAccountId } from 'openclaw/plugin-sdk/account-id';
3
3
  import { logger } from './logger.js';
4
4
  import { getCachedBotId } from './yuanbao-server/http/request.js';
5
+ import { DEFAULT_SLOW_REPLY_TIMEOUT_MS, DEFAULT_SLOW_REPLY_TEXT } from './config-schema.js';
5
6
  const DEFAULT_API_DOMAIN = 'bot.yuanbao.tencent.com';
6
7
  const DEFAULT_WS_GATEWAY_URL = 'wss://bot-wss.yuanbao.tencent.com/wss/connection';
7
8
  function listConfiguredAccountIds(cfg) {
@@ -93,6 +94,8 @@ export function resolveYuanbaoAccount(params) {
93
94
  const requireMention = merged.requireMention !== undefined ? merged.requireMention : true;
94
95
  const fallbackReply = merged.fallbackReply?.trim();
95
96
  const markdownHintEnabled = merged.markdownHintEnabled !== false;
97
+ const slowReplyTimeoutMs = merged.slowReplyTimeoutMs ?? DEFAULT_SLOW_REPLY_TIMEOUT_MS;
98
+ const slowReplyText = merged.slowReplyText ?? DEFAULT_SLOW_REPLY_TEXT;
96
99
  const configured = Boolean(appKey && appSecret);
97
100
  if (!configured && Boolean(yuanbaoConfig)) {
98
101
  warnIncompleteConfig(appKey, appSecret);
@@ -118,6 +121,8 @@ export function resolveYuanbaoAccount(params) {
118
121
  requireMention,
119
122
  fallbackReply,
120
123
  markdownHintEnabled,
124
+ slowReplyTimeoutMs,
125
+ slowReplyText,
121
126
  config: merged,
122
127
  };
123
128
  }
@@ -0,0 +1,27 @@
1
+ import type { OpenClawConfig } from 'openclaw/plugin-sdk';
2
+ export type CommandItem = {
3
+ name: string;
4
+ description: string;
5
+ };
6
+ export declare const SYNC_INFORMATION_TYPE: {
7
+ readonly UNSPECIFIED: 0;
8
+ readonly COMMANDS: 1;
9
+ };
10
+ export declare function registerPluginCommand(name: string, description: string): void;
11
+ export declare function getPluginCommands(): ReadonlyArray<CommandItem>;
12
+ export type SyncInformationPayload = {
13
+ syncType: number;
14
+ botVersion: string;
15
+ pluginVersion: string;
16
+ commandData: {
17
+ botCommands: Array<{
18
+ name: string;
19
+ description: string;
20
+ }>;
21
+ pluginCommands: Array<{
22
+ name: string;
23
+ description: string;
24
+ }>;
25
+ };
26
+ };
27
+ export declare function buildSyncCommandsPayload(config?: OpenClawConfig): Promise<SyncInformationPayload>;
@@ -0,0 +1,116 @@
1
+ import { getPluginVersion, getOpenclawVersion } from '../../utils/get-env.js';
2
+ import { createLog } from '../../logger.js';
3
+ let _listChatCommands = null;
4
+ let _listChatCommandsForConfig = null;
5
+ let _commandAuthLoadPromise = null;
6
+ function loadCommandAuth() {
7
+ if (_commandAuthLoadPromise)
8
+ return _commandAuthLoadPromise;
9
+ _commandAuthLoadPromise = import('openclaw/plugin-sdk/command-auth')
10
+ .then((mod) => {
11
+ _listChatCommands = mod.listChatCommands;
12
+ _listChatCommandsForConfig = mod.listChatCommandsForConfig;
13
+ })
14
+ .catch(() => {
15
+ const log = createLog('slash-commands');
16
+ log.warn('openclaw/plugin-sdk/command-auth 不可用(当前 OpenClaw 版本可能较旧),bot_commands 将返回兜底列表');
17
+ });
18
+ return _commandAuthLoadPromise;
19
+ }
20
+ export const SYNC_INFORMATION_TYPE = {
21
+ UNSPECIFIED: 0,
22
+ COMMANDS: 1,
23
+ };
24
+ const FALLBACK_BOT_COMMANDS = [
25
+ { name: '/help', description: 'Show available commands.' },
26
+ { name: '/status', description: 'Show current status.' },
27
+ { name: '/stop', description: 'Stop the current run.' },
28
+ { name: '/new', description: 'Start a new session.' },
29
+ { name: '/restart', description: 'Restart OpenClaw.' },
30
+ { name: '/compact', description: 'Compact the session context.' },
31
+ ];
32
+ async function fetchBotCommands(config) {
33
+ const log = createLog('slash-commands');
34
+ await loadCommandAuth();
35
+ if (!_listChatCommandsForConfig && !_listChatCommands) {
36
+ log.info(`command-auth 模块不可用(旧版 OpenClaw),使用兜底命令列表: ${FALLBACK_BOT_COMMANDS.length} 个命令`);
37
+ return FALLBACK_BOT_COMMANDS;
38
+ }
39
+ try {
40
+ if (config && _listChatCommandsForConfig) {
41
+ const commands = _listChatCommandsForConfig(config);
42
+ log.debug(`使用配置感知版本获取命令列表: ${commands.length} 个命令`);
43
+ if (commands.length > 0) {
44
+ log.debug('命令原始结构示例:', {
45
+ sample: commands.slice(0, 3).map((c) => ({
46
+ name: c.name, description: c.description, textAliases: c.textAliases, keys: Object.keys(c),
47
+ })),
48
+ });
49
+ }
50
+ return commands;
51
+ }
52
+ if (_listChatCommands) {
53
+ log.debug('OpenClawConfig 不可用, 降级为无参版本');
54
+ const commands = _listChatCommands();
55
+ log.debug(`使用无参版本获取命令列表: ${commands.length} 个命令`);
56
+ return commands;
57
+ }
58
+ return null;
59
+ }
60
+ catch (err) {
61
+ const msg = err instanceof Error ? err.message : String(err);
62
+ log.warn(`获取命令列表失败: ${msg}`);
63
+ return null;
64
+ }
65
+ }
66
+ function toBotCommandItems(commands) {
67
+ const log = createLog('slash-commands');
68
+ const result = [];
69
+ for (const cmd of commands) {
70
+ const aliases = cmd.textAliases;
71
+ let name = '';
72
+ if (aliases && aliases.length > 0) {
73
+ [name] = aliases;
74
+ }
75
+ else if (cmd.name) {
76
+ name = cmd.name;
77
+ }
78
+ if (!name)
79
+ continue;
80
+ if (!name.startsWith('/'))
81
+ name = `/${name}`;
82
+ if (name.length <= 1)
83
+ continue;
84
+ result.push({
85
+ name,
86
+ description: cmd.description ?? '',
87
+ });
88
+ }
89
+ log.debug(`toBotCommandItems 转换结果: ${result.length} 个命令`);
90
+ return result;
91
+ }
92
+ const pluginCommands = [];
93
+ export function registerPluginCommand(name, description) {
94
+ const fullName = name.startsWith('/') ? name : `/${name}`;
95
+ if (!pluginCommands.some(c => c.name === fullName)) {
96
+ pluginCommands.push({ name: fullName, description });
97
+ }
98
+ }
99
+ export function getPluginCommands() {
100
+ return pluginCommands;
101
+ }
102
+ export async function buildSyncCommandsPayload(config) {
103
+ const botVersion = getOpenclawVersion() || '0.0.0';
104
+ const pluginVersion = getPluginVersion() || '0.0.0';
105
+ const rawBotCommands = await fetchBotCommands(config);
106
+ const botCommands = rawBotCommands ? toBotCommandItems(rawBotCommands) : [];
107
+ return {
108
+ syncType: SYNC_INFORMATION_TYPE.COMMANDS,
109
+ botVersion,
110
+ pluginVersion,
111
+ commandData: {
112
+ botCommands: botCommands.map(c => ({ name: c.name, description: c.description })),
113
+ pluginCommands: pluginCommands.map(c => ({ name: c.name, description: c.description })),
114
+ },
115
+ };
116
+ }
@@ -1,2 +1,9 @@
1
1
  import type { ChannelConfigSchema } from 'openclaw/plugin-sdk';
2
+ export declare const DEFAULT_SLOW_REPLY_TIMEOUT_MS = 120000;
3
+ export declare const DEFAULT_SLOW_REPLY_TEXT = "\u4EFB\u52A1\u6709\u70B9\u590D\u6742\uFF0C\u6B63\u5728\u52AA\u529B\u5904\u7406\u4E2D\uFF0C\u8BF7\u8010\u5FC3\u7B49\u5F85...";
4
+ export declare const DEFAULT_MODEL_RATE_LIMIT_TEXT = "\u6A21\u578B\u670D\u52A1\u7E41\u5FD9\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5";
5
+ export declare const DEFAULT_MODEL_BILLING_DEFAULT_TEXT = "\u4ECA\u65E5\u8BCD\u5143\u6D88\u8017\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u660E\u65E5\u518D\u6765\u548C\u6211\u5BF9\u8BDD\u5427\uFF5E";
6
+ export declare const DEFAULT_MODEL_BILLING_CUSTOM_TEXT = "\u8BCD\u5143\u4E0D\u8DB3\uFF0C\u8BF7\u68C0\u67E5\u6A21\u578B\u72B6\u6001";
7
+ export declare const DEFAULT_MODEL_ERROR_TEXT = "\u6A21\u578B\u670D\u52A1\u51FA\u9519\uFF0C\u8BF7\u68C0\u67E5\u6A21\u578BAPI";
8
+ export declare const DEFAULT_MODEL_ONE_CLICK_ERROR_TEXT = "\u6A21\u578B\u670D\u52A1\u51FA\u9519\u4E86\uFF0C\u8BF7\u5728\"\u6211\u7684 Bot\"\u4E2D\u5C1D\u8BD5\u91CD\u542F\u6216\u8FD8\u539F Bot \u914D\u7F6E";
2
9
  export declare const yuanbaoConfigSchema: ChannelConfigSchema;
@@ -1,3 +1,10 @@
1
+ export const DEFAULT_SLOW_REPLY_TIMEOUT_MS = 120_000;
2
+ export const DEFAULT_SLOW_REPLY_TEXT = '任务有点复杂,正在努力处理中,请耐心等待...';
3
+ export const DEFAULT_MODEL_RATE_LIMIT_TEXT = '模型服务繁忙,请稍后重试';
4
+ export const DEFAULT_MODEL_BILLING_DEFAULT_TEXT = '今日词元消耗已达上限,明日再来和我对话吧~';
5
+ export const DEFAULT_MODEL_BILLING_CUSTOM_TEXT = '词元不足,请检查模型状态';
6
+ export const DEFAULT_MODEL_ERROR_TEXT = '模型服务出错,请检查模型API';
7
+ export const DEFAULT_MODEL_ONE_CLICK_ERROR_TEXT = '模型服务出错了,请在"我的 Bot"中尝试重启或还原 Bot 配置';
1
8
  export const yuanbaoConfigSchema = {
2
9
  schema: {
3
10
  $schema: 'http://json-schema.org/draft-07/schema#',
@@ -98,6 +105,19 @@ export const yuanbaoConfigSchema = {
98
105
  description: '开启后在系统提示词中自动注入指令,防止模型用代码块包裹整个 Markdown 回复',
99
106
  default: true,
100
107
  },
108
+ slowReplyTimeoutMs: {
109
+ type: 'integer',
110
+ title: '慢回复提醒超时 (ms)',
111
+ description: '收到消息后,在该时长内未推送首条回复则发送慢回复提示文案;0 表示禁用',
112
+ minimum: 0,
113
+ default: DEFAULT_SLOW_REPLY_TIMEOUT_MS,
114
+ },
115
+ slowReplyText: {
116
+ type: 'string',
117
+ title: '慢回复提示文案',
118
+ description: '超过慢回复超时后推送给用户的提示文案',
119
+ default: DEFAULT_SLOW_REPLY_TEXT,
120
+ },
101
121
  },
102
122
  additionalProperties: false,
103
123
  },
@@ -1,6 +1,6 @@
1
1
  import { getHandler } from './handlers/index.js';
2
2
  export function extractTextFromMsgBody(ctx, msgBody) {
3
- const resData = { rawBody: '', isAtBot: false, medias: [], mentions: [] };
3
+ const resData = { rawBody: '', isAtBot: false, medias: [], mentions: [], linkUrls: [] };
4
4
  if (!msgBody || !Array.isArray(msgBody))
5
5
  return resData;
6
6
  const texts = [];
@@ -1,3 +1,3 @@
1
- import type { MessageElemHandler, MsgBodyItemType } from './types.js';
1
+ import type { MessageElemHandler, MsgBodyItemType } from '../types.js';
2
2
  export declare function buildAtUserMsgBodyItem(userId: string, senderNickname: any): MsgBodyItemType;
3
3
  export declare const customHandler: MessageElemHandler;
@@ -0,0 +1,63 @@
1
+ import { createLog } from '../../../logger.js';
2
+ import { extractLinkCard, extractLinkCardUrls } from './link-card.js';
3
+ export function buildAtUserMsgBodyItem(userId, senderNickname) {
4
+ return {
5
+ msg_type: 'TIMCustomElem',
6
+ msg_content: {
7
+ data: JSON.stringify({ elem_type: 1002, text: `@${senderNickname ?? ''}`, user_id: userId }),
8
+ },
9
+ };
10
+ }
11
+ const FALLBACK_TEXT = '[当前消息暂不支持查看]';
12
+ export const customHandler = {
13
+ msgType: 'TIMCustomElem',
14
+ extract(ctx, elem, resData) {
15
+ if (elem.msg_content?.data) {
16
+ try {
17
+ const customContent = JSON.parse(elem.msg_content?.data);
18
+ switch (customContent?.elem_type) {
19
+ case 1002: {
20
+ const { botId } = ctx.account;
21
+ const isAtBotSelf = customContent?.user_id === botId;
22
+ if (!resData.isAtBot) {
23
+ resData.isAtBot = isAtBotSelf;
24
+ }
25
+ createLog('custom').info('@消息', { text: customContent?.text, userId: customContent?.user_id, isAtBot: resData.isAtBot });
26
+ if (customContent?.user_id) {
27
+ resData.mentions.push({
28
+ userId: customContent.user_id,
29
+ text: customContent.text || '',
30
+ });
31
+ }
32
+ if (isAtBotSelf && customContent.text) {
33
+ resData.botUsername = customContent.text.replace(/^@/, '');
34
+ }
35
+ return customContent.text || undefined;
36
+ }
37
+ case 1010:
38
+ case 1007: {
39
+ const urls = extractLinkCardUrls(customContent);
40
+ if (urls.length > 0)
41
+ resData.linkUrls.push(...urls);
42
+ return extractLinkCard(customContent);
43
+ }
44
+ default:
45
+ return FALLBACK_TEXT;
46
+ }
47
+ }
48
+ catch { }
49
+ }
50
+ return FALLBACK_TEXT;
51
+ },
52
+ buildMsgBody(data) {
53
+ const customData = typeof data.data === 'string'
54
+ ? data.data
55
+ : JSON.stringify(data.data);
56
+ return [
57
+ {
58
+ msg_type: 'TIMCustomElem',
59
+ msg_content: { data: customData },
60
+ },
61
+ ];
62
+ },
63
+ };
@@ -0,0 +1,2 @@
1
+ export declare function extractLinkCard(customContent: unknown): string | undefined;
2
+ export declare function extractLinkCardUrls(customContent: unknown): string[];
@@ -0,0 +1,64 @@
1
+ const CONTENT_MAX_LENGTH = 1000;
2
+ function isSharedLinkData(data) {
3
+ return typeof data === 'object' && data !== null && data.elem_type === 1010;
4
+ }
5
+ function truncate(text, maxLength) {
6
+ if (text.length <= maxLength)
7
+ return text;
8
+ return `${text.slice(0, maxLength)}...(truncated)`;
9
+ }
10
+ function formatSharedLink(data) {
11
+ const lines = [];
12
+ lines.push(`<share_card name="${data.title ?? ''}">`);
13
+ if (data.link)
14
+ lines.push(` <link>${data.link}</link>`);
15
+ if (data.card_content)
16
+ lines.push(` <preview_title>${truncate(data.card_content, CONTENT_MAX_LENGTH)}</preview_title>`);
17
+ if (data.wechat_des)
18
+ lines.push(` <preview_description>${truncate(data.wechat_des, CONTENT_MAX_LENGTH)}</preview_description>`);
19
+ lines.push(' <instruction>以上仅为分享卡片的摘要预览,请访问 link 获取完整页面内容后再进行分析回复</instruction>');
20
+ lines.push('</share_card>');
21
+ return lines.join('\n');
22
+ }
23
+ function isLinkUnderstandingData(data) {
24
+ return typeof data === 'object' && data !== null && data.elem_type === 1007;
25
+ }
26
+ function parseLinkFromContent(content) {
27
+ try {
28
+ const parsed = JSON.parse(content);
29
+ return typeof parsed?.link === 'string' ? parsed.link : undefined;
30
+ }
31
+ catch {
32
+ return undefined;
33
+ }
34
+ }
35
+ function formatLinkUnderstanding(data) {
36
+ const link = data.content ? parseLinkFromContent(data.content) : undefined;
37
+ if (!link)
38
+ return undefined;
39
+ const lines = [];
40
+ lines.push('<link_understanding>');
41
+ lines.push(` <link>${link}</link>`);
42
+ lines.push(' <instruction>请访问 link 获取完整页面内容后再进行分析回复</instruction>');
43
+ lines.push('</link_understanding>');
44
+ return lines.join('\n');
45
+ }
46
+ export function extractLinkCard(customContent) {
47
+ if (isSharedLinkData(customContent)) {
48
+ return formatSharedLink(customContent);
49
+ }
50
+ if (isLinkUnderstandingData(customContent)) {
51
+ return formatLinkUnderstanding(customContent);
52
+ }
53
+ return undefined;
54
+ }
55
+ export function extractLinkCardUrls(customContent) {
56
+ if (isSharedLinkData(customContent) && customContent.link) {
57
+ return [customContent.link];
58
+ }
59
+ if (isLinkUnderstandingData(customContent) && customContent.content) {
60
+ const link = parseLinkFromContent(customContent.content);
61
+ return link ? [link] : [];
62
+ }
63
+ return [];
64
+ }
@@ -7,7 +7,7 @@ export declare function prepareOutboundContent(text: string, groupCode?: string,
7
7
  export declare function buildOutboundMsgBody(items: OutboundContentItem[]): MsgBodyItemType[];
8
8
  export type { MessageElemHandler, MsgBodyItemType, MediaItem, ExtractTextFromMsgBodyResult, OutboundContentItem, } from './types.js';
9
9
  export { textHandler } from './text.js';
10
- export { customHandler, buildAtUserMsgBodyItem } from './custom.js';
10
+ export { customHandler, buildAtUserMsgBodyItem } from './custom/index.js';
11
11
  export { imageHandler } from './image.js';
12
12
  export { soundHandler } from './sound.js';
13
13
  export { fileHandler } from './file.js';
@@ -1,5 +1,5 @@
1
1
  import { textHandler } from './text.js';
2
- import { customHandler } from './custom.js';
2
+ import { customHandler } from './custom/index.js';
3
3
  import { imageHandler } from './image.js';
4
4
  import { soundHandler } from './sound.js';
5
5
  import { fileHandler } from './file.js';
@@ -108,7 +108,7 @@ export function buildOutboundMsgBody(items) {
108
108
  return msgBody;
109
109
  }
110
110
  export { textHandler } from './text.js';
111
- export { customHandler, buildAtUserMsgBodyItem } from './custom.js';
111
+ export { customHandler, buildAtUserMsgBodyItem } from './custom/index.js';
112
112
  export { imageHandler } from './image.js';
113
113
  export { soundHandler } from './sound.js';
114
114
  export { fileHandler } from './file.js';
@@ -38,6 +38,7 @@ export type ExtractTextFromMsgBodyResult = {
38
38
  medias: MediaItem[];
39
39
  mentions: MentionItem[];
40
40
  botUsername?: string;
41
+ linkUrls: string[];
41
42
  };
42
43
  export type OutboundContentItem = {
43
44
  type: 'text';
@@ -102,6 +102,21 @@ function buildGroupHistoryContext(params) {
102
102
  : undefined;
103
103
  return { combinedBody, inboundHistory };
104
104
  }
105
+ function createSlowReplyTimer(params) {
106
+ const { timeoutMs, text, send, log } = params;
107
+ if (timeoutMs <= 0)
108
+ return () => { };
109
+ let fired = false;
110
+ const timer = setTimeout(() => {
111
+ fired = true;
112
+ log.info(`慢回复超时 (${timeoutMs}ms),推送提示: "${text}"`);
113
+ send().catch(err => log.error('慢回复提示发送失败', { error: String(err) }));
114
+ }, timeoutMs);
115
+ return () => {
116
+ if (!fired)
117
+ clearTimeout(timer);
118
+ };
119
+ }
105
120
  function isSkippableBracketPlaceholder(rawBody, mediaCount) {
106
121
  if (mediaCount > 0)
107
122
  return false;
@@ -127,7 +142,7 @@ async function handleC2CMessage(params) {
127
142
  log.info(`跳过机器人自身消息 <- ${fromAccount}`);
128
143
  return;
129
144
  }
130
- const { rawBody, medias } = extractTextFromMsgBody(ctx, msg.msg_body);
145
+ const { rawBody, medias, linkUrls } = extractTextFromMsgBody(ctx, msg.msg_body);
131
146
  getMember(account.accountId).recordC2cUser(fromAccount, senderNickname || fromAccount);
132
147
  log.info(`收到消息 <- ${fromAccount}${senderNickname ? `(${senderNickname})` : ''}, msgKey: ${msg.msg_key}`);
133
148
  const quoteInfo = parseQuoteFromCloudCustomData(msg.cloud_custom_data);
@@ -258,6 +273,7 @@ async function handleC2CMessage(params) {
258
273
  ...(account.markdownHintEnabled && { GroupSystemPrompt: YUANBAO_MARKDOWN_HINT }),
259
274
  ...(mediaPaths.length > 0 && { MediaPaths: mediaPaths, MediaPath: mediaPaths[0] }),
260
275
  ...(mediaTypes.length > 0 && { MediaTypes: mediaTypes, MediaType: mediaTypes[0] }),
276
+ ...(linkUrls.length > 0 && { LinkUnderstanding: [...new Set(linkUrls)] }),
261
277
  });
262
278
  await core.channel.session.recordInboundSession({
263
279
  storePath,
@@ -295,6 +311,12 @@ async function handleC2CMessage(params) {
295
311
  ctx,
296
312
  }),
297
313
  };
314
+ const cancelSlowReply = createSlowReplyTimer({
315
+ timeoutMs: account.slowReplyTimeoutMs,
316
+ text: account.slowReplyText,
317
+ send: () => sendYuanbaoMessage({ account, toAccount: fromAccount, text: account.slowReplyText, fromAccount: outboundSender, ctx }),
318
+ log,
319
+ });
298
320
  const outboundSessionKey = `direct:${fromAccount}`;
299
321
  const msgId = msg.msg_id ?? String(msg.msg_seq ?? '');
300
322
  const queueManager = getOutboundQueue(account.accountId);
@@ -308,24 +330,33 @@ async function handleC2CMessage(params) {
308
330
  fromAccount: outboundSender,
309
331
  ctx,
310
332
  mergeOnFlush: account.disableBlockStreaming,
333
+ onFirstSent: cancelSlowReply,
311
334
  });
312
335
  log.debug(`[${outboundSessionKey}] 出站队列 session 已注册,msgId: ${msgId}`);
313
336
  }
314
337
  const replyRuntime = buildReplyRuntimeConfig(config, account);
315
- await executeReply({
316
- transport,
317
- ctx,
318
- account,
319
- core,
320
- config,
321
- ctxPayload,
322
- replyRuntime,
323
- tableMode,
324
- splitFinalText,
325
- overflowPolicy: account.overflowPolicy,
326
- sessionKey: outboundSessionKey,
327
- appendText: getAppendText(rawBody),
328
- });
338
+ try {
339
+ await executeReply({
340
+ transport,
341
+ ctx,
342
+ account,
343
+ core,
344
+ config,
345
+ ctxPayload,
346
+ replyRuntime,
347
+ tableMode,
348
+ splitFinalText,
349
+ overflowPolicy: account.overflowPolicy,
350
+ sessionKey: outboundSessionKey,
351
+ appendText: getAppendText(rawBody),
352
+ });
353
+ }
354
+ catch (err) {
355
+ log.error('executeReply 失败', { error: String(err) });
356
+ }
357
+ finally {
358
+ cancelSlowReply();
359
+ }
329
360
  log.info(`消息处理完成 <- ${fromAccount}`);
330
361
  }
331
362
  async function handleGroupMessage(params) {
@@ -343,7 +374,7 @@ async function handleGroupMessage(params) {
343
374
  glog.info('跳过机器人自身消息', { groupCode, fromAccount });
344
375
  return;
345
376
  }
346
- const { rawBody, isAtBot, medias, mentions, botUsername } = extractTextFromMsgBody(ctx, msg.msg_body);
377
+ const { rawBody, isAtBot, medias, mentions, botUsername, linkUrls } = extractTextFromMsgBody(ctx, msg.msg_body);
347
378
  glog.info(`收到群消息 <- group:${groupCode}, from: ${fromAccount}${senderNickname ? `(${senderNickname})` : ''}, msgSeq: ${msg.msg_seq}, isAtBot: ${isAtBot}`);
348
379
  const quoteInfo = parseQuoteFromCloudCustomData(msg.cloud_custom_data);
349
380
  if (quoteInfo) {
@@ -532,6 +563,7 @@ async function handleGroupMessage(params) {
532
563
  ...(account.markdownHintEnabled && { GroupSystemPrompt: YUANBAO_MARKDOWN_HINT }),
533
564
  ...(mediaPaths.length > 0 && { MediaPaths: mediaPaths, MediaPath: mediaPaths[0] }),
534
565
  ...(mediaTypes.length > 0 && { MediaTypes: mediaTypes, MediaType: mediaTypes[0] }),
566
+ ...(linkUrls.length > 0 && { LinkUnderstanding: [...new Set(linkUrls)] }),
535
567
  });
536
568
  await core.channel.session.recordInboundSession({
537
569
  storePath,
@@ -578,6 +610,20 @@ async function handleGroupMessage(params) {
578
610
  };
579
611
  const outboundGroupSessionKey = `group:${groupCode}`;
580
612
  const groupMsgId = msg.msg_id ?? String(msg.msg_seq ?? '');
613
+ const cancelSlowReply = createSlowReplyTimer({
614
+ timeoutMs: account.slowReplyTimeoutMs,
615
+ text: account.slowReplyText,
616
+ send: () => sendYuanbaoGroupMessageBody({
617
+ account,
618
+ groupCode,
619
+ msgBody: buildOutboundMsgBody(prepareOutboundContent(account.slowReplyText, groupCode, getMember(account.accountId))),
620
+ fromAccount: outboundSender,
621
+ refMsgId: msg.msg_id || msg.msg_key || undefined,
622
+ refFromAccount: fromAccount,
623
+ ctx,
624
+ }),
625
+ log: glog,
626
+ });
581
627
  const groupQueueManager = getOutboundQueue(account.accountId);
582
628
  if (groupQueueManager) {
583
629
  groupQueueManager.registerSession(outboundGroupSessionKey, {
@@ -591,6 +637,7 @@ async function handleGroupMessage(params) {
591
637
  refFromAccount: fromAccount,
592
638
  ctx,
593
639
  mergeOnFlush: account.disableBlockStreaming,
640
+ onFirstSent: cancelSlowReply,
594
641
  });
595
642
  glog.debug(`[${outboundGroupSessionKey}] 群出站队列 session 已注册,msgId: ${groupMsgId}`);
596
643
  }
@@ -613,9 +660,12 @@ async function handleGroupMessage(params) {
613
660
  });
614
661
  }
615
662
  catch (err) {
663
+ glog.error('executeReply 失败', { error: String(err) });
616
664
  const session = groupQueueManager?.getSession(outboundGroupSessionKey);
617
665
  session?.abort();
618
- throw err;
666
+ }
667
+ finally {
668
+ cancelSlowReply();
619
669
  }
620
670
  clearHistoryEntriesIfEnabled({
621
671
  historyMap: chatHistories,
@@ -1,6 +1,6 @@
1
1
  import type { OpenClawConfig, PluginRuntime } from 'openclaw/plugin-sdk';
2
2
  import type { MarkdownTableMode } from 'openclaw/plugin-sdk/config-runtime';
3
- import type { ResolvedYuanbaoAccount, YuanbaoMsgBodyElement } from '../types.js';
3
+ import { type ResolvedYuanbaoAccount, type 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';
@@ -1,3 +1,5 @@
1
+ import { BotCreateType, } from '../types.js';
2
+ import { DEFAULT_MODEL_RATE_LIMIT_TEXT, DEFAULT_MODEL_BILLING_DEFAULT_TEXT, DEFAULT_MODEL_BILLING_CUSTOM_TEXT, DEFAULT_MODEL_ONE_CLICK_ERROR_TEXT, } from '../config-schema.js';
1
3
  import { WS_HEARTBEAT } from '../yuanbao-server/ws/index.js';
2
4
  import { createLog } from '../logger.js';
3
5
  import { prepareOutboundContent, buildOutboundMsgBody } from './handlers/index.js';
@@ -151,6 +153,30 @@ export async function sendMsgBodyDirect(params) {
151
153
  ctx: minCtx,
152
154
  });
153
155
  }
156
+ const RATE_LIMIT_RE = /rate[_ ]?limit|too many requests|\b429\b/i;
157
+ const BILLING_RE = /billing|credits?|insufficient|payment|\b402\b/i;
158
+ function classifyErrorText(text) {
159
+ if (RATE_LIMIT_RE.test(text))
160
+ return 'rate_limit';
161
+ if (BILLING_RE.test(text))
162
+ return 'billing';
163
+ return 'other';
164
+ }
165
+ function resolveDeliverErrorText(params) {
166
+ const { originalText, botCreateType, config } = params;
167
+ if (botCreateType !== BotCreateType.ONE_CLICK)
168
+ return originalText;
169
+ const model = config?.agents?.defaults?.model;
170
+ const modelId = typeof model === 'string' ? model : model?.primary;
171
+ const isYuanbaoModel = Boolean(modelId?.startsWith('yuanbao'));
172
+ const kind = classifyErrorText(originalText);
173
+ if (kind === 'rate_limit')
174
+ return DEFAULT_MODEL_RATE_LIMIT_TEXT;
175
+ if (kind === 'billing') {
176
+ return isYuanbaoModel ? DEFAULT_MODEL_BILLING_DEFAULT_TEXT : DEFAULT_MODEL_BILLING_CUSTOM_TEXT;
177
+ }
178
+ return isYuanbaoModel ? DEFAULT_MODEL_ONE_CLICK_ERROR_TEXT : originalText;
179
+ }
154
180
  export async function executeReply(params) {
155
181
  const { transport, ctx, account, core, replyRuntime, splitFinalText, overflowPolicy, ctxPayload, sessionKey, appendText, } = params;
156
182
  const rlog = createLog('outbound');
@@ -190,7 +216,9 @@ export async function executeReply(params) {
190
216
  rlog.warn(`[deliver][${account.accountId}] 回复已中止,停止处理后续回复块`);
191
217
  return;
192
218
  }
193
- rlog.info('[deliver] 收到回复数据', { kind: info.kind, model_output: payload.text });
219
+ rlog.info('[deliver] 收到回复数据', {
220
+ kind: info.kind, isError: payload.isError, model_output: payload.text,
221
+ });
194
222
  if (payload.isReasoning) {
195
223
  rlog.info('[deliver] Reasoning', { text: payload.text });
196
224
  return;
@@ -205,7 +233,18 @@ export async function executeReply(params) {
205
233
  if (info.kind === 'final') {
206
234
  hasFinalInfo = true;
207
235
  }
208
- const text = payload.text ?? '';
236
+ let text = payload.text ?? '';
237
+ if (payload.isError && text) {
238
+ const replaced = resolveDeliverErrorText({
239
+ originalText: text,
240
+ botCreateType: account.createType,
241
+ config: replyRuntime.config,
242
+ });
243
+ if (replaced !== text) {
244
+ rlog.info('[deliver] 错误文案已替换', { original: text, replaced });
245
+ }
246
+ text = replaced;
247
+ }
209
248
  if (session) {
210
249
  if (text.trim()) {
211
250
  await session.push({ type: 'text', text });
@@ -48,6 +48,7 @@ interface RegisterSessionOptions {
48
48
  msgId: string;
49
49
  toAccount?: string;
50
50
  mergeOnFlush?: boolean;
51
+ onFirstSent?: () => void;
51
52
  }
52
53
  export interface LightRegisterSessionOptions {
53
54
  chatType: 'c2c' | 'group';
@@ -54,7 +54,7 @@ function createManager(config) {
54
54
  existing.abort();
55
55
  }
56
56
  const onComplete = () => sessions.delete(sessionKey);
57
- const { chatType, account, target, fromAccount, refMsgId, refFromAccount, ctx, msgId, mergeOnFlush, toAccount, } = options;
57
+ const { chatType, account, target, fromAccount, refMsgId, refFromAccount, ctx, msgId, mergeOnFlush, toAccount, onFirstSent, } = options;
58
58
  const heartbeatTarget = (toAccount ?? (chatType === 'c2c' ? target : '')).trim();
59
59
  const heartbeatGroupCode = chatType === 'group' ? target : undefined;
60
60
  const sendText = async (text) => {
@@ -149,9 +149,25 @@ function createManager(config) {
149
149
  });
150
150
  }
151
151
  };
152
+ let firstSentFired = false;
153
+ const fireFirstSent = () => {
154
+ if (!firstSentFired && onFirstSent) {
155
+ firstSentFired = true;
156
+ onFirstSent();
157
+ }
158
+ };
159
+ function wrapSend(fn) {
160
+ return (async (...args) => {
161
+ fireFirstSent();
162
+ return fn(...args);
163
+ });
164
+ }
165
+ const wrappedSendText = wrapSend(sendText);
166
+ const wrappedSendSticker = wrapSend(sendSticker);
167
+ const wrappedSendMedia = wrapSend(sendMedia);
152
168
  let session;
153
169
  if (mergeOnFlush) {
154
- session = createMergeOnFlushSession({ sendText, sendSticker, sendMedia }, msgId, onComplete, log, {
170
+ session = createMergeOnFlushSession({ sendText: wrappedSendText, sendSticker: wrappedSendSticker, sendMedia: wrappedSendMedia }, msgId, onComplete, log, {
155
171
  ctx,
156
172
  account,
157
173
  toAccount: heartbeatTarget,
@@ -161,7 +177,7 @@ function createManager(config) {
161
177
  else {
162
178
  switch (strategy) {
163
179
  case 'immediate':
164
- session = createImmediateSession({ sendText, sendSticker, sendMedia }, msgId, onComplete, log, {
180
+ session = createImmediateSession({ sendText: wrappedSendText, sendSticker: wrappedSendSticker, sendMedia: wrappedSendMedia }, msgId, onComplete, log, {
165
181
  ctx,
166
182
  account,
167
183
  toAccount: heartbeatTarget,
@@ -169,7 +185,7 @@ function createManager(config) {
169
185
  });
170
186
  break;
171
187
  case 'merge-text':
172
- session = createMergeTextSession({ sendText, sendSticker, sendMedia }, msgId, sessionKey, onComplete, log, mergeTextOpts, {
188
+ session = createMergeTextSession({ sendText: wrappedSendText, sendSticker: wrappedSendSticker, sendMedia: wrappedSendMedia }, msgId, sessionKey, onComplete, log, mergeTextOpts, {
173
189
  ctx,
174
190
  account,
175
191
  toAccount: heartbeatTarget,
@@ -27,6 +27,8 @@ export type YuanbaoAccountConfig = {
27
27
  requireMention?: boolean;
28
28
  fallbackReply?: string;
29
29
  markdownHintEnabled?: boolean;
30
+ slowReplyTimeoutMs?: number;
31
+ slowReplyText?: string;
30
32
  };
31
33
  export type YuanbaoConfig = YuanbaoAccountConfig & {
32
34
  accounts?: Record<string, YuanbaoAccountConfig>;
@@ -56,6 +58,9 @@ export type ResolvedYuanbaoAccount = {
56
58
  requireMention: boolean;
57
59
  fallbackReply?: string;
58
60
  markdownHintEnabled: boolean;
61
+ slowReplyTimeoutMs: number;
62
+ slowReplyText: string;
63
+ createType?: BotCreateType;
59
64
  config: YuanbaoAccountConfig;
60
65
  };
61
66
  export type ImImageInfoArrayItem = {
@@ -87,6 +92,10 @@ export type ImMsgSeq = {
87
92
  msg_seq?: number;
88
93
  msg_id?: string;
89
94
  };
95
+ export declare enum BotCreateType {
96
+ ONE_CLICK = 1,
97
+ LINKED = 2
98
+ }
90
99
  export declare enum EnumCLawMsgType {
91
100
  CLAW_MSG_UNKNOWN = 0,
92
101
  CLAW_MSG_GROUP = 1,
@@ -140,6 +149,7 @@ export type YuanbaoBufferedDeliverPayload = {
140
149
  mediaUrls?: string[];
141
150
  isCompactionNotice: boolean;
142
151
  isReasoning: boolean;
152
+ isError?: boolean;
143
153
  };
144
154
  export type YuanbaoSendMsgRequest = {
145
155
  sync_other_machine?: number;
package/dist/src/types.js CHANGED
@@ -1,3 +1,8 @@
1
+ export var BotCreateType;
2
+ (function (BotCreateType) {
3
+ BotCreateType[BotCreateType["ONE_CLICK"] = 1] = "ONE_CLICK";
4
+ BotCreateType[BotCreateType["LINKED"] = 2] = "LINKED";
5
+ })(BotCreateType || (BotCreateType = {}));
1
6
  export var EnumCLawMsgType;
2
7
  (function (EnumCLawMsgType) {
3
8
  EnumCLawMsgType[EnumCLawMsgType["CLAW_MSG_UNKNOWN"] = 0] = "CLAW_MSG_UNKNOWN";
@@ -1,10 +1,11 @@
1
- import type { ResolvedYuanbaoAccount } from '../../types.js';
1
+ import { BotCreateType, type ResolvedYuanbaoAccount } from '../../types.js';
2
2
  export type SignTokenData = {
3
3
  bot_id: string;
4
4
  duration: number;
5
5
  product: string;
6
6
  source: string;
7
7
  token: string;
8
+ create_type?: BotCreateType;
8
9
  };
9
10
  export type AuthHeaders = {
10
11
  'X-ID': string;
@@ -94,7 +94,7 @@ async function doFetchSignToken(account) {
94
94
  }
95
95
  const result = (await response.json());
96
96
  if (result.code === 0) {
97
- mlog.info(`签票成功: bot_id=${result.data.bot_id}`);
97
+ mlog.info(`签票成功: bot_id=${result.data.bot_id}, create_type=${result.data.create_type}`);
98
98
  return result.data;
99
99
  }
100
100
  if (result.code === RETRYABLE_SIGN_CODE && attempt < SIGN_MAX_RETRIES) {
@@ -1,5 +1,5 @@
1
1
  import type { YuanbaoInboundMessage, YuanbaoMsgBodyElement } from '../../types.js';
2
- import type { WsSendC2CMessageData, WsSendGroupMessageData, WsSendMessageResponse, WsSendPrivateHeartbeatData, WsSendGroupHeartbeatData, WsHeartbeatResponse, WsQueryGroupInfoData, WsQueryGroupInfoResponse, WsGetGroupMemberListData, WsGetGroupMemberListResponse } from './types.js';
2
+ import type { WsSendC2CMessageData, WsSendGroupMessageData, WsSendMessageResponse, WsSendPrivateHeartbeatData, WsSendGroupHeartbeatData, WsHeartbeatResponse, WsQueryGroupInfoData, WsQueryGroupInfoResponse, WsGetGroupMemberListData, WsGetGroupMemberListResponse, WsSyncInformationData, WsSyncInformationResponse } from './types.js';
3
3
  export declare const BIZ_MSG_TYPES: {
4
4
  readonly MsgContent: "trpc.yuanbao.yuanbao_conn.yuanbao_openclaw_proxy.MsgContent";
5
5
  readonly MsgBodyElement: "trpc.yuanbao.yuanbao_conn.yuanbao_openclaw_proxy.MsgBodyElement";
@@ -16,6 +16,8 @@ export declare const BIZ_MSG_TYPES: {
16
16
  readonly SendPrivateHeartbeatRsp: "trpc.yuanbao.yuanbao_conn.yuanbao_openclaw_proxy.SendPrivateHeartbeatRsp";
17
17
  readonly SendGroupHeartbeatReq: "trpc.yuanbao.yuanbao_conn.yuanbao_openclaw_proxy.SendGroupHeartbeatReq";
18
18
  readonly SendGroupHeartbeatRsp: "trpc.yuanbao.yuanbao_conn.yuanbao_openclaw_proxy.SendGroupHeartbeatRsp";
19
+ readonly SyncInformationReq: "trpc.yuanbao.yuanbao_conn.yuanbao_openclaw_proxy.SyncInformationReq";
20
+ readonly SyncInformationRsp: "trpc.yuanbao.yuanbao_conn.yuanbao_openclaw_proxy.SyncInformationRsp";
19
21
  };
20
22
  export declare function encodeBizPB(key: string, value: Record<string, unknown>): Uint8Array | null;
21
23
  export declare function decodeBizPB(key: string, data: Uint8Array | ArrayBuffer): any;
@@ -35,3 +37,5 @@ export declare function encodeGetGroupMemberListReq(data: WsGetGroupMemberListDa
35
37
  export declare function decodeGetGroupMemberListRsp(data: Uint8Array | ArrayBuffer, msgId: string): WsGetGroupMemberListResponse | null;
36
38
  export declare function decodeSendPrivateHeartbeatRsp(data: Uint8Array | ArrayBuffer, msgId: string): WsHeartbeatResponse | null;
37
39
  export declare function decodeSendGroupHeartbeatRsp(data: Uint8Array | ArrayBuffer, msgId: string): WsHeartbeatResponse | null;
40
+ export declare function encodeSyncInformationReq(data: WsSyncInformationData): Uint8Array | null;
41
+ export declare function decodeSyncInformationRsp(data: Uint8Array | ArrayBuffer, msgId: string): WsSyncInformationResponse | null;
@@ -25,6 +25,8 @@ export const BIZ_MSG_TYPES = {
25
25
  SendPrivateHeartbeatRsp: `${PKG}.SendPrivateHeartbeatRsp`,
26
26
  SendGroupHeartbeatReq: `${PKG}.SendGroupHeartbeatReq`,
27
27
  SendGroupHeartbeatRsp: `${PKG}.SendGroupHeartbeatRsp`,
28
+ SyncInformationReq: `${PKG}.SyncInformationReq`,
29
+ SyncInformationRsp: `${PKG}.SyncInformationRsp`,
28
30
  };
29
31
  export function encodeBizPB(key, value) {
30
32
  try {
@@ -284,3 +286,21 @@ export function decodeSendGroupHeartbeatRsp(data, msgId) {
284
286
  message: decoded.msg || '',
285
287
  };
286
288
  }
289
+ export function encodeSyncInformationReq(data) {
290
+ return encodeBizPB(BIZ_MSG_TYPES.SyncInformationReq, {
291
+ syncType: data.syncType,
292
+ botVersion: data.botVersion,
293
+ pluginVersion: data.pluginVersion,
294
+ ...(data.commandData ? { commandData: data.commandData } : {}),
295
+ });
296
+ }
297
+ export function decodeSyncInformationRsp(data, msgId) {
298
+ const decoded = decodeBizPB(BIZ_MSG_TYPES.SyncInformationRsp, data);
299
+ if (!decoded)
300
+ return null;
301
+ return {
302
+ msgId,
303
+ code: decoded.code || 0,
304
+ msg: decoded.msg || '',
305
+ };
306
+ }
@@ -1,4 +1,4 @@
1
- import type { WsClientCallbacks, WsClientConfig, WsClientState, WsConnectionConfig, WsSendMessageResponse, WsSendC2CMessageData, WsSendGroupMessageData, WsSendPrivateHeartbeatData, WsSendGroupHeartbeatData, WsHeartbeatResponse, WsQueryGroupInfoData, WsQueryGroupInfoResponse, WsGetGroupMemberListData, WsGetGroupMemberListResponse } from './types.js';
1
+ import type { WsClientCallbacks, WsClientConfig, WsClientState, WsConnectionConfig, WsSendMessageResponse, WsSendC2CMessageData, WsSendGroupMessageData, WsSendPrivateHeartbeatData, WsSendGroupHeartbeatData, WsHeartbeatResponse, WsQueryGroupInfoData, WsQueryGroupInfoResponse, WsGetGroupMemberListData, WsGetGroupMemberListResponse, WsSyncInformationData, WsSyncInformationResponse } from './types.js';
2
2
  export declare const BIZ_CMD: {
3
3
  readonly SendC2CMessage: "send_c2c_message";
4
4
  readonly SendGroupMessage: "send_group_message";
@@ -6,6 +6,7 @@ export declare const BIZ_CMD: {
6
6
  readonly GetGroupMemberList: "get_group_member_list";
7
7
  readonly SendPrivateHeartbeat: "send_private_heartbeat";
8
8
  readonly SendGroupHeartbeat: "send_group_heartbeat";
9
+ readonly SyncInformation: "sync_information";
9
10
  };
10
11
  export declare class YuanbaoWsClient {
11
12
  private connectionConfig;
@@ -44,6 +45,7 @@ export declare class YuanbaoWsClient {
44
45
  getGroupMemberList(data: WsGetGroupMemberListData): Promise<WsGetGroupMemberListResponse>;
45
46
  sendPrivateHeartbeat(data: WsSendPrivateHeartbeatData): Promise<WsHeartbeatResponse>;
46
47
  sendGroupHeartbeat(data: WsSendGroupHeartbeatData): Promise<WsHeartbeatResponse>;
48
+ syncInformation(data: WsSyncInformationData): Promise<WsSyncInformationResponse>;
47
49
  private doConnect;
48
50
  private onMessage;
49
51
  private handleConnMsg;
@@ -2,7 +2,7 @@ import WebSocket from 'ws';
2
2
  import { v4 as uuidv4 } from 'uuid';
3
3
  import { getPluginVersion, getOpenclawVersion, getOperationSystem } from '../../utils/get-env.js';
4
4
  import { decodeConnMsg, decodePB, buildAuthBindMsg, buildPingMsg, buildPushAck, buildBusinessConnMsg, PB_MSG_TYPES, CMD_TYPE, CMD, } from './conn-codec.js';
5
- import { encodeSendC2CMessageReq, encodeSendGroupMessageReq, decodeSendMessageRsp, encodeSendPrivateHeartbeatReq, encodeSendGroupHeartbeatReq, decodeSendPrivateHeartbeatRsp, decodeSendGroupHeartbeatRsp, encodeQueryGroupInfoReq, decodeQueryGroupInfoRsp, encodeGetGroupMemberListReq, decodeGetGroupMemberListRsp, } from './biz-codec.js';
5
+ import { encodeSendC2CMessageReq, encodeSendGroupMessageReq, decodeSendMessageRsp, encodeSendPrivateHeartbeatReq, encodeSendGroupHeartbeatReq, decodeSendPrivateHeartbeatRsp, decodeSendGroupHeartbeatRsp, encodeQueryGroupInfoReq, decodeQueryGroupInfoRsp, encodeGetGroupMemberListReq, decodeGetGroupMemberListRsp, encodeSyncInformationReq, decodeSyncInformationRsp, } from './biz-codec.js';
6
6
  import { createLog } from '../../logger.js';
7
7
  import { msgBodyDesensitization } from '../../utils.js';
8
8
  const DEFAULT_RECONNECT_DELAYS = [1_000, 2_000, 5_000, 10_000, 30_000, 60_000];
@@ -36,6 +36,7 @@ export const BIZ_CMD = {
36
36
  GetGroupMemberList: 'get_group_member_list',
37
37
  SendPrivateHeartbeat: 'send_private_heartbeat',
38
38
  SendGroupHeartbeat: 'send_group_heartbeat',
39
+ SyncInformation: 'sync_information',
39
40
  };
40
41
  const BIZ_MODULE = 'yuanbao_openclaw_proxy';
41
42
  export class YuanbaoWsClient {
@@ -185,6 +186,13 @@ export class YuanbaoWsClient {
185
186
  return Promise.reject(new Error('Failed to encode SendGroupHeartbeatReq'));
186
187
  return this.sendAndWaitWith(BIZ_CMD.SendGroupHeartbeat, BIZ_MODULE, encoded, decodeSendGroupHeartbeatRsp);
187
188
  }
189
+ syncInformation(data) {
190
+ this.log.info('[同步] 发送 SyncInformation 请求', { syncType: data.syncType, botVersion: data.botVersion, pluginVersion: data.pluginVersion });
191
+ const encoded = encodeSyncInformationReq(data);
192
+ if (!encoded)
193
+ return Promise.reject(new Error('Failed to encode SyncInformationReq'));
194
+ return this.sendAndWaitWith(BIZ_CMD.SyncInformation, BIZ_MODULE, encoded, decodeSyncInformationRsp);
195
+ }
188
196
  doConnect() {
189
197
  if (this.disposed)
190
198
  return;
@@ -5,6 +5,7 @@ import { handleInboundMessage, } from '../../message-handler/index.js';
5
5
  import { setActiveWsClient } from './runtime.js';
6
6
  import { decodeInboundMessage } from './biz-codec.js';
7
7
  import { getSignToken, forceRefreshSignToken } from '../api.js';
8
+ import { buildSyncCommandsPayload } from '../../commands/slash-commands/index.js';
8
9
  export async function startYuanbaoWsGateway(params) {
9
10
  const { account, config, abortSignal, runtime, statusSink } = params;
10
11
  const gwlog = createLog('ws');
@@ -26,6 +27,9 @@ export async function startYuanbaoWsGateway(params) {
26
27
  wsConnectId: data.connectId,
27
28
  lastConnectedAt: Date.now(),
28
29
  });
30
+ syncCommandsToServer(client, account.accountId, config).catch((err) => {
31
+ gwlog.warn(`[${account.accountId}] 同步命令列表失败(不影响正常功能)`, { error: String(err) });
32
+ });
29
33
  },
30
34
  onDispatch: (pushEvent) => {
31
35
  gwlog.debug(`[${account.accountId}] WS 推送: cmd=${pushEvent.cmd}, type=${pushEvent.type}`);
@@ -57,6 +61,9 @@ export async function startYuanbaoWsGateway(params) {
57
61
  if (tokenData.bot_id) {
58
62
  account.botId = tokenData.bot_id;
59
63
  }
64
+ if (tokenData.create_type != null) {
65
+ account.createType = tokenData.create_type;
66
+ }
60
67
  return {
61
68
  bizId: 'ybBot',
62
69
  uid,
@@ -114,7 +121,10 @@ async function resolveWsAuth(account) {
114
121
  if (tokenData.bot_id) {
115
122
  account.botId = tokenData.bot_id;
116
123
  }
117
- mlog.info(`[${account.accountId}] 签票完成 uid=${uid} (bot_id=${tokenData.bot_id}, botId=${account.botId})`);
124
+ if (tokenData.create_type != null) {
125
+ account.createType = tokenData.create_type;
126
+ }
127
+ mlog.info(`[${account.accountId}] 签票完成 uid=${uid} (bot_id=${tokenData.bot_id}, botId=${account.botId}), create_type=${tokenData.create_type}`);
118
128
  return {
119
129
  bizId: 'ybBot',
120
130
  uid,
@@ -259,3 +269,30 @@ function handleWsDispatchEvent(params) {
259
269
  dlog.error(`[${account.accountId}][dispatch] WS ${chatType === 'group' ? 'group ' : ''} message handler failed: ${String(err)}`);
260
270
  });
261
271
  }
272
+ async function syncCommandsToServer(client, accountId, config) {
273
+ const slog = createLog('ws');
274
+ const payload = await buildSyncCommandsPayload(config);
275
+ slog.info(`[${accountId}] 同步命令列表`, {
276
+ botVersion: payload.botVersion,
277
+ pluginVersion: payload.pluginVersion,
278
+ botCommands: payload.commandData.botCommands.length,
279
+ pluginCommands: payload.commandData.pluginCommands.length,
280
+ });
281
+ slog.info(`[${accountId}] SyncInformationReq 请求内容:`, {
282
+ sync_type: payload.syncType,
283
+ bot_version: payload.botVersion,
284
+ plugin_version: payload.pluginVersion,
285
+ command_data: {
286
+ bot_commands: payload.commandData.botCommands,
287
+ plugin_commands: payload.commandData.pluginCommands,
288
+ },
289
+ });
290
+ const rsp = await client.syncInformation(payload);
291
+ slog.info(`[${accountId}] SyncInformationRsp 响应内容:`, { code: rsp.code, msg: rsp.msg });
292
+ if (rsp.code !== 0) {
293
+ slog.warn(`[${accountId}] 同步命令列表返回非零码: code=${rsp.code}, msg=${rsp.msg}`);
294
+ }
295
+ else {
296
+ slog.info(`[${accountId}] 同步命令列表成功`);
297
+ }
298
+ }
@@ -1,9 +1,9 @@
1
1
  export { encodePB, decodePB, encodeConnMsg, decodeConnMsg, buildAuthBindMsg, buildPingMsg, buildPushAck, buildBusinessConnMsg, createHead, nextSeqNo, PB_MSG_TYPES, CMD_TYPE, CMD, MODULE, } from './conn-codec.js';
2
2
  export type { PBHead, PBConnMsg } from './conn-codec.js';
3
- export { encodeBizPB, decodeBizPB, toProtoMsgBody, fromProtoMsgBody, encodeSendC2CMessageReq, encodeSendGroupMessageReq, encodeSendPrivateHeartbeatReq, encodeSendGroupHeartbeatReq, decodeInboundMessage, decodeSendC2CMessageRsp, decodeSendGroupMessageRsp, decodeSendMessageRsp, decodeSendPrivateHeartbeatRsp, decodeSendGroupHeartbeatRsp, encodeQueryGroupInfoReq, decodeQueryGroupInfoRsp, encodeGetGroupMemberListReq, decodeGetGroupMemberListRsp, BIZ_MSG_TYPES, } from './biz-codec.js';
3
+ export { encodeBizPB, decodeBizPB, toProtoMsgBody, fromProtoMsgBody, encodeSendC2CMessageReq, encodeSendGroupMessageReq, encodeSendPrivateHeartbeatReq, encodeSendGroupHeartbeatReq, decodeInboundMessage, decodeSendC2CMessageRsp, decodeSendGroupMessageRsp, decodeSendMessageRsp, decodeSendPrivateHeartbeatRsp, decodeSendGroupHeartbeatRsp, encodeQueryGroupInfoReq, decodeQueryGroupInfoRsp, encodeGetGroupMemberListReq, decodeGetGroupMemberListRsp, encodeSyncInformationReq, decodeSyncInformationRsp, BIZ_MSG_TYPES, } from './biz-codec.js';
4
4
  export { YuanbaoWsClient, BIZ_CMD } from './client.js';
5
5
  export { startYuanbaoWsGateway, wsPushToInboundMessage } from './gateway.js';
6
6
  export type { StartWsGatewayParams } from './gateway.js';
7
7
  export { setActiveWsClient, getActiveWsClient, getAllActiveWsClients } from './runtime.js';
8
8
  export { WS_HEARTBEAT } from './types.js';
9
- export type { WsConnectionConfig, WsClientConfig, WsClientState, WsAuthBindResult, WsClientCallbacks, WsPushEvent, WsSendC2CMessageData, WsSendGroupMessageData, WsSendPrivateHeartbeatData, WsSendGroupHeartbeatData, WsOutboundMessageData, WsSendMessageResponse, WsHeartbeatResponse, WsHeartbeatValue, WsQueryGroupInfoData, WsQueryGroupInfoResponse, WsGroupInfo, WsGetGroupMemberListData, WsGetGroupMemberListResponse, WsGroupMember, } from './types.js';
9
+ export type { WsConnectionConfig, WsClientConfig, WsClientState, WsAuthBindResult, WsClientCallbacks, WsPushEvent, WsSendC2CMessageData, WsSendGroupMessageData, WsSendPrivateHeartbeatData, WsSendGroupHeartbeatData, WsOutboundMessageData, WsSendMessageResponse, WsHeartbeatResponse, WsHeartbeatValue, WsQueryGroupInfoData, WsQueryGroupInfoResponse, WsGroupInfo, WsGetGroupMemberListData, WsGetGroupMemberListResponse, WsGroupMember, WsSyncInformationData, WsSyncInformationResponse, WsSyncCommand, WsSyncCommandsData, } from './types.js';
@@ -1,5 +1,5 @@
1
1
  export { encodePB, decodePB, encodeConnMsg, decodeConnMsg, buildAuthBindMsg, buildPingMsg, buildPushAck, buildBusinessConnMsg, createHead, nextSeqNo, PB_MSG_TYPES, CMD_TYPE, CMD, MODULE, } from './conn-codec.js';
2
- export { encodeBizPB, decodeBizPB, toProtoMsgBody, fromProtoMsgBody, encodeSendC2CMessageReq, encodeSendGroupMessageReq, encodeSendPrivateHeartbeatReq, encodeSendGroupHeartbeatReq, decodeInboundMessage, decodeSendC2CMessageRsp, decodeSendGroupMessageRsp, decodeSendMessageRsp, decodeSendPrivateHeartbeatRsp, decodeSendGroupHeartbeatRsp, encodeQueryGroupInfoReq, decodeQueryGroupInfoRsp, encodeGetGroupMemberListReq, decodeGetGroupMemberListRsp, BIZ_MSG_TYPES, } from './biz-codec.js';
2
+ export { encodeBizPB, decodeBizPB, toProtoMsgBody, fromProtoMsgBody, encodeSendC2CMessageReq, encodeSendGroupMessageReq, encodeSendPrivateHeartbeatReq, encodeSendGroupHeartbeatReq, decodeInboundMessage, decodeSendC2CMessageRsp, decodeSendGroupMessageRsp, decodeSendMessageRsp, decodeSendPrivateHeartbeatRsp, decodeSendGroupHeartbeatRsp, encodeQueryGroupInfoReq, decodeQueryGroupInfoRsp, encodeGetGroupMemberListReq, decodeGetGroupMemberListRsp, encodeSyncInformationReq, decodeSyncInformationRsp, BIZ_MSG_TYPES, } from './biz-codec.js';
3
3
  export { YuanbaoWsClient, BIZ_CMD } from './client.js';
4
4
  export { startYuanbaoWsGateway, wsPushToInboundMessage } from './gateway.js';
5
5
  export { setActiveWsClient, getActiveWsClient, getAllActiveWsClients } from './runtime.js';
@@ -470,6 +470,75 @@
470
470
  "id": 2
471
471
  }
472
472
  }
473
+ },
474
+ "SyncInformationType": {
475
+ "values": {
476
+ "SYNC_INFORMATION_TYPE_UNSPECIFIED": 0,
477
+ "SYNC_INFORMATION_TYPE_COMMANDS": 1
478
+ }
479
+ },
480
+ "Command": {
481
+ "fields": {
482
+ "name": {
483
+ "type": "string",
484
+ "id": 1
485
+ },
486
+ "description": {
487
+ "type": "string",
488
+ "id": 2
489
+ }
490
+ }
491
+ },
492
+ "SyncCommandsData": {
493
+ "fields": {
494
+ "botCommands": {
495
+ "rule": "repeated",
496
+ "type": "Command",
497
+ "id": 1
498
+ },
499
+ "pluginCommands": {
500
+ "rule": "repeated",
501
+ "type": "Command",
502
+ "id": 2
503
+ }
504
+ }
505
+ },
506
+ "SyncInformationReq": {
507
+ "oneofs": {
508
+ "data": {
509
+ "oneof": ["commandData"]
510
+ }
511
+ },
512
+ "fields": {
513
+ "syncType": {
514
+ "type": "SyncInformationType",
515
+ "id": 1
516
+ },
517
+ "botVersion": {
518
+ "type": "string",
519
+ "id": 2
520
+ },
521
+ "pluginVersion": {
522
+ "type": "string",
523
+ "id": 3
524
+ },
525
+ "commandData": {
526
+ "type": "SyncCommandsData",
527
+ "id": 11
528
+ }
529
+ }
530
+ },
531
+ "SyncInformationRsp": {
532
+ "fields": {
533
+ "code": {
534
+ "type": "int32",
535
+ "id": 1
536
+ },
537
+ "msg": {
538
+ "type": "string",
539
+ "id": 2
540
+ }
541
+ }
473
542
  }
474
543
  }
475
544
  }
@@ -121,3 +121,22 @@ export type WsHeartbeatResponse = {
121
121
  msg?: string;
122
122
  message?: string;
123
123
  };
124
+ export type WsSyncCommand = {
125
+ name: string;
126
+ description: string;
127
+ };
128
+ export type WsSyncCommandsData = {
129
+ botCommands: WsSyncCommand[];
130
+ pluginCommands: WsSyncCommand[];
131
+ };
132
+ export type WsSyncInformationData = {
133
+ syncType: number;
134
+ botVersion: string;
135
+ pluginVersion: string;
136
+ commandData?: WsSyncCommandsData;
137
+ };
138
+ export type WsSyncInformationResponse = {
139
+ msgId: string;
140
+ code: number;
141
+ msg: string;
142
+ };
@@ -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.9.1",
5
+ "version": "2.10.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.9.1",
3
+ "version": "2.10.0",
4
4
  "type": "module",
5
5
  "description": "Tencent YuanBao intelligent bot channel plugin",
6
6
  "license": "MIT",
@@ -1,52 +0,0 @@
1
- import { createLog } from '../../logger.js';
2
- export function buildAtUserMsgBodyItem(userId, senderNickname) {
3
- return {
4
- msg_type: 'TIMCustomElem',
5
- msg_content: {
6
- data: JSON.stringify({ elem_type: 1002, text: `@${senderNickname ?? ''}`, user_id: userId }),
7
- },
8
- };
9
- }
10
- const FALLBACK_TEXT = '[custom]';
11
- export const customHandler = {
12
- msgType: 'TIMCustomElem',
13
- extract(ctx, elem, resData) {
14
- if (elem.msg_content?.data) {
15
- try {
16
- const customContent = JSON.parse(elem.msg_content?.data);
17
- if (customContent?.elem_type !== 1002) {
18
- return FALLBACK_TEXT;
19
- }
20
- const { botId } = ctx.account;
21
- const isAtBotSelf = customContent?.user_id === botId;
22
- if (!resData.isAtBot) {
23
- resData.isAtBot = isAtBotSelf;
24
- }
25
- createLog('custom').info('@消息', { text: customContent?.text, userId: customContent?.user_id, isAtBot: resData.isAtBot });
26
- if (customContent?.user_id) {
27
- resData.mentions.push({
28
- userId: customContent.user_id,
29
- text: customContent.text || '',
30
- });
31
- }
32
- if (isAtBotSelf && customContent.text) {
33
- resData.botUsername = customContent.text.replace(/^@/, '');
34
- }
35
- return customContent.text || undefined;
36
- }
37
- catch { }
38
- }
39
- return FALLBACK_TEXT;
40
- },
41
- buildMsgBody(data) {
42
- const customData = typeof data.data === 'string'
43
- ? data.data
44
- : JSON.stringify(data.data);
45
- return [
46
- {
47
- msg_type: 'TIMCustomElem',
48
- msg_content: { data: customData },
49
- },
50
- ];
51
- },
52
- };