@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 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.statSync(candidatePath);
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"].forEach((root) => {
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.readdirSync("/data")) {
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.readdirSync(openclawRoot)) {
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.readdirSync(dir, { withFileTypes: true });
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.readdirSync(current, { withFileTypes: true });
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.readFileSync(full, "utf8") });
523
+ files.push({ name: rel, contentText: await fs.promises.readFile(full, "utf8") });
524
524
  } else {
525
- files.push({ name: rel, contentBase64: fs.readFileSync(full).toString("base64") });
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.readFileSync(fileCandidate.path, "utf8");
1186
+ body.contentText = await fs.promises.readFile(fileCandidate.path, "utf8");
1187
1187
  } else {
1188
- body.contentBase64 = fs.readFileSync(fileCandidate.path).toString("base64");
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.readFileSync(candidate.path) }];
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") {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-deliverables",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "npm_package": "@dai_ming/plugin-deliverables",
5
5
  "description": "Deliverables plugin: native upload tool + skill + AGENTS rules for AI-generated file uploads",
6
6
  "skills": {
@@ -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.0",
5
+ "version": "1.2.1",
6
6
  "skills": ["./skills"],
7
7
  "configSchema": {
8
8
  "type": "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dai_ming/plugin-deliverables",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "OpenClaw deliverables native plugin — upload AI-generated files to OSS and return shareable preview/download links",
5
5
  "keywords": [
6
6
  "openclaw",