@dcrays/dcgchat-test 0.4.18 → 0.4.19
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 +1 -1
- package/src/channel.ts +19 -11
- package/src/cron.ts +6 -2
- package/src/cronToolCall.ts +9 -8
- package/src/tool.ts +14 -1
- package/src/transport.ts +2 -1
- package/src/utils/gatewayMsgHanlder.ts +6 -1
- package/src/utils/global.ts +8 -8
package/package.json
CHANGED
package/src/bot.ts
CHANGED
|
@@ -174,7 +174,7 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
174
174
|
sessionId: conversationId,
|
|
175
175
|
messageId: msg.content.message_id,
|
|
176
176
|
domainId: msg.content.domain_id,
|
|
177
|
-
appId:
|
|
177
|
+
appId: config.channels?.["dcgchat-test"]?.appId || 100,
|
|
178
178
|
botId: msg.content.bot_id ?? '',
|
|
179
179
|
agentId: msg.content.agent_id ?? '',
|
|
180
180
|
sessionKey: dcgSessionKey,
|
package/src/channel.ts
CHANGED
|
@@ -107,18 +107,27 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
|
|
|
107
107
|
const baseCtx = getOutboundMsgParams(sessionKey)
|
|
108
108
|
const msgCtx = opts.messageId?.trim() ? { ...baseCtx, messageId: opts.messageId.trim() } : baseCtx
|
|
109
109
|
if (!isWsOpen()) {
|
|
110
|
-
dcgLogger(`outbound media skipped -> ws not open: ${opts.mediaUrl ?? ''}`)
|
|
110
|
+
dcgLogger(`outbound media skipped -> ws not isWsOpen failed open: ${opts.mediaUrl ?? ''}`)
|
|
111
111
|
return
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
const mediaUrl = opts.mediaUrl
|
|
115
|
-
|
|
116
|
-
if (mediaUrl &&
|
|
117
|
-
|
|
118
|
-
|
|
115
|
+
|
|
116
|
+
if (mediaUrl && msgCtx.sessionId) {
|
|
117
|
+
if (hasSentMediaKey(msgCtx.sessionId, mediaUrl)) {
|
|
118
|
+
dcgLogger(`dcgchat: sendMedia skipped (hasSentMediaKey): ${mediaUrl} sessionId=${msgCtx.sessionId} sessionKey=${sessionKey}`)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
addSentMediaKey(msgCtx.sessionId, mediaUrl)
|
|
119
122
|
}
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
|
|
124
|
+
const { sessionId, agentId } = getInfoBySessionKey(sessionKey)
|
|
125
|
+
if (!msgCtx.sessionId) {
|
|
126
|
+
msgCtx.sessionId = sessionId
|
|
127
|
+
}
|
|
128
|
+
if (!mediaUrl || !msgCtx.sessionId) {
|
|
129
|
+
dcgLogger(`dcgchat: sendMedia skipped (duplicate in session): ${mediaUrl} sessionId=${msgCtx.sessionId} sessionKey=${sessionKey}`)
|
|
130
|
+
return
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
const fileName = mediaUrl?.split(/[\\/]/).pop() || ''
|
|
@@ -126,20 +135,19 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
|
|
|
126
135
|
try {
|
|
127
136
|
const botToken = msgCtx.botToken ?? getOpenClawConfig()?.channels?.["dcgchat-test"]?.botToken ?? ''
|
|
128
137
|
const url = opts.mediaUrl ? await ossUpload(opts.mediaUrl, botToken, 1) : ''
|
|
129
|
-
if (!msgCtx.
|
|
130
|
-
const { sessionId, agentId } = getInfoBySessionKey(sessionKey)
|
|
131
|
-
msgCtx.sessionId = sessionId
|
|
138
|
+
if (!msgCtx.agentId) {
|
|
132
139
|
msgCtx.agentId = agentId
|
|
133
140
|
}
|
|
134
141
|
wsSendRaw(msgCtx, {
|
|
135
142
|
response: opts.text ?? '',
|
|
143
|
+
is_finish: `${msgCtx?.messageId}`?.length === 13 ? -1 : 0,
|
|
136
144
|
files: [{ url, name: fileName }]
|
|
137
145
|
})
|
|
138
146
|
dcgLogger(`dcgchat: sendMedia session=${sessionKey}, file=${fileName}`)
|
|
139
147
|
} catch (error) {
|
|
140
148
|
wsSendRaw(msgCtx, {
|
|
141
149
|
response: opts.text ?? '',
|
|
142
|
-
|
|
150
|
+
is_finish: `${msgCtx?.messageId}`?.length === 13 ? -1 : 0,
|
|
143
151
|
files: [{ url: opts.mediaUrl ?? '', name: fileName }]
|
|
144
152
|
})
|
|
145
153
|
dcgLogger(`dcgchat: error sendMedia session=${sessionKey}: ${String(error)}`, 'error')
|
package/src/cron.ts
CHANGED
|
@@ -135,7 +135,7 @@ export const onRunCronJob = async (jobId: string, messageId: string) => {
|
|
|
135
135
|
)
|
|
136
136
|
sendMessageToGateway(JSON.stringify({ method: 'cron.run', params: { id: jobId, mode: 'force' } }))
|
|
137
137
|
}
|
|
138
|
-
export const finishedDcgchatCron = async (jobId: string, summary: string) => {
|
|
138
|
+
export const finishedDcgchatCron = async (jobId: string, summary: string, hasFileOutput?: boolean) => {
|
|
139
139
|
const id = jobId?.trim()
|
|
140
140
|
if (!id) {
|
|
141
141
|
dcgLogger('finishedDcgchatCron: empty jobId', 'error')
|
|
@@ -159,7 +159,11 @@ export const finishedDcgchatCron = async (jobId: string, summary: string) => {
|
|
|
159
159
|
messageId: messageId || `${Date.now()}`,
|
|
160
160
|
real_mobook: !sessionId ? 1 : ''
|
|
161
161
|
})
|
|
162
|
-
|
|
162
|
+
const message_tags = { source: 'cron' } as Record<string, string | boolean>
|
|
163
|
+
if (hasFileOutput) {
|
|
164
|
+
message_tags.hasFile = true
|
|
165
|
+
}
|
|
166
|
+
wsSendRaw(merged, { response: summary, message_tags, is_finish: -1 })
|
|
163
167
|
setTimeout(() => {
|
|
164
168
|
sendFinal(merged, 'cron send')
|
|
165
169
|
}, 200)
|
package/src/cronToolCall.ts
CHANGED
|
@@ -135,10 +135,11 @@ function injectBestEffort(params: Record<string, unknown>, sk: string): Record<s
|
|
|
135
135
|
// job.delivery
|
|
136
136
|
const job = newParams.job as Record<string, unknown> | undefined
|
|
137
137
|
if (job?.delivery && typeof job.delivery === 'object') {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
const jd = job.delivery as CronDelivery
|
|
139
|
+
jd.bestEffort = true
|
|
140
|
+
jd.to = `dcg-cron:${sk}`
|
|
141
|
+
jd.accountId = agentId
|
|
142
|
+
jd.channel = "dcgchat-test"
|
|
142
143
|
newParams.sessionKey = sk
|
|
143
144
|
return newParams
|
|
144
145
|
}
|
|
@@ -154,14 +155,14 @@ export function cronToolCall(event: { toolName: any; params: any; toolCallId: an
|
|
|
154
155
|
const delivery = extractDelivery(params)
|
|
155
156
|
if (!delivery) {
|
|
156
157
|
dcgLogger(`[${LOG_TAG}] cron call (${toolCallId}) has no delivery config, skip.`)
|
|
157
|
-
return
|
|
158
|
+
return undefined
|
|
158
159
|
}
|
|
159
160
|
if (!needsBestEffort(delivery)) {
|
|
160
161
|
dcgLogger(
|
|
161
162
|
`[${LOG_TAG}] cron call (${toolCallId}) delivery does not need bestEffort ` +
|
|
162
163
|
`(mode=${String(delivery.mode)}, channel=${String(delivery.channel)}, bestEffort=${String(delivery.bestEffort)}), skip.`
|
|
163
164
|
)
|
|
164
|
-
return
|
|
165
|
+
return undefined
|
|
165
166
|
}
|
|
166
167
|
|
|
167
168
|
// ★ 核心:注入 bestEffort: true
|
|
@@ -179,9 +180,9 @@ export function cronToolCall(event: { toolName: any; params: any; toolCallId: an
|
|
|
179
180
|
params.command.replace('--json', '') + ` --session-key ${sk} --channel ${"dcgchat-test"} --to dcg-cron:${sk} --json`
|
|
180
181
|
return { params: newParams }
|
|
181
182
|
} else {
|
|
182
|
-
return
|
|
183
|
+
return undefined
|
|
183
184
|
}
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
return
|
|
187
|
+
return undefined
|
|
187
188
|
}
|
package/src/tool.ts
CHANGED
|
@@ -325,6 +325,16 @@ function resolveHookSessionKey(
|
|
|
325
325
|
return (args?.sessionKey || '').trim()
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
/** 定时触发时会话往往非 running,但仍需跑 before_tool_call 以注入 sessionKey / delivery(见 cronToolCall) */
|
|
329
|
+
function shouldRunBeforeToolCallWithoutRunningSession(event: { toolName?: string; params?: { command?: string } }): boolean {
|
|
330
|
+
if (event?.toolName === 'cron') return true
|
|
331
|
+
const cmd = event?.params?.command
|
|
332
|
+
if (event?.toolName === 'exec' && typeof cmd === 'string') {
|
|
333
|
+
return cmd.includes('cron create') || cmd.includes('cron add')
|
|
334
|
+
}
|
|
335
|
+
return false
|
|
336
|
+
}
|
|
337
|
+
|
|
328
338
|
function trackSubagentLifecycle(eventName: string, event: any, args: any): void {
|
|
329
339
|
if (eventName === 'subagent_spawned') {
|
|
330
340
|
const runId = typeof event?.runId === 'string' ? event.runId : ''
|
|
@@ -351,7 +361,10 @@ export function monitoringToolMessage(api: OpenClawPluginApi) {
|
|
|
351
361
|
trackSubagentLifecycle(item.event, event, args)
|
|
352
362
|
const sk = resolveHookSessionKey(item.event, args ?? {})
|
|
353
363
|
if (sk) {
|
|
354
|
-
|
|
364
|
+
const toolHooksOk =
|
|
365
|
+
isSessionActiveForTool(sk) ||
|
|
366
|
+
(item.event === 'before_tool_call' && shouldRunBeforeToolCallWithoutRunningSession(event))
|
|
367
|
+
if (toolHooksOk) {
|
|
355
368
|
if (['after_tool_call', 'before_tool_call'].includes(item.event)) {
|
|
356
369
|
const { result: _result, ...rest } = event
|
|
357
370
|
dcgLogger(`工具调用结果: ~ event:${item.event} ~ params:${JSON.stringify(rest)}`)
|
package/src/transport.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getWsConnection } from './utils/global.js'
|
|
1
|
+
import { clearSentMediaKeys, getWsConnection } from './utils/global.js'
|
|
2
2
|
import { dcgLogger } from './utils/log.js'
|
|
3
3
|
import type { IMsgParams } from './types.js'
|
|
4
4
|
import { getEffectiveMsgParams, getParamsDefaults } from './utils/params.js'
|
|
@@ -170,6 +170,7 @@ export function sendChunk(text: string, ctx: IMsgParams, chunkIdx: number): bool
|
|
|
170
170
|
|
|
171
171
|
export function sendFinal(ctx: IMsgParams, tag: string): boolean {
|
|
172
172
|
dcgLogger(` message handling complete state: to=${ctx.sessionId} final tag:${tag}`)
|
|
173
|
+
clearSentMediaKeys(ctx.sessionId)
|
|
173
174
|
return wsSend(ctx, { response: '', state: 'final' })
|
|
174
175
|
}
|
|
175
176
|
|
|
@@ -36,7 +36,12 @@ export function handleGatewayEventMessage(msg: { event?: string; payload?: Recor
|
|
|
36
36
|
sendDcgchatCron(p?.jobId as string)
|
|
37
37
|
}
|
|
38
38
|
if (p?.action === 'finished' && p?.status === 'ok') {
|
|
39
|
-
|
|
39
|
+
const hasFileOutput = p.delivered === true && p.deliveryStatus === 'delivered'
|
|
40
|
+
let summary = p?.summary as string
|
|
41
|
+
if (summary.indexOf('HEARTBEAT_OK') >= 0 && summary !== 'HEARTBEAT_OK') {
|
|
42
|
+
summary = summary.replace('HEARTBEAT_OK', '')
|
|
43
|
+
}
|
|
44
|
+
finishedDcgchatCron(p?.jobId as string, summary, hasFileOutput)
|
|
40
45
|
}
|
|
41
46
|
}
|
|
42
47
|
} catch (error) {
|
package/src/utils/global.ts
CHANGED
|
@@ -82,11 +82,11 @@ const getMediaKey = (url: string) => url.split(/[\\/]/).pop() ?? url
|
|
|
82
82
|
/** 已发送媒体去重:外层 messageId → 内层该会话下已发送的媒体 key(文件名) */
|
|
83
83
|
const sentMediaKeysBySession = new Map<string, Set<string>>()
|
|
84
84
|
|
|
85
|
-
function getSessionMediaSet(
|
|
86
|
-
let set = sentMediaKeysBySession.get(
|
|
85
|
+
function getSessionMediaSet(sessionId: string): Set<string> {
|
|
86
|
+
let set = sentMediaKeysBySession.get(sessionId)
|
|
87
87
|
if (!set) {
|
|
88
88
|
set = new Set<string>()
|
|
89
|
-
sentMediaKeysBySession.set(
|
|
89
|
+
sentMediaKeysBySession.set(sessionId, set)
|
|
90
90
|
}
|
|
91
91
|
return set
|
|
92
92
|
}
|
|
@@ -95,14 +95,14 @@ export function addSentMediaKey(messageId: string, url: string) {
|
|
|
95
95
|
getSessionMediaSet(messageId).add(getMediaKey(url))
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
export function hasSentMediaKey(
|
|
99
|
-
return sentMediaKeysBySession.get(
|
|
98
|
+
export function hasSentMediaKey(sessionId: string, url: string): boolean {
|
|
99
|
+
return sentMediaKeysBySession.get(sessionId)?.has(getMediaKey(url)) ?? false
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/** 不传 messageId 时清空全部会话;传入则只清空该会话 */
|
|
103
|
-
export function clearSentMediaKeys(
|
|
104
|
-
if (
|
|
105
|
-
sentMediaKeysBySession.delete(
|
|
103
|
+
export function clearSentMediaKeys(sessionId?: string) {
|
|
104
|
+
if (sessionId) {
|
|
105
|
+
sentMediaKeysBySession.delete(sessionId)
|
|
106
106
|
} else {
|
|
107
107
|
sentMediaKeysBySession.clear()
|
|
108
108
|
}
|