mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
fix(discord): surface silent reply-delivery skips and remove runtime.error optional-chain
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { formatDiscordReplySkip } from "./message-handler.process.js";
|
||||
|
||||
describe("formatDiscordReplySkip", () => {
|
||||
it("includes target and session when both are present for an aborted skip", () => {
|
||||
expect(
|
||||
formatDiscordReplySkip({
|
||||
kind: "final",
|
||||
reason: "aborted before delivery",
|
||||
target: "channel:123",
|
||||
sessionKey: "agent:main:discord:channel:123",
|
||||
}),
|
||||
).toBe(
|
||||
"discord final reply skipped (aborted before delivery): target=channel:123 session=agent:main:discord:channel:123",
|
||||
);
|
||||
});
|
||||
|
||||
it("renders the reasoning-payload reason with the same shape", () => {
|
||||
expect(
|
||||
formatDiscordReplySkip({
|
||||
kind: "block",
|
||||
reason: "reasoning payload",
|
||||
target: "channel:456",
|
||||
sessionKey: "agent:friday:discord:channel:456",
|
||||
}),
|
||||
).toBe(
|
||||
"discord block reply skipped (reasoning payload): target=channel:456 session=agent:friday:discord:channel:456",
|
||||
);
|
||||
});
|
||||
|
||||
it("omits the session tag when sessionKey is undefined", () => {
|
||||
expect(
|
||||
formatDiscordReplySkip({
|
||||
kind: "tool",
|
||||
reason: "aborted before delivery",
|
||||
target: "channel:456",
|
||||
}),
|
||||
).toBe("discord tool reply skipped (aborted before delivery): target=channel:456");
|
||||
});
|
||||
|
||||
it("treats an empty-string sessionKey the same as undefined", () => {
|
||||
expect(
|
||||
formatDiscordReplySkip({
|
||||
kind: "tool",
|
||||
reason: "reasoning payload",
|
||||
target: "channel:c1",
|
||||
sessionKey: "",
|
||||
}),
|
||||
).toBe("discord tool reply skipped (reasoning payload): target=channel:c1");
|
||||
});
|
||||
|
||||
it("preserves the kind discriminant in the message prefix", () => {
|
||||
for (const kind of ["tool", "block", "final"] as const) {
|
||||
expect(
|
||||
formatDiscordReplySkip({
|
||||
kind,
|
||||
reason: "aborted before delivery",
|
||||
target: "channel:1",
|
||||
sessionKey: "s",
|
||||
}),
|
||||
).toContain(`discord ${kind} reply skipped`);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -104,6 +104,23 @@ function formatDiscordReplyDeliveryFailure(params: {
|
||||
return `discord ${params.kind} reply failed (${context}): ${String(params.err)}`;
|
||||
}
|
||||
|
||||
type DiscordReplySkipReason = "aborted before delivery" | "reasoning payload";
|
||||
|
||||
export function formatDiscordReplySkip(params: {
|
||||
kind: "tool" | "block" | "final";
|
||||
reason: DiscordReplySkipReason;
|
||||
target: string;
|
||||
sessionKey?: string;
|
||||
}) {
|
||||
const context = [
|
||||
`target=${params.target}`,
|
||||
params.sessionKey ? `session=${params.sessionKey}` : undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
return `discord ${params.kind} reply skipped (${params.reason}): ${context}`;
|
||||
}
|
||||
|
||||
type DiscordMessageProcessObserver = {
|
||||
onFinalReplyStart?: () => void;
|
||||
onFinalReplyDelivered?: () => void;
|
||||
@@ -528,11 +545,29 @@ export async function processDiscordMessage(
|
||||
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
|
||||
deliver: async (payload: ReplyPayload, info) => {
|
||||
if (isProcessAborted(abortSignal)) {
|
||||
// Surface so operators don't chase missing replies when an abort
|
||||
// drops a model-produced text payload (see PR for the incident).
|
||||
logVerbose(
|
||||
formatDiscordReplySkip({
|
||||
kind: info.kind,
|
||||
reason: "aborted before delivery",
|
||||
target: deliverTarget,
|
||||
sessionKey: ctxPayload.SessionKey,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
const isFinal = info.kind === "final";
|
||||
if (payload.isReasoning) {
|
||||
// Reasoning/thinking payloads should not be delivered to Discord.
|
||||
logVerbose(
|
||||
formatDiscordReplySkip({
|
||||
kind: info.kind,
|
||||
reason: "reasoning payload",
|
||||
target: deliverTarget,
|
||||
sessionKey: ctxPayload.SessionKey,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (isFinal) {
|
||||
@@ -699,6 +734,16 @@ export async function processDiscordMessage(
|
||||
}
|
||||
}
|
||||
if (isProcessAborted(abortSignal)) {
|
||||
// Mirror the entry-point abort log so a mid-deliver abort (after
|
||||
// the preview path bowed out) does not silently drop the reply.
|
||||
logVerbose(
|
||||
formatDiscordReplySkip({
|
||||
kind: info.kind,
|
||||
reason: "aborted before delivery",
|
||||
target: deliverTarget,
|
||||
sessionKey: ctxPayload.SessionKey,
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -732,7 +777,7 @@ export async function processDiscordMessage(
|
||||
}
|
||||
},
|
||||
onError: (err, info) => {
|
||||
runtime.error?.(
|
||||
runtime.error(
|
||||
danger(
|
||||
formatDiscordReplyDeliveryFailure({
|
||||
kind: info.kind,
|
||||
|
||||
Reference in New Issue
Block a user