@efengx/openclaw-channel-dragon 0.5.26 → 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.
- package/dist/components/channel/ChannelComponent.d.ts +4 -0
- package/dist/components/channel/ChannelComponent.js +80 -1
- package/dist/components/telemetry/TelemetryComponent.d.ts +1 -1
- package/dist/components/telemetry/TelemetryComponent.js +2 -2
- package/dist/index.js +87 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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))
|
|
@@ -140,6 +152,77 @@ const plugin = createChatChannelPlugin({
|
|
|
140
152
|
};
|
|
141
153
|
}
|
|
142
154
|
}
|
|
155
|
+
},
|
|
156
|
+
actions: {
|
|
157
|
+
describeMessageTool: ({ cfg, accountId }) => {
|
|
158
|
+
return { actions: ["send"] };
|
|
159
|
+
},
|
|
160
|
+
handleAction: async (ctx) => {
|
|
161
|
+
const { action, params, cfg, accountId } = ctx;
|
|
162
|
+
if (action === "send") {
|
|
163
|
+
const account = base.config.resolveAccount(cfg, accountId);
|
|
164
|
+
const container = await getOrCreateContainer(account, ctx);
|
|
165
|
+
const channel = container.get('channel');
|
|
166
|
+
let target = params.to || params.target || ctx.toolContext?.currentChannelId || "default";
|
|
167
|
+
for (const prefix of ["dragon-workbench-", "dragon-workbench:", "dragon-", "dragon:"]) {
|
|
168
|
+
if (target.startsWith(prefix)) {
|
|
169
|
+
target = target.substring(prefix.length);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
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
|
+
}
|
|
201
|
+
let text = params.message || params.text || "";
|
|
202
|
+
// Append media path to text so the Workbench can render the image
|
|
203
|
+
for (const url of sentMediaUrls) {
|
|
204
|
+
const mediaLine = `MEDIA: ${url}`;
|
|
205
|
+
if (!text.includes(mediaLine)) {
|
|
206
|
+
text = text ? `${text}\n${mediaLine}` : mediaLine;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const result = await channel.handleOutboundText({
|
|
210
|
+
text,
|
|
211
|
+
peer: { id: sessionId }
|
|
212
|
+
});
|
|
213
|
+
return {
|
|
214
|
+
ok: result.ok,
|
|
215
|
+
...result.messageId ? { messageId: result.messageId } : {},
|
|
216
|
+
// Evidence fields that OpenClaw checks via hasMessagingToolDeliveryEvidence
|
|
217
|
+
// and hasGatewayAgentDeliveredExpectedMedia
|
|
218
|
+
didSendViaMessagingTool: true,
|
|
219
|
+
messagingToolSentTexts: text ? [text] : [],
|
|
220
|
+
messagingToolSentMediaUrls: sentMediaUrls,
|
|
221
|
+
messagingToolSentTargets: [{ to: target, ...(sentMediaUrls.length > 0 ? { mediaUrls: sentMediaUrls } : {}) }],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
throw new Error(`Action "${action}" is not supported by the dragon channel plugin.`);
|
|
225
|
+
}
|
|
143
226
|
}
|
|
144
227
|
},
|
|
145
228
|
outbound: {
|
|
@@ -165,14 +248,15 @@ const entry = defineChannelPluginEntry({
|
|
|
165
248
|
if (typeof agentEventHandler === 'function') {
|
|
166
249
|
agentEventHandler(async (evt) => {
|
|
167
250
|
const sessionKey = evt.sessionKey;
|
|
168
|
-
if (!sessionKey
|
|
251
|
+
if (!isDragonSessionKey(sessionKey))
|
|
169
252
|
return;
|
|
170
|
-
const accountId = sessionKey
|
|
253
|
+
const accountId = resolveAccountIdFromSessionKey(sessionKey);
|
|
171
254
|
const account = base.config.resolveAccount(api?.runtime?.cfg, accountId);
|
|
172
255
|
const container = await getOrCreateContainer(account, {
|
|
173
256
|
cfg: api?.runtime?.cfg,
|
|
174
257
|
abortSignal: new AbortController().signal,
|
|
175
|
-
channelRuntime: api?.runtime?.channelRuntime
|
|
258
|
+
channelRuntime: api?.runtime?.channelRuntime,
|
|
259
|
+
logger: api?.runtime?.logger ?? api?.runtime?.log,
|
|
176
260
|
});
|
|
177
261
|
const channel = container.get('channel');
|
|
178
262
|
await channel.handleAgentEvent(evt);
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const dragonChannelPluginVersion = "0.5.
|
|
1
|
+
export declare const dragonChannelPluginVersion = "0.5.28";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const dragonChannelPluginVersion = "0.5.
|
|
1
|
+
export const dragonChannelPluginVersion = "0.5.28";
|