adp-openclaw 0.0.46 → 0.0.48

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/index.ts CHANGED
@@ -7,6 +7,8 @@ import {
7
7
  ADP_UPLOAD_TOOL_SCHEMA,
8
8
  parseAdpUploadToolParams,
9
9
  uploadFilesToAdpEndpoint,
10
+ uploadResultEmitter,
11
+ UPLOAD_RESULT_EVENT,
10
12
  type AdpUploadToolResult,
11
13
  type UploadedFileInfo,
12
14
  } from "./src/adp-upload-tool.js";
@@ -184,6 +186,12 @@ const plugin = {
184
186
  api.logger.info?.(`[${ADP_UPLOAD_TOOL_NAME}] file.downloadUrl: ${file.downloadUrl}`);
185
187
  api.logger.info?.(`[${ADP_UPLOAD_TOOL_NAME}] file.uri: ${file.uri}`);
186
188
  }
189
+
190
+ // 发射上传结果事件,让 monitor.ts 能够直接获取完整的下载链接
191
+ uploadResultEmitter.emit(UPLOAD_RESULT_EVENT, {
192
+ toolCallId,
193
+ result: successResult,
194
+ });
187
195
 
188
196
  // Build content with resource links and download URLs
189
197
  const content: Array<{ type: string; uri?: string; name?: string; mimeType?: string; text?: string; downloadUrl?: string }> = [];
@@ -200,15 +208,20 @@ const plugin = {
200
208
  }
201
209
 
202
210
  // Add a text summary with download URLs for AI to include in response
211
+ // 注意:URL 包含签名参数,必须完整保留,不能截断或修改
203
212
  const urlSummary = (successResult.files || [])
204
- .map((f: UploadedFileInfo) => `- [${f.name}](${f.downloadUrl || f.uri})`)
213
+ .map((f: UploadedFileInfo) => {
214
+ const url = f.downloadUrl || f.uri;
215
+ // 把完整 URL 作为代码块,防止 AI 截断或修改
216
+ return `- **${f.name}**: \`${url}\``;
217
+ })
205
218
  .join("\n");
206
219
 
207
220
  api.logger.info?.(`[${ADP_UPLOAD_TOOL_NAME}] urlSummary: ${urlSummary}`);
208
221
 
209
222
  content.push({
210
223
  type: "text",
211
- text: `Files uploaded successfully:\n${urlSummary}\n\n⚠️ Note: Please include these download links in your response to the user and remind them that the links are valid for 24 hours.`,
224
+ text: `Files uploaded successfully:\n${urlSummary}\n\n⚠️ IMPORTANT: The URLs above contain authentication signatures. You MUST copy the ENTIRE URL exactly as shown (including all query parameters after the "?"). Do NOT truncate or modify the URLs in any way. The links are valid for 24 hours.`,
212
225
  });
213
226
 
214
227
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adp-openclaw",
3
- "version": "0.0.46",
3
+ "version": "0.0.48",
4
4
  "description": "ADP-OpenClaw demo channel plugin (Go WebSocket backend)",
5
5
  "type": "module",
6
6
  "dependencies": {
@@ -12,8 +12,17 @@
12
12
  import { constants } from "node:fs";
13
13
  import { access, readFile, stat } from "node:fs/promises";
14
14
  import { basename, extname } from "node:path";
15
+ import { EventEmitter } from "node:events";
15
16
  import type { AdpOpenclawChannelConfig } from "./channel.js";
16
17
 
18
+ // ==================== 上传结果事件 ====================
19
+
20
+ /** 上传结果事件发射器,用于通知其他模块上传完成 */
21
+ export const uploadResultEmitter = new EventEmitter();
22
+
23
+ /** 上传结果事件名称 */
24
+ export const UPLOAD_RESULT_EVENT = "adp-upload-result";
25
+
17
26
  // ==================== 类型定义 ====================
18
27
 
19
28
  /** 存储凭证请求参数 */
package/src/channel.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  DEFAULT_ACCOUNT_ID,
8
8
  } from "openclaw/plugin-sdk";
9
9
  import { adpOpenclawOnboardingAdapter } from "./onboarding.js";
10
+ import { getActiveWebSocket } from "./runtime.js";
10
11
 
11
12
  // Default WebSocket URL for ADP OpenClaw
12
13
  const DEFAULT_WS_URL = "wss://wss.lke.cloud.tencent.com/bot/gateway/conn";
@@ -218,6 +219,42 @@ export const adpOpenclawPlugin: ChannelPlugin<ResolvedAdpOpenclawAccount> = {
218
219
  });
219
220
  },
220
221
  },
221
- // Note: outbound.send is not available in WebSocket-only architecture
222
- // All message sending is done through the WebSocket connection in monitor.ts
222
+ // Outbound message support for the "message" tool
223
+ outbound: {
224
+ send: async ({ text, to, log }) => {
225
+ const ws = getActiveWebSocket();
226
+ if (!ws) {
227
+ log?.error?.("[adp-openclaw] No active WebSocket connection for outbound message");
228
+ return { ok: false, error: "No active WebSocket connection" };
229
+ }
230
+
231
+ // Parse target: expected format is "adp-openclaw:{userId}" or "adp-openclaw:bot"
232
+ // The "to" parameter comes from the message tool with format like "adp-openclaw:user123"
233
+ const targetParts = to.split(":");
234
+ const targetUserId = targetParts.length > 1 ? targetParts.slice(1).join(":") : to;
235
+
236
+ log?.info?.(`[adp-openclaw] Sending outbound message to ${targetUserId}: ${text.slice(0, 50)}...`);
237
+
238
+ try {
239
+ // Generate unique request ID
240
+ const requestId = `outbound-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
241
+
242
+ const outMsg = {
243
+ type: "outbound",
244
+ requestId,
245
+ payload: {
246
+ to: targetUserId,
247
+ text: text,
248
+ },
249
+ timestamp: Date.now(),
250
+ };
251
+
252
+ ws.send(JSON.stringify(outMsg));
253
+ return { ok: true };
254
+ } catch (err) {
255
+ log?.error?.(`[adp-openclaw] Failed to send outbound message: ${err}`);
256
+ return { ok: false, error: String(err) };
257
+ }
258
+ },
259
+ },
223
260
  };
package/src/monitor.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Supports: API Token auth, conversation tracking for multi-turn dialogues
3
3
 
4
4
  import type { PluginLogger, ClawdbotConfig } from "openclaw/plugin-sdk";
5
- import { getAdpOpenclawRuntime } from "./runtime.js";
5
+ import { getAdpOpenclawRuntime, setActiveWebSocket } from "./runtime.js";
6
6
  import {
7
7
  getChatHistory,
8
8
  listSessions,
@@ -14,6 +14,8 @@ import {
14
14
  ADP_UPLOAD_TOOL_NAME,
15
15
  ADP_UPLOAD_TOOL_SCHEMA,
16
16
  executeAdpUploadTool,
17
+ uploadResultEmitter,
18
+ UPLOAD_RESULT_EVENT,
17
19
  type AdpUploadToolResult,
18
20
  } from "./adp-upload-tool.js";
19
21
  import {
@@ -174,6 +176,9 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
174
176
 
175
177
  ws.on("open", () => {
176
178
  log?.info(`[adp-openclaw] WebSocket connected, authenticating...`);
179
+
180
+ // Save active WebSocket for outbound messaging
181
+ setActiveWebSocket(ws);
177
182
 
178
183
  // Send authentication message with signature (includes timestamp for anti-replay)
179
184
  const nonce = generateNonce();
@@ -328,20 +333,58 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
328
333
  let lastPartialText = ""; // Track last sent text for delta calculation
329
334
  let finalSent = false; // Track if outbound_end has been sent
330
335
  const displayName = inMsg.user?.username || inMsg.from;
336
+
337
+ // 收集上传结果,在发送最终回复时追加完整的下载链接
338
+ let pendingUploadResults: AdpUploadToolResult[] = [];
339
+
340
+ // 监听上传结果事件
341
+ const uploadResultHandler = (event: { toolCallId: string; result: AdpUploadToolResult }) => {
342
+ log?.info(`[adp-openclaw] Received upload result event for toolCallId=${event.toolCallId}`);
343
+ if (event.result.ok && event.result.files && event.result.files.length > 0) {
344
+ pendingUploadResults.push(event.result);
345
+ // 打印完整的下载链接
346
+ for (const file of event.result.files) {
347
+ log?.info(`[adp-openclaw] Upload result - file.downloadUrl: ${file.downloadUrl}`);
348
+ }
349
+ }
350
+ };
351
+ uploadResultEmitter.on(UPLOAD_RESULT_EVENT, uploadResultHandler);
331
352
 
332
353
  // Helper function to send outbound_end message
333
354
  const sendOutboundEnd = (text: string) => {
334
355
  if (finalSent) return; // Prevent duplicate sends
335
356
  finalSent = true;
336
357
 
358
+ // 移除事件监听
359
+ uploadResultEmitter.off(UPLOAD_RESULT_EVENT, uploadResultHandler);
360
+
361
+ // 如果有上传结果,追加完整的下载链接到回复中
362
+ let finalText = text;
363
+ if (pendingUploadResults.length > 0) {
364
+ const uploadLinks: string[] = [];
365
+ for (const result of pendingUploadResults) {
366
+ for (const file of (result.files || [])) {
367
+ if (file.downloadUrl) {
368
+ // 使用完整的下载链接,包括签名参数
369
+ uploadLinks.push(`📎 [${file.name}](${file.downloadUrl})`);
370
+ log?.info(`[adp-openclaw] Appending download link: ${file.downloadUrl.substring(0, 100)}...`);
371
+ }
372
+ }
373
+ }
374
+ if (uploadLinks.length > 0) {
375
+ finalText = `${text}\n\n**文件下载链接(24小时有效):**\n${uploadLinks.join("\n")}`;
376
+ log?.info(`[adp-openclaw] Added ${uploadLinks.length} download links to final response`);
377
+ }
378
+ }
379
+
337
380
  if (chunkIndex > 0) {
338
- log?.info(`[adp-openclaw] Sending outbound_end to ${displayName}: ${text.slice(0, 50)}... (chunks=${chunkIndex})`);
381
+ log?.info(`[adp-openclaw] Sending outbound_end to ${displayName}: ${finalText.slice(0, 50)}... (chunks=${chunkIndex})`);
339
382
  const endMsg: WSMessage = {
340
383
  type: MsgType.OutboundEnd,
341
384
  requestId: generateRequestId(),
342
385
  payload: {
343
386
  to: inMsg.from,
344
- text: text,
387
+ text: finalText,
345
388
  conversationId: inMsg.conversationId,
346
389
  recordId: inMsg.recordId, // Pass recordId back to server
347
390
  streamId: streamId,
@@ -353,13 +396,13 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
353
396
  ws.send(JSON.stringify(endMsg));
354
397
  } else {
355
398
  // No streaming chunks were sent, send as regular outbound message
356
- log?.info(`[adp-openclaw] Sending outbound to ${displayName}: ${text.slice(0, 50)}...`);
399
+ log?.info(`[adp-openclaw] Sending outbound to ${displayName}: ${finalText.slice(0, 50)}...`);
357
400
  const outMsg: WSMessage = {
358
401
  type: MsgType.Outbound,
359
402
  requestId: generateRequestId(),
360
403
  payload: {
361
404
  to: inMsg.from,
362
- text: text,
405
+ text: finalText,
363
406
  conversationId: inMsg.conversationId,
364
407
  recordId: inMsg.recordId, // Pass recordId back to server
365
408
  user: inMsg.user,
@@ -494,6 +537,7 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
494
537
  // SDK may call deliver without kind when streaming ends
495
538
  if (kind === "final" || kind === undefined) {
496
539
  log?.info(`[adp-openclaw] deliver triggering sendOutboundEnd (kind=${kind})`);
540
+ log?.info(`[adp-openclaw] Final text content: ${text}`);
497
541
  sendOutboundEnd(text || lastPartialText);
498
542
  }
499
543
  },
@@ -650,6 +694,8 @@ async function connectAndHandle(params: ConnectParams): Promise<void> {
650
694
  ws.on("close", (code, reason) => {
651
695
  if (pingInterval) clearInterval(pingInterval);
652
696
  abortSignal?.removeEventListener("abort", abortHandler);
697
+ // Clear active WebSocket when connection closes
698
+ setActiveWebSocket(null);
653
699
  log?.info(`[adp-openclaw] WebSocket closed: ${code} ${reason.toString()}`);
654
700
  resolve();
655
701
  });
package/src/runtime.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Runtime singleton for adp-openclaw plugin
2
2
  import type { PluginRuntime } from "openclaw/plugin-sdk";
3
+ import type { WebSocket } from "ws";
3
4
 
4
5
  let adpOpenclawRuntime: PluginRuntime | null = null;
5
6
 
@@ -16,6 +17,17 @@ export type PluginConfig = {
16
17
 
17
18
  let pluginConfig: PluginConfig = {};
18
19
 
20
+ // Active WebSocket connection for outbound messaging
21
+ let activeWebSocket: WebSocket | null = null;
22
+
23
+ export function setActiveWebSocket(ws: WebSocket | null): void {
24
+ activeWebSocket = ws;
25
+ }
26
+
27
+ export function getActiveWebSocket(): WebSocket | null {
28
+ return activeWebSocket;
29
+ }
30
+
19
31
  export function setAdpOpenclawRuntime(runtime: PluginRuntime): void {
20
32
  adpOpenclawRuntime = runtime;
21
33
  }