@dcrays/dcgchat 0.2.0 → 0.2.8

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.ts CHANGED
@@ -2,7 +2,7 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
3
3
  import { dcgchatPlugin } from "./src/channel.js";
4
4
  import { setDcgchatRuntime, setWorkspaceDir } from "./src/runtime.js";
5
- import { monitoringToolMessage } from "./src/tool.js";
5
+ // import { monitoringToolMessage } from "./src/tool.js";
6
6
 
7
7
  const plugin = {
8
8
  id: "dcgchat",
@@ -11,7 +11,7 @@ const plugin = {
11
11
  configSchema: emptyPluginConfigSchema(),
12
12
  register(api: OpenClawPluginApi) {
13
13
  setDcgchatRuntime(api.runtime);
14
- monitoringToolMessage(api);
14
+ // monitoringToolMessage(api);
15
15
  api.registerChannel({ plugin: dcgchatPlugin });
16
16
  api.registerTool((ctx) => {
17
17
  const workspaceDir = ctx.workspaceDir;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat",
3
- "version": "0.2.0",
3
+ "version": "0.2.8",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
@@ -20,9 +20,10 @@
20
20
  "typecheck": "tsc --noEmit"
21
21
  },
22
22
  "dependencies": {
23
+ "ali-oss": "file:src/libs/ali-oss-6.23.0.tgz",
23
24
  "axios": "file:src/libs/axios-1.13.6.tgz",
24
25
  "ws": "file:src/libs/ws-8.19.0.tgz",
25
- "mime-types": "file:src/libs/mime-types-3.0.2.tgz",
26
+ "md5": "file:src/libs/md5-2.3.0.tgz",
26
27
  "unzipper": "file:src/libs/unzipper-0.12.3.tgz"
27
28
  },
28
29
  "openclaw": {
package/src/api.ts CHANGED
@@ -10,7 +10,7 @@ export const getStsToken = async (name: string, botToken: string) => {
10
10
  "/user/getStsToken",
11
11
  {
12
12
  sourceFileName: name,
13
- isPrivate: 0,
13
+ isPrivate: 1,
14
14
  },
15
15
  { botToken },
16
16
  );
@@ -21,6 +21,29 @@ export const getStsToken = async (name: string, botToken: string) => {
21
21
 
22
22
  return response.data;
23
23
  };
24
+ export const generateSignUrl = async (file_url: string, botToken: string) => {
25
+ try {
26
+ // 确保 userToken 已缓存(如果未缓存会自动获取并缓存)
27
+ await getUserToken(botToken);
28
+
29
+ const response = await post<any>(
30
+ "/user/generateSignUrl",
31
+ {
32
+ loudPlatform: 0,
33
+ fileName: file_url
34
+ },
35
+ { botToken },
36
+ );
37
+ if (response.code === 0 && response.data) {
38
+ // @ts-ignore
39
+ return response.data?.filePath
40
+ }
41
+ return ''
42
+
43
+ } catch (error) {
44
+ return ''
45
+ }
46
+ };
24
47
 
25
48
  /**
26
49
  * 通过 botToken 查询 userToken
package/src/bot.ts CHANGED
@@ -1,14 +1,13 @@
1
1
  import path from "node:path";
2
2
  import os from "node:os";
3
- import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
3
+ import type { ClawdbotConfig, ReplyPayload, RuntimeEnv } from "openclaw/plugin-sdk";
4
4
  import { createReplyPrefixContext } from "openclaw/plugin-sdk";
5
5
  import type { InboundMessage, OutboundReply } from "./types.js";
6
6
  import { getDcgchatRuntime } from "./runtime.js";
7
7
  import { resolveAccount } from "./channel.js";
8
8
  import { setMsgStatus } from "./tool.js";
9
- import mime from "mime-types"
10
-
11
- const targetPath = path.join(os.homedir(), '../');
9
+ import { generateSignUrl } from "./api.js";
10
+ import { ossUpload } from "./oss.js";
12
11
 
13
12
  type MediaInfo = {
14
13
  path: string;
@@ -16,27 +15,54 @@ type MediaInfo = {
16
15
  placeholder: string;
17
16
  };
18
17
 
19
- async function resolveMediaFromUrls(
20
- files: { url: string, name: string }[],
21
- log?: (msg: string) => void,
22
- ): Promise<MediaInfo[]> {
18
+ const mediaMaxBytes = 300 * 1024 * 1024;
19
+ async function resolveMediaFromUrls(files: { name: string, url: string }[], botToken: string, log: (message: string) => void): Promise<MediaInfo[]> {
20
+ const core = getDcgchatRuntime();
23
21
  const out: MediaInfo[] = [];
22
+ log(`dcgchat media: starting resolve for ${files.length} file(s): ${JSON.stringify(files)}`);
23
+
24
24
  for (let i = 0; i < files.length; i++) {
25
- const url = path.join(targetPath, files[i]?.url);
25
+ const file = files[i];
26
26
  try {
27
- const response = await fetch(url);
28
- const contentType = response.headers.get("content-type") || "";
27
+ let data = ''
28
+ if (/^https?:\/\//i.test(file.url)) {
29
+ data = file.url
30
+ } else {
31
+ data = await generateSignUrl(file.url, botToken);
32
+ }
33
+ log(`dcgchat media: [${i + 1}/${files.length}] generateSignUrl: ${data}`);
34
+ const response = await fetch(data);
35
+ if (!response.ok) {
36
+ log?.(`dcgchat media: [${i + 1}/${files.length}] fetch failed with HTTP ${response.status}, skipping`);
37
+ continue;
38
+ }
39
+ const buffer = Buffer.from(await response.arrayBuffer());
40
+
41
+ let contentType = response.headers.get("content-type") || "";
42
+ if (!contentType) {
43
+ contentType = await core.media.detectMime({ buffer }) || "";
44
+ }
45
+ const fileName = file.name || path.basename(new URL(file.url).pathname) || "file";
46
+ const saved = await core.channel.media.saveMediaBuffer(
47
+ buffer,
48
+ contentType,
49
+ "inbound",
50
+ mediaMaxBytes,
51
+ fileName,
52
+ );
29
53
  const isImage = contentType.startsWith("image/");
30
54
  out.push({
31
- path: url,
32
- // @ts-ignore
33
- contentType: saved.contentType,
55
+ path: saved.path,
56
+ contentType: saved.contentType || "",
34
57
  placeholder: isImage ? "<media:image>" : "<media:file>",
35
58
  });
59
+
36
60
  } catch (err) {
37
- log?.(`dcgchat media: [${i + 1}/${files.length}] FAILED to process ${url}: ${String(err)}`);
61
+ log(`dcgchat media: [${i + 1}/${files.length}] FAILED to process ${file.url}: ${String(err)}`);
38
62
  }
39
63
  }
64
+ log(`dcgchat media: resolve complete, ${out.length}/${files.length} file(s) succeeded`);
65
+
40
66
  return out;
41
67
  }
42
68
 
@@ -78,10 +104,9 @@ export async function handleDcgchatMessage(params: {
78
104
 
79
105
  const account = resolveAccount(cfg, accountId);
80
106
  const userId = msg._userId.toString();
81
- // const text = msg.text?.trim();
82
107
  const text = msg.content.text?.trim();
83
108
 
84
- if (!userId || !text) {
109
+ if (!text) {
85
110
  params.onChunk({
86
111
  messageType: "openclaw_bot_chat",
87
112
  _userId: msg._userId,
@@ -95,7 +120,7 @@ export async function handleDcgchatMessage(params: {
95
120
  agent_id: msg.content.agent_id,
96
121
  session_id: msg.content.session_id,
97
122
  message_id: msg.content.message_id,
98
- response: "[错误] 消息格式不正确",
123
+ response: "你需要我帮你做什么呢?",
99
124
  },
100
125
  });
101
126
  return;
@@ -106,7 +131,7 @@ export async function handleDcgchatMessage(params: {
106
131
 
107
132
  const route = core.channel.routing.resolveAgentRoute({
108
133
  cfg,
109
- channel: "dcgchat",
134
+ channel: "dcgchat-test",
110
135
  accountId: account.accountId,
111
136
  peer: { kind: "direct", id: userId },
112
137
  });
@@ -115,15 +140,7 @@ export async function handleDcgchatMessage(params: {
115
140
  const files = msg.content.files ?? [];
116
141
  let mediaPayload: Record<string, unknown> = {};
117
142
  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
- });
143
+ const mediaList = await resolveMediaFromUrls(files, msg.content.bot_token, log)
127
144
  mediaPayload = buildMediaPayload(mediaList);
128
145
  log(`dcgchat[${accountId}]: media resolved ${mediaList.length}/${files.length} file(s), payload=${JSON.stringify(mediaList)}`);
129
146
  }
@@ -151,19 +168,21 @@ export async function handleDcgchatMessage(params: {
151
168
  ChatType: "direct",
152
169
  SenderName: userId,
153
170
  SenderId: userId,
154
- Provider: "dcgchat" as const,
155
- Surface: "dcgchat" as const,
171
+ Provider: "dcgchat-test" as const,
172
+ Surface: "dcgchat-test" as const,
156
173
  MessageSid: msg.content.message_id,
157
174
  Timestamp: Date.now(),
158
175
  WasMentioned: true,
159
176
  CommandAuthorized: true,
160
- OriginatingChannel: "dcgchat" as const,
177
+ OriginatingChannel: "dcgchat-test" as const,
161
178
  OriginatingTo: `user:${userId}`,
162
179
  ...mediaPayload,
163
180
  });
164
181
 
165
182
  log(`dcgchat[${accountId}]: ctxPayload=${JSON.stringify(ctxPayload)}`);
166
183
 
184
+ let textChunk = ''
185
+
167
186
  const prefixContext = createReplyPrefixContext({ cfg, agentId: route.agentId });
168
187
 
169
188
  const { dispatcher, replyOptions, markDispatchIdle } =
@@ -172,53 +191,102 @@ export async function handleDcgchatMessage(params: {
172
191
  responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
173
192
  humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, route.agentId),
174
193
  onReplyStart: async () => {},
175
- deliver: async (payload) => {
194
+ deliver: async (payload: { text: string | any[]; }) => {
176
195
  log(`dcgchat[${accountId}][deliver]: received chunk, text length=${payload.text?.length || 0}`);
177
- const t = payload.text?.trim().replaceAll(
178
- "/root/.openclaw/workspace/moBooksAgentGenerate",
179
- "/upload"
180
- );
181
- if (t) {
182
- log(`dcgchat[${accountId}][deliver]: sending chunk to user ${msg._userId}, text="${t.slice(0, 50)}..."`);
183
- params.onChunk({
184
- messageType: "openclaw_bot_chat",
185
- _userId: msg._userId,
186
- source: "client",
187
- content: {
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,
193
- session_id: msg.content.session_id,
194
- message_id: msg.content.message_id,
195
- response: t,
196
- state: 'chunk',
197
- },
198
- });
199
- log(`dcgchat[${accountId}][deliver]: chunk sent successfully`);
200
- } else {
201
- log(`dcgchat[${accountId}][deliver]: skipping empty chunk`);
202
- }
203
196
  },
204
- onError: (err, info) => {
197
+ onError: (err: any, info: { kind: any; }) => {
205
198
  error(`dcgchat[${accountId}] ${info.kind} reply failed: ${String(err)}`);
206
199
  },
207
200
  onIdle: () => {},
208
201
  });
209
202
 
210
- log(`dcgchat[${accountId}]: dispatching to agent (session=${route.sessionKey})`);
203
+ if (text === '/new') {
204
+ log(`dcgchat[${accountId}]: skipping agent dispatch for /new`);
205
+ await core.channel.reply.dispatchReplyFromConfig({
206
+ ctx: ctxPayload,
207
+ cfg,
208
+ dispatcher,
209
+ replyOptions: {
210
+ ...replyOptions,
211
+ onModelSelected: prefixContext.onModelSelected
212
+ },
213
+ });
214
+ } else {
215
+ log(`dcgchat[${accountId}]: dispatching to agent (session=${route.sessionKey})`);
216
+ await core.channel.reply.dispatchReplyFromConfig({
217
+ ctx: ctxPayload,
218
+ cfg,
219
+ dispatcher,
220
+ replyOptions: {
221
+ ...replyOptions,
222
+ onModelSelected: prefixContext.onModelSelected,
223
+ onPartialReply: async (payload: ReplyPayload) => {
224
+ log(`dcgchat[${accountId}][deliver]: received chunk, text length=${payload.text?.length || 0}`);
225
+ const mediaList =
226
+ payload.mediaUrls && payload.mediaUrls.length > 0
227
+ ? payload.mediaUrls
228
+ : payload.mediaUrl
229
+ ? [payload.mediaUrl]
230
+ : [];
231
+ if (mediaList.length > 0) {
232
+ const files = []
233
+ for (let i = 0; i < mediaList.length; i++) {
234
+ const file = mediaList[i]
235
+ const fileName = file.split(/[\\/]/).pop() || ''
236
+ const url = await ossUpload(file, msg.content.bot_token)
237
+ files.push({
238
+ url: url,
239
+ name: fileName,
240
+ })
241
+ }
242
+ params.onChunk({
243
+ messageType: "openclaw_bot_chat",
244
+ _userId: msg._userId,
245
+ source: "client",
246
+ content: {
247
+ bot_token: msg.content.bot_token,
248
+ domain_id: msg.content.domain_id,
249
+ app_id: msg.content.app_id,
250
+ bot_id: msg.content.bot_id,
251
+ agent_id: msg.content.agent_id,
252
+ session_id: msg.content.session_id,
253
+ message_id: msg.content.message_id,
254
+ response: '',
255
+ files: files,
256
+ state: 'chunk',
257
+ },
258
+ });
259
+ }
260
+ if (payload.text) {
261
+ log(`dcgchat[${accountId}][deliver]: sending chunk to user ${msg._userId}, text="${payload.text.slice(0, 50)}..."`);
262
+ params.onChunk({
263
+ messageType: "openclaw_bot_chat",
264
+ _userId: msg._userId,
265
+ source: "client",
266
+ content: {
267
+ bot_token: msg.content.bot_token,
268
+ domain_id: msg.content.domain_id,
269
+ app_id: msg.content.app_id,
270
+ bot_id: msg.content.bot_id,
271
+ agent_id: msg.content.agent_id,
272
+ session_id: msg.content.session_id,
273
+ message_id: msg.content.message_id,
274
+ response: payload.text.replace(textChunk, ''),
275
+ state: 'chunk',
276
+ },
277
+ });
278
+ textChunk = payload.text
279
+ log(`dcgchat[${accountId}][deliver]: chunk sent successfully`);
280
+ } else if (payload.mediaUrl && payload.mediaUrls) {
211
281
 
212
- await core.channel.reply.dispatchReplyFromConfig({
213
- ctx: ctxPayload,
214
- cfg,
215
- dispatcher,
216
- replyOptions: {
217
- ...replyOptions,
218
- onModelSelected: prefixContext.onModelSelected,
219
- },
220
- });
221
282
 
283
+ } else {
284
+ log(`dcgchat[${accountId}][deliver]: skipping empty chunk`);
285
+ }
286
+ },
287
+ },
288
+ });
289
+ }
222
290
  log(`dcgchat[${accountId}]: dispatch complete, sending final state`);
223
291
  params.onChunk({
224
292
  messageType: "openclaw_bot_chat",
@@ -237,6 +305,7 @@ export async function handleDcgchatMessage(params: {
237
305
  },
238
306
  });
239
307
  setMsgStatus('finished');
308
+ textChunk = ''
240
309
  log(`dcgchat[${accountId}]: final state sent`);
241
310
 
242
311
  markDispatchIdle();
package/src/channel.ts CHANGED
@@ -1,54 +1,14 @@
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";
4
1
  import type { ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk";
5
2
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
6
3
  import type { ResolvedDcgchatAccount, DcgchatConfig } from "./types.js";
7
- import { logDcgchat } from "./log.js";
8
4
  import { getWsConnection } from "./connection.js";
5
+ import { ossUpload } from "./oss.js";
9
6
  import { getMsgParams } from "./tool.js";
10
7
 
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
8
 
49
9
  export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null): ResolvedDcgchatAccount {
50
10
  const id = accountId ?? DEFAULT_ACCOUNT_ID;
51
- const raw = (cfg.channels?.["dcgchat"] as DcgchatConfig | undefined) ?? {};
11
+ const raw = (cfg.channels?.["dcgchat-test"] as DcgchatConfig | undefined) ?? {};
52
12
  return {
53
13
  accountId: id,
54
14
  enabled: raw.enabled !== false,
@@ -62,13 +22,13 @@ export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null):
62
22
  }
63
23
 
64
24
  export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
65
- id: "dcgchat",
25
+ id: "dcgchat-test",
66
26
  meta: {
67
- id: "dcgchat",
27
+ id: "dcgchat-test",
68
28
  label: "书灵墨宝",
69
29
  selectionLabel: "书灵墨宝",
70
30
  docsPath: "/channels/dcgchat",
71
- docsLabel: "dcgchat",
31
+ docsLabel: "dcgchat-test",
72
32
  blurb: "连接 OpenClaw 与 书灵墨宝 产品",
73
33
  order: 80,
74
34
  },
@@ -84,11 +44,6 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
84
44
  effects: true,
85
45
  // blockStreaming: true,
86
46
  },
87
- agentPrompt: {
88
- messageToolHints: () => [
89
- "- 如果有文件生成,直接将文件生成到根目录的/upload文件夹下,并将文件发送给我",
90
- ],
91
- },
92
47
  reload: { configPrefixes: ["channels.dcgchat"] },
93
48
  configSchema: {
94
49
  schema: {
@@ -113,8 +68,8 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
113
68
  ...cfg,
114
69
  channels: {
115
70
  ...cfg.channels,
116
- "dcgchat": {
117
- ...(cfg.channels?.["dcgchat"] as Record<string, unknown> | undefined),
71
+ "dcgchat-test": {
72
+ ...(cfg.channels?.["dcgchat-test"] as Record<string, unknown> | undefined),
118
73
  enabled,
119
74
  },
120
75
  },
@@ -145,6 +100,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
145
100
  sendText: async (ctx) => {
146
101
  const ws = getWsConnection()
147
102
  const params = getMsgParams();
103
+ const log = ctx.runtime?.log ?? console.log;
148
104
  if (ws?.readyState === WebSocket.OPEN) {
149
105
  const {botToken} = resolveAccount(ctx.cfg, ctx.accountId);
150
106
  const content = {
@@ -157,21 +113,34 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
157
113
  app_id: params.appId,
158
114
  bot_id: params.botId,
159
115
  agent_id: params.agentId,
160
- response: ctx.text.replaceAll(
161
- "/root/.openclaw/workspace/moBooksAgentGenerate",
162
- "/upload"
163
- ),
116
+ response: ctx.text,
164
117
  session_id: params.sessionId,
165
118
  message_id: params.messageId || Date.now().toString(),
166
119
  },
167
120
  };
168
121
  ws.send(JSON.stringify(content));
169
- logDcgchat.info(`dcgchat[${ctx.accountId}]: sendText to ${params.userId}, ${JSON.stringify(content)}`);
122
+ ws.send(JSON.stringify({
123
+ messageType: "openclaw_bot_chat",
124
+ _userId: params.userId,
125
+ source: "client",
126
+ content: {
127
+ bot_token: botToken,
128
+ domain_id: params.domainId,
129
+ app_id: params.appId,
130
+ bot_id: params.botId,
131
+ agent_id: params.agentId,
132
+ ssession_id: params.sessionId,
133
+ message_id: params.messageId || Date.now().toString(),
134
+ response: '',
135
+ state: 'final',
136
+ },
137
+ }));
138
+ log(`dcgchat[${ctx.accountId}]: channel sendText to ${params.userId}, ${JSON.stringify(content)}`);
170
139
  } else {
171
- logDcgchat.warn(`[dcgchat][${ctx.accountId ?? DEFAULT_ACCOUNT_ID}] outbound -> ${ws?.readyState}: ${ctx.text}`);
140
+ log(`[dcgchat][${ctx.accountId ?? DEFAULT_ACCOUNT_ID}] outbound -> ${ws?.readyState}: ${ctx.text}`);
172
141
  }
173
142
  return {
174
- channel: "dcgchat",
143
+ channel: "dcgchat-test",
175
144
  messageId: `dcg-${Date.now()}`,
176
145
  chatId: params.userId.toString(),
177
146
  };
@@ -179,13 +148,12 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
179
148
  sendMedia: async (ctx) => {
180
149
  const ws = getWsConnection()
181
150
  const params = getMsgParams();
182
-
183
- if (ws?.readyState === WebSocket.OPEN) {
151
+ const log = ctx.runtime?.log ?? console.log;
152
+ if (ws?.readyState === WebSocket.OPEN) {
153
+ const fileName = ctx.mediaUrl?.split(/[\\/]/).pop() || ''
184
154
  const {botToken} = resolveAccount(ctx.cfg, ctx.accountId);
185
-
186
- // try {
187
- const url = await ensureMediaInUploadDir(ctx.mediaUrl ?? '');
188
- const fileName = url?.split(/[\\/]/).pop() || ''
155
+ try {
156
+ const url = ctx.mediaUrl ? await ossUpload(ctx.mediaUrl, botToken) : '';
189
157
  const content = {
190
158
  messageType: "openclaw_bot_chat",
191
159
  _userId: params.userId,
@@ -196,40 +164,61 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
196
164
  app_id: params.appId,
197
165
  bot_id: params.botId,
198
166
  agent_id: params.agentId,
199
- response: ctx.text.replaceAll(
200
- "/root/.openclaw/workspace/moBooksAgentGenerate",
201
- "/upload"
202
- ),
167
+ response: ctx.text,
203
168
  files: [{
204
169
  url: url,
205
170
  name: fileName,
206
171
  }],
207
172
  session_id: params.sessionId,
208
- message_id: params.messageId || Date.now().toString(),
173
+ message_id: params.messageId ||Date.now().toString(),
209
174
  },
210
175
  };
211
176
  ws.send(JSON.stringify(content));
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
- // }
177
+ log(`dcgchat[${ctx.accountId}]: sendMedia alioss to ${params.userId}, ${JSON.stringify(content)}`);
178
+ } catch (error) {
179
+ const content = {
180
+ messageType: "openclaw_bot_chat",
181
+ _userId: params.userId,
182
+ source: "client",
183
+ content: {
184
+ bot_token: botToken,
185
+ domain_id: params.domainId,
186
+ app_id: params.appId,
187
+ bot_id: params.botId,
188
+ agent_id: params.agentId,
189
+ response: ctx.text,
190
+ files: [{
191
+ url: ctx.mediaUrl,
192
+ name: fileName,
193
+ }],
194
+ session_id: params.sessionId || Date.now().toString(),
195
+ message_id: Date.now().toString(),
196
+ },
197
+ };
198
+ ws.send(JSON.stringify(content));
199
+ ws.send(JSON.stringify({
200
+ messageType: "openclaw_bot_chat",
201
+ _userId: params.userId,
202
+ source: "client",
203
+ content: {
204
+ bot_token: botToken,
205
+ domain_id: params.domainId,
206
+ app_id: params.appId,
207
+ bot_id: params.botId,
208
+ agent_id: params.agentId,
209
+ ssession_id: params.sessionId,
210
+ message_id: Date.now().toString(),
211
+ response: '',
212
+ state: 'final',
213
+ },
214
+ }));
215
+ log(`dcgchat[${ctx.accountId}]: error sendMedia to ${params.userId}, ${JSON.stringify(content)}`);
216
+ }
228
217
  } else {
229
- logDcgchat.warn(`[dcgchat][${ctx.accountId ?? DEFAULT_ACCOUNT_ID}] outbound -> ${ws?.readyState}: ${ctx.text}`);
218
+ log(`[dcgchat][${ctx.accountId ?? DEFAULT_ACCOUNT_ID}] outbound -> ${ws?.readyState}: ${ctx.text}`);
230
219
  }
231
220
  return {
232
- channel: "dcgchat",
221
+ channel: "dcgchat-test",
233
222
  messageId: `dcg-${Date.now()}`,
234
223
  chatId: params.userId.toString(),
235
224
  };
package/src/monitor.ts CHANGED
@@ -112,13 +112,14 @@ export async function monitorDcgchatProvider(opts: MonitorDcgchatOpts): Promise<
112
112
  if (parsed.messageType == "openclaw_bot_chat") {
113
113
  const msg = parsed as unknown as InboundMessage;
114
114
  setMsgStatus('running');
115
+ log(`dcgchat[${account.accountId}]: openclaw_bot_chat received, ${JSON.stringify(msg)}`);
115
116
  setMsgParams({
116
117
  userId: msg._userId,
117
118
  token: msg.content.bot_token,
118
119
  sessionId: msg.content.session_id,
119
120
  messageId: msg.content.message_id,
120
- domainId: msg.content.domain_id,
121
- appId: msg.content.app_id,
121
+ domainId: account.domainId || 1000,
122
+ appId: account.appId || '100',
122
123
  botId: msg.content.bot_id,
123
124
  agentId: msg.content.agent_id,
124
125
  });
@@ -138,7 +139,7 @@ export async function monitorDcgchatProvider(opts: MonitorDcgchatOpts): Promise<
138
139
  const { event_type, operation_type, skill_url, skill_code, skill_id, bot_token, websocket_trace_id } = parsed.content ? parsed.content : {} as Record<string, any>;
139
140
  const content = { event_type, operation_type, skill_url, skill_code, skill_id, bot_token, websocket_trace_id };
140
141
  if (event_type === "skill") {
141
- if (operation_type === "install" || operation_type === "enable") {
142
+ if (operation_type === "install" || operation_type === "enable" || operation_type === "update") {
142
143
  installSkill({ path: skill_url, code: skill_code }, content);
143
144
  } else if (operation_type === "remove" || operation_type === "disable") {
144
145
  uninstallSkill({ code: skill_code }, content);
package/src/oss.ts CHANGED
@@ -61,9 +61,9 @@ export const ossUpload = async (file: File | string | Buffer, botToken: string)
61
61
  if (objectResult?.res?.status !== 200) {
62
62
  throw new Error("OSS 上传失败");
63
63
  }
64
- console.log(objectResult.url);
64
+ console.log(11111, JSON.stringify(objectResult));
65
65
  // const url = `${data.protocol || 'http'}://${data.bucket}.${data.endPoint}/${data.uploadDir}${data.ossFileKey}`
66
- return objectResult.url;
66
+ return objectResult.name || objectResult.url;
67
67
  } catch (error) {
68
68
  console.error("OSS 上传失败:", error);
69
69
  throw error;
package/src/request.ts CHANGED
@@ -2,6 +2,7 @@ import axios from "axios";
2
2
  import md5 from "md5";
3
3
  import type { IResponse } from "./types.js";
4
4
  import { getUserTokenCache } from "./userInfo.js";
5
+ import { getMsgParams } from "./tool.js";
5
6
 
6
7
  export const apiUrlMap = {
7
8
  production: "https://api-gateway.shuwenda.com",
@@ -178,17 +179,23 @@ export function post<T = Record<string, unknown>, R = unknown>(
178
179
  botToken?: string;
179
180
  },
180
181
  ): Promise<IResponse<R>> {
182
+ const params = getMsgParams() || {}
181
183
  const config: any = {
182
184
  method: "POST",
183
185
  url,
184
- data,
185
- headers: buildHeaders(data as Record<string, unknown>, url, options?.userToken),
186
+ data: {
187
+ ...data,
188
+ _appId: params.appId
189
+ },
190
+ headers: buildHeaders({
191
+ ...data,
192
+ _appId: params.appId
193
+ } as Record<string, unknown>, url, options?.userToken),
186
194
  };
187
195
 
188
196
  // 将 botToken 附加到配置中,供请求拦截器使用
189
197
  if (options?.botToken) {
190
198
  config.__botToken = options.botToken;
191
199
  }
192
-
193
200
  return axiosInstance.request(config);
194
201
  }
package/src/skill.ts CHANGED
@@ -136,7 +136,7 @@ export function uninstallSkill(params: Omit<ISkillParams, 'path'>, msgContent: R
136
136
 
137
137
  const workspacePath = getWorkspaceDir();
138
138
  if (!workspacePath) {
139
- throw new Error('未找到工作区路径');
139
+ sendEvent({ ...msgContent, status: 'ok' })
140
140
  }
141
141
 
142
142
  const skillDir = path.join(workspacePath, 'skills', code);
@@ -145,6 +145,6 @@ export function uninstallSkill(params: Omit<ISkillParams, 'path'>, msgContent: R
145
145
  fs.rmSync(skillDir, { recursive: true, force: true });
146
146
  sendEvent({ ...msgContent, status: 'ok' })
147
147
  } else {
148
- sendEvent({ ...msgContent, status: 'fail' })
148
+ sendEvent({ ...msgContent, status: 'ok' })
149
149
  }
150
150
  }
package/src/tool.ts CHANGED
@@ -1,7 +1,6 @@
1
1
 
2
2
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
3
3
  import { getWsConnection } from "./connection.js";
4
- import { logDcgchat } from "./log.js";
5
4
 
6
5
  let msgParams = {} as {
7
6
  userId: number;
@@ -29,32 +28,35 @@ export function setMsgStatus(status: 'running' | 'finished' | '') {
29
28
  export function getMsgStatus() {
30
29
  return msgStatus;
31
30
  }
32
-
31
+ let runId = '';
32
+ let toolName = '';
33
33
  export function monitoringToolMessage(api: OpenClawPluginApi) {
34
- api.on("after_tool_call", (event, payload) => {
34
+ api.on("after_tool_call", (event) => {
35
35
  const ws = getWsConnection()
36
36
  const params = getMsgParams();
37
37
  const status = getMsgStatus();
38
38
  //
39
39
  if (ws?.readyState === WebSocket.OPEN && status === 'running') {
40
- ws.send(JSON.stringify({
41
- messageType: "openclaw_bot_chat",
42
- _userId: params?.userId,
43
- source: "client",
44
- content: {
45
- bot_token: params?.token,
46
- response: 'all_finished',
47
- session_id:params?.sessionId,
48
- message_id: params?.messageId || Date.now().toString()
49
- },
50
- }));
40
+ const log = api.runtime?.log ?? api.log;
41
+ // @ts-ignore
42
+ if (!runId || runId !== event.runId || !toolName || toolName !== event.toolName) {
43
+ ws.send(JSON.stringify({
44
+ messageType: "openclaw_bot_chat",
45
+ _userId: params?.userId,
46
+ source: "client",
47
+ content: {
48
+ bot_token: params?.token,
49
+ response: 'all_finished',
50
+ session_id:params?.sessionId,
51
+ message_id: params?.messageId || Date.now().toString()
52
+ },
53
+ }));
54
+ }
51
55
  const text = JSON.stringify({
52
56
  type: 'tool_call',
57
+ specialIdentification: 'dcgchat_tool_call_special_identification',
53
58
  ...event
54
- }).replaceAll(
55
- "/root/.openclaw/workspace/moBooksAgentGenerate",
56
- "/upload"
57
- );
59
+ });
58
60
  ws.send(JSON.stringify({
59
61
  messageType: "openclaw_bot_chat",
60
62
  _userId: params?.userId,
@@ -65,23 +67,29 @@ export function monitoringToolMessage(api: OpenClawPluginApi) {
65
67
  app_id: params?.appId,
66
68
  bot_id: params?.botId,
67
69
  agent_id: params?.agentId,
68
- response: text,
69
- session_id:params?.sessionId,
70
- message_id: params?.messageId || Date.now().toString()
71
- },
72
- }));
73
- ws.send(JSON.stringify({
74
- messageType: "openclaw_bot_chat",
75
- _userId: params?.userId,
76
- source: "client",
77
- content: {
78
- bot_token: params?.token,
79
- response: 'all_finished',
70
+ thinking_content: text,
71
+ response: '',
80
72
  session_id:params?.sessionId,
81
73
  message_id: params?.messageId || Date.now().toString()
82
74
  },
83
75
  }));
84
- logDcgchat.info(`dcgchat: tool message to ${params?.sessionId}, ${JSON.stringify(event)}`);
76
+ // @ts-ignore
77
+ if (!runId || runId !== event.runId || !toolName || toolName !== event.toolName) {
78
+ ws.send(JSON.stringify({
79
+ messageType: "openclaw_bot_chat",
80
+ _userId: params?.userId,
81
+ source: "client",
82
+ content: {
83
+ bot_token: params?.token,
84
+ response: 'all_finished',
85
+ session_id:params?.sessionId,
86
+ message_id: params?.messageId || Date.now().toString()
87
+ },
88
+ }));
89
+ }
90
+ runId = event.runId;
91
+ toolName = event.toolName;
92
+ log?.(`dcgchat[${params?.sessionId}]:11111111 tool message to ${params?.sessionId}, ${JSON.stringify(event)}`);
85
93
  }
86
94
  });
87
95
  }
package/src/types.ts CHANGED
@@ -79,6 +79,7 @@ export type OutboundReply = {
79
79
  app_id?: string;
80
80
  bot_id?: string;
81
81
  agent_id?: string;
82
+ files?: {url: string, name: string}[];
82
83
  };
83
84
  };
84
85