@dai_ming/plugin-deliverables 1.1.9 → 1.2.1
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/index.js +54 -43
- package/openclaw-plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -308,28 +308,28 @@ function shouldIgnorePath(candidatePath) {
|
|
|
308
308
|
return IGNORED_FILE_NAMES.has(path.basename(normalized));
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
-
function safeStat(candidatePath) {
|
|
311
|
+
async function safeStat(candidatePath) {
|
|
312
312
|
try {
|
|
313
|
-
return fs.
|
|
313
|
+
return await fs.promises.stat(candidatePath);
|
|
314
314
|
} catch (_err) {
|
|
315
315
|
return null;
|
|
316
316
|
}
|
|
317
317
|
}
|
|
318
318
|
|
|
319
|
-
function workspaceRoots() {
|
|
319
|
+
async function workspaceRoots() {
|
|
320
320
|
const roots = [];
|
|
321
|
-
["/home/node/.openclaw/workspace-main", "/home/node/.openclaw/workspace"]
|
|
322
|
-
if (safeStat(root)) {
|
|
321
|
+
for (const root of ["/home/node/.openclaw/workspace-main", "/home/node/.openclaw/workspace"]) {
|
|
322
|
+
if (await safeStat(root)) {
|
|
323
323
|
roots.push(root);
|
|
324
324
|
}
|
|
325
|
-
}
|
|
325
|
+
}
|
|
326
326
|
try {
|
|
327
|
-
for (const entry of fs.
|
|
327
|
+
for (const entry of await fs.promises.readdir("/data")) {
|
|
328
328
|
if (!entry || entry.indexOf("workspace-") !== 0) {
|
|
329
329
|
continue;
|
|
330
330
|
}
|
|
331
331
|
const root = path.join("/data", entry);
|
|
332
|
-
if (safeStat(root)) {
|
|
332
|
+
if (await safeStat(root)) {
|
|
333
333
|
roots.push(root);
|
|
334
334
|
}
|
|
335
335
|
}
|
|
@@ -338,12 +338,12 @@ function workspaceRoots() {
|
|
|
338
338
|
}
|
|
339
339
|
const openclawRoot = path.resolve(__dirname, "..", "..");
|
|
340
340
|
try {
|
|
341
|
-
for (const entry of fs.
|
|
341
|
+
for (const entry of await fs.promises.readdir(openclawRoot)) {
|
|
342
342
|
if (!entry || entry.indexOf("workspace") !== 0) {
|
|
343
343
|
continue;
|
|
344
344
|
}
|
|
345
345
|
const root = path.join(openclawRoot, entry);
|
|
346
|
-
if (safeStat(root) && roots.indexOf(root) === -1) {
|
|
346
|
+
if ((await safeStat(root)) && roots.indexOf(root) === -1) {
|
|
347
347
|
roots.push(root);
|
|
348
348
|
}
|
|
349
349
|
}
|
|
@@ -364,12 +364,12 @@ function resolveCandidatePath(rawPath) {
|
|
|
364
364
|
return cleaned;
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
-
function findFileByRelativeOrBaseName(rawRef) {
|
|
367
|
+
async function findFileByRelativeOrBaseName(rawRef) {
|
|
368
368
|
const cleaned = stripTrailingPathPunctuation(trimString(rawRef)).replace(/^\.\//, "");
|
|
369
369
|
if (!cleaned || isURLLike(cleaned) || !hasFileExtension(cleaned)) {
|
|
370
370
|
return "";
|
|
371
371
|
}
|
|
372
|
-
const roots = workspaceRoots();
|
|
372
|
+
const roots = await workspaceRoots();
|
|
373
373
|
const directCandidates = [];
|
|
374
374
|
for (const root of roots) {
|
|
375
375
|
directCandidates.push(path.join(root, cleaned));
|
|
@@ -378,7 +378,7 @@ function findFileByRelativeOrBaseName(rawRef) {
|
|
|
378
378
|
directCandidates.push(path.join(root, "output", path.basename(cleaned)));
|
|
379
379
|
}
|
|
380
380
|
for (const candidate of directCandidates) {
|
|
381
|
-
const stat = safeStat(candidate);
|
|
381
|
+
const stat = await safeStat(candidate);
|
|
382
382
|
if (stat && (stat.isFile() || stat.isDirectory()) && isAllowedWorkspacePath(candidate) && !shouldIgnorePath(candidate)) {
|
|
383
383
|
return path.resolve(candidate);
|
|
384
384
|
}
|
|
@@ -387,13 +387,13 @@ function findFileByRelativeOrBaseName(rawRef) {
|
|
|
387
387
|
const basename = path.basename(cleaned);
|
|
388
388
|
const found = [];
|
|
389
389
|
let visited = 0;
|
|
390
|
-
function scan(dir, depth) {
|
|
390
|
+
async function scan(dir, depth) {
|
|
391
391
|
if (depth < 0 || visited > AUTO_UPLOAD_SCAN_MAX_ENTRIES || shouldIgnorePath(dir)) {
|
|
392
392
|
return;
|
|
393
393
|
}
|
|
394
394
|
let entries;
|
|
395
395
|
try {
|
|
396
|
-
entries = fs.
|
|
396
|
+
entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
397
397
|
} catch (_err) {
|
|
398
398
|
return;
|
|
399
399
|
}
|
|
@@ -407,11 +407,11 @@ function findFileByRelativeOrBaseName(rawRef) {
|
|
|
407
407
|
continue;
|
|
408
408
|
}
|
|
409
409
|
if (entry.isDirectory()) {
|
|
410
|
-
scan(full, depth - 1);
|
|
410
|
+
await scan(full, depth - 1);
|
|
411
411
|
continue;
|
|
412
412
|
}
|
|
413
413
|
if (entry.isFile() && entry.name === basename) {
|
|
414
|
-
const stat = safeStat(full);
|
|
414
|
+
const stat = await safeStat(full);
|
|
415
415
|
if (stat) {
|
|
416
416
|
found.push({ path: full, mtimeMs: stat.mtimeMs });
|
|
417
417
|
}
|
|
@@ -419,7 +419,7 @@ function findFileByRelativeOrBaseName(rawRef) {
|
|
|
419
419
|
}
|
|
420
420
|
}
|
|
421
421
|
for (const root of roots) {
|
|
422
|
-
scan(root, AUTO_UPLOAD_SCAN_DEPTH);
|
|
422
|
+
await scan(root, AUTO_UPLOAD_SCAN_DEPTH);
|
|
423
423
|
}
|
|
424
424
|
found.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
425
425
|
return found.length > 0 ? path.resolve(found[0].path) : "";
|
|
@@ -457,16 +457,16 @@ function extractFileReferencesFromText(text) {
|
|
|
457
457
|
return refs;
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
function resolveFileReference(ref) {
|
|
460
|
+
async function resolveFileReference(ref) {
|
|
461
461
|
if (!ref || !ref.value) {
|
|
462
462
|
return null;
|
|
463
463
|
}
|
|
464
464
|
const candidate = resolveCandidatePath(ref.value);
|
|
465
|
-
const resolved = path.isAbsolute(candidate) ? candidate : findFileByRelativeOrBaseName(candidate);
|
|
465
|
+
const resolved = path.isAbsolute(candidate) ? candidate : await findFileByRelativeOrBaseName(candidate);
|
|
466
466
|
if (!resolved) {
|
|
467
467
|
return null;
|
|
468
468
|
}
|
|
469
|
-
const stat = safeStat(resolved);
|
|
469
|
+
const stat = await safeStat(resolved);
|
|
470
470
|
if (!stat || (!stat.isFile() && !stat.isDirectory())) {
|
|
471
471
|
return null;
|
|
472
472
|
}
|
|
@@ -483,17 +483,17 @@ function resolveFileReference(ref) {
|
|
|
483
483
|
};
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
-
function collectDirectoryFiles(dirPath) {
|
|
486
|
+
async function collectDirectoryFiles(dirPath) {
|
|
487
487
|
const files = [];
|
|
488
488
|
let total = 0;
|
|
489
489
|
let visited = 0;
|
|
490
|
-
function scan(current, depth) {
|
|
490
|
+
async function scan(current, depth) {
|
|
491
491
|
if (depth < 0 || visited > AUTO_UPLOAD_SCAN_MAX_ENTRIES || shouldIgnorePath(current)) {
|
|
492
492
|
return;
|
|
493
493
|
}
|
|
494
494
|
let entries;
|
|
495
495
|
try {
|
|
496
|
-
entries = fs.
|
|
496
|
+
entries = await fs.promises.readdir(current, { withFileTypes: true });
|
|
497
497
|
} catch (_err) {
|
|
498
498
|
return;
|
|
499
499
|
}
|
|
@@ -507,26 +507,26 @@ function collectDirectoryFiles(dirPath) {
|
|
|
507
507
|
continue;
|
|
508
508
|
}
|
|
509
509
|
if (entry.isDirectory()) {
|
|
510
|
-
scan(full, depth - 1);
|
|
510
|
+
await scan(full, depth - 1);
|
|
511
511
|
continue;
|
|
512
512
|
}
|
|
513
513
|
if (!entry.isFile()) {
|
|
514
514
|
continue;
|
|
515
515
|
}
|
|
516
|
-
const stat = safeStat(full);
|
|
516
|
+
const stat = await safeStat(full);
|
|
517
517
|
if (!stat || total + stat.size > AUTO_UPLOAD_MAX_BYTES) {
|
|
518
518
|
continue;
|
|
519
519
|
}
|
|
520
520
|
total += stat.size;
|
|
521
521
|
const rel = normalizeSlash(path.relative(dirPath, full));
|
|
522
522
|
if (isTextLikeFile(full)) {
|
|
523
|
-
files.push({ name: rel, contentText: fs.
|
|
523
|
+
files.push({ name: rel, contentText: await fs.promises.readFile(full, "utf8") });
|
|
524
524
|
} else {
|
|
525
|
-
files.push({ name: rel, contentBase64: fs.
|
|
525
|
+
files.push({ name: rel, contentBase64: (await fs.promises.readFile(full)).toString("base64") });
|
|
526
526
|
}
|
|
527
527
|
}
|
|
528
528
|
}
|
|
529
|
-
scan(dirPath, AUTO_UPLOAD_SCAN_DEPTH);
|
|
529
|
+
await scan(dirPath, AUTO_UPLOAD_SCAN_DEPTH);
|
|
530
530
|
return files;
|
|
531
531
|
}
|
|
532
532
|
|
|
@@ -689,7 +689,7 @@ function rememberPendingFile(event) {
|
|
|
689
689
|
});
|
|
690
690
|
}
|
|
691
691
|
|
|
692
|
-
function pendingCandidatesForPayload(body) {
|
|
692
|
+
async function pendingCandidatesForPayload(body) {
|
|
693
693
|
const resourceId = payloadResourceId(body);
|
|
694
694
|
if (!resourceId) {
|
|
695
695
|
return [];
|
|
@@ -702,7 +702,7 @@ function pendingCandidatesForPayload(body) {
|
|
|
702
702
|
continue;
|
|
703
703
|
}
|
|
704
704
|
const ref = { raw: entry.path, value: entry.path, kind: "pending" };
|
|
705
|
-
const resolved = resolveFileReference(ref);
|
|
705
|
+
const resolved = await resolveFileReference(ref);
|
|
706
706
|
if (resolved) {
|
|
707
707
|
out.push(resolved);
|
|
708
708
|
}
|
|
@@ -880,6 +880,7 @@ function kbMultipartUpload(fileBuffers, fields, pathsArray) {
|
|
|
880
880
|
"X-API-Key": kbApiKey(),
|
|
881
881
|
"Content-Length": body.length,
|
|
882
882
|
},
|
|
883
|
+
timeout: 10000,
|
|
883
884
|
},
|
|
884
885
|
(res) => {
|
|
885
886
|
const chunks = [];
|
|
@@ -905,6 +906,9 @@ function kbMultipartUpload(fileBuffers, fields, pathsArray) {
|
|
|
905
906
|
});
|
|
906
907
|
},
|
|
907
908
|
);
|
|
909
|
+
req.on("timeout", () => {
|
|
910
|
+
req.destroy(new Error("kb upload timeout after 10s"));
|
|
911
|
+
});
|
|
908
912
|
req.on("error", reject);
|
|
909
913
|
req.write(body);
|
|
910
914
|
req.end();
|
|
@@ -1151,11 +1155,11 @@ function normalizeNativeToolFiles(files) {
|
|
|
1151
1155
|
}));
|
|
1152
1156
|
}
|
|
1153
1157
|
|
|
1154
|
-
function buildNativeUploadRequestBody(args) {
|
|
1158
|
+
async function buildNativeUploadRequestBody(args) {
|
|
1155
1159
|
const rawFilePath = trimString(args && args.file_path);
|
|
1156
1160
|
let fileCandidate = null;
|
|
1157
1161
|
if (rawFilePath) {
|
|
1158
|
-
fileCandidate = resolveFileReference({ raw: rawFilePath, value: rawFilePath, kind: "tool" });
|
|
1162
|
+
fileCandidate = await resolveFileReference({ raw: rawFilePath, value: rawFilePath, kind: "tool" });
|
|
1159
1163
|
if (!fileCandidate) {
|
|
1160
1164
|
throw new Error(`file_path is not readable or not under an OpenClaw workspace: ${rawFilePath}`);
|
|
1161
1165
|
}
|
|
@@ -1164,7 +1168,7 @@ function buildNativeUploadRequestBody(args) {
|
|
|
1164
1168
|
const body = {};
|
|
1165
1169
|
|
|
1166
1170
|
if (fileCandidate) {
|
|
1167
|
-
const stat = safeStat(fileCandidate.path);
|
|
1171
|
+
const stat = await safeStat(fileCandidate.path);
|
|
1168
1172
|
const isDirectory = !!(stat && stat.isDirectory());
|
|
1169
1173
|
const requestedType = trimString(args.type);
|
|
1170
1174
|
body.type =
|
|
@@ -1173,15 +1177,15 @@ function buildNativeUploadRequestBody(args) {
|
|
|
1173
1177
|
: requestedType || deliverableTypeForPath(fileCandidate.path, isDirectory);
|
|
1174
1178
|
body.fileName = normalizeDeliverableFileName(args, fileCandidate.fileName);
|
|
1175
1179
|
if (isDirectory) {
|
|
1176
|
-
const files = collectDirectoryFiles(fileCandidate.path);
|
|
1180
|
+
const files = await collectDirectoryFiles(fileCandidate.path);
|
|
1177
1181
|
if (files.length === 0) {
|
|
1178
1182
|
throw new Error(`directory has no uploadable files: ${fileCandidate.fileName}`);
|
|
1179
1183
|
}
|
|
1180
1184
|
body.files = files;
|
|
1181
1185
|
} else if (isTextLikeFile(fileCandidate.path)) {
|
|
1182
|
-
body.contentText = fs.
|
|
1186
|
+
body.contentText = await fs.promises.readFile(fileCandidate.path, "utf8");
|
|
1183
1187
|
} else {
|
|
1184
|
-
body.contentBase64 = fs.
|
|
1188
|
+
body.contentBase64 = (await fs.promises.readFile(fileCandidate.path)).toString("base64");
|
|
1185
1189
|
}
|
|
1186
1190
|
return { body, isDirectory };
|
|
1187
1191
|
}
|
|
@@ -1219,7 +1223,7 @@ function buildNativeUploadRequestBody(args) {
|
|
|
1219
1223
|
}
|
|
1220
1224
|
|
|
1221
1225
|
async function uploadDeliverable(args) {
|
|
1222
|
-
const { body, isDirectory } = buildNativeUploadRequestBody(args || {});
|
|
1226
|
+
const { body, isDirectory } = await buildNativeUploadRequestBody(args || {});
|
|
1223
1227
|
const userId = payloadUserId(args) || "default";
|
|
1224
1228
|
const groupId = payloadGroupId(args) || DEFAULT_GROUP_ID;
|
|
1225
1229
|
const groupName = extractStringField(args || {}, ["group_name", "groupName", "group_subject", "groupSubject"]);
|
|
@@ -1270,7 +1274,7 @@ async function uploadCandidate(candidate, body) {
|
|
|
1270
1274
|
return cached.result;
|
|
1271
1275
|
}
|
|
1272
1276
|
|
|
1273
|
-
const stat = safeStat(candidate.path);
|
|
1277
|
+
const stat = await safeStat(candidate.path);
|
|
1274
1278
|
if (!stat) {
|
|
1275
1279
|
throw new Error(`file no longer exists: ${candidate.fileName}`);
|
|
1276
1280
|
}
|
|
@@ -1282,7 +1286,7 @@ async function uploadCandidate(candidate, body) {
|
|
|
1282
1286
|
|
|
1283
1287
|
let fileBuffers;
|
|
1284
1288
|
if (stat.isDirectory()) {
|
|
1285
|
-
const files = collectDirectoryFiles(candidate.path);
|
|
1289
|
+
const files = await collectDirectoryFiles(candidate.path);
|
|
1286
1290
|
if (files.length === 0) {
|
|
1287
1291
|
throw new Error(`directory has no uploadable files: ${candidate.fileName}`);
|
|
1288
1292
|
}
|
|
@@ -1294,7 +1298,7 @@ async function uploadCandidate(candidate, body) {
|
|
|
1294
1298
|
if (stat.size > AUTO_UPLOAD_MAX_BYTES) {
|
|
1295
1299
|
throw new Error(`file exceeds auto-upload limit: ${candidate.fileName}`);
|
|
1296
1300
|
}
|
|
1297
|
-
fileBuffers = [{ fileName, content: fs.
|
|
1301
|
+
fileBuffers = [{ fileName, content: await fs.promises.readFile(candidate.path) }];
|
|
1298
1302
|
}
|
|
1299
1303
|
|
|
1300
1304
|
const response = await kbMultipartUpload(fileBuffers, {
|
|
@@ -1393,8 +1397,8 @@ async function autoUploadAndRewritePayload(body, api) {
|
|
|
1393
1397
|
return body;
|
|
1394
1398
|
}
|
|
1395
1399
|
const refs = extractFileReferencesFromText(body.content);
|
|
1396
|
-
const explicitCandidates = refs.map(resolveFileReference).filter(Boolean);
|
|
1397
|
-
const candidates = dedupeCandidates(explicitCandidates.concat(pendingCandidatesForPayload(body)));
|
|
1400
|
+
const explicitCandidates = (await Promise.all(refs.map(resolveFileReference))).filter(Boolean);
|
|
1401
|
+
const candidates = dedupeCandidates(explicitCandidates.concat(await pendingCandidatesForPayload(body)));
|
|
1398
1402
|
if (candidates.length === 0) {
|
|
1399
1403
|
return body;
|
|
1400
1404
|
}
|
|
@@ -2290,6 +2294,13 @@ const plugin = {
|
|
|
2290
2294
|
name: "Deliverables",
|
|
2291
2295
|
description: "Upload-first runtime guard for generated file deliverables.",
|
|
2292
2296
|
register(api) {
|
|
2297
|
+
// Warm the config cache once at startup so the synchronous read in
|
|
2298
|
+
// loadDeliverableConfig() never lands on the message hot path.
|
|
2299
|
+
try {
|
|
2300
|
+
loadDeliverableConfig();
|
|
2301
|
+
} catch (err) {
|
|
2302
|
+
api.logger.warn?.(`[plugin-deliverables] config preload failed: ${err.message}`);
|
|
2303
|
+
}
|
|
2293
2304
|
installPalzFetchPatch(api);
|
|
2294
2305
|
|
|
2295
2306
|
if (api && typeof api.registerTool === "function") {
|
package/openclaw-plugin.json
CHANGED
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "plugin-deliverables",
|
|
3
3
|
"name": "Deliverables",
|
|
4
4
|
"description": "Deliverables runtime guard for upload-first file delivery with Palz split-send diagnostics.",
|
|
5
|
-
"version": "1.1
|
|
5
|
+
"version": "1.2.1",
|
|
6
6
|
"skills": ["./skills"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED