Files
openclaw/src/auto-reply/reply/dispatch-from-config.transcript.ts
T
Peter Steinberger eb38d5e486 fix(reply): report settled delivery outcomes once (#124773)
* refactor(reply): publish settled dispatch receipts

* refactor(reply): isolate dispatch outcome accounting

* refactor(discord): infer settled dispatch result

* test(reply): model settled dispatch receipts

* test(reply): type settled receipt fixtures

* test(telegram): declare settled final receipt

* test(telegram): adapt legacy dispatch fixtures

* fix(reply): complete settled receipt compatibility

* test(channels): keep delivery suite within lint budget

* test(channels): reuse settled count fixture

* test(channels): consolidate receipt assertions

* test(channels): extract delivery receipt fixtures

* fix(qa): recover stopped clients after config restart

* fix(reply): settle deferred delivery receipts

* test(channels): share delivery turn fixtures

* fix(channels): preserve non-visible reconciliation

* fix(reply): preserve settled receipt compatibility

* refactor(reply): make settled receipts canonical

* refactor(reply): absorb settlement at dispatcher boundary

* test(matrix): prove settled receipt precedence

* refactor(sessions): extract prompt snapshot types

* fix(reply): restore queued admission counts
2026-08-17 06:38:16 -07:00

152 lines
5.3 KiB
TypeScript

import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
import { runAgentHarnessBeforeMessageWriteHook } from "../../agents/harness/hook-helpers.js";
import {
appendAssistantMessageToSessionTranscript,
type SessionTranscriptDeliveryMirror,
} from "../../config/sessions/transcript.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { logVerbose } from "../../globals.js";
import { formatErrorMessage } from "../../infra/errors.js";
import { getReplyPayloadMetadata, type ReplyPayload } from "../reply-payload.js";
import type { ReplyDispatchDeliveryOutcome } from "./reply-dispatcher.js";
import type { ReplyDispatcher } from "./reply-dispatcher.types.js";
type SourceReplyTranscriptMirror = NonNullable<
NonNullable<ReturnType<typeof getReplyPayloadMetadata>>["sourceReplyTranscriptMirror"]
>;
type TranscriptMirror = SourceReplyTranscriptMirror & {
expectedSessionId?: string;
expectedLifecycleRevision?: string;
expectedWriterRunId?: string;
storePath?: string;
preferText?: boolean;
deliveryMirror?: SessionTranscriptDeliveryMirror;
transcriptOwner?: boolean;
};
export async function mirrorDeliveredReplyToTranscript(params: {
metadata?: TranscriptMirror;
cfg: OpenClawConfig;
}): Promise<void> {
const mirror = params.metadata;
if (!mirror) {
return;
}
try {
const result = await appendAssistantMessageToSessionTranscript({
sessionKey: mirror.sessionKey,
agentId: mirror.agentId,
...(mirror.expectedSessionId ? { expectedSessionId: mirror.expectedSessionId } : {}),
...(mirror.expectedLifecycleRevision !== undefined
? { expectedLifecycleRevision: mirror.expectedLifecycleRevision }
: {}),
...(mirror.expectedWriterRunId !== undefined
? { expectedWriterRunId: mirror.expectedWriterRunId }
: {}),
text: mirror.text,
mediaUrls: mirror.preferText && mirror.text ? undefined : mirror.mediaUrls,
idempotencyKey: mirror.idempotencyKey,
...(mirror.deliveryMirror ? { deliveryMirror: mirror.deliveryMirror } : {}),
...(mirror.storePath ? { storePath: mirror.storePath } : {}),
updateMode: "inline",
config: params.cfg,
beforeMessageWrite: runAgentHarnessBeforeMessageWriteHook,
});
if (!result.ok) {
logVerbose(`dispatch-from-config: transcript mirror skipped: ${result.reason}`);
}
} catch (error) {
logVerbose(
`dispatch-from-config: transcript mirror failed after delivery: ${formatErrorMessage(error)}`,
);
}
}
export function transcriptMirrorForDeliveredPayload(
metadata: TranscriptMirror,
payload: ReplyPayload,
): TranscriptMirror | undefined {
const sendable = resolveSendableOutboundReplyParts(payload);
if (!sendable.text && sendable.mediaUrls.length === 0) {
return undefined;
}
return {
...metadata,
text: sendable.text,
mediaUrls: sendable.mediaUrls.length > 0 ? sendable.mediaUrls : undefined,
};
}
export function captureDeliveredTranscriptMirror(params: {
dispatcher: ReplyDispatcher;
metadata?: TranscriptMirror;
captureToken?: object;
}): () => TranscriptMirror | undefined {
if (!params.metadata || !params.dispatcher.appendBeforeDeliver) {
return () => (params.metadata?.transcriptOwner ? undefined : params.metadata);
}
const metadata = params.metadata;
let deliveredMetadata: TranscriptMirror | undefined;
let observedFinal = false;
const { idempotencyKey, sessionKey } = metadata;
params.dispatcher.appendBeforeDeliver((payload, info) => {
if (info.kind !== "final") {
return payload;
}
if (getReplyPayloadMetadata(payload)?.finalDeliveryCapture !== params.captureToken) {
return payload;
}
observedFinal = true;
const payloadMetadata = getReplyPayloadMetadata(payload);
const payloadMirror = payloadMetadata?.sourceReplyTranscriptMirror;
if (
payloadMirror &&
payloadMirror.idempotencyKey === idempotencyKey &&
payloadMirror.sessionKey === sessionKey
) {
deliveredMetadata = transcriptMirrorForDeliveredPayload(
{
...payloadMirror,
...(metadata.expectedSessionId ? { expectedSessionId: metadata.expectedSessionId } : {}),
...(metadata.expectedLifecycleRevision !== undefined
? { expectedLifecycleRevision: metadata.expectedLifecycleRevision }
: {}),
...(metadata.expectedWriterRunId !== undefined
? { expectedWriterRunId: metadata.expectedWriterRunId }
: {}),
storePath: metadata.storePath,
},
payload,
);
} else if (
!payloadMirror &&
!metadata.transcriptOwner &&
(!idempotencyKey || metadata.deliveryMirror)
) {
deliveredMetadata = transcriptMirrorForDeliveredPayload(metadata, payload);
}
return payload;
});
return () =>
observedFinal ? deliveredMetadata : metadata.transcriptOwner ? undefined : metadata;
}
export async function mirrorTranscriptAfterDispatcherSettled(params: {
outcome: Promise<ReplyDispatchDeliveryOutcome>;
metadata: () => TranscriptMirror | undefined;
cfg: OpenClawConfig;
}): Promise<void> {
if ((await params.outcome) !== "delivered") {
return;
}
const metadata = params.metadata();
if (!metadata) {
return;
}
await mirrorDeliveredReplyToTranscript({
metadata,
cfg: params.cfg,
});
}