@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 +2 -2
- package/package.json +3 -2
- package/src/api.ts +24 -1
- package/src/bot.ts +138 -69
- package/src/channel.ts +77 -88
- package/src/monitor.ts +4 -3
- package/src/oss.ts +2 -2
- package/src/request.ts +10 -3
- package/src/skill.ts +2 -2
- package/src/tool.ts +39 -31
- package/src/types.ts +1 -0
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.
|
|
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
|
-
"
|
|
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:
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
25
|
+
const file = files[i];
|
|
26
26
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
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:
|
|
32
|
-
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 ||
|
|
173
|
+
message_id: params.messageId ||Date.now().toString(),
|
|
209
174
|
},
|
|
210
175
|
};
|
|
211
176
|
ws.send(JSON.stringify(content));
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
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:
|
|
121
|
-
appId:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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: '
|
|
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
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
})
|
|
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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
}
|