[AI-assisted] fix(reply): wait for block replies before tools

This commit is contained in:
clawsweeper
2026-05-22 03:19:09 +00:00
parent d8c410c4b6
commit 32576209a2
4 changed files with 46 additions and 1 deletions
+1
View File
@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- CLI/agents: allow `openclaw agent --session-key` to target explicit session keys, including agent-scoped legacy keys. (#85121) Thanks @Kaspre.
- Auto-reply/ACP: wait for same-channel block reply delivery before starting tool work, while still honoring ACP dispatch aborts so stopped turns do not wait on slow channel sends. (#83722) Thanks @IWhatsskill.
- Agents/subagents: surface blocked child-run completions as errors instead of successful subagent finishes. (#80886) Thanks @TurboTheTurtle.
- Agents/Pi: treat accepted embedded `sessions_spawn` child-session handoffs as terminal progress so parent turns no longer report false non-deliverable failures. (#85054) Thanks @samzong.
- WhatsApp: update Baileys to `7.0.0-rc13` and drop the obsolete logger type patch.
@@ -285,6 +285,48 @@ describe("createAcpDispatchDeliveryCoordinator", () => {
expect(deliverySettled).toBe(true);
});
it("stops waiting for direct block delivery when the ACP dispatch aborts", async () => {
const delivered: unknown[] = [];
const controller = new AbortController();
let releaseDelivery: (() => void) | undefined;
let markDeliveryStarted: (() => void) | undefined;
const deliveryStarted = new Promise<void>((resolve) => {
markDeliveryStarted = resolve;
});
const deliveryGate = new Promise<void>((resolve) => {
releaseDelivery = resolve;
});
const dispatcher = createReplyDispatcher({
deliver: async (payload) => {
delivered.push(payload);
markDeliveryStarted?.();
await deliveryGate;
},
});
const coordinator = createAcpDispatchDeliveryCoordinator({
cfg: createAcpTestConfig(),
ctx: buildTestCtx({
Provider: "visiblechat",
Surface: "visiblechat",
SessionKey: "agent:codex-acp:session-1",
}),
dispatcher,
inboundAudio: false,
shouldRouteToOriginating: false,
abortSignal: controller.signal,
});
const deliveryPromise = coordinator.deliver("block", { text: "hello" }, { skipTts: true });
await deliveryStarted;
controller.abort();
await expect(deliveryPromise).resolves.toBe(true);
expect(delivered).toEqual([{ text: "hello" }]);
releaseDelivery?.();
await dispatcher.waitForIdle();
});
it("strips split TTS directives from visible ACP block delivery", async () => {
const dispatcher = createDispatcher();
const coordinator = createAcpDispatchDeliveryCoordinator({
@@ -192,6 +192,7 @@ export function createAcpDispatchDeliveryCoordinator(params: {
originatingChannel?: string;
originatingTo?: string;
onReplyStart?: () => Promise<void> | void;
abortSignal?: AbortSignal;
}): AcpDispatchDeliveryCoordinator {
const directChannel = normalizeOptionalLowercaseString(params.ctx.Provider ?? params.ctx.Surface);
const routedChannel = normalizeOptionalLowercaseString(params.originatingChannel);
@@ -460,7 +461,7 @@ export function createAcpDispatchDeliveryCoordinator(params: {
state.failedVisibleTextDelivery = true;
}
if (kind === "block" && delivered) {
await waitForReplyDispatcherIdle(params.dispatcher);
await waitForReplyDispatcherIdle(params.dispatcher, params.abortSignal);
}
return delivered;
};
+1
View File
@@ -386,6 +386,7 @@ export async function tryDispatchAcpReply(params: {
originatingChannel: params.originatingChannel,
originatingTo: params.originatingTo,
onReplyStart: params.onReplyStart,
abortSignal: params.abortSignal,
});
const identityPendingBeforeTurn = isSessionIdentityPending(