dlinkclaw-openclaw 2026.8.24-alpha.1

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 (77) hide show
  1. package/README.en.md +46 -0
  2. package/README.md +25 -0
  3. package/dist/index.d.ts +9 -0
  4. package/dist/index.js +1 -0
  5. package/dist/package.json +95 -0
  6. package/dist/src/account.d.ts +53 -0
  7. package/dist/src/account.js +394 -0
  8. package/dist/src/channel.d.ts +17 -0
  9. package/dist/src/channel.js +153 -0
  10. package/dist/src/client/client-manager.d.ts +4 -0
  11. package/dist/src/client/client-manager.js +10 -0
  12. package/dist/src/client/const.d.ts +37 -0
  13. package/dist/src/client/const.js +155 -0
  14. package/dist/src/client/crypto.d.ts +47 -0
  15. package/dist/src/client/crypto.js +260 -0
  16. package/dist/src/client/dl-client.d.ts +99 -0
  17. package/dist/src/client/dl-client.js +535 -0
  18. package/dist/src/client/dl-http.d.ts +27 -0
  19. package/dist/src/client/dl-http.js +223 -0
  20. package/dist/src/client/media-handler.d.ts +21 -0
  21. package/dist/src/client/media-handler.js +196 -0
  22. package/dist/src/client/message.d.ts +96 -0
  23. package/dist/src/client/message.js +1204 -0
  24. package/dist/src/client/timeout.d.ts +20 -0
  25. package/dist/src/client/timeout.js +36 -0
  26. package/dist/src/client/types.d.ts +224 -0
  27. package/dist/src/client/types.js +1 -0
  28. package/dist/src/monitor.d.ts +17 -0
  29. package/dist/src/monitor.js +320 -0
  30. package/dist/src/outbound.d.ts +4 -0
  31. package/dist/src/outbound.js +83 -0
  32. package/dist/src/policy.d.ts +42 -0
  33. package/dist/src/policy.js +205 -0
  34. package/dist/src/runtime.d.ts +9 -0
  35. package/dist/src/runtime.js +65 -0
  36. package/dist/src/services/index.d.ts +41 -0
  37. package/dist/src/services/index.js +225 -0
  38. package/dist/src/services/services.d.ts +152 -0
  39. package/dist/src/services/services.js +1066 -0
  40. package/dist/src/services/types.d.ts +391 -0
  41. package/dist/src/services/types.js +5 -0
  42. package/dist/src/services/ws-client.d.ts +174 -0
  43. package/dist/src/services/ws-client.js +866 -0
  44. package/dist/src/utils.d.ts +20 -0
  45. package/dist/src/utils.js +128 -0
  46. package/ext_capabilities/workrelay-speak/CLONE_GUIDE.md +129 -0
  47. package/ext_capabilities/workrelay-speak/HARDWARE.md +84 -0
  48. package/ext_capabilities/workrelay-speak/QWEN_GUIDE.md +151 -0
  49. package/ext_capabilities/workrelay-speak/README.md +79 -0
  50. package/ext_capabilities/workrelay-speak/__pycache__/patch-kokoro-en.cpython-312.pyc +0 -0
  51. package/ext_capabilities/workrelay-speak/install.sh +280 -0
  52. package/ext_capabilities/workrelay-speak/install.sh.bak-20260814 +376 -0
  53. package/ext_capabilities/workrelay-speak/manifest.json +49 -0
  54. package/ext_capabilities/workrelay-speak/patch-kokoro-en.py +85 -0
  55. package/ext_capabilities/workrelay-speak/status.sh +90 -0
  56. package/ext_capabilities/workrelay-speak/workrelay-tts.sh +118 -0
  57. package/ext_capabilities/workrelay-speak-luxtts/README.md +44 -0
  58. package/ext_capabilities/workrelay-speak-luxtts/install.sh +282 -0
  59. package/ext_capabilities/workrelay-speak-luxtts/manifest.json +49 -0
  60. package/ext_capabilities/workrelay-speak-luxtts/samples/45ea2e2c-0863-4966-a739-8bff8817f4c0.wav +0 -0
  61. package/ext_capabilities/workrelay-speak-luxtts/samples/800e5af4-cba5-441e-8003-6d36f35409e6.wav +0 -0
  62. package/ext_capabilities/workrelay-speak-luxtts/samples/85fafe01-d4d6-4a48-890e-9ae113f21905.wav +0 -0
  63. package/ext_capabilities/workrelay-speak-luxtts/samples/reference_text.txt +4 -0
  64. package/ext_capabilities/workrelay-speak-luxtts/samples/stone-sample-6.4s.wav +0 -0
  65. package/ext_capabilities/workrelay-speak-luxtts/status.sh +94 -0
  66. package/ext_capabilities/workrelay-speak-luxtts/workrelay-tts.sh +98 -0
  67. package/ext_capabilities/workrelay-transcribe/README.md +76 -0
  68. package/ext_capabilities/workrelay-transcribe/__pycache__/transcribe.cpython-312.pyc +0 -0
  69. package/ext_capabilities/workrelay-transcribe/__pycache__/transcribe_server.cpython-312.pyc +0 -0
  70. package/ext_capabilities/workrelay-transcribe/install.sh +207 -0
  71. package/ext_capabilities/workrelay-transcribe/manifest.json +42 -0
  72. package/ext_capabilities/workrelay-transcribe/status.sh +62 -0
  73. package/ext_capabilities/workrelay-transcribe/transcribe.py +56 -0
  74. package/ext_capabilities/workrelay-transcribe/transcribe_server.py +116 -0
  75. package/ext_capabilities/workrelay-transcribe/voicebox-transcribe.sh +88 -0
  76. package/openclaw.plugin.json +142 -0
  77. package/package.json +95 -0
@@ -0,0 +1,99 @@
1
+ import EventEmitter from 'events';
2
+ import { OpenClawConfig, PluginLogger } from 'openclaw/plugin-sdk';
3
+ import { AccumulatorsStatus, AutoReplyOpts } from './message.js';
4
+ import { BotType, DLArchiveSession, DLKind, DLMessagePayload, DLResolvedAccount, MessageStatus } from './types.js';
5
+ import { CmdMessage, CmdResponse, EventType } from '../services/types.js';
6
+ export type DLContext = {
7
+ account: DLResolvedAccount;
8
+ channelId: string;
9
+ cfg: OpenClawConfig;
10
+ rbotId?: string;
11
+ };
12
+ export interface DLMessageContext {
13
+ ctx: DLContext;
14
+ msg: DLMessagePayload;
15
+ accStatus: AccumulatorsStatus;
16
+ }
17
+ export type DLReceiveMessageHandler = (topic: string, msg: DLMessagePayload) => void;
18
+ export interface MqttConfig {
19
+ url: string;
20
+ port: number;
21
+ websocketPort: number;
22
+ serverId: string;
23
+ qos: 0 | 1 | 2;
24
+ userName: string;
25
+ password: string;
26
+ willTopic: string;
27
+ serverTopics: Array<string>;
28
+ clientTopics: Array<string>;
29
+ openUserTopics: Array<string>;
30
+ openUserNotifyTopics: Array<string>;
31
+ }
32
+ export declare class DLClient extends EventEmitter {
33
+ private ctx;
34
+ private mqttConfig;
35
+ private mqttClient;
36
+ private cmdHandler;
37
+ private readonly http;
38
+ private readonly logger;
39
+ private readonly accountLog;
40
+ private lastInboundAt;
41
+ constructor(ctx: DLContext, logger: PluginLogger);
42
+ private fetchConfig;
43
+ private changeConnectStatus;
44
+ private connect;
45
+ private receiveEventHandle;
46
+ private closeHandler;
47
+ private publish;
48
+ getLastInboundAt(): number | null;
49
+ isConnected(): boolean;
50
+ start(): Promise<void>;
51
+ stop(): Promise<void>;
52
+ exeCmd(cmdMessage: CmdMessage): Promise<CmdResponse | undefined>;
53
+ publishEventToDLClient(payload: any, event: EventType, sqos?: '0' | '1' | '2', to?: string): Promise<void>;
54
+ publishCmdResultToDLClient(topic: string, playload: CmdResponse, sqos?: '0' | '1' | '2'): Promise<void>;
55
+ publishEventToServer(playload: any, event: EventType, sqos?: '0' | '1' | '2'): Promise<void>;
56
+ publishCmdResultToServer(playload: CmdResponse, sqos?: '0' | '1' | '2'): Promise<void>;
57
+ publishToServer(params: {
58
+ streamId: string;
59
+ content: string;
60
+ finish: boolean;
61
+ kind: DLKind;
62
+ mediaFiles?: Array<string>;
63
+ session?: DLArchiveSession;
64
+ }, autoParams?: AutoReplyOpts): Promise<{
65
+ autoRelay: boolean;
66
+ reqId: string | undefined;
67
+ } | {
68
+ autoRelay: boolean;
69
+ reqId?: undefined;
70
+ }>;
71
+ /**
72
+ * 减轻服务端负担,将处理状态直接推送到客户端(处理结果走服务端)
73
+ * @param params
74
+ */
75
+ publishProcessingState(params: {
76
+ streamId: string;
77
+ content: string;
78
+ status: MessageStatus;
79
+ runId?: string;
80
+ kind: DLKind;
81
+ }): Promise<void>;
82
+ downloadFileRaw(url: string, botType: BotType): Promise<{
83
+ buffer: Buffer;
84
+ filename?: string;
85
+ }>;
86
+ /**
87
+ * 对码成功回调通知
88
+ */
89
+ approved(ok: boolean): Promise<void>;
90
+ autoReply(params: {
91
+ to: string;
92
+ text?: string;
93
+ mediaFiles?: Array<string>;
94
+ }): Promise<{
95
+ channel: string;
96
+ messageId: string;
97
+ chatId: string;
98
+ }>;
99
+ }
@@ -0,0 +1,535 @@
1
+ import EventEmitter from 'events';
2
+ import { buildDLReplyMessage, messagePreviewHandler } from './message.js';
3
+ import mqtt from 'mqtt';
4
+ import { DLHttp } from './dl-http.js';
5
+ import { decryptAesEcbPkcs5 } from './crypto.js';
6
+ import { AUTO_RELAY_API, AUTO_RELAY_UPLOAD } from './const.js';
7
+ import { createCmdHandler } from '../services/index.js';
8
+ import { uuidStr } from '../utils.js';
9
+ export class DLClient extends EventEmitter {
10
+ ctx;
11
+ mqttConfig = null;
12
+ mqttClient = null;
13
+ cmdHandler = null;
14
+ http;
15
+ logger;
16
+ accountLog;
17
+ lastInboundAt = null;
18
+ //private runtime: PluginRuntime | null = null;
19
+ constructor(ctx, logger) {
20
+ super();
21
+ this.ctx = ctx;
22
+ this.logger = logger;
23
+ this.accountLog = `account[${ctx.account.accountId}] [DL-Client.ts]`;
24
+ const { baseUrl, apiKey, secret } = ctx.account.config;
25
+ this.http = new DLHttp({ baseUrl, apiKey, secret }, logger);
26
+ const cfg = ctx.cfg;
27
+ if (cfg.gateway?.auth?.token) {
28
+ const token = cfg.gateway.auth.token.toString();
29
+ const gatewayUrl = 'ws://127.0.0.1:18789';
30
+ this.cmdHandler = createCmdHandler({
31
+ cfg,
32
+ channelId: ctx.channelId,
33
+ gatewayUrl,
34
+ token,
35
+ baseUrl: ctx.account.config.baseUrl
36
+ }, this, this.logger);
37
+ }
38
+ }
39
+ async fetchConfig() {
40
+ const url = `/guest/mqtt/getConfig/${this.ctx.account.config.apiKey}`;
41
+ const res = await this.http.get(url);
42
+ if (res) {
43
+ const secret = this.ctx.account.config.secret;
44
+ const mqttConfig = decryptAesEcbPkcs5(res, secret);
45
+ if (mqttConfig) {
46
+ this.logger.info(`mqttconfig:${JSON.stringify(mqttConfig)}`);
47
+ const topics = mqttConfig.serverTopics;
48
+ const messageTopic = topics.find(item => item.endsWith('/message'));
49
+ const cmdTopic = topics.find(item => item.endsWith('/cmd'));
50
+ this.mqttConfig = { ...mqttConfig, serverTopics: [messageTopic, cmdTopic] };
51
+ return;
52
+ }
53
+ }
54
+ throw new Error(`Failed to get config!`);
55
+ }
56
+ async changeConnectStatus(status) {
57
+ const url = '/guest/relayBot/changeConnectStatus/' + this.ctx.account.config.apiKey;
58
+ const p = {
59
+ connect: status === 'start' ? 'true' : 'false'
60
+ };
61
+ return await this.http.post(url, p);
62
+ }
63
+ async connect() {
64
+ if (!this.mqttConfig) {
65
+ await this.fetchConfig();
66
+ }
67
+ const config = this.mqttConfig;
68
+ const connectionUrl = `mqtt://${config.url}:${config.port}`;
69
+ const rbotId = config.clientTopics[0].split('/')[1];
70
+ const msgTopic = config.clientTopics.filter((item) => item.endsWith('/message'))[0];
71
+ const cmdTopic = config.clientTopics.filter((item) => item.endsWith('/cmd'))[0];
72
+ const account = this.ctx.account;
73
+ return new Promise((resolve, reject) => {
74
+ this.mqttClient = mqtt.connect(connectionUrl, {
75
+ clientId: `openclaw_${account.accountId}_${rbotId}_${Date.now()}`,
76
+ username: config.userName,
77
+ password: config.password,
78
+ qos: config.qos,
79
+ clean: true,
80
+ reconnectPeriod: 5000,
81
+ connectTimeout: 30000
82
+ });
83
+ this.mqttClient.on("connect", () => {
84
+ this.logger.info(`${this.accountLog} Connected to broker`);
85
+ this.mqttClient?.subscribe([msgTopic, cmdTopic], { qos: config.qos }, (err) => {
86
+ if (err) {
87
+ this.logger.error(`${this.accountLog} Subscribe error:${err}`);
88
+ }
89
+ else {
90
+ this.logger.info(`${this.accountLog} Subscribed to topic:[${msgTopic}, ${cmdTopic}]`);
91
+ this.ctx.rbotId = rbotId;
92
+ }
93
+ resolve();
94
+ });
95
+ });
96
+ this.mqttClient.on("message", (topic, message) => {
97
+ try {
98
+ this.logger.debug ?? (`${this.accountLog} Topic: ${topic}, Received message:${message}`);
99
+ const payload = JSON.parse(message.toString());
100
+ this.receiveEventHandle(topic, payload);
101
+ }
102
+ catch (err) {
103
+ this.logger.error(`${this.accountLog} Failed to parse message:${String(err)}`);
104
+ }
105
+ });
106
+ this.mqttClient.on("error", (err) => {
107
+ this.logger.error(`${this.accountLog} Connection error:${err}`);
108
+ reject(err);
109
+ });
110
+ this.mqttClient.on("close", () => {
111
+ this.logger.warn(`${this.accountLog} Connection closed`);
112
+ if (this.closeHandler != null) {
113
+ this.closeHandler();
114
+ }
115
+ });
116
+ this.mqttClient.on("reconnect", () => {
117
+ this.logger.info(`${this.accountLog} Reconnecting...`);
118
+ });
119
+ // try {
120
+ // this.runtime = getDLRuntime();
121
+ // if (this.runtime) {
122
+ // this.runtime.events.onAgentEvent(event => {
123
+ // this.logger.warn(`中间文本 >>> ${JSON.stringify(event)}`)
124
+ // if (event?.stream === "assistant" && event.data?.phase === "commentary" && event.data?.text) {
125
+ // // this.logger.warn(`中间文本 >>> ${event.data?.text}`)
126
+ // }
127
+ // })
128
+ // this.logger.info(`runtime.events.onAgentEvent事件注册成功...`)
129
+ // }
130
+ // } catch (e) {
131
+ // this.logger.error(`${this.accountLog} ${new String(e)}`);
132
+ // }
133
+ });
134
+ }
135
+ async receiveEventHandle(topic, msg) {
136
+ if (topic.endsWith('/cmd')) {
137
+ const cmdMessage = msg;
138
+ let result = {
139
+ requestId: cmdMessage.requestId,
140
+ success: false,
141
+ error: 'CMD 处理器未初始化',
142
+ timestamp: Date.now()
143
+ };
144
+ if (this.cmdHandler?.isReady()) {
145
+ const res = await this.cmdHandler.handleCmdMessage(cmdMessage);
146
+ result = {
147
+ ...res,
148
+ requestId: result.requestId
149
+ };
150
+ }
151
+ if (cmdMessage.from) {
152
+ await this.publishCmdResultToDLClient(cmdMessage.from, result, "1");
153
+ }
154
+ else {
155
+ await this.publishCmdResultToServer(result, '1');
156
+ }
157
+ return;
158
+ }
159
+ this.lastInboundAt = Date.now();
160
+ const accStatus = messagePreviewHandler(msg, this.ctx.account.accountId, this.logger);
161
+ const t = this.emit('message', {
162
+ "ctx": this.ctx,
163
+ "msg": msg,
164
+ "accStatus": accStatus
165
+ }, this);
166
+ if (!t) {
167
+ this.logger.error('DLClient Event[message] emit fail!');
168
+ }
169
+ }
170
+ closeHandler() {
171
+ const t = this.emit('close');
172
+ if (!t) {
173
+ this.logger.warn(`DLClient account[${this.ctx.account.accountId}] Event[close] emit fail!`);
174
+ }
175
+ }
176
+ publish(topic, payload, sqos) {
177
+ const qos = (sqos ? parseInt(sqos) : (this.mqttConfig?.qos ?? 0));
178
+ return new Promise((reslove, reject) => {
179
+ this.mqttClient.publish(topic, payload, { qos }, (error) => {
180
+ if (error) {
181
+ const errorStr = String(error);
182
+ this.logger.error(`${this.accountLog} Publish error:${errorStr}`);
183
+ reject(errorStr);
184
+ }
185
+ else {
186
+ //this.logger.info(`${this.accountLog} Published to ${topic}`);
187
+ reslove();
188
+ }
189
+ });
190
+ });
191
+ }
192
+ getLastInboundAt() {
193
+ return this.lastInboundAt;
194
+ }
195
+ isConnected() {
196
+ return this.mqttClient?.connected ?? false;
197
+ }
198
+ async start() {
199
+ if (!this.isConnected()) {
200
+ await this.connect();
201
+ }
202
+ if (this.cmdHandler) {
203
+ await this.cmdHandler.initialize();
204
+ }
205
+ await this.changeConnectStatus('start');
206
+ }
207
+ async stop() {
208
+ if (this.mqttClient) {
209
+ this.mqttClient.end(false, () => {
210
+ this.mqttClient = null;
211
+ this.logger.warn(`DLClient account[${this.ctx.account.accountId}] Disconnected`);
212
+ if (this.cmdHandler) {
213
+ this.cmdHandler.cleanup();
214
+ }
215
+ this.cmdHandler = null;
216
+ this.mqttConfig = null;
217
+ });
218
+ }
219
+ await this.changeConnectStatus('stop');
220
+ }
221
+ async exeCmd(cmdMessage) {
222
+ if (this.cmdHandler?.isReady()) {
223
+ return await this.cmdHandler.handleCmdMessage(cmdMessage);
224
+ }
225
+ }
226
+ async publishEventToDLClient(payload, event, sqos, to) {
227
+ const timestamp = Date.now();
228
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
229
+ const api_key = this.ctx.account.config.apiKey;
230
+ const message = {
231
+ apiKey: api_key,
232
+ type: 'event',
233
+ event: event,
234
+ payload: payload,
235
+ agentBrand: 'openClaw',
236
+ timestamp,
237
+ timezone
238
+ };
239
+ if (to && to.length > 0) {
240
+ this.logger.info(`推送客户端事件消息, topic= ${to}`);
241
+ try {
242
+ await this.publish(to, JSON.stringify(message), sqos);
243
+ return;
244
+ }
245
+ catch (e) {
246
+ this.logger.error(String(e));
247
+ }
248
+ return;
249
+ }
250
+ const openUserTopics = this.mqttConfig?.openUserTopics ?? [];
251
+ //this.logger.info(`推送客户端事件消息, topics= [${openUserTopics}]`,)
252
+ for (const topic of openUserTopics) {
253
+ try {
254
+ await this.publish(topic, JSON.stringify(message), sqos);
255
+ }
256
+ catch (e) {
257
+ this.logger.error(String(e));
258
+ }
259
+ }
260
+ }
261
+ async publishCmdResultToDLClient(topic, playload, sqos) {
262
+ const timestamp = Date.now();
263
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
264
+ const api_key = this.ctx.account.config.apiKey;
265
+ const message = {
266
+ apiKey: api_key,
267
+ type: 'cmd',
268
+ payload: playload,
269
+ agentBrand: 'openClaw',
270
+ timestamp,
271
+ timezone
272
+ };
273
+ return await this.publish(topic, JSON.stringify(message), sqos);
274
+ }
275
+ async publishEventToServer(playload, event, sqos) {
276
+ const topic = this.mqttConfig?.serverTopics[1];
277
+ if (!topic) {
278
+ return;
279
+ }
280
+ const timestamp = Date.now();
281
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
282
+ const api_key = this.ctx.account.config.apiKey;
283
+ const message = {
284
+ apiKey: api_key,
285
+ type: 'event',
286
+ event: event,
287
+ payload: playload,
288
+ agentBrand: 'openClaw',
289
+ timestamp,
290
+ timezone
291
+ };
292
+ return await this.publish(topic, JSON.stringify(message), sqos);
293
+ }
294
+ async publishCmdResultToServer(playload, sqos) {
295
+ const topic = this.mqttConfig?.serverTopics[1];
296
+ if (!topic) {
297
+ return;
298
+ }
299
+ const timestamp = Date.now();
300
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
301
+ const api_key = this.ctx.account.config.apiKey;
302
+ const message = {
303
+ apiKey: api_key,
304
+ type: 'cmd',
305
+ payload: playload,
306
+ agentBrand: 'openClaw',
307
+ timestamp,
308
+ timezone
309
+ };
310
+ return await this.publish(topic, JSON.stringify(message), sqos);
311
+ }
312
+ async publishToServer(params, autoParams) {
313
+ const { streamId, content, finish, mediaFiles, session, kind } = params;
314
+ if (!this.mqttClient || !this.mqttConfig) {
315
+ throw new Error(`${this.accountLog} client not connected`);
316
+ }
317
+ const message = buildDLReplyMessage({
318
+ streamId,
319
+ content,
320
+ finish,
321
+ kind
322
+ });
323
+ if (!message) {
324
+ if (autoParams) {
325
+ const { toAgent, contactId, relayId, chatId, notifyUrl, isGroup, botType } = autoParams;
326
+ this.logger.info(`${this.accountLog} streamId:[${streamId}] has been cleaned up due to overdue. current use autoSend...`);
327
+ const data = {
328
+ "chatId": chatId,
329
+ "botType": botType,
330
+ "group": isGroup ? "true" : "false",
331
+ "content": content ?? "",
332
+ "notifyUrl": notifyUrl ?? "",
333
+ "toAgent.id": toAgent.id,
334
+ "toAgent.sessionKey": toAgent.sessionKey,
335
+ "contactId": contactId ?? '',
336
+ "relayId": relayId
337
+ };
338
+ if (session) {
339
+ data["archiveSession.archived"] = session.archived ? 'true' : 'false';
340
+ data["archiveSession.sessionKey"] = session.sessionKey;
341
+ if (session.newSessionKey) {
342
+ data["archiveSession.newSessionKey"] = session.newSessionKey;
343
+ }
344
+ data["archiveSession.runId"] = session.runId;
345
+ }
346
+ let res;
347
+ if (mediaFiles && mediaFiles.length > 0) {
348
+ res = await this.http.upload(AUTO_RELAY_API, mediaFiles, data);
349
+ }
350
+ else if (data.content.length > 0) {
351
+ res = await this.http.post(AUTO_RELAY_API, data);
352
+ }
353
+ return {
354
+ autoRelay: true,
355
+ reqId: res
356
+ };
357
+ }
358
+ this.logger.error(`${this.accountLog} streamId:[${streamId}] has been cleaned up due to overdue.`);
359
+ return {
360
+ autoRelay: true
361
+ };
362
+ }
363
+ const now = Date.now();
364
+ if (message.relayTimeRhreshold && now > message.relayTimeRhreshold) {
365
+ //超时回复使用主动回复
366
+ this.logger.info(`${this.accountLog} Over the reply time, take the initiative to reply!`);
367
+ message.notify = true;
368
+ }
369
+ if (mediaFiles && mediaFiles.length > 0) {
370
+ this.logger.info(`${this.accountLog} upload media files..`);
371
+ const data = {
372
+ "streamId": streamId
373
+ };
374
+ const res = await this.http.upload(AUTO_RELAY_UPLOAD, mediaFiles, data);
375
+ if (res && res.length > 0) {
376
+ this.logger.info(`${this.accountLog} upload media files success!`);
377
+ }
378
+ }
379
+ if (session) {
380
+ message.archiveSession = { ...session };
381
+ }
382
+ const topic = this.mqttConfig.serverTopics[0];
383
+ try {
384
+ await this.publish(topic, JSON.stringify(message));
385
+ return {
386
+ autoRelay: false,
387
+ reqId: message.streamId
388
+ };
389
+ }
390
+ catch (e) {
391
+ throw e;
392
+ }
393
+ }
394
+ /**
395
+ * 减轻服务端负担,将处理状态直接推送到客户端(处理结果走服务端)
396
+ * @param params
397
+ */
398
+ async publishProcessingState(params) {
399
+ const { streamId, content, kind, status, runId } = params;
400
+ if (!this.mqttClient || !this.mqttConfig) {
401
+ throw new Error(`${this.accountLog} client not connected`);
402
+ }
403
+ const message = buildDLReplyMessage({
404
+ streamId,
405
+ content,
406
+ finish: false,
407
+ kind,
408
+ runId
409
+ });
410
+ if (!message) {
411
+ this.logger.error(`[publishProcessingState]${this.accountLog} streamId:[${streamId}] has been cleaned up due to overdue.`);
412
+ return;
413
+ }
414
+ const now = Date.now();
415
+ if (message.relayTimeRhreshold && now > message.relayTimeRhreshold) {
416
+ this.logger.error(`[publishProcessingState]${this.accountLog} Over the reply time, take the initiative to reply!`);
417
+ return;
418
+ }
419
+ const fromUsers = message.fromUsers ?? [];
420
+ if (fromUsers.length === 0) {
421
+ this.logger.warn(`[publishProcessingState]${this.accountLog} from users is empty, can not send message status!`);
422
+ return;
423
+ }
424
+ if (kind === 'plan' && message.contactType === 'group') {
425
+ //群组时中间态消息就不回传了,显得消息太杂
426
+ return;
427
+ }
428
+ let result_content = message.payload;
429
+ if (kind !== 'patch' && kind !== 'plan' && kind !== 'block') {
430
+ if (result_content.length > 200) {
431
+ result_content = result_content.substring(0, 300);
432
+ }
433
+ }
434
+ const toAgent = message.toAgent;
435
+ const sessionKey = toAgent.sessionKey;
436
+ for (const user of fromUsers) {
437
+ const msg = {
438
+ msgId: uuidStr(36),
439
+ streamId,
440
+ chatId: message.chatId,
441
+ sender: {
442
+ id: toAgent.id,
443
+ nickname: toAgent.nickname,
444
+ avatar: toAgent.avatarUrl,
445
+ jobPost: toAgent.jobPost
446
+ },
447
+ receiver: {
448
+ id: user.id,
449
+ nickname: user.nickname
450
+ },
451
+ contactType: message.contactType,
452
+ contactId: message.contactId,
453
+ botType: message.botType,
454
+ payload: result_content,
455
+ createdAt: Date.now(),
456
+ timezone: message.timezone,
457
+ status,
458
+ finish: message.finish,
459
+ kind,
460
+ sessionKey,
461
+ runId,
462
+ relayType: 'openClaw'
463
+ };
464
+ try {
465
+ await this.publish(user.chatTopic, JSON.stringify(msg));
466
+ }
467
+ catch (e) {
468
+ this.logger.error(String(e));
469
+ }
470
+ }
471
+ }
472
+ async downloadFileRaw(url, botType) {
473
+ return await this.http.downloadFileRaw(url, { botType });
474
+ }
475
+ /**
476
+ * 对码成功回调通知
477
+ */
478
+ async approved(ok) {
479
+ const url = '/guest/relayBot/approved/' + this.ctx.rbotId;
480
+ const p = {
481
+ "approved": ok ? "true" : "false"
482
+ };
483
+ await this.http.post(url, p);
484
+ }
485
+ async autoReply(params) {
486
+ const { to: rawTo, text, mediaFiles } = params;
487
+ const { channelId, account } = this.ctx;
488
+ const config = account.config;
489
+ if (!config.configured) {
490
+ throw new Error(`${channelId} account[${account.accountId}] not configured`);
491
+ }
492
+ if ((!text || text.length === 0) && (!mediaFiles || mediaFiles.length === 0)) {
493
+ throw new Error(`${channelId} account[${account.accountId}] content is empty!`);
494
+ }
495
+ // 从 to 中解析 sessionKey(格式: dlink:user:xxx-xxx-dLink#agent:main:dlink-00001:direct:xxx)
496
+ const hashIdx = rawTo.lastIndexOf('#');
497
+ const to = hashIdx > 0 ? rawTo.substring(0, hashIdx) : rawTo;
498
+ const sessionKey = hashIdx > 0 ? rawTo.substring(hashIdx + 1) : undefined;
499
+ const channelPrefix = new RegExp(`^${channelId}:`, "i");
500
+ const chat_id = to.replace(channelPrefix, "");
501
+ const isGroup = chat_id.includes("group:");
502
+ const strArr = isGroup ? chat_id.replace(new RegExp('^group:', 'i'), "").split("-") : chat_id.replace(new RegExp('^user:', 'i'), "").split("-");
503
+ const chatId = strArr[0];
504
+ const botId = strArr[1];
505
+ const botType = strArr[2];
506
+ const relayId = strArr[3];
507
+ const data = {
508
+ "chatId": chatId,
509
+ "toAgent.id": botId,
510
+ "botType": botType,
511
+ "group": isGroup ? "true" : "false",
512
+ "content": text ?? '',
513
+ "relayId": relayId
514
+ };
515
+ // 携带 sessionKey 到服务端,用于下游 Agent 路由
516
+ if (sessionKey) {
517
+ data["toAgent.sessionKey"] = sessionKey;
518
+ }
519
+ let res;
520
+ if (mediaFiles && mediaFiles.length > 0) {
521
+ res = await this.http.upload(AUTO_RELAY_API, mediaFiles, data);
522
+ }
523
+ else {
524
+ res = await this.http.post(AUTO_RELAY_API, data);
525
+ }
526
+ if (res) {
527
+ return {
528
+ channel: channelId,
529
+ messageId: res,
530
+ chatId: chatId
531
+ };
532
+ }
533
+ throw new Error(`${channelId} account[${account.accountId}] Sending message failed.`);
534
+ }
535
+ }
@@ -0,0 +1,27 @@
1
+ import { PluginLogger } from 'openclaw/plugin-sdk';
2
+ import { BotType } from './types.js';
3
+ export type HttpOpts = {
4
+ baseUrl: string;
5
+ apiKey: string;
6
+ secret: string;
7
+ };
8
+ export declare class DLHttp {
9
+ private readonly opts;
10
+ private readonly deviceId;
11
+ private readonly client;
12
+ private readonly logger;
13
+ constructor(opts: HttpOpts, logger: PluginLogger, timeout?: number);
14
+ private randomString;
15
+ private genSign;
16
+ private response;
17
+ get<T>(url: string, data?: Record<string, string>): Promise<T | undefined>;
18
+ post<T>(url: string, data?: Record<string, string>): Promise<T | undefined>;
19
+ upload<T>(url: string, files: Array<string>, data?: Record<string, string>): Promise<T | undefined>;
20
+ downloadFileRaw(url: string, { timeout, botType }: {
21
+ timeout?: number;
22
+ botType: BotType;
23
+ }): Promise<{
24
+ buffer: Buffer;
25
+ filename?: string;
26
+ }>;
27
+ }