Fix cross-conversation reply auto-resume

This commit is contained in:
shiyue
2026-06-22 18:22:53 +08:00
parent a50933807f
commit e15736e302
5 changed files with 410 additions and 243 deletions

View File

@@ -3230,6 +3230,7 @@ function normalizeCrossConversationReplyState(raw = {}) {
status,
createdAt: String(raw.createdAt || '').trim() || new Date().toISOString(),
hopCount: Math.max(0, Number.parseInt(String(raw.hopCount || 0), 10) || 0),
sourceAutoRun: raw.sourceAutoRun !== false,
replyText: truncateTextValue(String(raw.replyText || ''), CROSS_CONVERSATION_MAX_CONTENT_CHARS),
completedAt: raw.completedAt || null,
returnedAt: raw.returnedAt || null,
@@ -3339,6 +3340,7 @@ function crossConversationReplySummary(pending = {}) {
completedAt: pending.completedAt || null,
returnedAt: pending.returnedAt || null,
replyMessageId: pending.replyMessageId || null,
sourceAutoRun: pending.sourceAutoRun !== false,
preview: truncateTextValue(pending.replyText || '', 240),
};
}
@@ -3923,6 +3925,41 @@ function buildCrossConversationReplyContent(targetSession, replyText) {
return `线程「${targetTitle}」已返回消息:\n\n${truncateTextValue(replyText, CROSS_CONVERSATION_MAX_CONTENT_CHARS)}`;
}
function buildCrossConversationReplyAutoRunText(targetSession, replyText) {
const targetTitle = targetSession?.title || 'Untitled';
const body = truncateTextValue(replyText, CROSS_CONVERSATION_MAX_CONTENT_CHARS);
return `子对话「${targetTitle}」已返回。请基于以下返回继续当前任务:\n\n${body}`;
}
function startCrossConversationReplyAutoRun(sourceSession, targetSession, replyText, crossConversation) {
if (!sourceSession?.id) return false;
const runtimeText = buildCrossConversationReplyAutoRunText(targetSession, replyText);
const sourceWs = findViewingSessionWs(sourceSession.id);
const result = handleMessage(sourceWs, {
text: runtimeText,
sessionId: sourceSession.id,
mode: sourceSession.permissionMode || 'yolo',
agent: getSessionAgent(sourceSession),
}, {
hideInHistory: true,
runtimeText,
crossConversation,
mcpContext: { hopCount: crossConversation?.hopCount || 0 },
skipPendingCrossConversationFlush: true,
});
if (!result?.ok) {
plog('WARN', 'cross_conversation_reply_autorun_failed', {
sourceSessionId: sourceSession.id.slice(0, 8),
targetSessionId: targetSession?.id ? targetSession.id.slice(0, 8) : null,
requestId: crossConversation?.replyToRequestId || null,
code: result?.code || null,
message: result?.message || null,
});
return false;
}
return true;
}
function hasProcessedCrossConversationReply(session, requestId) {
const normalizedRequestId = String(requestId || '').trim();
if (!normalizedRequestId || !Array.isArray(session?.messages)) return false;
@@ -3959,6 +3996,7 @@ function sendCrossConversationMessage(args = {}, sourceSessionId = '', sourceHop
? truncateTextValue(args.content.trim(), CROSS_CONVERSATION_MAX_CONTENT_CHARS)
: '';
const expectReply = !!options.expectReply;
const sourceAutoRun = expectReply && options.sourceAutoRun !== false;
if (!sourceId) {
return mcpToolError('missing_source_conversation', '缺少来源对话 ID。');
@@ -4013,6 +4051,7 @@ function sendCrossConversationMessage(args = {}, sourceSessionId = '', sourceHop
status: 'waiting',
createdAt: now,
hopCount: crossConversation.hopCount,
sourceAutoRun,
replyText: '',
completedAt: null,
returnedAt: null,
@@ -4051,8 +4090,8 @@ function sendCrossConversationMessage(args = {}, sourceSessionId = '', sourceHop
...(requestId ? {
requestId,
status: 'waiting',
replyDelivery: 'display_only',
sourceAutoRun: false,
replyDelivery: sourceAutoRun ? 'auto_run' : 'display_only',
sourceAutoRun,
} : {}),
};
}
@@ -4141,6 +4180,7 @@ function deliverCrossConversationReply(requestId) {
const now = new Date().toISOString();
const replyMessageId = crypto.randomUUID();
const replyContent = buildCrossConversationReplyContent(targetSession, pending.replyText);
const sourceAutoRun = pending.sourceAutoRun !== false;
const crossConversation = {
messageId: replyMessageId,
sourceSessionId: targetSession.id,
@@ -4151,7 +4191,7 @@ function deliverCrossConversationReply(requestId) {
replyToRequestId: requestId,
processed: true,
processedAt: now,
autoRun: false,
autoRun: sourceAutoRun,
};
updatePendingCrossConversationReply(requestId, (draft) => {
@@ -4185,6 +4225,9 @@ function deliverCrossConversationReply(requestId) {
});
deletePendingCrossConversationReply(requestId);
broadcastSessionList();
if (sourceAutoRun) {
startCrossConversationReplyAutoRun(sourceSession, targetSession, pending.replyText, crossConversation);
}
return true;
}
@@ -6266,8 +6309,10 @@ function handleMessage(ws, msg, options = {}) {
let session;
if (sessionId) {
reconcilePendingCrossConversationReplies();
flushPendingCrossConversationReplies(sessionId);
if (!options.skipPendingCrossConversationFlush) {
reconcilePendingCrossConversationReplies();
flushPendingCrossConversationReplies(sessionId);
}
session = loadSession(sessionId);
}
if (!session) {
@@ -7061,7 +7106,7 @@ function codexAppCommunicationDynamicTools() {
},
requestReply: {
type: 'boolean',
description: '可选。若为 true新对话完成本轮输出后会把回复作为已处理的只读消息写回来源对话,不会再次触发来源对话运行。',
description: '可选。若为 true新对话完成本轮输出后会把回复写回来源对话并继续触发来源对话运行。',
},
},
additionalProperties: false,
@@ -7070,7 +7115,7 @@ function codexAppCommunicationDynamicTools() {
{
name: 'ccweb_request_reply',
namespace: 'ccweb',
description: '向另一个 cc-web 对话发送消息,并在目标对话完成本轮输出后把回复作为已处理的只读消息写回当前对话;写回不会再次触发当前对话运行。',
description: '向另一个 cc-web 对话发送消息,并在目标对话完成本轮输出后把回复写回当前对话,然后继续触发当前对话运行。',
inputSchema: {
type: 'object',
required: ['targetConversationId', 'content'],