clawgram 2.29.1 → 2.29.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/actions-read.js +1 -0
- package/dist/attachments.js +1 -0
- package/dist/media.js +76 -7
- package/npm-shrinkwrap.json +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/actions-read.js
CHANGED
|
@@ -132,6 +132,7 @@ async function handleReadAction(ctx) {
|
|
|
132
132
|
? node_path_1.default.join((0, state_dir_1.resolveStateDir)(), "tmp")
|
|
133
133
|
: node_path_1.default.join(node_os_1.default.tmpdir(), `clawgram-${typeof process.getuid === "function" ? process.getuid() : "user"}`);
|
|
134
134
|
await (0, media_1.ensurePrivateDir)(mediaRoot);
|
|
135
|
+
await (0, media_1.sweepOrphanMediaDirs)(mediaRoot, Date.now());
|
|
135
136
|
const sharedFetchDir = node_path_1.default.join(mediaRoot, "clawgram-fetched");
|
|
136
137
|
const described = (0, media_1.describeMedia)(found.message?.media);
|
|
137
138
|
// A PDF is not read here: the agent is handed the path for its PDF tool,
|
package/dist/attachments.js
CHANGED
|
@@ -81,6 +81,7 @@ async function readInboundAttachment(params) {
|
|
|
81
81
|
if (!media || !message) {
|
|
82
82
|
return undefined;
|
|
83
83
|
}
|
|
84
|
+
await (0, media_1.sweepOrphanMediaDirs)(node_os_1.default.tmpdir(), Date.now());
|
|
84
85
|
let downloaded;
|
|
85
86
|
try {
|
|
86
87
|
downloaded = await (0, media_1.downloadInboundMediaToTempFile)({
|
package/dist/media.js
CHANGED
|
@@ -15,6 +15,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
15
15
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
16
16
|
};
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.ORPHAN_MEDIA_DIR_TTL_MS = void 0;
|
|
18
19
|
exports.describeMedia = describeMedia;
|
|
19
20
|
exports.inboundMediaUnderstanding = inboundMediaUnderstanding;
|
|
20
21
|
exports.fetchMediaUnderstanding = fetchMediaUnderstanding;
|
|
@@ -23,6 +24,8 @@ exports.describePrivateDirProblem = describePrivateDirProblem;
|
|
|
23
24
|
exports.ensurePrivateDir = ensurePrivateDir;
|
|
24
25
|
exports.downloadMessageMediaToFile = downloadMessageMediaToFile;
|
|
25
26
|
exports.pruneFetchedMedia = pruneFetchedMedia;
|
|
27
|
+
exports.pruneOrphanMediaDirs = pruneOrphanMediaDirs;
|
|
28
|
+
exports.sweepOrphanMediaDirs = sweepOrphanMediaDirs;
|
|
26
29
|
exports.isLocalMediaPath = isLocalMediaPath;
|
|
27
30
|
exports.assertLocalMediaWithinRoots = assertLocalMediaWithinRoots;
|
|
28
31
|
exports.loadOutboundMedia = loadOutboundMedia;
|
|
@@ -167,17 +170,32 @@ async function downloadInboundMediaToTempFile(params) {
|
|
|
167
170
|
return undefined;
|
|
168
171
|
}
|
|
169
172
|
const dir = await mkdtemp(join(params.tmpDir, "clawgram-media-"));
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
173
|
+
// The caller removes only a path it was handed. Every other way out — an
|
|
174
|
+
// empty download, a document, a throw — removes the directory here, or it
|
|
175
|
+
// stays in the temp directory for good (audit r3 V1-10, the inbound path).
|
|
176
|
+
const discard = async () => {
|
|
177
|
+
const { rm } = await import("node:fs/promises");
|
|
178
|
+
await rm(dir, { recursive: true, force: true }).catch(() => undefined);
|
|
179
|
+
};
|
|
180
|
+
let downloaded;
|
|
181
|
+
try {
|
|
182
|
+
downloaded = await downloadMessageMediaToFile({
|
|
183
|
+
client: params.client,
|
|
184
|
+
message: params.message,
|
|
185
|
+
maxBytes: params.maxBytes,
|
|
186
|
+
dir,
|
|
187
|
+
fileNameFor: ({ extension }) => `attachment.${extension}`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
await discard();
|
|
192
|
+
throw err;
|
|
193
|
+
}
|
|
177
194
|
// This path asks only for `inboundMediaUnderstanding`, so a document cannot
|
|
178
195
|
// get here. Keep the runtime guard as the public downloader also serves the
|
|
179
196
|
// explicit `fetch-media` action, which is allowed to request DOCX files.
|
|
180
197
|
if (!downloaded || downloaded.understanding === "document" || downloaded.understanding === "pdf") {
|
|
198
|
+
await discard();
|
|
181
199
|
return undefined;
|
|
182
200
|
}
|
|
183
201
|
return {
|
|
@@ -335,6 +353,57 @@ async function pruneFetchedMedia(dir, maxAgeMs, now) {
|
|
|
335
353
|
}
|
|
336
354
|
return removed;
|
|
337
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* A private `clawgram-media-*` directory lives for one call: the inbound
|
|
358
|
+
* reader and `fetch-media mode=read` remove it when they are done. One that is
|
|
359
|
+
* an hour old was left by a crash, or by a version before 2.29.1, which kept a
|
|
360
|
+
* directory on every error branch and every PDF read (audit r3 C2-02, V1-10).
|
|
361
|
+
*/
|
|
362
|
+
exports.ORPHAN_MEDIA_DIR_TTL_MS = 60 * 60 * 1000;
|
|
363
|
+
const ORPHAN_SWEEP_EVERY_MS = 10 * 60 * 1000;
|
|
364
|
+
const lastOrphanSweep = new Map();
|
|
365
|
+
/** Removes `clawgram-media-*` directories in `root` older than `maxAgeMs`. */
|
|
366
|
+
async function pruneOrphanMediaDirs(root, maxAgeMs, now) {
|
|
367
|
+
const { readdir, stat, rm } = await import("node:fs/promises");
|
|
368
|
+
const { join } = await import("node:path");
|
|
369
|
+
let entries;
|
|
370
|
+
try {
|
|
371
|
+
entries = await readdir(root);
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
return 0;
|
|
375
|
+
}
|
|
376
|
+
let removed = 0;
|
|
377
|
+
for (const entry of entries) {
|
|
378
|
+
if (!entry.startsWith("clawgram-media-"))
|
|
379
|
+
continue;
|
|
380
|
+
const path = join(root, entry);
|
|
381
|
+
try {
|
|
382
|
+
const info = await stat(path);
|
|
383
|
+
// In a shared /tmp another account's directory fails `rm` on the
|
|
384
|
+
// sticky bit and is skipped; only our own leftovers go.
|
|
385
|
+
if (info.isDirectory() && now - info.mtimeMs > maxAgeMs) {
|
|
386
|
+
await rm(path, { recursive: true, force: true });
|
|
387
|
+
removed += 1;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
// Gone already, or not ours.
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return removed;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* `pruneOrphanMediaDirs` at most once per ten minutes per root: it reads the
|
|
398
|
+
* whole temp directory, and it runs on the path of every inbound attachment.
|
|
399
|
+
*/
|
|
400
|
+
async function sweepOrphanMediaDirs(root, now) {
|
|
401
|
+
const last = lastOrphanSweep.get(root);
|
|
402
|
+
if (last !== undefined && now - last < ORPHAN_SWEEP_EVERY_MS)
|
|
403
|
+
return 0;
|
|
404
|
+
lastOrphanSweep.set(root, now);
|
|
405
|
+
return pruneOrphanMediaDirs(root, exports.ORPHAN_MEDIA_DIR_TTL_MS, now).catch(() => 0);
|
|
406
|
+
}
|
|
338
407
|
function extensionFor(media, understanding) {
|
|
339
408
|
if (understanding === "description") {
|
|
340
409
|
if (media.mimeType === "image/png")
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawgram",
|
|
3
|
-
"version": "2.29.
|
|
3
|
+
"version": "2.29.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "clawgram",
|
|
9
|
-
"version": "2.29.
|
|
9
|
+
"version": "2.29.2",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"json5": "2.2.3",
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "clawgram",
|
|
3
3
|
"name": "Clawgram",
|
|
4
4
|
"description": "Clawgram — personal Telegram (MTProto userbot) channel for OpenClaw. Your AI assistant reads and responds as you.",
|
|
5
|
-
"version": "2.29.
|
|
5
|
+
"version": "2.29.2",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
package/package.json
CHANGED