@dai_ming/plugin-deliverables 1.0.7 → 1.0.9
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/README.md +6 -2
- package/mcp-servers/deliverables.js +233 -24
- package/openclaw-plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ OpenClaw 交付物插件。安装后会把交付物 MCP、skill、AGENTS 规则
|
|
|
20
20
|
|
|
21
21
|
```yaml
|
|
22
22
|
installPlugins:
|
|
23
|
-
- "@dai_ming/plugin-deliverables@1.0.
|
|
23
|
+
- "@dai_ming/plugin-deliverables@1.0.9"
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
现有 chart 会在 init 阶段完成安装。
|
|
@@ -31,7 +31,7 @@ installPlugins:
|
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
33
|
npm config set registry https://registry.npmmirror.com
|
|
34
|
-
npm install @dai_ming/plugin-deliverables@1.0.
|
|
34
|
+
npm install @dai_ming/plugin-deliverables@1.0.9
|
|
35
35
|
node node_modules/@dai_ming/plugin-deliverables/install.js
|
|
36
36
|
```
|
|
37
37
|
|
|
@@ -133,3 +133,7 @@ env | grep -E 'CLAW_GATEWAY|OPENCLAW_GATEWAY|botID'
|
|
|
133
133
|
```bash
|
|
134
134
|
curl -H "X-API-Key: $CLAW_GATEWAY_API_KEY" "$CLAW_GATEWAY_URL/healthz"
|
|
135
135
|
```
|
|
136
|
+
|
|
137
|
+
### 返回的是 `/preview/:uuid` 或内部地址
|
|
138
|
+
|
|
139
|
+
`1.0.9` 起,插件会在 `output` 交付物场景下自动把相对/内部 preview 地址修正为最终的外部 `output` 直链,不再依赖 gateway 当前环境里是否显式配置了 `deliverable.gatewayPublicUrl`。
|
|
@@ -16,11 +16,12 @@ var http = require("http");
|
|
|
16
16
|
var https = require("https");
|
|
17
17
|
|
|
18
18
|
var GATEWAY_URL = (process.env.CLAW_GATEWAY_URL || "http://claw-gateway:8080").replace(/\/$/, "");
|
|
19
|
-
var
|
|
19
|
+
var RAW_GATEWAY_PUBLIC = (process.env.CLAW_GATEWAY_PUBLIC_URL || GATEWAY_URL).replace(/\/$/, "");
|
|
20
20
|
// Some existing pods were created without CLAW_GATEWAY_API_KEY injected.
|
|
21
21
|
// Keep env-driven behavior first, but provide a dev-compatible fallback to avoid
|
|
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
|
+
var GATEWAY_PUBLIC = resolveGatewayPublicBase();
|
|
24
25
|
|
|
25
26
|
var TOOL_DEFS = [
|
|
26
27
|
{
|
|
@@ -93,6 +94,200 @@ function parseURL(rawURL) {
|
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
|
97
|
+
function isPrivateIPv4(hostname) {
|
|
98
|
+
if (!hostname) return false;
|
|
99
|
+
if (/^10\./.test(hostname)) return true;
|
|
100
|
+
if (/^127\./.test(hostname)) return true;
|
|
101
|
+
if (/^192\.168\./.test(hostname)) return true;
|
|
102
|
+
var m = hostname.match(/^172\.(\d+)\./);
|
|
103
|
+
if (m) {
|
|
104
|
+
var second = parseInt(m[1], 10);
|
|
105
|
+
return second >= 16 && second <= 31;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isInternalHostname(hostname) {
|
|
111
|
+
var host = String(hostname || "").toLowerCase();
|
|
112
|
+
if (!host) return false;
|
|
113
|
+
if (host === "localhost" || host === "claw-gateway") return true;
|
|
114
|
+
if (host.indexOf(".svc") >= 0 || host.indexOf(".cluster.local") >= 0) return true;
|
|
115
|
+
if (host.indexOf("claw-gateway:") === 0) return true;
|
|
116
|
+
if (isPrivateIPv4(host)) return true;
|
|
117
|
+
if (host.indexOf(".") < 0) return true;
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isProbablyInternalURL(rawURL) {
|
|
122
|
+
var parsed = parseURL(rawURL);
|
|
123
|
+
if (!parsed) return false;
|
|
124
|
+
return isInternalHostname(parsed.hostname);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function envGatewayPublicFallback() {
|
|
128
|
+
var helmEnv = String(process.env.HELM_ENV || process.env.ENV || "").toLowerCase();
|
|
129
|
+
if (helmEnv === "prod" || helmEnv === "production" || helmEnv === "online") {
|
|
130
|
+
return "https://claw-gateway.csagentai.com";
|
|
131
|
+
}
|
|
132
|
+
if (helmEnv === "dev" || helmEnv === "development" || helmEnv === "staging" || helmEnv === "stage") {
|
|
133
|
+
return "https://claw-dev.csaiagent.com";
|
|
134
|
+
}
|
|
135
|
+
return "";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function resolveGatewayPublicBase() {
|
|
139
|
+
var candidate = RAW_GATEWAY_PUBLIC;
|
|
140
|
+
if (candidate && !isProbablyInternalURL(candidate)) {
|
|
141
|
+
return candidate;
|
|
142
|
+
}
|
|
143
|
+
var fallback = envGatewayPublicFallback();
|
|
144
|
+
if (fallback) {
|
|
145
|
+
return fallback.replace(/\/$/, "");
|
|
146
|
+
}
|
|
147
|
+
return candidate;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function absolutizePublicURL(rawURL) {
|
|
151
|
+
var val = String(rawURL || "").trim();
|
|
152
|
+
if (!val) return "";
|
|
153
|
+
if (/^https?:\/\//i.test(val)) {
|
|
154
|
+
if (isProbablyInternalURL(val) && GATEWAY_PUBLIC && !isProbablyInternalURL(GATEWAY_PUBLIC)) {
|
|
155
|
+
var parsed = parseURL(val);
|
|
156
|
+
if (parsed) {
|
|
157
|
+
return GATEWAY_PUBLIC + parsed.pathname + (parsed.search || "") + (parsed.hash || "");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return val;
|
|
161
|
+
}
|
|
162
|
+
if (val.charAt(0) !== "/") {
|
|
163
|
+
val = "/" + val;
|
|
164
|
+
}
|
|
165
|
+
return (GATEWAY_PUBLIC || RAW_GATEWAY_PUBLIC || "").replace(/\/$/, "") + val;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function outputPublicBase() {
|
|
169
|
+
return absolutizePublicURL("/openclaw-gateway/output");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function encodePathSegments(parts) {
|
|
173
|
+
return parts.map(function(part) {
|
|
174
|
+
return encodeURIComponent(String(part || ""));
|
|
175
|
+
}).join("/");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function sanitizePathSegment(v) {
|
|
179
|
+
var s = String(v || "").trim();
|
|
180
|
+
if (!s) return "unknown";
|
|
181
|
+
s = s.replace(/\//g, "_");
|
|
182
|
+
s = s.replace(/\\/g, "_");
|
|
183
|
+
s = s.replace(/\.\./g, "_");
|
|
184
|
+
return s;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function sanitizeFileName(v) {
|
|
188
|
+
var name = String(v || "").trim();
|
|
189
|
+
if (!name) return "file";
|
|
190
|
+
name = name.replace(/^.*[\/\\]/, "");
|
|
191
|
+
name = name.replace(/\//g, "_");
|
|
192
|
+
name = name.replace(/\\/g, "_");
|
|
193
|
+
name = name.replace(/\.\./g, "_");
|
|
194
|
+
if (!name || name === ".") return "file";
|
|
195
|
+
return name;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function extractUserIDFromResource(resourceID) {
|
|
199
|
+
var trimmed = String(resourceID || "").trim();
|
|
200
|
+
var prefix = "user_";
|
|
201
|
+
if (!trimmed || trimmed.indexOf(prefix) !== 0) {
|
|
202
|
+
return "";
|
|
203
|
+
}
|
|
204
|
+
var rest = trimmed.slice(prefix.length);
|
|
205
|
+
var end = rest.indexOf("_");
|
|
206
|
+
if (end <= 0) {
|
|
207
|
+
return "";
|
|
208
|
+
}
|
|
209
|
+
return rest.slice(0, end);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function deriveRelease(release, resourceID, userID) {
|
|
213
|
+
var rel = sanitizePathSegment(release);
|
|
214
|
+
if (rel && rel !== "unknown") {
|
|
215
|
+
return rel;
|
|
216
|
+
}
|
|
217
|
+
var uid = String(userID || "").trim();
|
|
218
|
+
if (!uid) {
|
|
219
|
+
uid = extractUserIDFromResource(resourceID);
|
|
220
|
+
}
|
|
221
|
+
if (uid) {
|
|
222
|
+
return "user-" + uid;
|
|
223
|
+
}
|
|
224
|
+
return "unknown";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function looksLikePreviewRoute(rawURL) {
|
|
228
|
+
var val = String(rawURL || "").trim();
|
|
229
|
+
return /\/openclaw-gateway\/preview\/[^/?#]+(?:[?#].*)?$/i.test(val);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function needsOutputURLRepair(data) {
|
|
233
|
+
if (!data) return false;
|
|
234
|
+
if (String(data.backend || "").toLowerCase() !== "output") {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
var previewURL = String(data.previewUrl || "").trim();
|
|
238
|
+
var downloadURL = String(data.downloadUrl || "").trim();
|
|
239
|
+
if (!previewURL || !downloadURL) {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
if (previewURL.charAt(0) === "/" || downloadURL.charAt(0) === "/") {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
if (isProbablyInternalURL(previewURL) || isProbablyInternalURL(downloadURL)) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
if (looksLikePreviewRoute(previewURL) || looksLikePreviewRoute(downloadURL)) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function fetchDeliverableMeta(uuid) {
|
|
255
|
+
if (!uuid) return Promise.resolve(null);
|
|
256
|
+
return httpRequest("GET", "/openclaw-gateway/be/deliverables/" + encodeURIComponent(uuid)).then(function(resp) {
|
|
257
|
+
return resp.body.data || resp.body || null;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function buildOutputURLs(meta, args, body, data) {
|
|
262
|
+
var base = outputPublicBase();
|
|
263
|
+
if (!base) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
var resourceID = sanitizePathSegment((meta && meta.resourceId) || body.resourceId || args.resource_id);
|
|
267
|
+
var userID = (meta && meta.userId) || body.userId || args.user_id;
|
|
268
|
+
var release = deriveRelease((meta && meta.release) || body.release, resourceID, userID);
|
|
269
|
+
var uuid = String((meta && meta.uuid) || (data && data.uuid) || "").trim();
|
|
270
|
+
if (!resourceID || !uuid) {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
var isDirectory = !!(args.files && args.files.length > 0) || String((meta && meta.mime) || "").trim() === "application/x-directory";
|
|
274
|
+
if (isDirectory) {
|
|
275
|
+
var dirURL = base.replace(/\/$/, "") + "/" + encodePathSegments([release, resourceID, uuid]);
|
|
276
|
+
return {
|
|
277
|
+
previewURL: dirURL,
|
|
278
|
+
downloadURL: dirURL + "?list=1",
|
|
279
|
+
isDirectory: true
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
var fileName = sanitizeFileName((meta && meta.fileName) || body.fileName || args.file_name);
|
|
283
|
+
var fileURL = base.replace(/\/$/, "") + "/" + encodePathSegments([release, resourceID, uuid, fileName]);
|
|
284
|
+
return {
|
|
285
|
+
previewURL: fileURL,
|
|
286
|
+
downloadURL: fileURL,
|
|
287
|
+
isDirectory: false
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
96
291
|
function httpRequest(method, path, body) {
|
|
97
292
|
return new Promise(function(resolve, reject) {
|
|
98
293
|
var parsed = parseURL(GATEWAY_URL + path);
|
|
@@ -167,6 +362,17 @@ function buildReplyMarkdown(opts) {
|
|
|
167
362
|
return lines.join("\n");
|
|
168
363
|
}
|
|
169
364
|
|
|
365
|
+
function resolveReplyURLs(data, args, body) {
|
|
366
|
+
if (!needsOutputURLRepair(data)) {
|
|
367
|
+
return Promise.resolve(null);
|
|
368
|
+
}
|
|
369
|
+
return fetchDeliverableMeta(data.uuid).catch(function() {
|
|
370
|
+
return null;
|
|
371
|
+
}).then(function(meta) {
|
|
372
|
+
return buildOutputURLs(meta, args, body, data);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
170
376
|
// ─── Tool implementations ─────────────────────────────────────────────────────
|
|
171
377
|
|
|
172
378
|
function uploadDeliverable(args) {
|
|
@@ -205,29 +411,32 @@ function uploadDeliverable(args) {
|
|
|
205
411
|
|
|
206
412
|
return httpRequest("POST", "/openclaw-gateway/be/deliverables", body).then(function(resp) {
|
|
207
413
|
var d = resp.body.data || resp.body;
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
isDirectory =
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
414
|
+
return resolveReplyURLs(d, args, body).then(function(repaired) {
|
|
415
|
+
// Prefer backend-aware previewUrl returned by gateway (OSS/output/link).
|
|
416
|
+
// Fallback to legacy gateway preview endpoint for compatibility.
|
|
417
|
+
var previewURL = absolutizePublicURL((repaired && repaired.previewURL) || d.previewUrl || ("/openclaw-gateway/preview/" + d.uuid));
|
|
418
|
+
var downloadURL = absolutizePublicURL((repaired && repaired.downloadURL) || d.downloadUrl);
|
|
419
|
+
var isDirectory = !!(repaired && repaired.isDirectory) || !!(args.files && args.files.length > 0);
|
|
420
|
+
if (!isDirectory && downloadURL && previewURL && downloadURL !== previewURL && /(?:\?|&)list=1(?:&|$)/.test(downloadURL)) {
|
|
421
|
+
isDirectory = true;
|
|
422
|
+
}
|
|
423
|
+
var replyMarkdown = buildReplyMarkdown({
|
|
424
|
+
previewURL: previewURL,
|
|
425
|
+
downloadURL: downloadURL,
|
|
426
|
+
type: args.type,
|
|
427
|
+
isDirectory: isDirectory
|
|
428
|
+
});
|
|
429
|
+
return {
|
|
430
|
+
uuid: d.uuid,
|
|
431
|
+
backend: d.backend || "",
|
|
432
|
+
download_url: downloadURL,
|
|
433
|
+
preview_url: previewURL,
|
|
434
|
+
expire_at: d.expireAt,
|
|
435
|
+
reply_markdown: replyMarkdown,
|
|
436
|
+
trace_id: resp.traceID || "",
|
|
437
|
+
message: replyMarkdown
|
|
438
|
+
};
|
|
220
439
|
});
|
|
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
|
-
};
|
|
231
440
|
});
|
|
232
441
|
}
|
|
233
442
|
|
|
@@ -252,7 +461,7 @@ function handleMessage(msg) {
|
|
|
252
461
|
send({ jsonrpc: "2.0", id: id, result: {
|
|
253
462
|
protocolVersion: "2024-11-05",
|
|
254
463
|
capabilities: { tools: {} },
|
|
255
|
-
serverInfo: { name: "deliverables", version: "1.0.
|
|
464
|
+
serverInfo: { name: "deliverables", version: "1.0.9" }
|
|
256
465
|
}});
|
|
257
466
|
return Promise.resolve();
|
|
258
467
|
|
package/openclaw-plugin.json
CHANGED
package/package.json
CHANGED