@dcrays/dcgchat-test 0.4.14 → 0.4.16
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 +15 -10
- package/src/cronToolCall.ts +5 -6
- package/src/tool.ts +41 -6
- package/src/utils/global.ts +20 -4
package/package.json
CHANGED
package/src/bot.ts
CHANGED
|
@@ -143,7 +143,6 @@ const typingCallbacks = createTypingCallbacks({
|
|
|
143
143
|
* 处理一条用户消息,调用 Agent 并返回回复
|
|
144
144
|
*/
|
|
145
145
|
export async function handleDcgchatMessage(msg: InboundMessage, accountId: string): Promise<void> {
|
|
146
|
-
let completeText = ''
|
|
147
146
|
const config = getOpenClawConfig()
|
|
148
147
|
if (!config) {
|
|
149
148
|
dcgLogger('no config available', 'error')
|
|
@@ -251,7 +250,8 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
251
250
|
|
|
252
251
|
const sentMediaKeys = new Set<string>()
|
|
253
252
|
const getMediaKey = (url: string) => url.split(/[\\/]/).pop() ?? url
|
|
254
|
-
|
|
253
|
+
/** 与 Feishu snapshot 模式一致:payload.text 为当前轮助手全文快照,据此算增量,避免工具前后快照变短或非单调时丢字 */
|
|
254
|
+
let lastStreamSnapshot = ''
|
|
255
255
|
|
|
256
256
|
if (msg.content.skills_scope.length > 0 && !msg.content?.agent_clone_code && !ignoreToolCommand.includes(text?.trim())) {
|
|
257
257
|
const workspaceDir = getWorkspaceDir()
|
|
@@ -394,22 +394,27 @@ export async function handleDcgchatMessage(msg: InboundMessage, accountId: strin
|
|
|
394
394
|
onPartialReply: async (payload: ReplyPayload) => {
|
|
395
395
|
if (sessionStreamSuppressed.has(dcgSessionKey)) return
|
|
396
396
|
|
|
397
|
-
if (payload.text) {
|
|
398
|
-
completeText = payload.text
|
|
399
|
-
}
|
|
400
397
|
// --- Streaming text chunks ---
|
|
401
398
|
if (payload.text) {
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
|
|
399
|
+
const t = payload.text
|
|
400
|
+
let delta = ''
|
|
401
|
+
if (t.startsWith(lastStreamSnapshot)) {
|
|
402
|
+
delta = t.slice(lastStreamSnapshot.length)
|
|
403
|
+
lastStreamSnapshot = t
|
|
404
|
+
} else if (lastStreamSnapshot.startsWith(t)) {
|
|
405
|
+
// 快照缩短(模型修订等):不重复下发
|
|
406
|
+
} else {
|
|
407
|
+
// 与上一轮快照不衔接(常见于工具后快照从新的助手片段重新开始):整段下发
|
|
408
|
+
delta = t
|
|
409
|
+
lastStreamSnapshot = t
|
|
410
|
+
}
|
|
405
411
|
if (delta.trim()) {
|
|
406
412
|
const prev = streamChunkIdxBySessionKey.get(dcgSessionKey) ?? 0
|
|
407
413
|
streamChunkIdxBySessionKey.set(dcgSessionKey, prev + 1)
|
|
408
414
|
sendChunk(delta, outboundCtx, prev)
|
|
409
415
|
}
|
|
410
|
-
streamedTextLen = payload.text.length
|
|
411
416
|
} else {
|
|
412
|
-
dcgLogger(`onPartialReply no text: ${JSON.stringify(payload)}
|
|
417
|
+
dcgLogger(`onPartialReply no text (media/tool metadata): ${JSON.stringify(payload)}`)
|
|
413
418
|
}
|
|
414
419
|
// --- Media from payload ---
|
|
415
420
|
const mediaList = resolveReplyMediaList(payload)
|
package/src/cronToolCall.ts
CHANGED
|
@@ -132,14 +132,13 @@ function injectBestEffort(params: Record<string, unknown>, sk: string): Record<s
|
|
|
132
132
|
return newParams
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
// job.delivery
|
|
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
|
-
|
|
142
|
-
del.channel = "dcgchat-test"
|
|
138
|
+
;(job.delivery as CronDelivery).bestEffort = true
|
|
139
|
+
;(newParams.delivery as CronDelivery).to = `dcg-cron:${sk}`
|
|
140
|
+
;(newParams.delivery as CronDelivery).accountId = agentId
|
|
141
|
+
;(newParams.delivery as CronDelivery).channel = "dcgchat-test"
|
|
143
142
|
newParams.sessionKey = sk
|
|
144
143
|
return newParams
|
|
145
144
|
}
|
package/src/tool.ts
CHANGED
|
@@ -51,8 +51,30 @@ const eventList = [
|
|
|
51
51
|
{ event: 'after_tool_call', message: '' }
|
|
52
52
|
]
|
|
53
53
|
|
|
54
|
+
/** 子 agent 的 sessionKey 往往未写入 params map,回落到主会话 outbound 参数避免 messageId 缺失 */
|
|
55
|
+
function resolveOutboundParamsForSession(sk: string) {
|
|
56
|
+
const k = sk.trim()
|
|
57
|
+
let params = getEffectiveMsgParams(k)
|
|
58
|
+
if (params.messageId?.trim() || params.sessionId?.trim()) return params
|
|
59
|
+
const parent = requesterByChildSessionKey.get(k)
|
|
60
|
+
if (parent) {
|
|
61
|
+
const parentParams = getEffectiveMsgParams(parent)
|
|
62
|
+
if (parentParams.messageId?.trim() || parentParams.sessionId?.trim()) return parentParams
|
|
63
|
+
}
|
|
64
|
+
return params
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 主会话已 running 时,子会话上的工具/事件也应下发(否则子 key 无 running 状态会整段丢消息) */
|
|
68
|
+
function isSessionActiveForTool(sk: string): boolean {
|
|
69
|
+
const k = sk.trim()
|
|
70
|
+
if (!k) return false
|
|
71
|
+
if (getMsgStatus(k) === 'running') return true
|
|
72
|
+
const parent = requesterByChildSessionKey.get(k)
|
|
73
|
+
return parent ? getMsgStatus(parent) === 'running' : false
|
|
74
|
+
}
|
|
75
|
+
|
|
54
76
|
function sendToolCallMessage(sk: string, text: string, toolCallId: string, isCover: number) {
|
|
55
|
-
const params =
|
|
77
|
+
const params = resolveOutboundParamsForSession(sk)
|
|
56
78
|
const content = { is_finish: -1, tool_call_id: toolCallId, is_cover: isCover, thinking_content: text, response: '' }
|
|
57
79
|
wsSendRaw(params, content, false)
|
|
58
80
|
}
|
|
@@ -306,14 +328,27 @@ export function monitoringToolMessage(api: OpenClawPluginApi) {
|
|
|
306
328
|
trackSubagentLifecycle(item.event, event, args)
|
|
307
329
|
const sk = resolveHookSessionKey(item.event, args ?? {})
|
|
308
330
|
if (sk) {
|
|
309
|
-
|
|
310
|
-
if (status === 'running') {
|
|
331
|
+
if (isSessionActiveForTool(sk)) {
|
|
311
332
|
if (['after_tool_call', 'before_tool_call'].includes(item.event)) {
|
|
312
333
|
const { result: _result, ...rest } = event
|
|
313
334
|
dcgLogger(`工具调用结果: ~ event:${item.event} ~ params:${JSON.stringify(rest)}`)
|
|
314
335
|
|
|
315
336
|
if (item.event === 'before_tool_call') {
|
|
316
|
-
|
|
337
|
+
const hookResult = cronToolCall(rest, sk)
|
|
338
|
+
const text = JSON.stringify({
|
|
339
|
+
type: item.event,
|
|
340
|
+
specialIdentification: 'dcgchat_tool_call_special_identification',
|
|
341
|
+
callId: event.toolCallId || event.runId || Date.now().toString(),
|
|
342
|
+
...rest,
|
|
343
|
+
status: 'running'
|
|
344
|
+
})
|
|
345
|
+
sendToolCallMessage(
|
|
346
|
+
sk,
|
|
347
|
+
text,
|
|
348
|
+
event.toolCallId || event.runId || Date.now().toString(),
|
|
349
|
+
0
|
|
350
|
+
)
|
|
351
|
+
return hookResult
|
|
317
352
|
}
|
|
318
353
|
const text = JSON.stringify({
|
|
319
354
|
type: item.event,
|
|
@@ -329,7 +364,7 @@ export function monitoringToolMessage(api: OpenClawPluginApi) {
|
|
|
329
364
|
item.event === 'after_tool_call' ? 1 : 0
|
|
330
365
|
)
|
|
331
366
|
} else if (item.event) {
|
|
332
|
-
const msgCtx =
|
|
367
|
+
const msgCtx = resolveOutboundParamsForSession(sk)
|
|
333
368
|
if (item.event === 'llm_output') {
|
|
334
369
|
if (event.lastAssistant?.errorMessage === '1003-额度不足请充值') {
|
|
335
370
|
const message = '您的积分已消耗完,您可以通过充值积分来继续使用'
|
|
@@ -346,7 +381,7 @@ export function monitoringToolMessage(api: OpenClawPluginApi) {
|
|
|
346
381
|
params: item.message
|
|
347
382
|
})
|
|
348
383
|
sendToolCallMessage(sk, text, event.runId || Date.now().toString(), 0)
|
|
349
|
-
dcgLogger(`工具调用结果: ~ event:${item.event}
|
|
384
|
+
dcgLogger(`工具调用结果: ~ event:${item.event}`)
|
|
350
385
|
}
|
|
351
386
|
}
|
|
352
387
|
} else if (item.event !== 'before_tool_call') {
|
package/src/utils/global.ts
CHANGED
|
@@ -108,18 +108,34 @@ export function clearSentMediaKeys(messageId?: string) {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
/** 每个 sessionKey 下多个定时任务 messageId,入队在尾、出队在头(FIFO) */
|
|
112
|
+
const cronMessageIdMap = new Map<string, string[]>()
|
|
112
113
|
|
|
113
114
|
export function setCronMessageId(sk: string, messageId: string) {
|
|
114
|
-
|
|
115
|
+
if (!sk?.trim() || !messageId?.trim()) return
|
|
116
|
+
let q = cronMessageIdMap.get(sk)
|
|
117
|
+
if (!q) {
|
|
118
|
+
q = []
|
|
119
|
+
cronMessageIdMap.set(sk, q)
|
|
120
|
+
}
|
|
121
|
+
q.push(messageId)
|
|
115
122
|
}
|
|
116
123
|
|
|
124
|
+
/** 窥视队首 messageId(不移除),供发送中与 finished 配对使用 */
|
|
117
125
|
export function getCronMessageId(sk: string): string {
|
|
118
|
-
|
|
126
|
+
if (!sk?.trim()) return ''
|
|
127
|
+
return cronMessageIdMap.get(sk)?.[0] ?? ''
|
|
119
128
|
}
|
|
120
129
|
|
|
130
|
+
/** 弹出队首一条,与一次 finished 对应;队列为空时移除 key */
|
|
121
131
|
export function removeCronMessageId(sk: string) {
|
|
122
|
-
|
|
132
|
+
if (!sk?.trim()) return
|
|
133
|
+
const q = cronMessageIdMap.get(sk)
|
|
134
|
+
if (!q?.length) return
|
|
135
|
+
q.shift()
|
|
136
|
+
if (q.length === 0) {
|
|
137
|
+
cronMessageIdMap.delete(sk)
|
|
138
|
+
}
|
|
123
139
|
}
|
|
124
140
|
|
|
125
141
|
export const getSessionKey = (content: any, accountId: string) => {
|