@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 CHANGED
@@ -20,7 +20,7 @@ OpenClaw 交付物插件。安装后会把交付物 MCP、skill、AGENTS 规则
20
20
 
21
21
  ```yaml
22
22
  installPlugins:
23
- - "@dai_ming/plugin-deliverables@1.0.7"
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.7
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 GATEWAY_PUBLIC = (process.env.CLAW_GATEWAY_PUBLIC_URL || GATEWAY_URL).replace(/\/$/, "");
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
- // 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
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.2" }
464
+ serverInfo: { name: "deliverables", version: "1.0.9" }
256
465
  }});
257
466
  return Promise.resolve();
258
467
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-deliverables",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
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": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dai_ming/plugin-deliverables",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "OpenClaw deliverables plugin — upload AI-generated files to OSS and return shareable preview/download links",
5
5
  "keywords": [
6
6
  "openclaw",