


























@@ -113,6 +113,58 @@ async function deliverDiscordDirectMessageCompletion(params: {
113113});
114114}
115115116+async function deliverSlackChannelAnnouncement(params: {
117+callGateway: typeof runtimeCallGateway;
118+isActive: boolean;
119+sessionId: string;
120+expectsCompletionMessage: boolean;
121+directIdempotencyKey: string;
122+completionDirectOrigin?: {
123+channel?: string;
124+to?: string;
125+accountId?: string;
126+threadId?: string | number;
127+};
128+queueEmbeddedPiMessage?: (sessionId: string, message: string) => boolean;
129+sendMessage?: typeof runtimeSendMessage;
130+internalEvents?: AgentInternalEvent[];
131+}) {
132+const origin = {
133+channel: "slack",
134+to: "channel:C123",
135+accountId: "acct-1",
136+} as const;
137+138+__testing.setDepsForTest({
139+callGateway: params.callGateway,
140+getRequesterSessionActivity: () => ({
141+sessionId: params.sessionId,
142+isActive: params.isActive,
143+}),
144+loadConfig: () => ({}) as never,
145+ ...(params.queueEmbeddedPiMessage
146+ ? { queueEmbeddedPiMessage: params.queueEmbeddedPiMessage }
147+ : {}),
148+ ...(params.sendMessage ? { sendMessage: params.sendMessage } : {}),
149+});
150+151+return deliverSubagentAnnouncement({
152+requesterSessionKey: "agent:main:slack:channel:C123",
153+targetRequesterSessionKey: "agent:main:slack:channel:C123",
154+triggerMessage: "child done",
155+steerMessage: "child done",
156+requesterOrigin: origin,
157+requesterSessionOrigin: origin,
158+completionDirectOrigin: params.completionDirectOrigin ?? origin,
159+directOrigin: origin,
160+requesterIsSubagent: false,
161+expectsCompletionMessage: params.expectsCompletionMessage,
162+bestEffortDeliver: true,
163+directIdempotencyKey: params.directIdempotencyKey,
164+internalEvents: params.internalEvents,
165+});
166+}
167+116168describe("resolveAnnounceOrigin threaded route targets", () => {
117169it("preserves stored thread ids when requester origin omits one for the same chat", () => {
118170expect(
@@ -399,7 +451,7 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
399451expect(result).toEqual(
400452expect.objectContaining({
401453delivered: true,
402-path: "direct-thread-fallback",
454+path: "direct-fallback",
403455}),
404456);
405457expect(callGateway).toHaveBeenCalledWith(
@@ -428,6 +480,106 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
428480);
429481});
430482483+it("uses a direct channel fallback when announce-agent returns no visible output", async () => {
484+const callGateway = createGatewayMock({
485+result: {
486+payloads: [],
487+},
488+});
489+const sendMessage = createSendMessageMock();
490+const result = await deliverSlackChannelAnnouncement({
491+ callGateway,
492+ sendMessage,
493+sessionId: "requester-session-channel",
494+isActive: false,
495+expectsCompletionMessage: true,
496+directIdempotencyKey: "announce-channel-fallback-empty",
497+internalEvents: [
498+{
499+type: "task_completion",
500+source: "subagent",
501+childSessionKey: "agent:worker:subagent:child",
502+childSessionId: "child-session-id",
503+announceType: "subagent task",
504+taskLabel: "channel completion smoke",
505+status: "ok",
506+statusLabel: "completed successfully",
507+result: "child completion output",
508+replyInstruction: "Summarize the result.",
509+},
510+],
511+});
512+513+expect(result).toEqual(
514+expect.objectContaining({
515+delivered: true,
516+path: "direct-fallback",
517+}),
518+);
519+expect(callGateway).toHaveBeenCalled();
520+expect(sendMessage).toHaveBeenCalledWith(
521+expect.objectContaining({
522+channel: "slack",
523+accountId: "acct-1",
524+to: "channel:C123",
525+threadId: undefined,
526+content: "child completion output",
527+requesterSessionKey: "agent:main:slack:channel:C123",
528+bestEffort: true,
529+idempotencyKey: "announce-channel-fallback-empty",
530+}),
531+);
532+});
533+534+it("falls back to the external requester route when completion origin is internal", async () => {
535+const callGateway = createGatewayMock({
536+result: {
537+payloads: [{ text: "child completion output" }],
538+},
539+});
540+const result = await deliverSlackChannelAnnouncement({
541+ callGateway,
542+sessionId: "requester-session-channel",
543+isActive: false,
544+expectsCompletionMessage: true,
545+directIdempotencyKey: "announce-channel-internal-origin",
546+completionDirectOrigin: {
547+channel: "webchat",
548+},
549+internalEvents: [
550+{
551+type: "task_completion",
552+source: "subagent",
553+childSessionKey: "agent:worker:subagent:child",
554+childSessionId: "child-session-id",
555+announceType: "subagent task",
556+taskLabel: "channel completion smoke",
557+status: "ok",
558+statusLabel: "completed successfully",
559+result: "child completion output",
560+replyInstruction: "Summarize the result.",
561+},
562+],
563+});
564+565+expect(result).toEqual(
566+expect.objectContaining({
567+delivered: true,
568+path: "direct",
569+}),
570+);
571+expect(callGateway).toHaveBeenCalledWith(
572+expect.objectContaining({
573+params: expect.objectContaining({
574+deliver: true,
575+channel: "slack",
576+accountId: "acct-1",
577+to: "channel:C123",
578+}),
579+}),
580+);
581+});
582+431583it("keeps direct external delivery for non-completion announces", async () => {
432584const callGateway = createGatewayMock();
433585await deliverSlackThreadAnnouncement({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。