@dai_ming/plugin-deliverables 1.1.8 → 1.2.0

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/index.js CHANGED
@@ -880,6 +880,7 @@ function kbMultipartUpload(fileBuffers, fields, pathsArray) {
880
880
  "X-API-Key": kbApiKey(),
881
881
  "Content-Length": body.length,
882
882
  },
883
+ timeout: 10000,
883
884
  },
884
885
  (res) => {
885
886
  const chunks = [];
@@ -905,6 +906,9 @@ function kbMultipartUpload(fileBuffers, fields, pathsArray) {
905
906
  });
906
907
  },
907
908
  );
909
+ req.on("timeout", () => {
910
+ req.destroy(new Error("kb upload timeout after 10s"));
911
+ });
908
912
  req.on("error", reject);
909
913
  req.write(body);
910
914
  req.end();
@@ -1802,6 +1806,30 @@ function extractDeliverableURL(line) {
1802
1806
  return "";
1803
1807
  }
1804
1808
 
1809
+ function extractAllDeliverableURLs(line) {
1810
+ const text = String(line || "").trim();
1811
+ if (!text) {
1812
+ return [];
1813
+ }
1814
+ const urls = [];
1815
+ const seen = new Set();
1816
+ const markdownLinks = text.matchAll(/\((https?:\/\/[^)\s]+)\)/giu);
1817
+ for (const m of markdownLinks) {
1818
+ const url = m[1].trim();
1819
+ if (url && !seen.has(url)) {
1820
+ seen.add(url);
1821
+ urls.push(url);
1822
+ }
1823
+ }
1824
+ if (urls.length === 0) {
1825
+ const rawLink = text.match(/https?:\/\/\S+/iu);
1826
+ if (rawLink && rawLink[0]) {
1827
+ urls.push(rawLink[0].trim());
1828
+ }
1829
+ }
1830
+ return urls;
1831
+ }
1832
+
1805
1833
  function configuredKBHosts() {
1806
1834
  const cfg = loadDeliverableConfig();
1807
1835
  const values = [
@@ -1943,10 +1971,12 @@ function splitDeliverableMessage(text) {
1943
1971
  const normalizedLine = stripBulletPrefix(line).trim();
1944
1972
  if (normalizedLine) {
1945
1973
  linkLines.push(normalizedLine);
1946
- const url = extractDeliverableURL(normalizedLine);
1947
- if (url) {
1948
- linkUrls.push(url);
1949
- linkItems.push({ line: normalizedLine, url });
1974
+ const urls = extractAllDeliverableURLs(normalizedLine);
1975
+ for (const url of urls) {
1976
+ if (isTrustedDeliverableURL(url)) {
1977
+ linkUrls.push(url);
1978
+ linkItems.push({ line: normalizedLine, url });
1979
+ }
1950
1980
  }
1951
1981
  }
1952
1982
  }
@@ -1965,6 +1995,7 @@ function splitDeliverableMessage(text) {
1965
1995
  linkItems,
1966
1996
  primaryLinkUrl: linkUrls.length > 0 ? linkUrls[0] : "",
1967
1997
  fileLinkUrl: selectPalzFileURL(linkItems),
1998
+ deduplicatedItems: deduplicateLinkItems(linkItems),
1968
1999
  };
1969
2000
  }
1970
2001
 
@@ -2007,6 +2038,31 @@ function selectPalzFileURL(linkItems) {
2007
2038
  return (preview && preview.url) || linkItems[0].url || "";
2008
2039
  }
2009
2040
 
2041
+ function deduplicateLinkItems(linkItems) {
2042
+ if (!Array.isArray(linkItems) || linkItems.length === 0) {
2043
+ return { fileItems: [], textItems: [] };
2044
+ }
2045
+ const byIdentity = new Map();
2046
+ const textItems = [];
2047
+ for (const item of linkItems) {
2048
+ const identity = extractDeliverableIdentity(item && item.url);
2049
+ if (!identity) {
2050
+ textItems.push(item);
2051
+ continue;
2052
+ }
2053
+ const existing = byIdentity.get(identity);
2054
+ if (!existing) {
2055
+ byIdentity.set(identity, item);
2056
+ } else if (/\/documents\/preview\//u.test(String(item.url || ""))) {
2057
+ byIdentity.set(identity, item);
2058
+ }
2059
+ }
2060
+ return {
2061
+ fileItems: Array.from(byIdentity.values()),
2062
+ textItems,
2063
+ };
2064
+ }
2065
+
2010
2066
  function cloneBody(body, content, suffix) {
2011
2067
  const next = {};
2012
2068
  Object.keys(body).forEach((key) => {
@@ -2063,6 +2119,7 @@ function shouldSplitPalzPayload(body) {
2063
2119
  links: split.links,
2064
2120
  primaryLinkUrl: split.primaryLinkUrl,
2065
2121
  fileLinkUrl: split.fileLinkUrl,
2122
+ deduplicatedItems: split.deduplicatedItems,
2066
2123
  };
2067
2124
  }
2068
2125
  return split;
@@ -2157,16 +2214,15 @@ function installPalzFetchPatch(api) {
2157
2214
  return originalFetch(input, init);
2158
2215
  }
2159
2216
 
2217
+ const dedup = split.deduplicatedItems || { fileItems: [], textItems: [] };
2218
+ const hasFileItems = dedup.fileItems.length > 0;
2219
+ const hasTextItems = dedup.textItems.length > 0;
2220
+
2160
2221
  const summaryBody = cloneBody(parsed, split.summary, "__summary");
2161
- const fileLinkUrl = split.fileLinkUrl || "";
2162
- const linksBody = fileLinkUrl
2163
- ? cloneBodyAsFileLink(parsed, fileLinkUrl, "__links")
2164
- : cloneBody(parsed, split.links, "__links");
2165
2222
  const summaryBodyStr = JSON.stringify(summaryBody);
2166
- const linksBodyStr = JSON.stringify(linksBody);
2167
2223
 
2168
2224
  api.logger.info?.(
2169
- `[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=${fileLinkUrl ? "file_url" : "text"}`,
2225
+ `[plugin-deliverables] split deliverable reply injected by plugin-deliverables target=${String(parsed.conversation_id || "")} summaryMsgId=${String(summaryBody.msg_id || "")} fileLinks=${dedup.fileItems.length} textLinks=${dedup.textItems.length}`,
2170
2226
  );
2171
2227
  api.logger.info?.(
2172
2228
  `[plugin-deliverables] palz summary request body_length=${summaryBodyStr.length}\n request_body=${summaryBodyStr}`,
@@ -2176,28 +2232,54 @@ function installPalzFetchPatch(api) {
2176
2232
  const summaryInit = Object.assign({}, baseInit, {
2177
2233
  body: summaryBodyStr,
2178
2234
  });
2179
- const linksInit = Object.assign({}, baseInit, {
2180
- body: linksBodyStr,
2181
- });
2182
2235
 
2183
- const firstResponse = await originalFetch(input, summaryInit);
2184
- const firstResponseText = await readResponseText(firstResponse);
2236
+ const summaryResponse = await originalFetch(input, summaryInit);
2237
+ const summaryResponseText = await readResponseText(summaryResponse);
2185
2238
  api.logger.info?.(
2186
- `[plugin-deliverables] palz summary response status=${firstResponse ? firstResponse.status : "unknown"} ok=${Boolean(firstResponse && firstResponse.ok)}\n response_body=${previewText(firstResponseText)}`,
2239
+ `[plugin-deliverables] palz summary response status=${summaryResponse ? summaryResponse.status : "unknown"} ok=${Boolean(summaryResponse && summaryResponse.ok)}\n response_body=${previewText(summaryResponseText)}`,
2187
2240
  );
2188
- if (!firstResponse || !firstResponse.ok) {
2189
- return firstResponse;
2241
+ if (!summaryResponse || !summaryResponse.ok) {
2242
+ return summaryResponse;
2190
2243
  }
2191
2244
 
2192
- api.logger.info?.(
2193
- `[plugin-deliverables] palz links request body_length=${linksBodyStr.length}\n request_body=${linksBodyStr}`,
2194
- );
2195
- const secondResponse = await originalFetch(input, linksInit);
2196
- const secondResponseText = await readResponseText(secondResponse);
2197
- api.logger.info?.(
2198
- `[plugin-deliverables] palz links response status=${secondResponse ? secondResponse.status : "unknown"} ok=${Boolean(secondResponse && secondResponse.ok)}\n response_body=${previewText(secondResponseText)}`,
2199
- );
2200
- return secondResponse;
2245
+ let lastResponse = summaryResponse;
2246
+
2247
+ for (let idx = 0; idx < dedup.fileItems.length; idx += 1) {
2248
+ const item = dedup.fileItems[idx];
2249
+ const fileBody = cloneBodyAsFileLink(parsed, item.url, `__links_${idx}`);
2250
+ const fileBodyStr = JSON.stringify(fileBody);
2251
+ api.logger.info?.(
2252
+ `[plugin-deliverables] palz file_url request [${idx}] body_length=${fileBodyStr.length} url=${item.url}\n request_body=${fileBodyStr}`,
2253
+ );
2254
+ const fileInit = Object.assign({}, baseInit, { body: fileBodyStr });
2255
+ const fileResponse = await originalFetch(input, fileInit);
2256
+ const fileResponseText = await readResponseText(fileResponse);
2257
+ api.logger.info?.(
2258
+ `[plugin-deliverables] palz file_url response [${idx}] status=${fileResponse ? fileResponse.status : "unknown"} ok=${Boolean(fileResponse && fileResponse.ok)}\n response_body=${previewText(fileResponseText)}`,
2259
+ );
2260
+ if (!fileResponse || !fileResponse.ok) {
2261
+ return fileResponse;
2262
+ }
2263
+ lastResponse = fileResponse;
2264
+ }
2265
+
2266
+ if (hasTextItems) {
2267
+ const textContent = dedup.textItems.map((item) => item.line).join("\n");
2268
+ const textBody = cloneBody(parsed, textContent, "__links_text");
2269
+ const textBodyStr = JSON.stringify(textBody);
2270
+ api.logger.info?.(
2271
+ `[plugin-deliverables] palz text links request body_length=${textBodyStr.length}\n request_body=${textBodyStr}`,
2272
+ );
2273
+ const textInit = Object.assign({}, baseInit, { body: textBodyStr });
2274
+ const textResponse = await originalFetch(input, textInit);
2275
+ const textResponseText = await readResponseText(textResponse);
2276
+ api.logger.info?.(
2277
+ `[plugin-deliverables] palz text links response status=${textResponse ? textResponse.status : "unknown"} ok=${Boolean(textResponse && textResponse.ok)}\n response_body=${previewText(textResponseText)}`,
2278
+ );
2279
+ lastResponse = textResponse;
2280
+ }
2281
+
2282
+ return lastResponse;
2201
2283
  };
2202
2284
 
2203
2285
  globalThis.fetch = patchedFetch;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugin-deliverables",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "npm_package": "@dai_ming/plugin-deliverables",
5
5
  "description": "Deliverables plugin: native upload tool + skill + AGENTS rules for AI-generated file uploads",
6
6
  "skills": {
@@ -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.1.8",
5
+ "version": "1.2.0",
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.1.8",
3
+ "version": "1.2.0",
4
4
  "description": "OpenClaw deliverables native plugin — upload AI-generated files to OSS and return shareable preview/download links",
5
5
  "keywords": [
6
6
  "openclaw",