@efengx/openclaw-channel-dragon 0.5.27 → 0.5.28

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.
@@ -18,7 +18,11 @@ export declare class ChannelComponent implements IComponent {
18
18
  private normalizeSessionSegment;
19
19
  private resolveOpenClawAgentId;
20
20
  private buildOpenClawSessionKey;
21
+ private resolveWorkbenchSessionIdFromOpenClawSessionKey;
21
22
  private stringifyProgressDetail;
23
+ private findTaskCompletionEvent;
24
+ private extractGeneratedMediaLines;
25
+ private formatTaskCompletionReply;
22
26
  deliverToOpenClaw: (content: string, sessionId?: string, modelId?: string, attachments?: any[], messageId?: string | number) => Promise<void>;
23
27
  handleOutboundText: (ctx: any) => Promise<{
24
28
  ok: boolean;
@@ -34,6 +34,19 @@ export class ChannelComponent {
34
34
  const workbenchSessionId = this.normalizeSessionSegment(sessionId, 'default');
35
35
  return `agent:${openClawAgentId}:dragon:direct:${dragonAgentId}:${workbenchSessionId}`;
36
36
  }
37
+ resolveWorkbenchSessionIdFromOpenClawSessionKey(sessionKey) {
38
+ const raw = String(sessionKey || '').trim();
39
+ if (!raw)
40
+ return 'default';
41
+ const parts = raw.split(':');
42
+ if (parts[0] === 'agent' && parts[2] === channelId) {
43
+ return parts.slice(5).join(':') || 'default';
44
+ }
45
+ if (parts[0] === channelId) {
46
+ return parts.slice(3).join(':') || 'default';
47
+ }
48
+ return 'default';
49
+ }
37
50
  stringifyProgressDetail(value, maxLength = 240) {
38
51
  if (typeof value === 'string') {
39
52
  const text = value.trim();
@@ -49,6 +62,48 @@ export class ChannelComponent {
49
62
  return String(value).slice(0, maxLength);
50
63
  }
51
64
  }
65
+ findTaskCompletionEvent(value) {
66
+ if (!value || typeof value !== 'object')
67
+ return undefined;
68
+ if (value.type === 'task_completion')
69
+ return value;
70
+ const candidates = [
71
+ value.event,
72
+ value.data,
73
+ ...(Array.isArray(value.internalEvents) ? value.internalEvents : []),
74
+ ...(Array.isArray(value.events) ? value.events : []),
75
+ ];
76
+ for (const candidate of candidates) {
77
+ const found = this.findTaskCompletionEvent(candidate);
78
+ if (found)
79
+ return found;
80
+ }
81
+ return undefined;
82
+ }
83
+ extractGeneratedMediaLines(event) {
84
+ const lines = new Set();
85
+ for (const url of Array.isArray(event?.mediaUrls) ? event.mediaUrls : []) {
86
+ if (typeof url === 'string' && url.trim()) {
87
+ lines.add(`MEDIA:${url.trim()}`);
88
+ }
89
+ }
90
+ for (const attachment of Array.isArray(event?.attachments) ? event.attachments : []) {
91
+ const value = attachment?.url || attachment?.path || attachment?.filePath || attachment?.mediaUrl;
92
+ if (typeof value === 'string' && value.trim()) {
93
+ lines.add(`MEDIA:${value.trim()}`);
94
+ }
95
+ }
96
+ return Array.from(lines);
97
+ }
98
+ formatTaskCompletionReply(event) {
99
+ const result = typeof event?.result === 'string' ? event.result.trim() : '';
100
+ const mediaLines = this.extractGeneratedMediaLines(event);
101
+ const lines = [
102
+ result,
103
+ ...mediaLines.filter((line) => !result.includes(line)),
104
+ ].filter((line) => line.trim());
105
+ return lines.join('\n');
106
+ }
52
107
  deliverToOpenClaw = async (content, sessionId = 'default', modelId, attachments, messageId) => {
53
108
  const replyMsgId = messageId ? `dragon_msg_${messageId}` : `dragon_msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
54
109
  if (messageId && this.processedMessageIds.has(messageId))
@@ -305,6 +360,30 @@ export class ChannelComponent {
305
360
  return { ok: true, messageId: Date.now().toString() };
306
361
  };
307
362
  handleAgentEvent = async (evt) => {
308
- await this.telemetry.reportEvent(evt.stream, evt.data, evt.ts);
363
+ const sessionId = evt?.sessionId ||
364
+ this.resolveWorkbenchSessionIdFromOpenClawSessionKey(evt?.sessionKey);
365
+ await this.telemetry.reportEvent(evt.stream, evt.data, evt.ts, sessionId);
366
+ const taskCompletion = this.findTaskCompletionEvent(evt?.data);
367
+ if (!taskCompletion)
368
+ return;
369
+ const content = this.formatTaskCompletionReply(taskCompletion);
370
+ if (!content)
371
+ return;
372
+ await this.telemetry.reportProgress({
373
+ sessionId,
374
+ msgId: taskCompletion.childSessionId ? `dragon_task_${taskCompletion.childSessionId}` : undefined,
375
+ kind: 'task_completion',
376
+ phase: 'end',
377
+ status: taskCompletion.status === 'ok' ? 'completed' : taskCompletion.status,
378
+ title: taskCompletion.status === 'ok' ? '后台任务完成' : '后台任务更新',
379
+ detail: this.stringifyProgressDetail(taskCompletion.taskLabel || taskCompletion.statusLabel),
380
+ data: taskCompletion,
381
+ });
382
+ await this.telemetry.reportReply({
383
+ content,
384
+ sessionId,
385
+ source: 'agent_event_task_completion',
386
+ msgId: taskCompletion.childSessionId ? `dragon_task_reply_${taskCompletion.childSessionId}` : undefined,
387
+ });
309
388
  };
310
389
  }
@@ -16,7 +16,7 @@ export declare class TelemetryComponent implements IComponent {
16
16
  source?: string;
17
17
  msgId?: string;
18
18
  }): Promise<void>;
19
- reportEvent(stream: string, data: any, ts: number): Promise<void>;
19
+ reportEvent(stream: string, data: any, ts: number, sessionId?: string): Promise<void>;
20
20
  reportProgress(payload: {
21
21
  sessionId: string;
22
22
  msgId?: string;
@@ -36,7 +36,7 @@ export class TelemetryComponent {
36
36
  // Error logged by HttpComponent
37
37
  }
38
38
  }
39
- async reportEvent(stream, data, ts) {
39
+ async reportEvent(stream, data, ts, sessionId = "default") {
40
40
  try {
41
41
  let telemetryPayload = null;
42
42
  if (stream === "thinking") {
@@ -63,7 +63,7 @@ export class TelemetryComponent {
63
63
  this.http.fetch(`/api/agents/${this.agentId}/messages/reply`, {
64
64
  method: "POST",
65
65
  headers: { "Content-Type": "application/json" },
66
- body: JSON.stringify({ ...telemetryPayload, msgId }),
66
+ body: JSON.stringify({ ...telemetryPayload, sessionId, msgId }),
67
67
  }).catch(() => { });
68
68
  }
69
69
  }
package/dist/index.js CHANGED
@@ -10,6 +10,18 @@ import { dragonChannelPluginVersion } from "./version.js";
10
10
  const channelId = "dragon";
11
11
  let cachedRuntime;
12
12
  const containers = new Map();
13
+ function isDragonSessionKey(sessionKey) {
14
+ const raw = String(sessionKey || '').trim();
15
+ return raw.startsWith(`${channelId}:`) || /^agent:[^:]+:dragon(?::|$)/.test(raw);
16
+ }
17
+ function resolveAccountIdFromSessionKey(sessionKey) {
18
+ const raw = String(sessionKey || '').trim();
19
+ const parts = raw.split(':');
20
+ if (parts[0] === channelId) {
21
+ return parts[2] || "default";
22
+ }
23
+ return "default";
24
+ }
13
25
  async function getOrCreateContainer(account, ctx) {
14
26
  const key = `${account.agentId}:${account.orchestratorUrl}`;
15
27
  if (containers.has(key))
@@ -158,10 +170,38 @@ const plugin = createChatChannelPlugin({
158
170
  }
159
171
  }
160
172
  const sessionId = target || "default";
173
+ // Collect ALL media URLs from the params (mirrors what OpenClaw tracks internally
174
+ // via collectMessagingMediaUrlsFromRecord when the tool call starts, so that
175
+ // hasGatewayAgentDeliveredExpectedMedia can match them against expectedMediaUrls).
176
+ const sentMediaUrls = [];
177
+ const pushMedia = (v) => {
178
+ if (typeof v === 'string' && v.trim())
179
+ sentMediaUrls.push(v.trim());
180
+ };
181
+ pushMedia(params.media);
182
+ pushMedia(params.mediaUrl);
183
+ pushMedia(params.path);
184
+ pushMedia(params.filePath);
185
+ pushMedia(params.fileUrl);
186
+ if (Array.isArray(params.mediaUrls)) {
187
+ for (const u of params.mediaUrls)
188
+ pushMedia(u);
189
+ }
190
+ if (Array.isArray(params.attachments)) {
191
+ for (const a of params.attachments) {
192
+ if (a && typeof a === 'object') {
193
+ pushMedia(a.media);
194
+ pushMedia(a.mediaUrl);
195
+ pushMedia(a.path);
196
+ pushMedia(a.filePath);
197
+ pushMedia(a.fileUrl);
198
+ }
199
+ }
200
+ }
161
201
  let text = params.message || params.text || "";
162
- const media = params.media || params.mediaUrl || params.path || params.filePath || params.fileUrl;
163
- if (media && typeof media === 'string') {
164
- const mediaLine = `MEDIA: ${media}`;
202
+ // Append media path to text so the Workbench can render the image
203
+ for (const url of sentMediaUrls) {
204
+ const mediaLine = `MEDIA: ${url}`;
165
205
  if (!text.includes(mediaLine)) {
166
206
  text = text ? `${text}\n${mediaLine}` : mediaLine;
167
207
  }
@@ -173,9 +213,12 @@ const plugin = createChatChannelPlugin({
173
213
  return {
174
214
  ok: result.ok,
175
215
  ...result.messageId ? { messageId: result.messageId } : {},
216
+ // Evidence fields that OpenClaw checks via hasMessagingToolDeliveryEvidence
217
+ // and hasGatewayAgentDeliveredExpectedMedia
176
218
  didSendViaMessagingTool: true,
177
- messagingToolSentTexts: [text],
178
- messagingToolSentTargets: [target]
219
+ messagingToolSentTexts: text ? [text] : [],
220
+ messagingToolSentMediaUrls: sentMediaUrls,
221
+ messagingToolSentTargets: [{ to: target, ...(sentMediaUrls.length > 0 ? { mediaUrls: sentMediaUrls } : {}) }],
179
222
  };
180
223
  }
181
224
  throw new Error(`Action "${action}" is not supported by the dragon channel plugin.`);
@@ -205,14 +248,15 @@ const entry = defineChannelPluginEntry({
205
248
  if (typeof agentEventHandler === 'function') {
206
249
  agentEventHandler(async (evt) => {
207
250
  const sessionKey = evt.sessionKey;
208
- if (!sessionKey || !sessionKey.startsWith(`${channelId}:`))
251
+ if (!isDragonSessionKey(sessionKey))
209
252
  return;
210
- const accountId = sessionKey.split(':')[2] || "default";
253
+ const accountId = resolveAccountIdFromSessionKey(sessionKey);
211
254
  const account = base.config.resolveAccount(api?.runtime?.cfg, accountId);
212
255
  const container = await getOrCreateContainer(account, {
213
256
  cfg: api?.runtime?.cfg,
214
257
  abortSignal: new AbortController().signal,
215
- channelRuntime: api?.runtime?.channelRuntime // Ensure runtime is passed
258
+ channelRuntime: api?.runtime?.channelRuntime,
259
+ logger: api?.runtime?.logger ?? api?.runtime?.log,
216
260
  });
217
261
  const channel = container.get('channel');
218
262
  await channel.handleAgentEvent(evt);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const dragonChannelPluginVersion = "0.5.27";
1
+ export declare const dragonChannelPluginVersion = "0.5.28";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const dragonChannelPluginVersion = "0.5.27";
1
+ export const dragonChannelPluginVersion = "0.5.28";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@efengx/openclaw-channel-dragon",
3
- "version": "0.5.27",
3
+ "version": "0.5.28",
4
4
  "description": "Dragon workbench channel for OpenClaw",
5
5
  "author": "feng xiang <ofengx@gmail.com>",
6
6
  "type": "module",