@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 +6 -6
- package/README.md +4 -4
- package/index.js +51 -3
- package/mcp-servers/deliverables.js +61 -2
- package/openclaw-plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/test/index.test.js +14 -0
- package/test/mcp-server.test.js +16 -0
package/INSTALL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# plugin-deliverables 安装文档
|
|
2
2
|
|
|
3
|
-
本文档描述 `@dai_ming/plugin-deliverables@1.0.
|
|
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.
|
|
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.
|
|
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.
|
|
70
|
-
tar xzf dai_ming-plugin-deliverables-1.0.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
1407
|
-
|
|
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=${
|
|
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 =
|
|
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) {
|
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.0.
|
|
5
|
+
"version": "1.0.22",
|
|
6
6
|
"skills": ["./skills"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED
package/test/index.test.js
CHANGED
|
@@ -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(
|
package/test/mcp-server.test.js
CHANGED
|
@@ -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"));
|