@dai_ming/plugin-deliverables 1.0.21 → 1.0.22

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/INSTALL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # plugin-deliverables 安装文档
2
2
 
3
- 本文档描述 `@dai_ming/plugin-deliverables@1.0.21` 的安装、升级与验证方式。
3
+ 本文档描述 `@dai_ming/plugin-deliverables@1.0.22` 的安装、升级与验证方式。
4
4
 
5
5
  ## 1. 目标
6
6
 
@@ -22,7 +22,7 @@
22
22
  适用于已经支持 `openclaw plugins install` 的运行环境。
23
23
 
24
24
  ```bash
25
- openclaw plugins install @dai_ming/plugin-deliverables@1.0.21 --pin
25
+ openclaw plugins install @dai_ming/plugin-deliverables@1.0.22 --pin
26
26
  openclaw plugins enable plugin-deliverables
27
27
  ```
28
28
 
@@ -47,7 +47,7 @@ openclaw plugins list
47
47
 
48
48
  ```yaml
49
49
  installPlugins:
50
- - "@dai_ming/plugin-deliverables@1.0.21"
50
+ - "@dai_ming/plugin-deliverables@1.0.22"
51
51
  ```
52
52
 
53
53
  Helm 初始化时会自动完成:
@@ -66,8 +66,8 @@ Helm 初始化时会自动完成:
66
66
  ```bash
67
67
  mkdir -p /home/node/.openclaw/extensions-extra/plugin-deliverables
68
68
  cd /tmp
69
- npm pack @dai_ming/plugin-deliverables@1.0.21 --registry https://registry.npmjs.org
70
- tar xzf dai_ming-plugin-deliverables-1.0.21.tgz \
69
+ npm pack @dai_ming/plugin-deliverables@1.0.22 --registry https://registry.npmjs.org
70
+ tar xzf dai_ming-plugin-deliverables-1.0.22.tgz \
71
71
  -C /home/node/.openclaw/extensions-extra/plugin-deliverables \
72
72
  --strip-components=1
73
73
  ```
@@ -212,5 +212,5 @@ cat /home/node/.openclaw/extensions-extra/plugin-deliverables/package.json
212
212
  必须是:
213
213
 
214
214
  ```json
215
- { "version": "1.0.21" }
215
+ { "version": "1.0.22" }
216
216
  ```
package/README.md CHANGED
@@ -24,7 +24,7 @@ OpenClaw 交付物插件 — 让 AI Agent 将生成的文件(文章、HTML 页
24
24
  现在这个包同时支持 OpenClaw 原生插件加载。也就是说,除了网关侧用 `openclaw-plugin.json` 注入 MCP/skill/AGENTS 规则外,OpenClaw 还会读取 `openclaw.plugin.json` + `index.js`,把运行时 hook 也一并启用。
25
25
 
26
26
  ```bash
27
- openclaw plugins install @dai_ming/plugin-deliverables@1.0.21 --pin
27
+ openclaw plugins install @dai_ming/plugin-deliverables@1.0.22 --pin
28
28
  openclaw plugins enable plugin-deliverables
29
29
  ```
30
30
 
@@ -44,11 +44,11 @@ openclaw plugins enable plugin-deliverables
44
44
  ```yaml
45
45
  # values.yaml(或 claw-gateway 管理界面的 Helm 参数)
46
46
  installPlugins:
47
- - "@dai_ming/plugin-deliverables@1.0.21"
47
+ - "@dai_ming/plugin-deliverables@1.0.22"
48
48
  ```
49
49
 
50
50
  initContainer 执行顺序:
51
- 1. **Phase 3**:`npm pack @dai_ming/plugin-deliverables@1.0.21` 下载 tarball → 解压到 `/data/extensions-extra/plugin-deliverables/`
51
+ 1. **Phase 3**:`npm pack @dai_ming/plugin-deliverables@1.0.22` 下载 tarball → 解压到 `/data/extensions-extra/plugin-deliverables/`
52
52
  2. **Phase 3b**:在插件目录执行 `npm install --omit=dev`(本插件无运行时依赖,此步骤跳过)
53
53
  3. **Phase 3e**:读取 `openclaw-plugin.json` 清单,自动:
54
54
  - 复制 `mcp-servers/deliverables.js` → `/data/mcp-servers/deliverables.js`
@@ -226,7 +226,7 @@ npm publish --registry https://registry.npmjs.org --access public
226
226
 
227
227
  | claw-gateway 版本 | plugin 版本 |
228
228
  |-------------------|-------------|
229
- | 当前 | 1.0.21 |
229
+ | 当前 | 1.0.22 |
230
230
 
231
231
  ---
232
232
 
package/index.js CHANGED
@@ -1222,6 +1222,7 @@ function splitDeliverableMessage(text) {
1222
1222
 
1223
1223
  const linkLines = [];
1224
1224
  const linkUrls = [];
1225
+ const linkItems = [];
1225
1226
  for (let i = firstLinkIndex; i < lines.length; i += 1) {
1226
1227
  const line = lines[i];
1227
1228
  if (isLinksHeading(line)) {
@@ -1234,6 +1235,7 @@ function splitDeliverableMessage(text) {
1234
1235
  const url = extractDeliverableURL(normalizedLine);
1235
1236
  if (url) {
1236
1237
  linkUrls.push(url);
1238
+ linkItems.push({ line: normalizedLine, url });
1237
1239
  }
1238
1240
  }
1239
1241
  }
@@ -1249,10 +1251,53 @@ function splitDeliverableMessage(text) {
1249
1251
  summary,
1250
1252
  links,
1251
1253
  linkUrls,
1254
+ linkItems,
1252
1255
  primaryLinkUrl: linkUrls.length > 0 ? linkUrls[0] : "",
1256
+ fileLinkUrl: selectPalzFileURL(linkItems),
1253
1257
  };
1254
1258
  }
1255
1259
 
1260
+ function extractDeliverableIdentity(rawURL) {
1261
+ if (!isString(rawURL) || !rawURL.trim()) {
1262
+ return "";
1263
+ }
1264
+ let parsed;
1265
+ try {
1266
+ parsed = new URL(rawURL.trim());
1267
+ } catch (_err) {
1268
+ return "";
1269
+ }
1270
+ if (!DELIVERABLE_GATEWAY_PATH_RE.test(parsed.pathname)) {
1271
+ return "";
1272
+ }
1273
+ const parts = parsed.pathname.split("/").filter(Boolean);
1274
+ const uuidRe = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
1275
+ for (const part of parts) {
1276
+ if (uuidRe.test(part)) {
1277
+ return part.toLowerCase();
1278
+ }
1279
+ }
1280
+ return parsed.href;
1281
+ }
1282
+
1283
+ function selectPalzFileURL(linkItems) {
1284
+ if (!Array.isArray(linkItems) || linkItems.length === 0) {
1285
+ return "";
1286
+ }
1287
+ const identities = new Set();
1288
+ for (const item of linkItems) {
1289
+ const identity = extractDeliverableIdentity(item && item.url);
1290
+ if (identity) {
1291
+ identities.add(identity);
1292
+ }
1293
+ }
1294
+ if (identities.size !== 1) {
1295
+ return "";
1296
+ }
1297
+ const download = linkItems.find((item) => /下载链接/u.test(String((item && item.line) || "")));
1298
+ return (download && download.url) || linkItems[0].url || "";
1299
+ }
1300
+
1256
1301
  function cloneBody(body, content, suffix) {
1257
1302
  const next = {};
1258
1303
  Object.keys(body).forEach((key) => {
@@ -1308,6 +1353,7 @@ function shouldSplitPalzPayload(body) {
1308
1353
  summary: cachedSummary,
1309
1354
  links: split.links,
1310
1355
  primaryLinkUrl: split.primaryLinkUrl,
1356
+ fileLinkUrl: split.fileLinkUrl,
1311
1357
  };
1312
1358
  }
1313
1359
  return split;
@@ -1403,14 +1449,15 @@ function installPalzFetchPatch(api) {
1403
1449
  }
1404
1450
 
1405
1451
  const summaryBody = cloneBody(parsed, split.summary, "__summary");
1406
- const linksBody = split.primaryLinkUrl && (!Array.isArray(split.linkUrls) || split.linkUrls.length === 1)
1407
- ? cloneBodyAsFileLink(parsed, split.primaryLinkUrl, "__links")
1452
+ const fileLinkUrl = split.fileLinkUrl || "";
1453
+ const linksBody = fileLinkUrl
1454
+ ? cloneBodyAsFileLink(parsed, fileLinkUrl, "__links")
1408
1455
  : cloneBody(parsed, split.links, "__links");
1409
1456
  const summaryBodyStr = JSON.stringify(summaryBody);
1410
1457
  const linksBodyStr = JSON.stringify(linksBody);
1411
1458
 
1412
1459
  api.logger.info?.(
1413
- `[plugin-deliverables] split deliverable reply injected by plugin-deliverables target=${String(parsed.conversation_id || "")} summaryMsgId=${String(summaryBody.msg_id || "")} linksMsgId=${String(linksBody.msg_id || "")} linksMode=${split.primaryLinkUrl ? "file_url" : "text"}`,
1460
+ `[plugin-deliverables] split deliverable reply injected by plugin-deliverables target=${String(parsed.conversation_id || "")} summaryMsgId=${String(summaryBody.msg_id || "")} linksMsgId=${String(linksBody.msg_id || "")} linksMode=${fileLinkUrl ? "file_url" : "text"}`,
1414
1461
  );
1415
1462
  api.logger.info?.(
1416
1463
  `[plugin-deliverables] palz summary request body_length=${summaryBodyStr.length}\n request_body=${summaryBodyStr}`,
@@ -1508,6 +1555,7 @@ plugin.__test = {
1508
1555
  isDeliverableLinkLine,
1509
1556
  isOSSLikeURL,
1510
1557
  isTrustedDeliverableURL,
1558
+ selectPalzFileURL,
1511
1559
  splitDeliverableMessage,
1512
1560
  };
1513
1561
 
@@ -274,12 +274,70 @@ function normalizeBase64Content(value, label) {
274
274
  return Buffer.from(padded, "base64").toString("base64");
275
275
  }
276
276
 
277
+ function candidateWorkspaceRoots() {
278
+ var roots = [];
279
+ [
280
+ process.env.OPENCLAW_WORKSPACE_PATH,
281
+ process.env.WORKSPACE_PATH,
282
+ "/home/node/.openclaw/workspace-main",
283
+ "/home/node/.openclaw/workspace",
284
+ "/data/workspace",
285
+ "/data/workspace-main"
286
+ ].forEach(function(root) {
287
+ root = trimString(root);
288
+ if (root && roots.indexOf(root) < 0) {
289
+ roots.push(root);
290
+ }
291
+ });
292
+ try {
293
+ fs.readdirSync("/data").forEach(function(entry) {
294
+ if (entry && entry.indexOf("workspace-") === 0) {
295
+ var root = path.join("/data", entry);
296
+ if (roots.indexOf(root) < 0) {
297
+ roots.push(root);
298
+ }
299
+ }
300
+ });
301
+ } catch (_err) {}
302
+ return roots;
303
+ }
304
+
305
+ function resolveReadableFilePath(filePath) {
306
+ var rawPath = trimString(filePath);
307
+ if (!rawPath) {
308
+ return "";
309
+ }
310
+ var candidates = [];
311
+ function add(candidate) {
312
+ if (candidate && candidates.indexOf(candidate) < 0) {
313
+ candidates.push(candidate);
314
+ }
315
+ }
316
+ if (path.isAbsolute(rawPath)) {
317
+ add(rawPath);
318
+ } else {
319
+ add(path.resolve(process.cwd(), rawPath));
320
+ candidateWorkspaceRoots().forEach(function(root) {
321
+ add(path.join(root, rawPath));
322
+ });
323
+ }
324
+ for (var i = 0; i < candidates.length; i += 1) {
325
+ try {
326
+ var stat = fs.statSync(candidates[i]);
327
+ if (stat.isFile()) {
328
+ return candidates[i];
329
+ }
330
+ } catch (_err) {}
331
+ }
332
+ return candidates[0] || rawPath;
333
+ }
334
+
277
335
  function readFileAsBase64(filePath, label) {
278
336
  var rawPath = trimString(filePath);
279
337
  if (!rawPath) {
280
338
  return "";
281
339
  }
282
- var resolvedPath = path.resolve(process.cwd(), rawPath);
340
+ var resolvedPath = resolveReadableFilePath(rawPath);
283
341
  var stat;
284
342
  try {
285
343
  stat = fs.statSync(resolvedPath);
@@ -448,7 +506,8 @@ function startMCPServer() {
448
506
  module.exports.__test = {
449
507
  buildContentPayload: buildContentPayload,
450
508
  buildUploadRequestBody: buildUploadRequestBody,
451
- normalizeBase64Content: normalizeBase64Content
509
+ normalizeBase64Content: normalizeBase64Content,
510
+ resolveReadableFilePath: resolveReadableFilePath
452
511
  };
453
512
 
454
513
  if (require.main === module) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-deliverables",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "npm_package": "@dai_ming/plugin-deliverables",
5
5
  "description": "Deliverables plugin: MCP server + skill + AGENTS rules for AI-generated file uploads",
6
6
  "mcp_servers": {
@@ -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.0.21",
5
+ "version": "1.0.22",
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.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "OpenClaw deliverables plugin — upload AI-generated files to OSS and return shareable preview/download links",
5
5
  "keywords": [
6
6
  "openclaw",
@@ -11,6 +11,7 @@ const {
11
11
  findUntrustedOSSDeliverableURL,
12
12
  isDeliverableLinkLine,
13
13
  isTrustedDeliverableURL,
14
+ selectPalzFileURL,
14
15
  splitDeliverableMessage,
15
16
  } = plugin.__test;
16
17
 
@@ -40,6 +41,19 @@ const split = splitDeliverableMessage(
40
41
  );
41
42
  assert.ok(split);
42
43
  assert.strictEqual(split.primaryLinkUrl, gatewayURL);
44
+ assert.strictEqual(split.fileLinkUrl, gatewayURL);
45
+
46
+ const outputURL =
47
+ "https://claw-gateway.csagentai.com/openclaw-gateway/output/user/res/uuid/7a9e27b7-08ca-4453-81e8-fe6a630d4566/report.md";
48
+ const previewURL =
49
+ "https://claw-gateway.csagentai.com/openclaw-gateway/preview/7a9e27b7-08ca-4453-81e8-fe6a630d4566";
50
+ assert.strictEqual(
51
+ selectPalzFileURL([
52
+ { line: `预览链接:[点击预览](${previewURL})`, url: previewURL },
53
+ { line: `下载链接:[点击下载](${outputURL})`, url: outputURL },
54
+ ]),
55
+ outputURL,
56
+ );
43
57
 
44
58
  assert.strictEqual(
45
59
  splitDeliverableMessage(
@@ -8,6 +8,7 @@ const path = require("path");
8
8
  const {
9
9
  buildUploadRequestBody,
10
10
  normalizeBase64Content,
11
+ resolveReadableFilePath,
11
12
  } = require("../mcp-servers/deliverables.js").__test;
12
13
 
13
14
  assert.strictEqual(
@@ -24,6 +25,7 @@ assert.throws(
24
25
  );
25
26
 
26
27
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "deliverables-mcp-"));
28
+ process.env.OPENCLAW_WORKSPACE_PATH = tmpDir;
27
29
  const pdfPath = path.join(tmpDir, "sample.pdf");
28
30
  const pdfBytes = Buffer.from("%PDF-1.3\nsample\n", "utf8");
29
31
  fs.writeFileSync(pdfPath, pdfBytes);
@@ -42,3 +44,17 @@ assert.strictEqual(body.resourceId, "res-1");
42
44
  assert.strictEqual(body.fileName, "sample.pdf");
43
45
  assert.strictEqual(body.contentText, "");
44
46
  assert.strictEqual(body.contentBase64, pdfBytes.toString("base64"));
47
+
48
+ const outputDir = path.join(tmpDir, "output");
49
+ fs.mkdirSync(outputDir, { recursive: true });
50
+ const relativeDoc = path.join(outputDir, "relative.md");
51
+ fs.writeFileSync(relativeDoc, "# Relative\n", "utf8");
52
+ assert.strictEqual(resolveReadableFilePath("output/relative.md"), relativeDoc);
53
+
54
+ const relativeBody = buildUploadRequestBody({
55
+ resource_id: "res-1",
56
+ type: "article",
57
+ file_name: "relative.md",
58
+ file_path: "output/relative.md",
59
+ });
60
+ assert.strictEqual(relativeBody.contentBase64, Buffer.from("# Relative\n").toString("base64"));