@dcrays/dcgchat 0.1.10 → 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/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
@@ -2,16 +2,16 @@ 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",
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);
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,8 +1,8 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat",
3
- "version": "0.1.10",
3
+ "version": "0.2.8",
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": [
@@ -21,32 +21,22 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "ali-oss": "file:src/libs/ali-oss-6.23.0.tgz",
24
- "ws": "file:src/libs/ws-8.19.0.tgz",
25
24
  "axios": "file:src/libs/axios-1.13.6.tgz",
25
+ "ws": "file:src/libs/ws-8.19.0.tgz",
26
26
  "md5": "file:src/libs/md5-2.3.0.tgz",
27
27
  "unzipper": "file:src/libs/unzipper-0.12.3.tgz"
28
28
  },
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
29
  "openclaw": {
40
30
  "extensions": [
41
31
  "./index.ts"
42
32
  ],
43
33
  "channel": {
44
34
  "id": "dcgchat",
45
- "label": "DCG Chat",
46
- "selectionLabel": "DCG Chat",
35
+ "label": "书灵墨宝",
36
+ "selectionLabel": "书灵墨宝",
47
37
  "docsPath": "/channels/dcgchat",
48
38
  "docsLabel": "dcgchat",
49
- "blurb": "连接 OpenClaw 与 DCG Chat 产品",
39
+ "blurb": "连接 OpenClaw 与 书灵墨宝 产品",
50
40
  "order": 80
51
41
  },
52
42
  "install": {
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,10 +1,13 @@
1
1
  import path from "node:path";
2
- import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
2
+ import os from "node:os";
3
+ import type { ClawdbotConfig, ReplyPayload, 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 { generateSignUrl } from "./api.js";
10
+ import { ossUpload } from "./oss.js";
8
11
 
9
12
  type MediaInfo = {
10
13
  path: string;
@@ -12,63 +15,53 @@ type MediaInfo = {
12
15
  placeholder: string;
13
16
  };
14
17
 
15
- async function resolveMediaFromUrls(
16
- fileUrls: string[],
17
- maxBytes: number,
18
- log?: (msg: string) => void,
19
- ): 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
20
  const core = getDcgchatRuntime();
21
21
  const out: MediaInfo[] = [];
22
+ log(`dcgchat media: starting resolve for ${files.length} file(s): ${JSON.stringify(files)}`);
22
23
 
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 file = files[i];
28
26
  try {
29
- 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")}`);
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);
31
35
  if (!response.ok) {
32
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] fetch failed with HTTP ${response.status}, skipping`);
36
+ log?.(`dcgchat media: [${i + 1}/${files.length}] fetch failed with HTTP ${response.status}, skipping`);
33
37
  continue;
34
38
  }
35
39
  const buffer = Buffer.from(await response.arrayBuffer());
36
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] downloaded buffer size=${buffer.length} bytes`);
37
40
 
38
41
  let contentType = response.headers.get("content-type") || "";
39
42
  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
+ contentType = await core.media.detectMime({ buffer }) || "";
43
44
  }
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
-
45
+ const fileName = file.name || path.basename(new URL(file.url).pathname) || "file";
49
46
  const saved = await core.channel.media.saveMediaBuffer(
50
47
  buffer,
51
48
  contentType,
52
49
  "inbound",
53
- maxBytes,
50
+ mediaMaxBytes,
54
51
  fileName,
55
52
  );
56
-
57
53
  const isImage = contentType.startsWith("image/");
58
54
  out.push({
59
55
  path: saved.path,
60
- // @ts-ignore
61
- contentType: saved.contentType,
56
+ contentType: saved.contentType || "",
62
57
  placeholder: isImage ? "<media:image>" : "<media:file>",
63
58
  });
64
59
 
65
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] saved to ${saved.path} (contentType=${saved.contentType}, isImage=${isImage})`);
66
60
  } catch (err) {
67
- log?.(`dcgchat media: [${i + 1}/${fileUrls.length}] FAILED to process ${url}: ${String(err)}`);
61
+ log(`dcgchat media: [${i + 1}/${files.length}] FAILED to process ${file.url}: ${String(err)}`);
68
62
  }
69
63
  }
70
-
71
- log?.(`dcgchat media: resolve complete, ${out.length}/${fileUrls.length} file(s) succeeded`);
64
+ log(`dcgchat media: resolve complete, ${out.length}/${files.length} file(s) succeeded`);
72
65
 
73
66
  return out;
74
67
  }
@@ -111,10 +104,9 @@ export async function handleDcgchatMessage(params: {
111
104
 
112
105
  const account = resolveAccount(cfg, accountId);
113
106
  const userId = msg._userId.toString();
114
- // const text = msg.text?.trim();
115
107
  const text = msg.content.text?.trim();
116
108
 
117
- if (!userId || !text) {
109
+ if (!text) {
118
110
  params.onChunk({
119
111
  messageType: "openclaw_bot_chat",
120
112
  _userId: msg._userId,
@@ -122,9 +114,13 @@ export async function handleDcgchatMessage(params: {
122
114
  // @ts-ignore
123
115
  content: {
124
116
  bot_token: msg.content.bot_token,
117
+ domain_id: msg.content.domain_id,
118
+ app_id: msg.content.app_id,
119
+ bot_id: msg.content.bot_id,
120
+ agent_id: msg.content.agent_id,
125
121
  session_id: msg.content.session_id,
126
122
  message_id: msg.content.message_id,
127
- response: "[错误] 消息格式不正确",
123
+ response: "你需要我帮你做什么呢?",
128
124
  },
129
125
  });
130
126
  return;
@@ -135,28 +131,26 @@ export async function handleDcgchatMessage(params: {
135
131
 
136
132
  const route = core.channel.routing.resolveAgentRoute({
137
133
  cfg,
138
- channel: "dcgchat",
134
+ channel: "dcgchat-test",
139
135
  accountId: account.accountId,
140
136
  peer: { kind: "direct", id: userId },
141
137
  });
142
138
 
143
139
  // 处理用户上传的文件
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}`);
140
+ const files = msg.content.files ?? [];
146
141
  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);
142
+ if (files.length > 0) {
143
+ const mediaList = await resolveMediaFromUrls(files, msg.content.bot_token, log)
151
144
  mediaPayload = buildMediaPayload(mediaList);
152
- log(`dcgchat[${accountId}]: media resolved ${mediaList.length}/${fileUrls.length} file(s), payload=${JSON.stringify(mediaPayload)}`);
145
+ log(`dcgchat[${accountId}]: media resolved ${mediaList.length}/${files.length} file(s), payload=${JSON.stringify(mediaList)}`);
153
146
  }
154
147
 
155
148
  const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
156
149
  // const messageBody = `${userId}: ${text}`;
150
+ // 补充消息
157
151
  const messageBody = text;
158
152
  const bodyFormatted = core.channel.reply.formatAgentEnvelope({
159
- channel: "DCG Chat",
153
+ channel: "书灵墨宝",
160
154
  from: userId,
161
155
  timestamp: new Date(),
162
156
  envelope: envelopeOptions,
@@ -174,19 +168,21 @@ export async function handleDcgchatMessage(params: {
174
168
  ChatType: "direct",
175
169
  SenderName: userId,
176
170
  SenderId: userId,
177
- Provider: "dcgchat" as const,
178
- Surface: "dcgchat" as const,
171
+ Provider: "dcgchat-test" as const,
172
+ Surface: "dcgchat-test" as const,
179
173
  MessageSid: msg.content.message_id,
180
174
  Timestamp: Date.now(),
181
175
  WasMentioned: true,
182
176
  CommandAuthorized: true,
183
- OriginatingChannel: "dcgchat" as const,
177
+ OriginatingChannel: "dcgchat-test" as const,
184
178
  OriginatingTo: `user:${userId}`,
185
179
  ...mediaPayload,
186
180
  });
187
181
 
188
182
  log(`dcgchat[${accountId}]: ctxPayload=${JSON.stringify(ctxPayload)}`);
189
183
 
184
+ let textChunk = ''
185
+
190
186
  const prefixContext = createReplyPrefixContext({ cfg, agentId: route.agentId });
191
187
 
192
188
  const { dispatcher, replyOptions, markDispatchIdle } =
@@ -195,46 +191,102 @@ export async function handleDcgchatMessage(params: {
195
191
  responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
196
192
  humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, route.agentId),
197
193
  onReplyStart: async () => {},
198
- deliver: async (payload) => {
194
+ deliver: async (payload: { text: string | any[]; }) => {
199
195
  log(`dcgchat[${accountId}][deliver]: received chunk, text length=${payload.text?.length || 0}`);
200
- const t = payload.text?.trim();
201
- if (t) {
202
- log(`dcgchat[${accountId}][deliver]: sending chunk to user ${msg._userId}, text="${t.slice(0, 50)}..."`);
203
- params.onChunk({
204
- messageType: "openclaw_bot_chat",
205
- _userId: msg._userId,
206
- source: "client",
207
- content: {
208
- bot_token: msg.content.bot_token,
209
- session_id: msg.content.session_id,
210
- message_id: msg.content.message_id,
211
- response: t,
212
- state: 'chunk',
213
- },
214
- });
215
- log(`dcgchat[${accountId}][deliver]: chunk sent successfully`);
216
- } else {
217
- log(`dcgchat[${accountId}][deliver]: skipping empty chunk`);
218
- }
219
196
  },
220
- onError: (err, info) => {
197
+ onError: (err: any, info: { kind: any; }) => {
221
198
  error(`dcgchat[${accountId}] ${info.kind} reply failed: ${String(err)}`);
222
199
  },
223
200
  onIdle: () => {},
224
201
  });
225
202
 
226
- 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) {
227
281
 
228
- await core.channel.reply.dispatchReplyFromConfig({
229
- ctx: ctxPayload,
230
- cfg,
231
- dispatcher,
232
- replyOptions: {
233
- ...replyOptions,
234
- onModelSelected: prefixContext.onModelSelected,
235
- },
236
- });
237
282
 
283
+ } else {
284
+ log(`dcgchat[${accountId}][deliver]: skipping empty chunk`);
285
+ }
286
+ },
287
+ },
288
+ });
289
+ }
238
290
  log(`dcgchat[${accountId}]: dispatch complete, sending final state`);
239
291
  params.onChunk({
240
292
  messageType: "openclaw_bot_chat",
@@ -242,6 +294,10 @@ export async function handleDcgchatMessage(params: {
242
294
  source: "client",
243
295
  content: {
244
296
  bot_token: msg.content.bot_token,
297
+ domain_id: msg.content.domain_id,
298
+ app_id: msg.content.app_id,
299
+ bot_id: msg.content.bot_id,
300
+ agent_id: msg.content.agent_id,
245
301
  session_id: msg.content.session_id,
246
302
  message_id: msg.content.message_id,
247
303
  response: '',
@@ -249,6 +305,7 @@ export async function handleDcgchatMessage(params: {
249
305
  },
250
306
  });
251
307
  setMsgStatus('finished');
308
+ textChunk = ''
252
309
  log(`dcgchat[${accountId}]: final state sent`);
253
310
 
254
311
  markDispatchIdle();
@@ -262,6 +319,10 @@ export async function handleDcgchatMessage(params: {
262
319
  source: "client",
263
320
  content: {
264
321
  bot_token: msg.content.bot_token,
322
+ domain_id: msg.content.domain_id,
323
+ app_id: msg.content.app_id,
324
+ bot_id: msg.content.bot_id,
325
+ agent_id: msg.content.agent_id,
265
326
  session_id: msg.content.session_id,
266
327
  message_id: msg.content.message_id,
267
328
  response: `[错误] ${err instanceof Error ? err.message : String(err)}`,
package/src/channel.ts CHANGED
@@ -1,14 +1,14 @@
1
1
  import type { ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk";
2
2
  import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
3
3
  import type { ResolvedDcgchatAccount, DcgchatConfig } from "./types.js";
4
- import { logDcgchat } from "./log.js";
5
4
  import { getWsConnection } from "./connection.js";
6
5
  import { ossUpload } from "./oss.js";
7
6
  import { getMsgParams } from "./tool.js";
8
7
 
8
+
9
9
  export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null): ResolvedDcgchatAccount {
10
10
  const id = accountId ?? DEFAULT_ACCOUNT_ID;
11
- const raw = (cfg.channels?.["dcgchat"] as DcgchatConfig | undefined) ?? {};
11
+ const raw = (cfg.channels?.["dcgchat-test"] as DcgchatConfig | undefined) ?? {};
12
12
  return {
13
13
  accountId: id,
14
14
  enabled: raw.enabled !== false,
@@ -22,14 +22,14 @@ export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null):
22
22
  }
23
23
 
24
24
  export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
25
- id: "dcgchat",
25
+ id: "dcgchat-test",
26
26
  meta: {
27
- id: "dcgchat",
28
- label: "DCG Chat",
29
- selectionLabel: "DCG Chat",
27
+ id: "dcgchat-test",
28
+ label: "书灵墨宝",
29
+ selectionLabel: "书灵墨宝",
30
30
  docsPath: "/channels/dcgchat",
31
- docsLabel: "dcgchat",
32
- blurb: "连接 OpenClaw 与 DCG Chat 产品",
31
+ docsLabel: "dcgchat-test",
32
+ blurb: "连接 OpenClaw 与 书灵墨宝 产品",
33
33
  order: 80,
34
34
  },
35
35
  capabilities: {
@@ -68,8 +68,8 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
68
68
  ...cfg,
69
69
  channels: {
70
70
  ...cfg.channels,
71
- "dcgchat": {
72
- ...(cfg.channels?.["dcgchat"] as Record<string, unknown> | undefined),
71
+ "dcgchat-test": {
72
+ ...(cfg.channels?.["dcgchat-test"] as Record<string, unknown> | undefined),
73
73
  enabled,
74
74
  },
75
75
  },
@@ -98,77 +98,129 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
98
98
  // textChunkLimit: 25,
99
99
  textChunkLimit: 4000,
100
100
  sendText: async (ctx) => {
101
- const target = ctx.to || "(implicit)";
102
101
  const ws = getWsConnection()
103
102
  const params = getMsgParams();
103
+ const log = ctx.runtime?.log ?? console.log;
104
104
  if (ws?.readyState === WebSocket.OPEN) {
105
105
  const {botToken} = resolveAccount(ctx.cfg, ctx.accountId);
106
106
  const content = {
107
107
  messageType: "openclaw_bot_chat",
108
- _userId: target,
108
+ _userId: params.userId,
109
109
  source: "client",
110
110
  content: {
111
111
  bot_token: botToken,
112
+ domain_id: params.domainId,
113
+ app_id: params.appId,
114
+ bot_id: params.botId,
115
+ agent_id: params.agentId,
112
116
  response: ctx.text,
113
117
  session_id: params.sessionId,
114
118
  message_id: params.messageId || Date.now().toString(),
115
119
  },
116
120
  };
117
121
  ws.send(JSON.stringify(content));
118
- logDcgchat.info(`dcgchat[${ctx.accountId}]: sendText to ${target}, ${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)}`);
119
139
  } else {
120
- 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}`);
121
141
  }
122
142
  return {
123
- channel: "dcgchat",
143
+ channel: "dcgchat-test",
124
144
  messageId: `dcg-${Date.now()}`,
125
- chatId: target,
145
+ chatId: params.userId.toString(),
126
146
  };
127
147
  },
128
148
  sendMedia: async (ctx) => {
129
- const target = ctx.to || "(implicit)";
130
149
  const ws = getWsConnection()
131
150
  const params = getMsgParams();
132
- 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() || ''
133
154
  const {botToken} = resolveAccount(ctx.cfg, ctx.accountId);
134
155
  try {
135
156
  const url = ctx.mediaUrl ? await ossUpload(ctx.mediaUrl, botToken) : '';
136
- const fileName = ctx.mediaUrl?.split(/[\\/]/).pop() || ''
137
157
  const content = {
138
158
  messageType: "openclaw_bot_chat",
139
- _userId: target,
159
+ _userId: params.userId,
140
160
  source: "client",
141
161
  content: {
142
162
  bot_token: botToken,
143
- response: ctx.text + '\n' + `[${fileName}](${url})`,
163
+ domain_id: params.domainId,
164
+ app_id: params.appId,
165
+ bot_id: params.botId,
166
+ agent_id: params.agentId,
167
+ response: ctx.text,
168
+ files: [{
169
+ url: url,
170
+ name: fileName,
171
+ }],
144
172
  session_id: params.sessionId,
145
173
  message_id: params.messageId ||Date.now().toString(),
146
174
  },
147
175
  };
148
176
  ws.send(JSON.stringify(content));
149
- logDcgchat.info(`dcgchat[${ctx.accountId}]: sendMedia to ${target}, ${JSON.stringify(content)}`);
177
+ log(`dcgchat[${ctx.accountId}]: sendMedia alioss to ${params.userId}, ${JSON.stringify(content)}`);
150
178
  } catch (error) {
151
179
  const content = {
152
180
  messageType: "openclaw_bot_chat",
153
- _userId: target,
181
+ _userId: params.userId,
154
182
  source: "client",
155
183
  content: {
156
184
  bot_token: botToken,
157
- response: ctx.text + '\n' + ctx.mediaUrl,
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
+ }],
158
194
  session_id: params.sessionId || Date.now().toString(),
159
- message_id: params.messageId ||Date.now().toString(),
195
+ message_id: Date.now().toString(),
160
196
  },
161
197
  };
162
198
  ws.send(JSON.stringify(content));
163
- logDcgchat.info(`dcgchat[${ctx.accountId}]: sendMedia to ${target}, ${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)}`);
164
216
  }
165
217
  } else {
166
- 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}`);
167
219
  }
168
220
  return {
169
- channel: "dcgchat",
221
+ channel: "dcgchat-test",
170
222
  messageId: `dcg-${Date.now()}`,
171
- chatId: target,
223
+ chatId: params.userId.toString(),
172
224
  };
173
225
  },
174
226
  },
Binary file
package/src/monitor.ts CHANGED
@@ -112,11 +112,16 @@ 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,
121
+ domainId: account.domainId || 1000,
122
+ appId: account.appId || '100',
123
+ botId: msg.content.bot_id,
124
+ agentId: msg.content.agent_id,
120
125
  });
121
126
  await handleDcgchatMessage({
122
127
  cfg,
@@ -134,7 +139,7 @@ export async function monitorDcgchatProvider(opts: MonitorDcgchatOpts): Promise<
134
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>;
135
140
  const content = { event_type, operation_type, skill_url, skill_code, skill_id, bot_token, websocket_trace_id };
136
141
  if (event_type === "skill") {
137
- if (operation_type === "install" || operation_type === "enable") {
142
+ if (operation_type === "install" || operation_type === "enable" || operation_type === "update") {
138
143
  installSkill({ path: skill_url, code: skill_code }, content);
139
144
  } else if (operation_type === "remove" || operation_type === "disable") {
140
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/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
@@ -116,7 +116,7 @@ export async function installSkill(params: ISkillParams, msgContent: Record<stri
116
116
  reject(err);
117
117
  }
118
118
  })
119
- .on("error", (err) => {
119
+ .on("error", (err: { message: any; }) => {
120
120
  hasError = true;
121
121
  reject(new Error(`解压流错误: ${err.message}`));
122
122
  });
@@ -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,13 +1,16 @@
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;
8
- token: string
9
- sessionId: string
10
- messageId: string
7
+ token: string;
8
+ sessionId: string;
9
+ messageId: string;
10
+ domainId: string;
11
+ appId: string;
12
+ botId: string;
13
+ agentId: string;
11
14
  }
12
15
  let msgStatus: 'running' | 'finished' | '' = '';
13
16
  export function setMsgParams(params: any) {
@@ -25,51 +28,68 @@ export function setMsgStatus(status: 'running' | 'finished' | '') {
25
28
  export function getMsgStatus() {
26
29
  return msgStatus;
27
30
  }
28
-
31
+ let runId = '';
32
+ let toolName = '';
29
33
  export function monitoringToolMessage(api: OpenClawPluginApi) {
30
- api.on("after_tool_call", (event, payload) => {
34
+ api.on("after_tool_call", (event) => {
31
35
  const ws = getWsConnection()
32
36
  const params = getMsgParams();
33
37
  const status = getMsgStatus();
34
38
  //
35
39
  if (ws?.readyState === WebSocket.OPEN && status === 'running') {
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
+ }
55
+ const text = JSON.stringify({
56
+ type: 'tool_call',
57
+ specialIdentification: 'dcgchat_tool_call_special_identification',
58
+ ...event
59
+ });
36
60
  ws.send(JSON.stringify({
37
61
  messageType: "openclaw_bot_chat",
38
62
  _userId: params?.userId,
39
63
  source: "client",
40
64
  content: {
41
65
  bot_token: params?.token,
42
- response: 'all_finished',
43
- session_id:params?.sessionId,
44
- message_id: params?.messageId || Date.now().toString()
45
- },
46
- }));
47
- ws.send(JSON.stringify({
48
- messageType: "openclaw_bot_chat",
49
- _userId: params?.userId,
50
- source: "client",
51
- content: {
52
- bot_token: params?.token,
53
- response: JSON.stringify({
54
- type: 'tool_call',
55
- ...event
56
- }),
57
- session_id:params?.sessionId,
58
- message_id: params?.messageId || Date.now().toString()
59
- },
60
- }));
61
- ws.send(JSON.stringify({
62
- messageType: "openclaw_bot_chat",
63
- _userId: params?.userId,
64
- source: "client",
65
- content: {
66
- bot_token: params?.token,
67
- response: 'all_finished',
66
+ domain_id: params?.domainId,
67
+ app_id: params?.appId,
68
+ bot_id: params?.botId,
69
+ agent_id: params?.agentId,
70
+ thinking_content: text,
71
+ response: '',
68
72
  session_id:params?.sessionId,
69
73
  message_id: params?.messageId || Date.now().toString()
70
74
  },
71
75
  }));
72
- 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)}`);
73
93
  }
74
94
  });
75
95
  }
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,11 @@ 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;
82
+ files?: {url: string, name: string}[];
71
83
  };
72
84
  };
73
85