openclaw-openagent 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2 -0
- package/dist/src/config/config-schema.d.ts +9 -0
- package/dist/src/config/config-schema.js +6 -0
- package/dist/src/messaging/executor.d.ts +2 -3
- package/dist/src/messaging/executor.js +4 -31
- package/dist/src/plugin-ui/assets/openagent-override.js +124 -18
- package/dist/src/proxy/auth-proxy.js +5 -0
- package/dist/src/runtime/update-checker.d.ts +18 -0
- package/dist/src/runtime/update-checker.js +216 -0
- package/index.ts +3 -0
- package/package.json +1 -3
- package/src/app/channel-tools.ts +0 -249
- package/src/app/discovery-tools.ts +0 -273
- package/src/app/download-file-tool.ts +0 -133
- package/src/app/hooks.ts +0 -144
- package/src/app/index.ts +0 -84
- package/src/app/messaging-tools.ts +0 -79
- package/src/app/ops-tools.ts +0 -155
- package/src/app/remote-agent-tool.ts +0 -762
- package/src/app/types.ts +0 -68
- package/src/app/upload-file-tool.ts +0 -411
- package/src/app/verbose-preflight.ts +0 -190
- package/src/auth/config.ts +0 -266
- package/src/auth/credential-manager.ts +0 -146
- package/src/auth/index.ts +0 -24
- package/src/auth/verify.ts +0 -99
- package/src/channel.ts +0 -566
- package/src/compat.ts +0 -94
- package/src/config/config-schema.ts +0 -39
- package/src/messaging/aggregator.ts +0 -120
- package/src/messaging/collector.ts +0 -89
- package/src/messaging/executor.ts +0 -72
- package/src/messaging/inbound.ts +0 -150
- package/src/messaging/index.ts +0 -11
- package/src/messaging/mention-protocol.ts +0 -94
- package/src/messaging/process-c2c-request.ts +0 -564
- package/src/messaging/process-message.ts +0 -373
- package/src/messaging/scheduler.ts +0 -55
- package/src/messaging/types.ts +0 -38
- package/src/plugin-ui/adapters/oc-2026-04.js +0 -103
- package/src/plugin-ui/adapters/oc-2026-05.js +0 -125
- package/src/plugin-ui/adapters/oc-2026-06.js +0 -125
- package/src/plugin-ui/adapters/oc-unknown.js +0 -48
- package/src/plugin-ui/assets/agentbook-icon.svg +0 -5
- package/src/plugin-ui/assets/bg.png +0 -0
- package/src/plugin-ui/assets/icon.png +0 -0
- package/src/plugin-ui/assets/magic.svg +0 -5
- package/src/plugin-ui/assets/openagent-override.js +0 -11156
- package/src/plugin-ui/build.cjs +0 -463
- package/src/plugin-ui/index.ts +0 -18
- package/src/plugin-ui/modules/agent-book/panel/agent-book.js +0 -570
- package/src/plugin-ui/modules/agent-book/panel/agent-card.js +0 -170
- package/src/plugin-ui/modules/agent-book/panel/agent-data.js +0 -643
- package/src/plugin-ui/modules/agent-book/panel/inject-ui.js +0 -665
- package/src/plugin-ui/modules/agent-book/panel/mention-state.js +0 -206
- package/src/plugin-ui/modules/agent-book/panel/styles.js +0 -833
- package/src/plugin-ui/modules/agent-book/panel/theme-adapter.js +0 -86
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/components-core.js +0 -295
- package/src/plugin-ui/modules/agent-book/remote-agent-tool/thought-chain-card.js +0 -211
- package/src/plugin-ui/modules/agent-book/scanner.js +0 -131
- package/src/plugin-ui/modules/agent-book/travelcard/travel-cards.js +0 -500
- package/src/plugin-ui/modules/agent-book/travelcard/travel-engine.js +0 -652
- package/src/plugin-ui/modules/agent-book/travelcard/travel-styles.js +0 -256
- package/src/plugin-ui/modules/loader/bootstrap.js +0 -38
- package/src/plugin-ui/modules/loader/shared-state.js +0 -838
- package/src/plugin-ui/modules/loader/ws-intercept.js +0 -199
- package/src/plugin-ui/modules/remote-agent/chunk-parser.js +0 -161
- package/src/plugin-ui/modules/remote-agent/execution-card.js +0 -530
- package/src/plugin-ui/modules/remote-agent/markdown-renderer.js +0 -256
- package/src/plugin-ui/modules/remote-agent/media-links.js +0 -151
- package/src/plugin-ui/modules/remote-agent/native-style-adapter.js +0 -134
- package/src/plugin-ui/modules/remote-agent/output-card.js +0 -342
- package/src/plugin-ui/modules/remote-agent/progress-store.js +0 -363
- package/src/plugin-ui/modules/remote-agent/render-hooks.js +0 -1700
- package/src/plugin-ui/modules/remote-agent/styles.js +0 -896
- package/src/plugin-ui/modules/remote-agent/tool-card-model.js +0 -130
- package/src/plugin-ui/postinstall-deploy.cjs +0 -52
- package/src/plugin-ui/ui-extension-loader/backup.ts +0 -92
- package/src/plugin-ui/ui-extension-loader/index.ts +0 -276
- package/src/plugin-ui/ui-extension-loader/locator.ts +0 -152
- package/src/plugin-ui/ui-extension-loader/manifest.ts +0 -57
- package/src/plugin-ui/ui-extension-loader/registry-regex.ts +0 -944
- package/src/plugin-ui/ui-extension-loader/removed-extensions.ts +0 -70
- package/src/plugin-ui/ui-extension-loader/types.ts +0 -72
- package/src/proxy/auth-proxy.ts +0 -409
- package/src/runtime/account.ts +0 -549
- package/src/runtime/index.ts +0 -7
- package/src/runtime/plugin-runtime.ts +0 -94
- package/src/runtime/registry.ts +0 -71
- package/src/sdk/CLASS_MAP.md +0 -143
- package/src/sdk/index.d.ts +0 -126
- package/src/sdk/index.js +0 -23990
- package/src/sdk/modules/cloud-search-module.js +0 -1117
- package/src/sdk/modules/follow-module.js +0 -1069
- package/src/sdk/modules/group-module.js +0 -7397
- package/src/sdk/modules/relationship-module.js +0 -2269
- package/src/sdk/modules/signaling-module.js +0 -1468
- package/src/sdk/modules/tim-upload-plugin.js +0 -730
- package/src/sdk/node-env/http-request.js +0 -90
- package/src/sdk/node-env/index.js +0 -57
- package/src/sdk/node-env/storage.js +0 -114
- package/src/sdk/package.json +0 -10
- package/src/sdk/tsconfig.json +0 -16
- package/src/state/pending-invocation-store.ts +0 -43
- package/src/state/store.ts +0 -756
- package/src/tim/c2c.ts +0 -451
- package/src/tim/channels.ts +0 -364
- package/src/tim/client.ts +0 -330
- package/src/tim/index.ts +0 -18
- package/src/tim/messages.ts +0 -166
- package/src/tim/sdk-logger-init.ts +0 -50
- package/src/tools.ts +0 -10
- package/src/transport/factory.ts +0 -95
- package/src/transport/oasn/index.ts +0 -17
- package/src/transport/oasn/oasn-agent-card.ts +0 -111
- package/src/transport/oasn/oasn-discovery.ts +0 -108
- package/src/transport/oasn/oasn-files.ts +0 -210
- package/src/transport/oasn/oasn-http.ts +0 -529
- package/src/transport/oasn/oasn-invocation.ts +0 -604
- package/src/transport/oasn/oasn-normalize.ts +0 -133
- package/src/transport/oasn/oasn-register.ts +0 -33
- package/src/transport/oasn/oasn-transport.ts +0 -318
- package/src/transport/oasn/oasn-types.ts +0 -404
- package/src/transport/tim/index.ts +0 -8
- package/src/transport/tim/tim-transport.ts +0 -515
- package/src/transport/types.ts +0 -538
- package/src/types/openclaw-plugin-sdk-media-store.d.ts +0 -9
- package/src/types/openclaw.d.ts +0 -97
- package/src/types/tencentcloud-chat.d.ts +0 -15
- package/src/util/http.ts +0 -113
- package/src/util/logger.ts +0 -131
- package/src/util/url-resolver.ts +0 -77
package/src/tim/channels.ts
DELETED
|
@@ -1,364 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TIM Channels — Channel management operations via SDK
|
|
3
|
-
*
|
|
4
|
-
* All operations use the TIM SDK (not REST API).
|
|
5
|
-
* listChannels uses the auth service endpoint.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { logger } from '../util/logger.js';
|
|
9
|
-
import { httpGet } from '../util/http.js';
|
|
10
|
-
import type { TIMClient } from './client.js';
|
|
11
|
-
|
|
12
|
-
const AUTH_BASE = 'https://auth.ai-talk.live';
|
|
13
|
-
|
|
14
|
-
// ── Types ──
|
|
15
|
-
|
|
16
|
-
export interface ChannelInfo {
|
|
17
|
-
id: string;
|
|
18
|
-
name: string;
|
|
19
|
-
desc: string;
|
|
20
|
-
memberCount?: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface MemberInfo {
|
|
24
|
-
id: string;
|
|
25
|
-
nick: string;
|
|
26
|
-
role: string;
|
|
27
|
-
avatar?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ── Functions ──
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* List all available channels from the auth service.
|
|
34
|
-
*/
|
|
35
|
-
export async function listChannels(client: TIMClient): Promise<ChannelInfo[]> {
|
|
36
|
-
logger.info('[tim/channels] listChannels: fetching from auth service');
|
|
37
|
-
try {
|
|
38
|
-
const headers: Record<string, string> = {};
|
|
39
|
-
if (client.userSig) {
|
|
40
|
-
headers['Authorization'] = `Bearer ${client.userSig}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const data = await httpGet<
|
|
44
|
-
| ChannelInfo[]
|
|
45
|
-
| { data?: ChannelInfo[]; channels?: ChannelInfo[] }
|
|
46
|
-
>(`${AUTH_BASE}/api/channels`, { headers });
|
|
47
|
-
|
|
48
|
-
const rawList: unknown[] = Array.isArray(data) ? data : (
|
|
49
|
-
(data as { data?: unknown[] }).data
|
|
50
|
-
|| (data as { channels?: unknown[] }).channels
|
|
51
|
-
|| []
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
logger.info(`[tim/channels] listChannels: found ${rawList.length} channels`);
|
|
55
|
-
return rawList.map((item) => {
|
|
56
|
-
const ch = item as Record<string, unknown>;
|
|
57
|
-
return {
|
|
58
|
-
id: (ch['channel_id'] || ch['id'] || ch['channelId'] || ch['GroupId'] || '') as string,
|
|
59
|
-
name: (ch['name'] || ch['Name'] || '') as string,
|
|
60
|
-
desc: (ch['description'] || ch['desc'] || ch['Introduction'] || '') as string,
|
|
61
|
-
memberCount: (ch['member_count'] || ch['memberCount'] || 0) as number,
|
|
62
|
-
};
|
|
63
|
-
});
|
|
64
|
-
} catch (err) {
|
|
65
|
-
logger.error(`[tim/channels] listChannels error: ${(err as Error).message}`);
|
|
66
|
-
throw err;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Search channels by keyword (client-side filter of listChannels).
|
|
72
|
-
*/
|
|
73
|
-
export async function searchChannels(
|
|
74
|
-
client: TIMClient,
|
|
75
|
-
keyword: string,
|
|
76
|
-
): Promise<ChannelInfo[]> {
|
|
77
|
-
logger.info(`[tim/channels] searchChannels: keyword="${keyword}"`);
|
|
78
|
-
const all = await listChannels(client);
|
|
79
|
-
const lower = keyword.toLowerCase();
|
|
80
|
-
return all.filter(ch =>
|
|
81
|
-
ch.name.toLowerCase().includes(lower) ||
|
|
82
|
-
ch.desc.toLowerCase().includes(lower) ||
|
|
83
|
-
ch.id.toLowerCase().includes(lower),
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* TIM SDK error code for "already a group member".
|
|
89
|
-
* @see https://cloud.tencent.com/document/product/269/75394
|
|
90
|
-
*/
|
|
91
|
-
const ERR_ALREADY_GROUP_MEMBER = 10013;
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Join a group channel.
|
|
95
|
-
*
|
|
96
|
-
* Per TIM Web SDK docs, joinGroup may:
|
|
97
|
-
* - Resolve with status JOIN_STATUS_SUCCESS / ALREADY_IN_GROUP / WAIT_APPROVAL
|
|
98
|
-
* - Reject with error code 10013 ("already group member")
|
|
99
|
-
*
|
|
100
|
-
* We handle both paths and always refresh the SDK conversation cache
|
|
101
|
-
* afterwards to prevent sendMessage error 2801.
|
|
102
|
-
*
|
|
103
|
-
* @see https://cloud.tencent.com/document/product/269/75395
|
|
104
|
-
*/
|
|
105
|
-
export async function joinChannel(
|
|
106
|
-
client: TIMClient,
|
|
107
|
-
channelId: string,
|
|
108
|
-
): Promise<{ success: boolean; message: string }> {
|
|
109
|
-
const chat = client._chat;
|
|
110
|
-
if (!chat || !client.isReady) throw new Error('Not connected');
|
|
111
|
-
|
|
112
|
-
logger.info(`[tim/channels] joinChannel: ${channelId}`);
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
await chat.joinGroup({ groupID: channelId });
|
|
116
|
-
logger.info(`[tim/channels] joinChannel: joined ${channelId}`);
|
|
117
|
-
} catch (err) {
|
|
118
|
-
const code = (err as { code?: number })?.code;
|
|
119
|
-
if (code === ERR_ALREADY_GROUP_MEMBER) {
|
|
120
|
-
logger.info(`[tim/channels] joinChannel: already a member of ${channelId} (code=${code})`);
|
|
121
|
-
} else {
|
|
122
|
-
logger.error(`[tim/channels] joinChannel failed: code=${code} msg=${(err as Error).message}`);
|
|
123
|
-
throw err;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Force SDK to cache group conversation profile.
|
|
128
|
-
// Required because: (1) ALREADY_IN_GROUP does not refresh cache,
|
|
129
|
-
// and (2) even new joins may not populate the conversation list.
|
|
130
|
-
// Without this, sendMessage fails with error 2801 (group profile cache miss).
|
|
131
|
-
try {
|
|
132
|
-
await chat.getConversationProfile(`GROUP${channelId}`);
|
|
133
|
-
logger.info(`[tim/channels] joinChannel: conversation profile cached for ${channelId}`);
|
|
134
|
-
} catch (cacheErr) {
|
|
135
|
-
const cacheCode = (cacheErr as { code?: number })?.code;
|
|
136
|
-
logger.warn(`[tim/channels] joinChannel: failed to cache conversation profile for ${channelId} (code=${cacheCode})`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return { success: true, message: `Joined ${channelId}` };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Leave a group channel.
|
|
144
|
-
*/
|
|
145
|
-
export async function leaveChannel(
|
|
146
|
-
client: TIMClient,
|
|
147
|
-
channelId: string,
|
|
148
|
-
): Promise<{ success: boolean; message: string }> {
|
|
149
|
-
const chat = client._chat;
|
|
150
|
-
if (!chat || !client.isReady) throw new Error('Not connected');
|
|
151
|
-
|
|
152
|
-
logger.info(`[tim/channels] leaveChannel: ${channelId}`);
|
|
153
|
-
try {
|
|
154
|
-
await chat.quitGroup(channelId);
|
|
155
|
-
return { success: true, message: `Left ${channelId}` };
|
|
156
|
-
} catch (err) {
|
|
157
|
-
const code = (err as { code?: number })?.code;
|
|
158
|
-
logger.error(`[tim/channels] leaveChannel failed: code=${code} msg=${(err as Error).message} ch=${channelId}`);
|
|
159
|
-
throw err;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Get members of a group channel.
|
|
165
|
-
*/
|
|
166
|
-
export async function getMembers(
|
|
167
|
-
client: TIMClient,
|
|
168
|
-
channelId: string,
|
|
169
|
-
): Promise<MemberInfo[]> {
|
|
170
|
-
const chat = client._chat;
|
|
171
|
-
if (!chat || !client.isReady) throw new Error('Not connected');
|
|
172
|
-
|
|
173
|
-
logger.info(`[tim/channels] getMembers: ${channelId}`);
|
|
174
|
-
try {
|
|
175
|
-
const res = await chat.getGroupMemberList({
|
|
176
|
-
groupID: channelId,
|
|
177
|
-
count: 100,
|
|
178
|
-
offset: 0,
|
|
179
|
-
}) as {
|
|
180
|
-
data?: {
|
|
181
|
-
memberList?: Array<{
|
|
182
|
-
userID: string;
|
|
183
|
-
nick?: string;
|
|
184
|
-
role?: string;
|
|
185
|
-
avatar?: string;
|
|
186
|
-
}>;
|
|
187
|
-
};
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const members = (res.data?.memberList || []).map(m => ({
|
|
191
|
-
id: m.userID,
|
|
192
|
-
nick: m.nick || m.userID,
|
|
193
|
-
role: m.role || 'Member',
|
|
194
|
-
avatar: m.avatar || '',
|
|
195
|
-
}));
|
|
196
|
-
logger.info(`[tim/channels] getMembers: ${channelId} found ${members.length} members`);
|
|
197
|
-
return members;
|
|
198
|
-
} catch (err) {
|
|
199
|
-
const code = (err as { code?: number })?.code;
|
|
200
|
-
logger.error(`[tim/channels] getMembers failed: code=${code} msg=${(err as Error).message} ch=${channelId}`);
|
|
201
|
-
throw err;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Create a new Community group channel.
|
|
207
|
-
*
|
|
208
|
-
* Uses GRP_COMMUNITY (社群) — supports large-scale membership, topics,
|
|
209
|
-
* and free join/leave. Requires Tencent IM flagship plan with Community
|
|
210
|
-
* feature enabled in console.
|
|
211
|
-
*
|
|
212
|
-
* groupID is always auto-generated by the SDK with @TGS#_ prefix.
|
|
213
|
-
* Community groups reject custom groupIDs that don't start with @TGS#_
|
|
214
|
-
* (SDK error ILLEGAL_GRP_ID / 2602).
|
|
215
|
-
*
|
|
216
|
-
* @see https://cloud.tencent.com/document/product/269/75394
|
|
217
|
-
* @see https://cloud.tencent.com/document/product/269/1502 (群组类型)
|
|
218
|
-
*/
|
|
219
|
-
export async function createChannel(
|
|
220
|
-
client: TIMClient,
|
|
221
|
-
name: string,
|
|
222
|
-
description = '',
|
|
223
|
-
skill = '',
|
|
224
|
-
): Promise<{ success: boolean; channelId: string }> {
|
|
225
|
-
const chat = client._chat;
|
|
226
|
-
if (!chat || !client.isReady) throw new Error('Not connected');
|
|
227
|
-
|
|
228
|
-
logger.info(`[tim/channels] createChannel: type=Community name="${name}"`);
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
// Community groups require groupID to start with @TGS#_ or be empty.
|
|
232
|
-
// SDK auto-generates @TGS#_ prefixed ID when groupID is omitted.
|
|
233
|
-
const result = await chat.createGroup({
|
|
234
|
-
type: client._types.GRP_COMMUNITY,
|
|
235
|
-
name,
|
|
236
|
-
// groupID intentionally omitted — SDK auto-generates @TGS#_ prefixed ID
|
|
237
|
-
introduction: description,
|
|
238
|
-
notification: skill,
|
|
239
|
-
isSupportTopic: false,
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const actualId = result?.data?.group?.groupID;
|
|
243
|
-
if (!actualId) throw new Error('SDK returned no groupID');
|
|
244
|
-
logger.info(`[tim/channels] Channel created: ${actualId} (type=Community)`);
|
|
245
|
-
return { success: true, channelId: actualId };
|
|
246
|
-
} catch (err) {
|
|
247
|
-
const code = (err as { code?: number })?.code;
|
|
248
|
-
const msg = (err as { message?: string })?.message || String(err);
|
|
249
|
-
logger.error(`[tim/channels] createChannel failed: code=${code} msg=${msg}`);
|
|
250
|
-
throw err;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Get channel skill (stored in group notification field).
|
|
256
|
-
*/
|
|
257
|
-
export async function getChannelSkill(
|
|
258
|
-
client: TIMClient,
|
|
259
|
-
channelId: string,
|
|
260
|
-
): Promise<string | null> {
|
|
261
|
-
const chat = client._chat;
|
|
262
|
-
if (!chat || !client.isReady) return null;
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
const res = await chat.getGroupProfile({
|
|
266
|
-
groupID: channelId,
|
|
267
|
-
groupCustomFieldFilter: [],
|
|
268
|
-
}) as { data?: { group?: { notification?: string } } };
|
|
269
|
-
|
|
270
|
-
return res.data?.group?.notification || null;
|
|
271
|
-
} catch (err) {
|
|
272
|
-
const code = (err as { code?: number })?.code;
|
|
273
|
-
logger.warn(`[tim/channels] getChannelSkill failed: code=${code} msg=${(err as Error).message} ch=${channelId}`);
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Set channel skill (stored in group notification field).
|
|
280
|
-
*/
|
|
281
|
-
export async function setChannelSkill(
|
|
282
|
-
client: TIMClient,
|
|
283
|
-
channelId: string,
|
|
284
|
-
skill: string,
|
|
285
|
-
): Promise<{ success: boolean }> {
|
|
286
|
-
const chat = client._chat;
|
|
287
|
-
if (!chat || !client.isReady) throw new Error('Not connected');
|
|
288
|
-
|
|
289
|
-
logger.info(`[tim/channels] setChannelSkill: ${channelId}`);
|
|
290
|
-
try {
|
|
291
|
-
await chat.updateGroupProfile({
|
|
292
|
-
groupID: channelId,
|
|
293
|
-
notification: skill,
|
|
294
|
-
});
|
|
295
|
-
return { success: true };
|
|
296
|
-
} catch (err) {
|
|
297
|
-
const code = (err as { code?: number })?.code;
|
|
298
|
-
logger.error(`[tim/channels] setChannelSkill failed: code=${code} msg=${(err as Error).message} ch=${channelId}`);
|
|
299
|
-
throw err;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Get channel display name from group profile.
|
|
305
|
-
*/
|
|
306
|
-
export async function getGroupName(
|
|
307
|
-
client: TIMClient,
|
|
308
|
-
channelId: string,
|
|
309
|
-
): Promise<string | null> {
|
|
310
|
-
const chat = client._chat;
|
|
311
|
-
if (!chat || !client.isReady) return null;
|
|
312
|
-
|
|
313
|
-
try {
|
|
314
|
-
const res = await chat.getGroupProfile({
|
|
315
|
-
groupID: channelId,
|
|
316
|
-
groupCustomFieldFilter: [],
|
|
317
|
-
}) as { data?: { group?: { name?: string } } };
|
|
318
|
-
|
|
319
|
-
return res.data?.group?.name || null;
|
|
320
|
-
} catch (err) {
|
|
321
|
-
const code = (err as { code?: number })?.code;
|
|
322
|
-
logger.warn(`[tim/channels] getGroupName failed: code=${code} msg=${(err as Error).message} ch=${channelId}`);
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Read a single group-level custom field (AppDefinedData).
|
|
329
|
-
*
|
|
330
|
-
* Uses getGroupProfile with groupCustomFieldFilter to fetch only the
|
|
331
|
-
* requested field. Returns null if field not set or fetch fails.
|
|
332
|
-
*
|
|
333
|
-
* @param fieldKey - The custom field key (e.g. 'gm_req_mention')
|
|
334
|
-
*/
|
|
335
|
-
export async function getGroupCustomField(
|
|
336
|
-
client: TIMClient,
|
|
337
|
-
channelId: string,
|
|
338
|
-
fieldKey: string,
|
|
339
|
-
): Promise<string | null> {
|
|
340
|
-
const chat = client._chat;
|
|
341
|
-
if (!chat || !client.isReady) return null;
|
|
342
|
-
|
|
343
|
-
try {
|
|
344
|
-
const res = await chat.getGroupProfile({
|
|
345
|
-
groupID: channelId,
|
|
346
|
-
groupCustomFieldFilter: [fieldKey],
|
|
347
|
-
}) as {
|
|
348
|
-
data?: {
|
|
349
|
-
group?: {
|
|
350
|
-
groupCustomField?: Array<{ key: string; value: string }>;
|
|
351
|
-
};
|
|
352
|
-
};
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
const fields = res.data?.group?.groupCustomField ?? [];
|
|
356
|
-
const target = fields.find((f: { key: string; value: string }) => f.key === fieldKey);
|
|
357
|
-
return target?.value ?? null;
|
|
358
|
-
} catch (err) {
|
|
359
|
-
const code = (err as { code?: number })?.code;
|
|
360
|
-
logger.warn(`[tim/channels] getGroupCustomField(${fieldKey}) failed: code=${code} ch=${channelId}`);
|
|
361
|
-
return null;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
package/src/tim/client.ts
DELETED
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TIM Client — SDK connection core
|
|
3
|
-
*
|
|
4
|
-
* Manages TIM SDK lifecycle: connect, disconnect, auto-reconnect.
|
|
5
|
-
* Emits 'message' events for incoming group chat messages.
|
|
6
|
-
* Emits 'reconnected' when auto-reconnect succeeds.
|
|
7
|
-
*
|
|
8
|
-
* Internal noise containment: all SDK calls wrapped in try/catch,
|
|
9
|
-
* known TIM noise errors logged at debug level.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import './sdk-logger-init.js'; // Must be before @tencentcloud/chat — redirects SDK logs to file
|
|
13
|
-
// @ts-ignore — JS side-effect module, no types needed
|
|
14
|
-
import '../sdk/node-env/index.js'; // Must be before @tencentcloud/chat — installs ws (no permessage-deflate), localStorage, rejection handler
|
|
15
|
-
import TencentCloudChat from '../sdk/index.js';
|
|
16
|
-
// @ts-ignore — vendor JS module, typed as unknown
|
|
17
|
-
import GroupModule from '../sdk/modules/group-module.js';
|
|
18
|
-
// @ts-ignore — vendor JS module, patched for Node.js
|
|
19
|
-
import TIMUploadPlugin from '../sdk/modules/tim-upload-plugin.js';
|
|
20
|
-
import { EventEmitter } from 'node:events';
|
|
21
|
-
|
|
22
|
-
import { logger } from '../util/logger.js';
|
|
23
|
-
import type { TIMCredentials } from '../auth/verify.js';
|
|
24
|
-
|
|
25
|
-
// ── Types ──
|
|
26
|
-
|
|
27
|
-
export interface RawPushMessage {
|
|
28
|
-
id: string;
|
|
29
|
-
channelId: string;
|
|
30
|
-
seq: number;
|
|
31
|
-
from: string;
|
|
32
|
-
nick: string;
|
|
33
|
-
text: string;
|
|
34
|
-
time: number;
|
|
35
|
-
mentionsMe: boolean;
|
|
36
|
-
source: 'push';
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ── TIMClient ──
|
|
40
|
-
|
|
41
|
-
export class TIMClient extends EventEmitter {
|
|
42
|
-
private chat: ReturnType<typeof TencentCloudChat.create> | null = null;
|
|
43
|
-
private _agentId: string = '';
|
|
44
|
-
private _credentials: TIMCredentials | null = null;
|
|
45
|
-
private _reconnecting = false;
|
|
46
|
-
private _ready = false;
|
|
47
|
-
|
|
48
|
-
get agentId(): string { return this._agentId; }
|
|
49
|
-
get isReady(): boolean { return this._ready; }
|
|
50
|
-
get userSig(): string | null { return this._credentials?.userSig ?? null; }
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Connect to TIM SDK using verified credentials.
|
|
54
|
-
*/
|
|
55
|
-
async connect(credentials: TIMCredentials): Promise<void> {
|
|
56
|
-
this._agentId = credentials.userId;
|
|
57
|
-
this._credentials = credentials;
|
|
58
|
-
|
|
59
|
-
logger.info(`[tim/client] Connecting agentId=${this._agentId}`);
|
|
60
|
-
|
|
61
|
-
// Create SDK instance
|
|
62
|
-
this.chat = TencentCloudChat.create({
|
|
63
|
-
SDKAppID: credentials.sdkAppId,
|
|
64
|
-
modules: { 'group-module': GroupModule },
|
|
65
|
-
});
|
|
66
|
-
this.chat.setLogLevel(0); // ALL logs — WebSocket heartbeat, transport, message push (P0-A diagnosis)
|
|
67
|
-
|
|
68
|
-
// Register tim-upload-plugin — enables createFileMessage/sendMessage for T4 file transfer
|
|
69
|
-
// Must be after create(), before login(). SDK_READY triggers UploadModule._init() which looks up this plugin.
|
|
70
|
-
// @see docs/audit/023-tim-upload-plugin-v1.md §4.7
|
|
71
|
-
this.chat.registerPlugin({ 'tim-upload-plugin': TIMUploadPlugin });
|
|
72
|
-
logger.info('[tim/client] tim-upload-plugin registered');
|
|
73
|
-
|
|
74
|
-
// Wait for SDK_READY with guaranteed timer cleanup
|
|
75
|
-
let readyTimeout: ReturnType<typeof setTimeout>;
|
|
76
|
-
const sdkReadyPromise = new Promise<void>((resolve, reject) => {
|
|
77
|
-
readyTimeout = setTimeout(
|
|
78
|
-
() => reject(new Error('SDK_READY timeout (15s)')),
|
|
79
|
-
15_000,
|
|
80
|
-
);
|
|
81
|
-
this.chat!.on(TencentCloudChat.EVENT.SDK_READY, () => {
|
|
82
|
-
clearTimeout(readyTimeout);
|
|
83
|
-
this._ready = true;
|
|
84
|
-
resolve();
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// Register event listeners
|
|
89
|
-
this._registerListeners();
|
|
90
|
-
|
|
91
|
-
logger.info('[tim/client] Calling TIM SDK login...');
|
|
92
|
-
try {
|
|
93
|
-
await this.chat.login({
|
|
94
|
-
userID: credentials.userId,
|
|
95
|
-
userSig: credentials.userSig,
|
|
96
|
-
});
|
|
97
|
-
await sdkReadyPromise;
|
|
98
|
-
} catch (err) {
|
|
99
|
-
clearTimeout(readyTimeout!);
|
|
100
|
-
this._ready = false;
|
|
101
|
-
throw err;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
logger.info('[tim/client] SDK_READY — connected');
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Gracefully disconnect from TIM SDK.
|
|
109
|
-
*/
|
|
110
|
-
async disconnect(): Promise<void> {
|
|
111
|
-
this._reconnecting = false;
|
|
112
|
-
if (this.chat) {
|
|
113
|
-
try { await this.chat.logout(); } catch { /* ignore */ }
|
|
114
|
-
this._ready = false;
|
|
115
|
-
this._removeListeners();
|
|
116
|
-
this.chat = null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// ── Event Listeners ──
|
|
121
|
-
|
|
122
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
|
-
private _messageHandler: ((...args: any[]) => void) | null = null;
|
|
124
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
125
|
-
private _notReadyHandler: ((...args: any[]) => void) | null = null;
|
|
126
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
127
|
-
private _kickedHandler: ((...args: any[]) => void) | null = null;
|
|
128
|
-
|
|
129
|
-
private _registerListeners(): void {
|
|
130
|
-
if (!this.chat) {
|
|
131
|
-
logger.error('[tim/client] _registerListeners: chat instance is null, cannot register');
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
logger.info('[tim/client] _registerListeners: installing SDK event handlers');
|
|
135
|
-
|
|
136
|
-
// Disconnect detection
|
|
137
|
-
this._notReadyHandler = () => {
|
|
138
|
-
logger.warn('[tim/client] SDK_NOT_READY — connection lost');
|
|
139
|
-
this._ready = false;
|
|
140
|
-
void this._autoReconnect();
|
|
141
|
-
};
|
|
142
|
-
this.chat.on(TencentCloudChat.EVENT.SDK_NOT_READY, this._notReadyHandler);
|
|
143
|
-
|
|
144
|
-
this._kickedHandler = (event: unknown) => {
|
|
145
|
-
const ev = event as { data?: { type?: string } } | undefined;
|
|
146
|
-
const reason = ev?.data?.type || 'unknown';
|
|
147
|
-
logger.warn(`[tim/client] KICKED_OUT (reason=${reason})`);
|
|
148
|
-
this._ready = false;
|
|
149
|
-
|
|
150
|
-
switch (reason) {
|
|
151
|
-
case 'multipleDevice':
|
|
152
|
-
case 'multipleAccount':
|
|
153
|
-
// Multi-device/page kick: do NOT reconnect to avoid kick loop
|
|
154
|
-
logger.warn('[tim/client] Kicked by multi-device/account login, will not reconnect');
|
|
155
|
-
break;
|
|
156
|
-
|
|
157
|
-
case 'userSigExpired':
|
|
158
|
-
// UserSig expired: old credentials are useless, notify upper layer
|
|
159
|
-
logger.warn('[tim/client] UserSig expired, emitting credential_expired event');
|
|
160
|
-
this.emit('credential_expired');
|
|
161
|
-
break;
|
|
162
|
-
|
|
163
|
-
case 'REST_API_Kick':
|
|
164
|
-
default:
|
|
165
|
-
// REST API kick or unknown: reconnect with existing credentials
|
|
166
|
-
void this._autoReconnect();
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
this.chat.on(TencentCloudChat.EVENT.KICKED_OUT, this._kickedHandler);
|
|
171
|
-
|
|
172
|
-
// Push message listener
|
|
173
|
-
this._messageHandler = (event: unknown) => {
|
|
174
|
-
const ev = event as { data: unknown[] };
|
|
175
|
-
const messages = ev.data as Array<{
|
|
176
|
-
conversationType: string;
|
|
177
|
-
type?: string;
|
|
178
|
-
from: string;
|
|
179
|
-
to: string;
|
|
180
|
-
sequence: number;
|
|
181
|
-
nick?: string;
|
|
182
|
-
time: number;
|
|
183
|
-
payload?: { text?: string; data?: string; description?: string; extension?: string };
|
|
184
|
-
atUserList?: string[];
|
|
185
|
-
}>;
|
|
186
|
-
|
|
187
|
-
logger.info(`[tim/client] MESSAGE_RECEIVED fired: ${messages?.length ?? 0} message(s)`);
|
|
188
|
-
|
|
189
|
-
if (!messages || messages.length === 0) {
|
|
190
|
-
logger.warn('[tim/client] MESSAGE_RECEIVED: empty data array, nothing to process');
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
for (const msg of messages) {
|
|
195
|
-
try {
|
|
196
|
-
// ── C2C messages → emit for c2c.ts handler, skip group logic ──
|
|
197
|
-
if (msg.conversationType === TencentCloudChat.TYPES.CONV_C2C) {
|
|
198
|
-
logger.info(`[tim/client] C2C msg from=${msg.from}`);
|
|
199
|
-
this.emit('c2c_message', {
|
|
200
|
-
from: msg.from,
|
|
201
|
-
payload: msg.payload,
|
|
202
|
-
type: msg.type,
|
|
203
|
-
time: msg.time,
|
|
204
|
-
});
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Only group messages
|
|
209
|
-
if (msg.conversationType !== TencentCloudChat.TYPES.CONV_GROUP) {
|
|
210
|
-
logger.debug(`[tim/client] skip non-group msg type=${msg.conversationType} from=${msg.from}`);
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
// Skip own messages
|
|
214
|
-
if (msg.from === this._agentId) {
|
|
215
|
-
logger.debug(`[tim/client] skip self msg from=${msg.from} to=${msg.to}`);
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const text = msg.payload?.text || '';
|
|
220
|
-
if (!text) {
|
|
221
|
-
logger.debug(`[tim/client] skip empty text msg from=${msg.from} to=${msg.to}`);
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const channelId = msg.to;
|
|
226
|
-
const seq = msg.sequence;
|
|
227
|
-
|
|
228
|
-
const rawMessage: RawPushMessage = {
|
|
229
|
-
id: `${channelId}:${seq}`,
|
|
230
|
-
channelId,
|
|
231
|
-
seq,
|
|
232
|
-
from: msg.from,
|
|
233
|
-
nick: msg.nick || msg.from,
|
|
234
|
-
text,
|
|
235
|
-
time: msg.time * 1000,
|
|
236
|
-
mentionsMe: Array.isArray(msg.atUserList)
|
|
237
|
-
&& (msg.atUserList.includes(this._agentId)
|
|
238
|
-
|| msg.atUserList.includes(TencentCloudChat.TYPES.MSG_AT_ALL)),
|
|
239
|
-
source: 'push',
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
logger.debug(`[tim/client] PUSH msg id=${rawMessage.id} ch=${channelId} from=${rawMessage.nick}`);
|
|
243
|
-
this.emit('message', rawMessage);
|
|
244
|
-
} catch (err) {
|
|
245
|
-
logger.debug(`[tim/client] Error processing push message: ${(err as Error).message}`);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
};
|
|
249
|
-
this.chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, this._messageHandler);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
private _removeListeners(): void {
|
|
253
|
-
if (!this.chat) return;
|
|
254
|
-
if (this._notReadyHandler) {
|
|
255
|
-
this.chat.off(TencentCloudChat.EVENT.SDK_NOT_READY, this._notReadyHandler);
|
|
256
|
-
this._notReadyHandler = null;
|
|
257
|
-
}
|
|
258
|
-
if (this._kickedHandler) {
|
|
259
|
-
this.chat.off(TencentCloudChat.EVENT.KICKED_OUT, this._kickedHandler);
|
|
260
|
-
this._kickedHandler = null;
|
|
261
|
-
}
|
|
262
|
-
if (this._messageHandler) {
|
|
263
|
-
this.chat.off(TencentCloudChat.EVENT.MESSAGE_RECEIVED, this._messageHandler);
|
|
264
|
-
this._messageHandler = null;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// ── Auto-reconnect ──
|
|
269
|
-
|
|
270
|
-
private async _autoReconnect(maxRetries = 5): Promise<void> {
|
|
271
|
-
if (this._reconnecting) {
|
|
272
|
-
logger.debug('[tim/client] Reconnect already in progress');
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
if (!this._credentials) {
|
|
276
|
-
logger.error('[tim/client] Cannot reconnect: missing credentials');
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
this._reconnecting = true;
|
|
281
|
-
logger.warn('[tim/client] Starting auto-reconnect...');
|
|
282
|
-
|
|
283
|
-
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
284
|
-
if (!this._reconnecting) {
|
|
285
|
-
logger.info('[tim/client] Reconnect cancelled');
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const delay = Math.min(5000 * attempt, 25_000);
|
|
290
|
-
logger.info(`[tim/client] Reconnect attempt ${attempt}/${maxRetries} in ${delay / 1000}s...`);
|
|
291
|
-
await new Promise(r => setTimeout(r, delay));
|
|
292
|
-
|
|
293
|
-
try {
|
|
294
|
-
// Teardown old SDK instance
|
|
295
|
-
if (this.chat) {
|
|
296
|
-
this._removeListeners();
|
|
297
|
-
try { await this.chat.logout(); } catch { /* ignore */ }
|
|
298
|
-
this.chat = null;
|
|
299
|
-
}
|
|
300
|
-
this._ready = false;
|
|
301
|
-
|
|
302
|
-
await this.connect(this._credentials);
|
|
303
|
-
|
|
304
|
-
if (this._ready) {
|
|
305
|
-
logger.info(`[tim/client] Auto-reconnect successful on attempt ${attempt}`);
|
|
306
|
-
this._reconnecting = false;
|
|
307
|
-
this.emit('reconnected');
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
} catch (err) {
|
|
311
|
-
logger.error(`[tim/client] Reconnect attempt ${attempt}/${maxRetries} failed: ${(err as Error).message}`);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
this._reconnecting = false;
|
|
316
|
-
logger.error(`[tim/client] All ${maxRetries} reconnect attempts failed`);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// ── Internal SDK access (for messages.ts and channels.ts) ──
|
|
320
|
-
|
|
321
|
-
/** @internal */
|
|
322
|
-
get _chat(): ReturnType<typeof TencentCloudChat.create> | null {
|
|
323
|
-
return this.chat;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/** @internal */
|
|
327
|
-
get _types(): typeof TencentCloudChat.TYPES {
|
|
328
|
-
return TencentCloudChat.TYPES;
|
|
329
|
-
}
|
|
330
|
-
}
|
package/src/tim/index.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TIM — Module barrel export
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export { TIMClient, type RawPushMessage } from './client.js';
|
|
6
|
-
export { sendMessage, fetchMessages, mapToHistoryMessage, type HistoryMessage, type SendResult } from './messages.js';
|
|
7
|
-
export {
|
|
8
|
-
listChannels,
|
|
9
|
-
searchChannels,
|
|
10
|
-
joinChannel,
|
|
11
|
-
leaveChannel,
|
|
12
|
-
getMembers,
|
|
13
|
-
createChannel,
|
|
14
|
-
getChannelSkill,
|
|
15
|
-
setChannelSkill,
|
|
16
|
-
type ChannelInfo,
|
|
17
|
-
type MemberInfo,
|
|
18
|
-
} from './channels.js';
|