@efengx/openclaw-channel-dragon 0.5.36 → 0.5.37
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 +5 -2
- package/dist/components/channel/ChannelComponent.js +26 -31
- package/dist/components/media/MediaRegistryComponent.d.ts +35 -0
- package/dist/components/media/MediaRegistryComponent.js +141 -0
- package/dist/components/telemetry/TelemetryComponent.d.ts +1 -0
- package/dist/components/telemetry/TelemetryComponent.js +1 -1
- package/dist/index.js +17 -45
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IComponent } from "../../core/IComponent.js";
|
|
2
2
|
import { TelemetryComponent } from "../telemetry/TelemetryComponent.js";
|
|
3
|
+
import { MediaRegistryComponent } from "../media/MediaRegistryComponent.js";
|
|
3
4
|
export interface ChannelOptions {
|
|
4
5
|
accountId: string;
|
|
5
6
|
agentId: string;
|
|
@@ -10,9 +11,10 @@ export interface ChannelOptions {
|
|
|
10
11
|
export declare class ChannelComponent implements IComponent {
|
|
11
12
|
private options;
|
|
12
13
|
private telemetry;
|
|
14
|
+
private mediaRegistry;
|
|
13
15
|
private processedMessageIds;
|
|
14
16
|
private sessionQueues;
|
|
15
|
-
constructor(options: ChannelOptions, telemetry: TelemetryComponent);
|
|
17
|
+
constructor(options: ChannelOptions, telemetry: TelemetryComponent, mediaRegistry: MediaRegistryComponent);
|
|
16
18
|
start(): Promise<void>;
|
|
17
19
|
stop(): Promise<void>;
|
|
18
20
|
private normalizeSessionSegment;
|
|
@@ -23,16 +25,17 @@ export declare class ChannelComponent implements IComponent {
|
|
|
23
25
|
private resolveWorkbenchSessionIdFromOpenClawSessionKey;
|
|
24
26
|
private stringifyProgressDetail;
|
|
25
27
|
private findTaskCompletionEvent;
|
|
26
|
-
private extractGeneratedMediaLines;
|
|
27
28
|
private formatTaskCompletionReply;
|
|
28
29
|
deliverToOpenClaw: (content: string, sessionId?: string, modelId?: string, attachments?: any[], messageId?: string | number) => Promise<void>;
|
|
29
30
|
handleOutboundText: (ctx: any) => Promise<{
|
|
30
31
|
ok: boolean;
|
|
31
32
|
messageId: string;
|
|
32
33
|
error: any;
|
|
34
|
+
attachments?: undefined;
|
|
33
35
|
} | {
|
|
34
36
|
ok: boolean;
|
|
35
37
|
messageId: string;
|
|
38
|
+
attachments: import("../media/MediaRegistryComponent.js").DragonMediaAttachment[];
|
|
36
39
|
error?: undefined;
|
|
37
40
|
}>;
|
|
38
41
|
reportTargetProtocolError: (message: string, rawTarget: unknown) => Promise<void>;
|
|
@@ -4,11 +4,13 @@ const workbenchTargetPrefix = "dragon-workbench:";
|
|
|
4
4
|
export class ChannelComponent {
|
|
5
5
|
options;
|
|
6
6
|
telemetry;
|
|
7
|
+
mediaRegistry;
|
|
7
8
|
processedMessageIds = new Set();
|
|
8
9
|
sessionQueues = new Map();
|
|
9
|
-
constructor(options, telemetry) {
|
|
10
|
+
constructor(options, telemetry, mediaRegistry) {
|
|
10
11
|
this.options = options;
|
|
11
12
|
this.telemetry = telemetry;
|
|
13
|
+
this.mediaRegistry = mediaRegistry;
|
|
12
14
|
}
|
|
13
15
|
async start() { }
|
|
14
16
|
async stop() { }
|
|
@@ -96,29 +98,8 @@ export class ChannelComponent {
|
|
|
96
98
|
}
|
|
97
99
|
return undefined;
|
|
98
100
|
}
|
|
99
|
-
extractGeneratedMediaLines(event) {
|
|
100
|
-
const lines = new Set();
|
|
101
|
-
for (const url of Array.isArray(event?.mediaUrls) ? event.mediaUrls : []) {
|
|
102
|
-
if (typeof url === 'string' && url.trim()) {
|
|
103
|
-
lines.add(`MEDIA:${url.trim()}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
for (const attachment of Array.isArray(event?.attachments) ? event.attachments : []) {
|
|
107
|
-
const value = attachment?.url || attachment?.path || attachment?.filePath || attachment?.mediaUrl;
|
|
108
|
-
if (typeof value === 'string' && value.trim()) {
|
|
109
|
-
lines.add(`MEDIA:${value.trim()}`);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return Array.from(lines);
|
|
113
|
-
}
|
|
114
101
|
formatTaskCompletionReply(event) {
|
|
115
|
-
|
|
116
|
-
const mediaLines = this.extractGeneratedMediaLines(event);
|
|
117
|
-
const lines = [
|
|
118
|
-
result,
|
|
119
|
-
...mediaLines.filter((line) => !result.includes(line)),
|
|
120
|
-
].filter((line) => line.trim());
|
|
121
|
-
return lines.join('\n');
|
|
102
|
+
return typeof event?.result === 'string' ? event.result.trim() : '';
|
|
122
103
|
}
|
|
123
104
|
deliverToOpenClaw = async (content, sessionId = 'default', modelId, attachments, messageId) => {
|
|
124
105
|
const replyMsgId = messageId ? `dragon_msg_${messageId}` : `dragon_msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -166,13 +147,15 @@ export class ChannelComponent {
|
|
|
166
147
|
dispatcherOptions: {
|
|
167
148
|
deliver: async (payload) => {
|
|
168
149
|
const text = payload?.text || "";
|
|
169
|
-
|
|
150
|
+
const archivedAttachments = await this.mediaRegistry.archiveStructuredMedia(payload);
|
|
151
|
+
if (!text && !payload?.tool_calls?.length && archivedAttachments.length === 0)
|
|
170
152
|
return;
|
|
171
153
|
await this.telemetry.reportReply({
|
|
172
154
|
content: text,
|
|
173
155
|
sessionId,
|
|
174
156
|
tool_calls: payload?.tool_calls,
|
|
175
157
|
reasoning_content: payload?.reasoning_content,
|
|
158
|
+
attachments: archivedAttachments.length > 0 ? archivedAttachments : payload?.attachments,
|
|
176
159
|
source: "telemetry_deliver",
|
|
177
160
|
msgId: replyMsgId,
|
|
178
161
|
});
|
|
@@ -218,12 +201,16 @@ export class ChannelComponent {
|
|
|
218
201
|
});
|
|
219
202
|
},
|
|
220
203
|
onToolResult: async (payload) => {
|
|
204
|
+
const archivedAttachments = await this.mediaRegistry.archiveStructuredMedia(payload);
|
|
221
205
|
await progress({
|
|
222
|
-
kind: 'tool',
|
|
206
|
+
kind: archivedAttachments.length > 0 ? 'media' : 'tool',
|
|
223
207
|
phase: 'end',
|
|
224
208
|
status: 'completed',
|
|
225
|
-
title: '工具执行完成',
|
|
226
|
-
detail:
|
|
209
|
+
title: archivedAttachments.length > 0 ? '媒体文件已生成' : '工具执行完成',
|
|
210
|
+
detail: archivedAttachments.length > 0
|
|
211
|
+
? archivedAttachments.map((item) => item.name).join(', ')
|
|
212
|
+
: this.stringifyProgressDetail(payload?.text || payload),
|
|
213
|
+
data: archivedAttachments.length > 0 ? { ...payload, attachments: archivedAttachments } : payload,
|
|
227
214
|
});
|
|
228
215
|
},
|
|
229
216
|
onItemEvent: async (payload) => {
|
|
@@ -371,6 +358,7 @@ export class ChannelComponent {
|
|
|
371
358
|
handleOutboundText = async (ctx) => {
|
|
372
359
|
const text = ctx?.text || "";
|
|
373
360
|
const { logger } = this.options;
|
|
361
|
+
const archivedAttachments = await this.mediaRegistry.archiveStructuredMedia(ctx);
|
|
374
362
|
let sessionId;
|
|
375
363
|
try {
|
|
376
364
|
sessionId = this.decodeWorkbenchTarget(ctx?.peer?.id);
|
|
@@ -382,8 +370,13 @@ export class ChannelComponent {
|
|
|
382
370
|
return { ok: false, messageId: Date.now().toString(), error: message };
|
|
383
371
|
}
|
|
384
372
|
logger?.info?.(`[dragon channels][Dragon Plugin] Outbound Text (Action): "${text.substring(0, 50)}${text.length > 50 ? '...' : ''}" [Session: ${sessionId}] [RawPeer: ${ctx?.peer?.id || 'none'}]`);
|
|
385
|
-
await this.telemetry.reportReply({
|
|
386
|
-
|
|
373
|
+
await this.telemetry.reportReply({
|
|
374
|
+
content: text,
|
|
375
|
+
sessionId,
|
|
376
|
+
attachments: archivedAttachments.length > 0 ? archivedAttachments : ctx?.attachments,
|
|
377
|
+
source: "channel_outbound",
|
|
378
|
+
});
|
|
379
|
+
return { ok: true, messageId: Date.now().toString(), attachments: archivedAttachments };
|
|
387
380
|
};
|
|
388
381
|
reportTargetProtocolError = async (message, rawTarget) => {
|
|
389
382
|
const content = [
|
|
@@ -407,7 +400,8 @@ export class ChannelComponent {
|
|
|
407
400
|
if (!taskCompletion)
|
|
408
401
|
return;
|
|
409
402
|
const content = this.formatTaskCompletionReply(taskCompletion);
|
|
410
|
-
|
|
403
|
+
const archivedAttachments = await this.mediaRegistry.archiveStructuredMedia(taskCompletion);
|
|
404
|
+
if (!content && archivedAttachments.length === 0)
|
|
411
405
|
return;
|
|
412
406
|
await this.telemetry.reportProgress({
|
|
413
407
|
sessionId,
|
|
@@ -417,11 +411,12 @@ export class ChannelComponent {
|
|
|
417
411
|
status: taskCompletion.status === 'ok' ? 'completed' : taskCompletion.status,
|
|
418
412
|
title: taskCompletion.status === 'ok' ? '后台任务完成' : '后台任务更新',
|
|
419
413
|
detail: this.stringifyProgressDetail(taskCompletion.taskLabel || taskCompletion.statusLabel),
|
|
420
|
-
data: taskCompletion,
|
|
414
|
+
data: archivedAttachments.length > 0 ? { ...taskCompletion, attachments: archivedAttachments } : taskCompletion,
|
|
421
415
|
});
|
|
422
416
|
await this.telemetry.reportReply({
|
|
423
417
|
content,
|
|
424
418
|
sessionId,
|
|
419
|
+
attachments: archivedAttachments.length > 0 ? archivedAttachments : taskCompletion?.attachments,
|
|
425
420
|
source: 'agent_event_task_completion',
|
|
426
421
|
msgId: taskCompletion.childSessionId ? `dragon_task_reply_${taskCompletion.childSessionId}` : undefined,
|
|
427
422
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { IComponent } from "../../core/IComponent.js";
|
|
2
|
+
type LoggerLike = {
|
|
3
|
+
info?: (...args: any[]) => void;
|
|
4
|
+
warn?: (...args: any[]) => void;
|
|
5
|
+
error?: (...args: any[]) => void;
|
|
6
|
+
};
|
|
7
|
+
export type DragonMediaAttachment = {
|
|
8
|
+
type: string;
|
|
9
|
+
kind: string;
|
|
10
|
+
name: string;
|
|
11
|
+
mimeType?: string;
|
|
12
|
+
sourcePath?: string;
|
|
13
|
+
workspacePath?: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
url?: string;
|
|
16
|
+
size?: number;
|
|
17
|
+
};
|
|
18
|
+
export declare class MediaRegistryComponent implements IComponent {
|
|
19
|
+
private options;
|
|
20
|
+
constructor(options: {
|
|
21
|
+
cfg: any;
|
|
22
|
+
logger?: LoggerLike;
|
|
23
|
+
});
|
|
24
|
+
start(): Promise<void>;
|
|
25
|
+
stop(): Promise<void>;
|
|
26
|
+
private resolveWorkspaceRoot;
|
|
27
|
+
private normalizeLocalPath;
|
|
28
|
+
private isPathInside;
|
|
29
|
+
private inferMime;
|
|
30
|
+
private inferKind;
|
|
31
|
+
private buildWorkspaceRelativePath;
|
|
32
|
+
private collectStructuredMedia;
|
|
33
|
+
archiveStructuredMedia(value: any): Promise<DragonMediaAttachment[]>;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class MediaRegistryComponent {
|
|
4
|
+
options;
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
}
|
|
8
|
+
async start() { }
|
|
9
|
+
async stop() { }
|
|
10
|
+
resolveWorkspaceRoot() {
|
|
11
|
+
const candidates = [
|
|
12
|
+
this.options.cfg?.agents?.defaults?.workspace,
|
|
13
|
+
this.options.cfg?.agents?.default?.workspace,
|
|
14
|
+
...(Array.isArray(this.options.cfg?.agents?.list)
|
|
15
|
+
? this.options.cfg.agents.list.map((agent) => agent?.workspace)
|
|
16
|
+
: []),
|
|
17
|
+
process.env.OPENCLAW_WORKSPACE_ROOT,
|
|
18
|
+
process.env.DRAGON_OPENCLAW_WORKSPACE_ROOT,
|
|
19
|
+
];
|
|
20
|
+
const found = candidates.find((value) => typeof value === "string" && value.trim());
|
|
21
|
+
return found ? path.resolve(String(found).trim()) : undefined;
|
|
22
|
+
}
|
|
23
|
+
normalizeLocalPath(value) {
|
|
24
|
+
if (typeof value !== "string")
|
|
25
|
+
return undefined;
|
|
26
|
+
const raw = value.trim();
|
|
27
|
+
if (!raw)
|
|
28
|
+
return undefined;
|
|
29
|
+
if (raw.startsWith("file://")) {
|
|
30
|
+
try {
|
|
31
|
+
return path.resolve(decodeURIComponent(new URL(raw).pathname));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!path.isAbsolute(raw))
|
|
38
|
+
return undefined;
|
|
39
|
+
return path.resolve(raw);
|
|
40
|
+
}
|
|
41
|
+
isPathInside(parent, child) {
|
|
42
|
+
const rel = path.relative(parent, child);
|
|
43
|
+
return rel === "" || (!!rel && !rel.startsWith("..") && !path.isAbsolute(rel));
|
|
44
|
+
}
|
|
45
|
+
inferMime(filePath, fallback) {
|
|
46
|
+
if (fallback)
|
|
47
|
+
return fallback;
|
|
48
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
49
|
+
if (ext === ".png")
|
|
50
|
+
return "image/png";
|
|
51
|
+
if (ext === ".jpg" || ext === ".jpeg")
|
|
52
|
+
return "image/jpeg";
|
|
53
|
+
if (ext === ".webp")
|
|
54
|
+
return "image/webp";
|
|
55
|
+
if (ext === ".gif")
|
|
56
|
+
return "image/gif";
|
|
57
|
+
if (ext === ".svg")
|
|
58
|
+
return "image/svg+xml";
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
inferKind(mimeType) {
|
|
62
|
+
if (mimeType?.startsWith("image/"))
|
|
63
|
+
return "image";
|
|
64
|
+
if (mimeType?.startsWith("video/"))
|
|
65
|
+
return "video";
|
|
66
|
+
if (mimeType?.startsWith("audio/"))
|
|
67
|
+
return "audio";
|
|
68
|
+
return "file";
|
|
69
|
+
}
|
|
70
|
+
buildWorkspaceRelativePath(sourcePath, workspaceRoot) {
|
|
71
|
+
const normalized = path.resolve(sourcePath);
|
|
72
|
+
if (this.isPathInside(workspaceRoot, normalized)) {
|
|
73
|
+
return path.relative(workspaceRoot, normalized).split(path.sep).join("/");
|
|
74
|
+
}
|
|
75
|
+
const marker = `${path.sep}media${path.sep}`;
|
|
76
|
+
const mediaIndex = normalized.lastIndexOf(marker);
|
|
77
|
+
if (mediaIndex >= 0) {
|
|
78
|
+
return path
|
|
79
|
+
.join("media", normalized.slice(mediaIndex + marker.length))
|
|
80
|
+
.split(path.sep)
|
|
81
|
+
.join("/");
|
|
82
|
+
}
|
|
83
|
+
return path.join("media", path.basename(normalized)).split(path.sep).join("/");
|
|
84
|
+
}
|
|
85
|
+
collectStructuredMedia(value) {
|
|
86
|
+
const items = [];
|
|
87
|
+
if (!value || typeof value !== "object")
|
|
88
|
+
return items;
|
|
89
|
+
if (Array.isArray(value.attachments))
|
|
90
|
+
items.push(...value.attachments);
|
|
91
|
+
if (Array.isArray(value.media))
|
|
92
|
+
items.push(...value.media);
|
|
93
|
+
if (Array.isArray(value.mediaUrls)) {
|
|
94
|
+
items.push(...value.mediaUrls.map((url) => ({ url })));
|
|
95
|
+
}
|
|
96
|
+
return items.filter(Boolean);
|
|
97
|
+
}
|
|
98
|
+
async archiveStructuredMedia(value) {
|
|
99
|
+
const workspaceRoot = this.resolveWorkspaceRoot();
|
|
100
|
+
if (!workspaceRoot)
|
|
101
|
+
return [];
|
|
102
|
+
const media = this.collectStructuredMedia(value);
|
|
103
|
+
const seen = new Set();
|
|
104
|
+
const attachments = [];
|
|
105
|
+
for (const item of media) {
|
|
106
|
+
const sourcePath = this.normalizeLocalPath(item?.path ?? item?.filePath ?? item?.mediaPath ?? item?.url);
|
|
107
|
+
if (!sourcePath || seen.has(sourcePath))
|
|
108
|
+
continue;
|
|
109
|
+
seen.add(sourcePath);
|
|
110
|
+
try {
|
|
111
|
+
const stat = await fs.stat(sourcePath);
|
|
112
|
+
if (!stat.isFile())
|
|
113
|
+
continue;
|
|
114
|
+
const workspacePath = this.buildWorkspaceRelativePath(sourcePath, workspaceRoot);
|
|
115
|
+
const targetPath = path.resolve(workspaceRoot, workspacePath);
|
|
116
|
+
if (!this.isPathInside(workspaceRoot, targetPath))
|
|
117
|
+
continue;
|
|
118
|
+
if (path.resolve(sourcePath) !== targetPath) {
|
|
119
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
120
|
+
await fs.copyFile(sourcePath, targetPath);
|
|
121
|
+
}
|
|
122
|
+
const mimeType = this.inferMime(targetPath, item?.mimeType || item?.mime);
|
|
123
|
+
const kind = item?.kind || item?.type || this.inferKind(mimeType);
|
|
124
|
+
attachments.push({
|
|
125
|
+
type: kind,
|
|
126
|
+
kind,
|
|
127
|
+
name: item?.name || path.basename(targetPath),
|
|
128
|
+
mimeType,
|
|
129
|
+
sourcePath,
|
|
130
|
+
workspacePath,
|
|
131
|
+
path: workspacePath,
|
|
132
|
+
size: stat.size,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
this.options.logger?.warn?.(`[dragon channels][media] failed to archive media: source=${sourcePath}, error=${error?.message || error}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return attachments;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -15,7 +15,7 @@ export class TelemetryComponent {
|
|
|
15
15
|
const sessionId = payload.sessionId || "default";
|
|
16
16
|
const content = payload.content || "";
|
|
17
17
|
const normalized = content.replace(/\s+/g, "");
|
|
18
|
-
if (normalized) {
|
|
18
|
+
if (normalized && !payload.attachments?.length) {
|
|
19
19
|
const key = `${source}_${sessionId}`;
|
|
20
20
|
const last = this.lastReplies.get(key);
|
|
21
21
|
const now = Date.now();
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { TelemetryComponent } from "./components/telemetry/TelemetryComponent.js
|
|
|
6
6
|
import { HttpComponent } from "./components/http/HttpComponent.js";
|
|
7
7
|
import { ChannelComponent } from "./components/channel/ChannelComponent.js";
|
|
8
8
|
import { SseComponent } from "./components/sync/SseComponent.js";
|
|
9
|
+
import { MediaRegistryComponent } from "./components/media/MediaRegistryComponent.js";
|
|
9
10
|
import { dragonChannelPluginVersion } from "./version.js";
|
|
10
11
|
const channelId = "dragon";
|
|
11
12
|
const workbenchTargetPrefix = "dragon-workbench:";
|
|
@@ -62,6 +63,10 @@ async function getOrCreateContainer(account, ctx) {
|
|
|
62
63
|
logger
|
|
63
64
|
}));
|
|
64
65
|
const telemetry = container.register('telemetry', new TelemetryComponent(http, account.agentId));
|
|
66
|
+
const mediaRegistry = container.register('mediaRegistry', new MediaRegistryComponent({
|
|
67
|
+
cfg: ctx.cfg,
|
|
68
|
+
logger,
|
|
69
|
+
}));
|
|
65
70
|
// 2. Channel Logic (Centralized)
|
|
66
71
|
const channel = container.register('channel', new ChannelComponent({
|
|
67
72
|
accountId: account.accountId,
|
|
@@ -69,7 +74,7 @@ async function getOrCreateContainer(account, ctx) {
|
|
|
69
74
|
channelRuntime: ctx.channelRuntime,
|
|
70
75
|
cfg: ctx.cfg,
|
|
71
76
|
logger
|
|
72
|
-
}, telemetry));
|
|
77
|
+
}, telemetry, mediaRegistry));
|
|
73
78
|
// 3. Sync Infrastructure
|
|
74
79
|
container.register('sse', new SseComponent(http, channel, {
|
|
75
80
|
agentId: account.agentId,
|
|
@@ -248,54 +253,21 @@ const plugin = createChatChannelPlugin({
|
|
|
248
253
|
};
|
|
249
254
|
}
|
|
250
255
|
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction target resolved: sessionId=${sessionId}, target=${target}, rawTarget=${rawTarget}`);
|
|
251
|
-
// Collect ALL media URLs from the params (mirrors what OpenClaw tracks internally
|
|
252
|
-
// via collectMessagingMediaUrlsFromRecord when the tool call starts, so that
|
|
253
|
-
// hasGatewayAgentDeliveredExpectedMedia can match them against expectedMediaUrls).
|
|
254
|
-
const sentMediaUrls = [];
|
|
255
|
-
const pushMedia = (v, source) => {
|
|
256
|
-
if (typeof v === 'string' && v.trim()) {
|
|
257
|
-
const url = v.trim();
|
|
258
|
-
sentMediaUrls.push(url);
|
|
259
|
-
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction extracted media URL from ${source}: ${url}`);
|
|
260
|
-
}
|
|
261
|
-
};
|
|
262
|
-
pushMedia(params.media, "params.media");
|
|
263
|
-
pushMedia(params.mediaUrl, "params.mediaUrl");
|
|
264
|
-
pushMedia(params.path, "params.path");
|
|
265
|
-
pushMedia(params.filePath, "params.filePath");
|
|
266
|
-
pushMedia(params.fileUrl, "params.fileUrl");
|
|
267
|
-
if (Array.isArray(params.mediaUrls)) {
|
|
268
|
-
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction found params.mediaUrls array, size=${params.mediaUrls.length}`);
|
|
269
|
-
for (const u of params.mediaUrls)
|
|
270
|
-
pushMedia(u, "params.mediaUrls");
|
|
271
|
-
}
|
|
272
|
-
if (Array.isArray(params.attachments)) {
|
|
273
|
-
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction found params.attachments array, size=${params.attachments.length}`);
|
|
274
|
-
for (const a of params.attachments) {
|
|
275
|
-
if (a && typeof a === 'object') {
|
|
276
|
-
pushMedia(a.media, "attachment.media");
|
|
277
|
-
pushMedia(a.mediaUrl, "attachment.mediaUrl");
|
|
278
|
-
pushMedia(a.path, "attachment.path");
|
|
279
|
-
pushMedia(a.filePath, "attachment.filePath");
|
|
280
|
-
pushMedia(a.fileUrl, "attachment.fileUrl");
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction total media URLs extracted: count=${sentMediaUrls.length}, urls=${JSON.stringify(sentMediaUrls)}`);
|
|
285
256
|
let text = params.message || params.text || "";
|
|
286
257
|
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction base message text length: ${text.length}`);
|
|
287
|
-
// Append media path to text so the Workbench can render the image
|
|
288
|
-
for (const url of sentMediaUrls) {
|
|
289
|
-
const mediaLine = `MEDIA: ${url}`;
|
|
290
|
-
if (!text.includes(mediaLine)) {
|
|
291
|
-
text = text ? `${text}\n${mediaLine}` : mediaLine;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
258
|
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction sending text over channel: text_len=${text.length}, targetSession=${sessionId}`);
|
|
295
259
|
const result = await channel.handleOutboundText({
|
|
296
260
|
text,
|
|
297
|
-
peer: { id: target }
|
|
261
|
+
peer: { id: target },
|
|
262
|
+
attachments: params.attachments,
|
|
263
|
+
media: params.media,
|
|
264
|
+
mediaUrls: params.mediaUrls,
|
|
298
265
|
});
|
|
266
|
+
const deliveredMediaUrls = Array.isArray(result.attachments)
|
|
267
|
+
? result.attachments
|
|
268
|
+
.map((attachment) => attachment?.sourcePath || attachment?.path || attachment?.url)
|
|
269
|
+
.filter((value) => typeof value === 'string' && value.trim())
|
|
270
|
+
: (Array.isArray(params.mediaUrls) ? params.mediaUrls : []);
|
|
299
271
|
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction channel send result: ok=${result.ok}, messageId=${result.messageId || "none"}`);
|
|
300
272
|
const evidence = {
|
|
301
273
|
ok: result.ok,
|
|
@@ -304,8 +276,8 @@ const plugin = createChatChannelPlugin({
|
|
|
304
276
|
// and hasGatewayAgentDeliveredExpectedMedia
|
|
305
277
|
didSendViaMessagingTool: true,
|
|
306
278
|
messagingToolSentTexts: text ? [text] : [],
|
|
307
|
-
messagingToolSentMediaUrls:
|
|
308
|
-
messagingToolSentTargets: [{ to: target, ...(
|
|
279
|
+
messagingToolSentMediaUrls: deliveredMediaUrls,
|
|
280
|
+
messagingToolSentTargets: [{ to: target, ...(deliveredMediaUrls.length > 0 ? { mediaUrls: deliveredMediaUrls } : {}) }],
|
|
309
281
|
};
|
|
310
282
|
logger?.info?.(`[dragon channels][Dragon Plugin] handleAction returning evidence to OpenClaw: didSendViaMessagingTool=${evidence.didSendViaMessagingTool}, sentMediaUrlsCount=${evidence.messagingToolSentMediaUrls.length}`);
|
|
311
283
|
return evidence;
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const dragonChannelPluginVersion = "0.5.
|
|
1
|
+
export declare const dragonChannelPluginVersion = "0.5.37";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const dragonChannelPluginVersion = "0.5.
|
|
1
|
+
export const dragonChannelPluginVersion = "0.5.37";
|