@dcrays/dcgchat 0.1.9 → 0.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # OpenClaw DCG Chat 插件
1
+ # OpenClaw 书灵墨宝 插件
2
2
 
3
- 连接 OpenClaw 与 DCG Chat 产品的通道插件。
3
+ 连接 OpenClaw 与 书灵墨宝 产品的通道插件。
4
4
 
5
5
  ## 架构
6
6
 
package/index.ts CHANGED
@@ -6,8 +6,8 @@ import { monitoringToolMessage } from "./src/tool.js";
6
6
 
7
7
  const plugin = {
8
8
  id: "dcgchat",
9
- name: "DCG Chat",
10
- description: "连接 OpenClaw 与 DCG Chat 产品(WebSocket)",
9
+ name: "书灵墨宝",
10
+ description: "连接 OpenClaw 与 书灵墨宝 产品(WebSocket)",
11
11
  configSchema: emptyPluginConfigSchema(),
12
12
  register(api: OpenClawPluginApi) {
13
13
  setDcgchatRuntime(api.runtime);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
- "description": "OpenClaw channel plugin for DCG Chat (WebSocket)",
5
+ "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
7
7
  "license": "MIT",
8
8
  "files": [
@@ -20,33 +20,22 @@
20
20
  "typecheck": "tsc --noEmit"
21
21
  },
22
22
  "dependencies": {
23
- "ali-oss": "file:src/libs/ali-oss-6.23.0.tgz",
24
- "ws": "file:src/libs/ws-8.19.0.tgz",
25
23
  "axios": "file:src/libs/axios-1.13.6.tgz",
26
- "md5": "file:src/libs/md5-2.3.0.tgz",
24
+ "ws": "file:src/libs/ws-8.19.0.tgz",
25
+ "mime-types": "file:src/libs/mime-types-3.0.2.tgz",
27
26
  "unzipper": "file:src/libs/unzipper-0.12.3.tgz"
28
27
  },
29
- "devDependencies": {
30
- "@types/node": "^22.0.0",
31
- "@types/ws": "^8.5.0",
32
- "openclaw": "2026.2.13",
33
- "tsx": "^4.19.0",
34
- "typescript": "^5.7.0"
35
- },
36
- "peerDependencies": {
37
- "openclaw": ">=2026.2.13"
38
- },
39
28
  "openclaw": {
40
29
  "extensions": [
41
30
  "./index.ts"
42
31
  ],
43
32
  "channel": {
44
33
  "id": "dcgchat",
45
- "label": "DCG Chat",
46
- "selectionLabel": "DCG Chat",
34
+ "label": "书灵墨宝",
35
+ "selectionLabel": "书灵墨宝",
47
36
  "docsPath": "/channels/dcgchat",
48
37
  "docsLabel": "dcgchat",
49
- "blurb": "连接 OpenClaw 与 DCG Chat 产品",
38
+ "blurb": "连接 OpenClaw 与 书灵墨宝 产品",
50
39
  "order": 80
51
40
  },
52
41
  "install": {
package/src/bot.ts CHANGED
@@ -1,10 +1,14 @@
1
1
  import path from "node:path";
2
+ import os from "node:os";
2
3
  import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
3
4
  import { createReplyPrefixContext } from "openclaw/plugin-sdk";
4
5
  import type { InboundMessage, OutboundReply } from "./types.js";
5
6
  import { getDcgchatRuntime } from "./runtime.js";
6
7
  import { resolveAccount } from "./channel.js";
7
8
  import { setMsgStatus } from "./tool.js";
9
+ import mime from "mime-types"
10
+
11
+ const targetPath = path.join(os.homedir(), '../');
8
12
 
9
13
  type MediaInfo = {
10
14
  path: string;
@@ -13,63 +17,26 @@ type MediaInfo = {
13
17
  };
14
18
 
15
19
  async function resolveMediaFromUrls(
16
- fileUrls: string[],
17
- maxBytes: number,
20
+ files: { url: string, name: string }[],
18
21
  log?: (msg: string) => void,
19
22
  ): Promise<MediaInfo[]> {
20
- const core = getDcgchatRuntime();
21
23
  const out: MediaInfo[] = [];
22
-
23
- log?.(`dcgchat media: starting resolve for ${fileUrls.length} file(s): ${JSON.stringify(fileUrls)}`);
24
-
25
- for (let i = 0; i < fileUrls.length; i++) {
26
- const url = fileUrls[i];
27
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fetching ${url}`);
24
+ for (let i = 0; i < files.length; i++) {
25
+ const url = path.join(targetPath, files[i]?.url);
28
26
  try {
29
27
  const response = await fetch(url);
30
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fetch response status=${response.status}, content-type=${response.headers.get("content-type")}, content-length=${response.headers.get("content-length")}`);
31
- if (!response.ok) {
32
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fetch failed with HTTP ${response.status}, skipping`);
33
- continue;
34
- }
35
- const buffer = Buffer.from(await response.arrayBuffer());
36
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] downloaded buffer size=${buffer.length} bytes`);
37
-
38
- let contentType = response.headers.get("content-type") || "";
39
- if (!contentType) {
40
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] no content-type header, detecting mime...`);
41
- // @ts-ignore
42
- contentType = await core.media.detectMime({ buffer });
43
- }
44
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] resolved contentType=${contentType}`);
45
-
46
- const fileName = path.basename(new URL(url).pathname) || "file";
47
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fileName=${fileName}, saving to disk (maxBytes=${maxBytes})...`);
48
-
49
- const saved = await core.channel.media.saveMediaBuffer(
50
- buffer,
51
- contentType,
52
- "inbound",
53
- maxBytes,
54
- fileName,
55
- );
56
-
28
+ const contentType = response.headers.get("content-type") || "";
57
29
  const isImage = contentType.startsWith("image/");
58
30
  out.push({
59
- path: saved.path,
31
+ path: url,
60
32
  // @ts-ignore
61
33
  contentType: saved.contentType,
62
34
  placeholder: isImage ? "<media:image>" : "<media:file>",
63
35
  });
64
-
65
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] saved to ${saved.path} (contentType=${saved.contentType}, isImage=${isImage})`);
66
36
  } catch (err) {
67
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] FAILED to process ${url}: ${String(err)}`);
37
+ log?.(`dcgchat media: [${i + 1}/${files.length}] FAILED to process ${url}: ${String(err)}`);
68
38
  }
69
39
  }
70
-
71
- log?.(`dcgchat media: resolve complete, ${out.length}/${fileUrls.length} file(s) succeeded`);
72
-
73
40
  return out;
74
41
  }
75
42
 
@@ -122,6 +89,10 @@ export async function handleDcgchatMessage(params: {
122
89
  // @ts-ignore
123
90
  content: {
124
91
  bot_token: msg.content.bot_token,
92
+ domain_id: msg.content.domain_id,
93
+ app_id: msg.content.app_id,
94
+ bot_id: msg.content.bot_id,
95
+ agent_id: msg.content.agent_id,
125
96
  session_id: msg.content.session_id,
126
97
  message_id: msg.content.message_id,
127
98
  response: "[错误] 消息格式不正确",
@@ -141,22 +112,28 @@ export async function handleDcgchatMessage(params: {
141
112
  });
142
113
 
143
114
  // 处理用户上传的文件
144
- const fileUrls = msg.content.file_urls ?? [];
145
- log(`dcgchat[${accountId}]: incoming message from user=${userId}, text="${text?.slice(0, 80)}", file_urls count=${fileUrls.length}`);
115
+ const files = msg.content.files ?? [];
146
116
  let mediaPayload: Record<string, unknown> = {};
147
- if (fileUrls.length > 0) {
148
- log(`dcgchat[${accountId}]: processing ${fileUrls.length} file(s): ${JSON.stringify(fileUrls)}`);
149
- const mediaMaxBytes = 30 * 1024 * 1024;
150
- const mediaList = await resolveMediaFromUrls(fileUrls, mediaMaxBytes, log);
117
+ if (files.length > 0) {
118
+ const mediaList = files?.map(item => {
119
+ const contentType = mime.lookup(item.name) || "application/octet-stream";
120
+ const isImage = contentType.startsWith("image/");
121
+ return {
122
+ path: path.join(targetPath, item?.url),
123
+ contentType: contentType,
124
+ placeholder: isImage ? "<media:image>" : "<media:file>",
125
+ }
126
+ });
151
127
  mediaPayload = buildMediaPayload(mediaList);
152
- log(`dcgchat[${accountId}]: media resolved ${mediaList.length}/${fileUrls.length} file(s), payload=${JSON.stringify(mediaPayload)}`);
128
+ log(`dcgchat[${accountId}]: media resolved ${mediaList.length}/${files.length} file(s), payload=${JSON.stringify(mediaList)}`);
153
129
  }
154
130
 
155
131
  const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
156
132
  // const messageBody = `${userId}: ${text}`;
133
+ // 补充消息
157
134
  const messageBody = text;
158
135
  const bodyFormatted = core.channel.reply.formatAgentEnvelope({
159
- channel: "DCG Chat",
136
+ channel: "书灵墨宝",
160
137
  from: userId,
161
138
  timestamp: new Date(),
162
139
  envelope: envelopeOptions,
@@ -197,7 +174,10 @@ export async function handleDcgchatMessage(params: {
197
174
  onReplyStart: async () => {},
198
175
  deliver: async (payload) => {
199
176
  log(`dcgchat[${accountId}][deliver]: received chunk, text length=${payload.text?.length || 0}`);
200
- const t = payload.text?.trim();
177
+ const t = payload.text?.trim().replaceAll(
178
+ "/root/.openclaw/workspace/moBooksAgentGenerate",
179
+ "/upload"
180
+ );
201
181
  if (t) {
202
182
  log(`dcgchat[${accountId}][deliver]: sending chunk to user ${msg._userId}, text="${t.slice(0, 50)}..."`);
203
183
  params.onChunk({
@@ -206,6 +186,10 @@ export async function handleDcgchatMessage(params: {
206
186
  source: "client",
207
187
  content: {
208
188
  bot_token: msg.content.bot_token,
189
+ domain_id: msg.content.domain_id,
190
+ app_id: msg.content.app_id,
191
+ bot_id: msg.content.bot_id,
192
+ agent_id: msg.content.agent_id,
209
193
  session_id: msg.content.session_id,
210
194
  message_id: msg.content.message_id,
211
195
  response: t,
@@ -242,6 +226,10 @@ export async function handleDcgchatMessage(params: {
242
226
  source: "client",
243
227
  content: {
244
228
  bot_token: msg.content.bot_token,
229
+ domain_id: msg.content.domain_id,
230
+ app_id: msg.content.app_id,
231
+ bot_id: msg.content.bot_id,
232
+ agent_id: msg.content.agent_id,
245
233
  session_id: msg.content.session_id,
246
234
  message_id: msg.content.message_id,
247
235
  response: '',
@@ -262,6 +250,10 @@ export async function handleDcgchatMessage(params: {
262
250
  source: "client",
263
251
  content: {
264
252
  bot_token: msg.content.bot_token,
253
+ domain_id: msg.content.domain_id,
254
+ app_id: msg.content.app_id,
255
+ bot_id: msg.content.bot_id,
256
+ agent_id: msg.content.agent_id,
265
257
  session_id: msg.content.session_id,
266
258
  message_id: msg.content.message_id,
267
259
  response: `[错误] ${err instanceof Error ? err.message : String(err)}`,
package/src/channel.ts CHANGED
@@ -1,11 +1,51 @@
1
+ import { copyFile, mkdir, rename, unlink } from "node:fs/promises";
2
+ import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
3
+ import os from "node:os";
1
4
  import type { ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk";
2
5
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
3
6
  import type { ResolvedDcgchatAccount, DcgchatConfig } from "./types.js";
4
7
  import { logDcgchat } from "./log.js";
5
8
  import { getWsConnection } from "./connection.js";
6
- import { ossUpload } from "./oss.js";
7
9
  import { getMsgParams } from "./tool.js";
8
10
 
11
+ const uploadRoot = resolve('/', "upload");
12
+
13
+ function isPathInside(parentPath: string, targetPath: string): boolean {
14
+ const relativePath = relative(parentPath, targetPath);
15
+ return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
16
+ }
17
+
18
+ async function ensureMediaInUploadDir(url: string): Promise<string> {
19
+ if (!url || /^([a-z][a-z\d+\-.]*):\/\//i.test(url) || !isAbsolute(url)) {
20
+ return url;
21
+ }
22
+ const sourcePath = resolve(url);
23
+ if (isPathInside(uploadRoot, sourcePath)) {
24
+ return sourcePath;
25
+ }
26
+ const fileName = basename(sourcePath);
27
+ if (!fileName) {
28
+ return sourcePath;
29
+ }
30
+ const targetPath = resolve(uploadRoot, fileName);
31
+ if (targetPath === sourcePath) {
32
+ return targetPath;
33
+ }
34
+
35
+ await mkdir(uploadRoot, { recursive: true });
36
+
37
+ try {
38
+ await rename(sourcePath, targetPath);
39
+ } catch (error) {
40
+ if ((error as NodeJS.ErrnoException).code !== "EXDEV") {
41
+ throw error;
42
+ }
43
+ await copyFile(sourcePath, targetPath);
44
+ await unlink(sourcePath);
45
+ }
46
+ return targetPath;
47
+ }
48
+
9
49
  export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null): ResolvedDcgchatAccount {
10
50
  const id = accountId ?? DEFAULT_ACCOUNT_ID;
11
51
  const raw = (cfg.channels?.["dcgchat"] as DcgchatConfig | undefined) ?? {};
@@ -25,11 +65,11 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
25
65
  id: "dcgchat",
26
66
  meta: {
27
67
  id: "dcgchat",
28
- label: "DCG Chat",
29
- selectionLabel: "DCG Chat",
68
+ label: "书灵墨宝",
69
+ selectionLabel: "书灵墨宝",
30
70
  docsPath: "/channels/dcgchat",
31
71
  docsLabel: "dcgchat",
32
- blurb: "连接 OpenClaw 与 DCG Chat 产品",
72
+ blurb: "连接 OpenClaw 与 书灵墨宝 产品",
33
73
  order: 80,
34
74
  },
35
75
  capabilities: {
@@ -44,6 +84,11 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
44
84
  effects: true,
45
85
  // blockStreaming: true,
46
86
  },
87
+ agentPrompt: {
88
+ messageToolHints: () => [
89
+ "- 如果有文件生成,直接将文件生成到根目录的/upload文件夹下,并将文件发送给我",
90
+ ],
91
+ },
47
92
  reload: { configPrefixes: ["channels.dcgchat"] },
48
93
  configSchema: {
49
94
  schema: {
@@ -98,77 +143,95 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
98
143
  // textChunkLimit: 25,
99
144
  textChunkLimit: 4000,
100
145
  sendText: async (ctx) => {
101
- const target = ctx.to || "(implicit)";
102
146
  const ws = getWsConnection()
103
147
  const params = getMsgParams();
104
148
  if (ws?.readyState === WebSocket.OPEN) {
105
149
  const {botToken} = resolveAccount(ctx.cfg, ctx.accountId);
106
150
  const content = {
107
151
  messageType: "openclaw_bot_chat",
108
- _userId: target,
152
+ _userId: params.userId,
109
153
  source: "client",
110
154
  content: {
111
155
  bot_token: botToken,
112
- response: ctx.text,
156
+ domain_id: params.domainId,
157
+ app_id: params.appId,
158
+ bot_id: params.botId,
159
+ agent_id: params.agentId,
160
+ response: ctx.text.replaceAll(
161
+ "/root/.openclaw/workspace/moBooksAgentGenerate",
162
+ "/upload"
163
+ ),
113
164
  session_id: params.sessionId,
114
165
  message_id: params.messageId || Date.now().toString(),
115
166
  },
116
167
  };
117
168
  ws.send(JSON.stringify(content));
118
- logDcgchat.info(`dcgchat[${ctx.accountId}]: sendText to ${target}, ${JSON.stringify(content)}`);
169
+ logDcgchat.info(`dcgchat[${ctx.accountId}]: sendText to ${params.userId}, ${JSON.stringify(content)}`);
119
170
  } else {
120
171
  logDcgchat.warn(`[dcgchat][${ctx.accountId ?? DEFAULT_ACCOUNT_ID}] outbound -> ${ws?.readyState}: ${ctx.text}`);
121
172
  }
122
173
  return {
123
174
  channel: "dcgchat",
124
175
  messageId: `dcg-${Date.now()}`,
125
- chatId: target,
176
+ chatId: params.userId.toString(),
126
177
  };
127
178
  },
128
179
  sendMedia: async (ctx) => {
129
- const target = ctx.to || "(implicit)";
130
180
  const ws = getWsConnection()
131
181
  const params = getMsgParams();
182
+
132
183
  if (ws?.readyState === WebSocket.OPEN) {
133
184
  const {botToken} = resolveAccount(ctx.cfg, ctx.accountId);
134
- try {
135
- const url = ctx.mediaUrl ? await ossUpload(ctx.mediaUrl, botToken) : '';
136
- const fileName = ctx.mediaUrl?.split(/[\\/]/).pop() || ''
185
+
186
+ // try {
187
+ const url = await ensureMediaInUploadDir(ctx.mediaUrl ?? '');
188
+ const fileName = url?.split(/[\\/]/).pop() || ''
137
189
  const content = {
138
190
  messageType: "openclaw_bot_chat",
139
- _userId: target,
191
+ _userId: params.userId,
140
192
  source: "client",
141
193
  content: {
142
194
  bot_token: botToken,
143
- response: ctx.text + '\n' + `[${fileName}](${url})`,
195
+ domain_id: params.domainId,
196
+ app_id: params.appId,
197
+ bot_id: params.botId,
198
+ agent_id: params.agentId,
199
+ response: ctx.text.replaceAll(
200
+ "/root/.openclaw/workspace/moBooksAgentGenerate",
201
+ "/upload"
202
+ ),
203
+ files: [{
204
+ url: url,
205
+ name: fileName,
206
+ }],
144
207
  session_id: params.sessionId,
145
- message_id: params.messageId ||Date.now().toString(),
146
- },
147
- };
148
- ws.send(JSON.stringify(content));
149
- logDcgchat.info(`dcgchat[${ctx.accountId}]: sendMedia to ${target}, ${JSON.stringify(content)}`);
150
- } catch (error) {
151
- const content = {
152
- messageType: "openclaw_bot_chat",
153
- _userId: target,
154
- source: "client",
155
- content: {
156
- bot_token: botToken,
157
- response: ctx.text + '\n' + ctx.mediaUrl,
158
- session_id: params.sessionId || Date.now().toString(),
159
- message_id: params.messageId ||Date.now().toString(),
208
+ message_id: params.messageId || Date.now().toString(),
160
209
  },
161
210
  };
162
211
  ws.send(JSON.stringify(content));
163
- logDcgchat.info(`dcgchat[${ctx.accountId}]: sendMedia to ${target}, ${JSON.stringify(content)}`);
164
- }
212
+ logDcgchat.info(`dcgchat[${ctx.accountId}]: agent sendMedia to ${params.userId}, ${JSON.stringify(content)}`);
213
+ // } catch (error) {
214
+ // const content = {
215
+ // messageType: "openclaw_bot_chat",
216
+ // _userId: target,
217
+ // source: "client",
218
+ // content: {
219
+ // bot_token: botToken,
220
+ // response: ctx.text + '\n' + ctx.mediaUrl,
221
+ // session_id: params.sessionId || Date.now().toString(),
222
+ // message_id: params.messageId ||Date.now().toString(),
223
+ // },
224
+ // };
225
+ // ws.send(JSON.stringify(content));
226
+ // logDcgchat.info(`dcgchat[${ctx.accountId}]: sendMedia to ${target}, ${JSON.stringify(content)}`);
227
+ // }
165
228
  } else {
166
229
  logDcgchat.warn(`[dcgchat][${ctx.accountId ?? DEFAULT_ACCOUNT_ID}] outbound -> ${ws?.readyState}: ${ctx.text}`);
167
230
  }
168
231
  return {
169
232
  channel: "dcgchat",
170
233
  messageId: `dcg-${Date.now()}`,
171
- chatId: target,
234
+ chatId: params.userId.toString(),
172
235
  };
173
236
  },
174
237
  },
Binary file
package/src/monitor.ts CHANGED
@@ -117,6 +117,10 @@ export async function monitorDcgchatProvider(opts: MonitorDcgchatOpts): Promise<
117
117
  token: msg.content.bot_token,
118
118
  sessionId: msg.content.session_id,
119
119
  messageId: msg.content.message_id,
120
+ domainId: msg.content.domain_id,
121
+ appId: msg.content.app_id,
122
+ botId: msg.content.bot_id,
123
+ agentId: msg.content.agent_id,
120
124
  });
121
125
  await handleDcgchatMessage({
122
126
  cfg,
package/src/runtime.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import type { PluginRuntime } from "openclaw/plugin-sdk";
2
+ import { logDcgchat } from "./log.js";
2
3
 
3
4
  const path = require('path');
4
5
  const fs = require('fs');
6
+ const os = require("os")
7
+
5
8
  function getWorkspacePath() {
6
- const current = process.cwd();
7
- const workspacePath = path.join(current, '.openclaw/workspace');
9
+ const workspacePath = path.join(os.homedir(), '.openclaw', 'workspace');
8
10
  if (fs.existsSync(workspacePath)) {
9
11
  return workspacePath;
10
12
  }
@@ -21,7 +23,7 @@ export function setWorkspaceDir(dir?: string) {
21
23
  }
22
24
  export function getWorkspaceDir(): string {
23
25
  if (!workspaceDir) {
24
- throw new Error("Workspace directory not initialized");
26
+ logDcgchat.error("Workspace directory not initialized");
25
27
  }
26
28
  return workspaceDir;
27
29
  }
@@ -32,7 +34,7 @@ export function setDcgchatRuntime(next: PluginRuntime) {
32
34
 
33
35
  export function getDcgchatRuntime(): PluginRuntime {
34
36
  if (!runtime) {
35
- throw new Error("DCG Chat runtime not initialized");
37
+ throw new Error("书灵墨宝 runtime not initialized");
36
38
  }
37
39
  return runtime;
38
40
  }
package/src/skill.ts CHANGED
@@ -2,6 +2,7 @@
2
2
  import axios from 'axios';
3
3
  /** @ts-ignore */
4
4
  import unzipper from 'unzipper';
5
+ import { pipeline } from "stream/promises";
5
6
  import fs from 'fs';
6
7
  import path from 'path';
7
8
  import { logDcgchat } from './log.js';
@@ -53,36 +54,74 @@ export async function installSkill(params: ISkillParams, msgContent: Record<stri
53
54
  fs.mkdirSync(skillDir, { recursive: true });
54
55
  // 解压文件到目标目录,跳过顶层文件夹
55
56
  await new Promise((resolve, reject) => {
56
- response.data
57
- .pipe(unzipper.Parse())
58
- .on('entry', (entry: any) => {
59
- const entryPath = entry.path;
60
- // 跳过顶层目录,只处理子文件和文件夹
61
- const pathParts = entryPath.split('/');
62
- if (pathParts.length > 1) {
63
- // 移除第一级目录
64
- const newPath = pathParts.slice(1).join('/');
65
- const targetPath = path.join(skillDir, newPath);
66
-
67
- if (entry.type === 'Directory') {
68
- fs.mkdirSync(targetPath, { recursive: true });
69
- entry.autodrain();
70
- } else {
71
- // 确保父目录存在
72
- const parentDir = path.dirname(targetPath);
73
- if (!fs.existsSync(parentDir)) {
74
- fs.mkdirSync(parentDir, { recursive: true });
75
- }
76
- entry.pipe(fs.createWriteStream(targetPath));
77
- }
78
- } else {
79
- entry.autodrain();
80
- }
81
- })
82
- .on('close', resolve)
83
- .on('error', reject);
57
+ const tasks: Promise<void>[] = [];
58
+ let rootDir: string | null = null;
59
+ let hasError = false;
60
+
61
+ response.data
62
+ .pipe(unzipper.Parse())
63
+ .on("entry", (entry: any) => {
64
+ if (hasError) {
65
+ entry.autodrain();
66
+ return;
67
+ }
68
+
69
+ try {
70
+ const entryPath = entry.path;
71
+ const pathParts = entryPath.split("/");
72
+
73
+ // 检测根目录
74
+ if (!rootDir && pathParts.length > 1) {
75
+ rootDir = pathParts[0];
76
+ }
77
+
78
+ let newPath = entryPath;
79
+
80
+ // 移除顶层文件夹
81
+ if (rootDir && entryPath.startsWith(rootDir + "/")) {
82
+ newPath = entryPath.slice(rootDir.length + 1);
83
+ }
84
+
85
+ if (!newPath) {
86
+ entry.autodrain();
87
+ return;
88
+ }
89
+
90
+ const targetPath = path.join(skillDir, newPath);
91
+
92
+ if (entry.type === "Directory") {
93
+ fs.mkdirSync(targetPath, { recursive: true });
94
+ entry.autodrain();
95
+ } else {
96
+ const parentDir = path.dirname(targetPath);
97
+ fs.mkdirSync(parentDir, { recursive: true });
98
+ const writeStream = fs.createWriteStream(targetPath);
99
+ const task = pipeline(entry, writeStream).catch((err) => {
100
+ hasError = true;
101
+ throw new Error(`解压文件失败 ${entryPath}: ${err.message}`);
102
+ });
103
+ tasks.push(task);
104
+ }
105
+ } catch (err) {
106
+ hasError = true;
107
+ entry.autodrain();
108
+ reject(new Error(`处理entry失败: ${err}`));
109
+ }
110
+ })
111
+ .on("close", async () => {
112
+ try {
113
+ await Promise.all(tasks);
114
+ resolve(null);
115
+ } catch (err) {
116
+ reject(err);
117
+ }
118
+ })
119
+ .on("error", (err: { message: any; }) => {
120
+ hasError = true;
121
+ reject(new Error(`解压流错误: ${err.message}`));
84
122
  });
85
- sendEvent({ ...msgContent, status: 'ok' })
123
+ });
124
+ sendEvent({ ...msgContent, status: 'ok' })
86
125
  } catch (error) {
87
126
  // 如果安装失败,清理目录
88
127
  if (fs.existsSync(skillDir)) {
package/src/tool.ts CHANGED
@@ -5,9 +5,13 @@ import { logDcgchat } from "./log.js";
5
5
 
6
6
  let msgParams = {} as {
7
7
  userId: number;
8
- token: string
9
- sessionId: string
10
- messageId: string
8
+ token: string;
9
+ sessionId: string;
10
+ messageId: string;
11
+ domainId: string;
12
+ appId: string;
13
+ botId: string;
14
+ agentId: string;
11
15
  }
12
16
  let msgStatus: 'running' | 'finished' | '' = '';
13
17
  export function setMsgParams(params: any) {
@@ -44,16 +48,24 @@ export function monitoringToolMessage(api: OpenClawPluginApi) {
44
48
  message_id: params?.messageId || Date.now().toString()
45
49
  },
46
50
  }));
51
+ const text = JSON.stringify({
52
+ type: 'tool_call',
53
+ ...event
54
+ }).replaceAll(
55
+ "/root/.openclaw/workspace/moBooksAgentGenerate",
56
+ "/upload"
57
+ );
47
58
  ws.send(JSON.stringify({
48
59
  messageType: "openclaw_bot_chat",
49
60
  _userId: params?.userId,
50
61
  source: "client",
51
62
  content: {
52
63
  bot_token: params?.token,
53
- response: JSON.stringify({
54
- type: 'tool_call',
55
- ...event
56
- }),
64
+ domain_id: params?.domainId,
65
+ app_id: params?.appId,
66
+ bot_id: params?.botId,
67
+ agent_id: params?.agentId,
68
+ response: text,
57
69
  session_id:params?.sessionId,
58
70
  message_id: params?.messageId || Date.now().toString()
59
71
  },
package/src/types.ts CHANGED
@@ -39,10 +39,17 @@ export type InboundMessage = {
39
39
  // content: string;
40
40
  content: {
41
41
  bot_token: string;
42
+ domain_id?: string;
43
+ app_id?: string;
44
+ bot_id?: string;
45
+ agent_id?: string;
42
46
  session_id: string;
43
47
  message_id: string;
44
48
  text: string;
45
- file_urls?: string[];
49
+ files?: {
50
+ url: string;
51
+ name: string;
52
+ }[];
46
53
  };
47
54
  };
48
55
 
@@ -68,6 +75,10 @@ export type OutboundReply = {
68
75
  message_id: string; // ""
69
76
  response: string; // ""
70
77
  state: string; // final, chunk
78
+ domain_id?: string;
79
+ app_id?: string;
80
+ bot_id?: string;
81
+ agent_id?: string;
71
82
  };
72
83
  };
73
84