@dai_ming/plugin-deliverables 1.0.17 → 1.0.19

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.
@@ -55,21 +55,23 @@ Tool name note:
55
55
  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
56
  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
57
  4. If format is HTML, prefer `type=article` and file name ends with `.html`.
58
- 5. If format is markdown/text, use `type=article` and `.md`/`.txt`.
59
- 6. 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.
60
- 7. Static multi-file game/site deliverables SHOULD include a root `index.html` so the preview link can open the homepage directly.
61
- 8. 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.
62
- 9. 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.
63
- 10. The intro must be based on the actual deliverable content/request, not a fixed sentence like `交付物已上传成功,可直接在线预览或下载。`
64
- 11. If tool result contains `reply_markdown`, append that field verbatim after your intro on the following lines.
65
- 12. Otherwise final reply MUST include Markdown links (short label + full URL target):
58
+ 5. Every single-file deliverable MUST use a file name with an explicit extension.
59
+ 6. If format is markdown/text, use `type=article` and `.md`/`.txt`.
60
+ 7. If the user did not specify a document/text format, default to Markdown and use a `.md` file name.
61
+ 8. 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.
62
+ 9. Static multi-file game/site deliverables SHOULD include a root `index.html` so the preview link can open the homepage directly.
63
+ 10. 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.
64
+ 11. 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.
65
+ 12. The intro must be based on the actual deliverable content/request, not a fixed sentence like `交付物已上传成功,可直接在线预览或下载。`
66
+ 13. If tool result contains `reply_markdown`, append that field verbatim after your intro on the following lines.
67
+ 14. Otherwise final reply MUST include Markdown links (short label + full URL target):
66
68
  `预览链接:[点击预览](<full_url>)`
67
69
  `下载链接:[点击下载](<full_url>)`
68
- 13. For multi-file/game deliverables, if the tool gives directory-style output, the second line may be:
70
+ 15. For multi-file/game deliverables, if the tool gives directory-style output, the second line may be:
69
71
  `文件列表:[查看目录](<full_url>)`
70
72
  Keep that format instead of forcing a zip link.
71
- 14. Do NOT only say "已保存到工作空间".
72
- 15. Do NOT append workspace path or a raw URL block after the Markdown links.
73
+ 16. Do NOT only say "已保存到工作空间".
74
+ 17. Do NOT append workspace path or a raw URL block after the Markdown links.
73
75
 
74
76
  ### Exception
75
77
 
package/index.js CHANGED
@@ -10,6 +10,7 @@ const RUNTIME_DELIVERABLES_GUIDANCE = [
10
10
  "",
11
11
  "- These rules apply to the main agent and all subagents.",
12
12
  "- 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`.",
13
+ "- 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.",
13
14
  "- 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.",
14
15
  "- Do not emit `MEDIA:` file references for user-facing deliverables. Deliverable links must come from the deliverables upload tool instead.",
15
16
  "- 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.",
@@ -428,6 +429,7 @@ function splitDeliverableMessage(text) {
428
429
  summaryLines = trimTrailingBlankLines(summaryLines);
429
430
 
430
431
  const linkLines = [];
432
+ const linkUrls = [];
431
433
  for (let i = firstLinkIndex; i < lines.length; i += 1) {
432
434
  const line = lines[i];
433
435
  if (isLinksHeading(line)) {
@@ -437,6 +439,10 @@ function splitDeliverableMessage(text) {
437
439
  const normalizedLine = stripBulletPrefix(line).trim();
438
440
  if (normalizedLine) {
439
441
  linkLines.push(normalizedLine);
442
+ const url = extractDeliverableURL(normalizedLine);
443
+ if (url) {
444
+ linkUrls.push(url);
445
+ }
440
446
  }
441
447
  }
442
448
  }
@@ -447,7 +453,11 @@ function splitDeliverableMessage(text) {
447
453
  return null;
448
454
  }
449
455
 
450
- return { summary, links };
456
+ return {
457
+ summary,
458
+ links,
459
+ primaryLinkUrl: linkUrls.length > 0 ? linkUrls[0] : "",
460
+ };
451
461
  }
452
462
 
453
463
  function cloneBody(body, content, suffix) {
@@ -462,6 +472,12 @@ function cloneBody(body, content, suffix) {
462
472
  return next;
463
473
  }
464
474
 
475
+ function cloneBodyAsFileLink(body, fileUrl, suffix) {
476
+ const next = cloneBody(body, [{ type: "file", file_url: { url: fileUrl } }], suffix);
477
+ delete next.msg_type;
478
+ return next;
479
+ }
480
+
465
481
  function shouldSplitPalzPayload(body) {
466
482
  if (!body || typeof body !== "object" || Array.isArray(body)) {
467
483
  return null;
@@ -490,6 +506,7 @@ function shouldSplitPalzPayload(body) {
490
506
  return {
491
507
  summary: cachedSummary,
492
508
  links: split.links,
509
+ primaryLinkUrl: split.primaryLinkUrl,
493
510
  };
494
511
  }
495
512
  return split;
@@ -569,12 +586,14 @@ function installPalzFetchPatch(api) {
569
586
  }
570
587
 
571
588
  const summaryBody = cloneBody(parsed, split.summary, "__summary");
572
- const linksBody = cloneBody(parsed, split.links, "__links");
589
+ const linksBody = split.primaryLinkUrl
590
+ ? cloneBodyAsFileLink(parsed, split.primaryLinkUrl, "__links")
591
+ : cloneBody(parsed, split.links, "__links");
573
592
  const summaryBodyStr = JSON.stringify(summaryBody);
574
593
  const linksBodyStr = JSON.stringify(linksBody);
575
594
 
576
595
  api.logger.info?.(
577
- `[plugin-deliverables] split deliverable reply for palz target=${String(parsed.conversation_id || "")} summaryMsgId=${String(summaryBody.msg_id || "")} linksMsgId=${String(linksBody.msg_id || "")}`,
596
+ `[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"}`,
578
597
  );
579
598
  api.logger.info?.(
580
599
  `[plugin-deliverables] palz summary request body_length=${summaryBodyStr.length}\n request_body=${summaryBodyStr}`,
@@ -22,6 +22,68 @@ var GATEWAY_PUBLIC = (process.env.CLAW_GATEWAY_PUBLIC_URL || GATEWAY_URL).replac
22
22
  // breaking deliverable uploads during rolling migration.
23
23
  var API_KEY = process.env.CLAW_GATEWAY_API_KEY || process.env.OPENCLAW_GATEWAY_API_KEY || "api-key-1";
24
24
 
25
+ function trimString(value) {
26
+ return typeof value === "string" ? value.trim() : "";
27
+ }
28
+
29
+ function hasExtension(fileName) {
30
+ return /\.[A-Za-z0-9]+$/.test(trimString(fileName));
31
+ }
32
+
33
+ function looksLikeHTMLContent(contentText) {
34
+ var text = trimString(contentText).toLowerCase();
35
+ if (!text) {
36
+ return false;
37
+ }
38
+ if (text.indexOf("<!doctype html") === 0 || text.indexOf("<html") === 0) {
39
+ return true;
40
+ }
41
+ return text.indexOf("<head") >= 0 ||
42
+ text.indexOf("<body") >= 0 ||
43
+ text.indexOf("<main") >= 0 ||
44
+ text.indexOf("<section") >= 0 ||
45
+ text.indexOf("<article") >= 0 ||
46
+ text.indexOf("<style") >= 0 ||
47
+ text.indexOf("<script") >= 0;
48
+ }
49
+
50
+ function defaultExtensionForDeliverable(args) {
51
+ var deliverableType = trimString(args && args.type);
52
+ switch (deliverableType) {
53
+ case "article":
54
+ case "game":
55
+ return looksLikeHTMLContent(args && args.content_text) ? ".html" : ".md";
56
+ case "zip":
57
+ return ".zip";
58
+ case "ppt":
59
+ return ".pptx";
60
+ case "image":
61
+ return ".png";
62
+ case "video":
63
+ return ".mp4";
64
+ default:
65
+ return "";
66
+ }
67
+ }
68
+
69
+ function normalizeDeliverableFileName(args) {
70
+ var fileName = trimString(args && args.file_name);
71
+ if (!fileName) {
72
+ fileName = "file";
73
+ }
74
+ if (trimString(args && args.type) === "link") {
75
+ return fileName;
76
+ }
77
+ if (Array.isArray(args && args.files) && args.files.length > 0) {
78
+ return fileName;
79
+ }
80
+ if (hasExtension(fileName)) {
81
+ return fileName;
82
+ }
83
+ var ext = defaultExtensionForDeliverable(args);
84
+ return ext ? fileName + ext : fileName;
85
+ }
86
+
25
87
  var TOOL_DEFS = [
26
88
  {
27
89
  name: "upload_deliverable",
@@ -54,7 +116,7 @@ var TOOL_DEFS = [
54
116
  },
55
117
  file_name: {
56
118
  type: "string",
57
- description: "用户可见的文件名,例如 adventure-game 或 report.md。多文件交付时这里应是目录名/项目名,不要写成 .zip,除非用户明确要求压缩包。"
119
+ description: "用户可见的文件名,单文件必须带扩展名,例如 report.md 或 report.html。若用户未指定文档格式,默认使用 .md。多文件交付时这里应是目录名/项目名,不要写成 .zip,除非用户明确要求压缩包。"
58
120
  },
59
121
  content_text: {
60
122
  type: "string",
@@ -170,12 +232,13 @@ function buildReplyMarkdown(opts) {
170
232
  // ─── Tool implementations ─────────────────────────────────────────────────────
171
233
 
172
234
  function uploadDeliverable(args) {
235
+ var normalizedFileName = normalizeDeliverableFileName(args);
173
236
  var body = {
174
237
  resourceId: args.resource_id,
175
238
  groupId: args.group_id,
176
239
  userId: args.user_id,
177
240
  type: args.type,
178
- fileName: args.file_name,
241
+ fileName: normalizedFileName,
179
242
  release: "" // overwritten below after release name derivation
180
243
  };
181
244
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-deliverables",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
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.17",
5
+ "version": "1.0.19",
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.17",
3
+ "version": "1.0.19",
4
4
  "description": "OpenClaw deliverables plugin — upload AI-generated files to OSS and return shareable preview/download links",
5
5
  "keywords": [
6
6
  "openclaw",
@@ -32,7 +32,7 @@ description: 上传AI生成的文件到交付物系统,返回可分享的预
32
32
  | `group_id` | 消息元数据中的 `group_id` 或 `conversation_id` | `group_abc123` |
33
33
  | `user_id` | 消息元数据中的 `sender_id` 或 `owner_id` | `cbb0fab9...` |
34
34
  | `type` | 根据内容选择:`article`/`game`/`image`/`video`/`ppt`/`zip`/`link` | `article` |
35
- | `file_name` | 有意义的文件名,含扩展名 | `report-2026.html` |
35
+ | `file_name` | 有意义的文件名,单文件必须含扩展名;若用户未指定文档格式,默认用 `.md` | `report-2026.html` |
36
36
  | `content_text` | 文件的完整文本内容(HTML/Markdown等) | `<html>...</html>` |
37
37
 
38
38
  > **直接对话(direct chat)时**:`group_id` 填 `conversation_id`,`user_id` 填 `owner_id`。