@efengx/openclaw-channel-dragon 0.3.5 → 0.3.7

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 (2) hide show
  1. package/dist/index.js +44 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ let cachedRuntime;
11
11
  let globalDispatcher;
12
12
  console.log("[Dragon] Plugin script is being evaluated by OpenClaw gateway...");
13
13
  const channelId = "dragon";
14
+ const processedMessageIds = new Set();
14
15
  async function ensureConnection(account, runtimeLogger) {
15
16
  if (bridgeClient)
16
17
  return;
@@ -69,10 +70,23 @@ async function startPolling(ctx) {
69
70
  }
70
71
  return;
71
72
  }
72
- const deliverToOpenClaw = async (content, sessionId = 'default', modelId, attachments) => {
73
+ const deliverToOpenClaw = async (content, sessionId = 'default', modelId, attachments, messageId) => {
73
74
  const msg = String(content || '').trim();
74
75
  if (!msg && (!attachments || attachments.length === 0))
75
76
  return;
77
+ // Deduplication check
78
+ if (messageId && processedMessageIds.has(messageId)) {
79
+ return;
80
+ }
81
+ if (messageId) {
82
+ processedMessageIds.add(messageId);
83
+ // Keep cache size manageable
84
+ if (processedMessageIds.size > 1000) {
85
+ const first = processedMessageIds.values().next().value;
86
+ if (first !== undefined)
87
+ processedMessageIds.delete(first);
88
+ }
89
+ }
76
90
  // sessionKey format: channel:agentId:direct:peerId
77
91
  // peerId can be the sessionId for multi-session support
78
92
  const sessionKey = `dragon:${account.agentId}:direct:${sessionId}`;
@@ -153,8 +167,30 @@ async function startPolling(ctx) {
153
167
  }
154
168
  });
155
169
  };
170
+ const consumePendingMessages = async () => {
171
+ try {
172
+ const pollUrl = `${orchestratorUrl}/api/agents/${agentId}/messages/poll`;
173
+ const res = await fetch(pollUrl);
174
+ if (res.ok) {
175
+ const data = (await res.json());
176
+ const messages = data.messages || [];
177
+ if (messages.length > 0) {
178
+ logger?.info?.(`dragon channel: [RECOVERY] Consuming ${messages.length} pending messages via polling.`);
179
+ for (const m of messages) {
180
+ await deliverToOpenClaw(String(m.content || ''), String(m.sessionId || 'default'), m.modelId, m.attachments, m.id // Pass DB ID for deduplication
181
+ );
182
+ }
183
+ }
184
+ }
185
+ }
186
+ catch (e) {
187
+ logger?.error?.(`dragon channel: [RECOVERY] Polling failed: ${e.message}`);
188
+ }
189
+ };
156
190
  const connectOnce = async () => {
157
191
  const sseUrl = `${orchestratorUrl}/api/agents/events`;
192
+ // Recovery: Catch up on any messages missed during downtime
193
+ void consumePendingMessages();
158
194
  const handleSseText = async (chunkText, bufRef) => {
159
195
  // Diagnostic: log first chunk of SSE if it's not a heartbeat comment
160
196
  const isHeartbeat = chunkText.trim().startsWith(':');
@@ -191,7 +227,8 @@ async function startPolling(ctx) {
191
227
  logger?.info?.(`dragon channel: [DEBUG] Received WORKBENCH_MESSAGE via SSE. AgentID=${evt.agentId}, ContentLen=${evt.payload?.content?.length}, Latency=${latency}ms`);
192
228
  logger?.info?.(`dragon channel: [DEBUG] Payload Content: "${evt.payload?.content?.substring(0, 500)}"`);
193
229
  logger?.info?.(`dragon channel: [DEBUG] Current Account Config: ${JSON.stringify(account)}`);
194
- await deliverToOpenClaw(String(evt?.payload?.content || ''), String(evt?.payload?.sessionId || 'default'), evt?.payload?.modelId, evt?.payload?.attachments);
230
+ await deliverToOpenClaw(String(evt?.payload?.content || ''), String(evt?.payload?.sessionId || 'default'), evt?.payload?.modelId, evt?.payload?.attachments, evt?.payload?.id // Pass DB ID for deduplication
231
+ );
195
232
  }
196
233
  else if (evt.type === 'FETCH_HISTORY') {
197
234
  const { sessionId } = evt.payload;
@@ -314,6 +351,10 @@ async function startPolling(ctx) {
314
351
  }
315
352
  };
316
353
  let attempt = 0;
354
+ // Safety: Periodic polling fallback every 60 seconds
355
+ const pollInterval = setInterval(() => {
356
+ void consumePendingMessages();
357
+ }, 60_000);
317
358
  while (!ctx.abortSignal.aborted) {
318
359
  try {
319
360
  attempt += 1;
@@ -329,6 +370,7 @@ async function startPolling(ctx) {
329
370
  await new Promise((r) => setTimeout(r, delayMs));
330
371
  }
331
372
  }
373
+ clearInterval(pollInterval);
332
374
  logger?.info?.({ agentId }, "dragon channel: SSE loop terminated");
333
375
  }
334
376
  const base = createChannelPluginBase({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@efengx/openclaw-channel-dragon",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Dragon workbench channel for OpenClaw",
5
5
  "author": "feng xiang <ofengx@gmail.com>",
6
6
  "type": "module",