@dcrays/dcgchat-test 0.3.36 → 0.3.37
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 +1 -1
- package/src/bot.ts +129 -124
- package/src/channel.ts +15 -6
- package/src/gateway/index.ts +2 -26
- package/src/tool.ts +236 -2
- package/src/utils/constant.ts +2 -2
- package/src/utils/gatewayMsgHanlder.ts +43 -0
- package/src/utils/global.ts +5 -13
- package/src/utils/params.ts +18 -0
package/package.json
CHANGED
package/src/bot.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import os from 'node:os'
|
|
4
|
-
import type { ReplyPayload } from 'openclaw/plugin-sdk'
|
|
5
|
-
import { createReplyPrefixContext } from 'openclaw/plugin-sdk'
|
|
4
|
+
import type { PluginRuntime, ReplyPayload } from 'openclaw/plugin-sdk'
|
|
5
|
+
import { createPluginRuntimeStore, createReplyPrefixContext, createTypingCallbacks } from 'openclaw/plugin-sdk'
|
|
6
6
|
import type { InboundMessage } from './types.js'
|
|
7
7
|
import {
|
|
8
8
|
clearSentMediaKeys,
|
|
@@ -18,8 +18,9 @@ import { extractMobookFiles } from './utils/searchFile.js'
|
|
|
18
18
|
import { sendChunk, sendFinal, sendText as sendTextMsg, sendError, wsSendRaw, sendText } from './transport.js'
|
|
19
19
|
import { dcgLogger } from './utils/log.js'
|
|
20
20
|
import { channelInfo, systemCommand, interruptCommand, ENV, ignoreToolCommand } from './utils/constant.js'
|
|
21
|
-
import {
|
|
22
|
-
import { getEffectiveMsgParams, setParamsMessage } from './utils/params.js'
|
|
21
|
+
import { sendGatewayRpc } from './gateway/socket.js'
|
|
22
|
+
import { clearParamsMessage, getEffectiveMsgParams, setParamsMessage } from './utils/params.js'
|
|
23
|
+
import { getChildSessionKeysTrackedForRequester, resetSubagentStateForRequesterSession, waitUntilSubagentsIdle } from './tool.js'
|
|
23
24
|
|
|
24
25
|
type MediaInfo = {
|
|
25
26
|
path: string
|
|
@@ -44,13 +45,6 @@ const sessionStreamSuppressed = new Set<string>()
|
|
|
44
45
|
/** 各 sessionKey 当前轮回复的流式分片序号(仅统计实际下发的文本 chunk);每轮非打断消息开始时置 0 */
|
|
45
46
|
const streamChunkIdxBySessionKey = new Map<string, number>()
|
|
46
47
|
|
|
47
|
-
/** Active LLM generation abort controllers, keyed by conversationId */
|
|
48
|
-
// const activeGenerations = new Map<string, AbortController>()
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Extract agentId from conversation_id formatted as "agentId::suffix".
|
|
52
|
-
* Returns null if the conversation_id does not contain the "::" separator.
|
|
53
|
-
*/
|
|
54
48
|
export function extractAgentIdFromConversationId(conversationId: string): string | null {
|
|
55
49
|
const idx = conversationId.indexOf('::')
|
|
56
50
|
if (idx <= 0) return null
|
|
@@ -132,7 +126,14 @@ function resolveReplyMediaList(payload: ReplyPayload): string[] {
|
|
|
132
126
|
if (payload.mediaUrls?.length) return payload.mediaUrls.filter(Boolean)
|
|
133
127
|
return payload.mediaUrl ? [payload.mediaUrl] : []
|
|
134
128
|
}
|
|
135
|
-
|
|
129
|
+
const typingCallbacks = createTypingCallbacks({
|
|
130
|
+
start: async () => {
|
|
131
|
+
console.log('typing start')
|
|
132
|
+
},
|
|
133
|
+
onStartError: (err) => {
|
|
134
|
+
console.log('typing start error', err)
|
|
135
|
+
}
|
|
136
|
+
})
|
|
136
137
|
/**
|
|
137
138
|
* 处理一条用户消息,调用 Agent 并返回回复
|
|
138
139
|
*/
|
|
@@ -178,10 +179,6 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
178
179
|
real_mobook
|
|
179
180
|
}
|
|
180
181
|
setParamsMessage(effectiveSessionKey, mergedParams)
|
|
181
|
-
// 与 OpenClaw 会话投递里仍可能出现的 ctx.to=SenderId(userId)对齐,便于 getOutboundMsgParams 命中
|
|
182
|
-
dcgLogger(
|
|
183
|
-
`target normalize: rawTarget=${userId}, normalizedTarget=${effectiveSessionKey}, conversationId=${conversationId ?? ''}, messageId=${msg.content.message_id}`
|
|
184
|
-
)
|
|
185
182
|
setParamsMessage(userId, mergedParams)
|
|
186
183
|
dcgLogger(`target alias bound: aliasTarget=${userId} -> sessionKey=${effectiveSessionKey}`)
|
|
187
184
|
const outboundCtx = getEffectiveMsgParams(effectiveSessionKey)
|
|
@@ -189,29 +186,15 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
189
186
|
effectiveAgentId && effectiveAgentId !== 'main' ? config.agents?.list?.find((a) => a.id === effectiveAgentId) : undefined
|
|
190
187
|
const agentDisplayName = agentEntry?.name ?? (effectiveAgentId && effectiveAgentId !== 'main' ? effectiveAgentId : undefined)
|
|
191
188
|
|
|
192
|
-
const safeSendFinal = (tag: string) => {
|
|
193
|
-
if (finalSent) return
|
|
194
|
-
finalSent = true
|
|
195
|
-
sendFinal(outboundCtx, tag)
|
|
196
|
-
setMsgStatus(effectiveSessionKey, 'finished')
|
|
197
|
-
}
|
|
198
|
-
|
|
199
189
|
const text = msg.content.text?.trim()
|
|
200
190
|
|
|
201
191
|
if (!text) {
|
|
202
192
|
sendTextMsg('你需要我帮你做什么呢?', outboundCtx)
|
|
203
|
-
|
|
193
|
+
sendFinal(outboundCtx, 'not text')
|
|
204
194
|
return
|
|
205
195
|
}
|
|
206
196
|
|
|
207
197
|
try {
|
|
208
|
-
// Abort any existing generation for this conversation, then start a new one
|
|
209
|
-
// const existingCtrl = activeGenerations.get(conversationId)
|
|
210
|
-
// if (existingCtrl) existingCtrl.abort()
|
|
211
|
-
// const genCtrl = new AbortController()
|
|
212
|
-
// const genSignal = genCtrl.signal
|
|
213
|
-
// activeGenerations.set(conversationId, genCtrl)
|
|
214
|
-
|
|
215
198
|
// 处理用户上传的文件
|
|
216
199
|
const files = msg.content.files ?? []
|
|
217
200
|
let mediaPayload: Record<string, unknown> = {}
|
|
@@ -249,11 +232,10 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
249
232
|
CommandAuthorized: true,
|
|
250
233
|
OriginatingChannel: "dcgchat-test",
|
|
251
234
|
OriginatingTo: effectiveSessionKey,
|
|
235
|
+
Target: effectiveSessionKey,
|
|
236
|
+
SourceTarget: effectiveSessionKey,
|
|
252
237
|
...mediaPayload
|
|
253
238
|
})
|
|
254
|
-
dcgLogger(
|
|
255
|
-
`inbound context target: rawTarget=${userId}, normalizedTarget=${effectiveSessionKey}, ctx.To=${String(ctxPayload.To ?? '')}, ctx.SessionKey=${String(ctxPayload.SessionKey ?? '')}, ctx.OriginatingTo=${String(ctxPayload.OriginatingTo ?? '')}`
|
|
256
|
-
)
|
|
257
239
|
|
|
258
240
|
const sentMediaKeys = new Set<string>()
|
|
259
241
|
const getMediaKey = (url: string) => url.split(/[\\/]/).pop() ?? url
|
|
@@ -266,7 +248,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
266
248
|
accountId: account.accountId
|
|
267
249
|
})
|
|
268
250
|
|
|
269
|
-
const { dispatcher, replyOptions, markDispatchIdle
|
|
251
|
+
const { dispatcher, replyOptions, markDispatchIdle } = core.channel.reply.createReplyDispatcherWithTyping({
|
|
270
252
|
responsePrefix: prefixContext.responsePrefix,
|
|
271
253
|
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
|
272
254
|
humanDelay: core.channel.reply.resolveHumanDelayConfig(config, route.agentId),
|
|
@@ -282,7 +264,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
282
264
|
}
|
|
283
265
|
},
|
|
284
266
|
onError: (err: unknown, info: { kind: string }) => {
|
|
285
|
-
|
|
267
|
+
setMsgStatus(effectiveSessionKey, 'finished')
|
|
268
|
+
sendFinal(outboundCtx, 'error')
|
|
286
269
|
dcgLogger(`${info.kind} reply failed: ${String(err)}`, 'error')
|
|
287
270
|
activeRunIdBySessionKey.delete(effectiveSessionKey)
|
|
288
271
|
streamChunkIdxBySessionKey.delete(effectiveSessionKey)
|
|
@@ -292,7 +275,9 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
292
275
|
}
|
|
293
276
|
dcgLogger(`${info.kind} reply failed: ${String(err)}`, 'error')
|
|
294
277
|
},
|
|
295
|
-
onIdle: () => {
|
|
278
|
+
onIdle: () => {
|
|
279
|
+
typingCallbacks.onIdle?.()
|
|
280
|
+
}
|
|
296
281
|
})
|
|
297
282
|
|
|
298
283
|
try {
|
|
@@ -302,42 +287,78 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
302
287
|
}
|
|
303
288
|
|
|
304
289
|
if (systemCommand.includes(text?.trim())) {
|
|
305
|
-
dcgLogger(`dispatching
|
|
306
|
-
await core.channel.reply.
|
|
307
|
-
ctx: ctxPayload,
|
|
308
|
-
cfg: config,
|
|
290
|
+
dcgLogger(`dispatching ${text?.trim()}`)
|
|
291
|
+
await core.channel.reply.withReplyDispatcher({
|
|
309
292
|
dispatcher,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
293
|
+
onSettled: () => markDispatchIdle(),
|
|
294
|
+
run: () =>
|
|
295
|
+
core.channel.reply.dispatchReplyFromConfig({
|
|
296
|
+
ctx: ctxPayload,
|
|
297
|
+
cfg: config,
|
|
298
|
+
dispatcher,
|
|
299
|
+
replyOptions: {
|
|
300
|
+
...replyOptions,
|
|
301
|
+
onModelSelected: prefixContext.onModelSelected,
|
|
302
|
+
onAgentRunStart: (runId) => {
|
|
303
|
+
activeRunIdBySessionKey.set(effectiveSessionKey, runId)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
})
|
|
317
307
|
})
|
|
318
308
|
} else if (interruptCommand.includes(text?.trim())) {
|
|
319
309
|
dcgLogger(`interrupt command: ${text}`)
|
|
320
310
|
sendFinal(outboundCtx, 'abort')
|
|
321
311
|
sendText('会话已终止', outboundCtx)
|
|
322
312
|
sessionStreamSuppressed.add(effectiveSessionKey)
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
method: 'chat.abort',
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
313
|
+
|
|
314
|
+
const abortOneSession = async (sessionKey: string) => {
|
|
315
|
+
try {
|
|
316
|
+
await sendGatewayRpc({ method: 'chat.abort', params: { sessionKey } })
|
|
317
|
+
} catch (e) {
|
|
318
|
+
dcgLogger(`chat.abort ${sessionKey}: ${String(e)}`, 'error')
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const keysToAbort = new Set<string>(getChildSessionKeysTrackedForRequester(effectiveSessionKey))
|
|
323
|
+
try {
|
|
324
|
+
const listed = await sendGatewayRpc<{ sessions?: Array<{ key?: string }> }>({
|
|
325
|
+
method: 'sessions.list',
|
|
326
|
+
params: { spawnedBy: effectiveSessionKey, limit: 256 }
|
|
327
|
+
})
|
|
328
|
+
for (const s of listed?.sessions ?? []) {
|
|
329
|
+
const k = typeof s?.key === 'string' ? s.key.trim() : ''
|
|
330
|
+
if (k) keysToAbort.add(k)
|
|
331
|
+
}
|
|
332
|
+
} catch (e) {
|
|
333
|
+
dcgLogger(`sessions.list spawnedBy: ${String(e)}`, 'error')
|
|
334
|
+
}
|
|
335
|
+
for (const sk of keysToAbort) {
|
|
336
|
+
await abortOneSession(sk)
|
|
337
|
+
}
|
|
338
|
+
await abortOneSession(effectiveSessionKey)
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
await sendGatewayRpc({
|
|
342
|
+
method: 'sessions.reset',
|
|
343
|
+
params: { key: effectiveSessionKey, reason: 'reset' }
|
|
331
344
|
})
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
|
|
345
|
+
} catch (e) {
|
|
346
|
+
dcgLogger(`sessions.reset: ${String(e)}`, 'error')
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
activeRunIdBySessionKey.delete(effectiveSessionKey)
|
|
350
|
+
streamChunkIdxBySessionKey.delete(effectiveSessionKey)
|
|
351
|
+
resetSubagentStateForRequesterSession(effectiveSessionKey)
|
|
352
|
+
setMsgStatus(effectiveSessionKey, 'finished')
|
|
353
|
+
clearSentMediaKeys(msg.content.message_id)
|
|
354
|
+
clearParamsMessage(effectiveSessionKey)
|
|
355
|
+
clearParamsMessage(userId)
|
|
356
|
+
|
|
357
|
+
sendFinal(outboundCtx, 'stop')
|
|
335
358
|
return
|
|
336
359
|
} else {
|
|
337
|
-
dcgLogger(`dispatching to agent (session=${route.sessionKey})`)
|
|
338
360
|
const params = getEffectiveMsgParams(effectiveSessionKey)
|
|
339
361
|
if (!ignoreToolCommand.includes(text?.trim())) {
|
|
340
|
-
// message_received 没有 sessionKey 前置到bot中执行
|
|
341
362
|
wsSendRaw(params, {
|
|
342
363
|
is_finish: -1,
|
|
343
364
|
tool_call_id: Date.now().toString(),
|
|
@@ -352,70 +373,58 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
352
373
|
response: ''
|
|
353
374
|
})
|
|
354
375
|
}
|
|
355
|
-
await core.channel.reply.
|
|
356
|
-
ctx: ctxPayload,
|
|
357
|
-
cfg: config,
|
|
376
|
+
await core.channel.reply.withReplyDispatcher({
|
|
358
377
|
dispatcher,
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
378
|
+
onSettled: () => markDispatchIdle(),
|
|
379
|
+
run: () =>
|
|
380
|
+
core.channel.reply.dispatchReplyFromConfig({
|
|
381
|
+
ctx: ctxPayload,
|
|
382
|
+
cfg: config,
|
|
383
|
+
dispatcher,
|
|
384
|
+
replyOptions: {
|
|
385
|
+
...replyOptions,
|
|
386
|
+
onModelSelected: prefixContext.onModelSelected,
|
|
387
|
+
onAgentRunStart: (runId) => {
|
|
388
|
+
activeRunIdBySessionKey.set(effectiveSessionKey, runId)
|
|
389
|
+
},
|
|
390
|
+
onPartialReply: async (payload: ReplyPayload) => {
|
|
391
|
+
if (sessionStreamSuppressed.has(effectiveSessionKey)) return
|
|
392
|
+
|
|
393
|
+
if (payload.text) {
|
|
394
|
+
completeText = payload.text
|
|
395
|
+
}
|
|
396
|
+
// --- Streaming text chunks ---
|
|
397
|
+
if (payload.text) {
|
|
398
|
+
const delta = payload.text.startsWith(completeText.slice(0, streamedTextLen))
|
|
399
|
+
? payload.text.slice(streamedTextLen)
|
|
400
|
+
: payload.text
|
|
401
|
+
if (delta.trim()) {
|
|
402
|
+
const prev = streamChunkIdxBySessionKey.get(effectiveSessionKey) ?? 0
|
|
403
|
+
streamChunkIdxBySessionKey.set(effectiveSessionKey, prev + 1)
|
|
404
|
+
sendChunk(delta, outboundCtx, prev)
|
|
405
|
+
dcgLogger(`[stream]: chunkIdx=${prev} len=${delta.length} user=${msg._userId} ${delta.slice(0, 100)}`)
|
|
406
|
+
}
|
|
407
|
+
streamedTextLen = payload.text.length
|
|
408
|
+
} else {
|
|
409
|
+
dcgLogger(`onPartialReply no text: ${JSON.stringify(payload)}`, 'error')
|
|
410
|
+
}
|
|
411
|
+
// --- Media from payload ---
|
|
412
|
+
const mediaList = resolveReplyMediaList(payload)
|
|
413
|
+
for (const mediaUrl of mediaList) {
|
|
414
|
+
const key = getMediaKey(mediaUrl)
|
|
415
|
+
if (sentMediaKeys.has(key)) continue
|
|
416
|
+
sentMediaKeys.add(key)
|
|
417
|
+
await sendDcgchatMedia({ sessionKey: effectiveSessionKey, mediaUrl, text: '' })
|
|
418
|
+
}
|
|
383
419
|
}
|
|
384
|
-
streamedTextLen = payload.text.length
|
|
385
|
-
}
|
|
386
|
-
// --- Media from payload ---
|
|
387
|
-
const mediaList = resolveReplyMediaList(payload)
|
|
388
|
-
for (const mediaUrl of mediaList) {
|
|
389
|
-
const key = getMediaKey(mediaUrl)
|
|
390
|
-
if (sentMediaKeys.has(key)) continue
|
|
391
|
-
sentMediaKeys.add(key)
|
|
392
|
-
await sendDcgchatMedia({ sessionKey: effectiveSessionKey, mediaUrl, text: '' })
|
|
393
420
|
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
421
|
+
})
|
|
396
422
|
})
|
|
397
423
|
}
|
|
398
424
|
} catch (err: unknown) {
|
|
399
|
-
|
|
400
|
-
// wasAborted = true
|
|
401
|
-
// dcgLogger(` generation aborted for conversationId=${conversationId}`)
|
|
402
|
-
// } else if (err instanceof Error && err.name === 'AbortError') {
|
|
403
|
-
// wasAborted = true
|
|
404
|
-
// dcgLogger(` generation aborted for conversationId=${conversationId}`)
|
|
405
|
-
// } else {
|
|
406
|
-
// dcgLogger(` dispatchReplyFromConfig error: ${String(err)}`, 'error')
|
|
407
|
-
// }
|
|
408
|
-
} finally {
|
|
409
|
-
// if (activeGenerations.get(conversationId) === genCtrl) {
|
|
410
|
-
// activeGenerations.delete(conversationId)
|
|
411
|
-
// }
|
|
412
|
-
}
|
|
413
|
-
try {
|
|
414
|
-
markRunComplete()
|
|
415
|
-
markDispatchIdle()
|
|
416
|
-
} catch (err) {
|
|
417
|
-
dcgLogger(` markRunComplete||markRunComplete error: ${String(err)}`, 'error')
|
|
425
|
+
dcgLogger(` dispatchReplyFromConfig error: ${String(err)}`, 'error')
|
|
418
426
|
}
|
|
427
|
+
|
|
419
428
|
if (![...systemCommand, ...interruptCommand].includes(text?.trim())) {
|
|
420
429
|
if (sessionStreamSuppressed.has(effectiveSessionKey)) {
|
|
421
430
|
sessionStreamSuppressed.delete(effectiveSessionKey)
|
|
@@ -442,11 +451,10 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
442
451
|
}
|
|
443
452
|
}
|
|
444
453
|
}
|
|
445
|
-
safeSendFinal('end')
|
|
446
454
|
clearSentMediaKeys(msg.content.message_id)
|
|
447
|
-
|
|
448
|
-
// Record session metadata
|
|
449
455
|
const storePath = core.channel.session.resolveStorePath(config.session?.store)
|
|
456
|
+
await waitUntilSubagentsIdle(effectiveSessionKey, { timeoutMs: 600_000 })
|
|
457
|
+
sendFinal(outboundCtx, 'end')
|
|
450
458
|
dcgLogger(
|
|
451
459
|
`record session route: rawTarget=${userId}, normalizedTarget=${effectiveSessionKey}, updateLastRoute.to=${effectiveSessionKey}, accountId=${route.accountId}`
|
|
452
460
|
)
|
|
@@ -455,7 +463,6 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
455
463
|
storePath,
|
|
456
464
|
sessionKey: effectiveSessionKey,
|
|
457
465
|
ctx: ctxPayload,
|
|
458
|
-
// 与 Telegram/Discord 等一致:写入 deliveryContext.to,否则投递可能回退为 From(userId),channel sendMedia 里 ctx.to 会变成数字 userId
|
|
459
466
|
updateLastRoute: {
|
|
460
467
|
sessionKey: effectiveSessionKey,
|
|
461
468
|
channel: "dcgchat-test",
|
|
@@ -472,7 +479,5 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
472
479
|
} catch (err) {
|
|
473
480
|
dcgLogger(` handle message failed: ${String(err)}`, 'error')
|
|
474
481
|
sendError(err instanceof Error ? err.message : String(err), outboundCtx)
|
|
475
|
-
} finally {
|
|
476
|
-
safeSendFinal('finally')
|
|
477
482
|
}
|
|
478
483
|
}
|
package/src/channel.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import type { ChannelPlugin, OpenClawConfig } from 'openclaw/plugin-sdk'
|
|
2
|
-
import { DEFAULT_ACCOUNT_ID } from 'openclaw/plugin-sdk'
|
|
1
|
+
import type { ChannelPlugin, OpenClawConfig, PluginRuntime } from 'openclaw/plugin-sdk'
|
|
2
|
+
import { createPluginRuntimeStore, 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 {
|
|
5
|
+
import {
|
|
6
|
+
addSentMediaKey,
|
|
7
|
+
getCronMessageId,
|
|
8
|
+
getDcgchatRuntime,
|
|
9
|
+
getInfoBySessionKey,
|
|
10
|
+
getOpenClawConfig,
|
|
11
|
+
hasSentMediaKey
|
|
12
|
+
} from './utils/global.js'
|
|
6
13
|
import { isWsOpen, mergeDefaultParams, mergeSessionParams, sendFinal, wsSendRaw } from './transport.js'
|
|
7
14
|
import { dcgLogger, setLogger } from './utils/log.js'
|
|
8
15
|
import { getOutboundMsgParams, getParamsMessage } from './utils/params.js'
|
|
@@ -116,7 +123,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
116
123
|
uiHints: {
|
|
117
124
|
userId: {
|
|
118
125
|
label: 'WS 连接 _userId',
|
|
119
|
-
help: '仅用于拼接网关 WebSocket URL 的查询参数,不是 Agent 发消息时的 target。发消息请使用 effectiveSessionKey(与入站上下文 SessionKey 相同,格式如 agent:main:mobook:direct
|
|
126
|
+
help: '仅用于拼接网关 WebSocket URL 的查询参数,不是 Agent 发消息时的 target。发消息请使用 effectiveSessionKey(与入站上下文 SessionKey 相同,格式如 agent:main:mobook:direct:<agent_id>:<session_id>)。'
|
|
120
127
|
}
|
|
121
128
|
}
|
|
122
129
|
},
|
|
@@ -155,8 +162,9 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
155
162
|
},
|
|
156
163
|
agentPrompt: {
|
|
157
164
|
messageToolHints: () => [
|
|
158
|
-
'
|
|
159
|
-
'
|
|
165
|
+
'生成文件后,**尽可能不要**把文件路径、地址直接告诉用户。',
|
|
166
|
+
'生成文件后,把文件名告诉用户。',
|
|
167
|
+
'生成文件后,必须调用 message 工具发送文件,不可以直接在文本回复里包含文件路径、文件名、地址。'
|
|
160
168
|
]
|
|
161
169
|
},
|
|
162
170
|
outbound: {
|
|
@@ -167,6 +175,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
167
175
|
}
|
|
168
176
|
return { ok: true, to: to }
|
|
169
177
|
},
|
|
178
|
+
chunker: (text, limit) => getDcgchatRuntime().channel.text.chunkMarkdownText(text, limit),
|
|
170
179
|
textChunkLimit: 4000,
|
|
171
180
|
sendText: async (ctx) => {
|
|
172
181
|
dcgLogger(`channel sendText to ${ctx.to} `)
|
package/src/gateway/index.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { WebSocket } from 'ws'
|
|
|
3
3
|
import crypto from 'crypto'
|
|
4
4
|
import { deriveDeviceIdFromPublicKey, publicKeyRawBase64UrlFromPem, buildDeviceAuthPayloadV3, signDevicePayload } from './security.js'
|
|
5
5
|
import { dcgLogger } from '../utils/log.js'
|
|
6
|
-
import { finishedDcgchatCron, sendDcgchatCron } from '../cron.js'
|
|
7
6
|
import { getWorkspaceDir } from '../utils/global.js'
|
|
7
|
+
import { handleGatewayEventMessage } from '../utils/gatewayMsgHanlder.js'
|
|
8
8
|
|
|
9
9
|
export interface GatewayEvent {
|
|
10
10
|
type: string
|
|
@@ -358,31 +358,7 @@ export class GatewayConnection {
|
|
|
358
358
|
}
|
|
359
359
|
|
|
360
360
|
if (msg.type === 'event') {
|
|
361
|
-
|
|
362
|
-
// 定时任务相关事件
|
|
363
|
-
if (msg.event === 'cron') {
|
|
364
|
-
dcgLogger(`[Gateway] 收到事件: ${JSON.stringify(msg)}`)
|
|
365
|
-
if (msg.payload?.action === 'added') {
|
|
366
|
-
sendDcgchatCron(msg.payload?.jobId)
|
|
367
|
-
}
|
|
368
|
-
if (msg.payload?.action === 'updated') {
|
|
369
|
-
sendDcgchatCron(msg.payload?.jobId as string)
|
|
370
|
-
}
|
|
371
|
-
if (msg.payload?.action === 'removed') {
|
|
372
|
-
sendDcgchatCron(msg.payload?.jobId as string)
|
|
373
|
-
}
|
|
374
|
-
if (msg.payload?.action === 'finished') {
|
|
375
|
-
finishedDcgchatCron(msg.payload?.jobId as string)
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
} catch (error) {
|
|
379
|
-
dcgLogger(`[Gateway] 处理事件失败: ${error}`, 'error')
|
|
380
|
-
}
|
|
381
|
-
const event: GatewayEvent = {
|
|
382
|
-
type: msg.event as string,
|
|
383
|
-
payload: msg.payload as Record<string, unknown> | undefined,
|
|
384
|
-
seq: msg.seq as number | undefined
|
|
385
|
-
}
|
|
361
|
+
const event = handleGatewayEventMessage(msg)
|
|
386
362
|
this.eventHandlers.forEach((h) => h(event))
|
|
387
363
|
}
|
|
388
364
|
}
|
package/src/tool.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { OpenClawPluginApi } from 'openclaw/plugin-sdk'
|
|
|
2
2
|
import { getMsgStatus } from './utils/global.js'
|
|
3
3
|
import { dcgLogger } from './utils/log.js'
|
|
4
4
|
import { sendFinal, sendText, wsSendRaw } from './transport.js'
|
|
5
|
-
import { getEffectiveMsgParams } from './utils/params.js'
|
|
5
|
+
import { getEffectiveMsgParams, deleteSessionKeyBySubAgentRunId, setSessionKeyBySubAgentRunId } from './utils/params.js'
|
|
6
6
|
import { cronToolCall } from './cronToolCall.js'
|
|
7
7
|
|
|
8
8
|
type PluginHookName =
|
|
@@ -68,10 +68,244 @@ interface CronDelivery {
|
|
|
68
68
|
[key: string]: unknown
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
// --- Subagent 活跃跟踪(按主会话 requesterSessionKey)---
|
|
72
|
+
|
|
73
|
+
/** 主会话 sessionKey -> 仍活跃的子 agent runId */
|
|
74
|
+
const activeSubagentRunIdsByRequester = new Map<string, Set<string>>()
|
|
75
|
+
/** 子会话 childSessionKey -> 主会话 requesterSessionKey */
|
|
76
|
+
const requesterByChildSessionKey = new Map<string, string>()
|
|
77
|
+
/** 子会话 childSessionKey -> spawn 时的 runId(ended 事件可能不带 runId) */
|
|
78
|
+
const runIdByChildSessionKey = new Map<string, string>()
|
|
79
|
+
/** 主会话 -> 等待「子 agent 全部结束」的回调 */
|
|
80
|
+
const subagentIdleWaiters = new Map<string, Set<() => void>>()
|
|
81
|
+
|
|
82
|
+
function getOrCreateRunIdSet(requesterSessionKey: string): Set<string> {
|
|
83
|
+
let set = activeSubagentRunIdsByRequester.get(requesterSessionKey)
|
|
84
|
+
if (!set) {
|
|
85
|
+
set = new Set()
|
|
86
|
+
activeSubagentRunIdsByRequester.set(requesterSessionKey, set)
|
|
87
|
+
}
|
|
88
|
+
return set
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function flushSubagentIdleWaiters(requesterSessionKey: string): void {
|
|
92
|
+
const set = activeSubagentRunIdsByRequester.get(requesterSessionKey)
|
|
93
|
+
if (set && set.size > 0) return
|
|
94
|
+
activeSubagentRunIdsByRequester.delete(requesterSessionKey)
|
|
95
|
+
const waiters = subagentIdleWaiters.get(requesterSessionKey)
|
|
96
|
+
if (!waiters?.size) return
|
|
97
|
+
subagentIdleWaiters.delete(requesterSessionKey)
|
|
98
|
+
for (const w of waiters) {
|
|
99
|
+
try {
|
|
100
|
+
w()
|
|
101
|
+
} catch (e) {
|
|
102
|
+
dcgLogger(`subagent idle waiter error: ${String(e)}`, 'error')
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function registerSubagentSpawn(requesterSessionKey: string, runId: string, childSessionKey: string): void {
|
|
108
|
+
const req = requesterSessionKey.trim()
|
|
109
|
+
const rid = runId.trim()
|
|
110
|
+
const child = childSessionKey.trim()
|
|
111
|
+
if (!req || !rid || !child) {
|
|
112
|
+
dcgLogger(`subagent track spawn skipped: missing key req=${req} runId=${rid} child=${child}`)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
getOrCreateRunIdSet(req).add(rid)
|
|
116
|
+
requesterByChildSessionKey.set(child, req)
|
|
117
|
+
runIdByChildSessionKey.set(child, rid)
|
|
118
|
+
dcgLogger(`subagent track spawn: requester=${req} runId=${rid} child=${child} active=${getOrCreateRunIdSet(req).size}`)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function registerSubagentEnd(
|
|
122
|
+
ctx: { requesterSessionKey?: string; sessionKey?: string },
|
|
123
|
+
targetSessionKey: string,
|
|
124
|
+
runId?: string
|
|
125
|
+
): void {
|
|
126
|
+
const child = targetSessionKey.trim()
|
|
127
|
+
if (!child) return
|
|
128
|
+
const req = ctx.requesterSessionKey?.trim() || requesterByChildSessionKey.get(child) || ctx.sessionKey?.trim() || ''
|
|
129
|
+
const resolvedRunId = (runId?.trim() || runIdByChildSessionKey.get(child) || '').trim()
|
|
130
|
+
deleteSessionKeyBySubAgentRunId(resolvedRunId)
|
|
131
|
+
if (!req) {
|
|
132
|
+
dcgLogger(`subagent track end: no requester for child=${child} runId=${resolvedRunId}`)
|
|
133
|
+
requesterByChildSessionKey.delete(child)
|
|
134
|
+
runIdByChildSessionKey.delete(child)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
const set = activeSubagentRunIdsByRequester.get(req)
|
|
138
|
+
if (set && resolvedRunId) {
|
|
139
|
+
set.delete(resolvedRunId)
|
|
140
|
+
}
|
|
141
|
+
requesterByChildSessionKey.delete(child)
|
|
142
|
+
runIdByChildSessionKey.delete(child)
|
|
143
|
+
dcgLogger(`subagent track end: requester=${req} runId=${resolvedRunId || 'n/a'} remaining=${set?.size ?? 0}`)
|
|
144
|
+
if (set && set.size === 0) {
|
|
145
|
+
activeSubagentRunIdsByRequester.delete(req)
|
|
146
|
+
}
|
|
147
|
+
flushSubagentIdleWaiters(req)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** 当前跟踪到的、挂在该主会话下的子会话 sessionKey(供 /stop 时逐个 chat.abort) */
|
|
151
|
+
export function getChildSessionKeysTrackedForRequester(requesterSessionKey: string): string[] {
|
|
152
|
+
const req = requesterSessionKey.trim()
|
|
153
|
+
if (!req) return []
|
|
154
|
+
const out: string[] = []
|
|
155
|
+
for (const [child, parent] of requesterByChildSessionKey.entries()) {
|
|
156
|
+
if (parent === req) out.push(child)
|
|
157
|
+
}
|
|
158
|
+
return out
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 打断后清空本地子 agent 跟踪(runId、父子映射、子 runId→sessionKey),并唤醒 waitUntilSubagentsIdle,避免永久挂起。
|
|
163
|
+
*/
|
|
164
|
+
export function resetSubagentStateForRequesterSession(requesterSessionKey: string): void {
|
|
165
|
+
const req = requesterSessionKey.trim()
|
|
166
|
+
if (!req) return
|
|
167
|
+
|
|
168
|
+
const runIdSet = activeSubagentRunIdsByRequester.get(req)
|
|
169
|
+
if (runIdSet) {
|
|
170
|
+
for (const rid of runIdSet) {
|
|
171
|
+
deleteSessionKeyBySubAgentRunId(rid)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const [child, parent] of [...requesterByChildSessionKey.entries()]) {
|
|
176
|
+
if (parent === req) {
|
|
177
|
+
requesterByChildSessionKey.delete(child)
|
|
178
|
+
runIdByChildSessionKey.delete(child)
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
activeSubagentRunIdsByRequester.delete(req)
|
|
183
|
+
|
|
184
|
+
const waiters = subagentIdleWaiters.get(req)
|
|
185
|
+
if (!waiters?.size) return
|
|
186
|
+
subagentIdleWaiters.delete(req)
|
|
187
|
+
for (const w of waiters) {
|
|
188
|
+
try {
|
|
189
|
+
w()
|
|
190
|
+
} catch (e) {
|
|
191
|
+
dcgLogger(`subagent idle waiter error: ${String(e)}`, 'error')
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** 当前主会话下仍在跑的子 agent 数量(按 spawn 时 runId 去重) */
|
|
197
|
+
export function getActiveSubagentCount(sessionKey: string): number {
|
|
198
|
+
const sk = sessionKey?.trim()
|
|
199
|
+
if (!sk) return 0
|
|
200
|
+
return activeSubagentRunIdsByRequester.get(sk)?.size ?? 0
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 等到指定主会话下已跟踪的子 agent 全部结束(spawn 失败等路径也会触发 subagent_ended,一般会配对清理)。
|
|
205
|
+
* 注意:须在收到 `subagent_spawned` 之后才会计入;仅 spawning 未 spawned 的不会阻塞。
|
|
206
|
+
*/
|
|
207
|
+
export function waitUntilSubagentsIdle(sessionKey: string, opts?: { timeoutMs?: number; signal?: AbortSignal }): Promise<void> {
|
|
208
|
+
console.log('🚀 ~ waitUntilSubagentsIdle ~ sessionKey:', sessionKey)
|
|
209
|
+
const sk = sessionKey?.trim()
|
|
210
|
+
if (!sk) return Promise.resolve()
|
|
211
|
+
|
|
212
|
+
if (getActiveSubagentCount(sk) === 0) return Promise.resolve()
|
|
213
|
+
|
|
214
|
+
return new Promise<void>((resolve, reject) => {
|
|
215
|
+
let settled = false
|
|
216
|
+
const finish = (fn: () => void) => {
|
|
217
|
+
if (settled) return
|
|
218
|
+
settled = true
|
|
219
|
+
if (timeoutId) clearTimeout(timeoutId)
|
|
220
|
+
opts?.signal?.removeEventListener('abort', onAbort)
|
|
221
|
+
removeWaiter()
|
|
222
|
+
fn()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const removeWaiter = () => {
|
|
226
|
+
const bucket = subagentIdleWaiters.get(sk)
|
|
227
|
+
if (!bucket) return
|
|
228
|
+
bucket.delete(onIdle)
|
|
229
|
+
if (bucket.size === 0) subagentIdleWaiters.delete(sk)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const onIdle = () => finish(() => resolve())
|
|
233
|
+
|
|
234
|
+
const onAbort = () => {
|
|
235
|
+
const reason = opts?.signal?.reason
|
|
236
|
+
finish(() => reject(reason instanceof Error ? reason : new Error(String(reason ?? 'Aborted'))))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined
|
|
240
|
+
if (opts?.timeoutMs != null && opts.timeoutMs > 0) {
|
|
241
|
+
timeoutId = setTimeout(
|
|
242
|
+
() => finish(() => reject(new Error(`waitUntilSubagentsIdle timeout ${opts.timeoutMs}ms`))),
|
|
243
|
+
opts.timeoutMs
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (opts?.signal) {
|
|
248
|
+
if (opts.signal.aborted) {
|
|
249
|
+
onAbort()
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
opts.signal.addEventListener('abort', onAbort, { once: true })
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
let set = subagentIdleWaiters.get(sk)
|
|
256
|
+
if (!set) {
|
|
257
|
+
set = new Set()
|
|
258
|
+
subagentIdleWaiters.set(sk, set)
|
|
259
|
+
}
|
|
260
|
+
set.add(onIdle)
|
|
261
|
+
|
|
262
|
+
if (getActiveSubagentCount(sk) === 0) {
|
|
263
|
+
onIdle()
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function resolveHookSessionKey(
|
|
269
|
+
eventName: string,
|
|
270
|
+
args: { sessionKey?: string; requesterSessionKey?: string; runId?: string }
|
|
271
|
+
): string {
|
|
272
|
+
if (
|
|
273
|
+
eventName === 'subagent_spawned' ||
|
|
274
|
+
eventName === 'subagent_ended' ||
|
|
275
|
+
eventName === 'subagent_spawning' ||
|
|
276
|
+
eventName === 'subagent_delivery_target'
|
|
277
|
+
) {
|
|
278
|
+
if (args?.runId && args?.requesterSessionKey) setSessionKeyBySubAgentRunId(args?.runId, args?.requesterSessionKey)
|
|
279
|
+
return (args?.requesterSessionKey || args?.sessionKey || '').trim()
|
|
280
|
+
}
|
|
281
|
+
return (args?.sessionKey || '').trim()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function trackSubagentLifecycle(eventName: string, event: any, args: any): void {
|
|
285
|
+
if (eventName === 'subagent_spawned') {
|
|
286
|
+
const runId = typeof event?.runId === 'string' ? event.runId : ''
|
|
287
|
+
const childSessionKey = typeof event?.childSessionKey === 'string' ? event.childSessionKey : ''
|
|
288
|
+
const requester =
|
|
289
|
+
typeof args?.requesterSessionKey === 'string'
|
|
290
|
+
? args.requesterSessionKey
|
|
291
|
+
: typeof args?.sessionKey === 'string'
|
|
292
|
+
? args.sessionKey
|
|
293
|
+
: ''
|
|
294
|
+
registerSubagentSpawn(requester, runId, childSessionKey)
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
if (eventName === 'subagent_ended') {
|
|
298
|
+
const targetSessionKey = typeof event?.targetSessionKey === 'string' ? event.targetSessionKey : ''
|
|
299
|
+
const runId = typeof event?.runId === 'string' ? event.runId : undefined
|
|
300
|
+
registerSubagentEnd(args ?? {}, targetSessionKey, runId)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
71
304
|
export function monitoringToolMessage(api: OpenClawPluginApi) {
|
|
72
305
|
for (const item of eventList) {
|
|
73
306
|
api.on(item.event as PluginHookName, (event: any, args: any) => {
|
|
74
|
-
|
|
307
|
+
trackSubagentLifecycle(item.event, event, args)
|
|
308
|
+
const sk = resolveHookSessionKey(item.event, args ?? {})
|
|
75
309
|
if (sk) {
|
|
76
310
|
const status = getMsgStatus(sk)
|
|
77
311
|
if (status === 'running') {
|
package/src/utils/constant.ts
CHANGED
|
@@ -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 = ['
|
|
5
|
+
export const interruptCommand = ['/stop']
|
|
6
6
|
|
|
7
|
-
export const ignoreToolCommand = ['/search', '/abort', '/
|
|
7
|
+
export const ignoreToolCommand = ['/search', '/abort', '/queue interrupt', ...systemCommand, ...interruptCommand]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { GatewayEvent } from '../gateway/index.js'
|
|
2
|
+
import { finishedDcgchatCron, sendDcgchatCron } from '../cron.js'
|
|
3
|
+
import { dcgLogger } from './log.js'
|
|
4
|
+
import { getEffectiveMsgParams, getSessionKeyBySubAgentRunId } from './params.js'
|
|
5
|
+
import { sendChunk } from '../transport.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 处理网关 event 帧的副作用(agent 流式输出、cron 同步),并构造供上层分发的 GatewayEvent。
|
|
9
|
+
*/
|
|
10
|
+
export function handleGatewayEventMessage(msg: { event?: string; payload?: Record<string, unknown>; seq?: number }): GatewayEvent {
|
|
11
|
+
try {
|
|
12
|
+
if (msg.event === 'agent') {
|
|
13
|
+
dcgLogger(`[Gateway] 收到agent事件: ${JSON.stringify(msg).slice(0, 100)}`)
|
|
14
|
+
const pl = msg.payload as { runId: string; data?: { delta?: unknown } }
|
|
15
|
+
const sessionKey = getSessionKeyBySubAgentRunId(pl.runId)
|
|
16
|
+
const outboundCtx = getEffectiveMsgParams(sessionKey)
|
|
17
|
+
if (outboundCtx.sessionId && pl.data?.delta) sendChunk(pl.data.delta as string, outboundCtx, 0)
|
|
18
|
+
}
|
|
19
|
+
if (msg.event === 'cron') {
|
|
20
|
+
const p = msg.payload
|
|
21
|
+
dcgLogger(`[Gateway] 收到定时任务事件: ${JSON.stringify(p)}`)
|
|
22
|
+
if (p?.action === 'added') {
|
|
23
|
+
sendDcgchatCron(p?.jobId as string)
|
|
24
|
+
}
|
|
25
|
+
if (p?.action === 'updated') {
|
|
26
|
+
sendDcgchatCron(p?.jobId as string)
|
|
27
|
+
}
|
|
28
|
+
if (p?.action === 'removed') {
|
|
29
|
+
sendDcgchatCron(p?.jobId as string)
|
|
30
|
+
}
|
|
31
|
+
if (p?.action === 'finished') {
|
|
32
|
+
finishedDcgchatCron(p?.jobId as string)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch (error) {
|
|
36
|
+
dcgLogger(`[Gateway] 处理事件失败: ${error}`, 'error')
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
type: msg.event as string,
|
|
40
|
+
payload: msg.payload,
|
|
41
|
+
seq: msg.seq as number | undefined
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/utils/global.ts
CHANGED
|
@@ -22,7 +22,7 @@ export function getOpenClawConfig(): OpenClawConfig | null {
|
|
|
22
22
|
return config
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
import type
|
|
25
|
+
import { createPluginRuntimeStore, type OpenClawConfig, type PluginRuntime } from 'openclaw/plugin-sdk'
|
|
26
26
|
import { dcgLogger } from './log.js'
|
|
27
27
|
import { channelInfo, ENV } from './constant.js'
|
|
28
28
|
|
|
@@ -42,7 +42,6 @@ function getWorkspacePath() {
|
|
|
42
42
|
return null
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
let runtime: PluginRuntime | null = null
|
|
46
45
|
let workspaceDir: string = getWorkspacePath()
|
|
47
46
|
|
|
48
47
|
export function setWorkspaceDir(dir?: string) {
|
|
@@ -56,17 +55,10 @@ export function getWorkspaceDir(): string {
|
|
|
56
55
|
}
|
|
57
56
|
return workspaceDir
|
|
58
57
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function getDcgchatRuntime(): PluginRuntime {
|
|
65
|
-
if (!runtime) {
|
|
66
|
-
dcgLogger?.('runtime not initialized', 'error')
|
|
67
|
-
}
|
|
68
|
-
return runtime as PluginRuntime
|
|
69
|
-
}
|
|
58
|
+
const { setRuntime: setDcgchatRuntime, getRuntime: getDcgchatRuntime } = createPluginRuntimeStore<PluginRuntime>(
|
|
59
|
+
`${"dcgchat-test"} runtime not initialized`
|
|
60
|
+
)
|
|
61
|
+
export { setDcgchatRuntime, getDcgchatRuntime }
|
|
70
62
|
|
|
71
63
|
export type MsgSessionStatus = 'running' | 'finished' | ''
|
|
72
64
|
|
package/src/utils/params.ts
CHANGED
|
@@ -69,3 +69,21 @@ export function setParamsMessage(sessionKey: string, params: Partial<IMsgParams>
|
|
|
69
69
|
export function getParamsMessage(sessionKey: string): IMsgParams | undefined {
|
|
70
70
|
return paramsMessageMap.get(sessionKey)
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
export function clearParamsMessage(sessionKey: string): void {
|
|
74
|
+
const k = sessionKey?.trim()
|
|
75
|
+
if (!k) return
|
|
76
|
+
paramsMessageMap.delete(k)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// sessionKey 对应的 子agent的runId
|
|
80
|
+
const subagentRunIdMap = new Map<string, string>()
|
|
81
|
+
export function getSessionKeyBySubAgentRunId(runId: string): string | undefined {
|
|
82
|
+
return subagentRunIdMap.get(runId)
|
|
83
|
+
}
|
|
84
|
+
export function setSessionKeyBySubAgentRunId(runId: string, sessionKey: string) {
|
|
85
|
+
subagentRunIdMap.set(runId, sessionKey)
|
|
86
|
+
}
|
|
87
|
+
export function deleteSessionKeyBySubAgentRunId(runId: string) {
|
|
88
|
+
subagentRunIdMap.delete(runId)
|
|
89
|
+
}
|