@qihoo/tuitui-openclaw-channel 1.0.30 → 1.0.31
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/openclaw.plugin.json +4 -4
- package/package.json +1 -1
- package/src/accounts.ts +2 -2
- package/src/channel.ts +2 -1
- package/src/monitor.ts +15 -6
- package/src/tools.ts +12 -4
- package/src/websocket.ts +8 -0
package/openclaw.plugin.json
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"enum": ["channel", "thread"]
|
|
44
44
|
},
|
|
45
45
|
"emojiReaction": { "type": "boolean", "default": true },
|
|
46
|
-
"monitorEnabled": { "type": "boolean", "default":
|
|
46
|
+
"monitorEnabled": { "type": "boolean", "default": true },
|
|
47
47
|
"accounts": {
|
|
48
48
|
"type": "object",
|
|
49
49
|
"additionalProperties": {
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"enum": ["channel", "thread"]
|
|
80
80
|
},
|
|
81
81
|
"emojiReaction": { "type": "boolean", "default": true },
|
|
82
|
-
"monitorEnabled": { "type": "boolean", "default":
|
|
82
|
+
"monitorEnabled": { "type": "boolean", "default": true }
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -131,7 +131,7 @@
|
|
|
131
131
|
"advanced": true
|
|
132
132
|
},
|
|
133
133
|
"monitorEnabled": {
|
|
134
|
-
"help": "
|
|
134
|
+
"help": "是否开启频道agent事件上报,用于频道展示agent执行中间步骤(默认 true)",
|
|
135
135
|
"order": 17,
|
|
136
136
|
"advanced": true
|
|
137
137
|
},
|
|
@@ -185,7 +185,7 @@
|
|
|
185
185
|
"advanced": true
|
|
186
186
|
},
|
|
187
187
|
"accounts.*.monitorEnabled": {
|
|
188
|
-
"help": "
|
|
188
|
+
"help": "是否开启频道agent事件上报,用于频道展示agent执行中间步骤(默认 true)。",
|
|
189
189
|
"order": 311,
|
|
190
190
|
"advanced": true
|
|
191
191
|
}
|
package/package.json
CHANGED
package/src/accounts.ts
CHANGED
|
@@ -7,7 +7,7 @@ export const dmPolicyDefault = 'pairing';
|
|
|
7
7
|
export const groupPolicyDefault = 'allowlist';
|
|
8
8
|
export const requireMentionDefault = true;
|
|
9
9
|
export const emojiReactionDefault = true;
|
|
10
|
-
export const monitorEnabledDefault =
|
|
10
|
+
export const monitorEnabledDefault = true;
|
|
11
11
|
|
|
12
12
|
const mergeArrs = (arr1: any, arr2: any) => {
|
|
13
13
|
return [...new Set([...(arr1 || []), ...arr2 || []])];
|
|
@@ -20,7 +20,7 @@ export const getAccountInfo = (acct?: any) => ({
|
|
|
20
20
|
dmPolicy: acct?.dmPolicy || dmPolicyDefault,
|
|
21
21
|
allowFrom: parseAllowFroms(acct?.allowFrom || []),
|
|
22
22
|
// 群组策略与白名单、群组级覆盖
|
|
23
|
-
groupPolicy: acct?.
|
|
23
|
+
groupPolicy: acct?.groupPolicy || groupPolicyDefault,
|
|
24
24
|
groupAllowFrom: parseAllowFroms(acct?.groupAllowFrom || []),
|
|
25
25
|
requireMention: isEnabled(acct?.requireMention ?? requireMentionDefault),
|
|
26
26
|
emojiReaction: isEnabled(acct?.emojiReaction ?? emojiReactionDefault),
|
package/src/channel.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk/account-id';
|
|
7
7
|
import { setAccountEnabledInConfigSection, deleteAccountFromConfigSection, OpenClawPluginApi } from 'openclaw/plugin-sdk/core';
|
|
8
8
|
import { CHANNEL_ID, CHANNEL_NAME } from "./const";
|
|
9
|
+
import { version } from "../package.json"
|
|
9
10
|
import { handleInboundMessage } from './inbound';
|
|
10
11
|
import { guessChatTypeV2 } from "./chat_record"
|
|
11
12
|
import {
|
|
@@ -55,7 +56,7 @@ function createTuiTuiChannelPlugin(apiRuntime: any) {
|
|
|
55
56
|
id: CHANNEL_ID,
|
|
56
57
|
label: CHANNEL_NAME,
|
|
57
58
|
selectionLabel: CHANNEL_NAME,
|
|
58
|
-
detailLabel: CHANNEL_NAME,
|
|
59
|
+
detailLabel: CHANNEL_NAME + " " + version,
|
|
59
60
|
docsPath: `/channels/${CHANNEL_ID}`,
|
|
60
61
|
blurb: `Connect to ${CHANNEL_NAME} bot via WebSocket`,
|
|
61
62
|
order: 100,
|
package/src/monitor.ts
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* TuiTui Monitor — 将 OpenClaw Hook 事件批量上报到监控接口。
|
|
3
3
|
*
|
|
4
4
|
* 每个账户(appId/appSecret)维护独立队列,满足以下任一条件时批量上报:
|
|
5
|
-
* - 队列积累达 BATCH_MAX_SIZE
|
|
6
|
-
* - 距上次 flush 超过 BATCH_INTERVAL_MS
|
|
5
|
+
* - 队列积累达 BATCH_MAX_SIZE 条
|
|
6
|
+
* - 距上次 flush 超过 BATCH_INTERVAL_MS
|
|
7
7
|
*
|
|
8
|
-
* 需在配置中将 monitorEnabled 设为 true
|
|
8
|
+
* 需在配置中将 monitorEnabled 设为 true 才会上报
|
|
9
9
|
*
|
|
10
10
|
* 配置示例(openclaw.json):
|
|
11
11
|
* channels.tuitui.monitorEnabled: true
|
|
@@ -14,6 +14,7 @@ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk';
|
|
|
14
14
|
import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk/account-id';
|
|
15
15
|
import { CHANNEL_ID } from './const';
|
|
16
16
|
import { tuituiRobotApi } from './robot_api';
|
|
17
|
+
import {parseChannelIdBySessionKey} from "./chat_base"
|
|
17
18
|
|
|
18
19
|
// 批量上报参数
|
|
19
20
|
const BATCH_MAX_SIZE = 100; // 达到此条数立即 flush
|
|
@@ -272,19 +273,27 @@ function report(
|
|
|
272
273
|
ctx: unknown,
|
|
273
274
|
data: unknown,
|
|
274
275
|
): void {
|
|
275
|
-
let
|
|
276
|
+
let sessionKey = "";
|
|
276
277
|
|
|
277
278
|
// subagent hook 的 ctx 有 childSessionKey,普通 hook 有 sessionKey
|
|
278
279
|
if (eventType === 'subagent_spawned' || eventType === 'subagent_ended') {
|
|
279
|
-
|
|
280
|
+
sessionKey = (ctx as any)?.childSessionKey
|
|
280
281
|
} else {
|
|
281
|
-
|
|
282
|
+
sessionKey = (ctx as any)?.sessionKey
|
|
282
283
|
}
|
|
283
284
|
|
|
285
|
+
const agentId = extractAgentId(sessionKey);
|
|
284
286
|
if (!agentId) return;
|
|
285
287
|
|
|
288
|
+
// 仅频道支持;私聊群聊不启用
|
|
289
|
+
const channelId = parseChannelIdBySessionKey(sessionKey);
|
|
290
|
+
if(!channelId) return;
|
|
291
|
+
|
|
292
|
+
const reportedAccountIds = new Map<string, boolean>();
|
|
286
293
|
for (const target of targets) {
|
|
287
294
|
if (target.agentId !== agentId) continue;
|
|
295
|
+
if (reportedAccountIds.has(target.accountId)) continue;
|
|
296
|
+
reportedAccountIds.set(target.accountId, true);
|
|
288
297
|
|
|
289
298
|
const payload: MonitorPayload = {
|
|
290
299
|
event: eventType,
|
package/src/tools.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { resolveAccount } from "./accounts"
|
|
|
4
4
|
import { Type } from "@sinclair/typebox";
|
|
5
5
|
|
|
6
6
|
import {sendTextMsg, get_announcement} from "./outbound"
|
|
7
|
-
import {CHAT_TYPE_DIRECT,CHAT_TYPE_GROUP,CHAT_TYPE_CHANNEL, teamsBuildChatId} from "./chat_base"
|
|
7
|
+
import {CHAT_TYPE_DIRECT,CHAT_TYPE_GROUP,CHAT_TYPE_CHANNEL,guessChatType, teamsBuildChatId} from "./chat_base"
|
|
8
8
|
import {getChatRecord, getChannelInfoById} from "./chat_record"
|
|
9
9
|
import {file_space_list, file_space_add} from "./filespace"
|
|
10
10
|
|
|
@@ -74,9 +74,17 @@ const tuitui_send_channel_post_factory = (ctx: OpenClawPluginToolContext) => {
|
|
|
74
74
|
return tool_errmsg(`invalid tuitui account ${ctx.agentAccountId}`);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
77
|
+
let chatId = "";
|
|
78
|
+
const guessType = guessChatType(params?.channel_id);
|
|
79
|
+
if(guessType == CHAT_TYPE_CHANNEL) {
|
|
80
|
+
// 解决badcase: 发个帖子,内容是个笑话
|
|
81
|
+
// 如果用户没明确说频道ID,大模型在频道调用发帖tool会传sessionKey过来
|
|
82
|
+
chatId = params?.channel_id;
|
|
83
|
+
} else {
|
|
84
|
+
const channel_info = await getChannelInfoById(account, params?.channel_id);
|
|
85
|
+
const team_id = channel_info?.team_id;
|
|
86
|
+
chatId = teamsBuildChatId(team_id, params?.channel_id, params?.parent_id);
|
|
87
|
+
}
|
|
80
88
|
|
|
81
89
|
const result: Promise<any> = sendTextMsg(account, chatId, CHAT_TYPE_CHANNEL, params?.markdown);
|
|
82
90
|
return result;
|
package/src/websocket.ts
CHANGED
|
@@ -73,6 +73,14 @@ export default function createWebSocket({ account, log, abortSignal, onConnected
|
|
|
73
73
|
if (firsEvtId) wsEvtIds.delete(firsEvtId);
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// 收到包含心跳在内的任意消息,则重置心跳超时计时器,如果 300 秒内没有收到任何消息(包括心跳),则认为连接已失效
|
|
77
|
+
_clearTimeoutTimer();
|
|
78
|
+
_timeoutId = setTimeout(() => {
|
|
79
|
+
log?.warn?.(`[${CHANNEL_ID}] AccountId: ${accountId}, WebSocket[${wsId}] Heartbeat Timeout`);
|
|
80
|
+
_closeWS();
|
|
81
|
+
_restartWS(1e3);
|
|
82
|
+
}, 3e5); // 300秒心跳超时
|
|
83
|
+
|
|
76
84
|
const wsEvent = json?.body?.event;
|
|
77
85
|
if (wsEvent === 'keepalive') return;
|
|
78
86
|
|