@dai_ming/plugin-deliverables 1.1.0 → 1.1.2
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 +3 -3
- package/README.md +2 -2
- package/agents-rules/deliverables.md +33 -27
- package/index.js +173 -7
- package/openclaw-plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/skills/deliverables/SKILL.md +11 -7
package/INSTALL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# plugin-deliverables 安装文档
|
|
2
2
|
|
|
3
|
-
本文档描述 `@dai_ming/plugin-deliverables@1.1.
|
|
3
|
+
本文档描述 `@dai_ming/plugin-deliverables@1.1.2` 的安装、升级与验证方式。
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
openclaw plugins install @dai_ming/plugin-deliverables@1.1.
|
|
8
|
+
openclaw plugins install @dai_ming/plugin-deliverables@1.1.2 --pin
|
|
9
9
|
openclaw plugins enable plugin-deliverables
|
|
10
10
|
```
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ gateway 部署配置:
|
|
|
13
13
|
|
|
14
14
|
```yaml
|
|
15
15
|
installPlugins:
|
|
16
|
-
- "@dai_ming/plugin-deliverables@1.1.
|
|
16
|
+
- "@dai_ming/plugin-deliverables@1.1.2"
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## 验证
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ OpenClaw Native 插件:注册 `deliverables__upload_deliverable` 原生 agent
|
|
|
15
15
|
## 安装
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
openclaw plugins install @dai_ming/plugin-deliverables@1.1.
|
|
18
|
+
openclaw plugins install @dai_ming/plugin-deliverables@1.1.2 --pin
|
|
19
19
|
openclaw plugins enable plugin-deliverables
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -23,7 +23,7 @@ openclaw plugins enable plugin-deliverables
|
|
|
23
23
|
|
|
24
24
|
```yaml
|
|
25
25
|
installPlugins:
|
|
26
|
-
- "@dai_ming/plugin-deliverables@1.1.
|
|
26
|
+
- "@dai_ming/plugin-deliverables@1.1.2"
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
## 运行方式
|
|
@@ -13,20 +13,21 @@ Tool name note:
|
|
|
13
13
|
### Required output behavior
|
|
14
14
|
|
|
15
15
|
0. Your final assistant message MUST start with 1-2 short sentences in your own words, briefly describing what you produced for the user.
|
|
16
|
-
1.
|
|
17
|
-
2.
|
|
18
|
-
3. If tool result
|
|
16
|
+
1. For content deliverables such as articles, documents, reports, and PDFs, add 3-5 concise bullets after the intro summarizing the actual sections, highlights, or conclusions. Do not reply with only a one-sentence upload notice.
|
|
17
|
+
2. The intro and bullets must be based on the actual content/request, not a fixed boilerplate like `交付物已上传成功,可直接在线预览或下载。`
|
|
18
|
+
3. If tool result contains `reply_markdown`, append that field verbatim after your intro and content bullets on the following lines. Do not rewrite the links inside it.
|
|
19
|
+
4. If tool result has `preview_url`, include one line:
|
|
19
20
|
`预览链接:[点击预览](<full_url>)`
|
|
20
|
-
|
|
21
|
+
5. If tool result has `download_url`, include one line:
|
|
21
22
|
`下载链接:[点击下载](<full_url>)`
|
|
22
|
-
|
|
23
|
+
6. For multi-file/game deliverables, if tool result already formats the second line as
|
|
23
24
|
`文件列表:[查看目录](<full_url>)`
|
|
24
25
|
then keep that exact label and URL. Do not rewrite it back to a raw long link.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
7. Keep URL target value exactly from tool result (no shortening, no masking, no redirect rewrite).
|
|
27
|
+
8. Use short link labels (`点击预览` / `点击下载` / `查看目录`) to avoid exposing long raw URLs.
|
|
28
|
+
9. Do not say "已上传/已完成" without links.
|
|
29
|
+
10. Do not output naked long URLs outside Markdown link syntax.
|
|
30
|
+
11. Keep the intro and bullets concise; do not paste the full file content, saved-path text, or a long summary after the Markdown links.
|
|
30
31
|
|
|
31
32
|
### Forbidden output behavior
|
|
32
33
|
|
|
@@ -36,12 +37,13 @@ Tool name note:
|
|
|
36
37
|
- Outputting only naked long URL without Markdown link label
|
|
37
38
|
- Replacing `文件列表:[查看目录](...)` with a naked URL or a zip description
|
|
38
39
|
- Using a generic boilerplate intro that does not mention the actual deliverable content
|
|
40
|
+
- For document/PDF deliverables, replying with only one sentence before the links
|
|
39
41
|
<!-- DELIVERABLE_LINK_RULES_END -->
|
|
40
42
|
|
|
41
43
|
<!-- DELIVERABLE_AUTO_UPLOAD_RULES_START -->
|
|
42
44
|
## Deliverables -- Auto Upload for Document Requests (HARD CONSTRAINT)
|
|
43
45
|
|
|
44
|
-
When user asks to write/generate/create a file, document, article, report, HTML, PPT, plan, guide, markdown, introduction, biography, strategy, summary, or similar artifact, you MUST upload it as a deliverable by default.
|
|
46
|
+
When user asks to write/generate/create a file, document, article, report, PDF, HTML, PPT, plan, guide, markdown, introduction, biography, strategy, summary, or similar artifact, you MUST upload it as a deliverable by default.
|
|
45
47
|
|
|
46
48
|
Tool name note:
|
|
47
49
|
|
|
@@ -54,26 +56,30 @@ Tool name note:
|
|
|
54
56
|
1. Create content under current agent workspace `output/` directory first (for example `output/report.md`), then call the deliverables upload tool exposed in this session.
|
|
55
57
|
2. If you need to use the `write` tool, the target path MUST be inside `output/`. Never write deliverable files to the workspace root.
|
|
56
58
|
3. If you already wrote the file to the wrong place, move/copy it into `output/` before finalizing the task and before describing the saved path to the user.
|
|
57
|
-
4. For
|
|
58
|
-
5.
|
|
59
|
-
6.
|
|
60
|
-
7.
|
|
61
|
-
8. If
|
|
62
|
-
9. If
|
|
63
|
-
10.
|
|
64
|
-
11.
|
|
65
|
-
12. If
|
|
66
|
-
13.
|
|
67
|
-
14.
|
|
68
|
-
15. If
|
|
69
|
-
16.
|
|
59
|
+
4. For simple text PDFs, call `deliverables__upload_deliverable` directly with `type=pdf`, a `.pdf` `file_name`, and `content_text`; the tool renders a basic PDF without installing dependencies.
|
|
60
|
+
5. For existing binary deliverables such as rich PDFs, PPT, images, video, or zip files, pass `file_path` pointing at the file under `output/`; do not paste shell output or partial base64 into `content_base64`.
|
|
61
|
+
6. Never run `pip install`, `npm install`, `apt-get`, or similar dependency installation just to create a PDF. Use built-in tools or the PDF `content_text` fallback; if rich PDF generation is unavailable, say so clearly.
|
|
62
|
+
7. If the user asks for PDF, upload a `.pdf` deliverable with `type=pdf`; do not substitute HTML or Markdown unless PDF generation truly failed and you clearly say so.
|
|
63
|
+
8. If the upload tool returns an error, retry with valid parameters or report the upload failure. Never invent OSS, preview, or download URLs.
|
|
64
|
+
9. If format is HTML, prefer `type=article` and file name ends with `.html`.
|
|
65
|
+
10. Every single-file deliverable MUST use a file name with an explicit extension.
|
|
66
|
+
11. If format is markdown/text, use `type=article` and `.md`/`.txt`.
|
|
67
|
+
12. If the user did not specify a document/text format, default to Markdown and use a `.md` file name.
|
|
68
|
+
13. For multi-file deliverables (game/site), you MUST prefer `type=game` with `files[]`, keep files as a folder structure, and do NOT zip before upload unless the user explicitly asks for a zip package.
|
|
69
|
+
14. Static multi-file game/site deliverables SHOULD include a root `index.html` so the preview link can open the homepage directly.
|
|
70
|
+
15. If a generated project requires starting a separate backend service, custom port, database, or long-running process to work, do NOT pretend the deliverables preview can run it. Tell the user that deliverables preview only supports static output, and that runtime projects need deployment/ingress instead.
|
|
71
|
+
16. After successful upload, the final assistant message MUST start with 1-2 short sentences in your own words, briefly describing what you generated for the user.
|
|
72
|
+
17. For content deliverables such as articles, documents, reports, and PDFs, add 3-5 concise bullets after the intro summarizing the actual sections, highlights, or conclusions. Do not reply with only a one-sentence upload notice.
|
|
73
|
+
18. The intro and bullets must be based on the actual deliverable content/request, not a fixed sentence like `交付物已上传成功,可直接在线预览或下载。`
|
|
74
|
+
19. If tool result contains `reply_markdown`, append that field verbatim after your intro and content bullets on the following lines.
|
|
75
|
+
20. Otherwise final reply MUST include Markdown links (short label + full URL target):
|
|
70
76
|
`预览链接:[点击预览](<full_url>)`
|
|
71
77
|
`下载链接:[点击下载](<full_url>)`
|
|
72
|
-
|
|
78
|
+
21. For multi-file/game deliverables, if the tool gives directory-style output, the second line may be:
|
|
73
79
|
`文件列表:[查看目录](<full_url>)`
|
|
74
80
|
Keep that format instead of forcing a zip link.
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
22. Do NOT only say "已保存到工作空间".
|
|
82
|
+
23. Do NOT append workspace path or a raw URL block after the Markdown links.
|
|
77
83
|
|
|
78
84
|
### Exception
|
|
79
85
|
|
package/index.js
CHANGED
|
@@ -53,6 +53,7 @@ const TEXT_EXTENSIONS = new Set([
|
|
|
53
53
|
const IMAGE_EXTENSIONS = new Set([".apng", ".avif", ".bmp", ".gif", ".ico", ".jpeg", ".jpg", ".png", ".svg", ".webp"]);
|
|
54
54
|
const VIDEO_EXTENSIONS = new Set([".avi", ".m4v", ".mkv", ".mov", ".mp4", ".mpeg", ".mpg", ".webm"]);
|
|
55
55
|
const PPT_EXTENSIONS = new Set([".ppt", ".pptx"]);
|
|
56
|
+
const PDF_EXTENSIONS = new Set([".pdf"]);
|
|
56
57
|
const ARCHIVE_EXTENSIONS = new Set([".7z", ".gz", ".rar", ".tar", ".tgz", ".zip"]);
|
|
57
58
|
const IGNORED_PATH_PARTS = new Set([
|
|
58
59
|
".git",
|
|
@@ -76,13 +77,16 @@ const RUNTIME_DELIVERABLES_GUIDANCE = [
|
|
|
76
77
|
"## Deliverables Runtime Guard (HARD CONSTRAINT)",
|
|
77
78
|
"",
|
|
78
79
|
"- These rules apply to the main agent and all subagents.",
|
|
79
|
-
"- When the user asks for a document, article, report, HTML page, Markdown file, PPT, image, archive, game, or any other file deliverable, you MUST create it under the current workspace `output/` directory and upload it with `deliverables__upload_deliverable`.",
|
|
80
|
-
"- For
|
|
80
|
+
"- When the user asks for a document, article, report, PDF, HTML page, Markdown file, PPT, image, archive, game, or any other file deliverable, you MUST create it under the current workspace `output/` directory and upload it with `deliverables__upload_deliverable`.",
|
|
81
|
+
"- For simple text PDF deliverables, call `deliverables__upload_deliverable` with `type: \"pdf\"`, a `.pdf` `file_name`, and `content_text`; the tool will render a basic PDF without installing dependencies.",
|
|
82
|
+
"- For existing binary deliverables such as rich PDFs, PPT, images, video, or zip files, pass `file_path` to `deliverables__upload_deliverable` after writing the file under `output/`; do not paste command output or partial base64 into `content_base64`.",
|
|
83
|
+
"- Never install PDF libraries or other packages at runtime just to create a deliverable. Use built-in tools or the PDF `content_text` fallback; if a rich PDF renderer is unavailable, say so clearly.",
|
|
84
|
+
"- If the user asks for PDF, upload the generated `.pdf` as `type: \"pdf\"`; do not substitute HTML or Markdown unless PDF generation truly failed and you clearly say so.",
|
|
81
85
|
"- Every single-file deliverable must use a file name with an explicit extension. If the user did not specify a document/text format, default to Markdown and use a `.md` file name.",
|
|
82
86
|
"- Do not use direct message attachments to deliver generated files. This includes the message tool attachment fields such as `media`, `path`, `filePath`, `buffer`, `attachment`, or similar file-carrying fields.",
|
|
83
87
|
"- Do not emit `MEDIA:` file references for user-facing deliverables. Deliverable links must come from the deliverables upload tool instead.",
|
|
84
88
|
"- If the deliverables upload tool returns an error, retry with a valid `file_path` or explain the upload failure; never invent or rewrite OSS/download URLs.",
|
|
85
|
-
"- After a successful upload, reply with a substantive content summary before the links. For documents/articles/reports,
|
|
89
|
+
"- After a successful upload, reply with a substantive content summary before the links. For documents/articles/reports/PDFs, include 1 short intro plus 3-5 concise bullets covering the actual sections, highlights, or findings; never respond with only a generic one-sentence upload notice.",
|
|
86
90
|
].join("\n");
|
|
87
91
|
|
|
88
92
|
const UPLOAD_DELIVERABLE_TOOL = {
|
|
@@ -109,7 +113,7 @@ const UPLOAD_DELIVERABLE_TOOL = {
|
|
|
109
113
|
},
|
|
110
114
|
type: {
|
|
111
115
|
type: "string",
|
|
112
|
-
enum: ["article", "game", "zip", "image", "video", "ppt", "link"],
|
|
116
|
+
enum: ["article", "game", "zip", "image", "video", "ppt", "pdf", "link"],
|
|
113
117
|
description: "交付物类型。若使用 file_path 且未准确判断,可由插件按文件扩展名兜底。",
|
|
114
118
|
},
|
|
115
119
|
file_name: {
|
|
@@ -122,7 +126,7 @@ const UPLOAD_DELIVERABLE_TOOL = {
|
|
|
122
126
|
},
|
|
123
127
|
content_text: {
|
|
124
128
|
type: "string",
|
|
125
|
-
description: "文本内容(Markdown、HTML 等)。",
|
|
129
|
+
description: "文本内容(Markdown、HTML 等)。type=pdf 时可传纯文本/Markdown,插件会生成基础 PDF。",
|
|
126
130
|
},
|
|
127
131
|
content_base64: {
|
|
128
132
|
type: "string",
|
|
@@ -215,6 +219,9 @@ function deliverableTypeForPath(filePath, isDirectory) {
|
|
|
215
219
|
if (PPT_EXTENSIONS.has(ext)) {
|
|
216
220
|
return "ppt";
|
|
217
221
|
}
|
|
222
|
+
if (PDF_EXTENSIONS.has(ext)) {
|
|
223
|
+
return "pdf";
|
|
224
|
+
}
|
|
218
225
|
if (ARCHIVE_EXTENSIONS.has(ext)) {
|
|
219
226
|
return "zip";
|
|
220
227
|
}
|
|
@@ -655,6 +662,8 @@ function defaultExtensionForDeliverable(args) {
|
|
|
655
662
|
return ".zip";
|
|
656
663
|
case "ppt":
|
|
657
664
|
return ".pptx";
|
|
665
|
+
case "pdf":
|
|
666
|
+
return ".pdf";
|
|
658
667
|
case "image":
|
|
659
668
|
return ".png";
|
|
660
669
|
case "video":
|
|
@@ -664,6 +673,155 @@ function defaultExtensionForDeliverable(args) {
|
|
|
664
673
|
}
|
|
665
674
|
}
|
|
666
675
|
|
|
676
|
+
function normalizePDFSourceText(contentText, fallbackTitle) {
|
|
677
|
+
const text = trimString(contentText) || trimString(fallbackTitle) || "PDF deliverable";
|
|
678
|
+
return text
|
|
679
|
+
.replace(/\r\n/g, "\n")
|
|
680
|
+
.replace(/\r/g, "\n")
|
|
681
|
+
.split("\n")
|
|
682
|
+
.map((line) =>
|
|
683
|
+
line
|
|
684
|
+
.replace(/^#{1,6}\s+/, "")
|
|
685
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
686
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
687
|
+
.replace(/^\s*[-*]\s+/, "- ")
|
|
688
|
+
.trimEnd(),
|
|
689
|
+
)
|
|
690
|
+
.join("\n")
|
|
691
|
+
.slice(0, 20000);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function visualWidth(text) {
|
|
695
|
+
let width = 0;
|
|
696
|
+
for (const ch of String(text || "")) {
|
|
697
|
+
width += ch.charCodeAt(0) > 0xff ? 2 : 1;
|
|
698
|
+
}
|
|
699
|
+
return width;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function wrapPDFTextLine(line, maxWidth) {
|
|
703
|
+
const input = String(line || "");
|
|
704
|
+
if (input.length === 0) {
|
|
705
|
+
return [""];
|
|
706
|
+
}
|
|
707
|
+
const out = [];
|
|
708
|
+
let current = "";
|
|
709
|
+
for (const ch of input) {
|
|
710
|
+
if (current && visualWidth(current + ch) > maxWidth) {
|
|
711
|
+
out.push(current);
|
|
712
|
+
current = ch;
|
|
713
|
+
} else {
|
|
714
|
+
current += ch;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (current) {
|
|
718
|
+
out.push(current);
|
|
719
|
+
}
|
|
720
|
+
return out;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function utf16BEHex(text) {
|
|
724
|
+
const input = String(text || "");
|
|
725
|
+
const bytes = [0xfe, 0xff];
|
|
726
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
727
|
+
const code = input.charCodeAt(i);
|
|
728
|
+
bytes.push((code >> 8) & 0xff, code & 0xff);
|
|
729
|
+
}
|
|
730
|
+
return Buffer.from(bytes).toString("hex").toUpperCase();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function buildPDFObjects(objects) {
|
|
734
|
+
const chunks = ["%PDF-1.4\n%\xE2\xE3\xCF\xD3\n"];
|
|
735
|
+
const offsets = [0];
|
|
736
|
+
let length = Buffer.byteLength(chunks[0], "utf8");
|
|
737
|
+
for (const obj of objects) {
|
|
738
|
+
const body = `${obj.id} 0 obj\n${obj.body}\nendobj\n`;
|
|
739
|
+
offsets[obj.id] = length;
|
|
740
|
+
chunks.push(body);
|
|
741
|
+
length += Buffer.byteLength(body, "utf8");
|
|
742
|
+
}
|
|
743
|
+
const xrefOffset = length;
|
|
744
|
+
const maxID = objects.reduce((max, obj) => Math.max(max, obj.id), 0);
|
|
745
|
+
const xref = ["xref\n", `0 ${maxID + 1}\n`, "0000000000 65535 f \n"];
|
|
746
|
+
for (let id = 1; id <= maxID; id += 1) {
|
|
747
|
+
xref.push(`${String(offsets[id] || 0).padStart(10, "0")} 00000 n \n`);
|
|
748
|
+
}
|
|
749
|
+
xref.push(
|
|
750
|
+
"trailer\n",
|
|
751
|
+
`<< /Size ${maxID + 1} /Root 1 0 R >>\n`,
|
|
752
|
+
"startxref\n",
|
|
753
|
+
`${xrefOffset}\n`,
|
|
754
|
+
"%%EOF\n",
|
|
755
|
+
);
|
|
756
|
+
return Buffer.from(chunks.join("") + xref.join(""), "utf8");
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function createBasicTextPDF(contentText, fileName) {
|
|
760
|
+
const normalized = normalizePDFSourceText(contentText, fileName);
|
|
761
|
+
const wrappedLines = [];
|
|
762
|
+
for (const line of normalized.split("\n")) {
|
|
763
|
+
wrappedLines.push(...wrapPDFTextLine(line, 72));
|
|
764
|
+
}
|
|
765
|
+
const linesPerPage = 44;
|
|
766
|
+
const pages = [];
|
|
767
|
+
for (let i = 0; i < wrappedLines.length; i += linesPerPage) {
|
|
768
|
+
pages.push(wrappedLines.slice(i, i + linesPerPage));
|
|
769
|
+
}
|
|
770
|
+
if (pages.length === 0) {
|
|
771
|
+
pages.push(["PDF deliverable"]);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
const pageIDs = pages.map((_, idx) => 3 + idx);
|
|
775
|
+
const fontID = 3 + pages.length;
|
|
776
|
+
const cidFontID = fontID + 1;
|
|
777
|
+
const contentStartID = cidFontID + 1;
|
|
778
|
+
const objects = [
|
|
779
|
+
{ id: 1, body: "<< /Type /Catalog /Pages 2 0 R >>" },
|
|
780
|
+
{
|
|
781
|
+
id: 2,
|
|
782
|
+
body: `<< /Type /Pages /Kids [${pageIDs.map((id) => `${id} 0 R`).join(" ")}] /Count ${pages.length} >>`,
|
|
783
|
+
},
|
|
784
|
+
];
|
|
785
|
+
|
|
786
|
+
pages.forEach((pageLines, idx) => {
|
|
787
|
+
const contentID = contentStartID + idx;
|
|
788
|
+
objects.push({
|
|
789
|
+
id: pageIDs[idx],
|
|
790
|
+
body: `<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /ProcSet [/PDF /Text] /Font << /F1 ${fontID} 0 R >> >> /Contents ${contentID} 0 R >>`,
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
objects.push(
|
|
795
|
+
{
|
|
796
|
+
id: fontID,
|
|
797
|
+
body: `<< /Type /Font /Subtype /Type0 /BaseFont /STSong-Light /Encoding /UniGB-UCS2-H /DescendantFonts [${cidFontID} 0 R] >>`,
|
|
798
|
+
},
|
|
799
|
+
{
|
|
800
|
+
id: cidFontID,
|
|
801
|
+
body:
|
|
802
|
+
"<< /Type /Font /Subtype /CIDFontType0 /BaseFont /STSong-Light /CIDSystemInfo << /Registry (Adobe) /Ordering (GB1) /Supplement 2 >> /DW 1000 >>",
|
|
803
|
+
},
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
pages.forEach((pageLines, idx) => {
|
|
807
|
+
const streamLines = ["BT", "/F1 12 Tf", "50 790 Td", "16 TL"];
|
|
808
|
+
pageLines.forEach((line) => {
|
|
809
|
+
if (line) {
|
|
810
|
+
streamLines.push(`<${utf16BEHex(line)}> Tj`);
|
|
811
|
+
}
|
|
812
|
+
streamLines.push("T*");
|
|
813
|
+
});
|
|
814
|
+
streamLines.push("ET");
|
|
815
|
+
const stream = `${streamLines.join("\n")}\n`;
|
|
816
|
+
objects.push({
|
|
817
|
+
id: contentStartID + idx,
|
|
818
|
+
body: `<< /Length ${Buffer.byteLength(stream, "utf8")} >>\nstream\n${stream}endstream`,
|
|
819
|
+
});
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
return buildPDFObjects(objects);
|
|
823
|
+
}
|
|
824
|
+
|
|
667
825
|
function normalizeDeliverableFileName(args, fallbackName) {
|
|
668
826
|
let fileName = trimString(args && args.file_name) || trimString(fallbackName) || "file";
|
|
669
827
|
if (trimString(args && args.type) === "link") {
|
|
@@ -758,8 +916,14 @@ function buildNativeUploadRequestBody(args) {
|
|
|
758
916
|
body.files = files;
|
|
759
917
|
return { body, isDirectory: true };
|
|
760
918
|
}
|
|
761
|
-
|
|
762
|
-
|
|
919
|
+
const contentText = (args && (args.content_text || args.contentText)) || "";
|
|
920
|
+
const contentBase64 = (args && (args.content_base64 || args.contentBase64)) || "";
|
|
921
|
+
if (body.type === "pdf" && trimString(contentText) && !trimString(contentBase64)) {
|
|
922
|
+
body.contentBase64 = createBasicTextPDF(contentText, body.fileName).toString("base64");
|
|
923
|
+
} else {
|
|
924
|
+
body.contentText = contentText;
|
|
925
|
+
body.contentBase64 = contentBase64;
|
|
926
|
+
}
|
|
763
927
|
return { body, isDirectory: false };
|
|
764
928
|
}
|
|
765
929
|
|
|
@@ -1787,6 +1951,8 @@ const plugin = {
|
|
|
1787
1951
|
};
|
|
1788
1952
|
|
|
1789
1953
|
plugin.__test = {
|
|
1954
|
+
buildNativeUploadRequestBody,
|
|
1955
|
+
createBasicTextPDF,
|
|
1790
1956
|
deliverableTypeForPath,
|
|
1791
1957
|
extractFileReferencesFromText,
|
|
1792
1958
|
extractDeliverableURL,
|
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.1.
|
|
5
|
+
"version": "1.1.2",
|
|
6
6
|
"skills": ["./skills"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@ description: 上传AI生成的文件到交付物系统,返回可分享的预
|
|
|
5
5
|
|
|
6
6
|
# 文件交付规则(强制)
|
|
7
7
|
|
|
8
|
-
当你生成了文件内容(文章、文档、介绍、攻略、HTML页面、Markdown、游戏、图片等),**必须**调用交付物上传工具,不得把文件内容直接粘贴在消息里。
|
|
8
|
+
当你生成了文件内容(文章、文档、介绍、攻略、PDF、HTML页面、Markdown、游戏、图片等),**必须**调用交付物上传工具,不得把文件内容直接粘贴在消息里。
|
|
9
9
|
|
|
10
10
|
## 工具名说明(强制)
|
|
11
11
|
|
|
@@ -31,17 +31,20 @@ description: 上传AI生成的文件到交付物系统,返回可分享的预
|
|
|
31
31
|
| `resource_id` | 消息元数据中的 `resource_id` 字段 | `user_xxx_lobster_yyy` |
|
|
32
32
|
| `group_id` | 消息元数据中的 `group_id` 或 `conversation_id` | `group_abc123` |
|
|
33
33
|
| `user_id` | 消息元数据中的 `sender_id` 或 `owner_id` | `cbb0fab9...` |
|
|
34
|
-
| `type` | 根据内容选择:`article`/`game`/`image`/`video`/`ppt`/`zip`/`link` | `article` |
|
|
34
|
+
| `type` | 根据内容选择:`article`/`game`/`image`/`video`/`ppt`/`pdf`/`zip`/`link` | `article` |
|
|
35
35
|
| `file_name` | 有意义的文件名,单文件必须含扩展名;若用户未指定文档格式,默认用 `.md` | `report-2026.html` |
|
|
36
|
-
| `content_text` | 文件的完整文本内容(HTML/Markdown
|
|
36
|
+
| `content_text` | 文件的完整文本内容(HTML/Markdown等);简单 PDF 可传文本/Markdown,由工具生成基础 PDF | `<html>...</html>` |
|
|
37
37
|
| `file_path` | PDF/PPT/图片/zip 等二进制文件的本地路径,推荐使用,避免手工复制 base64 | `output/sample.pdf` |
|
|
38
38
|
|
|
39
39
|
> **直接对话(direct chat)时**:`group_id` 填 `conversation_id`,`user_id` 填 `owner_id`。
|
|
40
40
|
|
|
41
41
|
## 二进制文件上传(强制)
|
|
42
42
|
|
|
43
|
-
- PDF
|
|
43
|
+
- 简单文本 PDF 可以直接调用 `deliverables__upload_deliverable`:`type=pdf`、`.pdf` 文件名、`content_text` 填完整内容;工具会生成基础 PDF,不需要额外依赖。
|
|
44
|
+
- 已经生成好的 PDF、PPT、图片、视频、zip 等二进制文件必须优先传 `file_path`,不要把 `base64` 命令输出复制到 `content_base64`。
|
|
44
45
|
- `file_path` 指向你已经写入 `output/` 的文件,例如 `output/sample.pdf`;上传工具会读取文件并自动编码。
|
|
46
|
+
- 不要为了生成 PDF 临时执行 `pip install`、`npm install`、`apt-get` 等依赖安装。使用已有工具或上面的 PDF `content_text` 兜底;如果复杂 PDF 渲染器不可用,要明确说明限制。
|
|
47
|
+
- 用户要求 PDF 时,必须上传 `.pdf` 交付物,`type` 使用 `pdf`;不要只返回 HTML/Markdown 作为替代格式。
|
|
45
48
|
- 如果上传工具返回错误,必须修正参数后重试,或明确告诉用户上传失败;禁止编造 OSS、下载或预览链接。
|
|
46
49
|
|
|
47
50
|
## 多文件(游戏)
|
|
@@ -64,9 +67,10 @@ files: [
|
|
|
64
67
|
## 上传成功后
|
|
65
68
|
|
|
66
69
|
优先规则:
|
|
67
|
-
- 最终消息必须先用你自己的话写 1-2
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
+
- 最终消息必须先用你自己的话写 1-2 句简短介绍,说明你生成了什么内容。
|
|
71
|
+
- 对文章、文档、报告、PDF 等内容型交付物,介绍后必须补充 3-5 条内容要点,概括实际章节、重点信息或结论;不要只写一句“已上传/已生成”。
|
|
72
|
+
- 介绍和要点必须基于实际产物内容,不要使用固定模板,例如:`交付物已上传成功,可直接在线预览或下载。`
|
|
73
|
+
- 如果工具结果里有 `reply_markdown`,把它原样放在内容要点后面,不要改写其中的链接。
|
|
70
74
|
|
|
71
75
|
否则只发给用户:
|
|
72
76
|
- 预览链接(`preview_url`,必须用 Markdown 链接格式)
|