@dai_ming/plugin-deliverables 1.0.14 → 1.0.16
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 +216 -0
- package/README.md +188 -90
- package/agents-rules/deliverables.md +30 -34
- package/index.js +239 -55
- package/mcp-servers/deliverables.js +43 -324
- package/openclaw-plugin.json +2 -4
- package/openclaw.plugin.json +4 -3
- package/package.json +9 -9
- package/skills/deliverables/SKILL.md +12 -20
- package/install.js +0 -559
|
@@ -14,21 +14,19 @@
|
|
|
14
14
|
|
|
15
15
|
var http = require("http");
|
|
16
16
|
var https = require("https");
|
|
17
|
-
var path = require("path");
|
|
18
17
|
|
|
19
18
|
var GATEWAY_URL = (process.env.CLAW_GATEWAY_URL || "http://claw-gateway:8080").replace(/\/$/, "");
|
|
20
|
-
var
|
|
19
|
+
var GATEWAY_PUBLIC = (process.env.CLAW_GATEWAY_PUBLIC_URL || GATEWAY_URL).replace(/\/$/, "");
|
|
21
20
|
// Some existing pods were created without CLAW_GATEWAY_API_KEY injected.
|
|
22
21
|
// Keep env-driven behavior first, but provide a dev-compatible fallback to avoid
|
|
23
22
|
// breaking deliverable uploads during rolling migration.
|
|
24
23
|
var API_KEY = process.env.CLAW_GATEWAY_API_KEY || process.env.OPENCLAW_GATEWAY_API_KEY || "api-key-1";
|
|
25
|
-
var GATEWAY_PUBLIC = resolveGatewayPublicBase();
|
|
26
24
|
|
|
27
25
|
var TOOL_DEFS = [
|
|
28
26
|
{
|
|
29
27
|
name: "upload_deliverable",
|
|
30
28
|
description: [
|
|
31
|
-
"将 AI
|
|
29
|
+
"将 AI 生成的内容(文章、游戏、图片等)上传为交付物,返回可分享的下载/预览链接。",
|
|
32
30
|
"单文件交付物:提供 content_text 或 content_base64。",
|
|
33
31
|
"多文件交付物(网页游戏/静态站点等):必须优先提供 files 列表,每项包含 name(相对路径)和 content_text 或 content_base64,不要先打 zip。",
|
|
34
32
|
"静态多文件预览建议在根目录提供 index.html;需要单独启动端口/后端服务的项目不属于交付物预览范围,应走部署流程。",
|
|
@@ -56,7 +54,7 @@ var TOOL_DEFS = [
|
|
|
56
54
|
},
|
|
57
55
|
file_name: {
|
|
58
56
|
type: "string",
|
|
59
|
-
description: "
|
|
57
|
+
description: "用户可见的文件名,例如 adventure-game 或 report.md。多文件交付时这里应是目录名/项目名,不要写成 .zip,除非用户明确要求压缩包。"
|
|
60
58
|
},
|
|
61
59
|
content_text: {
|
|
62
60
|
type: "string",
|
|
@@ -95,269 +93,6 @@ function parseURL(rawURL) {
|
|
|
95
93
|
}
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
function isPrivateIPv4(hostname) {
|
|
99
|
-
if (!hostname) return false;
|
|
100
|
-
if (/^10\./.test(hostname)) return true;
|
|
101
|
-
if (/^127\./.test(hostname)) return true;
|
|
102
|
-
if (/^192\.168\./.test(hostname)) return true;
|
|
103
|
-
var m = hostname.match(/^172\.(\d+)\./);
|
|
104
|
-
if (m) {
|
|
105
|
-
var second = parseInt(m[1], 10);
|
|
106
|
-
return second >= 16 && second <= 31;
|
|
107
|
-
}
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function isInternalHostname(hostname) {
|
|
112
|
-
var host = String(hostname || "").toLowerCase();
|
|
113
|
-
if (!host) return false;
|
|
114
|
-
if (host === "localhost" || host === "claw-gateway") return true;
|
|
115
|
-
if (host.indexOf(".svc") >= 0 || host.indexOf(".cluster.local") >= 0) return true;
|
|
116
|
-
if (host.indexOf("claw-gateway:") === 0) return true;
|
|
117
|
-
if (isPrivateIPv4(host)) return true;
|
|
118
|
-
if (host.indexOf(".") < 0) return true;
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function isProbablyInternalURL(rawURL) {
|
|
123
|
-
var parsed = parseURL(rawURL);
|
|
124
|
-
if (!parsed) return false;
|
|
125
|
-
return isInternalHostname(parsed.hostname);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function envGatewayPublicFallback() {
|
|
129
|
-
var helmEnv = String(process.env.HELM_ENV || process.env.ENV || "").toLowerCase();
|
|
130
|
-
if (helmEnv === "prod" || helmEnv === "production" || helmEnv === "online") {
|
|
131
|
-
return "https://claw-gateway.csagentai.com";
|
|
132
|
-
}
|
|
133
|
-
if (helmEnv === "dev" || helmEnv === "development" || helmEnv === "staging" || helmEnv === "stage") {
|
|
134
|
-
return "https://claw-dev.csaiagent.com";
|
|
135
|
-
}
|
|
136
|
-
return "";
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function resolveGatewayPublicBase() {
|
|
140
|
-
var candidate = RAW_GATEWAY_PUBLIC;
|
|
141
|
-
if (candidate && !isProbablyInternalURL(candidate)) {
|
|
142
|
-
return candidate;
|
|
143
|
-
}
|
|
144
|
-
var fallback = envGatewayPublicFallback();
|
|
145
|
-
if (fallback) {
|
|
146
|
-
return fallback.replace(/\/$/, "");
|
|
147
|
-
}
|
|
148
|
-
return candidate;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function absolutizePublicURL(rawURL) {
|
|
152
|
-
var val = String(rawURL || "").trim();
|
|
153
|
-
if (!val) return "";
|
|
154
|
-
if (/^https?:\/\//i.test(val)) {
|
|
155
|
-
if (isProbablyInternalURL(val) && GATEWAY_PUBLIC && !isProbablyInternalURL(GATEWAY_PUBLIC)) {
|
|
156
|
-
var parsed = parseURL(val);
|
|
157
|
-
if (parsed) {
|
|
158
|
-
return GATEWAY_PUBLIC + parsed.pathname + (parsed.search || "") + (parsed.hash || "");
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return val;
|
|
162
|
-
}
|
|
163
|
-
if (val.charAt(0) !== "/") {
|
|
164
|
-
val = "/" + val;
|
|
165
|
-
}
|
|
166
|
-
return (GATEWAY_PUBLIC || RAW_GATEWAY_PUBLIC || "").replace(/\/$/, "") + val;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function outputPublicBase() {
|
|
170
|
-
return absolutizePublicURL("/openclaw-gateway/output");
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function encodePathSegments(parts) {
|
|
174
|
-
return parts.map(function(part) {
|
|
175
|
-
return encodeURIComponent(String(part || ""));
|
|
176
|
-
}).join("/");
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function sanitizePathSegment(v) {
|
|
180
|
-
var s = String(v || "").trim();
|
|
181
|
-
if (!s) return "unknown";
|
|
182
|
-
s = s.replace(/\//g, "_");
|
|
183
|
-
s = s.replace(/\\/g, "_");
|
|
184
|
-
s = s.replace(/\.\./g, "_");
|
|
185
|
-
return s;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function sanitizeFileName(v) {
|
|
189
|
-
var name = String(v || "").trim();
|
|
190
|
-
if (!name) return "file";
|
|
191
|
-
name = name.replace(/^.*[\/\\]/, "");
|
|
192
|
-
name = name.replace(/\//g, "_");
|
|
193
|
-
name = name.replace(/\\/g, "_");
|
|
194
|
-
name = name.replace(/\.\./g, "_");
|
|
195
|
-
if (!name || name === ".") return "file";
|
|
196
|
-
return name;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function fileExtension(name) {
|
|
200
|
-
return path.extname(String(name || ""));
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function hasFileExtension(name) {
|
|
204
|
-
return !!fileExtension(name);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function looksLikeHTML(text) {
|
|
208
|
-
var s = String(text || "").trim().toLowerCase();
|
|
209
|
-
if (!s) return false;
|
|
210
|
-
return s.indexOf("<!doctype html") === 0 ||
|
|
211
|
-
s.indexOf("<html") === 0 ||
|
|
212
|
-
s.indexOf("<head") === 0 ||
|
|
213
|
-
s.indexOf("<body") === 0;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function looksLikeMarkdown(text) {
|
|
217
|
-
var s = String(text || "").trim();
|
|
218
|
-
if (!s) return false;
|
|
219
|
-
return /^#{1,6}\s+\S/m.test(s) ||
|
|
220
|
-
/^\s*[-*+]\s+\S/m.test(s) ||
|
|
221
|
-
/^\s*\d+\.\s+\S/m.test(s) ||
|
|
222
|
-
/\[[^\]]+\]\([^)]+\)/.test(s) ||
|
|
223
|
-
/```/.test(s);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function defaultExtensionForDeliverable(type, contentText, files) {
|
|
227
|
-
var kind = String(type || "").trim().toLowerCase();
|
|
228
|
-
if (files && files.length > 0) return "";
|
|
229
|
-
switch (kind) {
|
|
230
|
-
case "article":
|
|
231
|
-
if (looksLikeHTML(contentText)) return ".html";
|
|
232
|
-
return ".md";
|
|
233
|
-
case "game":
|
|
234
|
-
return ".html";
|
|
235
|
-
case "image":
|
|
236
|
-
return ".png";
|
|
237
|
-
case "video":
|
|
238
|
-
return ".mp4";
|
|
239
|
-
case "zip":
|
|
240
|
-
return ".zip";
|
|
241
|
-
case "ppt":
|
|
242
|
-
return ".pptx";
|
|
243
|
-
case "link":
|
|
244
|
-
return "";
|
|
245
|
-
default:
|
|
246
|
-
if (looksLikeHTML(contentText)) return ".html";
|
|
247
|
-
if (looksLikeMarkdown(contentText)) return ".md";
|
|
248
|
-
return "";
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function normalizeDeliverableFileName(fileName, type, contentText, files) {
|
|
253
|
-
if (String(type || "").trim().toLowerCase() === "link") {
|
|
254
|
-
return String(fileName || "").trim();
|
|
255
|
-
}
|
|
256
|
-
var normalized = sanitizeFileName(fileName);
|
|
257
|
-
if (files && files.length > 0) {
|
|
258
|
-
return normalized || "deliverable";
|
|
259
|
-
}
|
|
260
|
-
if (hasFileExtension(normalized)) {
|
|
261
|
-
return normalized;
|
|
262
|
-
}
|
|
263
|
-
var ext = defaultExtensionForDeliverable(type, contentText, files);
|
|
264
|
-
if (!normalized) normalized = "deliverable";
|
|
265
|
-
return normalized + ext;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function extractUserIDFromResource(resourceID) {
|
|
269
|
-
var trimmed = String(resourceID || "").trim();
|
|
270
|
-
var prefix = "user_";
|
|
271
|
-
if (!trimmed || trimmed.indexOf(prefix) !== 0) {
|
|
272
|
-
return "";
|
|
273
|
-
}
|
|
274
|
-
var rest = trimmed.slice(prefix.length);
|
|
275
|
-
var end = rest.indexOf("_");
|
|
276
|
-
if (end <= 0) {
|
|
277
|
-
return "";
|
|
278
|
-
}
|
|
279
|
-
return rest.slice(0, end);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
function deriveRelease(release, resourceID, userID) {
|
|
283
|
-
var rel = sanitizePathSegment(release);
|
|
284
|
-
if (rel && rel !== "unknown") {
|
|
285
|
-
return rel;
|
|
286
|
-
}
|
|
287
|
-
var uid = String(userID || "").trim();
|
|
288
|
-
if (!uid) {
|
|
289
|
-
uid = extractUserIDFromResource(resourceID);
|
|
290
|
-
}
|
|
291
|
-
if (uid) {
|
|
292
|
-
return "user-" + uid;
|
|
293
|
-
}
|
|
294
|
-
return "unknown";
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function looksLikePreviewRoute(rawURL) {
|
|
298
|
-
var val = String(rawURL || "").trim();
|
|
299
|
-
return /\/openclaw-gateway\/preview\/[^/?#]+(?:[?#].*)?$/i.test(val);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function needsOutputURLRepair(data) {
|
|
303
|
-
if (!data) return false;
|
|
304
|
-
if (String(data.backend || "").toLowerCase() !== "output") {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
var previewURL = String(data.previewUrl || "").trim();
|
|
308
|
-
var downloadURL = String(data.downloadUrl || "").trim();
|
|
309
|
-
if (!previewURL || !downloadURL) {
|
|
310
|
-
return true;
|
|
311
|
-
}
|
|
312
|
-
if (previewURL.charAt(0) === "/" || downloadURL.charAt(0) === "/") {
|
|
313
|
-
return true;
|
|
314
|
-
}
|
|
315
|
-
if (isProbablyInternalURL(previewURL) || isProbablyInternalURL(downloadURL)) {
|
|
316
|
-
return true;
|
|
317
|
-
}
|
|
318
|
-
if (looksLikePreviewRoute(previewURL) || looksLikePreviewRoute(downloadURL)) {
|
|
319
|
-
return true;
|
|
320
|
-
}
|
|
321
|
-
return false;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function fetchDeliverableMeta(uuid) {
|
|
325
|
-
if (!uuid) return Promise.resolve(null);
|
|
326
|
-
return httpRequest("GET", "/openclaw-gateway/be/deliverables/" + encodeURIComponent(uuid)).then(function(resp) {
|
|
327
|
-
return resp.body.data || resp.body || null;
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function buildOutputURLs(meta, args, body, data) {
|
|
332
|
-
var base = outputPublicBase();
|
|
333
|
-
if (!base) {
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
var resourceID = sanitizePathSegment((meta && meta.resourceId) || body.resourceId || args.resource_id);
|
|
337
|
-
var userID = (meta && meta.userId) || body.userId || args.user_id;
|
|
338
|
-
var release = deriveRelease((meta && meta.release) || body.release, resourceID, userID);
|
|
339
|
-
var uuid = String((meta && meta.uuid) || (data && data.uuid) || "").trim();
|
|
340
|
-
if (!resourceID || !uuid) {
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
var isDirectory = !!(args.files && args.files.length > 0) || String((meta && meta.mime) || "").trim() === "application/x-directory";
|
|
344
|
-
if (isDirectory) {
|
|
345
|
-
var dirURL = base.replace(/\/$/, "") + "/" + encodePathSegments([release, resourceID, uuid]);
|
|
346
|
-
return {
|
|
347
|
-
previewURL: dirURL,
|
|
348
|
-
downloadURL: dirURL + "?list=1",
|
|
349
|
-
isDirectory: true
|
|
350
|
-
};
|
|
351
|
-
}
|
|
352
|
-
var fileName = sanitizeFileName((meta && meta.fileName) || body.fileName || args.file_name);
|
|
353
|
-
var fileURL = base.replace(/\/$/, "") + "/" + encodePathSegments([release, resourceID, uuid, fileName]);
|
|
354
|
-
return {
|
|
355
|
-
previewURL: fileURL,
|
|
356
|
-
downloadURL: fileURL,
|
|
357
|
-
isDirectory: false
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
96
|
function httpRequest(method, path, body) {
|
|
362
97
|
return new Promise(function(resolve, reject) {
|
|
363
98
|
var parsed = parseURL(GATEWAY_URL + path);
|
|
@@ -410,41 +145,37 @@ function formatGatewayError(statusCode, message, traceID) {
|
|
|
410
145
|
return msg;
|
|
411
146
|
}
|
|
412
147
|
|
|
413
|
-
function
|
|
148
|
+
function buildReplyMarkdown(opts) {
|
|
414
149
|
var previewURL = opts.previewURL || "";
|
|
415
150
|
var downloadURL = opts.downloadURL || "";
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
151
|
+
var deliverableType = opts.type || "";
|
|
152
|
+
var isDirectory = !!opts.isDirectory;
|
|
153
|
+
var lines = [];
|
|
154
|
+
if (previewURL) {
|
|
155
|
+
lines.push("预览链接:[点击预览](" + previewURL + ")");
|
|
156
|
+
}
|
|
157
|
+
if (downloadURL) {
|
|
158
|
+
if (isDirectory || deliverableType === "game") {
|
|
159
|
+
lines.push("文件列表:[查看目录](" + downloadURL + ")");
|
|
160
|
+
} else {
|
|
161
|
+
lines.push("下载链接:[点击下载](" + downloadURL + ")");
|
|
162
|
+
}
|
|
423
163
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
function resolveReplyURLs(data, args, body) {
|
|
428
|
-
if (!needsOutputURLRepair(data)) {
|
|
429
|
-
return Promise.resolve(null);
|
|
164
|
+
if (lines.length === 0 && !previewURL && !downloadURL) {
|
|
165
|
+
return "交付物已上传成功。";
|
|
430
166
|
}
|
|
431
|
-
return
|
|
432
|
-
return null;
|
|
433
|
-
}).then(function(meta) {
|
|
434
|
-
return buildOutputURLs(meta, args, body, data);
|
|
435
|
-
});
|
|
167
|
+
return lines.join("\n");
|
|
436
168
|
}
|
|
437
169
|
|
|
438
170
|
// ─── Tool implementations ─────────────────────────────────────────────────────
|
|
439
171
|
|
|
440
172
|
function uploadDeliverable(args) {
|
|
441
|
-
var finalFileName = normalizeDeliverableFileName(args.file_name, args.type, args.content_text, args.files);
|
|
442
173
|
var body = {
|
|
443
174
|
resourceId: args.resource_id,
|
|
444
175
|
groupId: args.group_id,
|
|
445
176
|
userId: args.user_id,
|
|
446
177
|
type: args.type,
|
|
447
|
-
fileName:
|
|
178
|
+
fileName: args.file_name,
|
|
448
179
|
release: "" // overwritten below after release name derivation
|
|
449
180
|
};
|
|
450
181
|
|
|
@@ -474,41 +205,29 @@ function uploadDeliverable(args) {
|
|
|
474
205
|
|
|
475
206
|
return httpRequest("POST", "/openclaw-gateway/be/deliverables", body).then(function(resp) {
|
|
476
207
|
var d = resp.body.data || resp.body;
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
type: args.type,
|
|
490
|
-
isDirectory: isDirectory
|
|
491
|
-
});
|
|
492
|
-
var primaryURL = resolvePrimaryURL({
|
|
493
|
-
previewURL: previewURL,
|
|
494
|
-
downloadURL: downloadURL,
|
|
495
|
-
type: args.type,
|
|
496
|
-
isDirectory: isDirectory
|
|
497
|
-
});
|
|
498
|
-
return {
|
|
499
|
-
uuid: d.uuid,
|
|
500
|
-
file_name: finalFileName,
|
|
501
|
-
backend: d.backend || "",
|
|
502
|
-
download_url: downloadURL,
|
|
503
|
-
preview_url: previewURL,
|
|
504
|
-
primary_url: primaryURL,
|
|
505
|
-
expire_at: d.expireAt,
|
|
506
|
-
is_directory: isDirectory,
|
|
507
|
-
reply_markdown: replyMarkdown,
|
|
508
|
-
trace_id: resp.traceID || "",
|
|
509
|
-
message: replyMarkdown
|
|
510
|
-
};
|
|
208
|
+
// Prefer backend-aware previewUrl returned by gateway (OSS/output/link).
|
|
209
|
+
// Fallback to legacy gateway preview endpoint for compatibility.
|
|
210
|
+
var previewURL = d.previewUrl || (GATEWAY_PUBLIC + "/openclaw-gateway/preview/" + d.uuid);
|
|
211
|
+
var isDirectory = !!(args.files && args.files.length > 0);
|
|
212
|
+
if (!isDirectory && d.downloadUrl && previewURL && d.downloadUrl !== previewURL && /(?:\?|&)list=1(?:&|$)/.test(d.downloadUrl)) {
|
|
213
|
+
isDirectory = true;
|
|
214
|
+
}
|
|
215
|
+
var replyMarkdown = buildReplyMarkdown({
|
|
216
|
+
previewURL: previewURL,
|
|
217
|
+
downloadURL: d.downloadUrl,
|
|
218
|
+
type: args.type,
|
|
219
|
+
isDirectory: isDirectory
|
|
511
220
|
});
|
|
221
|
+
return {
|
|
222
|
+
uuid: d.uuid,
|
|
223
|
+
backend: d.backend || "",
|
|
224
|
+
download_url: d.downloadUrl,
|
|
225
|
+
preview_url: previewURL,
|
|
226
|
+
expire_at: d.expireAt,
|
|
227
|
+
reply_markdown: replyMarkdown,
|
|
228
|
+
trace_id: resp.traceID || "",
|
|
229
|
+
message: replyMarkdown
|
|
230
|
+
};
|
|
512
231
|
});
|
|
513
232
|
}
|
|
514
233
|
|
|
@@ -533,7 +252,7 @@ function handleMessage(msg) {
|
|
|
533
252
|
send({ jsonrpc: "2.0", id: id, result: {
|
|
534
253
|
protocolVersion: "2024-11-05",
|
|
535
254
|
capabilities: { tools: {} },
|
|
536
|
-
serverInfo: { name: "deliverables", version: "1.0.
|
|
255
|
+
serverInfo: { name: "deliverables", version: "1.0.2" }
|
|
537
256
|
}});
|
|
538
257
|
return Promise.resolve();
|
|
539
258
|
|
package/openclaw-plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plugin-deliverables",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
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": {
|
|
@@ -26,9 +26,7 @@
|
|
|
26
26
|
"openclaw_config": {
|
|
27
27
|
"plugins": {
|
|
28
28
|
"entries": {
|
|
29
|
-
"plugin-deliverables": {
|
|
30
|
-
"enabled": true
|
|
31
|
-
}
|
|
29
|
+
"plugin-deliverables": {}
|
|
32
30
|
}
|
|
33
31
|
}
|
|
34
32
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "plugin-deliverables",
|
|
3
|
-
"name": "Deliverables
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"name": "Deliverables",
|
|
4
|
+
"description": "Deliverables runtime guard for upload-first file delivery with Palz split-send diagnostics.",
|
|
5
|
+
"version": "1.0.16",
|
|
6
|
+
"skills": ["./skills"],
|
|
6
7
|
"configSchema": {
|
|
7
8
|
"type": "object",
|
|
8
9
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
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
|
|
3
|
+
"version": "1.0.16",
|
|
4
|
+
"description": "OpenClaw deliverables plugin — upload AI-generated files to OSS and return shareable preview/download links",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openclaw",
|
|
7
7
|
"plugin",
|
|
8
8
|
"deliverables",
|
|
9
9
|
"mcp"
|
|
10
10
|
],
|
|
11
|
-
"main": "index.js",
|
|
12
|
-
"openclaw": {
|
|
13
|
-
"extensions": [
|
|
14
|
-
"./index.js"
|
|
15
|
-
]
|
|
16
|
-
},
|
|
17
11
|
"license": "MIT",
|
|
12
|
+
"main": "index.js",
|
|
18
13
|
"files": [
|
|
14
|
+
"INSTALL.md",
|
|
19
15
|
"index.js",
|
|
20
|
-
"install.js",
|
|
21
16
|
"openclaw.plugin.json",
|
|
22
17
|
"openclaw-plugin.json",
|
|
23
18
|
"mcp-servers/",
|
|
24
19
|
"skills/",
|
|
25
20
|
"agents-rules/"
|
|
26
21
|
],
|
|
22
|
+
"openclaw": {
|
|
23
|
+
"extensions": [
|
|
24
|
+
"./index.js"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": ">=16"
|
|
29
29
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: deliverables
|
|
3
|
-
description: 上传AI
|
|
3
|
+
description: 上传AI生成的文件到交付物系统,返回可分享的预览链接
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# 文件交付规则(强制)
|
|
@@ -21,8 +21,6 @@ description: 上传AI生成的文件到交付物系统,返回一个可直接
|
|
|
21
21
|
- 多文件内容(游戏/网站)写到 `output/<目录名>/...`,保持目录结构。
|
|
22
22
|
- 如果你使用 `write` 工具创建交付物文件,目标路径必须在 `output/` 下。
|
|
23
23
|
- 如果你已经误写到工作区根目录,必须先移动或复制到 `output/`,再继续上传交付物并向用户汇报。
|
|
24
|
-
- 交付物文件名必须保留原始扩展名,不要丢后缀。示例:`刘德华介绍.md`、`周杰伦.md`、`996专题.html`。
|
|
25
|
-
- 如果用户要求 Markdown,就保留 `.md`;如果用户要求 HTML,就保留 `.html`;如果是纯文本,就保留 `.txt`。
|
|
26
24
|
|
|
27
25
|
## 调用前必须准备的参数(全部必填)
|
|
28
26
|
|
|
@@ -34,7 +32,7 @@ description: 上传AI生成的文件到交付物系统,返回一个可直接
|
|
|
34
32
|
| `group_id` | 消息元数据中的 `group_id` 或 `conversation_id` | `group_abc123` |
|
|
35
33
|
| `user_id` | 消息元数据中的 `sender_id` 或 `owner_id` | `cbb0fab9...` |
|
|
36
34
|
| `type` | 根据内容选择:`article`/`game`/`image`/`video`/`ppt`/`zip`/`link` | `article` |
|
|
37
|
-
| `file_name` |
|
|
35
|
+
| `file_name` | 有意义的文件名,含扩展名 | `report-2026.html` |
|
|
38
36
|
| `content_text` | 文件的完整文本内容(HTML/Markdown等) | `<html>...</html>` |
|
|
39
37
|
|
|
40
38
|
> **直接对话(direct chat)时**:`group_id` 填 `conversation_id`,`user_id` 填 `owner_id`。
|
|
@@ -59,19 +57,13 @@ files: [
|
|
|
59
57
|
## 上传成功后
|
|
60
58
|
|
|
61
59
|
优先规则:
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- 优先 `primary_url`
|
|
73
|
-
- 没有就退回 `preview_url`
|
|
74
|
-
- 再没有才用 `download_url`
|
|
75
|
-
- 不要同时输出“预览链接 + 下载链接”两条。
|
|
76
|
-
|
|
77
|
-
不要在消息里输出文件的完整内容、工作区保存路径,也不要只输出“一句简介 + 两个链接”这种过短格式。
|
|
60
|
+
- 最终消息必须先用你自己的话写 1-2 句简短介绍,粗略说明你生成了什么内容、包含哪些重点。
|
|
61
|
+
- 这句介绍必须基于实际产物内容,不要使用固定模板,例如:`交付物已上传成功,可直接在线预览或下载。`
|
|
62
|
+
- 如果工具结果里有 `reply_markdown`,把它原样放在这句介绍后面,不要改写其中的链接。
|
|
63
|
+
|
|
64
|
+
否则只发给用户:
|
|
65
|
+
- 预览链接(`preview_url`,必须用 Markdown 链接格式)
|
|
66
|
+
- 下载链接(`download_url`,必须用 Markdown 链接格式)
|
|
67
|
+
- 多文件/游戏目录场景下,第二行也可以是 `文件列表:[查看目录](...)`,不要改成裸 URL 或 zip 描述。
|
|
68
|
+
|
|
69
|
+
不要在消息里输出文件的完整内容、工作区保存路径或裸链接。
|