openclaw-elys 1.8.0 → 1.8.2
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/src/monitor.js +78 -12
- package/dist/src/mqtt-client.d.ts +1 -1
- package/dist/src/mqtt-client.js +18 -4
- package/package.json +1 -1
package/dist/src/monitor.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { createWriteStream } from "node:fs";
|
|
2
|
+
import { mkdtemp } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { pipeline } from "node:stream/promises";
|
|
1
6
|
import { loadCredentials } from "./config.js";
|
|
2
7
|
import { registerDevice } from "./register.js";
|
|
3
8
|
import { ElysDeviceMQTTClient } from "./mqtt-client.js";
|
|
@@ -39,15 +44,25 @@ export async function monitorElysProvider(opts) {
|
|
|
39
44
|
try {
|
|
40
45
|
let seq = 0;
|
|
41
46
|
let fullText = "";
|
|
42
|
-
//
|
|
43
|
-
|
|
47
|
+
// Download inbound media (user-sent) to local temp files
|
|
48
|
+
// OpenClaw expects local file paths in MediaPath/MediaUrl, not remote URLs
|
|
49
|
+
const rawMediaUrls = cmd.mediaUrls?.length
|
|
44
50
|
? cmd.mediaUrls
|
|
45
51
|
: cmd.mediaUrl
|
|
46
52
|
? [cmd.mediaUrl]
|
|
47
|
-
:
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
: [];
|
|
54
|
+
const downloadedPaths = [];
|
|
55
|
+
const downloadedTypes = [];
|
|
56
|
+
for (const url of rawMediaUrls) {
|
|
57
|
+
try {
|
|
58
|
+
const localPath = await downloadToTemp(url, log);
|
|
59
|
+
downloadedPaths.push(localPath);
|
|
60
|
+
downloadedTypes.push(cmd.mediaType ?? guessMediaType(url));
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
log(`[elys] failed to download media ${url}:`, err);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
51
66
|
const inboundCtx = finalizeCtx({
|
|
52
67
|
Body: formatCommandAsText(cmd),
|
|
53
68
|
BodyForAgent: formatCommandAsText(cmd),
|
|
@@ -63,12 +78,14 @@ export async function monitorElysProvider(opts) {
|
|
|
63
78
|
CommandAuthorized: true,
|
|
64
79
|
OriginatingChannel: "elys",
|
|
65
80
|
OriginatingTo: credentials.deviceId,
|
|
66
|
-
// Inbound media
|
|
67
|
-
...(
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
81
|
+
// Inbound media as local file paths
|
|
82
|
+
...(downloadedPaths.length > 0 && {
|
|
83
|
+
MediaPath: downloadedPaths[0],
|
|
84
|
+
MediaUrl: downloadedPaths[0],
|
|
85
|
+
MediaPaths: downloadedPaths,
|
|
86
|
+
MediaUrls: downloadedPaths,
|
|
87
|
+
MediaType: downloadedTypes[0],
|
|
88
|
+
MediaTypes: downloadedTypes,
|
|
72
89
|
}),
|
|
73
90
|
});
|
|
74
91
|
// Deliver callback: stream chunks back via MQTT
|
|
@@ -174,3 +191,52 @@ function formatCommandAsText(cmd) {
|
|
|
174
191
|
}
|
|
175
192
|
return parts.join(" ");
|
|
176
193
|
}
|
|
194
|
+
const MIME_BY_EXT = {
|
|
195
|
+
".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png",
|
|
196
|
+
".gif": "image/gif", ".webp": "image/webp", ".bmp": "image/bmp",
|
|
197
|
+
".mp4": "video/mp4", ".mov": "video/quicktime", ".avi": "video/x-msvideo",
|
|
198
|
+
".mp3": "audio/mpeg", ".ogg": "audio/ogg", ".wav": "audio/wav",
|
|
199
|
+
".pdf": "application/pdf",
|
|
200
|
+
};
|
|
201
|
+
function guessMediaType(url) {
|
|
202
|
+
const pathname = new URL(url).pathname.toLowerCase();
|
|
203
|
+
for (const [ext, mime] of Object.entries(MIME_BY_EXT)) {
|
|
204
|
+
if (pathname.includes(ext))
|
|
205
|
+
return mime;
|
|
206
|
+
}
|
|
207
|
+
return "application/octet-stream";
|
|
208
|
+
}
|
|
209
|
+
function extFromMimeOrUrl(url, mime) {
|
|
210
|
+
for (const [ext, m] of Object.entries(MIME_BY_EXT)) {
|
|
211
|
+
if (m === mime)
|
|
212
|
+
return ext;
|
|
213
|
+
}
|
|
214
|
+
const pathname = new URL(url).pathname;
|
|
215
|
+
const dot = pathname.lastIndexOf(".");
|
|
216
|
+
if (dot >= 0) {
|
|
217
|
+
const ext = pathname.slice(dot).split(/[?#!/]/)[0];
|
|
218
|
+
if (ext.length <= 6)
|
|
219
|
+
return ext;
|
|
220
|
+
}
|
|
221
|
+
return ".bin";
|
|
222
|
+
}
|
|
223
|
+
let tempDir = null;
|
|
224
|
+
async function downloadToTemp(url, log) {
|
|
225
|
+
if (!tempDir) {
|
|
226
|
+
tempDir = await mkdtemp(join(tmpdir(), "elys-media-"));
|
|
227
|
+
}
|
|
228
|
+
const mime = guessMediaType(url);
|
|
229
|
+
const ext = extFromMimeOrUrl(url, mime);
|
|
230
|
+
const filename = `media_${Date.now()}${ext}`;
|
|
231
|
+
const filePath = join(tempDir, filename);
|
|
232
|
+
log(`[elys] downloading media: ${url} → ${filePath}`);
|
|
233
|
+
const resp = await fetch(url);
|
|
234
|
+
if (!resp.ok || !resp.body) {
|
|
235
|
+
throw new Error(`HTTP ${resp.status} downloading ${url}`);
|
|
236
|
+
}
|
|
237
|
+
const { Readable } = await import("node:stream");
|
|
238
|
+
const nodeStream = Readable.fromWeb(resp.body);
|
|
239
|
+
await pipeline(nodeStream, createWriteStream(filePath));
|
|
240
|
+
log(`[elys] downloaded media: ${filePath}`);
|
|
241
|
+
return filePath;
|
|
242
|
+
}
|
|
@@ -45,7 +45,7 @@ export declare class ElysDeviceMQTTClient {
|
|
|
45
45
|
private flushDebounce;
|
|
46
46
|
private startCommand;
|
|
47
47
|
private processPendingCommands;
|
|
48
|
-
/** Merge multiple buffered commands into one. Joins text args with newline. */
|
|
48
|
+
/** Merge multiple buffered commands into one. Joins text args with newline, collects all media. */
|
|
49
49
|
private mergeCommands;
|
|
50
50
|
private executeCommand;
|
|
51
51
|
private cleanupDedup;
|
package/dist/src/mqtt-client.js
CHANGED
|
@@ -208,7 +208,7 @@ export class ElysDeviceMQTTClient {
|
|
|
208
208
|
this.log(`[elys] processing queued command ${merged.id}`);
|
|
209
209
|
this.startCommand(merged);
|
|
210
210
|
}
|
|
211
|
-
/** Merge multiple buffered commands into one. Joins text args with newline. */
|
|
211
|
+
/** Merge multiple buffered commands into one. Joins text args with newline, collects all media. */
|
|
212
212
|
mergeCommands(cmds) {
|
|
213
213
|
if (cmds.length === 1)
|
|
214
214
|
return cmds[0];
|
|
@@ -216,11 +216,25 @@ export class ElysDeviceMQTTClient {
|
|
|
216
216
|
const texts = cmds
|
|
217
217
|
.map((c) => c.args?.text ?? "")
|
|
218
218
|
.filter(Boolean);
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
// Collect all media URLs from all commands
|
|
220
|
+
const allMediaUrls = [];
|
|
221
|
+
for (const c of cmds) {
|
|
222
|
+
if (c.mediaUrls?.length) {
|
|
223
|
+
allMediaUrls.push(...c.mediaUrls);
|
|
224
|
+
}
|
|
225
|
+
else if (c.mediaUrl) {
|
|
226
|
+
allMediaUrls.push(c.mediaUrl);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const mergedText = texts.length > 1 ? texts.join("\n") : texts[0] ?? "";
|
|
230
|
+
if (texts.length > 1 || allMediaUrls.length > 0) {
|
|
231
|
+
this.log(`[elys] debounce: merged ${cmds.length} commands (${texts.length} texts, ${allMediaUrls.length} media)`);
|
|
221
232
|
return {
|
|
222
233
|
...last,
|
|
223
|
-
args: { ...last.args, text:
|
|
234
|
+
args: { ...last.args, ...(mergedText && { text: mergedText }) },
|
|
235
|
+
mediaUrl: allMediaUrls[0] ?? last.mediaUrl,
|
|
236
|
+
mediaUrls: allMediaUrls.length > 0 ? allMediaUrls : undefined,
|
|
237
|
+
mediaType: last.mediaType,
|
|
224
238
|
};
|
|
225
239
|
}
|
|
226
240
|
// Can't merge non-text commands, use the latest one
|