@dcrays/dcgchat 0.2.32 → 0.3.18

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
@@ -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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat",
3
- "version": "0.2.32",
3
+ "version": "0.3.18",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
@@ -12,7 +12,7 @@
12
12
  ],
13
13
  "keywords": [
14
14
  "openclaw",
15
- "dcgchat",
15
+ "dcgchat-test",
16
16
  "websocket",
17
17
  "ai"
18
18
  ],
@@ -42,4 +42,4 @@
42
42
  "defaultChoice": "npm"
43
43
  }
44
44
  }
45
- }
45
+ }
package/src/bot.ts CHANGED
@@ -3,20 +3,23 @@ import path from 'node:path'
3
3
  import type { ReplyPayload } from 'openclaw/plugin-sdk'
4
4
  import { createReplyPrefixContext } from 'openclaw/plugin-sdk'
5
5
  import type { InboundMessage } from './types.js'
6
+ import os from 'node:os'
6
7
  import {
7
8
  clearSentMediaKeys,
8
9
  getDcgchatRuntime,
9
10
  getOpenClawConfig,
11
+ getSessionKey,
10
12
  getWorkspaceDir,
11
- getWsConnection,
12
13
  setMsgStatus
13
14
  } from './utils/global.js'
14
15
  import { resolveAccount, sendDcgchatMedia } from './channel.js'
15
16
  import { generateSignUrl } from './request/api.js'
16
17
  import { extractMobookFiles } from './utils/searchFile.js'
17
- import { createMsgContext, sendChunk, sendFinal, sendText as sendTextMsg, sendError, sendText } from './transport.js'
18
+ import { sendChunk, sendFinal, sendText as sendTextMsg, sendError, wsSendRaw, sendText } from './transport.js'
18
19
  import { dcgLogger } from './utils/log.js'
19
- import { channelInfo, systemCommand, interruptCommand, ENV } from './utils/constant.js'
20
+ import { channelInfo, systemCommand, interruptCommand, ENV, ignoreToolCommand } from './utils/constant.js'
21
+ import { sendMessageToGateway } from './gateway/socket.js'
22
+ import { getEffectiveMsgParams, setParamsMessage } from './utils/params.js'
20
23
 
21
24
  type MediaInfo = {
22
25
  path: string
@@ -29,17 +32,20 @@ type TFileInfo = { name: string; url: string }
29
32
 
30
33
  const mediaMaxBytes = 300 * 1024 * 1024
31
34
 
35
+ /** 当前会话最近一次 agent run 的 runId(供 chat.abort 精确打断;无则仅传 sessionKey 仍会中止该会话全部活动运行) */
36
+ const activeRunIdBySessionKey = new Map<string, string>()
37
+
38
+ /**
39
+ * 用户在该 sessionKey 上触发打断后,旧 run 的流式/投递不再下发;与 sessionKey 一一对应,支持多会话。
40
+ * 清除时机:① 下一条非打断用户消息开始处理时;② 旧 run 收尾到 mobook 段时若仍抑制则跳过并发后删除。
41
+ */
42
+ const sessionStreamSuppressed = new Set<string>()
43
+
44
+ /** 各 sessionKey 当前轮回复的流式分片序号(仅统计实际下发的文本 chunk);每轮非打断消息开始时置 0 */
45
+ const streamChunkIdxBySessionKey = new Map<string, number>()
46
+
32
47
  /** Active LLM generation abort controllers, keyed by conversationId */
33
- const activeGenerations = new Map<string, AbortController>()
34
-
35
- /** Abort an in-progress LLM generation for a given conversationId */
36
- export function abortMobookappGeneration(conversationId: string): void {
37
- const ctrl = activeGenerations.get(conversationId)
38
- if (ctrl) {
39
- ctrl.abort()
40
- activeGenerations.delete(conversationId)
41
- }
42
- }
48
+ // const activeGenerations = new Map<string, AbortController>()
43
49
 
44
50
  /**
45
51
  * Extract agentId from conversation_id formatted as "agentId::suffix".
@@ -131,7 +137,7 @@ function resolveReplyMediaList(payload: ReplyPayload): string[] {
131
137
  * 处理一条用户消息,调用 Agent 并返回回复
132
138
  */
133
139
  export async function handleDcgchatMessage(msg: InboundMessage, accountId: string): Promise<void> {
134
- const msgCtx = createMsgContext(msg)
140
+ let finalSent = false
135
141
 
136
142
  let completeText = ''
137
143
  const config = getOpenClawConfig()
@@ -141,43 +147,64 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
141
147
  }
142
148
  const account = resolveAccount(config, accountId)
143
149
  const userId = msg._userId.toString()
144
- const text = msg.content.text?.trim()
145
150
 
146
- if (!text) {
147
- sendTextMsg(msgCtx, '你需要我帮你做什么呢?')
148
- sendFinal(msgCtx)
149
- return
150
- }
151
+ const core = getDcgchatRuntime()
151
152
 
152
- try {
153
- const core = getDcgchatRuntime()
153
+ const conversationId = msg.content.session_id?.trim()
154
+ const agentId = msg.content.agent_id?.trim()
155
+ const real_mobook = msg.content.real_mobook?.toString().trim()
154
156
 
155
- const conversationId = msg.content.session_id?.trim()
157
+ const route = core.channel.routing.resolveAgentRoute({
158
+ cfg: config,
159
+ channel: "dcgchat",
160
+ accountId: account.accountId,
161
+ peer: { kind: 'direct', id: conversationId }
162
+ })
156
163
 
157
- const route = core.channel.routing.resolveAgentRoute({
158
- cfg: config,
159
- channel: "dcgchat",
160
- accountId: account.accountId,
161
- peer: { kind: 'direct', id: conversationId }
162
- })
164
+ const embeddedAgentId = extractAgentIdFromConversationId(conversationId)
163
165
 
164
- // If conversation_id encodes an agentId prefix ("agentId::suffix"), override the route.
165
- const embeddedAgentId = extractAgentIdFromConversationId(conversationId)
166
- const effectiveAgentId = embeddedAgentId ?? route.agentId
167
- const effectiveSessionKey = embeddedAgentId
168
- ? `agent:${embeddedAgentId}:mobook:direct:${conversationId}`.toLowerCase()
169
- : route.sessionKey
166
+ const effectiveAgentId = embeddedAgentId ?? route.agentId
167
+ const effectiveSessionKey = getSessionKey(msg.content, account.accountId)
168
+
169
+ setParamsMessage(effectiveSessionKey, {
170
+ userId: msg._userId,
171
+ botToken: msg.content.bot_token,
172
+ sessionId: conversationId,
173
+ messageId: msg.content.message_id,
174
+ domainId: msg.content.domain_id,
175
+ appId: msg.content.app_id,
176
+ botId: msg.content.bot_id ?? '',
177
+ agentId: msg.content.agent_id ?? '',
178
+ sessionKey: effectiveSessionKey,
179
+ real_mobook
180
+ })
181
+ const outboundCtx = getEffectiveMsgParams(effectiveSessionKey)
182
+ const agentEntry =
183
+ effectiveAgentId && effectiveAgentId !== 'main' ? config.agents?.list?.find((a) => a.id === effectiveAgentId) : undefined
184
+ const agentDisplayName = agentEntry?.name ?? (effectiveAgentId && effectiveAgentId !== 'main' ? effectiveAgentId : undefined)
185
+
186
+ const safeSendFinal = (tag: string) => {
187
+ if (finalSent) return
188
+ finalSent = true
189
+ sendFinal(outboundCtx, tag)
190
+ setMsgStatus(effectiveSessionKey, 'finished')
191
+ }
192
+
193
+ const text = msg.content.text?.trim()
170
194
 
171
- const agentEntry =
172
- effectiveAgentId && effectiveAgentId !== 'main' ? config.agents?.list?.find((a) => a.id === effectiveAgentId) : undefined
173
- const agentDisplayName = agentEntry?.name ?? (effectiveAgentId && effectiveAgentId !== 'main' ? effectiveAgentId : undefined)
195
+ if (!text) {
196
+ sendTextMsg('你需要我帮你做什么呢?', outboundCtx)
197
+ safeSendFinal('not text')
198
+ return
199
+ }
174
200
 
201
+ try {
175
202
  // 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)
203
+ // const existingCtrl = activeGenerations.get(conversationId)
204
+ // if (existingCtrl) existingCtrl.abort()
205
+ // const genCtrl = new AbortController()
206
+ // const genSignal = genCtrl.signal
207
+ // activeGenerations.set(conversationId, genCtrl)
181
208
 
182
209
  // 处理用户上传的文件
183
210
  const files = msg.content.files ?? []
@@ -202,7 +229,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
202
229
  RawBody: text,
203
230
  CommandBody: text,
204
231
  From: userId,
205
- To: conversationId,
232
+ To: effectiveSessionKey,
206
233
  SessionKey: effectiveSessionKey,
207
234
  AccountId: route.accountId,
208
235
  ChatType: 'direct',
@@ -215,7 +242,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
215
242
  WasMentioned: true,
216
243
  CommandAuthorized: true,
217
244
  OriginatingChannel: "dcgchat",
218
- OriginatingTo: `user:${userId}`,
245
+ OriginatingTo: effectiveSessionKey,
219
246
  ...mediaPayload
220
247
  })
221
248
 
@@ -236,24 +263,35 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
236
263
  humanDelay: core.channel.reply.resolveHumanDelayConfig(config, route.agentId),
237
264
  onReplyStart: async () => {},
238
265
  deliver: async (payload: ReplyPayload, info) => {
266
+ if (sessionStreamSuppressed.has(effectiveSessionKey)) return
239
267
  const mediaList = resolveReplyMediaList(payload)
240
268
  for (const mediaUrl of mediaList) {
241
269
  const key = getMediaKey(mediaUrl)
242
270
  if (sentMediaKeys.has(key)) continue
243
271
  sentMediaKeys.add(key)
244
- await sendDcgchatMedia({ msgCtx, mediaUrl, text: '' })
272
+ await sendDcgchatMedia({ sessionKey: effectiveSessionKey, mediaUrl, text: '' })
245
273
  }
246
274
  },
247
275
  onError: (err: unknown, info: { kind: string }) => {
276
+ safeSendFinal('error')
277
+ dcgLogger(`${info.kind} reply failed: ${String(err)}`, 'error')
278
+ activeRunIdBySessionKey.delete(effectiveSessionKey)
279
+ streamChunkIdxBySessionKey.delete(effectiveSessionKey)
280
+ if (sessionStreamSuppressed.has(effectiveSessionKey)) {
281
+ dcgLogger(`${info.kind} reply failed (stream suppressed): ${String(err)}`, 'error')
282
+ return
283
+ }
248
284
  dcgLogger(`${info.kind} reply failed: ${String(err)}`, 'error')
249
285
  },
250
- onIdle: () => {
251
- sendFinal(msgCtx)
252
- }
286
+ onIdle: () => {}
253
287
  })
254
288
 
255
- let wasAborted = false
256
289
  try {
290
+ if (!interruptCommand.includes(text?.trim())) {
291
+ sessionStreamSuppressed.delete(effectiveSessionKey)
292
+ streamChunkIdxBySessionKey.set(effectiveSessionKey, 0)
293
+ }
294
+
257
295
  if (systemCommand.includes(text?.trim())) {
258
296
  dcgLogger(`dispatching /new`)
259
297
  await core.channel.reply.dispatchReplyFromConfig({
@@ -262,25 +300,63 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
262
300
  dispatcher,
263
301
  replyOptions: {
264
302
  ...replyOptions,
265
- onModelSelected: prefixContext.onModelSelected
303
+ onModelSelected: prefixContext.onModelSelected,
304
+ onAgentRunStart: (runId) => {
305
+ activeRunIdBySessionKey.set(effectiveSessionKey, runId)
306
+ }
266
307
  }
267
308
  })
268
309
  } else if (interruptCommand.includes(text?.trim())) {
269
310
  dcgLogger(`interrupt command: ${text}`)
270
- abortMobookappGeneration(conversationId)
271
- sendFinal(msgCtx)
311
+ safeSendFinal('abort')
312
+ sendText('会话已终止', outboundCtx)
313
+ sessionStreamSuppressed.add(effectiveSessionKey)
314
+ const runId = activeRunIdBySessionKey.get(effectiveSessionKey)
315
+ sendMessageToGateway(
316
+ JSON.stringify({
317
+ method: 'chat.abort',
318
+ params: {
319
+ sessionKey: effectiveSessionKey,
320
+ ...(runId ? { runId } : {})
321
+ }
322
+ })
323
+ )
324
+ if (runId) activeRunIdBySessionKey.delete(effectiveSessionKey)
325
+ safeSendFinal('stop')
272
326
  return
273
327
  } else {
274
328
  dcgLogger(`dispatching to agent (session=${route.sessionKey})`)
329
+ const params = getEffectiveMsgParams(effectiveSessionKey)
330
+ if (!ignoreToolCommand.includes(text?.trim())) {
331
+ // message_received 没有 sessionKey 前置到bot中执行
332
+ wsSendRaw(params, {
333
+ is_finish: -1,
334
+ tool_call_id: Date.now().toString(),
335
+ is_cover: 0,
336
+ thinking_content: JSON.stringify({
337
+ type: 'message_received',
338
+ specialIdentification: 'dcgchat_tool_call_special_identification',
339
+ toolName: '',
340
+ callId: Date.now().toString(),
341
+ params: ''
342
+ }),
343
+ response: ''
344
+ })
345
+ }
275
346
  await core.channel.reply.dispatchReplyFromConfig({
276
347
  ctx: ctxPayload,
277
348
  cfg: config,
278
349
  dispatcher,
279
350
  replyOptions: {
280
351
  ...replyOptions,
281
- abortSignal: genSignal,
352
+ // abortSignal: genSignal,
282
353
  onModelSelected: prefixContext.onModelSelected,
354
+ onAgentRunStart: (runId) => {
355
+ activeRunIdBySessionKey.set(effectiveSessionKey, runId)
356
+ },
283
357
  onPartialReply: async (payload: ReplyPayload) => {
358
+ if (sessionStreamSuppressed.has(effectiveSessionKey)) return
359
+
284
360
  // Accumulate full text
285
361
  if (payload.text) {
286
362
  completeText = payload.text
@@ -291,8 +367,10 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
291
367
  ? payload.text.slice(streamedTextLen)
292
368
  : payload.text
293
369
  if (delta.trim()) {
294
- sendChunk(msgCtx, delta)
295
- dcgLogger(`[stream]: chunk ${delta.length} chars to user ${msg._userId} ${delta.slice(0, 100)}`)
370
+ const prev = streamChunkIdxBySessionKey.get(effectiveSessionKey) ?? 0
371
+ streamChunkIdxBySessionKey.set(effectiveSessionKey, prev + 1)
372
+ sendChunk(delta, outboundCtx, prev)
373
+ dcgLogger(`[stream]: chunkIdx=${prev} len=${delta.length} user=${msg._userId} ${delta.slice(0, 100)}`)
296
374
  }
297
375
  streamedTextLen = payload.text.length
298
376
  }
@@ -302,46 +380,61 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
302
380
  const key = getMediaKey(mediaUrl)
303
381
  if (sentMediaKeys.has(key)) continue
304
382
  sentMediaKeys.add(key)
305
- await sendDcgchatMedia({ msgCtx, mediaUrl, text: '' })
383
+ await sendDcgchatMedia({ sessionKey: effectiveSessionKey, mediaUrl, text: '' })
306
384
  }
307
385
  }
308
386
  }
309
387
  })
310
388
  }
311
389
  } catch (err: unknown) {
312
- if (genSignal.aborted) {
313
- wasAborted = true
314
- dcgLogger(` generation aborted for conversationId=${conversationId}`)
315
- } else if (err instanceof Error && err.name === 'AbortError') {
316
- wasAborted = true
317
- dcgLogger(` generation aborted for conversationId=${conversationId}`)
318
- } else {
319
- dcgLogger(` dispatchReplyFromConfig error: ${String(err)}`, 'error')
320
- }
390
+ // if (genSignal.aborted) {
391
+ // wasAborted = true
392
+ // dcgLogger(` generation aborted for conversationId=${conversationId}`)
393
+ // } else if (err instanceof Error && err.name === 'AbortError') {
394
+ // wasAborted = true
395
+ // dcgLogger(` generation aborted for conversationId=${conversationId}`)
396
+ // } else {
397
+ // dcgLogger(` dispatchReplyFromConfig error: ${String(err)}`, 'error')
398
+ // }
321
399
  } finally {
322
- if (activeGenerations.get(conversationId) === genCtrl) {
323
- activeGenerations.delete(conversationId)
324
- }
400
+ // if (activeGenerations.get(conversationId) === genCtrl) {
401
+ // activeGenerations.delete(conversationId)
402
+ // }
325
403
  }
326
404
  try {
327
405
  markRunComplete()
406
+ markDispatchIdle()
328
407
  } catch (err) {
329
- dcgLogger(` markRunComplete error: ${String(err)}`, 'error')
408
+ dcgLogger(` markRunComplete||markRunComplete error: ${String(err)}`, 'error')
330
409
  }
331
- markDispatchIdle()
332
410
  if (![...systemCommand, ...interruptCommand].includes(text?.trim())) {
333
- for (const file of extractMobookFiles(completeText)) {
334
- let resolved = file
335
- if (!fs.existsSync(resolved)) {
336
- resolved = path.join(getWorkspaceDir(), file)
337
- if (!fs.existsSync(resolved)) return
411
+ if (sessionStreamSuppressed.has(effectiveSessionKey)) {
412
+ sessionStreamSuppressed.delete(effectiveSessionKey)
413
+ } else {
414
+ for (const file of extractMobookFiles(completeText)) {
415
+ const candidates: string[] = [file]
416
+ candidates.push(path.join(getWorkspaceDir(), file))
417
+ candidates.push(path.join(getWorkspaceDir(), file.replace(/^\//, '')))
418
+ const underMobook = file.replace(/^\/mobook\//i, '').replace(/^mobook[\\/]/i, '')
419
+ if (underMobook) {
420
+ if (process.platform === 'win32') {
421
+ candidates.push(path.join('C:\\', 'mobook', underMobook))
422
+ } else if (process.platform === 'darwin') {
423
+ candidates.push(path.join(os.homedir(), 'mobook', underMobook))
424
+ }
425
+ }
426
+ const resolved = candidates.find((p) => fs.existsSync(p))
427
+ if (!resolved) continue
428
+ try {
429
+ await sendDcgchatMedia({ sessionKey: effectiveSessionKey, mediaUrl: resolved, text: '' })
430
+ } catch (err) {
431
+ dcgLogger(` sendDcgchatMedia error: ${String(err)}`, 'error')
432
+ }
338
433
  }
339
- await sendDcgchatMedia({ msgCtx, mediaUrl: resolved, text: '' })
340
434
  }
341
435
  }
342
- sendFinal(msgCtx)
436
+ safeSendFinal('end')
343
437
  clearSentMediaKeys(msg.content.message_id)
344
- setMsgStatus('finished')
345
438
 
346
439
  // Record session metadata
347
440
  const storePath = core.channel.session.resolveStorePath(config.session?.store)
@@ -359,6 +452,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
359
452
  })
360
453
  } catch (err) {
361
454
  dcgLogger(` handle message failed: ${String(err)}`, 'error')
362
- sendError(msgCtx, err instanceof Error ? err.message : String(err))
455
+ sendError(err instanceof Error ? err.message : String(err), outboundCtx)
456
+ } finally {
457
+ safeSendFinal('finally')
363
458
  }
364
459
  }
package/src/channel.ts CHANGED
@@ -2,36 +2,41 @@ 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
4
  import { ossUpload } from './request/oss.js'
5
- import { addSentMediaKey, getMsgParams, hasSentMediaKey } from './utils/global.js'
6
- import { type DcgchatMsgContext, isWsOpen, sendFinal, wsSendRaw } from './transport.js'
5
+ import { addSentMediaKey, getCronMessageId, getOpenClawConfig, hasSentMediaKey } from './utils/global.js'
6
+ import { isWsOpen, mergeDefaultParams, mergeSessionParams, sendFinal, wsSendRaw } from './transport.js'
7
7
  import { dcgLogger, setLogger } from './utils/log.js'
8
+ import { getEffectiveMsgParams, getCurrentSessionKey } from './utils/params.js'
9
+ import { startDcgchatGatewaySocket } from './gateway/socket.js'
8
10
 
9
11
  export type DcgchatMediaSendOptions = {
10
- msgCtx: DcgchatMsgContext
12
+ /** 与 setParamsMessage / map 一致,用于 getEffectiveMsgParams */
13
+ sessionKey: string
11
14
  mediaUrl?: string
12
15
  text?: string
13
16
  }
14
17
 
15
18
  export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<void> {
16
- const { msgCtx } = opts
19
+ const msgCtx = getEffectiveMsgParams(opts.sessionKey)
17
20
  if (!isWsOpen()) {
18
21
  dcgLogger(`outbound media skipped -> ws not open: ${opts.mediaUrl ?? ''}`)
19
22
  return
20
23
  }
21
24
 
22
25
  const mediaUrl = opts.mediaUrl
23
- if (mediaUrl && hasSentMediaKey(msgCtx.messageId, mediaUrl)) {
26
+ const dedupeId = msgCtx.messageId
27
+ if (mediaUrl && dedupeId && hasSentMediaKey(dedupeId, mediaUrl)) {
24
28
  dcgLogger(`dcgchat: sendMedia skipped (duplicate in session): ${mediaUrl}`)
25
29
  return
26
30
  }
27
- if (mediaUrl) {
28
- addSentMediaKey(msgCtx.messageId, mediaUrl)
31
+ if (mediaUrl && dedupeId) {
32
+ addSentMediaKey(dedupeId, mediaUrl)
29
33
  }
30
34
 
31
35
  const fileName = mediaUrl?.split(/[\\/]/).pop() || ''
32
36
 
33
37
  try {
34
- const url = opts.mediaUrl ? await ossUpload(opts.mediaUrl, msgCtx.botToken) : ''
38
+ const botToken = msgCtx.botToken ?? getOpenClawConfig()?.channels?.["dcgchat"]?.botToken ?? ''
39
+ const url = opts.mediaUrl ? await ossUpload(opts.mediaUrl, botToken, 1) : ''
35
40
  wsSendRaw(msgCtx, {
36
41
  response: opts.text ?? '',
37
42
  files: [{ url, name: fileName }]
@@ -61,22 +66,6 @@ export function resolveAccount(cfg: OpenClawConfig, accountId?: string | null):
61
66
  }
62
67
  }
63
68
 
64
- /** Build a DcgchatMsgContext for the outbound pipeline (uses global msgParams). */
65
- function createOutboundMsgContext(cfg: OpenClawConfig, accountId?: string | null): DcgchatMsgContext {
66
- const params = getMsgParams()
67
- const { botToken } = resolveAccount(cfg, accountId)
68
- return {
69
- userId: params.userId,
70
- botToken,
71
- domainId: params.domainId,
72
- appId: params.appId,
73
- botId: params.botId,
74
- agentId: params.agentId,
75
- sessionId: params.sessionId,
76
- messageId: params.messageId
77
- }
78
- }
79
-
80
69
  export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
81
70
  id: "dcgchat",
82
71
  meta: {
@@ -153,24 +142,55 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
153
142
  deliveryMode: 'direct',
154
143
  textChunkLimit: 4000,
155
144
  sendText: async (ctx) => {
156
- const msgCtx = createOutboundMsgContext(ctx.cfg, ctx.accountId)
157
145
  if (isWsOpen()) {
158
- wsSendRaw(msgCtx, { response: ctx.text })
159
- dcgLogger(`channel sendText to ${msgCtx.userId} ${ctx.text?.slice(0, 50)}`)
146
+ // if (ctx.to.indexOf('cron:') >= 0) {
147
+ // const sessionInfo = ctx.to.split(':')[1]
148
+ // const sessionId = sessionInfo.split('-')[0]
149
+ // const agentId = sessionInfo.split('-')[1]
150
+ // const merged = mergeDefaultParams({
151
+ // agentId: agentId,
152
+ // sessionId: sessionId,
153
+ // messageId: `${Date.now()}`,
154
+ // is_finish: -1
155
+ // })
156
+ // wsSendRaw(merged, { response: ctx.text })
157
+ // } else {
158
+ // }
159
+ const outboundCtx = getEffectiveMsgParams(ctx.to)
160
+ const messageId = getCronMessageId(ctx.to)
161
+ if (outboundCtx?.sessionId) {
162
+ const newCtx = messageId ? { ...outboundCtx, messageId } : outboundCtx
163
+ wsSendRaw(newCtx, { response: ctx.text, is_finish: -1, message_tags: { source: 'channel' } })
164
+ dcgLogger(`channel sendText to ${ctx.to} ${ctx.text?.slice(0, 50)}`)
165
+ } else {
166
+ const sessionInfo = ctx.to.split(':')
167
+ const sessionId = sessionInfo.at(-1) ?? ''
168
+ const agentId = sessionInfo.at(-2) ?? ''
169
+ const merged = mergeDefaultParams({
170
+ agentId: agentId,
171
+ sessionId: `${sessionId}`,
172
+ messageId: messageId,
173
+ is_finish: -1,
174
+ real_mobook: !sessionId ? 1 : ''
175
+ })
176
+ dcgLogger(`channel sendText to ${ctx.to} ${ctx.text?.slice(0, 50)}`)
177
+ wsSendRaw(merged, { response: ctx.text })
178
+ }
160
179
  }
161
180
  return {
162
181
  channel: "dcgchat",
163
182
  messageId: `dcg-${Date.now()}`,
164
- chatId: msgCtx.userId.toString()
183
+ chatId: ctx.to
165
184
  }
166
185
  },
167
186
  sendMedia: async (ctx) => {
168
- const msgCtx = createOutboundMsgContext(ctx.cfg, ctx.accountId)
169
- await sendDcgchatMedia({ msgCtx, mediaUrl: ctx.mediaUrl })
187
+ const msgCtx = getEffectiveMsgParams(ctx.to)
188
+ dcgLogger(`channel sendMedia to ${ctx.to} ${ctx.mediaUrl?.slice(0, 50)}`)
189
+ await sendDcgchatMedia({ sessionKey: ctx.to ?? '', mediaUrl: ctx.mediaUrl ?? '' })
170
190
  return {
171
191
  channel: "dcgchat",
172
192
  messageId: `dcg-${Date.now()}`,
173
- chatId: msgCtx.userId.toString()
193
+ chatId: msgCtx.userId?.toString()
174
194
  }
175
195
  }
176
196
  },
@@ -183,6 +203,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
183
203
  dcgLogger(`dcgchat[${account.accountId}]: wsUrl not configured, skipping`, 'error')
184
204
  return
185
205
  }
206
+ startDcgchatGatewaySocket()
186
207
  return monitorDcgchatProvider({
187
208
  config: ctx.cfg,
188
209
  runtime: ctx.runtime,