@dcrays/dcgchat-test 0.3.2 → 0.3.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/dcgchat-test",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "description": "OpenClaw channel plugin for 书灵墨宝 (WebSocket)",
6
6
  "main": "index.ts",
package/src/bot.ts CHANGED
@@ -3,13 +3,20 @@ 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 { clearSentMediaKeys, getDcgchatRuntime, getOpenClawConfig, getWorkspaceDir, setMsgStatus } from './utils/global.js'
6
+ import {
7
+ clearSentMediaKeys,
8
+ getDcgchatRuntime,
9
+ getOpenClawConfig,
10
+ getSessionKey,
11
+ getWorkspaceDir,
12
+ setMsgStatus
13
+ } from './utils/global.js'
7
14
  import { resolveAccount, sendDcgchatMedia } from './channel.js'
8
15
  import { generateSignUrl } from './request/api.js'
9
16
  import { extractMobookFiles } from './utils/searchFile.js'
10
- import { sendChunk, sendFinal, sendText as sendTextMsg, sendError } from './transport.js'
17
+ import { sendChunk, sendFinal, sendText as sendTextMsg, sendError, wsSendRaw, sendText } from './transport.js'
11
18
  import { dcgLogger } from './utils/log.js'
12
- import { channelInfo, systemCommand, interruptCommand, ENV } from './utils/constant.js'
19
+ import { channelInfo, systemCommand, interruptCommand, ENV, ignoreToolCommand } from './utils/constant.js'
13
20
  import { sendMessageToGateway } from './gateway/socket.js'
14
21
  import { getEffectiveMsgParams, setParamsMessage } from './utils/params.js'
15
22
 
@@ -24,6 +31,18 @@ type TFileInfo = { name: string; url: string }
24
31
 
25
32
  const mediaMaxBytes = 300 * 1024 * 1024
26
33
 
34
+ /** 当前会话最近一次 agent run 的 runId(供 chat.abort 精确打断;无则仅传 sessionKey 仍会中止该会话全部活动运行) */
35
+ const activeRunIdBySessionKey = new Map<string, string>()
36
+
37
+ /**
38
+ * 用户在该 sessionKey 上触发打断后,旧 run 的流式/投递不再下发;与 sessionKey 一一对应,支持多会话。
39
+ * 清除时机:① 下一条非打断用户消息开始处理时;② 旧 run 收尾到 mobook 段时若仍抑制则跳过并发后删除。
40
+ */
41
+ const sessionStreamSuppressed = new Set<string>()
42
+
43
+ /** 各 sessionKey 当前轮回复的流式分片序号(仅统计实际下发的文本 chunk);每轮非打断消息开始时置 0 */
44
+ const streamChunkIdxBySessionKey = new Map<string, number>()
45
+
27
46
  /** Active LLM generation abort controllers, keyed by conversationId */
28
47
  // const activeGenerations = new Map<string, AbortController>()
29
48
 
@@ -144,8 +163,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
144
163
  const embeddedAgentId = extractAgentIdFromConversationId(conversationId)
145
164
 
146
165
  const effectiveAgentId = embeddedAgentId ?? route.agentId
147
- const effectiveSessionKey =
148
- real_mobook === '1' ? route.sessionKey : `agent:main:mobook:direct:${agentId}:${conversationId}`.toLowerCase()
166
+ const effectiveSessionKey = getSessionKey(msg.content, account.accountId)
167
+ console.log('🚀 ~ handleDcgchatMessage ~ effectiveSessionKey:', effectiveSessionKey)
149
168
 
150
169
  setParamsMessage(effectiveSessionKey, {
151
170
  userId: msg._userId,
@@ -168,6 +187,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
168
187
  if (finalSent) return
169
188
  finalSent = true
170
189
  sendFinal(outboundCtx)
190
+ setMsgStatus(effectiveSessionKey, 'finished')
171
191
  }
172
192
 
173
193
  const text = msg.content.text?.trim()
@@ -243,6 +263,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
243
263
  humanDelay: core.channel.reply.resolveHumanDelayConfig(config, route.agentId),
244
264
  onReplyStart: async () => {},
245
265
  deliver: async (payload: ReplyPayload, info) => {
266
+ if (sessionStreamSuppressed.has(effectiveSessionKey)) return
246
267
  const mediaList = resolveReplyMediaList(payload)
247
268
  for (const mediaUrl of mediaList) {
248
269
  const key = getMediaKey(mediaUrl)
@@ -252,15 +273,29 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
252
273
  }
253
274
  },
254
275
  onError: (err: unknown, info: { kind: string }) => {
276
+ activeRunIdBySessionKey.delete(effectiveSessionKey)
277
+ streamChunkIdxBySessionKey.delete(effectiveSessionKey)
278
+ if (sessionStreamSuppressed.has(effectiveSessionKey)) {
279
+ dcgLogger(`${info.kind} reply failed (stream suppressed): ${String(err)}`, 'error')
280
+ return
281
+ }
255
282
  safeSendFinal()
256
283
  dcgLogger(`${info.kind} reply failed: ${String(err)}`, 'error')
257
284
  },
258
285
  onIdle: () => {
286
+ activeRunIdBySessionKey.delete(effectiveSessionKey)
287
+ streamChunkIdxBySessionKey.delete(effectiveSessionKey)
288
+ if (sessionStreamSuppressed.has(effectiveSessionKey)) return
259
289
  safeSendFinal()
260
290
  }
261
291
  })
262
292
 
263
293
  try {
294
+ if (!interruptCommand.includes(text?.trim())) {
295
+ sessionStreamSuppressed.delete(effectiveSessionKey)
296
+ streamChunkIdxBySessionKey.set(effectiveSessionKey, 0)
297
+ }
298
+
264
299
  if (systemCommand.includes(text?.trim())) {
265
300
  dcgLogger(`dispatching /new`)
266
301
  await core.channel.reply.dispatchReplyFromConfig({
@@ -269,21 +304,48 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
269
304
  dispatcher,
270
305
  replyOptions: {
271
306
  ...replyOptions,
272
- onModelSelected: prefixContext.onModelSelected
307
+ onModelSelected: prefixContext.onModelSelected,
308
+ onAgentRunStart: (runId) => {
309
+ activeRunIdBySessionKey.set(effectiveSessionKey, runId)
310
+ }
273
311
  }
274
312
  })
275
313
  } else if (interruptCommand.includes(text?.trim())) {
276
314
  dcgLogger(`interrupt command: ${text}`)
315
+ sendText('会话已终止', outboundCtx)
316
+ safeSendFinal()
317
+ sessionStreamSuppressed.add(effectiveSessionKey)
318
+ const runId = activeRunIdBySessionKey.get(effectiveSessionKey)
277
319
  sendMessageToGateway(
278
320
  JSON.stringify({
279
321
  method: 'chat.abort',
280
- params: { sessionKey: effectiveSessionKey }
322
+ params: {
323
+ sessionKey: effectiveSessionKey,
324
+ ...(runId ? { runId } : {})
325
+ }
281
326
  })
282
327
  )
283
- safeSendFinal()
328
+ if (runId) activeRunIdBySessionKey.delete(effectiveSessionKey)
284
329
  return
285
330
  } else {
286
331
  dcgLogger(`dispatching to agent (session=${route.sessionKey})`)
332
+ const params = getEffectiveMsgParams(effectiveSessionKey)
333
+ if (!ignoreToolCommand.includes(text?.trim())) {
334
+ // message_received 没有 sessionKey 前置到bot中执行
335
+ wsSendRaw(params, {
336
+ is_finish: -1,
337
+ tool_call_id: Date.now().toString(),
338
+ is_cover: 0,
339
+ thinking_content: JSON.stringify({
340
+ type: 'message_received',
341
+ specialIdentification: 'dcgchat_tool_call_special_identification',
342
+ toolName: '',
343
+ callId: Date.now().toString(),
344
+ params: ''
345
+ }),
346
+ response: ''
347
+ })
348
+ }
287
349
  await core.channel.reply.dispatchReplyFromConfig({
288
350
  ctx: ctxPayload,
289
351
  cfg: config,
@@ -292,7 +354,15 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
292
354
  ...replyOptions,
293
355
  // abortSignal: genSignal,
294
356
  onModelSelected: prefixContext.onModelSelected,
357
+ onAgentRunStart: (runId) => {
358
+ activeRunIdBySessionKey.set(effectiveSessionKey, runId)
359
+ },
295
360
  onPartialReply: async (payload: ReplyPayload) => {
361
+ if (sessionStreamSuppressed.has(effectiveSessionKey)) return
362
+ console.log(
363
+ '🚀 ~ handleDcgchatMessage ~ sessionStreamSuppressed.has(effectiveSessionKey):',
364
+ sessionStreamSuppressed.has(effectiveSessionKey)
365
+ )
296
366
  // Accumulate full text
297
367
  if (payload.text) {
298
368
  completeText = payload.text
@@ -303,8 +373,10 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
303
373
  ? payload.text.slice(streamedTextLen)
304
374
  : payload.text
305
375
  if (delta.trim()) {
306
- sendChunk(delta, outboundCtx)
307
- dcgLogger(`[stream]: chunk ${delta.length} chars to user ${msg._userId} ${delta.slice(0, 100)}`)
376
+ const prev = streamChunkIdxBySessionKey.get(effectiveSessionKey) ?? 0
377
+ streamChunkIdxBySessionKey.set(effectiveSessionKey, prev + 1)
378
+ sendChunk(delta, outboundCtx, prev)
379
+ dcgLogger(`[stream]: chunkIdx=${prev} len=${delta.length} user=${msg._userId} ${delta.slice(0, 100)}`)
308
380
  }
309
381
  streamedTextLen = payload.text.length
310
382
  }
@@ -342,28 +414,31 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
342
414
  dcgLogger(` markRunComplete||markRunComplete error: ${String(err)}`, 'error')
343
415
  }
344
416
  if (![...systemCommand, ...interruptCommand].includes(text?.trim())) {
345
- for (const file of extractMobookFiles(completeText)) {
346
- const candidates: string[] = [file]
347
- candidates.push(path.join(getWorkspaceDir(), file))
348
- candidates.push(path.join(getWorkspaceDir(), file.replace(/^\//, '')))
349
- if (process.platform === 'win32') {
350
- const underMobook = file.replace(/^\/mobook\//i, '').replace(/^mobook[\\/]/i, '')
351
- if (underMobook) {
352
- candidates.push(path.join('C:\\', 'mobook', underMobook))
417
+ if (sessionStreamSuppressed.has(effectiveSessionKey)) {
418
+ sessionStreamSuppressed.delete(effectiveSessionKey)
419
+ } else {
420
+ for (const file of extractMobookFiles(completeText)) {
421
+ const candidates: string[] = [file]
422
+ candidates.push(path.join(getWorkspaceDir(), file))
423
+ candidates.push(path.join(getWorkspaceDir(), file.replace(/^\//, '')))
424
+ if (process.platform === 'win32') {
425
+ const underMobook = file.replace(/^\/mobook\//i, '').replace(/^mobook[\\/]/i, '')
426
+ if (underMobook) {
427
+ candidates.push(path.join('C:\\', 'mobook', underMobook))
428
+ }
429
+ }
430
+ const resolved = candidates.find((p) => fs.existsSync(p))
431
+ if (!resolved) continue
432
+ try {
433
+ await sendDcgchatMedia({ sessionKey: effectiveSessionKey, mediaUrl: resolved, text: '' })
434
+ } catch (err) {
435
+ dcgLogger(` sendDcgchatMedia error: ${String(err)}`, 'error')
353
436
  }
354
- }
355
- const resolved = candidates.find((p) => fs.existsSync(p))
356
- if (!resolved) continue
357
- try {
358
- await sendDcgchatMedia({ sessionKey: effectiveSessionKey, mediaUrl: resolved, text: '' })
359
- } catch (err) {
360
- dcgLogger(` sendDcgchatMedia error: ${String(err)}`, 'error')
361
437
  }
362
438
  }
363
439
  }
364
440
  safeSendFinal()
365
441
  clearSentMediaKeys(msg.content.message_id)
366
- setMsgStatus('finished')
367
442
 
368
443
  // Record session metadata
369
444
  const storePath = core.channel.session.resolveStorePath(config.session?.store)
package/src/channel.ts CHANGED
@@ -5,7 +5,7 @@ import { ossUpload } from './request/oss.js'
5
5
  import { addSentMediaKey, getOpenClawConfig, hasSentMediaKey } from './utils/global.js'
6
6
  import { isWsOpen, mergeDefaultParams, mergeSessionParams, sendFinal, wsSendRaw } from './transport.js'
7
7
  import { dcgLogger, setLogger } from './utils/log.js'
8
- import { getParamsDefaults, getEffectiveMsgParams, getCurrentSessionKey } from './utils/params.js'
8
+ import { getEffectiveMsgParams, getCurrentSessionKey } from './utils/params.js'
9
9
  import { startDcgchatGatewaySocket } from './gateway/socket.js'
10
10
 
11
11
  export type DcgchatMediaSendOptions = {
@@ -146,7 +146,8 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
146
146
  const merged = mergeDefaultParams({
147
147
  agentId: ctx.accountId ?? '',
148
148
  sessionId: ctx.to,
149
- messageId: `${Date.now()}`
149
+ messageId: `${Date.now()}`,
150
+ is_finish: -1
150
151
  })
151
152
  wsSendRaw(merged, { response: ctx.text })
152
153
  sendFinal(merged)
@@ -230,10 +230,8 @@ export function startDcgchatGatewaySocket(): void {
230
230
  socketStopped = false
231
231
  clearReconnectTimer()
232
232
  if (startupConnectTimer != null) return
233
- startupConnectTimer = setTimeout(() => {
234
- startupConnectTimer = null
235
- void connectPersistentGateway()
236
- }, 10000)
233
+ startupConnectTimer = null
234
+ void connectPersistentGateway()
237
235
  }
238
236
 
239
237
  /**
package/src/monitor.ts CHANGED
@@ -2,12 +2,12 @@ import type { ClawdbotConfig, RuntimeEnv } from 'openclaw/plugin-sdk'
2
2
  import WebSocket from 'ws'
3
3
  import { handleDcgchatMessage } from './bot.js'
4
4
  import { resolveAccount } from './channel.js'
5
- import { setWsConnection, getOpenClawConfig, setMsgStatus } from './utils/global.js'
5
+ import { setWsConnection, getOpenClawConfig, setMsgStatus, getSessionKey } from './utils/global.js'
6
6
  import type { InboundMessage } from './types.js'
7
7
  import { installSkill, uninstallSkill } from './skill.js'
8
8
  import { dcgLogger } from './utils/log.js'
9
- import { ignoreToolCommand } from './utils/constant.js'
10
9
  import { onDisabledCronJob, onEnabledCronJob, onRemoveCronJob, onRunCronJob } from './cron.js'
10
+ import { ignoreToolCommand } from './utils/constant.js'
11
11
 
12
12
  export type MonitorDcgchatOpts = {
13
13
  config?: ClawdbotConfig
@@ -126,10 +126,14 @@ export async function monitorDcgchatProvider(opts: MonitorDcgchatOpts): Promise<
126
126
 
127
127
  if (parsed.messageType == 'openclaw_bot_chat') {
128
128
  const msg = parsed as unknown as InboundMessage
129
- if (!ignoreToolCommand.includes(msg.content.text?.trim())) {
130
- setMsgStatus('running')
129
+ // monitor 原逻辑一致:工具类指令不进入 running,避免误触工具链监控
130
+ const effectiveSessionKey = getSessionKey(msg.content, account.accountId)
131
+ console.log('🚀 ~ connect ~ effectiveSessionKey:', effectiveSessionKey)
132
+ if (!ignoreToolCommand.includes(msg.content.text?.trim() ?? '')) {
133
+ setMsgStatus(effectiveSessionKey, 'running')
134
+ } else {
135
+ setMsgStatus(effectiveSessionKey, 'finished')
131
136
  }
132
-
133
137
  await handleDcgchatMessage(msg, account.accountId)
134
138
  } else if (parsed.messageType == 'openclaw_bot_event') {
135
139
  const { event_type, operation_type } = parsed.content ? parsed.content : ({} as Record<string, any>)
package/src/tool.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
2
- import { getMsgStatus, getWsConnection } from './utils/global.js'
2
+ import { getMsgStatus } from './utils/global.js'
3
3
  import { dcgLogger } from './utils/log.js'
4
- import { isWsOpen, sendFinal, sendText } from './transport.js'
4
+ import { sendFinal, sendText, wsSendRaw } from './transport.js'
5
5
  import { getCurrentSessionKey, getEffectiveMsgParams } from './utils/params.js'
6
6
 
7
7
  let toolCallId = ''
@@ -31,8 +31,10 @@ type PluginHookName =
31
31
  | 'subagent_ended'
32
32
  | 'gateway_start'
33
33
  | 'gateway_stop'
34
+
35
+ // message_received 没有 sessionKey 前置到bot中执行
34
36
  const eventList = [
35
- { event: 'message_received', message: '' },
37
+ // { event: 'message_received', message: '' },
36
38
  // {event: 'before_model_resolve', message: ''},
37
39
  // {event: 'before_prompt_build', message: '正在查阅背景资料,构建思考逻辑'},
38
40
  // {event: 'before_agent_start', message: '书灵墨宝已就位,准备开始执行任务'},
@@ -50,75 +52,63 @@ const eventList = [
50
52
  { event: 'after_tool_call', message: '' }
51
53
  ]
52
54
 
53
- function sendToolCallMessage(text: string, toolCallId: string, isCover: number) {
54
- const ws = getWsConnection()
55
- const sk = getCurrentSessionKey()
56
- if (!sk) return
55
+ function sendToolCallMessage(sk: string, text: string, toolCallId: string, isCover: number) {
57
56
  const params = getEffectiveMsgParams(sk)
58
- if (isWsOpen()) {
59
- ws?.send(
60
- JSON.stringify({
61
- messageType: 'openclaw_bot_chat',
62
- _userId: params?.userId,
63
- source: 'client',
64
- is_finish: -1,
65
- content: {
66
- is_finish: -1,
67
- bot_token: params?.botToken,
68
- domain_id: params?.domainId,
69
- app_id: params?.appId,
70
- bot_id: params?.botId,
71
- agent_id: params?.agentId,
72
- tool_call_id: toolCallId,
73
- is_cover: isCover,
74
- thinking_content: text,
75
- response: '',
76
- session_id: params?.sessionId,
77
- message_id: params?.messageId || Date.now().toString()
78
- }
79
- })
80
- )
81
- }
57
+ wsSendRaw(params, {
58
+ is_finish: -1,
59
+ tool_call_id: toolCallId,
60
+ is_cover: isCover,
61
+ thinking_content: text,
62
+ response: ''
63
+ })
82
64
  }
83
65
 
84
66
  export function monitoringToolMessage(api: OpenClawPluginApi) {
85
67
  for (const item of eventList) {
86
68
  api.on(item.event as PluginHookName, (event: any, args: any) => {
87
- const status = getMsgStatus()
88
- if (status === 'running') {
89
- if (['after_tool_call', 'before_tool_call'].includes(item.event)) {
90
- const { result: _result, ...rest } = event
91
- dcgLogger(`工具调用结果: ~ event:${item.event} ~ params:${JSON.stringify(rest)}`)
92
- const text = JSON.stringify({
93
- type: item.event,
94
- specialIdentification: 'dcgchat_tool_call_special_identification',
95
- callId: event.toolCallId || event.runId || Date.now().toString(),
96
- ...rest,
97
- status: item.event === 'after_tool_call' ? 'finished' : 'running'
98
- })
99
- sendToolCallMessage(text, event.toolCallId || event.runId || Date.now().toString(), item.event === 'after_tool_call' ? 1 : 0)
100
- } else if (item.event) {
101
- dcgLogger(`工具调用结果: ~ event:${item.event}`)
102
- if (item.event === 'llm_output') {
103
- if (event.lastAssistant?.errorMessage === '429-账户额度耗尽') {
104
- const message = '您的积分已消耗完,您可以通过充值积分来继续使用'
105
- const sk = ((args?.sessionKey as string | undefined) ?? getCurrentSessionKey()) || ''
106
- if (!sk) return
107
- const msgCtx = getEffectiveMsgParams(sk)
108
- sendText(message, msgCtx, { message_tags: { insufficient_balance: 1 }, is_finish: -1 })
109
- sendFinal(msgCtx)
110
- return
69
+ const sk = args?.sessionKey as string
70
+ if (sk) {
71
+ const status = getMsgStatus(sk)
72
+ if (status === 'running') {
73
+ if (['after_tool_call', 'before_tool_call'].includes(item.event)) {
74
+ const { result: _result, ...rest } = event
75
+ dcgLogger(`工具调用结果: ~ event:${item.event} ~ params:${JSON.stringify(rest)}`)
76
+ const text = JSON.stringify({
77
+ type: item.event,
78
+ specialIdentification: 'dcgchat_tool_call_special_identification',
79
+ callId: event.toolCallId || event.runId || Date.now().toString(),
80
+ ...rest,
81
+ status: item.event === 'after_tool_call' ? 'finished' : 'running'
82
+ })
83
+ sendToolCallMessage(
84
+ sk,
85
+ text,
86
+ event.toolCallId || event.runId || Date.now().toString(),
87
+ item.event === 'after_tool_call' ? 1 : 0
88
+ )
89
+ } else if (item.event) {
90
+ const msgCtx = getEffectiveMsgParams(sk)
91
+ if (item.event === 'llm_output') {
92
+ if (event.lastAssistant?.errorMessage === '429-账户额度耗尽') {
93
+ const message = '您的积分已消耗完,您可以通过充值积分来继续使用'
94
+ sendText(message, msgCtx, { message_tags: { insufficient_balance: 1 }, is_finish: -1 })
95
+ sendFinal(msgCtx)
96
+ return
97
+ }
111
98
  }
99
+ const text = JSON.stringify({
100
+ type: item.event,
101
+ specialIdentification: 'dcgchat_tool_call_special_identification',
102
+ toolName: '',
103
+ callId: event.runId || Date.now().toString(),
104
+ params: item.message
105
+ })
106
+ sendToolCallMessage(sk, text, event.runId || Date.now().toString(), 0)
107
+ dcgLogger(`工具调用结果: ~ event:${item.event} ${status}`)
112
108
  }
113
- const text = JSON.stringify({
114
- type: item.event,
115
- specialIdentification: 'dcgchat_tool_call_special_identification',
116
- toolName: '',
117
- callId: event.runId || Date.now().toString(),
118
- params: item.message
119
- })
120
- sendToolCallMessage(text, event.runId || Date.now().toString(), 0)
121
109
  }
110
+ } else {
111
+ dcgLogger(`工具调用结果: ~ event:${item.event} ~ 没有sessionKey 为执行`)
122
112
  }
123
113
  })
124
114
  }
package/src/transport.ts CHANGED
@@ -158,8 +158,8 @@ export function wsSendRaw(ctx: IMsgParams, content: Record<string, unknown>): bo
158
158
  return true
159
159
  }
160
160
 
161
- export function sendChunk(text: string, ctx: IMsgParams): boolean {
162
- return wsSend(ctx, { response: text, state: 'chunk' })
161
+ export function sendChunk(text: string, ctx: IMsgParams, chunkIdx: number): boolean {
162
+ return wsSend(ctx, { response: text, state: 'chunk', chunk_idx: chunkIdx })
163
163
  }
164
164
 
165
165
  export function sendFinal(ctx: IMsgParams): boolean {
package/src/types.ts CHANGED
@@ -127,4 +127,5 @@ export interface IMsgParams {
127
127
  /** 与 OpenClaw 路由一致,用于 map 与异步链路(工具 / HTTP / cron)对齐当前会话 */
128
128
  sessionKey?: string
129
129
  real_mobook?: string | number
130
+ is_finish?: number
130
131
  }
@@ -2,6 +2,6 @@ export const ENV: 'production' | 'test' | 'develop' = 'test'
2
2
 
3
3
 
4
4
  export const systemCommand = ['/new', '/status']
5
- export const interruptCommand = ['/stop']
5
+ export const interruptCommand = ['chat.stop']
6
6
 
7
- export const ignoreToolCommand = ['/search', '/abort', '/queue interrupt', ...systemCommand, ...interruptCommand]
7
+ export const ignoreToolCommand = ['/search', '/abort', '/stop', '/queue interrupt', ...systemCommand, ...interruptCommand]
@@ -68,12 +68,22 @@ export function getDcgchatRuntime(): PluginRuntime {
68
68
  return runtime as PluginRuntime
69
69
  }
70
70
 
71
- let msgStatus: 'running' | 'finished' | '' = ''
72
- export function setMsgStatus(status: 'running' | 'finished' | '') {
73
- msgStatus = status
71
+ export type MsgSessionStatus = 'running' | 'finished' | ''
72
+
73
+ const msgStatusBySessionKey = new Map<string, MsgSessionStatus>()
74
+
75
+ export function setMsgStatus(sessionKey: string, status: MsgSessionStatus) {
76
+ if (!sessionKey?.trim()) return
77
+ if (status === '') {
78
+ msgStatusBySessionKey.delete(sessionKey)
79
+ } else {
80
+ msgStatusBySessionKey.set(sessionKey, status)
81
+ }
74
82
  }
75
- export function getMsgStatus() {
76
- return msgStatus
83
+
84
+ export function getMsgStatus(sessionKey: string): MsgSessionStatus {
85
+ if (!sessionKey?.trim()) return ''
86
+ return msgStatusBySessionKey.get(sessionKey) ?? ''
77
87
  }
78
88
 
79
89
  const getMediaKey = (url: string) => url.split(/[\\/]/).pop() ?? url
@@ -106,3 +116,16 @@ export function clearSentMediaKeys(messageId?: string) {
106
116
  sentMediaKeysBySession.clear()
107
117
  }
108
118
  }
119
+
120
+ export const getSessionKey = (content: any, accountId: string) => {
121
+ const { real_mobook, agent_id, conversation_id, session_id } = content
122
+ const core = getDcgchatRuntime()
123
+
124
+ const route = core.channel.routing.resolveAgentRoute({
125
+ cfg: getOpenClawConfig() as OpenClawConfig,
126
+ channel: "dcgchat-test",
127
+ accountId: accountId || 'default',
128
+ peer: { kind: 'direct', id: session_id }
129
+ })
130
+ return real_mobook === '1' ? route.sessionKey : `agent:main:mobook:direct:${agent_id}:${session_id}`.toLowerCase()
131
+ }