


























@@ -76,6 +76,43 @@ async function deliverSlackThreadAnnouncement(params: {
7676});
7777}
787879+async function deliverDiscordDirectMessageCompletion(params: {
80+callGateway: typeof runtimeCallGateway;
81+sendMessage?: typeof runtimeSendMessage;
82+internalEvents?: AgentInternalEvent[];
83+}) {
84+const origin = {
85+channel: "discord",
86+to: "dm:U123",
87+accountId: "acct-1",
88+};
89+__testing.setDepsForTest({
90+callGateway: params.callGateway,
91+getRequesterSessionActivity: () => ({
92+sessionId: "requester-session-dm",
93+isActive: false,
94+}),
95+loadConfig: () => ({}) as never,
96+ ...(params.sendMessage ? { sendMessage: params.sendMessage } : {}),
97+});
98+99+return deliverSubagentAnnouncement({
100+requesterSessionKey: "agent:main:discord:dm:U123",
101+targetRequesterSessionKey: "agent:main:discord:dm:U123",
102+triggerMessage: "child done",
103+steerMessage: "child done",
104+requesterOrigin: origin,
105+requesterSessionOrigin: origin,
106+completionDirectOrigin: origin,
107+directOrigin: origin,
108+requesterIsSubagent: false,
109+expectsCompletionMessage: true,
110+bestEffortDeliver: true,
111+directIdempotencyKey: "announce-dm-fallback-empty",
112+internalEvents: params.internalEvents,
113+});
114+}
115+79116describe("resolveAnnounceOrigin threaded route targets", () => {
80117it("preserves stored thread ids when requester origin omits one for the same chat", () => {
81118expect(
@@ -332,6 +369,65 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
332369);
333370});
334371372+it("uses direct fallback for completion DMs without a thread id when announce-agent returns no visible output", async () => {
373+const callGateway = createGatewayMock({
374+result: {
375+payloads: [],
376+},
377+});
378+const sendMessage = createSendMessageMock();
379+const result = await deliverDiscordDirectMessageCompletion({
380+ callGateway,
381+ sendMessage,
382+internalEvents: [
383+{
384+type: "task_completion",
385+source: "music_generation",
386+childSessionKey: "music_generate:task-123",
387+childSessionId: "task-123",
388+announceType: "music generation task",
389+taskLabel: "night-drive synthwave",
390+status: "ok",
391+statusLabel: "completed successfully",
392+result: "Generated 1 track.\nMEDIA:/tmp/generated-night-drive.mp3",
393+mediaUrls: ["/tmp/generated-night-drive.mp3"],
394+replyInstruction: "Deliver the generated music.",
395+},
396+],
397+});
398+399+expect(result).toEqual(
400+expect.objectContaining({
401+delivered: true,
402+path: "direct-thread-fallback",
403+}),
404+);
405+expect(callGateway).toHaveBeenCalledWith(
406+expect.objectContaining({
407+method: "agent",
408+params: expect.objectContaining({
409+deliver: true,
410+channel: "discord",
411+accountId: "acct-1",
412+to: "dm:U123",
413+threadId: undefined,
414+}),
415+}),
416+);
417+expect(sendMessage).toHaveBeenCalledWith(
418+expect.objectContaining({
419+channel: "discord",
420+accountId: "acct-1",
421+to: "dm:U123",
422+threadId: undefined,
423+content: "Generated 1 track.\nMEDIA:/tmp/generated-night-drive.mp3",
424+requesterSessionKey: "agent:main:discord:dm:U123",
425+bestEffort: true,
426+idempotencyKey: "announce-dm-fallback-empty",
427+}),
428+);
429+});
430+335431it("keeps direct external delivery for non-completion announces", async () => {
336432const callGateway = createGatewayMock();
337433await deliverSlackThreadAnnouncement({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。