@qihoo/tuitui-openclaw-channel 1.0.8 → 1.0.10
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/package.json +1 -2
- package/src/channel.ts +40 -90
- package/src/confs.ts +90 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qihoo/tuitui-openclaw-channel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"maintainers": [
|
|
5
5
|
{
|
|
6
6
|
"name": "huzunjie",
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
"description": "TuiTui channel plugin for OpenClaw",
|
|
19
19
|
"type": "module",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"zod": "^4.3.6",
|
|
22
21
|
"ws": "^8.13.0"
|
|
23
22
|
},
|
|
24
23
|
"openclaw": {
|
package/src/channel.ts
CHANGED
|
@@ -5,12 +5,10 @@
|
|
|
5
5
|
* Supports single chat (text, image, voice, file) and group chat with @mentions.
|
|
6
6
|
*/
|
|
7
7
|
import WebSocket from 'ws';
|
|
8
|
-
import { z } from 'zod';
|
|
9
8
|
import {
|
|
10
9
|
DEFAULT_ACCOUNT_ID,
|
|
11
10
|
setAccountEnabledInConfigSection,
|
|
12
11
|
deleteAccountFromConfigSection,
|
|
13
|
-
buildChannelConfigSchema,
|
|
14
12
|
} from 'openclaw/plugin-sdk';
|
|
15
13
|
import type { TuiTuiInboundMessage, TuiTuiOutboundDeliverOptions } from './types';
|
|
16
14
|
import {
|
|
@@ -24,86 +22,33 @@ import {
|
|
|
24
22
|
sendMediaMsg,
|
|
25
23
|
} from "./utils";
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
import { capabilities, configSchema } from './confs';
|
|
26
|
+
|
|
27
|
+
const isEnabled = (val: any) => val === undefined || !!val;
|
|
28
|
+
const isConfigured = (account: any)=> !!(account?.appId && account?.appSecret);
|
|
29
|
+
const resolveAccount = (cfg: any, accountId?: string | null) => {
|
|
28
30
|
const channelConfig = cfg?.channels?.[CHANNEL_ID] || {};
|
|
29
31
|
const targetId = accountId || DEFAULT_ACCOUNT_ID;
|
|
30
32
|
const acct = targetId === DEFAULT_ACCOUNT_ID ? channelConfig : channelConfig.accounts?.[targetId];
|
|
31
33
|
return {
|
|
32
34
|
accountId: targetId,
|
|
33
|
-
enabled: acct?.enabled
|
|
35
|
+
enabled: isEnabled(acct?.enabled),
|
|
34
36
|
appId: acct?.appId as string | undefined,
|
|
35
37
|
appSecret: acct?.appSecret as string | undefined,
|
|
36
38
|
dmPolicy: acct?.dmPolicy || 'pairing',
|
|
37
39
|
allowFrom: acct?.allowFrom || [],
|
|
38
40
|
// 群组策略与白名单、群组级覆盖
|
|
39
41
|
groupPolicy: (acct?.groupPolicy as string | undefined) ?? 'allowlist',
|
|
40
|
-
groupAllowFrom: acct
|
|
42
|
+
groupAllowFrom: acct?.groupAllowFrom || [],
|
|
41
43
|
groups: (acct?.groups as Record<string, { requireMention?: boolean, shouldReply?: boolean }> | undefined) ?? {},
|
|
42
44
|
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const isConfigured = (account: any)=> Boolean(
|
|
46
|
-
String(account?.appId ?? '').trim() &&
|
|
47
|
-
String(account?.appSecret ?? '').trim()
|
|
48
|
-
);
|
|
49
|
-
|
|
45
|
+
};
|
|
50
46
|
const isToGroup = (chatId: string) => /^\d+$/.test(chatId);
|
|
51
|
-
const arrLowerCaseTrim = (arr: any[]) => arr.filter((v: any) => !!v).map((v: any) => String(v).toLowerCase().trim())
|
|
52
|
-
|
|
53
|
-
z
|
|
54
|
-
.object({
|
|
55
|
-
enabled: z.boolean().optional().describe('开启或关闭'),
|
|
56
|
-
appId: z.string().min(1).describe('推推机器人身份 AppId(你可以推推搜索【推推机器人助手】,和它聊天自助申请推推机器人)'),
|
|
57
|
-
appSecret: z.string().min(1).describe('推推机器人密钥 Secret'),
|
|
58
|
-
// 私聊策略(默认 pairing)
|
|
59
|
-
dmPolicy: z
|
|
60
|
-
.enum(['pairing', 'allowlist', 'open', 'disabled'])
|
|
61
|
-
.optional()
|
|
62
|
-
.default('pairing')
|
|
63
|
-
.describe('私聊策略:pairing=配对(默认);allowlist=白名单;open=允许所有(不安全);disabled=禁用私聊',),
|
|
64
|
-
// 私聊允许列表(当 dmPolicy=allowlist 生效;pairing 下也可显式允许)
|
|
65
|
-
allowFrom: z
|
|
66
|
-
.array(z.string())
|
|
67
|
-
.optional()
|
|
68
|
-
.describe('私聊白名单(dmPolicy=allowlist 时生效;pairing 下可用于显式放行用户)'),
|
|
69
|
-
|
|
70
|
-
// 群组策略
|
|
71
|
-
groupPolicy: z
|
|
72
|
-
.enum(['allowlist', 'disabled'])
|
|
73
|
-
.default('allowlist')
|
|
74
|
-
.describe('群聊策略:allowlist=白名单;disabled=禁用群聊',),
|
|
75
|
-
// 仅在 allowlist 生效的群组 ID 列表
|
|
76
|
-
groupAllowFrom: z
|
|
77
|
-
.array(z.string())
|
|
78
|
-
.optional()
|
|
79
|
-
.describe('群组白名单(仅在 groupPolicy=allowlist 生效)'),
|
|
80
|
-
// 每个群组的覆盖配置
|
|
81
|
-
/*
|
|
82
|
-
groups: z
|
|
83
|
-
.record(
|
|
84
|
-
z.string(),
|
|
85
|
-
z
|
|
86
|
-
.object({
|
|
87
|
-
requireMention: z
|
|
88
|
-
.boolean()
|
|
89
|
-
.optional()
|
|
90
|
-
.describe('该群组是否需要 @ 机器人才触发 Agent(默认 true)'),
|
|
91
|
-
shouldReply: z
|
|
92
|
-
.boolean()
|
|
93
|
-
.optional()
|
|
94
|
-
.describe('该群组是否允许 Agent 回复(默认 true)'),
|
|
95
|
-
})
|
|
96
|
-
.passthrough(),
|
|
97
|
-
)
|
|
98
|
-
.optional()
|
|
99
|
-
.describe('群组级覆盖配置'),
|
|
100
|
-
*/
|
|
101
|
-
}) as any,
|
|
102
|
-
);
|
|
47
|
+
const arrLowerCaseTrim = (arr: any[]) => arr.filter((v: any) => !!v).map((v: any) => String(v).toLowerCase().trim());
|
|
48
|
+
|
|
103
49
|
export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
104
50
|
return {
|
|
105
51
|
id: CHANNEL_ID,
|
|
106
|
-
|
|
107
52
|
meta: {
|
|
108
53
|
id: CHANNEL_ID,
|
|
109
54
|
label: CHANNEL_NAME,
|
|
@@ -113,29 +58,16 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
113
58
|
blurb: `Connect to ${CHANNEL_NAME} bot via WebSocket`,
|
|
114
59
|
order: 100,
|
|
115
60
|
},
|
|
116
|
-
|
|
117
|
-
capabilities: {
|
|
118
|
-
chatTypes: ['direct' as const, 'group' as const],
|
|
119
|
-
media: true,
|
|
120
|
-
threads: false,
|
|
121
|
-
reactions: false,
|
|
122
|
-
edit: false,
|
|
123
|
-
unsend: false,
|
|
124
|
-
reply: true,
|
|
125
|
-
effects: false,
|
|
126
|
-
blockStreaming: false,
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
reload: { configPrefixes: [`channels.${CHANNEL_ID}`] },
|
|
130
|
-
|
|
61
|
+
capabilities,
|
|
131
62
|
configSchema,
|
|
63
|
+
reload: { configPrefixes: [`channels.${CHANNEL_ID}`] },
|
|
132
64
|
|
|
133
65
|
config: {
|
|
134
66
|
listAccountIds: (cfg: any) => {
|
|
135
67
|
const base = cfg?.channels?.[CHANNEL_ID];
|
|
136
68
|
if (!base) return [];
|
|
137
69
|
const ids = new Set<string>();
|
|
138
|
-
if (base.enabled) ids.add(DEFAULT_ACCOUNT_ID);
|
|
70
|
+
if (isEnabled(base.enabled)) ids.add(DEFAULT_ACCOUNT_ID);
|
|
139
71
|
if (base.accounts) for (const k in base.accounts) ids.add(k);
|
|
140
72
|
return Array.from(ids);
|
|
141
73
|
},
|
|
@@ -144,13 +76,13 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
144
76
|
|
|
145
77
|
defaultAccountId: (_cfg: any) => DEFAULT_ACCOUNT_ID,
|
|
146
78
|
|
|
147
|
-
isEnabled
|
|
79
|
+
isEnabled,
|
|
148
80
|
|
|
149
81
|
isConfigured,
|
|
150
82
|
|
|
151
83
|
describeAccount: (account: any) => ({
|
|
152
84
|
accountId: account?.accountId || DEFAULT_ACCOUNT_ID,
|
|
153
|
-
enabled: account?.enabled
|
|
85
|
+
enabled: isEnabled(account?.enabled),
|
|
154
86
|
configured: isConfigured(account),
|
|
155
87
|
}),
|
|
156
88
|
|
|
@@ -180,7 +112,7 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
180
112
|
...cfg.channels,
|
|
181
113
|
[CHANNEL_ID]: {
|
|
182
114
|
...(cfg?.channels?.[CHANNEL_ID] ?? {}),
|
|
183
|
-
enabled:
|
|
115
|
+
enabled: true,
|
|
184
116
|
appId: undefined,
|
|
185
117
|
appSecret: undefined,
|
|
186
118
|
},
|
|
@@ -215,7 +147,7 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
215
147
|
return {
|
|
216
148
|
...params.runtime,
|
|
217
149
|
accountId: params.accountId ?? account?.accountId ?? DEFAULT_ACCOUNT_ID,
|
|
218
|
-
enabled: account?.enabled
|
|
150
|
+
enabled: isEnabled(account?.enabled),
|
|
219
151
|
configured: isConfigured(account),
|
|
220
152
|
};
|
|
221
153
|
},
|
|
@@ -314,7 +246,7 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
314
246
|
};
|
|
315
247
|
const account = resolveAccount(cfg, accountId);
|
|
316
248
|
|
|
317
|
-
if (!account.enabled) {
|
|
249
|
+
if (!isEnabled(account.enabled)) {
|
|
318
250
|
log?.info?.(`[${CHANNEL_ID}] account ${accountId} is disabled, skipping`);
|
|
319
251
|
return _setStatus();
|
|
320
252
|
}
|
|
@@ -576,8 +508,24 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
576
508
|
// 确保 accountId 有有效值,优先使用 account.accountId,其次使用 ctx 中的 accountId,最后使用 DEFAULT_ACCOUNT_ID
|
|
577
509
|
|
|
578
510
|
const effectiveAccountId = String(account.accountId || accountId || DEFAULT_ACCOUNT_ID || 'default');
|
|
579
|
-
const
|
|
580
|
-
|
|
511
|
+
const chatId = chatTypeIsGroup ? String(groupId) : String(senderId);
|
|
512
|
+
|
|
513
|
+
// 关于 sessionKey 格式的解释
|
|
514
|
+
// https://docs.openclaw.ai/channels/channel-routing
|
|
515
|
+
// 但 sessionKey 一般不要自己拼字符串,需要用系统api识别(根据 bindings)
|
|
516
|
+
const route = apiRuntime.channel.routing.resolveAgentRoute({
|
|
517
|
+
cfg: await apiRuntime.config.loadConfig(),
|
|
518
|
+
channel: CHANNEL_ID,
|
|
519
|
+
accountId: account.accountId,
|
|
520
|
+
peer: {
|
|
521
|
+
kind: chatTypeIsGroup ? 'group' : 'direct',
|
|
522
|
+
id: chatId,
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
const sessionKey = route.sessionKey;
|
|
527
|
+
console.log(`[${CHANNEL_ID}] chatType: ${chatType}, chatId ${chatId}, senderId: ${senderId}, accountId: ${accountId}, DEFAULT_ACCOUNT_ID: ${DEFAULT_ACCOUNT_ID}, effectiveAccountId: ${effectiveAccountId}`);
|
|
528
|
+
console.log(`[${CHANNEL_ID}] dispatching to agent session=${sessionKey}`);
|
|
581
529
|
|
|
582
530
|
const msgCtx: any = {
|
|
583
531
|
Body: text || ' ',
|
|
@@ -587,11 +535,13 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
587
535
|
SessionKey: String(sessionKey).replace(/\//g, '_'),
|
|
588
536
|
AccountId: effectiveAccountId,
|
|
589
537
|
OriginatingChannel: CHANNEL_ID,
|
|
590
|
-
OriginatingTo:
|
|
538
|
+
OriginatingTo: chatId,
|
|
591
539
|
ChatType: chatType,
|
|
592
540
|
Surface: CHANNEL_ID,
|
|
593
541
|
Provider: CHANNEL_ID,
|
|
594
542
|
SenderName: msgUname || String(senderId),
|
|
543
|
+
MsgUname: msgUname,
|
|
544
|
+
UserAccount: userAccount,
|
|
595
545
|
};
|
|
596
546
|
|
|
597
547
|
// Add group-specific fields
|
|
@@ -666,7 +616,7 @@ export function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
666
616
|
_sendMsg = defSendMsg;
|
|
667
617
|
if (!abortSignal?.aborted) {
|
|
668
618
|
log?.warn?.(`[${CHANNEL_ID}] WebSocket Restart`);
|
|
669
|
-
startWebSocket
|
|
619
|
+
setTimeout(startWebSocket, 10e3); // 10秒后尝试重启
|
|
670
620
|
}
|
|
671
621
|
};
|
|
672
622
|
ws.on('close', () => {
|
package/src/confs.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/*** 一些配置字段设定信息 ***/
|
|
2
|
+
const fieldsConf = {
|
|
3
|
+
enabled: { type: 'boolean', default: true },
|
|
4
|
+
appId: { type: 'string' } ,
|
|
5
|
+
appSecret: { type: 'string' },
|
|
6
|
+
dmPolicy: {
|
|
7
|
+
type: 'string',
|
|
8
|
+
default: 'pairing',
|
|
9
|
+
enum: ['pairing', 'allowlist', 'open', 'disabled'],
|
|
10
|
+
},
|
|
11
|
+
allowFrom: {
|
|
12
|
+
type: 'array',
|
|
13
|
+
items: { type: 'string' }
|
|
14
|
+
},
|
|
15
|
+
groupPolicy: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
default: 'allowlist',
|
|
18
|
+
enum: ['allowlist', 'disabled']
|
|
19
|
+
},
|
|
20
|
+
groupAllowFrom: {
|
|
21
|
+
type: 'array',
|
|
22
|
+
items: { type: 'string' }
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const configSchema = {
|
|
27
|
+
schema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
properties: {
|
|
31
|
+
...fieldsConf,
|
|
32
|
+
/* 基础配置信息中,准备 accounts 配置支持多账户管理,以备后续允许用户配置多个推推机器人账号
|
|
33
|
+
accounts: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
additionalProperties: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
additionalProperties: false,
|
|
38
|
+
properties: { ...fieldsConf },
|
|
39
|
+
},
|
|
40
|
+
},*/
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
uiHints: {
|
|
44
|
+
// 基础配置(优先显示)
|
|
45
|
+
enabled: { order: 1, help: '开启或关闭' },
|
|
46
|
+
appId: {
|
|
47
|
+
help: '推推机器人身份 AppId(你可以推推搜索【推推机器人助手】,和它聊天自助申请推推机器人)',
|
|
48
|
+
order: 2,
|
|
49
|
+
},
|
|
50
|
+
appSecret: {
|
|
51
|
+
help: '推推机器人密钥 Secret(推推机器人的 App Secret)',
|
|
52
|
+
order: 3,
|
|
53
|
+
},
|
|
54
|
+
// 高级配置
|
|
55
|
+
dmPolicy: {
|
|
56
|
+
help: '私聊策略(pairing=配对(默认);allowlist=白名单;open=允许所有(不安全);disabled=禁用私聊)',
|
|
57
|
+
order: 10,
|
|
58
|
+
},
|
|
59
|
+
allowFrom: {
|
|
60
|
+
help: '私聊白名单(dmPolicy=allowlist 时生效;pairing 下可用于显式放行用户)',
|
|
61
|
+
order: 11,
|
|
62
|
+
},
|
|
63
|
+
groupPolicy: {
|
|
64
|
+
help: '群聊策略(allowlist=白名单;disabled=禁用群聊)',
|
|
65
|
+
order: 12,
|
|
66
|
+
},
|
|
67
|
+
groupAllowFrom: {
|
|
68
|
+
help: '群组白名单(仅在 groupPolicy=allowlist 生效)',
|
|
69
|
+
order: 13,
|
|
70
|
+
},
|
|
71
|
+
/* 基础配置信息中,准备 accounts 配置支持多账户管理,以备后续允许用户配置多个推推机器人账号
|
|
72
|
+
accounts: {
|
|
73
|
+
help: 'Accounts(多账户配置)',
|
|
74
|
+
order: 30,
|
|
75
|
+
advanced: true
|
|
76
|
+
},*/
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const capabilities = {
|
|
81
|
+
chatTypes: ['direct' as const, 'group' as const],
|
|
82
|
+
media: true,
|
|
83
|
+
threads: false,
|
|
84
|
+
reactions: false,
|
|
85
|
+
edit: false,
|
|
86
|
+
unsend: false,
|
|
87
|
+
reply: true,
|
|
88
|
+
effects: false,
|
|
89
|
+
blockStreaming: false,
|
|
90
|
+
};
|