@dcrays/dcgchat-test 0.2.33 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +2 -1
- package/package.json +1 -1
- package/src/bot.ts +62 -39
- package/src/channel.ts +1 -1
- package/src/cron.ts +125 -0
- package/src/gateway/index.ts +458 -0
- package/src/gateway/security.ts +101 -0
- package/src/gateway/socket.ts +271 -0
- package/src/monitor.ts +42 -15
- package/src/request/api.ts +4 -18
- package/src/request/oss.ts +5 -4
- package/src/tool.ts +4 -2
- package/src/transport.ts +23 -0
- package/src/types.ts +2 -0
- package/src/utils/global.ts +3 -0
- package/src/utils/searchFile.ts +3 -1
package/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { dcgchatPlugin } from './src/channel.js'
|
|
|
4
4
|
import { setDcgchatRuntime, setWorkspaceDir } from './src/utils/global.js'
|
|
5
5
|
import { monitoringToolMessage } from './src/tool.js'
|
|
6
6
|
import { setOpenClawConfig } from './src/utils/global.js'
|
|
7
|
+
import { startDcgchatGatewaySocket } from './src/gateway/socket.js'
|
|
7
8
|
|
|
8
9
|
const plugin = {
|
|
9
10
|
id: "dcgchat-test",
|
|
@@ -13,9 +14,9 @@ const plugin = {
|
|
|
13
14
|
register(api: OpenClawPluginApi) {
|
|
14
15
|
setDcgchatRuntime(api.runtime)
|
|
15
16
|
|
|
16
|
-
console.log('🚀 ~ handleDcgchatMessage ~ process.platform:', process.platform)
|
|
17
17
|
monitoringToolMessage(api)
|
|
18
18
|
setOpenClawConfig(api.config)
|
|
19
|
+
startDcgchatGatewaySocket()
|
|
19
20
|
api.registerChannel({ plugin: dcgchatPlugin })
|
|
20
21
|
setWorkspaceDir(api.config?.agents?.defaults?.workspace)
|
|
21
22
|
api.registerTool((ctx) => {
|
package/package.json
CHANGED
package/src/bot.ts
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
getDcgchatRuntime,
|
|
9
9
|
getOpenClawConfig,
|
|
10
10
|
getWorkspaceDir,
|
|
11
|
-
|
|
11
|
+
setMsgParamsSessionKey,
|
|
12
12
|
setMsgStatus
|
|
13
13
|
} from './utils/global.js'
|
|
14
14
|
import { resolveAccount, sendDcgchatMedia } from './channel.js'
|
|
@@ -17,6 +17,7 @@ import { extractMobookFiles } from './utils/searchFile.js'
|
|
|
17
17
|
import { createMsgContext, sendChunk, sendFinal, sendText as sendTextMsg, sendError, sendText } from './transport.js'
|
|
18
18
|
import { dcgLogger } from './utils/log.js'
|
|
19
19
|
import { channelInfo, systemCommand, interruptCommand, ENV } from './utils/constant.js'
|
|
20
|
+
import { sendMessageToGateway } from './gateway/socket.js'
|
|
20
21
|
|
|
21
22
|
type MediaInfo = {
|
|
22
23
|
path: string
|
|
@@ -30,16 +31,16 @@ type TFileInfo = { name: string; url: string }
|
|
|
30
31
|
const mediaMaxBytes = 300 * 1024 * 1024
|
|
31
32
|
|
|
32
33
|
/** Active LLM generation abort controllers, keyed by conversationId */
|
|
33
|
-
const activeGenerations = new Map<string, AbortController>()
|
|
34
|
+
// const activeGenerations = new Map<string, AbortController>()
|
|
34
35
|
|
|
35
|
-
/** Abort an in-progress LLM generation for a given conversationId */
|
|
36
|
-
export function abortMobookappGeneration(conversationId: string): void {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
36
|
+
// /** Abort an in-progress LLM generation for a given conversationId */
|
|
37
|
+
// export function abortMobookappGeneration(conversationId: string): void {
|
|
38
|
+
// const ctrl = activeGenerations.get(conversationId)
|
|
39
|
+
// if (ctrl) {
|
|
40
|
+
// ctrl.abort()
|
|
41
|
+
// activeGenerations.delete(conversationId)
|
|
42
|
+
// }
|
|
43
|
+
// }
|
|
43
44
|
|
|
44
45
|
/**
|
|
45
46
|
* Extract agentId from conversation_id formatted as "agentId::suffix".
|
|
@@ -133,6 +134,14 @@ function resolveReplyMediaList(payload: ReplyPayload): string[] {
|
|
|
133
134
|
export async function handleDcgchatMessage(msg: InboundMessage, accountId: string): Promise<void> {
|
|
134
135
|
const msgCtx = createMsgContext(msg)
|
|
135
136
|
|
|
137
|
+
let finalSent = false
|
|
138
|
+
|
|
139
|
+
const safeSendFinal = () => {
|
|
140
|
+
if (finalSent) return
|
|
141
|
+
finalSent = true
|
|
142
|
+
sendFinal(msgCtx)
|
|
143
|
+
}
|
|
144
|
+
|
|
136
145
|
let completeText = ''
|
|
137
146
|
const config = getOpenClawConfig()
|
|
138
147
|
if (!config) {
|
|
@@ -145,7 +154,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
145
154
|
|
|
146
155
|
if (!text) {
|
|
147
156
|
sendTextMsg(msgCtx, '你需要我帮你做什么呢?')
|
|
148
|
-
|
|
157
|
+
safeSendFinal()
|
|
149
158
|
return
|
|
150
159
|
}
|
|
151
160
|
|
|
@@ -153,6 +162,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
153
162
|
const core = getDcgchatRuntime()
|
|
154
163
|
|
|
155
164
|
const conversationId = msg.content.session_id?.trim()
|
|
165
|
+
const agentId = msg.content.agent_id?.trim()
|
|
166
|
+
const realMobook = msg.content.real_mobook?.toString().trim()
|
|
156
167
|
|
|
157
168
|
const route = core.channel.routing.resolveAgentRoute({
|
|
158
169
|
cfg: config,
|
|
@@ -164,20 +175,20 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
164
175
|
// If conversation_id encodes an agentId prefix ("agentId::suffix"), override the route.
|
|
165
176
|
const embeddedAgentId = extractAgentIdFromConversationId(conversationId)
|
|
166
177
|
const effectiveAgentId = embeddedAgentId ?? route.agentId
|
|
167
|
-
const effectiveSessionKey =
|
|
168
|
-
? `agent
|
|
169
|
-
|
|
178
|
+
const effectiveSessionKey =
|
|
179
|
+
realMobook === '1' ? route.sessionKey : `agent:main:mobook:direct:${agentId}:${conversationId}`.toLowerCase()
|
|
180
|
+
setMsgParamsSessionKey(effectiveSessionKey)
|
|
170
181
|
|
|
171
182
|
const agentEntry =
|
|
172
183
|
effectiveAgentId && effectiveAgentId !== 'main' ? config.agents?.list?.find((a) => a.id === effectiveAgentId) : undefined
|
|
173
184
|
const agentDisplayName = agentEntry?.name ?? (effectiveAgentId && effectiveAgentId !== 'main' ? effectiveAgentId : undefined)
|
|
174
185
|
|
|
175
186
|
// Abort any existing generation for this conversation, then start a new one
|
|
176
|
-
const existingCtrl = activeGenerations.get(conversationId)
|
|
177
|
-
if (existingCtrl) existingCtrl.abort()
|
|
178
|
-
const genCtrl = new AbortController()
|
|
179
|
-
const genSignal = genCtrl.signal
|
|
180
|
-
activeGenerations.set(conversationId, genCtrl)
|
|
187
|
+
// const existingCtrl = activeGenerations.get(conversationId)
|
|
188
|
+
// if (existingCtrl) existingCtrl.abort()
|
|
189
|
+
// const genCtrl = new AbortController()
|
|
190
|
+
// const genSignal = genCtrl.signal
|
|
191
|
+
// activeGenerations.set(conversationId, genCtrl)
|
|
181
192
|
|
|
182
193
|
// 处理用户上传的文件
|
|
183
194
|
const files = msg.content.files ?? []
|
|
@@ -245,14 +256,14 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
245
256
|
}
|
|
246
257
|
},
|
|
247
258
|
onError: (err: unknown, info: { kind: string }) => {
|
|
259
|
+
safeSendFinal()
|
|
248
260
|
dcgLogger(`${info.kind} reply failed: ${String(err)}`, 'error')
|
|
249
261
|
},
|
|
250
262
|
onIdle: () => {
|
|
251
|
-
|
|
263
|
+
safeSendFinal()
|
|
252
264
|
}
|
|
253
265
|
})
|
|
254
266
|
|
|
255
|
-
let wasAborted = false
|
|
256
267
|
try {
|
|
257
268
|
if (systemCommand.includes(text?.trim())) {
|
|
258
269
|
dcgLogger(`dispatching /new`)
|
|
@@ -267,7 +278,13 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
267
278
|
})
|
|
268
279
|
} else if (interruptCommand.includes(text?.trim())) {
|
|
269
280
|
dcgLogger(`interrupt command: ${text}`)
|
|
270
|
-
abortMobookappGeneration(conversationId)
|
|
281
|
+
// abortMobookappGeneration(conversationId)
|
|
282
|
+
sendMessageToGateway(
|
|
283
|
+
JSON.stringify({
|
|
284
|
+
method: 'chat.abort',
|
|
285
|
+
params: { sessionKey: effectiveSessionKey }
|
|
286
|
+
})
|
|
287
|
+
)
|
|
271
288
|
sendFinal(msgCtx)
|
|
272
289
|
return
|
|
273
290
|
} else {
|
|
@@ -278,7 +295,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
278
295
|
dispatcher,
|
|
279
296
|
replyOptions: {
|
|
280
297
|
...replyOptions,
|
|
281
|
-
abortSignal: genSignal,
|
|
298
|
+
// abortSignal: genSignal,
|
|
282
299
|
onModelSelected: prefixContext.onModelSelected,
|
|
283
300
|
onPartialReply: async (payload: ReplyPayload) => {
|
|
284
301
|
// Accumulate full text
|
|
@@ -309,26 +326,26 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
309
326
|
})
|
|
310
327
|
}
|
|
311
328
|
} catch (err: unknown) {
|
|
312
|
-
if (genSignal.aborted) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
} else if (err instanceof Error && err.name === 'AbortError') {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
} else {
|
|
319
|
-
|
|
320
|
-
}
|
|
329
|
+
// if (genSignal.aborted) {
|
|
330
|
+
// wasAborted = true
|
|
331
|
+
// dcgLogger(` generation aborted for conversationId=${conversationId}`)
|
|
332
|
+
// } else if (err instanceof Error && err.name === 'AbortError') {
|
|
333
|
+
// wasAborted = true
|
|
334
|
+
// dcgLogger(` generation aborted for conversationId=${conversationId}`)
|
|
335
|
+
// } else {
|
|
336
|
+
// dcgLogger(` dispatchReplyFromConfig error: ${String(err)}`, 'error')
|
|
337
|
+
// }
|
|
321
338
|
} finally {
|
|
322
|
-
if (activeGenerations.get(conversationId) === genCtrl) {
|
|
323
|
-
|
|
324
|
-
}
|
|
339
|
+
// if (activeGenerations.get(conversationId) === genCtrl) {
|
|
340
|
+
// activeGenerations.delete(conversationId)
|
|
341
|
+
// }
|
|
325
342
|
}
|
|
326
343
|
try {
|
|
327
344
|
markRunComplete()
|
|
345
|
+
markDispatchIdle()
|
|
328
346
|
} catch (err) {
|
|
329
|
-
dcgLogger(` markRunComplete error: ${String(err)}`, 'error')
|
|
347
|
+
dcgLogger(` markRunComplete||markRunComplete error: ${String(err)}`, 'error')
|
|
330
348
|
}
|
|
331
|
-
markDispatchIdle()
|
|
332
349
|
if (![...systemCommand, ...interruptCommand].includes(text?.trim())) {
|
|
333
350
|
for (const file of extractMobookFiles(completeText)) {
|
|
334
351
|
const candidates: string[] = [file]
|
|
@@ -342,10 +359,14 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
342
359
|
}
|
|
343
360
|
const resolved = candidates.find((p) => fs.existsSync(p))
|
|
344
361
|
if (!resolved) continue
|
|
345
|
-
|
|
362
|
+
try {
|
|
363
|
+
await sendDcgchatMedia({ msgCtx, mediaUrl: resolved, text: '' })
|
|
364
|
+
} catch (err) {
|
|
365
|
+
dcgLogger(` sendDcgchatMedia error: ${String(err)}`, 'error')
|
|
366
|
+
}
|
|
346
367
|
}
|
|
347
368
|
}
|
|
348
|
-
|
|
369
|
+
safeSendFinal()
|
|
349
370
|
clearSentMediaKeys(msg.content.message_id)
|
|
350
371
|
setMsgStatus('finished')
|
|
351
372
|
|
|
@@ -366,5 +387,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
366
387
|
} catch (err) {
|
|
367
388
|
dcgLogger(` handle message failed: ${String(err)}`, 'error')
|
|
368
389
|
sendError(msgCtx, err instanceof Error ? err.message : String(err))
|
|
390
|
+
} finally {
|
|
391
|
+
safeSendFinal()
|
|
369
392
|
}
|
|
370
393
|
}
|
package/src/channel.ts
CHANGED
|
@@ -31,7 +31,7 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
|
|
|
31
31
|
const fileName = mediaUrl?.split(/[\\/]/).pop() || ''
|
|
32
32
|
|
|
33
33
|
try {
|
|
34
|
-
const url = opts.mediaUrl ? await ossUpload(opts.mediaUrl, msgCtx.botToken) : ''
|
|
34
|
+
const url = opts.mediaUrl ? await ossUpload(opts.mediaUrl, msgCtx.botToken, 1) : ''
|
|
35
35
|
wsSendRaw(msgCtx, {
|
|
36
36
|
response: opts.text ?? '',
|
|
37
37
|
files: [{ url, name: fileName }]
|
package/src/cron.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
import { execFile } from 'node:child_process'
|
|
5
|
+
import { promisify } from 'node:util'
|
|
6
|
+
|
|
7
|
+
const execFileAsync = promisify(execFile)
|
|
8
|
+
import type { IMsgParams } from './types.js'
|
|
9
|
+
import { DcgchatMsgContext, sendEventMessage } from './transport.js'
|
|
10
|
+
import { getMsgParams, getWorkspaceDir } from './utils/global.js'
|
|
11
|
+
import { ossUpload } from './request/oss.js'
|
|
12
|
+
import { dcgLogger } from './utils/log.js'
|
|
13
|
+
import { sendMessageToGateway } from './gateway/socket.js'
|
|
14
|
+
|
|
15
|
+
export function getCronJobsPath(): string {
|
|
16
|
+
const workspaceDir = getWorkspaceDir()
|
|
17
|
+
const cronDir = workspaceDir.replace('workspace', 'cron')
|
|
18
|
+
return path.join(cronDir, 'jobs.json')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function msgParamsToCtx(p: IMsgParams): DcgchatMsgContext | null {
|
|
22
|
+
if (!p?.token) return null
|
|
23
|
+
return {
|
|
24
|
+
userId: p.userId,
|
|
25
|
+
botToken: p.token,
|
|
26
|
+
domainId: p.domainId,
|
|
27
|
+
appId: p.appId,
|
|
28
|
+
botId: p.botId,
|
|
29
|
+
agentId: p.agentId,
|
|
30
|
+
sessionId: p.sessionId,
|
|
31
|
+
messageId: p.messageId
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const CRON_UPLOAD_DEBOUNCE_MS = 30_000
|
|
36
|
+
|
|
37
|
+
/** 待合并的上传上下文(短时间内多次调用只保留最后一次) */
|
|
38
|
+
let pendingCronUploadCtx: DcgchatMsgContext | null = null
|
|
39
|
+
let cronUploadFlushTimer: ReturnType<typeof setTimeout> | null = null
|
|
40
|
+
|
|
41
|
+
async function runCronJobsUpload(msgCtx: DcgchatMsgContext): Promise<void> {
|
|
42
|
+
const jobPath = getCronJobsPath()
|
|
43
|
+
if (fs.existsSync(jobPath)) {
|
|
44
|
+
try {
|
|
45
|
+
const url = await ossUpload(jobPath, msgCtx.botToken, 0)
|
|
46
|
+
dcgLogger(`定时任务创建成功: ${url}`)
|
|
47
|
+
sendEventMessage(msgCtx, url)
|
|
48
|
+
} catch (error) {
|
|
49
|
+
dcgLogger(`${jobPath} upload failed: ${error}`, 'error')
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
dcgLogger(`${jobPath} not found`, 'error')
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function flushCronUploadQueue(): void {
|
|
57
|
+
cronUploadFlushTimer = null
|
|
58
|
+
const ctx = pendingCronUploadCtx
|
|
59
|
+
pendingCronUploadCtx = null
|
|
60
|
+
if (!ctx) return
|
|
61
|
+
void runCronJobsUpload(ctx)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 将 jobs.json 同步到 OSS 并推送事件。30s 内多次调用合并为一次上传;定时触发后清空待处理项,避免重复执行。
|
|
66
|
+
* @param msgCtx 可选;省略时使用当前 getMsgParams() 快照
|
|
67
|
+
*/
|
|
68
|
+
export function sendDcgchatCron(): void {
|
|
69
|
+
const ctx = msgParamsToCtx(getMsgParams() as IMsgParams)
|
|
70
|
+
if (!ctx) {
|
|
71
|
+
dcgLogger('sendDcgchatCron: no message context (missing token / params)', 'error')
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
pendingCronUploadCtx = ctx
|
|
75
|
+
if (cronUploadFlushTimer !== null) {
|
|
76
|
+
clearTimeout(cronUploadFlushTimer)
|
|
77
|
+
}
|
|
78
|
+
cronUploadFlushTimer = setTimeout(flushCronUploadQueue, CRON_UPLOAD_DEBOUNCE_MS)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 通过 OpenClaw CLI 删除定时任务(走 Gateway,与内存状态一致)。
|
|
83
|
+
* 文档:运行中请勿手改 jobs.json,应使用 `openclaw cron rm` 或工具 API。
|
|
84
|
+
*/
|
|
85
|
+
export const onRemoveCronJob = async (jobId: string) => {
|
|
86
|
+
const id = jobId?.trim()
|
|
87
|
+
if (!id) {
|
|
88
|
+
dcgLogger('onRemoveCronJob: empty jobId', 'error')
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
sendMessageToGateway(JSON.stringify({ method: 'cron.remove', params: { id: jobId } }))
|
|
92
|
+
}
|
|
93
|
+
export const onDisabledCronJob = async (jobId: string) => {
|
|
94
|
+
const id = jobId?.trim()
|
|
95
|
+
if (!id) {
|
|
96
|
+
dcgLogger('onRemoveCronJob: empty jobId', 'error')
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
sendMessageToGateway(JSON.stringify({ method: 'cron.update', params: { id: jobId, patch: { enabled: false } } }))
|
|
100
|
+
}
|
|
101
|
+
export const onEnabledCronJob = async (jobId: string) => {
|
|
102
|
+
const id = jobId?.trim()
|
|
103
|
+
if (!id) {
|
|
104
|
+
dcgLogger('onRemoveCronJob: empty jobId', 'error')
|
|
105
|
+
return
|
|
106
|
+
}
|
|
107
|
+
sendMessageToGateway(JSON.stringify({ method: 'cron.update', params: { id: jobId, patch: { enabled: true } } }))
|
|
108
|
+
}
|
|
109
|
+
export const onRunCronJob = async (jobId: string) => {
|
|
110
|
+
const id = jobId?.trim()
|
|
111
|
+
if (!id) {
|
|
112
|
+
dcgLogger('onRemoveCronJob: empty jobId', 'error')
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
sendMessageToGateway(JSON.stringify({ method: 'cron.update', jobId }))
|
|
116
|
+
}
|
|
117
|
+
export const updateCronJobSessionKey = async (jobId: string) => {
|
|
118
|
+
const id = jobId?.trim()
|
|
119
|
+
if (!id) {
|
|
120
|
+
dcgLogger('onRemoveCronJob: empty jobId', 'error')
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
const params = getMsgParams()
|
|
124
|
+
sendMessageToGateway(JSON.stringify({ method: 'cron.update', params: { id: jobId, patch: { sessionKey: params.sessionKey } } }))
|
|
125
|
+
}
|