






















@@ -1083,8 +1083,12 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
10831083expect(sendMessage).not.toHaveBeenCalled();
10841084});
108510851086-it("does not queue when an active Telegram requester cannot be woken directly", async () => {
1087-const callGateway = createGatewayMock();
1086+it("falls back to requester-agent handoff when an active Telegram requester cannot be woken", async () => {
1087+const callGateway = createGatewayMock({
1088+result: {
1089+payloads: [{ text: "child completion output" }],
1090+},
1091+});
10881092const sendMessage = createSendMessageMock();
10891093const queueEmbeddedPiMessageWithOutcome = createQueueOutcomeMock(false);
10901094const result = await deliverTelegramDirectMessageCompletion({
@@ -1109,47 +1113,95 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
11091113});
1110111411111115expectRecordFields(result, {
1112-delivered: false,
1116+delivered: true,
11131117path: "direct",
11141118phases: [
11151119{
11161120phase: "direct-primary",
1117-delivered: false,
1121+delivered: true,
11181122path: "direct",
1119-error:
1120-"active requester session could not be woken: queue_message_failed reason=not_streaming sessionId=requester-session-telegram gatewayHealth=live",
1121-},
1122-{
1123-phase: "steer-fallback",
1124-delivered: false,
1125-path: "none",
11261123error: undefined,
11271124},
11281125],
11291126});
1130-expect(queueEmbeddedPiMessageWithOutcome).toHaveBeenCalledTimes(2);
1131-expect(queueEmbeddedPiMessageWithOutcome).toHaveBeenNthCalledWith(
1132-1,
1133-"requester-session-telegram",
1134-"child done",
1135-{
1136-steeringMode: "all",
1137-debounceMs: 500,
1138-},
1139-);
1140-expect(queueEmbeddedPiMessageWithOutcome).toHaveBeenNthCalledWith(
1141-2,
1127+expect(queueEmbeddedPiMessageWithOutcome).toHaveBeenCalledTimes(1);
1128+expect(queueEmbeddedPiMessageWithOutcome).toHaveBeenCalledWith(
11421129"requester-session-telegram",
11431130"child done",
11441131{
11451132steeringMode: "all",
11461133debounceMs: 500,
11471134},
11481135);
1149-expect(callGateway).not.toHaveBeenCalled();
1136+expect(callGateway).toHaveBeenCalledTimes(1);
11501137expect(sendMessage).not.toHaveBeenCalled();
11511138});
115211391140+it("uses steer fallback when a completion handoff has no visible output", async () => {
1141+const callGateway = createGatewayMock({
1142+result: {
1143+payloads: [],
1144+},
1145+});
1146+const queueEmbeddedPiMessageWithOutcome = vi
1147+.fn<QueueEmbeddedPiMessageWithOutcome>()
1148+.mockImplementationOnce((sessionId: string) => ({
1149+queued: false,
1150+ sessionId,
1151+reason: "not_streaming",
1152+gatewayHealth: "live",
1153+}))
1154+.mockImplementationOnce((sessionId: string) => ({
1155+queued: true,
1156+ sessionId,
1157+target: "embedded_run",
1158+gatewayHealth: "live",
1159+}));
1160+const result = await deliverSlackChannelAnnouncement({
1161+ callGateway,
1162+sessionId: "requester-session-channel",
1163+isActive: false,
1164+expectsCompletionMessage: true,
1165+directIdempotencyKey: "announce-channel-empty-direct-steer-fallback",
1166+ queueEmbeddedPiMessageWithOutcome,
1167+internalEvents: [
1168+{
1169+type: "task_completion",
1170+source: "subagent",
1171+childSessionKey: "agent:worker:subagent:child",
1172+childSessionId: "child-session-id",
1173+announceType: "subagent task",
1174+taskLabel: "channel completion smoke",
1175+status: "ok",
1176+statusLabel: "completed successfully",
1177+result: "child completion output",
1178+replyInstruction: "Summarize the result.",
1179+},
1180+],
1181+});
1182+1183+expectRecordFields(result, {
1184+delivered: true,
1185+path: "steered",
1186+phases: [
1187+{
1188+phase: "direct-primary",
1189+delivered: false,
1190+path: "direct",
1191+error: "completion agent did not produce a visible reply",
1192+},
1193+{
1194+phase: "steer-fallback",
1195+delivered: true,
1196+path: "steered",
1197+error: undefined,
1198+},
1199+],
1200+});
1201+expect(queueEmbeddedPiMessageWithOutcome).toHaveBeenCalledTimes(2);
1202+expect(callGateway).toHaveBeenCalledTimes(1);
1203+});
1204+11531205it("reports failure when announce-agent returns no visible output", async () => {
11541206const callGateway = createGatewayMock({
11551207result: {
@@ -1846,6 +1898,88 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
18461898expect(sendMessage).not.toHaveBeenCalled();
18471899});
184819001901+it("requires message-tool delivery for channel subagent completions", async () => {
1902+const callGateway = createGatewayMock({
1903+result: {
1904+payloads: [{ text: "The subagent is done." }],
1905+},
1906+});
1907+const result = await deliverSlackChannelAnnouncement({
1908+ callGateway,
1909+sessionId: "requester-session-channel",
1910+isActive: false,
1911+expectsCompletionMessage: true,
1912+directIdempotencyKey: "announce-channel-subagent-message-tool",
1913+sourceTool: "subagent_announce",
1914+internalEvents: [
1915+{
1916+type: "task_completion",
1917+source: "subagent",
1918+childSessionKey: "agent:worker:subagent:child",
1919+childSessionId: "child-session-id",
1920+announceType: "subagent task",
1921+taskLabel: "channel completion smoke",
1922+status: "ok",
1923+statusLabel: "completed successfully",
1924+result: "child completion output",
1925+replyInstruction: "Summarize the result.",
1926+},
1927+],
1928+});
1929+1930+expectRecordFields(result, {
1931+delivered: false,
1932+path: "direct",
1933+error: "completion agent did not deliver through the message tool",
1934+});
1935+expectGatewayAgentParams(callGateway, {
1936+deliver: false,
1937+channel: "slack",
1938+accountId: "acct-1",
1939+to: "channel:C123",
1940+threadId: undefined,
1941+sourceReplyDeliveryMode: "message_tool_only",
1942+});
1943+});
1944+1945+it("keeps automatic final delivery for direct subagent completions", async () => {
1946+const callGateway = createGatewayMock({
1947+result: {
1948+payloads: [{ text: "The subagent is done." }],
1949+},
1950+});
1951+const result = await deliverDiscordDirectMessageCompletion({
1952+ callGateway,
1953+sourceTool: "subagent_announce",
1954+internalEvents: [
1955+{
1956+type: "task_completion",
1957+source: "subagent",
1958+childSessionKey: "agent:worker:subagent:child",
1959+childSessionId: "child-session-id",
1960+announceType: "subagent task",
1961+taskLabel: "direct completion smoke",
1962+status: "ok",
1963+statusLabel: "completed successfully",
1964+result: "child completion output",
1965+replyInstruction: "Summarize the result.",
1966+},
1967+],
1968+});
1969+1970+expectRecordFields(result, {
1971+delivered: true,
1972+path: "direct",
1973+});
1974+expectGatewayAgentParams(callGateway, {
1975+deliver: true,
1976+channel: "discord",
1977+accountId: "acct-1",
1978+to: "dm:U123",
1979+threadId: undefined,
1980+});
1981+});
1982+18491983it("falls back to the external requester route when completion origin is internal", async () => {
18501984const callGateway = createGatewayMock({
18511985result: {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。