@dai_ming/plugin-deliverables 1.1.3 → 1.1.5
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 +4 -4
- package/README.md +18 -7
- package/agents-rules/deliverables.md +4 -0
- package/deliverables.dev.config.json +4 -0
- package/deliverables.prod.config.json +4 -0
- package/deliverables.staging.config.json +4 -0
- package/index.js +317 -114
- package/openclaw-plugin.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/skills/deliverables/SKILL.md +14 -12
package/INSTALL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# plugin-deliverables 安装文档
|
|
2
2
|
|
|
3
|
-
本文档描述 `@dai_ming/plugin-deliverables@1.1.
|
|
3
|
+
本文档描述 `@dai_ming/plugin-deliverables@1.1.2` 的安装、升级与验证方式。
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
openclaw plugins install @dai_ming/plugin-deliverables@1.1.
|
|
8
|
+
openclaw plugins install @dai_ming/plugin-deliverables@1.1.2 --pin
|
|
9
9
|
openclaw plugins enable plugin-deliverables
|
|
10
10
|
```
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ gateway 部署配置:
|
|
|
13
13
|
|
|
14
14
|
```yaml
|
|
15
15
|
installPlugins:
|
|
16
|
-
- "@dai_ming/plugin-deliverables@1.1.
|
|
16
|
+
- "@dai_ming/plugin-deliverables@1.1.2"
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## 验证
|
|
@@ -21,7 +21,7 @@ installPlugins:
|
|
|
21
21
|
1. `openclaw plugins info plugin-deliverables` 能看到插件已启用。
|
|
22
22
|
2. Agent 工具列表里存在 `deliverables__upload_deliverable`。
|
|
23
23
|
3. Pod 内不再出现 `node ...mcp-servers/deliverables.js` 进程。
|
|
24
|
-
4. 生成交付物时,工具返回 `reply_markdown`、`preview_url`、`download_url`。
|
|
24
|
+
4. 生成交付物时,工具返回 `reply_markdown`、`preview_url`、`download_url`、`document_id`。
|
|
25
25
|
|
|
26
26
|
## 说明
|
|
27
27
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @dai_ming/plugin-deliverables
|
|
2
2
|
|
|
3
|
-
OpenClaw Native 插件:注册 `deliverables__upload_deliverable` 原生 agent tool,把 AI
|
|
3
|
+
OpenClaw Native 插件:注册 `deliverables__upload_deliverable` 原生 agent tool,把 AI 生成的文件上传到知识库交付物系统,并返回可分享的预览/下载链接。
|
|
4
4
|
|
|
5
5
|
## 包含内容
|
|
6
6
|
|
|
@@ -11,11 +11,12 @@ OpenClaw Native 插件:注册 `deliverables__upload_deliverable` 原生 agent
|
|
|
11
11
|
| `openclaw-plugin.json` | gateway 兼容清单:声明 skill、AGENTS 规则和插件 entry |
|
|
12
12
|
| `skills/deliverables/SKILL.md` | Agent 使用交付物工具的技能说明 |
|
|
13
13
|
| `agents-rules/deliverables.md` | 注入到 workspace `AGENTS.md` 的硬规则 |
|
|
14
|
+
| `deliverables.*.config.json` | 各环境知识库连接配置 |
|
|
14
15
|
|
|
15
16
|
## 安装
|
|
16
17
|
|
|
17
18
|
```bash
|
|
18
|
-
openclaw plugins install @dai_ming/plugin-deliverables@1.1.
|
|
19
|
+
openclaw plugins install @dai_ming/plugin-deliverables@1.1.2 --pin
|
|
19
20
|
openclaw plugins enable plugin-deliverables
|
|
20
21
|
```
|
|
21
22
|
|
|
@@ -23,7 +24,7 @@ openclaw plugins enable plugin-deliverables
|
|
|
23
24
|
|
|
24
25
|
```yaml
|
|
25
26
|
installPlugins:
|
|
26
|
-
- "@dai_ming/plugin-deliverables@1.1.
|
|
27
|
+
- "@dai_ming/plugin-deliverables@1.1.2"
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
## 运行方式
|
|
@@ -34,11 +35,21 @@ installPlugins:
|
|
|
34
35
|
deliverables__upload_deliverable
|
|
35
36
|
```
|
|
36
37
|
|
|
37
|
-
|
|
38
|
+
上传工具根据 `HELM_ENV` 环境变量加载对应配置文件:
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
| HELM_ENV | 配置文件 | 说明 |
|
|
41
|
+
|----------|---------|------|
|
|
42
|
+
| (空) | `deliverables.config.json` | 本地开发(127.0.0.1) |
|
|
43
|
+
| `dev` | `deliverables.dev.config.json` | dev 环境 |
|
|
44
|
+
| `staging` | `deliverables.staging.config.json` | staging 环境 |
|
|
45
|
+
| `prod` | `deliverables.prod.config.json` | 生产环境 |
|
|
46
|
+
|
|
47
|
+
配置字段:
|
|
48
|
+
|
|
49
|
+
- `kbBaseUrl` — 知识库 API 内部地址(Pod 内通信)
|
|
50
|
+
- `kbPublicUrl` — 用户可见的预览链接地址
|
|
51
|
+
|
|
52
|
+
环境变量 `KNOWLEDGE_DB_URL` / `KNOWLEDGE_DB_PUBLIC_URL` 可覆盖配置文件值。`KNOWLEDGE_DB_API_KEY` 仅通过环境变量配置(默认 `kb-agent-key-2025`)。
|
|
42
53
|
|
|
43
54
|
## 发布
|
|
44
55
|
|
|
@@ -81,6 +81,10 @@ Tool name note:
|
|
|
81
81
|
Keep that format instead of forcing a zip link.
|
|
82
82
|
23. Do NOT only say "已保存到工作空间".
|
|
83
83
|
24. Do NOT append workspace path or a raw URL block after the Markdown links.
|
|
84
|
+
25. 群聊场景(消息元数据包含 `is_group_chat == true` 或 `conversation_type == "group"`)调用上传工具时,MUST 传入 `group_id`(取自元数据的 `group_id` 或 `conversation_id`)和 `group_name`(取自元数据的 `group_name` 或 `group_subject`)。
|
|
85
|
+
26. 单聊/私聊场景,不传 `group_id` 或传 `"default"`,不传 `group_name`。
|
|
86
|
+
27. 无法判断会话类型时,若 `resource_id` 存在且不是 `"default"`,将 `resource_id` 作为 `group_id` 传入。
|
|
87
|
+
28. 调用上传工具时 MUST 传入 `user_id`:单聊取 `owner_id`,群聊取 `sender_id`。漏传 `user_id` 会导致 KB 上传失败(error 13002: 用户不存在),尤其在同时传了真实 `group_id` 的场景下。
|
|
84
88
|
|
|
85
89
|
### Exception
|
|
86
90
|
|
package/index.js
CHANGED
|
@@ -11,14 +11,50 @@ const PENDING_FILES_KEY = "__plugin_deliverables_pending_files__";
|
|
|
11
11
|
const UPLOAD_CACHE_KEY = "__plugin_deliverables_upload_cache__";
|
|
12
12
|
const SUMMARY_CACHE_LIMIT = 200;
|
|
13
13
|
const SHORT_SUMMARY_THRESHOLD = 120;
|
|
14
|
-
const
|
|
15
|
-
const
|
|
14
|
+
const DEFAULT_KB_BASE_URL = "http://knowledge-db:8080/api/v1";
|
|
15
|
+
const DEFAULT_KB_API_KEY = "kb-agent-key-2025";
|
|
16
|
+
const DEFAULT_GROUP_ID = "default";
|
|
17
|
+
const DEFAULT_GROUP_NAME = "我的文件夹";
|
|
18
|
+
const DEFAULT_DELIVERABLES_CONFIG_FILENAME = "deliverables.config.json";
|
|
19
|
+
let _cachedDeliverableConfig = null;
|
|
20
|
+
|
|
21
|
+
function getDeliverableConfigFilename() {
|
|
22
|
+
const helmEnv = (process.env.HELM_ENV || "").trim();
|
|
23
|
+
return helmEnv
|
|
24
|
+
? "deliverables." + helmEnv + ".config.json"
|
|
25
|
+
: DEFAULT_DELIVERABLES_CONFIG_FILENAME;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function loadDeliverableConfig() {
|
|
29
|
+
if (_cachedDeliverableConfig) return _cachedDeliverableConfig;
|
|
30
|
+
const helmEnv = (process.env.HELM_ENV || "").trim();
|
|
31
|
+
const configFilename = getDeliverableConfigFilename();
|
|
32
|
+
const candidates = [
|
|
33
|
+
path.resolve(process.cwd(), configFilename),
|
|
34
|
+
path.resolve(__dirname, configFilename),
|
|
35
|
+
];
|
|
36
|
+
for (const filePath of candidates) {
|
|
37
|
+
try {
|
|
38
|
+
if (fs.existsSync(filePath)) {
|
|
39
|
+
_cachedDeliverableConfig = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
40
|
+
return _cachedDeliverableConfig;
|
|
41
|
+
}
|
|
42
|
+
} catch (_err) { /* ignore */ }
|
|
43
|
+
}
|
|
44
|
+
if (helmEnv) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"plugin-deliverables: HELM_ENV=\"" + helmEnv + "\" but config file \"" + configFilename + "\" not found, searched: " + candidates.join(", ")
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
_cachedDeliverableConfig = {};
|
|
50
|
+
return _cachedDeliverableConfig;
|
|
51
|
+
}
|
|
16
52
|
const AUTO_UPLOAD_TTL_MS = 15 * 60 * 1000;
|
|
17
53
|
const AUTO_UPLOAD_MAX_FILES = 8;
|
|
18
54
|
const AUTO_UPLOAD_MAX_BYTES = 200 * 1024 * 1024;
|
|
19
55
|
const AUTO_UPLOAD_SCAN_MAX_ENTRIES = 4000;
|
|
20
56
|
const AUTO_UPLOAD_SCAN_DEPTH = 5;
|
|
21
|
-
const
|
|
57
|
+
const DELIVERABLE_KB_PATH_RE = /\/documents\/content\b/u;
|
|
22
58
|
const DELIVERABLE_LINK_PREFIX_RE =
|
|
23
59
|
/^\s*(?:[-*+]\s+)?(?:预览链接|下载链接|文件列表|项目入口|在线预览|在线体验|目录链接)\s*[::]/u;
|
|
24
60
|
const DELIVERABLE_LINK_LABEL_RE =
|
|
@@ -26,8 +62,8 @@ const DELIVERABLE_LINK_LABEL_RE =
|
|
|
26
62
|
const INTERNAL_ABSOLUTE_PATH_RE =
|
|
27
63
|
/(?:\/data\/workspace-[^\s`"'<>,。;:、))\]}]+|\/home\/node\/\.openclaw\/workspace(?:-[A-Za-z0-9_.-]+)?\/[^\s`"'<>,。;:、))\]}]+)/gu;
|
|
28
64
|
const INLINE_FILE_REF_RE = /`([^`\n]{1,512}\.[A-Za-z0-9]{1,16})`/gu;
|
|
29
|
-
const MARKDOWN_FILE_LINK_RE = /\[[^\]\n]{0,200}\]\(([^)\s]+?\.[A-Za-z0-9]{1,16})\)/gu;
|
|
30
65
|
const OUTPUT_RELATIVE_REF_RE = /(?:^|[\s(::])((?:\.\/)?output\/[^\s`"'<>,。;:、))\]}]+\.[A-Za-z0-9]{1,16})/gu;
|
|
66
|
+
const MARKDOWN_FILE_LINK_RE = /\[[^\]\n]{0,200}\]\(([^)\s]+?\.[A-Za-z0-9]{1,16})\)/gu;
|
|
31
67
|
const FILE_EXTENSION_RE = /\.[A-Za-z0-9]{1,16}$/u;
|
|
32
68
|
const TEXT_EXTENSIONS = new Set([
|
|
33
69
|
".css",
|
|
@@ -81,14 +117,16 @@ const RUNTIME_DELIVERABLES_GUIDANCE = [
|
|
|
81
117
|
"- When the user asks for a document, article, report, PDF, 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`.",
|
|
82
118
|
"- For simple text PDF deliverables, call `deliverables__upload_deliverable` with `type: \"pdf\"`, a `.pdf` `file_name`, and `content_text`; the tool will render a basic PDF without installing dependencies.",
|
|
83
119
|
"- For existing binary deliverables such as rich PDFs, PPT, images, video, or zip files, pass `file_path` to `deliverables__upload_deliverable` after writing the file under `output/`; do not paste command output or partial base64 into `content_base64`.",
|
|
84
|
-
"- Never use `type: \"link\"` for local files, relative paths, or generated files such as `review.html`; use `file_path`, `content_text`, or `files` so the file is uploaded.",
|
|
85
120
|
"- Never install PDF libraries or other packages at runtime just to create a deliverable. Use built-in tools or the PDF `content_text` fallback; if a rich PDF renderer is unavailable, say so clearly.",
|
|
86
121
|
"- If the user asks for PDF, upload the generated `.pdf` as `type: \"pdf\"`; do not substitute HTML or Markdown unless PDF generation truly failed and you clearly say so.",
|
|
87
122
|
"- 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.",
|
|
88
123
|
"- 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.",
|
|
89
124
|
"- Do not emit `MEDIA:` file references for user-facing deliverables. Deliverable links must come from the deliverables upload tool instead.",
|
|
125
|
+
"- Never use `type: \"link\"` for local files, relative paths, or generated files such as `review.html`; use `file_path`, `content_text`, or `files` so the file is uploaded.",
|
|
90
126
|
"- If the deliverables upload tool returns an error, retry with a valid `file_path` or explain the upload failure; never invent or rewrite OSS/download URLs.",
|
|
91
127
|
"- After a successful upload, reply with a substantive content summary before the links. For documents/articles/reports/PDFs, include 1 short intro plus 3-5 concise bullets covering the actual sections, highlights, or findings; never respond with only a generic one-sentence upload notice.",
|
|
128
|
+
"- CRITICAL: In group chats (metadata `is_group_chat == true` or `conversation_type == \"group\"`), you MUST pass `group_id` (from metadata `group_id` or `conversation_id`) and `group_name` (from metadata `group_name` or `group_subject`) to `deliverables__upload_deliverable`. For single/direct chats, omit `group_id` or pass `\"default\"`. Omitting `group_id` in a group chat causes the deliverable to land in the wrong knowledge base.",
|
|
129
|
+
"- CRITICAL: You MUST always pass `user_id` to `deliverables__upload_deliverable`. In single/direct chats, use `owner_id`; in group chats, use `sender_id`. Omitting `user_id` causes KB upload failures (error 13002: 用户不存在) when `group_id` is set to a real group.",
|
|
92
130
|
].join("\n");
|
|
93
131
|
|
|
94
132
|
const UPLOAD_DELIVERABLE_TOOL = {
|
|
@@ -103,15 +141,23 @@ const UPLOAD_DELIVERABLE_TOOL = {
|
|
|
103
141
|
properties: {
|
|
104
142
|
resource_id: {
|
|
105
143
|
type: "string",
|
|
106
|
-
description: "当前聊天框/会话的唯一 ID
|
|
144
|
+
description: "当前聊天框/会话的唯一 ID,用于缓存去重。",
|
|
145
|
+
},
|
|
146
|
+
user_id: {
|
|
147
|
+
type: "string",
|
|
148
|
+
description: "上传者用户 ID。单聊取 owner_id,群聊取 sender_id。",
|
|
149
|
+
},
|
|
150
|
+
owner_id: {
|
|
151
|
+
type: "string",
|
|
152
|
+
description: "等同于 user_id,从消息元数据 owner_id 字段获取。两者任传其一即可。",
|
|
107
153
|
},
|
|
108
154
|
group_id: {
|
|
109
155
|
type: "string",
|
|
110
|
-
description: "
|
|
156
|
+
description: "分组 ID。单聊不传或传 'default';群聊传群 ID。",
|
|
111
157
|
},
|
|
112
|
-
|
|
158
|
+
group_name: {
|
|
113
159
|
type: "string",
|
|
114
|
-
description: "
|
|
160
|
+
description: "群聊名称,群聊场景必填,用于构造知识库存储路径;单聊不传。",
|
|
115
161
|
},
|
|
116
162
|
type: {
|
|
117
163
|
type: "string",
|
|
@@ -148,7 +194,7 @@ const UPLOAD_DELIVERABLE_TOOL = {
|
|
|
148
194
|
},
|
|
149
195
|
},
|
|
150
196
|
},
|
|
151
|
-
required: ["
|
|
197
|
+
required: ["type", "file_name"],
|
|
152
198
|
},
|
|
153
199
|
};
|
|
154
200
|
|
|
@@ -241,10 +287,14 @@ function deliverableTypeForPath(filePath, isDirectory) {
|
|
|
241
287
|
|
|
242
288
|
function isAllowedWorkspacePath(candidatePath) {
|
|
243
289
|
const normalized = normalizeSlash(path.resolve(candidatePath));
|
|
244
|
-
|
|
290
|
+
if (
|
|
245
291
|
normalized.indexOf("/data/workspace-") === 0 ||
|
|
246
292
|
normalized.indexOf("/home/node/.openclaw/workspace") === 0
|
|
247
|
-
)
|
|
293
|
+
) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
const openclawRoot = normalizeSlash(path.resolve(__dirname, "..", ".."));
|
|
297
|
+
return normalized.indexOf(openclawRoot + "/workspace") === 0;
|
|
248
298
|
}
|
|
249
299
|
|
|
250
300
|
function shouldIgnorePath(candidatePath) {
|
|
@@ -286,6 +336,20 @@ function workspaceRoots() {
|
|
|
286
336
|
} catch (_err) {
|
|
287
337
|
// /data is not guaranteed in non-pod test environments.
|
|
288
338
|
}
|
|
339
|
+
const openclawRoot = path.resolve(__dirname, "..", "..");
|
|
340
|
+
try {
|
|
341
|
+
for (const entry of fs.readdirSync(openclawRoot)) {
|
|
342
|
+
if (!entry || entry.indexOf("workspace") !== 0) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const root = path.join(openclawRoot, entry);
|
|
346
|
+
if (safeStat(root) && roots.indexOf(root) === -1) {
|
|
347
|
+
roots.push(root);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} catch (_err) {
|
|
351
|
+
// local dev path may not exist
|
|
352
|
+
}
|
|
289
353
|
return roots;
|
|
290
354
|
}
|
|
291
355
|
|
|
@@ -384,12 +448,12 @@ function extractFileReferencesFromText(text) {
|
|
|
384
448
|
for (const match of normalized.matchAll(INLINE_FILE_REF_RE)) {
|
|
385
449
|
add(match[0], match[1], "inline");
|
|
386
450
|
}
|
|
387
|
-
for (const match of normalized.matchAll(MARKDOWN_FILE_LINK_RE)) {
|
|
388
|
-
add(match[0], match[1], "markdown-link");
|
|
389
|
-
}
|
|
390
451
|
for (const match of normalized.matchAll(OUTPUT_RELATIVE_REF_RE)) {
|
|
391
452
|
add(match[1], match[1], "relative");
|
|
392
453
|
}
|
|
454
|
+
for (const match of normalized.matchAll(MARKDOWN_FILE_LINK_RE)) {
|
|
455
|
+
add(match[0], match[1], "markdown-link");
|
|
456
|
+
}
|
|
393
457
|
return refs;
|
|
394
458
|
}
|
|
395
459
|
|
|
@@ -505,13 +569,87 @@ function payloadResourceId(body) {
|
|
|
505
569
|
}
|
|
506
570
|
|
|
507
571
|
function payloadGroupId(body) {
|
|
508
|
-
|
|
572
|
+
const explicit = extractStringField(body, ["group_id", "groupId"]);
|
|
573
|
+
if (explicit) {
|
|
574
|
+
return explicit;
|
|
575
|
+
}
|
|
576
|
+
const fallback = extractStringField(body, ["resource_id", "resourceId", "conversation_id", "conversationId"]);
|
|
577
|
+
return fallback && fallback !== "default" ? fallback : "";
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function enrichDeliverableGroupContext(event) {
|
|
581
|
+
if (!event || !event.params || typeof event.params !== "object") {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const existingGroupId = trimString(event.params.group_id);
|
|
585
|
+
if (!existingGroupId) {
|
|
586
|
+
const inferredGroupId = extractContextString(event, [
|
|
587
|
+
"group_id", "groupId", "conversation_id", "conversationId",
|
|
588
|
+
]);
|
|
589
|
+
if (inferredGroupId && inferredGroupId !== "default") {
|
|
590
|
+
event.params.group_id = inferredGroupId;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const existingGroupName = trimString(event.params.group_name);
|
|
594
|
+
if (!existingGroupName) {
|
|
595
|
+
const inferredGroupName = extractContextString(event, [
|
|
596
|
+
"group_name", "groupName", "group_subject", "groupSubject",
|
|
597
|
+
]);
|
|
598
|
+
if (inferredGroupName) {
|
|
599
|
+
event.params.group_name = inferredGroupName;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const existingUserId = trimString(event.params.user_id);
|
|
603
|
+
if (!existingUserId || existingUserId === "default") {
|
|
604
|
+
const inferredUserId = extractContextString(event, [
|
|
605
|
+
"sender_id", "senderId", "owner_id", "ownerId", "user_id", "userId",
|
|
606
|
+
]);
|
|
607
|
+
if (inferredUserId && inferredUserId !== "default") {
|
|
608
|
+
event.params.user_id = inferredUserId;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
509
611
|
}
|
|
510
612
|
|
|
511
613
|
function payloadUserId(body) {
|
|
512
614
|
return extractStringField(body, ["user_id", "userId", "sender_id", "senderId", "owner_id", "ownerId"]);
|
|
513
615
|
}
|
|
514
616
|
|
|
617
|
+
function normalizeBareUserID(value) {
|
|
618
|
+
let raw = trimString(value);
|
|
619
|
+
if (!raw) {
|
|
620
|
+
return "";
|
|
621
|
+
}
|
|
622
|
+
if (raw.indexOf("user_") === 0 && raw.indexOf("_lobster_") > 0) {
|
|
623
|
+
raw = raw.slice("user_".length, raw.indexOf("_lobster_"));
|
|
624
|
+
} else if (raw.indexOf("user-") === 0 || raw.indexOf("user_") === 0) {
|
|
625
|
+
raw = raw.slice("user-".length);
|
|
626
|
+
}
|
|
627
|
+
return raw;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function userIDFromRelease(release) {
|
|
631
|
+
return normalizeBareUserID(release);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function userIDFromResourceID(resourceID) {
|
|
635
|
+
const raw = trimString(resourceID);
|
|
636
|
+
if (raw.indexOf("user_") !== 0 && raw.indexOf("user-") !== 0) {
|
|
637
|
+
return "";
|
|
638
|
+
}
|
|
639
|
+
return normalizeBareUserID(raw);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function deriveReleaseName() {
|
|
643
|
+
let release = process.env.botID || process.env.OPENCLAW_RELEASE || process.env.BOT_ID || "";
|
|
644
|
+
if (!release) {
|
|
645
|
+
const tok = process.env.OPENCLAW_GATEWAY_TOKEN || "";
|
|
646
|
+
if (tok.indexOf("oc-") === 0) {
|
|
647
|
+
release = tok.slice(3);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
return release;
|
|
651
|
+
}
|
|
652
|
+
|
|
515
653
|
function isWriteTool(toolName) {
|
|
516
654
|
const normalized = normalizeToolName(toolName);
|
|
517
655
|
return normalized === "write" || normalized.endsWith("__write") || normalized === "write_file" || normalized.endsWith("__write_file");
|
|
@@ -572,76 +710,128 @@ function pendingCandidatesForPayload(body) {
|
|
|
572
710
|
return out;
|
|
573
711
|
}
|
|
574
712
|
|
|
575
|
-
function
|
|
576
|
-
|
|
713
|
+
function kbBaseURL() {
|
|
714
|
+
const cfg = loadDeliverableConfig();
|
|
715
|
+
return (process.env.KNOWLEDGE_DB_URL || cfg.kbBaseUrl || DEFAULT_KB_BASE_URL).replace(/\/$/, "");
|
|
577
716
|
}
|
|
578
717
|
|
|
579
|
-
function
|
|
580
|
-
|
|
718
|
+
function kbPublicURL() {
|
|
719
|
+
const cfg = loadDeliverableConfig();
|
|
720
|
+
return (process.env.KNOWLEDGE_DB_PUBLIC_URL || cfg.kbPublicUrl || process.env.KNOWLEDGE_DB_URL || cfg.kbBaseUrl || DEFAULT_KB_BASE_URL).replace(/\/$/, "");
|
|
581
721
|
}
|
|
582
722
|
|
|
583
|
-
function
|
|
584
|
-
return process.env.
|
|
723
|
+
function kbApiKey() {
|
|
724
|
+
return process.env.KNOWLEDGE_DB_API_KEY || DEFAULT_KB_API_KEY;
|
|
585
725
|
}
|
|
586
726
|
|
|
587
|
-
function
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
return release;
|
|
727
|
+
function kbUploadPath(groupId, groupName) {
|
|
728
|
+
const gid = trimString(groupId) || DEFAULT_GROUP_ID;
|
|
729
|
+
const gname = gid === DEFAULT_GROUP_ID
|
|
730
|
+
? DEFAULT_GROUP_NAME
|
|
731
|
+
: trimString(groupName) || gid;
|
|
732
|
+
return "/" + gname + "/raw";
|
|
596
733
|
}
|
|
597
734
|
|
|
598
|
-
function
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
if (raw.indexOf("user_") === 0 && raw.indexOf("_lobster_") > 0) {
|
|
604
|
-
raw = raw.slice("user_".length, raw.indexOf("_lobster_"));
|
|
605
|
-
} else if (raw.indexOf("user-") === 0 || raw.indexOf("user_") === 0) {
|
|
606
|
-
raw = raw.slice("user-".length);
|
|
607
|
-
}
|
|
608
|
-
return raw;
|
|
735
|
+
function kbPreviewURL(documentId, userId, groupId) {
|
|
736
|
+
return kbPublicURL() + "/documents/content?user_id="
|
|
737
|
+
+ encodeURIComponent(trimString(userId) || "default")
|
|
738
|
+
+ "&group_id=" + encodeURIComponent(trimString(groupId) || DEFAULT_GROUP_ID)
|
|
739
|
+
+ "&id=" + encodeURIComponent(String(documentId));
|
|
609
740
|
}
|
|
610
741
|
|
|
611
|
-
function
|
|
612
|
-
|
|
742
|
+
function kbExtractDocumentId(response) {
|
|
743
|
+
const data = (response && response.data) || response || {};
|
|
744
|
+
const items = Array.isArray(data.items) ? data.items : [];
|
|
745
|
+
if (items.length > 0 && items[0].document && items[0].document.id !== undefined) {
|
|
746
|
+
return items[0].document.id;
|
|
747
|
+
}
|
|
748
|
+
if (data.id !== undefined) {
|
|
749
|
+
return data.id;
|
|
750
|
+
}
|
|
751
|
+
return null;
|
|
613
752
|
}
|
|
614
753
|
|
|
615
|
-
function
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
754
|
+
function buildFileBuffersFromBody(body, isDirectory) {
|
|
755
|
+
if (isDirectory && Array.isArray(body.files)) {
|
|
756
|
+
return body.files.map((file) => ({
|
|
757
|
+
fileName: file.name || "file",
|
|
758
|
+
content: file.contentText
|
|
759
|
+
? Buffer.from(file.contentText, "utf8")
|
|
760
|
+
: Buffer.from(file.contentBase64 || "", "base64"),
|
|
761
|
+
}));
|
|
619
762
|
}
|
|
620
|
-
|
|
763
|
+
const content = body.contentBase64
|
|
764
|
+
? Buffer.from(body.contentBase64, "base64")
|
|
765
|
+
: Buffer.from(body.contentText || "", "utf8");
|
|
766
|
+
return [{ fileName: body.fileName || "file", content }];
|
|
621
767
|
}
|
|
622
768
|
|
|
623
|
-
function
|
|
769
|
+
function kbMultipartUpload(fileBuffers, fields, pathsArray) {
|
|
624
770
|
return new Promise((resolve, reject) => {
|
|
625
771
|
let parsed;
|
|
626
772
|
try {
|
|
627
|
-
parsed = new URL(
|
|
773
|
+
parsed = new URL(kbBaseURL() + "/documents/batch-upload");
|
|
628
774
|
} catch (err) {
|
|
629
775
|
reject(err);
|
|
630
776
|
return;
|
|
631
777
|
}
|
|
778
|
+
const boundary = "----Deliverables" + Date.now() + Math.random().toString(36).slice(2);
|
|
779
|
+
const parts = [];
|
|
780
|
+
|
|
781
|
+
for (const file of fileBuffers) {
|
|
782
|
+
parts.push(
|
|
783
|
+
Buffer.from(
|
|
784
|
+
"--" + boundary + "\r\n" +
|
|
785
|
+
'Content-Disposition: form-data; name="files"; filename="' + file.fileName + '"\r\n' +
|
|
786
|
+
"Content-Type: application/octet-stream\r\n\r\n",
|
|
787
|
+
),
|
|
788
|
+
);
|
|
789
|
+
parts.push(file.content);
|
|
790
|
+
parts.push(Buffer.from("\r\n"));
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
if (Array.isArray(pathsArray) && pathsArray.length > 0) {
|
|
794
|
+
for (const p of pathsArray) {
|
|
795
|
+
parts.push(
|
|
796
|
+
Buffer.from(
|
|
797
|
+
"--" + boundary + "\r\n" +
|
|
798
|
+
'Content-Disposition: form-data; name="paths"\r\n\r\n' +
|
|
799
|
+
(p || "") + "\r\n",
|
|
800
|
+
),
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
for (const [key, value] of Object.entries(fields || {})) {
|
|
806
|
+
if (value === undefined || value === null || value === "") {
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
parts.push(
|
|
810
|
+
Buffer.from(
|
|
811
|
+
"--" + boundary + "\r\n" +
|
|
812
|
+
'Content-Disposition: form-data; name="' + key + '"\r\n\r\n' +
|
|
813
|
+
String(value) + "\r\n",
|
|
814
|
+
),
|
|
815
|
+
);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
parts.push(Buffer.from("--" + boundary + "--\r\n"));
|
|
819
|
+
const body = Buffer.concat(parts);
|
|
820
|
+
|
|
632
821
|
const isHTTPS = parsed.protocol === "https:";
|
|
633
822
|
const transport = isHTTPS ? https : http;
|
|
634
|
-
const
|
|
823
|
+
const userId = (fields && fields.user_id) || "default";
|
|
635
824
|
const req = transport.request(
|
|
636
825
|
{
|
|
637
826
|
hostname: parsed.hostname,
|
|
638
827
|
port: parsed.port || (isHTTPS ? 443 : 80),
|
|
639
828
|
path: parsed.pathname + (parsed.search || ""),
|
|
640
|
-
method,
|
|
829
|
+
method: "POST",
|
|
641
830
|
headers: {
|
|
642
|
-
"Content-Type": "
|
|
643
|
-
"X-
|
|
644
|
-
"
|
|
831
|
+
"Content-Type": "multipart/form-data; boundary=" + boundary,
|
|
832
|
+
"X-User-ID": userId,
|
|
833
|
+
"X-API-Key": kbApiKey(),
|
|
834
|
+
"Content-Length": body.length,
|
|
645
835
|
},
|
|
646
836
|
},
|
|
647
837
|
(res) => {
|
|
@@ -653,11 +843,15 @@ function httpJSONRequest(method, requestPath, body) {
|
|
|
653
843
|
try {
|
|
654
844
|
obj = JSON.parse(text);
|
|
655
845
|
} catch (_err) {
|
|
656
|
-
reject(new Error(`
|
|
846
|
+
reject(new Error(`kb ${res.statusCode}: non-JSON response: ${text.slice(0, 200)}`));
|
|
657
847
|
return;
|
|
658
848
|
}
|
|
659
849
|
if (res.statusCode >= 400) {
|
|
660
|
-
reject(new Error(`
|
|
850
|
+
reject(new Error(`kb ${res.statusCode}: ${obj.message || text.slice(0, 200)}`));
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (obj.code !== 0 && obj.code !== undefined && res.statusCode !== 206) {
|
|
854
|
+
reject(new Error(`kb API error: ${obj.message || text.slice(0, 200)}`));
|
|
661
855
|
return;
|
|
662
856
|
}
|
|
663
857
|
resolve(obj);
|
|
@@ -665,9 +859,7 @@ function httpJSONRequest(method, requestPath, body) {
|
|
|
665
859
|
},
|
|
666
860
|
);
|
|
667
861
|
req.on("error", reject);
|
|
668
|
-
|
|
669
|
-
req.write(bodyStr);
|
|
670
|
-
}
|
|
862
|
+
req.write(body);
|
|
671
863
|
req.end();
|
|
672
864
|
});
|
|
673
865
|
}
|
|
@@ -888,7 +1080,7 @@ function buildReplyMarkdown(opts) {
|
|
|
888
1080
|
if (previewURL) {
|
|
889
1081
|
lines.push(`预览链接:[点击预览](${previewURL})`);
|
|
890
1082
|
}
|
|
891
|
-
if (downloadURL) {
|
|
1083
|
+
if (downloadURL && downloadURL !== previewURL) {
|
|
892
1084
|
if (isDirectory || deliverableType === "game") {
|
|
893
1085
|
lines.push(`文件列表:[查看目录](${downloadURL})`);
|
|
894
1086
|
} else {
|
|
@@ -922,15 +1114,7 @@ function buildNativeUploadRequestBody(args) {
|
|
|
922
1114
|
}
|
|
923
1115
|
}
|
|
924
1116
|
|
|
925
|
-
const body = {
|
|
926
|
-
resourceId: trimString(args && args.resource_id),
|
|
927
|
-
groupId: trimString(args && args.group_id) || trimString(args && args.resource_id),
|
|
928
|
-
userId:
|
|
929
|
-
normalizeBareUserID(args && args.user_id) ||
|
|
930
|
-
userIDFromResourceID(args && args.resource_id) ||
|
|
931
|
-
userIDFromRelease(deriveReleaseName()),
|
|
932
|
-
release: deriveReleaseName(),
|
|
933
|
-
};
|
|
1117
|
+
const body = {};
|
|
934
1118
|
|
|
935
1119
|
if (fileCandidate) {
|
|
936
1120
|
const stat = safeStat(fileCandidate.path);
|
|
@@ -965,9 +1149,9 @@ function buildNativeUploadRequestBody(args) {
|
|
|
965
1149
|
body.files = files;
|
|
966
1150
|
return { body, isDirectory: true };
|
|
967
1151
|
}
|
|
968
|
-
const contentText = (args && (args.content_text || args.contentText)) || "";
|
|
969
|
-
const contentBase64 = (args && (args.content_base64 || args.contentBase64)) || "";
|
|
970
1152
|
if (body.type === "link") {
|
|
1153
|
+
const contentText = (args && (args.content_text || args.contentText)) || "";
|
|
1154
|
+
const contentBase64 = (args && (args.content_base64 || args.contentBase64)) || "";
|
|
971
1155
|
if (trimString(contentText) || trimString(contentBase64)) {
|
|
972
1156
|
body.type = deliverableTypeForPath(body.fileName, false);
|
|
973
1157
|
} else if (!isHTTPURLLike(body.fileName)) {
|
|
@@ -976,6 +1160,8 @@ function buildNativeUploadRequestBody(args) {
|
|
|
976
1160
|
);
|
|
977
1161
|
}
|
|
978
1162
|
}
|
|
1163
|
+
const contentText = (args && (args.content_text || args.contentText)) || "";
|
|
1164
|
+
const contentBase64 = (args && (args.content_base64 || args.contentBase64)) || "";
|
|
979
1165
|
if (body.type === "pdf" && trimString(contentText) && !trimString(contentBase64)) {
|
|
980
1166
|
body.contentBase64 = createBasicTextPDF(contentText, body.fileName).toString("base64");
|
|
981
1167
|
} else {
|
|
@@ -987,10 +1173,20 @@ function buildNativeUploadRequestBody(args) {
|
|
|
987
1173
|
|
|
988
1174
|
async function uploadDeliverable(args) {
|
|
989
1175
|
const { body, isDirectory } = buildNativeUploadRequestBody(args || {});
|
|
990
|
-
const
|
|
991
|
-
const
|
|
992
|
-
const
|
|
993
|
-
const
|
|
1176
|
+
const userId = payloadUserId(args) || "default";
|
|
1177
|
+
const groupId = payloadGroupId(args) || DEFAULT_GROUP_ID;
|
|
1178
|
+
const groupName = extractStringField(args || {}, ["group_name", "groupName", "group_subject", "groupSubject"]);
|
|
1179
|
+
const uploadPath = kbUploadPath(groupId, groupName);
|
|
1180
|
+
const fileBuffers = buildFileBuffersFromBody(body, isDirectory);
|
|
1181
|
+
const fields = {
|
|
1182
|
+
user_id: userId,
|
|
1183
|
+
group_id: groupId,
|
|
1184
|
+
path: uploadPath,
|
|
1185
|
+
};
|
|
1186
|
+
const response = await kbMultipartUpload(fileBuffers, fields);
|
|
1187
|
+
const documentId = kbExtractDocumentId(response);
|
|
1188
|
+
const previewURL = documentId ? kbPreviewURL(documentId, userId, groupId) : "";
|
|
1189
|
+
const downloadURL = previewURL;
|
|
994
1190
|
const replyMarkdown = buildReplyMarkdown({
|
|
995
1191
|
previewURL,
|
|
996
1192
|
downloadURL,
|
|
@@ -998,11 +1194,9 @@ async function uploadDeliverable(args) {
|
|
|
998
1194
|
isDirectory,
|
|
999
1195
|
});
|
|
1000
1196
|
return {
|
|
1001
|
-
|
|
1002
|
-
backend: data.backend || "",
|
|
1197
|
+
document_id: documentId,
|
|
1003
1198
|
download_url: downloadURL,
|
|
1004
1199
|
preview_url: previewURL,
|
|
1005
|
-
expire_at: data.expireAt,
|
|
1006
1200
|
reply_markdown: replyMarkdown,
|
|
1007
1201
|
message: replyMarkdown,
|
|
1008
1202
|
};
|
|
@@ -1032,35 +1226,41 @@ async function uploadCandidate(candidate, body) {
|
|
|
1032
1226
|
if (!stat) {
|
|
1033
1227
|
throw new Error(`file no longer exists: ${candidate.fileName}`);
|
|
1034
1228
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
};
|
|
1229
|
+
|
|
1230
|
+
const userId = payloadUserId(body) || "default";
|
|
1231
|
+
const groupId = payloadGroupId(body) || DEFAULT_GROUP_ID;
|
|
1232
|
+
const fileName = candidate.fileName || path.basename(candidate.path);
|
|
1233
|
+
const uploadPath = kbUploadPath(groupId, "");
|
|
1234
|
+
|
|
1235
|
+
let fileBuffers;
|
|
1043
1236
|
if (stat.isDirectory()) {
|
|
1044
1237
|
const files = collectDirectoryFiles(candidate.path);
|
|
1045
1238
|
if (files.length === 0) {
|
|
1046
1239
|
throw new Error(`directory has no uploadable files: ${candidate.fileName}`);
|
|
1047
1240
|
}
|
|
1048
|
-
|
|
1241
|
+
fileBuffers = files.map((f) => ({
|
|
1242
|
+
fileName: f.name,
|
|
1243
|
+
content: f.contentText ? Buffer.from(f.contentText, "utf8") : Buffer.from(f.contentBase64 || "", "base64"),
|
|
1244
|
+
}));
|
|
1049
1245
|
} else {
|
|
1050
1246
|
if (stat.size > AUTO_UPLOAD_MAX_BYTES) {
|
|
1051
1247
|
throw new Error(`file exceeds auto-upload limit: ${candidate.fileName}`);
|
|
1052
1248
|
}
|
|
1053
|
-
|
|
1249
|
+
fileBuffers = [{ fileName, content: fs.readFileSync(candidate.path) }];
|
|
1054
1250
|
}
|
|
1055
1251
|
|
|
1056
|
-
const response = await
|
|
1057
|
-
|
|
1058
|
-
|
|
1252
|
+
const response = await kbMultipartUpload(fileBuffers, {
|
|
1253
|
+
user_id: userId,
|
|
1254
|
+
group_id: groupId,
|
|
1255
|
+
path: uploadPath,
|
|
1256
|
+
});
|
|
1257
|
+
const documentId = kbExtractDocumentId(response);
|
|
1258
|
+
const previewURL = documentId ? kbPreviewURL(documentId, userId, groupId) : "";
|
|
1059
1259
|
const result = {
|
|
1060
|
-
fileName
|
|
1061
|
-
|
|
1260
|
+
fileName,
|
|
1261
|
+
document_id: documentId,
|
|
1062
1262
|
previewURL,
|
|
1063
|
-
downloadURL:
|
|
1263
|
+
downloadURL: previewURL,
|
|
1064
1264
|
};
|
|
1065
1265
|
cache.set(cacheKey, { result, createdAt: Date.now() });
|
|
1066
1266
|
return result;
|
|
@@ -1552,12 +1752,14 @@ function extractDeliverableURL(line) {
|
|
|
1552
1752
|
return "";
|
|
1553
1753
|
}
|
|
1554
1754
|
|
|
1555
|
-
function
|
|
1755
|
+
function configuredKBHosts() {
|
|
1756
|
+
const cfg = loadDeliverableConfig();
|
|
1556
1757
|
const values = [
|
|
1557
|
-
process.env.
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1758
|
+
process.env.KNOWLEDGE_DB_PUBLIC_URL,
|
|
1759
|
+
cfg.kbPublicUrl,
|
|
1760
|
+
process.env.KNOWLEDGE_DB_URL,
|
|
1761
|
+
cfg.kbBaseUrl,
|
|
1762
|
+
DEFAULT_KB_BASE_URL,
|
|
1561
1763
|
];
|
|
1562
1764
|
const hosts = new Set();
|
|
1563
1765
|
for (const value of values) {
|
|
@@ -1585,10 +1787,10 @@ function isTrustedDeliverableURL(rawURL) {
|
|
|
1585
1787
|
} catch (_err) {
|
|
1586
1788
|
return false;
|
|
1587
1789
|
}
|
|
1588
|
-
if (!
|
|
1790
|
+
if (!DELIVERABLE_KB_PATH_RE.test(parsed.pathname)) {
|
|
1589
1791
|
return false;
|
|
1590
1792
|
}
|
|
1591
|
-
const hosts =
|
|
1793
|
+
const hosts = configuredKBHosts();
|
|
1592
1794
|
const host = parsed.host.toLowerCase();
|
|
1593
1795
|
const hostname = parsed.hostname.toLowerCase();
|
|
1594
1796
|
return hosts.has(host) || hosts.has(hostname);
|
|
@@ -1716,17 +1918,11 @@ function extractDeliverableIdentity(rawURL) {
|
|
|
1716
1918
|
} catch (_err) {
|
|
1717
1919
|
return "";
|
|
1718
1920
|
}
|
|
1719
|
-
if (!
|
|
1921
|
+
if (!DELIVERABLE_KB_PATH_RE.test(parsed.pathname)) {
|
|
1720
1922
|
return "";
|
|
1721
1923
|
}
|
|
1722
|
-
const
|
|
1723
|
-
|
|
1724
|
-
for (const part of parts) {
|
|
1725
|
-
if (uuidRe.test(part)) {
|
|
1726
|
-
return part.toLowerCase();
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
return parsed.href;
|
|
1924
|
+
const docId = parsed.searchParams.get("id");
|
|
1925
|
+
return docId ? String(docId) : "";
|
|
1730
1926
|
}
|
|
1731
1927
|
|
|
1732
1928
|
function selectPalzFileURL(linkItems) {
|
|
@@ -1768,7 +1964,7 @@ function cloneBodyAsFileLink(body, fileUrl, suffix) {
|
|
|
1768
1964
|
function cloneBodyAsBlockedDeliverableLink(body) {
|
|
1769
1965
|
return cloneBody(
|
|
1770
1966
|
body,
|
|
1771
|
-
"
|
|
1967
|
+
"交付物上传未成功,已阻止发送未通过知识库交付物系统生成的文件链接。请重新通过交付物上传工具上传后再发送。",
|
|
1772
1968
|
"",
|
|
1773
1969
|
);
|
|
1774
1970
|
}
|
|
@@ -1972,6 +2168,7 @@ const plugin = {
|
|
|
1972
2168
|
|
|
1973
2169
|
api.on("before_tool_call", async (event) => {
|
|
1974
2170
|
if (isDeliverablesUploadTool(event.toolName)) {
|
|
2171
|
+
enrichDeliverableGroupContext(event);
|
|
1975
2172
|
cacheUploadSummary(event.params);
|
|
1976
2173
|
return;
|
|
1977
2174
|
}
|
|
@@ -2009,15 +2206,21 @@ const plugin = {
|
|
|
2009
2206
|
};
|
|
2010
2207
|
|
|
2011
2208
|
plugin.__test = {
|
|
2209
|
+
buildFileBuffersFromBody,
|
|
2012
2210
|
buildNativeUploadRequestBody,
|
|
2013
2211
|
createBasicTextPDF,
|
|
2014
2212
|
deliverableTypeForPath,
|
|
2015
|
-
extractFileReferencesFromText,
|
|
2016
2213
|
extractDeliverableURL,
|
|
2214
|
+
extractFileReferencesFromText,
|
|
2017
2215
|
findUntrustedOSSDeliverableURL,
|
|
2018
2216
|
isDeliverableLinkLine,
|
|
2019
2217
|
isOSSLikeURL,
|
|
2020
2218
|
isTrustedDeliverableURL,
|
|
2219
|
+
kbExtractDocumentId,
|
|
2220
|
+
kbMultipartUpload,
|
|
2221
|
+
kbPreviewURL,
|
|
2222
|
+
kbUploadPath,
|
|
2223
|
+
loadDeliverableConfig,
|
|
2021
2224
|
selectPalzFileURL,
|
|
2022
2225
|
splitDeliverableMessage,
|
|
2023
2226
|
};
|
package/openclaw-plugin.json
CHANGED
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "1.1.5",
|
|
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.
|
|
3
|
+
"version": "1.1.5",
|
|
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",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"files": [
|
|
16
16
|
"INSTALL.md",
|
|
17
17
|
"index.js",
|
|
18
|
+
"deliverables.*.config.json",
|
|
18
19
|
"openclaw.plugin.json",
|
|
19
20
|
"openclaw-plugin.json",
|
|
20
21
|
"skills/",
|
|
@@ -22,21 +22,23 @@ description: 上传AI生成的文件到交付物系统,返回可分享的预
|
|
|
22
22
|
- 如果你使用 `write` 工具创建交付物文件,目标路径必须在 `output/` 下。
|
|
23
23
|
- 如果你已经误写到工作区根目录,必须先移动或复制到 `output/`,再继续上传交付物并向用户汇报。
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## 调用前必须准备的参数
|
|
26
26
|
|
|
27
27
|
从当前会话上下文提取以下值,**调用时一次性填完,不要留空**:
|
|
28
28
|
|
|
29
|
-
| 参数 | 取值来源 | 示例 |
|
|
30
|
-
|
|
31
|
-
| `
|
|
32
|
-
| `group_id` |
|
|
33
|
-
| `
|
|
34
|
-
| `type` | 根据内容选择:`article`/`game`/`image`/`video`/`ppt`/`pdf`/`zip`;`link` 仅用于外部 `http(s)` URL | `article` |
|
|
35
|
-
| `file_name` | 有意义的文件名,单文件必须含扩展名;若用户未指定文档格式,默认用 `.md` | `report-2026.html` |
|
|
36
|
-
| `content_text` | 文件的完整文本内容(HTML/Markdown等) | `<html>...</html>` |
|
|
37
|
-
| `file_path` | PDF/PPT/图片/zip 等二进制文件的本地路径,推荐使用,避免手工复制 base64 | `output/sample.pdf` |
|
|
38
|
-
|
|
39
|
-
>
|
|
29
|
+
| 参数 | 必填 | 取值来源 | 示例 |
|
|
30
|
+
|------|------|---------|------|
|
|
31
|
+
| `user_id` | **是(必传)** | 单聊取 `owner_id`,群聊取 `sender_id`。**不可省略**,否则 KB 上传报错 13002 | `cbb0fab9...` |
|
|
32
|
+
| `group_id` | 群聊时必填 | 群聊取消息元数据中的 `group_id`;**单聊不传**(自动为 `default`) | `grp_43c75713` |
|
|
33
|
+
| `group_name` | 群聊时必填 | 群聊取消息元数据中的群聊名称;**单聊不传** | `项目讨论群` |
|
|
34
|
+
| `type` | 是 | 根据内容选择:`article`/`game`/`image`/`video`/`ppt`/`pdf`/`zip`;`link` 仅用于外部 `http(s)` URL | `article` |
|
|
35
|
+
| `file_name` | 是 | 有意义的文件名,单文件必须含扩展名;若用户未指定文档格式,默认用 `.md` | `report-2026.html` |
|
|
36
|
+
| `content_text` | 按需 | 文件的完整文本内容(HTML/Markdown等) | `<html>...</html>` |
|
|
37
|
+
| `file_path` | 按需 | PDF/PPT/图片/zip 等二进制文件的本地路径,推荐使用,避免手工复制 base64 | `output/sample.pdf` |
|
|
38
|
+
|
|
39
|
+
> **单聊(direct chat)**:`user_id` 填 `owner_id`,不传 `group_id` 和 `group_name`。
|
|
40
|
+
> **群聊(group chat)**:`user_id` 填 `sender_id`,`group_id` 和 `group_name` 从群聊元数据获取。
|
|
41
|
+
> **⚠ 警告**:`user_id` 必须传入真实值。当 `group_id` 是真实群 ID 但 `user_id` 缺失或为 `"default"` 时,KB 后端会返回 `500: 用户不存在`。
|
|
40
42
|
|
|
41
43
|
## 二进制文件上传(强制)
|
|
42
44
|
|