fix(agents): fall back for threadless completion delivery

This commit is contained in:
Peter Steinberger
2026-04-25 12:23:25 +01:00
parent bdba4fa1bf
commit f1470b52fb
2 changed files with 98 additions and 2 deletions

View File

@@ -76,6 +76,43 @@ async function deliverSlackThreadAnnouncement(params: {
});
}
async function deliverDiscordDirectMessageCompletion(params: {
callGateway: typeof runtimeCallGateway;
sendMessage?: typeof runtimeSendMessage;
internalEvents?: AgentInternalEvent[];
}) {
const origin = {
channel: "discord",
to: "dm:U123",
accountId: "acct-1",
};
__testing.setDepsForTest({
callGateway: params.callGateway,
getRequesterSessionActivity: () => ({
sessionId: "requester-session-dm",
isActive: false,
}),
loadConfig: () => ({}) as never,
...(params.sendMessage ? { sendMessage: params.sendMessage } : {}),
});
return deliverSubagentAnnouncement({
requesterSessionKey: "agent:main:discord:dm:U123",
targetRequesterSessionKey: "agent:main:discord:dm:U123",
triggerMessage: "child done",
steerMessage: "child done",
requesterOrigin: origin,
requesterSessionOrigin: origin,
completionDirectOrigin: origin,
directOrigin: origin,
requesterIsSubagent: false,
expectsCompletionMessage: true,
bestEffortDeliver: true,
directIdempotencyKey: "announce-dm-fallback-empty",
internalEvents: params.internalEvents,
});
}
describe("resolveAnnounceOrigin threaded route targets", () => {
it("preserves stored thread ids when requester origin omits one for the same chat", () => {
expect(
@@ -332,6 +369,65 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
);
});
it("uses direct fallback for completion DMs without a thread id when announce-agent returns no visible output", async () => {
const callGateway = createGatewayMock({
result: {
payloads: [],
},
});
const sendMessage = createSendMessageMock();
const result = await deliverDiscordDirectMessageCompletion({
callGateway,
sendMessage,
internalEvents: [
{
type: "task_completion",
source: "music_generation",
childSessionKey: "music_generate:task-123",
childSessionId: "task-123",
announceType: "music generation task",
taskLabel: "night-drive synthwave",
status: "ok",
statusLabel: "completed successfully",
result: "Generated 1 track.\nMEDIA:/tmp/generated-night-drive.mp3",
mediaUrls: ["/tmp/generated-night-drive.mp3"],
replyInstruction: "Deliver the generated music.",
},
],
});
expect(result).toEqual(
expect.objectContaining({
delivered: true,
path: "direct-thread-fallback",
}),
);
expect(callGateway).toHaveBeenCalledWith(
expect.objectContaining({
method: "agent",
params: expect.objectContaining({
deliver: true,
channel: "discord",
accountId: "acct-1",
to: "dm:U123",
threadId: undefined,
}),
}),
);
expect(sendMessage).toHaveBeenCalledWith(
expect.objectContaining({
channel: "discord",
accountId: "acct-1",
to: "dm:U123",
threadId: undefined,
content: "Generated 1 track.\nMEDIA:/tmp/generated-night-drive.mp3",
requesterSessionKey: "agent:main:discord:dm:U123",
bestEffort: true,
idempotencyKey: "announce-dm-fallback-empty",
}),
);
});
it("keeps direct external delivery for non-completion announces", async () => {
const callGateway = createGatewayMock();
await deliverSlackThreadAnnouncement({

View File

@@ -563,7 +563,7 @@ async function sendThreadCompletionFallback(params: {
const channel = params.channel?.trim();
const to = params.to?.trim();
const content = params.content.trim();
if (!channel || !to || !params.threadId || !content) {
if (!channel || !to || !content) {
return false;
}
await runAnnounceDeliveryWithRetry({
@@ -674,7 +674,7 @@ async function sendSubagentAnnounceDirectly(params: {
};
}
const threadCompletionFallbackText =
params.expectsCompletionMessage && deliveryTarget.deliver && deliveryTarget.threadId
params.expectsCompletionMessage && deliveryTarget.deliver
? extractThreadCompletionFallbackText(params.internalEvents)
: "";
let directAnnounceResponse: unknown;