@dai_ming/plugin-deliverables 1.2.0 → 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 +50 -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
|
}
|
|
@@ -1155,11 +1155,11 @@ function normalizeNativeToolFiles(files) {
|
|
|
1155
1155
|
}));
|
|
1156
1156
|
}
|
|
1157
1157
|
|
|
1158
|
-
function buildNativeUploadRequestBody(args) {
|
|
1158
|
+
async function buildNativeUploadRequestBody(args) {
|
|
1159
1159
|
const rawFilePath = trimString(args && args.file_path);
|
|
1160
1160
|
let fileCandidate = null;
|
|
1161
1161
|
if (rawFilePath) {
|
|
1162
|
-
fileCandidate = resolveFileReference({ raw: rawFilePath, value: rawFilePath, kind: "tool" });
|
|
1162
|
+
fileCandidate = await resolveFileReference({ raw: rawFilePath, value: rawFilePath, kind: "tool" });
|
|
1163
1163
|
if (!fileCandidate) {
|
|
1164
1164
|
throw new Error(`file_path is not readable or not under an OpenClaw workspace: ${rawFilePath}`);
|
|
1165
1165
|
}
|
|
@@ -1168,7 +1168,7 @@ function buildNativeUploadRequestBody(args) {
|
|
|
1168
1168
|
const body = {};
|
|
1169
1169
|
|
|
1170
1170
|
if (fileCandidate) {
|
|
1171
|
-
const stat = safeStat(fileCandidate.path);
|
|
1171
|
+
const stat = await safeStat(fileCandidate.path);
|
|
1172
1172
|
const isDirectory = !!(stat && stat.isDirectory());
|
|
1173
1173
|
const requestedType = trimString(args.type);
|
|
1174
1174
|
body.type =
|
|
@@ -1177,15 +1177,15 @@ function buildNativeUploadRequestBody(args) {
|
|
|
1177
1177
|
: requestedType || deliverableTypeForPath(fileCandidate.path, isDirectory);
|
|
1178
1178
|
body.fileName = normalizeDeliverableFileName(args, fileCandidate.fileName);
|
|
1179
1179
|
if (isDirectory) {
|
|
1180
|
-
const files = collectDirectoryFiles(fileCandidate.path);
|
|
1180
|
+
const files = await collectDirectoryFiles(fileCandidate.path);
|
|
1181
1181
|
if (files.length === 0) {
|
|
1182
1182
|
throw new Error(`directory has no uploadable files: ${fileCandidate.fileName}`);
|
|
1183
1183
|
}
|
|
1184
1184
|
body.files = files;
|
|
1185
1185
|
} else if (isTextLikeFile(fileCandidate.path)) {
|
|
1186
|
-
body.contentText = fs.
|
|
1186
|
+
body.contentText = await fs.promises.readFile(fileCandidate.path, "utf8");
|
|
1187
1187
|
} else {
|
|
1188
|
-
body.contentBase64 = fs.
|
|
1188
|
+
body.contentBase64 = (await fs.promises.readFile(fileCandidate.path)).toString("base64");
|
|
1189
1189
|
}
|
|
1190
1190
|
return { body, isDirectory };
|
|
1191
1191
|
}
|
|
@@ -1223,7 +1223,7 @@ function buildNativeUploadRequestBody(args) {
|
|
|
1223
1223
|
}
|
|
1224
1224
|
|
|
1225
1225
|
async function uploadDeliverable(args) {
|
|
1226
|
-
const { body, isDirectory } = buildNativeUploadRequestBody(args || {});
|
|
1226
|
+
const { body, isDirectory } = await buildNativeUploadRequestBody(args || {});
|
|
1227
1227
|
const userId = payloadUserId(args) || "default";
|
|
1228
1228
|
const groupId = payloadGroupId(args) || DEFAULT_GROUP_ID;
|
|
1229
1229
|
const groupName = extractStringField(args || {}, ["group_name", "groupName", "group_subject", "groupSubject"]);
|
|
@@ -1274,7 +1274,7 @@ async function uploadCandidate(candidate, body) {
|
|
|
1274
1274
|
return cached.result;
|
|
1275
1275
|
}
|
|
1276
1276
|
|
|
1277
|
-
const stat = safeStat(candidate.path);
|
|
1277
|
+
const stat = await safeStat(candidate.path);
|
|
1278
1278
|
if (!stat) {
|
|
1279
1279
|
throw new Error(`file no longer exists: ${candidate.fileName}`);
|
|
1280
1280
|
}
|
|
@@ -1286,7 +1286,7 @@ async function uploadCandidate(candidate, body) {
|
|
|
1286
1286
|
|
|
1287
1287
|
let fileBuffers;
|
|
1288
1288
|
if (stat.isDirectory()) {
|
|
1289
|
-
const files = collectDirectoryFiles(candidate.path);
|
|
1289
|
+
const files = await collectDirectoryFiles(candidate.path);
|
|
1290
1290
|
if (files.length === 0) {
|
|
1291
1291
|
throw new Error(`directory has no uploadable files: ${candidate.fileName}`);
|
|
1292
1292
|
}
|
|
@@ -1298,7 +1298,7 @@ async function uploadCandidate(candidate, body) {
|
|
|
1298
1298
|
if (stat.size > AUTO_UPLOAD_MAX_BYTES) {
|
|
1299
1299
|
throw new Error(`file exceeds auto-upload limit: ${candidate.fileName}`);
|
|
1300
1300
|
}
|
|
1301
|
-
fileBuffers = [{ fileName, content: fs.
|
|
1301
|
+
fileBuffers = [{ fileName, content: await fs.promises.readFile(candidate.path) }];
|
|
1302
1302
|
}
|
|
1303
1303
|
|
|
1304
1304
|
const response = await kbMultipartUpload(fileBuffers, {
|
|
@@ -1397,8 +1397,8 @@ async function autoUploadAndRewritePayload(body, api) {
|
|
|
1397
1397
|
return body;
|
|
1398
1398
|
}
|
|
1399
1399
|
const refs = extractFileReferencesFromText(body.content);
|
|
1400
|
-
const explicitCandidates = refs.map(resolveFileReference).filter(Boolean);
|
|
1401
|
-
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)));
|
|
1402
1402
|
if (candidates.length === 0) {
|
|
1403
1403
|
return body;
|
|
1404
1404
|
}
|
|
@@ -2294,6 +2294,13 @@ const plugin = {
|
|
|
2294
2294
|
name: "Deliverables",
|
|
2295
2295
|
description: "Upload-first runtime guard for generated file deliverables.",
|
|
2296
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
|
+
}
|
|
2297
2304
|
installPalzFetchPatch(api);
|
|
2298
2305
|
|
|
2299
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.2.
|
|
5
|
+
"version": "1.2.1",
|
|
6
6
|
"skills": ["./skills"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED