@dcrays/dcgchat-test 0.3.39 → 0.3.40
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 +1 -2
- package/package.json +1 -1
- package/src/bot.ts +49 -58
- package/src/channel.ts +16 -12
- package/src/gateway/socket.ts +5 -3
- package/src/tool.ts +0 -1
- package/src/tools/{meeageToll.ts → messageTool.ts} +13 -5
- package/src/transport.ts +8 -5
- package/src/utils/global.ts +13 -14
- package/src/utils/params.ts +1 -1
package/index.ts
CHANGED
|
@@ -4,8 +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 {
|
|
8
|
-
import { createDcgchatMessageTool } from './src/tools/meeageToll.js'
|
|
7
|
+
import { createDcgchatMessageTool } from './src/tools/messageTool.js'
|
|
9
8
|
|
|
10
9
|
const plugin = {
|
|
11
10
|
id: "dcgchat-test",
|
package/package.json
CHANGED
package/src/bot.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import type { PluginRuntime, ReplyPayload } from 'openclaw/plugin-sdk'
|
|
5
|
-
import { createPluginRuntimeStore, createReplyPrefixContext, createTypingCallbacks } from 'openclaw/plugin-sdk'
|
|
2
|
+
import type { ReplyPayload } from 'openclaw/plugin-sdk'
|
|
3
|
+
import { createReplyPrefixContext, createTypingCallbacks } from 'openclaw/plugin-sdk'
|
|
6
4
|
import type { InboundMessage } from './types.js'
|
|
7
5
|
import {
|
|
8
6
|
clearSentMediaKeys,
|
|
@@ -126,20 +124,18 @@ function resolveReplyMediaList(payload: ReplyPayload): string[] {
|
|
|
126
124
|
if (payload.mediaUrls?.length) return payload.mediaUrls.filter(Boolean)
|
|
127
125
|
return payload.mediaUrl ? [payload.mediaUrl] : []
|
|
128
126
|
}
|
|
127
|
+
|
|
129
128
|
const typingCallbacks = createTypingCallbacks({
|
|
130
|
-
start: async () => {
|
|
131
|
-
console.log('typing start')
|
|
132
|
-
},
|
|
129
|
+
start: async () => {},
|
|
133
130
|
onStartError: (err) => {
|
|
134
|
-
|
|
131
|
+
dcgLogger(`typing start error: ${String(err)}`, 'error')
|
|
135
132
|
}
|
|
136
133
|
})
|
|
134
|
+
|
|
137
135
|
/**
|
|
138
136
|
* 处理一条用户消息,调用 Agent 并返回回复
|
|
139
137
|
*/
|
|
140
138
|
export async function handleDcgchatMessage(msg: InboundMessage, accountId: string): Promise<void> {
|
|
141
|
-
let finalSent = false
|
|
142
|
-
|
|
143
139
|
let completeText = ''
|
|
144
140
|
const config = getOpenClawConfig()
|
|
145
141
|
if (!config) {
|
|
@@ -164,7 +160,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
164
160
|
const embeddedAgentId = extractAgentIdFromConversationId(conversationId)
|
|
165
161
|
|
|
166
162
|
const effectiveAgentId = embeddedAgentId ?? route.agentId
|
|
167
|
-
const
|
|
163
|
+
const dcgSessionKey = getSessionKey(msg.content, account.accountId)
|
|
168
164
|
|
|
169
165
|
const mergedParams = {
|
|
170
166
|
userId: msg._userId,
|
|
@@ -175,13 +171,12 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
175
171
|
appId: msg.content.app_id,
|
|
176
172
|
botId: msg.content.bot_id ?? '',
|
|
177
173
|
agentId: msg.content.agent_id ?? '',
|
|
178
|
-
sessionKey:
|
|
174
|
+
sessionKey: dcgSessionKey,
|
|
179
175
|
real_mobook
|
|
180
176
|
}
|
|
181
|
-
setParamsMessage(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const outboundCtx = getEffectiveMsgParams(effectiveSessionKey)
|
|
177
|
+
setParamsMessage(dcgSessionKey, mergedParams)
|
|
178
|
+
dcgLogger(`target alias bound: aliasTarget=${userId} -> sessionKey=${dcgSessionKey}`)
|
|
179
|
+
const outboundCtx = getEffectiveMsgParams(dcgSessionKey)
|
|
185
180
|
const agentEntry =
|
|
186
181
|
effectiveAgentId && effectiveAgentId !== 'main' ? config.agents?.list?.find((a) => a.id === effectiveAgentId) : undefined
|
|
187
182
|
const agentDisplayName = agentEntry?.name ?? (effectiveAgentId && effectiveAgentId !== 'main' ? effectiveAgentId : undefined)
|
|
@@ -218,8 +213,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
218
213
|
RawBody: text,
|
|
219
214
|
CommandBody: text,
|
|
220
215
|
From: userId,
|
|
221
|
-
To:
|
|
222
|
-
SessionKey:
|
|
216
|
+
To: dcgSessionKey,
|
|
217
|
+
SessionKey: dcgSessionKey,
|
|
223
218
|
AccountId: route.accountId,
|
|
224
219
|
ChatType: 'direct',
|
|
225
220
|
SenderName: agentDisplayName,
|
|
@@ -231,9 +226,9 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
231
226
|
WasMentioned: true,
|
|
232
227
|
CommandAuthorized: true,
|
|
233
228
|
OriginatingChannel: "dcgchat-test",
|
|
234
|
-
OriginatingTo:
|
|
235
|
-
Target:
|
|
236
|
-
SourceTarget:
|
|
229
|
+
OriginatingTo: dcgSessionKey,
|
|
230
|
+
Target: dcgSessionKey,
|
|
231
|
+
SourceTarget: dcgSessionKey,
|
|
237
232
|
...mediaPayload
|
|
238
233
|
})
|
|
239
234
|
|
|
@@ -254,26 +249,22 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
254
249
|
humanDelay: core.channel.reply.resolveHumanDelayConfig(config, route.agentId),
|
|
255
250
|
onReplyStart: async () => {},
|
|
256
251
|
deliver: async (payload: ReplyPayload, info) => {
|
|
257
|
-
if (sessionStreamSuppressed.has(
|
|
252
|
+
if (sessionStreamSuppressed.has(dcgSessionKey)) return
|
|
258
253
|
const mediaList = resolveReplyMediaList(payload)
|
|
259
254
|
for (const mediaUrl of mediaList) {
|
|
260
255
|
const key = getMediaKey(mediaUrl)
|
|
261
256
|
if (sentMediaKeys.has(key)) continue
|
|
262
257
|
sentMediaKeys.add(key)
|
|
263
|
-
await sendDcgchatMedia({ sessionKey:
|
|
258
|
+
await sendDcgchatMedia({ sessionKey: dcgSessionKey, mediaUrl, text: '' })
|
|
264
259
|
}
|
|
265
260
|
},
|
|
266
261
|
onError: (err: unknown, info: { kind: string }) => {
|
|
267
|
-
setMsgStatus(
|
|
262
|
+
setMsgStatus(dcgSessionKey, 'finished')
|
|
268
263
|
sendFinal(outboundCtx, 'error')
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
dcgLogger(`${info.kind} reply failed (stream suppressed): ${String(err)}`, 'error')
|
|
274
|
-
return
|
|
275
|
-
}
|
|
276
|
-
dcgLogger(`${info.kind} reply failed: ${String(err)}`, 'error')
|
|
264
|
+
activeRunIdBySessionKey.delete(dcgSessionKey)
|
|
265
|
+
streamChunkIdxBySessionKey.delete(dcgSessionKey)
|
|
266
|
+
const suppressed = sessionStreamSuppressed.has(dcgSessionKey)
|
|
267
|
+
dcgLogger(`${info.kind} reply failed${suppressed ? ' (stream suppressed)' : ''}: ${String(err)}`, 'error')
|
|
277
268
|
},
|
|
278
269
|
onIdle: () => {
|
|
279
270
|
typingCallbacks.onIdle?.()
|
|
@@ -282,8 +273,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
282
273
|
|
|
283
274
|
try {
|
|
284
275
|
if (!interruptCommand.includes(text?.trim())) {
|
|
285
|
-
sessionStreamSuppressed.delete(
|
|
286
|
-
streamChunkIdxBySessionKey.set(
|
|
276
|
+
sessionStreamSuppressed.delete(dcgSessionKey)
|
|
277
|
+
streamChunkIdxBySessionKey.set(dcgSessionKey, 0)
|
|
287
278
|
}
|
|
288
279
|
|
|
289
280
|
if (systemCommand.includes(text?.trim())) {
|
|
@@ -300,7 +291,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
300
291
|
...replyOptions,
|
|
301
292
|
onModelSelected: prefixContext.onModelSelected,
|
|
302
293
|
onAgentRunStart: (runId) => {
|
|
303
|
-
activeRunIdBySessionKey.set(
|
|
294
|
+
activeRunIdBySessionKey.set(dcgSessionKey, runId)
|
|
304
295
|
}
|
|
305
296
|
}
|
|
306
297
|
})
|
|
@@ -309,7 +300,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
309
300
|
dcgLogger(`interrupt command: ${text}`)
|
|
310
301
|
sendFinal(outboundCtx, 'abort')
|
|
311
302
|
sendText('会话已终止', outboundCtx)
|
|
312
|
-
sessionStreamSuppressed.add(
|
|
303
|
+
sessionStreamSuppressed.add(dcgSessionKey)
|
|
313
304
|
|
|
314
305
|
const abortOneSession = async (sessionKey: string) => {
|
|
315
306
|
try {
|
|
@@ -319,11 +310,11 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
319
310
|
}
|
|
320
311
|
}
|
|
321
312
|
|
|
322
|
-
const keysToAbort = new Set<string>(getChildSessionKeysTrackedForRequester(
|
|
313
|
+
const keysToAbort = new Set<string>(getChildSessionKeysTrackedForRequester(dcgSessionKey))
|
|
323
314
|
try {
|
|
324
315
|
const listed = await sendGatewayRpc<{ sessions?: Array<{ key?: string }> }>({
|
|
325
316
|
method: 'sessions.list',
|
|
326
|
-
params: { spawnedBy:
|
|
317
|
+
params: { spawnedBy: dcgSessionKey, limit: 256 }
|
|
327
318
|
})
|
|
328
319
|
for (const s of listed?.sessions ?? []) {
|
|
329
320
|
const k = typeof s?.key === 'string' ? s.key.trim() : ''
|
|
@@ -335,29 +326,29 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
335
326
|
for (const sk of keysToAbort) {
|
|
336
327
|
await abortOneSession(sk)
|
|
337
328
|
}
|
|
338
|
-
await abortOneSession(
|
|
329
|
+
await abortOneSession(dcgSessionKey)
|
|
339
330
|
|
|
340
331
|
try {
|
|
341
332
|
await sendGatewayRpc({
|
|
342
333
|
method: 'sessions.reset',
|
|
343
|
-
params: { key:
|
|
334
|
+
params: { key: dcgSessionKey, reason: 'reset' }
|
|
344
335
|
})
|
|
345
336
|
} catch (e) {
|
|
346
337
|
dcgLogger(`sessions.reset: ${String(e)}`, 'error')
|
|
347
338
|
}
|
|
348
339
|
|
|
349
|
-
activeRunIdBySessionKey.delete(
|
|
350
|
-
streamChunkIdxBySessionKey.delete(
|
|
351
|
-
resetSubagentStateForRequesterSession(
|
|
352
|
-
setMsgStatus(
|
|
340
|
+
activeRunIdBySessionKey.delete(dcgSessionKey)
|
|
341
|
+
streamChunkIdxBySessionKey.delete(dcgSessionKey)
|
|
342
|
+
resetSubagentStateForRequesterSession(dcgSessionKey)
|
|
343
|
+
setMsgStatus(dcgSessionKey, 'finished')
|
|
353
344
|
clearSentMediaKeys(msg.content.message_id)
|
|
354
|
-
clearParamsMessage(
|
|
345
|
+
clearParamsMessage(dcgSessionKey)
|
|
355
346
|
clearParamsMessage(userId)
|
|
356
347
|
|
|
357
348
|
sendFinal(outboundCtx, 'stop')
|
|
358
349
|
return
|
|
359
350
|
} else {
|
|
360
|
-
const params = getEffectiveMsgParams(
|
|
351
|
+
const params = getEffectiveMsgParams(dcgSessionKey)
|
|
361
352
|
if (!ignoreToolCommand.includes(text?.trim())) {
|
|
362
353
|
wsSendRaw(params, {
|
|
363
354
|
is_finish: -1,
|
|
@@ -385,10 +376,10 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
385
376
|
...replyOptions,
|
|
386
377
|
onModelSelected: prefixContext.onModelSelected,
|
|
387
378
|
onAgentRunStart: (runId) => {
|
|
388
|
-
activeRunIdBySessionKey.set(
|
|
379
|
+
activeRunIdBySessionKey.set(dcgSessionKey, runId)
|
|
389
380
|
},
|
|
390
381
|
onPartialReply: async (payload: ReplyPayload) => {
|
|
391
|
-
if (sessionStreamSuppressed.has(
|
|
382
|
+
if (sessionStreamSuppressed.has(dcgSessionKey)) return
|
|
392
383
|
|
|
393
384
|
if (payload.text) {
|
|
394
385
|
completeText = payload.text
|
|
@@ -399,8 +390,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
399
390
|
? payload.text.slice(streamedTextLen)
|
|
400
391
|
: payload.text
|
|
401
392
|
if (delta.trim()) {
|
|
402
|
-
const prev = streamChunkIdxBySessionKey.get(
|
|
403
|
-
streamChunkIdxBySessionKey.set(
|
|
393
|
+
const prev = streamChunkIdxBySessionKey.get(dcgSessionKey) ?? 0
|
|
394
|
+
streamChunkIdxBySessionKey.set(dcgSessionKey, prev + 1)
|
|
404
395
|
sendChunk(delta, outboundCtx, prev)
|
|
405
396
|
dcgLogger(`[stream]: chunkIdx=${prev} len=${delta.length} user=${msg._userId} ${delta.slice(0, 100)}`)
|
|
406
397
|
}
|
|
@@ -414,7 +405,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
414
405
|
const key = getMediaKey(mediaUrl)
|
|
415
406
|
if (sentMediaKeys.has(key)) continue
|
|
416
407
|
sentMediaKeys.add(key)
|
|
417
|
-
await sendDcgchatMedia({ sessionKey:
|
|
408
|
+
await sendDcgchatMedia({ sessionKey: dcgSessionKey, mediaUrl, text: '' })
|
|
418
409
|
}
|
|
419
410
|
}
|
|
420
411
|
}
|
|
@@ -426,26 +417,26 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
426
417
|
}
|
|
427
418
|
|
|
428
419
|
if (![...systemCommand, ...interruptCommand].includes(text?.trim())) {
|
|
429
|
-
if (sessionStreamSuppressed.has(
|
|
430
|
-
sessionStreamSuppressed.delete(
|
|
420
|
+
if (sessionStreamSuppressed.has(dcgSessionKey)) {
|
|
421
|
+
sessionStreamSuppressed.delete(dcgSessionKey)
|
|
431
422
|
}
|
|
432
423
|
}
|
|
433
424
|
clearSentMediaKeys(msg.content.message_id)
|
|
434
425
|
const storePath = core.channel.session.resolveStorePath(config.session?.store)
|
|
435
|
-
await waitUntilSubagentsIdle(
|
|
426
|
+
await waitUntilSubagentsIdle(dcgSessionKey, { timeoutMs: 600_000 })
|
|
436
427
|
sendFinal(outboundCtx, 'end')
|
|
437
428
|
dcgLogger(
|
|
438
|
-
`record session route: rawTarget=${userId}, normalizedTarget=${
|
|
429
|
+
`record session route: rawTarget=${userId}, normalizedTarget=${dcgSessionKey}, updateLastRoute.to=${dcgSessionKey}, accountId=${route.accountId}`
|
|
439
430
|
)
|
|
440
431
|
core.channel.session
|
|
441
432
|
.recordInboundSession({
|
|
442
433
|
storePath,
|
|
443
|
-
sessionKey:
|
|
434
|
+
sessionKey: dcgSessionKey,
|
|
444
435
|
ctx: ctxPayload,
|
|
445
436
|
updateLastRoute: {
|
|
446
|
-
sessionKey:
|
|
437
|
+
sessionKey: dcgSessionKey,
|
|
447
438
|
channel: "dcgchat-test",
|
|
448
|
-
to:
|
|
439
|
+
to: dcgSessionKey,
|
|
449
440
|
accountId: route.accountId
|
|
450
441
|
},
|
|
451
442
|
onRecordError: (err) => {
|
package/src/channel.ts
CHANGED
|
@@ -105,7 +105,8 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
105
105
|
effects: true
|
|
106
106
|
// blockStreaming: true,
|
|
107
107
|
},
|
|
108
|
-
|
|
108
|
+
/** 当前构建的 channel id + 兼容旧配置键 `channels.dcgchat` */
|
|
109
|
+
reload: { configPrefixes: [`channels.${"dcgchat-test"}`, 'channels.dcgchat'] },
|
|
109
110
|
configSchema: {
|
|
110
111
|
schema: {
|
|
111
112
|
type: 'object',
|
|
@@ -114,7 +115,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
114
115
|
enabled: { type: 'boolean' },
|
|
115
116
|
wsUrl: { type: 'string' },
|
|
116
117
|
botToken: { type: 'string' },
|
|
117
|
-
userId: { type: 'string', description: 'WebSocket 连接参数 _userId,与 message 工具的 target(
|
|
118
|
+
userId: { type: 'string', description: 'WebSocket 连接参数 _userId,与 message 工具的 target(dcgSessionKey)无关' },
|
|
118
119
|
appId: { type: 'string' },
|
|
119
120
|
domainId: { type: 'string' },
|
|
120
121
|
capabilities: { type: 'array', items: { type: 'string' } }
|
|
@@ -123,7 +124,7 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
123
124
|
uiHints: {
|
|
124
125
|
userId: {
|
|
125
126
|
label: 'WS 连接 _userId',
|
|
126
|
-
help: '仅用于拼接网关 WebSocket URL 的查询参数,不是 Agent 发消息时的 target。发消息请使用
|
|
127
|
+
help: '仅用于拼接网关 WebSocket URL 的查询参数,不是 Agent 发消息时的 target。发消息请使用 dcgSessionKey(与入站上下文 SessionKey 相同)。'
|
|
127
128
|
}
|
|
128
129
|
}
|
|
129
130
|
},
|
|
@@ -131,16 +132,18 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
131
132
|
listAccountIds: () => [DEFAULT_ACCOUNT_ID],
|
|
132
133
|
resolveAccount: (cfg, accountId) => resolveAccount(cfg, accountId),
|
|
133
134
|
defaultAccountId: () => DEFAULT_ACCOUNT_ID,
|
|
134
|
-
setAccountEnabled: ({ cfg, enabled }) =>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
setAccountEnabled: ({ cfg, enabled }) => {
|
|
136
|
+
const channelKey = "dcgchat-test"
|
|
137
|
+
const prev =
|
|
138
|
+
(cfg.channels?.[channelKey as keyof NonNullable<typeof cfg.channels>] as Record<string, unknown> | undefined) ?? {}
|
|
139
|
+
return {
|
|
140
|
+
...cfg,
|
|
141
|
+
channels: {
|
|
142
|
+
...cfg.channels,
|
|
143
|
+
[channelKey]: { ...prev, enabled }
|
|
141
144
|
}
|
|
142
145
|
}
|
|
143
|
-
}
|
|
146
|
+
},
|
|
144
147
|
isConfigured: (account) => account.configured,
|
|
145
148
|
describeAccount: (account) => ({
|
|
146
149
|
accountId: account.accountId,
|
|
@@ -157,13 +160,14 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
157
160
|
normalizeTarget: (raw) => raw || undefined,
|
|
158
161
|
targetResolver: {
|
|
159
162
|
looksLikeId: (raw) => Boolean(raw?.trim()),
|
|
160
|
-
hint: '
|
|
163
|
+
hint: 'dcgSessionKey(与 SessionKey 一致;勿填配置里的 WS userId)'
|
|
161
164
|
}
|
|
162
165
|
},
|
|
163
166
|
agentPrompt: {
|
|
164
167
|
messageToolHints: () => [
|
|
165
168
|
'生成文件后,**尽可能不要**把文件路径、地址直接告诉用户。',
|
|
166
169
|
'生成文件后,把文件名告诉用户。',
|
|
170
|
+
'调用 message 工具时,target 必须填写 dcgSessionKey(即 SessionKey),绝不能填写 WS userId。',
|
|
167
171
|
'生成文件后,必须调用 message 工具发送文件,不可以直接在文本回复里包含文件路径、文件名、地址。'
|
|
168
172
|
]
|
|
169
173
|
},
|
package/src/gateway/socket.ts
CHANGED
|
@@ -212,7 +212,7 @@ async function connectPersistentGateway(): Promise<void> {
|
|
|
212
212
|
startPingTimer(gw)
|
|
213
213
|
dcgLogger(`Gateway 持久连接成功 connId=${gw.getConnId() ?? '?'}`)
|
|
214
214
|
} catch (e) {
|
|
215
|
-
dcgLogger(`Gateway
|
|
215
|
+
dcgLogger(`Gateway 连接失败: ${e}`, 'error')
|
|
216
216
|
persistentConn = null
|
|
217
217
|
clearPingTimer()
|
|
218
218
|
if (!socketStopped) {
|
|
@@ -230,8 +230,10 @@ export function startDcgchatGatewaySocket(): void {
|
|
|
230
230
|
socketStopped = false
|
|
231
231
|
clearReconnectTimer()
|
|
232
232
|
if (startupConnectTimer != null) return
|
|
233
|
-
startupConnectTimer =
|
|
234
|
-
|
|
233
|
+
startupConnectTimer = setTimeout(() => {
|
|
234
|
+
startupConnectTimer = null
|
|
235
|
+
void connectPersistentGateway()
|
|
236
|
+
}, 0)
|
|
235
237
|
}
|
|
236
238
|
|
|
237
239
|
/**
|
package/src/tool.ts
CHANGED
|
@@ -205,7 +205,6 @@ export function getActiveSubagentCount(sessionKey: string): number {
|
|
|
205
205
|
* 注意:须在收到 `subagent_spawned` 之后才会计入;仅 spawning 未 spawned 的不会阻塞。
|
|
206
206
|
*/
|
|
207
207
|
export function waitUntilSubagentsIdle(sessionKey: string, opts?: { timeoutMs?: number; signal?: AbortSignal }): Promise<void> {
|
|
208
|
-
console.log('🚀 ~ waitUntilSubagentsIdle ~ sessionKey:', sessionKey)
|
|
209
208
|
const sk = sessionKey?.trim()
|
|
210
209
|
if (!sk) return Promise.resolve()
|
|
211
210
|
|
|
@@ -2,15 +2,15 @@ import fs from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import type { AnyAgentTool } from 'openclaw/plugin-sdk'
|
|
4
4
|
import { jsonResult } from 'openclaw/plugin-sdk'
|
|
5
|
+
import { sendDcgchatMedia } from '../channel.js'
|
|
6
|
+
import { getOutboundMsgParams } from '../utils/params.js'
|
|
7
|
+
import { sendText } from '../transport.js'
|
|
5
8
|
|
|
6
9
|
/** 与 `registerTool` 工厂入参一致(主包未导出 `OpenClawPluginToolContext` 时仅用所需字段)。 */
|
|
7
10
|
export type DcgchatMessageToolContext = {
|
|
8
11
|
sessionKey?: string
|
|
9
12
|
workspaceDir?: string
|
|
10
13
|
}
|
|
11
|
-
import { sendDcgchatMedia } from '../channel.js'
|
|
12
|
-
import { getOutboundMsgParams } from '../utils/params.js'
|
|
13
|
-
import { sendText } from '../transport.js'
|
|
14
14
|
|
|
15
15
|
const SAFE_PREFIXES = ['/workspace/', '/mobook/']
|
|
16
16
|
|
|
@@ -22,7 +22,13 @@ const SAFE_EXTENSIONS = new Set([...fileType1, ...fileType2, ...fileType3, ...fi
|
|
|
22
22
|
|
|
23
23
|
const messageToolParameters = {
|
|
24
24
|
type: 'object',
|
|
25
|
+
additionalProperties: false,
|
|
25
26
|
properties: {
|
|
27
|
+
target: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description:
|
|
30
|
+
'目标会话键(sessionKey),必须与当前会话 SessionKey 一致,禁止填写 userId。'
|
|
31
|
+
},
|
|
26
32
|
content: {
|
|
27
33
|
type: 'string',
|
|
28
34
|
description: '发送文本内容'
|
|
@@ -32,6 +38,7 @@ const messageToolParameters = {
|
|
|
32
38
|
description: '发送附件',
|
|
33
39
|
items: {
|
|
34
40
|
type: 'object',
|
|
41
|
+
additionalProperties: false,
|
|
35
42
|
properties: {
|
|
36
43
|
file: {
|
|
37
44
|
type: 'string',
|
|
@@ -41,7 +48,8 @@ const messageToolParameters = {
|
|
|
41
48
|
required: ['file']
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
|
-
}
|
|
51
|
+
},
|
|
52
|
+
oneOf: [{ required: ['content'] }, { required: ['media'] }]
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
function extractPaths(text?: string) {
|
|
@@ -68,12 +76,12 @@ function isSafeFile(filepath: string) {
|
|
|
68
76
|
* 通过注册时的 `OpenClawPluginToolContext.sessionKey` 出站,不再使用非标准的 `execute(args, ctx)`。
|
|
69
77
|
*/
|
|
70
78
|
export function createDcgchatMessageTool(pluginCtx: DcgchatMessageToolContext): AnyAgentTool {
|
|
71
|
-
console.log('🚀 ~ createDcgchatMessageTool ~ pluginCtx:', pluginCtx)
|
|
72
79
|
return {
|
|
73
80
|
name: 'message',
|
|
74
81
|
label: 'message',
|
|
75
82
|
description: `
|
|
76
83
|
向用户发送消息。
|
|
84
|
+
若传 target,target 必须是 sessionKey,不能是 userId。
|
|
77
85
|
如果发送附件:必须使用 media 字段
|
|
78
86
|
支持路径目录:
|
|
79
87
|
/workspace/
|
package/src/transport.ts
CHANGED
|
@@ -152,11 +152,14 @@ export function wsSend(ctx: IMsgParams, content: Record<string, unknown>): boole
|
|
|
152
152
|
*/
|
|
153
153
|
export function wsSendRaw(ctx: IMsgParams, content: Record<string, unknown>, isLog = true): boolean {
|
|
154
154
|
const ws = getWsConnection()
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
if (ws?.readyState !== WebSocket.OPEN) {
|
|
156
|
+
dcgLogger(`server socket not ready ${ws?.readyState}`, 'error')
|
|
157
|
+
return false
|
|
158
|
+
}
|
|
159
|
+
const envelope = buildOpenclawBotChat(ctx, content, { mergeChannelDefaults: true })
|
|
160
|
+
ws.send(JSON.stringify(envelope))
|
|
161
|
+
if (isLog) {
|
|
162
|
+
dcgLogger('已发送:' + JSON.stringify(envelope))
|
|
160
163
|
}
|
|
161
164
|
return true
|
|
162
165
|
}
|
package/src/utils/global.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
/** socket connection */
|
|
2
1
|
import type WebSocket from 'ws'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import os from 'node:os'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
import { createPluginRuntimeStore, type OpenClawConfig, type PluginRuntime } from 'openclaw/plugin-sdk'
|
|
6
|
+
import { channelInfo, ENV } from './constant.js'
|
|
7
|
+
import { dcgLogger } from './log.js'
|
|
3
8
|
|
|
9
|
+
/** socket connection */
|
|
4
10
|
let ws: WebSocket | null = null
|
|
5
11
|
|
|
6
12
|
export function setWsConnection(next: WebSocket | null) {
|
|
@@ -11,7 +17,6 @@ export function getWsConnection(): WebSocket | null {
|
|
|
11
17
|
return ws
|
|
12
18
|
}
|
|
13
19
|
|
|
14
|
-
// OpenClawConfig
|
|
15
20
|
let config: OpenClawConfig | null = null
|
|
16
21
|
|
|
17
22
|
export function setOpenClawConfig(next: OpenClawConfig | null) {
|
|
@@ -22,15 +27,7 @@ export function getOpenClawConfig(): OpenClawConfig | null {
|
|
|
22
27
|
return config
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
import { dcgLogger } from './log.js'
|
|
27
|
-
import { channelInfo, ENV } from './constant.js'
|
|
28
|
-
|
|
29
|
-
const path = require('path')
|
|
30
|
-
const fs = require('fs')
|
|
31
|
-
const os = require('os')
|
|
32
|
-
|
|
33
|
-
function getWorkspacePath() {
|
|
30
|
+
function getWorkspacePath(): string | null {
|
|
34
31
|
const workspacePath = path.join(
|
|
35
32
|
os.homedir(),
|
|
36
33
|
config?.channels?.["dcgchat-test"]?.appId == 110 ? '.mobook' : '.openclaw',
|
|
@@ -42,19 +39,21 @@ function getWorkspacePath() {
|
|
|
42
39
|
return null
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
let workspaceDir: string = getWorkspacePath()
|
|
42
|
+
let workspaceDir: string = getWorkspacePath() ?? ''
|
|
46
43
|
|
|
47
44
|
export function setWorkspaceDir(dir?: string) {
|
|
48
45
|
if (dir) {
|
|
49
46
|
workspaceDir = dir
|
|
50
47
|
}
|
|
51
48
|
}
|
|
49
|
+
|
|
52
50
|
export function getWorkspaceDir(): string {
|
|
53
51
|
if (!workspaceDir) {
|
|
54
52
|
dcgLogger?.('Workspace directory not initialized', 'error')
|
|
55
53
|
}
|
|
56
54
|
return workspaceDir
|
|
57
55
|
}
|
|
56
|
+
|
|
58
57
|
const { setRuntime: setDcgchatRuntime, getRuntime: getDcgchatRuntime } = createPluginRuntimeStore<PluginRuntime>(
|
|
59
58
|
`${"dcgchat-test"} runtime not initialized`
|
|
60
59
|
)
|
|
@@ -127,7 +126,7 @@ export const getSessionKey = (content: any, accountId: string) => {
|
|
|
127
126
|
const { real_mobook, agent_id, agent_clone_code, session_id } = content
|
|
128
127
|
const core = getDcgchatRuntime()
|
|
129
128
|
|
|
130
|
-
const
|
|
129
|
+
const agentCode = agent_clone_code || 'main'
|
|
131
130
|
|
|
132
131
|
const route = core.channel.routing.resolveAgentRoute({
|
|
133
132
|
cfg: getOpenClawConfig() as OpenClawConfig,
|
|
@@ -135,7 +134,7 @@ export const getSessionKey = (content: any, accountId: string) => {
|
|
|
135
134
|
accountId: accountId || 'default',
|
|
136
135
|
peer: { kind: 'direct', id: session_id }
|
|
137
136
|
})
|
|
138
|
-
return real_mobook == '1' ? route.sessionKey : `agent:${
|
|
137
|
+
return real_mobook == '1' ? route.sessionKey : `agent:${agentCode}:mobook:direct:${agent_id}:${session_id}`.toLowerCase()
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
export function getInfoBySessionKey(sk: string): { sessionId: string; agentId: string } {
|
package/src/utils/params.ts
CHANGED
|
@@ -47,7 +47,7 @@ export function getEffectiveMsgParams(sessionKey?: string): IMsgParams {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* Agent `message` 工具的 `target` 应为 `
|
|
50
|
+
* Agent `message` 工具的 `target` 应为 `dcgSessionKey`(如 `agent:main:mobook:direct:...`)。
|
|
51
51
|
* `setParamsMessage` 使用的 key 与此一致。若按 preferredKey 查不到 map,
|
|
52
52
|
* 则回落到当前会话 `currentSessionKey`,避免拿到空 `messageId` / `sessionId` 导致无文件卡片、WS 上下文错误。
|
|
53
53
|
*/
|