@dai_ming/plugin-deliverables 1.1.1 → 1.1.3
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/agents-rules/deliverables.md +21 -18
- package/index.js +227 -10
- package/package.json +1 -1
- package/skills/deliverables/SKILL.md +2 -1
|
@@ -56,28 +56,31 @@ Tool name note:
|
|
|
56
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.
|
|
57
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.
|
|
58
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.
|
|
59
|
-
4. For
|
|
60
|
-
5.
|
|
61
|
-
6.
|
|
62
|
-
7. If
|
|
63
|
-
8.
|
|
64
|
-
9. If format is
|
|
65
|
-
10.
|
|
66
|
-
11.
|
|
67
|
-
12.
|
|
68
|
-
13. If
|
|
69
|
-
14.
|
|
70
|
-
15.
|
|
71
|
-
16.
|
|
72
|
-
17.
|
|
73
|
-
18.
|
|
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. Local file names or relative paths such as `review.html` and `output/report.md` are not links. Do not use `type=link` for them; upload them with `file_path` or `content_text`.
|
|
66
|
+
11. Every single-file deliverable MUST use a file name with an explicit extension.
|
|
67
|
+
12. If format is markdown/text, use `type=article` and `.md`/`.txt`.
|
|
68
|
+
13. If the user did not specify a document/text format, default to Markdown and use a `.md` file name.
|
|
69
|
+
14. 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.
|
|
70
|
+
15. Static multi-file game/site deliverables SHOULD include a root `index.html` so the preview link can open the homepage directly.
|
|
71
|
+
16. 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.
|
|
72
|
+
17. 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.
|
|
73
|
+
18. 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.
|
|
74
|
+
19. The intro and bullets must be based on the actual deliverable content/request, not a fixed sentence like `交付物已上传成功,可直接在线预览或下载。`
|
|
75
|
+
20. If tool result contains `reply_markdown`, append that field verbatim after your intro and content bullets on the following lines.
|
|
76
|
+
21. Otherwise final reply MUST include Markdown links (short label + full URL target):
|
|
74
77
|
`预览链接:[点击预览](<full_url>)`
|
|
75
78
|
`下载链接:[点击下载](<full_url>)`
|
|
76
|
-
|
|
79
|
+
22. For multi-file/game deliverables, if the tool gives directory-style output, the second line may be:
|
|
77
80
|
`文件列表:[查看目录](<full_url>)`
|
|
78
81
|
Keep that format instead of forcing a zip link.
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
23. Do NOT only say "已保存到工作空间".
|
|
83
|
+
24. Do NOT append workspace path or a raw URL block after the Markdown links.
|
|
81
84
|
|
|
82
85
|
### Exception
|
|
83
86
|
|
package/index.js
CHANGED
|
@@ -26,6 +26,7 @@ const DELIVERABLE_LINK_LABEL_RE =
|
|
|
26
26
|
const INTERNAL_ABSOLUTE_PATH_RE =
|
|
27
27
|
/(?:\/data\/workspace-[^\s`"'<>,。;:、))\]}]+|\/home\/node\/\.openclaw\/workspace(?:-[A-Za-z0-9_.-]+)?\/[^\s`"'<>,。;:、))\]}]+)/gu;
|
|
28
28
|
const INLINE_FILE_REF_RE = /`([^`\n]{1,512}\.[A-Za-z0-9]{1,16})`/gu;
|
|
29
|
+
const MARKDOWN_FILE_LINK_RE = /\[[^\]\n]{0,200}\]\(([^)\s]+?\.[A-Za-z0-9]{1,16})\)/gu;
|
|
29
30
|
const OUTPUT_RELATIVE_REF_RE = /(?:^|[\s(::])((?:\.\/)?output\/[^\s`"'<>,。;:、))\]}]+\.[A-Za-z0-9]{1,16})/gu;
|
|
30
31
|
const FILE_EXTENSION_RE = /\.[A-Za-z0-9]{1,16}$/u;
|
|
31
32
|
const TEXT_EXTENSIONS = new Set([
|
|
@@ -78,7 +79,10 @@ const RUNTIME_DELIVERABLES_GUIDANCE = [
|
|
|
78
79
|
"",
|
|
79
80
|
"- These rules apply to the main agent and all subagents.",
|
|
80
81
|
"- 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
|
|
82
|
+
"- 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.",
|
|
83
|
+
"- 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`.",
|
|
84
|
+
"- Never use `type: \"link\"` for local files, relative paths, or generated files such as `review.html`; use `file_path`, `content_text`, or `files` so the file is uploaded.",
|
|
85
|
+
"- 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.",
|
|
82
86
|
"- 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.",
|
|
83
87
|
"- 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.",
|
|
84
88
|
"- 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.",
|
|
@@ -112,7 +116,7 @@ const UPLOAD_DELIVERABLE_TOOL = {
|
|
|
112
116
|
type: {
|
|
113
117
|
type: "string",
|
|
114
118
|
enum: ["article", "game", "zip", "image", "video", "ppt", "pdf", "link"],
|
|
115
|
-
description: "
|
|
119
|
+
description: "交付物类型。link 仅用于外部 http(s) URL;本地/生成文件必须使用 file_path、content_text 或 files。",
|
|
116
120
|
},
|
|
117
121
|
file_name: {
|
|
118
122
|
type: "string",
|
|
@@ -195,6 +199,15 @@ function isURLLike(value) {
|
|
|
195
199
|
return /^[a-z][a-z0-9+.-]*:\/\//i.test(String(value || ""));
|
|
196
200
|
}
|
|
197
201
|
|
|
202
|
+
function isHTTPURLLike(value) {
|
|
203
|
+
try {
|
|
204
|
+
const parsed = new URL(String(value || "").trim());
|
|
205
|
+
return (parsed.protocol === "http:" || parsed.protocol === "https:") && !!parsed.hostname;
|
|
206
|
+
} catch (_err) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
198
211
|
function fileExtension(filePath) {
|
|
199
212
|
return path.extname(stripTrailingPathPunctuation(filePath)).toLowerCase();
|
|
200
213
|
}
|
|
@@ -371,6 +384,9 @@ function extractFileReferencesFromText(text) {
|
|
|
371
384
|
for (const match of normalized.matchAll(INLINE_FILE_REF_RE)) {
|
|
372
385
|
add(match[0], match[1], "inline");
|
|
373
386
|
}
|
|
387
|
+
for (const match of normalized.matchAll(MARKDOWN_FILE_LINK_RE)) {
|
|
388
|
+
add(match[0], match[1], "markdown-link");
|
|
389
|
+
}
|
|
374
390
|
for (const match of normalized.matchAll(OUTPUT_RELATIVE_REF_RE)) {
|
|
375
391
|
add(match[1], match[1], "relative");
|
|
376
392
|
}
|
|
@@ -579,6 +595,31 @@ function deriveReleaseName() {
|
|
|
579
595
|
return release;
|
|
580
596
|
}
|
|
581
597
|
|
|
598
|
+
function normalizeBareUserID(value) {
|
|
599
|
+
let raw = trimString(value);
|
|
600
|
+
if (!raw) {
|
|
601
|
+
return "";
|
|
602
|
+
}
|
|
603
|
+
if (raw.indexOf("user_") === 0 && raw.indexOf("_lobster_") > 0) {
|
|
604
|
+
raw = raw.slice("user_".length, raw.indexOf("_lobster_"));
|
|
605
|
+
} else if (raw.indexOf("user-") === 0 || raw.indexOf("user_") === 0) {
|
|
606
|
+
raw = raw.slice("user-".length);
|
|
607
|
+
}
|
|
608
|
+
return raw;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function userIDFromRelease(release) {
|
|
612
|
+
return normalizeBareUserID(release);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function userIDFromResourceID(resourceID) {
|
|
616
|
+
const raw = trimString(resourceID);
|
|
617
|
+
if (raw.indexOf("user_") !== 0 && raw.indexOf("user-") !== 0) {
|
|
618
|
+
return "";
|
|
619
|
+
}
|
|
620
|
+
return normalizeBareUserID(raw);
|
|
621
|
+
}
|
|
622
|
+
|
|
582
623
|
function httpJSONRequest(method, requestPath, body) {
|
|
583
624
|
return new Promise((resolve, reject) => {
|
|
584
625
|
let parsed;
|
|
@@ -671,6 +712,155 @@ function defaultExtensionForDeliverable(args) {
|
|
|
671
712
|
}
|
|
672
713
|
}
|
|
673
714
|
|
|
715
|
+
function normalizePDFSourceText(contentText, fallbackTitle) {
|
|
716
|
+
const text = trimString(contentText) || trimString(fallbackTitle) || "PDF deliverable";
|
|
717
|
+
return text
|
|
718
|
+
.replace(/\r\n/g, "\n")
|
|
719
|
+
.replace(/\r/g, "\n")
|
|
720
|
+
.split("\n")
|
|
721
|
+
.map((line) =>
|
|
722
|
+
line
|
|
723
|
+
.replace(/^#{1,6}\s+/, "")
|
|
724
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
725
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
726
|
+
.replace(/^\s*[-*]\s+/, "- ")
|
|
727
|
+
.trimEnd(),
|
|
728
|
+
)
|
|
729
|
+
.join("\n")
|
|
730
|
+
.slice(0, 20000);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function visualWidth(text) {
|
|
734
|
+
let width = 0;
|
|
735
|
+
for (const ch of String(text || "")) {
|
|
736
|
+
width += ch.charCodeAt(0) > 0xff ? 2 : 1;
|
|
737
|
+
}
|
|
738
|
+
return width;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function wrapPDFTextLine(line, maxWidth) {
|
|
742
|
+
const input = String(line || "");
|
|
743
|
+
if (input.length === 0) {
|
|
744
|
+
return [""];
|
|
745
|
+
}
|
|
746
|
+
const out = [];
|
|
747
|
+
let current = "";
|
|
748
|
+
for (const ch of input) {
|
|
749
|
+
if (current && visualWidth(current + ch) > maxWidth) {
|
|
750
|
+
out.push(current);
|
|
751
|
+
current = ch;
|
|
752
|
+
} else {
|
|
753
|
+
current += ch;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (current) {
|
|
757
|
+
out.push(current);
|
|
758
|
+
}
|
|
759
|
+
return out;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function utf16BEHex(text) {
|
|
763
|
+
const input = String(text || "");
|
|
764
|
+
const bytes = [0xfe, 0xff];
|
|
765
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
766
|
+
const code = input.charCodeAt(i);
|
|
767
|
+
bytes.push((code >> 8) & 0xff, code & 0xff);
|
|
768
|
+
}
|
|
769
|
+
return Buffer.from(bytes).toString("hex").toUpperCase();
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function buildPDFObjects(objects) {
|
|
773
|
+
const chunks = ["%PDF-1.4\n%\xE2\xE3\xCF\xD3\n"];
|
|
774
|
+
const offsets = [0];
|
|
775
|
+
let length = Buffer.byteLength(chunks[0], "utf8");
|
|
776
|
+
for (const obj of objects) {
|
|
777
|
+
const body = `${obj.id} 0 obj\n${obj.body}\nendobj\n`;
|
|
778
|
+
offsets[obj.id] = length;
|
|
779
|
+
chunks.push(body);
|
|
780
|
+
length += Buffer.byteLength(body, "utf8");
|
|
781
|
+
}
|
|
782
|
+
const xrefOffset = length;
|
|
783
|
+
const maxID = objects.reduce((max, obj) => Math.max(max, obj.id), 0);
|
|
784
|
+
const xref = ["xref\n", `0 ${maxID + 1}\n`, "0000000000 65535 f \n"];
|
|
785
|
+
for (let id = 1; id <= maxID; id += 1) {
|
|
786
|
+
xref.push(`${String(offsets[id] || 0).padStart(10, "0")} 00000 n \n`);
|
|
787
|
+
}
|
|
788
|
+
xref.push(
|
|
789
|
+
"trailer\n",
|
|
790
|
+
`<< /Size ${maxID + 1} /Root 1 0 R >>\n`,
|
|
791
|
+
"startxref\n",
|
|
792
|
+
`${xrefOffset}\n`,
|
|
793
|
+
"%%EOF\n",
|
|
794
|
+
);
|
|
795
|
+
return Buffer.from(chunks.join("") + xref.join(""), "utf8");
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function createBasicTextPDF(contentText, fileName) {
|
|
799
|
+
const normalized = normalizePDFSourceText(contentText, fileName);
|
|
800
|
+
const wrappedLines = [];
|
|
801
|
+
for (const line of normalized.split("\n")) {
|
|
802
|
+
wrappedLines.push(...wrapPDFTextLine(line, 72));
|
|
803
|
+
}
|
|
804
|
+
const linesPerPage = 44;
|
|
805
|
+
const pages = [];
|
|
806
|
+
for (let i = 0; i < wrappedLines.length; i += linesPerPage) {
|
|
807
|
+
pages.push(wrappedLines.slice(i, i + linesPerPage));
|
|
808
|
+
}
|
|
809
|
+
if (pages.length === 0) {
|
|
810
|
+
pages.push(["PDF deliverable"]);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const pageIDs = pages.map((_, idx) => 3 + idx);
|
|
814
|
+
const fontID = 3 + pages.length;
|
|
815
|
+
const cidFontID = fontID + 1;
|
|
816
|
+
const contentStartID = cidFontID + 1;
|
|
817
|
+
const objects = [
|
|
818
|
+
{ id: 1, body: "<< /Type /Catalog /Pages 2 0 R >>" },
|
|
819
|
+
{
|
|
820
|
+
id: 2,
|
|
821
|
+
body: `<< /Type /Pages /Kids [${pageIDs.map((id) => `${id} 0 R`).join(" ")}] /Count ${pages.length} >>`,
|
|
822
|
+
},
|
|
823
|
+
];
|
|
824
|
+
|
|
825
|
+
pages.forEach((pageLines, idx) => {
|
|
826
|
+
const contentID = contentStartID + idx;
|
|
827
|
+
objects.push({
|
|
828
|
+
id: pageIDs[idx],
|
|
829
|
+
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 >>`,
|
|
830
|
+
});
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
objects.push(
|
|
834
|
+
{
|
|
835
|
+
id: fontID,
|
|
836
|
+
body: `<< /Type /Font /Subtype /Type0 /BaseFont /STSong-Light /Encoding /UniGB-UCS2-H /DescendantFonts [${cidFontID} 0 R] >>`,
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
id: cidFontID,
|
|
840
|
+
body:
|
|
841
|
+
"<< /Type /Font /Subtype /CIDFontType0 /BaseFont /STSong-Light /CIDSystemInfo << /Registry (Adobe) /Ordering (GB1) /Supplement 2 >> /DW 1000 >>",
|
|
842
|
+
},
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
pages.forEach((pageLines, idx) => {
|
|
846
|
+
const streamLines = ["BT", "/F1 12 Tf", "50 790 Td", "16 TL"];
|
|
847
|
+
pageLines.forEach((line) => {
|
|
848
|
+
if (line) {
|
|
849
|
+
streamLines.push(`<${utf16BEHex(line)}> Tj`);
|
|
850
|
+
}
|
|
851
|
+
streamLines.push("T*");
|
|
852
|
+
});
|
|
853
|
+
streamLines.push("ET");
|
|
854
|
+
const stream = `${streamLines.join("\n")}\n`;
|
|
855
|
+
objects.push({
|
|
856
|
+
id: contentStartID + idx,
|
|
857
|
+
body: `<< /Length ${Buffer.byteLength(stream, "utf8")} >>\nstream\n${stream}endstream`,
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
return buildPDFObjects(objects);
|
|
862
|
+
}
|
|
863
|
+
|
|
674
864
|
function normalizeDeliverableFileName(args, fallbackName) {
|
|
675
865
|
let fileName = trimString(args && args.file_name) || trimString(fallbackName) || "file";
|
|
676
866
|
if (trimString(args && args.type) === "link") {
|
|
@@ -733,16 +923,23 @@ function buildNativeUploadRequestBody(args) {
|
|
|
733
923
|
}
|
|
734
924
|
|
|
735
925
|
const body = {
|
|
736
|
-
resourceId: args.resource_id,
|
|
737
|
-
groupId: args.group_id,
|
|
738
|
-
userId:
|
|
926
|
+
resourceId: trimString(args && args.resource_id),
|
|
927
|
+
groupId: trimString(args && args.group_id) || trimString(args && args.resource_id),
|
|
928
|
+
userId:
|
|
929
|
+
normalizeBareUserID(args && args.user_id) ||
|
|
930
|
+
userIDFromResourceID(args && args.resource_id) ||
|
|
931
|
+
userIDFromRelease(deriveReleaseName()),
|
|
739
932
|
release: deriveReleaseName(),
|
|
740
933
|
};
|
|
741
934
|
|
|
742
935
|
if (fileCandidate) {
|
|
743
936
|
const stat = safeStat(fileCandidate.path);
|
|
744
937
|
const isDirectory = !!(stat && stat.isDirectory());
|
|
745
|
-
|
|
938
|
+
const requestedType = trimString(args.type);
|
|
939
|
+
body.type =
|
|
940
|
+
requestedType === "link"
|
|
941
|
+
? deliverableTypeForPath(fileCandidate.path, isDirectory)
|
|
942
|
+
: requestedType || deliverableTypeForPath(fileCandidate.path, isDirectory);
|
|
746
943
|
body.fileName = normalizeDeliverableFileName(args, fileCandidate.fileName);
|
|
747
944
|
if (isDirectory) {
|
|
748
945
|
const files = collectDirectoryFiles(fileCandidate.path);
|
|
@@ -762,11 +959,29 @@ function buildNativeUploadRequestBody(args) {
|
|
|
762
959
|
body.fileName = normalizeDeliverableFileName(args, "");
|
|
763
960
|
const files = normalizeNativeToolFiles(args && args.files);
|
|
764
961
|
if (files.length > 0) {
|
|
962
|
+
if (body.type === "link") {
|
|
963
|
+
body.type = "game";
|
|
964
|
+
}
|
|
765
965
|
body.files = files;
|
|
766
966
|
return { body, isDirectory: true };
|
|
767
967
|
}
|
|
768
|
-
|
|
769
|
-
|
|
968
|
+
const contentText = (args && (args.content_text || args.contentText)) || "";
|
|
969
|
+
const contentBase64 = (args && (args.content_base64 || args.contentBase64)) || "";
|
|
970
|
+
if (body.type === "link") {
|
|
971
|
+
if (trimString(contentText) || trimString(contentBase64)) {
|
|
972
|
+
body.type = deliverableTypeForPath(body.fileName, false);
|
|
973
|
+
} else if (!isHTTPURLLike(body.fileName)) {
|
|
974
|
+
throw new Error(
|
|
975
|
+
"type=link requires file_name to be an absolute http(s) URL; use file_path or content_text for local/generated files",
|
|
976
|
+
);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (body.type === "pdf" && trimString(contentText) && !trimString(contentBase64)) {
|
|
980
|
+
body.contentBase64 = createBasicTextPDF(contentText, body.fileName).toString("base64");
|
|
981
|
+
} else {
|
|
982
|
+
body.contentText = contentText;
|
|
983
|
+
body.contentBase64 = contentBase64;
|
|
984
|
+
}
|
|
770
985
|
return { body, isDirectory: false };
|
|
771
986
|
}
|
|
772
987
|
|
|
@@ -819,8 +1034,8 @@ async function uploadCandidate(candidate, body) {
|
|
|
819
1034
|
}
|
|
820
1035
|
const requestBody = {
|
|
821
1036
|
resourceId,
|
|
822
|
-
groupId: payloadGroupId(body),
|
|
823
|
-
userId: payloadUserId(body),
|
|
1037
|
+
groupId: payloadGroupId(body) || resourceId,
|
|
1038
|
+
userId: normalizeBareUserID(payloadUserId(body)) || userIDFromResourceID(resourceId) || userIDFromRelease(deriveReleaseName()),
|
|
824
1039
|
release: deriveReleaseName(),
|
|
825
1040
|
type: deliverableTypeForPath(candidate.path, stat.isDirectory()),
|
|
826
1041
|
fileName: candidate.fileName || path.basename(candidate.path),
|
|
@@ -1794,6 +2009,8 @@ const plugin = {
|
|
|
1794
2009
|
};
|
|
1795
2010
|
|
|
1796
2011
|
plugin.__test = {
|
|
2012
|
+
buildNativeUploadRequestBody,
|
|
2013
|
+
createBasicTextPDF,
|
|
1797
2014
|
deliverableTypeForPath,
|
|
1798
2015
|
extractFileReferencesFromText,
|
|
1799
2016
|
extractDeliverableURL,
|
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@ 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`/`pdf`/`zip
|
|
34
|
+
| `type` | 根据内容选择:`article`/`game`/`image`/`video`/`ppt`/`pdf`/`zip`;`link` 仅用于外部 `http(s)` URL | `article` |
|
|
35
35
|
| `file_name` | 有意义的文件名,单文件必须含扩展名;若用户未指定文档格式,默认用 `.md` | `report-2026.html` |
|
|
36
36
|
| `content_text` | 文件的完整文本内容(HTML/Markdown等) | `<html>...</html>` |
|
|
37
37
|
| `file_path` | PDF/PPT/图片/zip 等二进制文件的本地路径,推荐使用,避免手工复制 base64 | `output/sample.pdf` |
|
|
@@ -44,6 +44,7 @@ description: 上传AI生成的文件到交付物系统,返回可分享的预
|
|
|
44
44
|
- `file_path` 指向你已经写入 `output/` 的文件,例如 `output/sample.pdf`;上传工具会读取文件并自动编码。
|
|
45
45
|
- 用户要求 PDF 时,必须上传生成好的 `.pdf` 文件,`type` 使用 `pdf`;不要只返回 HTML/Markdown 作为替代格式。
|
|
46
46
|
- 如果上传工具返回错误,必须修正参数后重试,或明确告诉用户上传失败;禁止编造 OSS、下载或预览链接。
|
|
47
|
+
- 本地文件名或相对路径(例如 `review.html`、`output/report.md`)不是外部链接,不能用 `type=link`;必须用 `file_path` 或 `content_text` 上传。
|
|
47
48
|
|
|
48
49
|
## 多文件(游戏)
|
|
49
50
|
|