@dcrays/dcgchat-test 0.4.17 → 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 +19 -6
- package/src/channel.ts +59 -14
- package/src/cron.ts +6 -2
- package/src/cronToolCall.ts +9 -8
- package/src/tool.ts +37 -1
- package/src/transport.ts +2 -1
- package/src/utils/gatewayMsgHanlder.ts +7 -2
- package/src/utils/global.ts +8 -8
package/package.json
CHANGED
package/src/bot.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { dcgLogger } from './utils/log.js'
|
|
|
17
17
|
import { channelInfo, systemCommand, interruptCommand, ENV, ignoreToolCommand } from './utils/constant.js'
|
|
18
18
|
import { sendGatewayRpc } from './gateway/socket.js'
|
|
19
19
|
import { clearParamsMessage, getEffectiveMsgParams, setParamsMessage } from './utils/params.js'
|
|
20
|
-
import {
|
|
20
|
+
import { getDescendantSessionKeysForRequester, resetSubagentStateForRequesterSession, waitUntilSubagentsIdle } from './tool.js'
|
|
21
21
|
|
|
22
22
|
type MediaInfo = {
|
|
23
23
|
path: string
|
|
@@ -30,7 +30,7 @@ type TFileInfo = { name: string; url: string }
|
|
|
30
30
|
|
|
31
31
|
const mediaMaxBytes = 300 * 1024 * 1024
|
|
32
32
|
|
|
33
|
-
/** 当前会话最近一次 agent run 的 runId
|
|
33
|
+
/** 当前会话最近一次 agent run 的 runId(供观测;/stop 时对子会话与主会话使用不传 runId 的 chat.abort 以终止该键下全部活跃 run) */
|
|
34
34
|
const activeRunIdBySessionKey = new Map<string, string>()
|
|
35
35
|
|
|
36
36
|
/**
|
|
@@ -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,
|
|
@@ -327,12 +327,25 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
327
327
|
const ctxForAbort = priorOutboundCtx.messageId?.trim() || priorOutboundCtx.sessionId?.trim() ? priorOutboundCtx : outboundCtx
|
|
328
328
|
sendFinal(ctxForAbort.messageId?.trim() ? ctxForAbort : { ...ctxForAbort, messageId: `${Date.now()}` }, 'abort')
|
|
329
329
|
sessionStreamSuppressed.add(dcgSessionKey)
|
|
330
|
+
// 网关侧彻底中止:子 agent 使用独立 sessionKey,仅 abort 主会话不会停子 run;自深到浅 abort 后代,再 abort 主会话;不传 runId 以终止该键下全部活跃 chat run
|
|
331
|
+
const descendantKeys = getDescendantSessionKeysForRequester(dcgSessionKey)
|
|
332
|
+
const abortSubKeys = [...descendantKeys].reverse()
|
|
333
|
+
if (abortSubKeys.length > 0) {
|
|
334
|
+
dcgLogger(`interrupt: chat.abort ${abortSubKeys.length} subagent session(s) (nested incl.)`)
|
|
335
|
+
}
|
|
336
|
+
for (const subKey of abortSubKeys) {
|
|
337
|
+
try {
|
|
338
|
+
await sendGatewayRpc({ method: 'chat.abort', params: { sessionKey: subKey } })
|
|
339
|
+
} catch (e) {
|
|
340
|
+
dcgLogger(`chat.abort subagent ${subKey}: ${String(e)}`, 'error')
|
|
341
|
+
}
|
|
342
|
+
}
|
|
330
343
|
try {
|
|
331
|
-
|
|
332
|
-
await sendGatewayRpc({ method: 'chat.abort', params: { sessionKey: dcgSessionKey, runId } })
|
|
344
|
+
await sendGatewayRpc({ method: 'chat.abort', params: { sessionKey: dcgSessionKey } })
|
|
333
345
|
} catch (e) {
|
|
334
|
-
dcgLogger(`chat.abort ${dcgSessionKey}: ${String(e)}`, 'error')
|
|
346
|
+
dcgLogger(`chat.abort main ${dcgSessionKey}: ${String(e)}`, 'error')
|
|
335
347
|
}
|
|
348
|
+
activeRunIdBySessionKey.delete(dcgSessionKey)
|
|
336
349
|
streamChunkIdxBySessionKey.delete(dcgSessionKey)
|
|
337
350
|
clearSentMediaKeys(msg.content.message_id)
|
|
338
351
|
resetSubagentStateForRequesterSession(dcgSessionKey)
|
package/src/channel.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { isWsOpen, mergeDefaultParams, mergeSessionParams, sendFinal, wsSendRaw
|
|
|
14
14
|
import { dcgLogger, setLogger } from './utils/log.js'
|
|
15
15
|
import { getOutboundMsgParams, getParamsMessage } from './utils/params.js'
|
|
16
16
|
import { startDcgchatGatewaySocket } from './gateway/socket.js'
|
|
17
|
+
import { getCronJobsPath, readCronJob } from './cron.js'
|
|
17
18
|
|
|
18
19
|
function dcgchatChannelCfg(): DcgchatConfig {
|
|
19
20
|
return (getOpenClawConfig()?.channels?.["dcgchat-test"] as DcgchatConfig | undefined) ?? {}
|
|
@@ -61,6 +62,8 @@ export type DcgchatMediaSendOptions = {
|
|
|
61
62
|
sessionKey: string
|
|
62
63
|
mediaUrl?: string
|
|
63
64
|
text?: string
|
|
65
|
+
/** 定时任务等场景须与 `getCronMessageId` 一致,避免沿用 map 里上一条用户消息的 messageId */
|
|
66
|
+
messageId?: string
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
function normalizeSessionTarget(rawTo: string): string {
|
|
@@ -69,6 +72,29 @@ function normalizeSessionTarget(rawTo: string): string {
|
|
|
69
72
|
return getParamsMessage(cleaned)?.sessionKey?.trim() || cleaned
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
/**
|
|
76
|
+
* OpenClaw 定时任务 `sessionTarget: isolated` 时,出站 `to` 常为 `agent:<code>:cron:<jobId>[:run:…]`,
|
|
77
|
+
* 与 `paramsMessageMap` / `getCronMessageId` 使用的 jobs.json `sessionKey`(mobook 用户会话)不一致,导致发文件时 sessionId、messageId 错位或缺省。
|
|
78
|
+
*/
|
|
79
|
+
function extractCronJobIdFromIsolatedSessionKey(sessionKey: string): string | null {
|
|
80
|
+
const parts = sessionKey.split(':').filter((p) => p.length > 0)
|
|
81
|
+
const i = parts.findIndex((p) => p.toLowerCase() === 'cron')
|
|
82
|
+
if (i < 0 || i + 1 >= parts.length) return null
|
|
83
|
+
return parts[i + 1] ?? null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolveIsolatedCronSessionToJobSessionKey(sessionKey: string): string {
|
|
87
|
+
const jobId = extractCronJobIdFromIsolatedSessionKey(sessionKey)
|
|
88
|
+
if (!jobId) return sessionKey
|
|
89
|
+
const job = readCronJob(getCronJobsPath(), jobId)
|
|
90
|
+
const sk = job && typeof job.sessionKey === 'string' ? job.sessionKey.trim() : ''
|
|
91
|
+
if (!sk) {
|
|
92
|
+
dcgLogger(`dcgchat: cron job ${jobId} has no sessionKey in jobs.json, keep outbound key=${sessionKey}`, 'error')
|
|
93
|
+
return sessionKey
|
|
94
|
+
}
|
|
95
|
+
return sk
|
|
96
|
+
}
|
|
97
|
+
|
|
72
98
|
/** 出站返回的 chatId:含 `dcg-cron:` 时保留原始 `to`,便于下游识别定时投递 */
|
|
73
99
|
function outboundChatId(rawTo: string | undefined, normalizedTo: string): string {
|
|
74
100
|
const raw = rawTo?.trim() ?? ''
|
|
@@ -76,21 +102,32 @@ function outboundChatId(rawTo: string | undefined, normalizedTo: string): string
|
|
|
76
102
|
}
|
|
77
103
|
|
|
78
104
|
export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<void> {
|
|
79
|
-
|
|
80
|
-
|
|
105
|
+
let sessionKey = normalizeSessionTarget(opts.sessionKey ?? '')
|
|
106
|
+
sessionKey = resolveIsolatedCronSessionToJobSessionKey(sessionKey)
|
|
107
|
+
const baseCtx = getOutboundMsgParams(sessionKey)
|
|
108
|
+
const msgCtx = opts.messageId?.trim() ? { ...baseCtx, messageId: opts.messageId.trim() } : baseCtx
|
|
81
109
|
if (!isWsOpen()) {
|
|
82
|
-
dcgLogger(`outbound media skipped -> ws not open: ${opts.mediaUrl ?? ''}`)
|
|
110
|
+
dcgLogger(`outbound media skipped -> ws not isWsOpen failed open: ${opts.mediaUrl ?? ''}`)
|
|
83
111
|
return
|
|
84
112
|
}
|
|
85
113
|
|
|
86
114
|
const mediaUrl = opts.mediaUrl
|
|
87
|
-
|
|
88
|
-
if (mediaUrl &&
|
|
89
|
-
|
|
90
|
-
|
|
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)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const { sessionId, agentId } = getInfoBySessionKey(sessionKey)
|
|
125
|
+
if (!msgCtx.sessionId) {
|
|
126
|
+
msgCtx.sessionId = sessionId
|
|
91
127
|
}
|
|
92
|
-
if (mediaUrl
|
|
93
|
-
|
|
128
|
+
if (!mediaUrl || !msgCtx.sessionId) {
|
|
129
|
+
dcgLogger(`dcgchat: sendMedia skipped (duplicate in session): ${mediaUrl} sessionId=${msgCtx.sessionId} sessionKey=${sessionKey}`)
|
|
130
|
+
return
|
|
94
131
|
}
|
|
95
132
|
|
|
96
133
|
const fileName = mediaUrl?.split(/[\\/]/).pop() || ''
|
|
@@ -98,16 +135,19 @@ export async function sendDcgchatMedia(opts: DcgchatMediaSendOptions): Promise<v
|
|
|
98
135
|
try {
|
|
99
136
|
const botToken = msgCtx.botToken ?? getOpenClawConfig()?.channels?.["dcgchat-test"]?.botToken ?? ''
|
|
100
137
|
const url = opts.mediaUrl ? await ossUpload(opts.mediaUrl, botToken, 1) : ''
|
|
138
|
+
if (!msgCtx.agentId) {
|
|
139
|
+
msgCtx.agentId = agentId
|
|
140
|
+
}
|
|
101
141
|
wsSendRaw(msgCtx, {
|
|
102
142
|
response: opts.text ?? '',
|
|
103
|
-
|
|
143
|
+
is_finish: `${msgCtx?.messageId}`?.length === 13 ? -1 : 0,
|
|
104
144
|
files: [{ url, name: fileName }]
|
|
105
145
|
})
|
|
106
146
|
dcgLogger(`dcgchat: sendMedia session=${sessionKey}, file=${fileName}`)
|
|
107
147
|
} catch (error) {
|
|
108
148
|
wsSendRaw(msgCtx, {
|
|
109
149
|
response: opts.text ?? '',
|
|
110
|
-
|
|
150
|
+
is_finish: `${msgCtx?.messageId}`?.length === 13 ? -1 : 0,
|
|
111
151
|
files: [{ url: opts.mediaUrl ?? '', name: fileName }]
|
|
112
152
|
})
|
|
113
153
|
dcgLogger(`dcgchat: error sendMedia session=${sessionKey}: ${String(error)}`, 'error')
|
|
@@ -283,8 +323,9 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
283
323
|
}
|
|
284
324
|
},
|
|
285
325
|
sendMedia: async (ctx) => {
|
|
286
|
-
const
|
|
287
|
-
const
|
|
326
|
+
const normalizedFromTo = normalizeSessionTarget(ctx.to)
|
|
327
|
+
const isCron = ctx.to.indexOf('dcg-cron:') >= 0 || extractCronJobIdFromIsolatedSessionKey(normalizedFromTo) !== null
|
|
328
|
+
const to = resolveIsolatedCronSessionToJobSessionKey(normalizedFromTo)
|
|
288
329
|
const outboundCtx = getOutboundMsgParams(to)
|
|
289
330
|
const msgCtx = getParamsMessage(to) ?? outboundCtx
|
|
290
331
|
const cronMsgId = getCronMessageId(to)
|
|
@@ -301,7 +342,11 @@ export const dcgchatPlugin: ChannelPlugin<ResolvedDcgchatAccount> = {
|
|
|
301
342
|
}
|
|
302
343
|
|
|
303
344
|
dcgLogger(`channel sendMedia to ${ctx.to}`)
|
|
304
|
-
await sendDcgchatMedia({
|
|
345
|
+
await sendDcgchatMedia({
|
|
346
|
+
sessionKey: to || '',
|
|
347
|
+
mediaUrl: ctx.mediaUrl || '',
|
|
348
|
+
...(isCron ? { messageId } : {})
|
|
349
|
+
})
|
|
305
350
|
return {
|
|
306
351
|
channel: "dcgchat-test",
|
|
307
352
|
messageId,
|
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
|
@@ -180,6 +180,29 @@ export function getChildSessionKeysTrackedForRequester(requesterSessionKey: stri
|
|
|
180
180
|
return out
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
/**
|
|
184
|
+
* 自根 requester 起 BFS 收集所有已跟踪后代子会话(含嵌套)。网关 abort 时宜自深到浅,调用方对结果 `.reverse()` 后再逐个 chat.abort。
|
|
185
|
+
*/
|
|
186
|
+
export function getDescendantSessionKeysForRequester(rootRequesterSessionKey: string): string[] {
|
|
187
|
+
const root = rootRequesterSessionKey.trim()
|
|
188
|
+
if (!root) return []
|
|
189
|
+
const ordered: string[] = []
|
|
190
|
+
const seen = new Set<string>()
|
|
191
|
+
let frontier = getChildSessionKeysTrackedForRequester(root)
|
|
192
|
+
while (frontier.length > 0) {
|
|
193
|
+
const next: string[] = []
|
|
194
|
+
for (const sk of frontier) {
|
|
195
|
+
const k = sk.trim()
|
|
196
|
+
if (!k || seen.has(k)) continue
|
|
197
|
+
seen.add(k)
|
|
198
|
+
ordered.push(k)
|
|
199
|
+
next.push(...getChildSessionKeysTrackedForRequester(k))
|
|
200
|
+
}
|
|
201
|
+
frontier = next
|
|
202
|
+
}
|
|
203
|
+
return ordered
|
|
204
|
+
}
|
|
205
|
+
|
|
183
206
|
/**
|
|
184
207
|
* 打断后清空本地子 agent 跟踪(runId、父子映射、子 runId→sessionKey),并唤醒 waitUntilSubagentsIdle,避免永久挂起。
|
|
185
208
|
*/
|
|
@@ -302,6 +325,16 @@ function resolveHookSessionKey(
|
|
|
302
325
|
return (args?.sessionKey || '').trim()
|
|
303
326
|
}
|
|
304
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
|
+
|
|
305
338
|
function trackSubagentLifecycle(eventName: string, event: any, args: any): void {
|
|
306
339
|
if (eventName === 'subagent_spawned') {
|
|
307
340
|
const runId = typeof event?.runId === 'string' ? event.runId : ''
|
|
@@ -328,7 +361,10 @@ export function monitoringToolMessage(api: OpenClawPluginApi) {
|
|
|
328
361
|
trackSubagentLifecycle(item.event, event, args)
|
|
329
362
|
const sk = resolveHookSessionKey(item.event, args ?? {})
|
|
330
363
|
if (sk) {
|
|
331
|
-
|
|
364
|
+
const toolHooksOk =
|
|
365
|
+
isSessionActiveForTool(sk) ||
|
|
366
|
+
(item.event === 'before_tool_call' && shouldRunBeforeToolCallWithoutRunningSession(event))
|
|
367
|
+
if (toolHooksOk) {
|
|
332
368
|
if (['after_tool_call', 'before_tool_call'].includes(item.event)) {
|
|
333
369
|
const { result: _result, ...rest } = event
|
|
334
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
|
|
|
@@ -35,8 +35,13 @@ export function handleGatewayEventMessage(msg: { event?: string; payload?: Recor
|
|
|
35
35
|
if (p?.action === 'removed') {
|
|
36
36
|
sendDcgchatCron(p?.jobId as string)
|
|
37
37
|
}
|
|
38
|
-
if (p?.action === 'finished') {
|
|
39
|
-
|
|
38
|
+
if (p?.action === 'finished' && p?.status === 'ok') {
|
|
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
|
}
|