ACP: carry dedupe/projector updates onto configurable acpx branch

This commit is contained in:
Onur
2026-03-01 09:19:11 +01:00
committed by Onur Solmaz
parent f88bc09f85
commit 2466a9bb13
14 changed files with 1076 additions and 171 deletions

View File

@@ -11,6 +11,7 @@ import { readAcpSessionEntry } from "../../acp/runtime/session-meta.js";
import type { OpenClawConfig } from "../../config/config.js";
import type { TtsAutoMode } from "../../config/types.tts.js";
import { logVerbose } from "../../globals.js";
import { runMessageAction } from "../../infra/outbound/message-action-runner.js";
import { getSessionBindingService } from "../../infra/outbound/session-binding-service.js";
import { generateSecureUuid } from "../../infra/secure-random.js";
import { prefixSystemMessage } from "../../infra/system-message.js";
@@ -157,6 +158,7 @@ export async function tryDispatchAcpReply(params: {
originatingTo?: string;
shouldSendToolSummaries: boolean;
bypassForCommand: boolean;
onReplyStart?: () => Promise<void> | void;
recordProcessed: DispatchProcessedRecorder;
markIdle: (reason: string) => void;
}): Promise<AcpDispatchAttemptResult | null> {
@@ -182,9 +184,69 @@ export async function tryDispatchAcpReply(params: {
let queuedFinal = false;
let acpAccumulatedBlockText = "";
let acpBlockCount = 0;
let startedReplyLifecycle = false;
const toolUpdateMessageById = new Map<
string,
{
channel: string;
accountId?: string;
to: string;
threadId?: string | number;
messageId: string;
}
>();
const ensureReplyLifecycleStarted = async () => {
if (startedReplyLifecycle) {
return;
}
startedReplyLifecycle = true;
await params.onReplyStart?.();
};
const tryEditToolUpdate = async (payload: ReplyPayload, toolCallId: string): Promise<boolean> => {
if (!params.shouldRouteToOriginating || !params.originatingChannel || !params.originatingTo) {
return false;
}
const handle = toolUpdateMessageById.get(toolCallId);
if (!handle?.messageId) {
return false;
}
const message = payload.text?.trim();
if (!message) {
return false;
}
try {
await runMessageAction({
cfg: params.cfg,
action: "edit",
params: {
channel: handle.channel,
accountId: handle.accountId,
to: handle.to,
threadId: handle.threadId,
messageId: handle.messageId,
message,
},
sessionKey: params.ctx.SessionKey,
});
routedCounts.tool += 1;
return true;
} catch (error) {
logVerbose(
`dispatch-acp: tool message edit failed for ${toolCallId}: ${error instanceof Error ? error.message : String(error)}`,
);
return false;
}
};
const deliverAcpPayload = async (
kind: ReplyDispatchKind,
payload: ReplyPayload,
meta?: {
toolCallId?: string;
allowEdit?: boolean;
},
): Promise<boolean> => {
if (kind === "block" && payload.text?.trim()) {
if (acpAccumulatedBlockText.length > 0) {
@@ -193,6 +255,9 @@ export async function tryDispatchAcpReply(params: {
acpAccumulatedBlockText += payload.text;
acpBlockCount += 1;
}
if ((payload.text?.trim() ?? "").length > 0 || payload.mediaUrl || payload.mediaUrls?.length) {
await ensureReplyLifecycleStarted();
}
const ttsPayload = await maybeApplyTtsToPayload({
payload,
@@ -204,6 +269,13 @@ export async function tryDispatchAcpReply(params: {
});
if (params.shouldRouteToOriginating && params.originatingChannel && params.originatingTo) {
const toolCallId = meta?.toolCallId?.trim();
if (kind === "tool" && meta?.allowEdit === true && toolCallId) {
const edited = await tryEditToolUpdate(ttsPayload, toolCallId);
if (edited) {
return true;
}
}
const result = await routeReply({
payload: ttsPayload,
channel: params.originatingChannel,
@@ -219,6 +291,15 @@ export async function tryDispatchAcpReply(params: {
);
return false;
}
if (kind === "tool" && meta?.toolCallId && result.messageId) {
toolUpdateMessageById.set(meta.toolCallId, {
channel: params.originatingChannel,
accountId: params.ctx.AccountId,
to: params.originatingTo,
...(params.ctx.MessageThreadId != null ? { threadId: params.ctx.MessageThreadId } : {}),
messageId: result.messageId,
});
}
routedCounts[kind] += 1;
return true;
}