@dai_ming/plugin-deliverables 1.0.22 → 1.1.0
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 +12 -200
- package/README.md +22 -227
- package/index.js +239 -0
- package/openclaw-plugin.json +2 -14
- package/openclaw.plugin.json +1 -1
- package/package.json +5 -8
- package/mcp-servers/deliverables.js +0 -515
- package/test/index.test.js +0 -114
- package/test/mcp-server.test.js +0 -60
package/index.js
CHANGED
|
@@ -85,6 +85,67 @@ const RUNTIME_DELIVERABLES_GUIDANCE = [
|
|
|
85
85
|
"- After a successful upload, reply with a substantive content summary before the links. For documents/articles/reports, prefer 1 short intro plus 3-6 concise bullets covering key sections, highlights, or findings, then preserve the Markdown links returned by the deliverables tool.",
|
|
86
86
|
].join("\n");
|
|
87
87
|
|
|
88
|
+
const UPLOAD_DELIVERABLE_TOOL = {
|
|
89
|
+
name: "deliverables__upload_deliverable",
|
|
90
|
+
description: [
|
|
91
|
+
"将 AI 生成的内容或 output/ 下的文件上传为交付物,返回可分享的下载/预览链接。",
|
|
92
|
+
"二进制文件优先传 file_path;文本单文件可传 content_text;多文件静态站点/游戏传 files。",
|
|
93
|
+
"返回的 reply_markdown 可直接回显给用户。",
|
|
94
|
+
].join(" "),
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
resource_id: {
|
|
99
|
+
type: "string",
|
|
100
|
+
description: "当前聊天框/会话的唯一 ID(必填)。",
|
|
101
|
+
},
|
|
102
|
+
group_id: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "当前会话所属的群聊 ID(可选)。",
|
|
105
|
+
},
|
|
106
|
+
user_id: {
|
|
107
|
+
type: "string",
|
|
108
|
+
description: "请求交付物的用户 ID(可选)。",
|
|
109
|
+
},
|
|
110
|
+
type: {
|
|
111
|
+
type: "string",
|
|
112
|
+
enum: ["article", "game", "zip", "image", "video", "ppt", "link"],
|
|
113
|
+
description: "交付物类型。若使用 file_path 且未准确判断,可由插件按文件扩展名兜底。",
|
|
114
|
+
},
|
|
115
|
+
file_name: {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "用户可见文件名。单文件必须带扩展名;未指定文档格式时默认 .md。",
|
|
118
|
+
},
|
|
119
|
+
file_path: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: "output/ 下已生成文件或目录的路径。二进制交付物优先使用此字段。",
|
|
122
|
+
},
|
|
123
|
+
content_text: {
|
|
124
|
+
type: "string",
|
|
125
|
+
description: "文本内容(Markdown、HTML 等)。",
|
|
126
|
+
},
|
|
127
|
+
content_base64: {
|
|
128
|
+
type: "string",
|
|
129
|
+
description: "Base64 编码的二进制内容。",
|
|
130
|
+
},
|
|
131
|
+
files: {
|
|
132
|
+
type: "array",
|
|
133
|
+
description: "多文件列表。每项包含 name 以及 content_text 或 content_base64。",
|
|
134
|
+
items: {
|
|
135
|
+
type: "object",
|
|
136
|
+
properties: {
|
|
137
|
+
name: { type: "string", description: "相对路径,例如 index.html 或 assets/main.js" },
|
|
138
|
+
content_text: { type: "string", description: "文本内容" },
|
|
139
|
+
content_base64: { type: "string", description: "Base64 二进制内容" },
|
|
140
|
+
},
|
|
141
|
+
required: ["name"],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
required: ["resource_id", "type", "file_name"],
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
|
|
88
149
|
function isString(value) {
|
|
89
150
|
return typeof value === "string";
|
|
90
151
|
}
|
|
@@ -565,6 +626,172 @@ function httpJSONRequest(method, requestPath, body) {
|
|
|
565
626
|
});
|
|
566
627
|
}
|
|
567
628
|
|
|
629
|
+
function looksLikeHTMLContent(contentText) {
|
|
630
|
+
const text = trimString(contentText).toLowerCase();
|
|
631
|
+
if (!text) {
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
if (text.indexOf("<!doctype html") === 0 || text.indexOf("<html") === 0) {
|
|
635
|
+
return true;
|
|
636
|
+
}
|
|
637
|
+
return (
|
|
638
|
+
text.indexOf("<head") >= 0 ||
|
|
639
|
+
text.indexOf("<body") >= 0 ||
|
|
640
|
+
text.indexOf("<main") >= 0 ||
|
|
641
|
+
text.indexOf("<section") >= 0 ||
|
|
642
|
+
text.indexOf("<article") >= 0 ||
|
|
643
|
+
text.indexOf("<style") >= 0 ||
|
|
644
|
+
text.indexOf("<script") >= 0
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function defaultExtensionForDeliverable(args) {
|
|
649
|
+
const deliverableType = trimString(args && args.type);
|
|
650
|
+
switch (deliverableType) {
|
|
651
|
+
case "article":
|
|
652
|
+
case "game":
|
|
653
|
+
return looksLikeHTMLContent(args && args.content_text) ? ".html" : ".md";
|
|
654
|
+
case "zip":
|
|
655
|
+
return ".zip";
|
|
656
|
+
case "ppt":
|
|
657
|
+
return ".pptx";
|
|
658
|
+
case "image":
|
|
659
|
+
return ".png";
|
|
660
|
+
case "video":
|
|
661
|
+
return ".mp4";
|
|
662
|
+
default:
|
|
663
|
+
return "";
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function normalizeDeliverableFileName(args, fallbackName) {
|
|
668
|
+
let fileName = trimString(args && args.file_name) || trimString(fallbackName) || "file";
|
|
669
|
+
if (trimString(args && args.type) === "link") {
|
|
670
|
+
return fileName;
|
|
671
|
+
}
|
|
672
|
+
if (Array.isArray(args && args.files) && args.files.length > 0) {
|
|
673
|
+
return fileName;
|
|
674
|
+
}
|
|
675
|
+
if (trimString(args && args.file_path)) {
|
|
676
|
+
return fileName;
|
|
677
|
+
}
|
|
678
|
+
if (hasFileExtension(fileName)) {
|
|
679
|
+
return fileName;
|
|
680
|
+
}
|
|
681
|
+
const ext = defaultExtensionForDeliverable(args);
|
|
682
|
+
return ext ? fileName + ext : fileName;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function buildReplyMarkdown(opts) {
|
|
686
|
+
const previewURL = opts.previewURL || "";
|
|
687
|
+
const downloadURL = opts.downloadURL || "";
|
|
688
|
+
const deliverableType = opts.type || "";
|
|
689
|
+
const isDirectory = !!opts.isDirectory;
|
|
690
|
+
const lines = [];
|
|
691
|
+
if (previewURL) {
|
|
692
|
+
lines.push(`预览链接:[点击预览](${previewURL})`);
|
|
693
|
+
}
|
|
694
|
+
if (downloadURL) {
|
|
695
|
+
if (isDirectory || deliverableType === "game") {
|
|
696
|
+
lines.push(`文件列表:[查看目录](${downloadURL})`);
|
|
697
|
+
} else {
|
|
698
|
+
lines.push(`下载链接:[点击下载](${downloadURL})`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
if (lines.length === 0) {
|
|
702
|
+
return "交付物已上传成功。";
|
|
703
|
+
}
|
|
704
|
+
return lines.join("\n");
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
function normalizeNativeToolFiles(files) {
|
|
708
|
+
if (!Array.isArray(files)) {
|
|
709
|
+
return [];
|
|
710
|
+
}
|
|
711
|
+
return files.map((file) => ({
|
|
712
|
+
name: file.name,
|
|
713
|
+
contentText: file.contentText || file.content_text || "",
|
|
714
|
+
contentBase64: file.contentBase64 || file.content_base64 || "",
|
|
715
|
+
}));
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function buildNativeUploadRequestBody(args) {
|
|
719
|
+
const rawFilePath = trimString(args && args.file_path);
|
|
720
|
+
let fileCandidate = null;
|
|
721
|
+
if (rawFilePath) {
|
|
722
|
+
fileCandidate = resolveFileReference({ raw: rawFilePath, value: rawFilePath, kind: "tool" });
|
|
723
|
+
if (!fileCandidate) {
|
|
724
|
+
throw new Error(`file_path is not readable or not under an OpenClaw workspace: ${rawFilePath}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
const body = {
|
|
729
|
+
resourceId: args.resource_id,
|
|
730
|
+
groupId: args.group_id,
|
|
731
|
+
userId: args.user_id,
|
|
732
|
+
release: deriveReleaseName(),
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
if (fileCandidate) {
|
|
736
|
+
const stat = safeStat(fileCandidate.path);
|
|
737
|
+
const isDirectory = !!(stat && stat.isDirectory());
|
|
738
|
+
body.type = trimString(args.type) || deliverableTypeForPath(fileCandidate.path, isDirectory);
|
|
739
|
+
body.fileName = normalizeDeliverableFileName(args, fileCandidate.fileName);
|
|
740
|
+
if (isDirectory) {
|
|
741
|
+
const files = collectDirectoryFiles(fileCandidate.path);
|
|
742
|
+
if (files.length === 0) {
|
|
743
|
+
throw new Error(`directory has no uploadable files: ${fileCandidate.fileName}`);
|
|
744
|
+
}
|
|
745
|
+
body.files = files;
|
|
746
|
+
} else if (isTextLikeFile(fileCandidate.path)) {
|
|
747
|
+
body.contentText = fs.readFileSync(fileCandidate.path, "utf8");
|
|
748
|
+
} else {
|
|
749
|
+
body.contentBase64 = fs.readFileSync(fileCandidate.path).toString("base64");
|
|
750
|
+
}
|
|
751
|
+
return { body, isDirectory };
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
body.type = trimString(args && args.type);
|
|
755
|
+
body.fileName = normalizeDeliverableFileName(args, "");
|
|
756
|
+
const files = normalizeNativeToolFiles(args && args.files);
|
|
757
|
+
if (files.length > 0) {
|
|
758
|
+
body.files = files;
|
|
759
|
+
return { body, isDirectory: true };
|
|
760
|
+
}
|
|
761
|
+
body.contentText = (args && (args.content_text || args.contentText)) || "";
|
|
762
|
+
body.contentBase64 = (args && (args.content_base64 || args.contentBase64)) || "";
|
|
763
|
+
return { body, isDirectory: false };
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
async function uploadDeliverable(args) {
|
|
767
|
+
const { body, isDirectory } = buildNativeUploadRequestBody(args || {});
|
|
768
|
+
const response = await httpJSONRequest("POST", "/openclaw-gateway/be/deliverables", body);
|
|
769
|
+
const data = response.data || response;
|
|
770
|
+
const previewURL = data.previewUrl || `${gatewayPublicURL()}/openclaw-gateway/preview/${data.uuid}`;
|
|
771
|
+
const downloadURL = data.downloadUrl || previewURL;
|
|
772
|
+
const replyMarkdown = buildReplyMarkdown({
|
|
773
|
+
previewURL,
|
|
774
|
+
downloadURL,
|
|
775
|
+
type: body.type,
|
|
776
|
+
isDirectory,
|
|
777
|
+
});
|
|
778
|
+
return {
|
|
779
|
+
uuid: data.uuid,
|
|
780
|
+
backend: data.backend || "",
|
|
781
|
+
download_url: downloadURL,
|
|
782
|
+
preview_url: previewURL,
|
|
783
|
+
expire_at: data.expireAt,
|
|
784
|
+
reply_markdown: replyMarkdown,
|
|
785
|
+
message: replyMarkdown,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
function nativeToolResult(result) {
|
|
790
|
+
return {
|
|
791
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
568
795
|
function uploadCacheKey(candidate, resourceId) {
|
|
569
796
|
return `${resourceId || ""}:${candidate.path}:${candidate.mtimeMs || 0}:${candidate.size || 0}`;
|
|
570
797
|
}
|
|
@@ -1505,6 +1732,18 @@ const plugin = {
|
|
|
1505
1732
|
register(api) {
|
|
1506
1733
|
installPalzFetchPatch(api);
|
|
1507
1734
|
|
|
1735
|
+
if (api && typeof api.registerTool === "function") {
|
|
1736
|
+
api.registerTool({
|
|
1737
|
+
name: UPLOAD_DELIVERABLE_TOOL.name,
|
|
1738
|
+
description: UPLOAD_DELIVERABLE_TOOL.description,
|
|
1739
|
+
parameters: UPLOAD_DELIVERABLE_TOOL.inputSchema,
|
|
1740
|
+
async execute(_callId, params) {
|
|
1741
|
+
cacheUploadSummary(params);
|
|
1742
|
+
return nativeToolResult(await uploadDeliverable(params || {}));
|
|
1743
|
+
},
|
|
1744
|
+
});
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1508
1747
|
api.on("before_prompt_build", async () => ({
|
|
1509
1748
|
prependSystemContext: RUNTIME_DELIVERABLES_GUIDANCE,
|
|
1510
1749
|
}));
|
package/openclaw-plugin.json
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plugin-deliverables",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"npm_package": "@dai_ming/plugin-deliverables",
|
|
5
|
-
"description": "Deliverables plugin:
|
|
6
|
-
"mcp_servers": {
|
|
7
|
-
"deliverables": {
|
|
8
|
-
"script": "mcp-servers/deliverables.js",
|
|
9
|
-
"command": "node",
|
|
10
|
-
"env": {
|
|
11
|
-
"CLAW_GATEWAY_URL": "${CLAW_GATEWAY_URL}",
|
|
12
|
-
"CLAW_GATEWAY_PUBLIC_URL": "${CLAW_GATEWAY_PUBLIC_URL}",
|
|
13
|
-
"CLAW_GATEWAY_API_KEY": "${CLAW_GATEWAY_API_KEY}",
|
|
14
|
-
"OPENCLAW_GATEWAY_API_KEY": "${OPENCLAW_GATEWAY_API_KEY}"
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
},
|
|
5
|
+
"description": "Deliverables plugin: native upload tool + skill + AGENTS rules for AI-generated file uploads",
|
|
18
6
|
"skills": {
|
|
19
7
|
"deliverables": "skills/deliverables/SKILL.md"
|
|
20
8
|
},
|
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.1.0",
|
|
6
6
|
"skills": ["./skills"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dai_ming/plugin-deliverables",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "OpenClaw deliverables plugin — upload AI-generated files to OSS and return shareable preview/download links",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "OpenClaw deliverables native plugin — upload AI-generated files to OSS and return shareable preview/download links",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|
|
7
7
|
"plugin",
|
|
8
|
-
"deliverables"
|
|
9
|
-
"mcp"
|
|
8
|
+
"deliverables"
|
|
10
9
|
],
|
|
11
10
|
"license": "MIT",
|
|
12
11
|
"main": "index.js",
|
|
13
12
|
"scripts": {
|
|
14
|
-
"test": "node test/index.test.js
|
|
13
|
+
"test": "node test/index.test.js"
|
|
15
14
|
},
|
|
16
15
|
"files": [
|
|
17
16
|
"INSTALL.md",
|
|
18
17
|
"index.js",
|
|
19
18
|
"openclaw.plugin.json",
|
|
20
19
|
"openclaw-plugin.json",
|
|
21
|
-
"mcp-servers/",
|
|
22
20
|
"skills/",
|
|
23
|
-
"agents-rules/"
|
|
24
|
-
"test/"
|
|
21
|
+
"agents-rules/"
|
|
25
22
|
],
|
|
26
23
|
"openclaw": {
|
|
27
24
|
"extensions": [
|