





















@@ -5,6 +5,7 @@ import {
55} from "../infra/outbound/session-binding-service.js";
66import type { AgentInternalEvent } from "./internal-events.js";
77import type {
8+EmbeddedPiQueueFailureReason,
89EmbeddedPiQueueMessageOptions,
910EmbeddedPiQueueMessageOutcome,
1011} from "./pi-embedded-runner/runs.js";
@@ -36,6 +37,17 @@ function createGatewayMock(response: Record<string, unknown> = {}) {
3637return vi.fn(async () => response) as unknown as typeof runtimeCallGateway;
3738}
383940+function createGatewaySequenceMock(
41+responses: Record<string, unknown>[],
42+): ReturnType<typeof vi.fn> & typeof runtimeCallGateway {
43+let index = 0;
44+return vi.fn(async () => {
45+const response = responses[Math.min(index, responses.length - 1)] ?? {};
46+index += 1;
47+return response;
48+}) as unknown as ReturnType<typeof vi.fn> & typeof runtimeCallGateway;
49+}
50+3951function createInProcessGatewayMock(response: Record<string, unknown> = {}) {
4052return vi.fn(async () => response) as unknown as typeof runtimeDispatchGatewayMethodInProcess;
4153}
@@ -78,6 +90,29 @@ function createQueueOutcomeMock(
7890);
7991}
809293+function createQueueOutcomeSequenceMock(
94+queuedOutcomes: (boolean | EmbeddedPiQueueFailureReason)[],
95+): ReturnType<typeof vi.fn<QueueEmbeddedPiMessageWithOutcome>> {
96+let index = 0;
97+return vi.fn((sessionId: string) => {
98+const outcome = queuedOutcomes[Math.min(index, queuedOutcomes.length - 1)] ?? false;
99+index += 1;
100+return outcome === true
101+ ? {
102+queued: true,
103+ sessionId,
104+target: "embedded_run",
105+gatewayHealth: "live",
106+}
107+ : {
108+queued: false,
109+ sessionId,
110+reason: typeof outcome === "string" ? outcome : "not_streaming",
111+gatewayHealth: "live",
112+};
113+});
114+}
115+81116const longChildCompletionOutput = [
82117"34/34 tests pass, clean build. Now docker repro:",
83118"Root cause: the requester's announce delivery accepted a prefix-only assistant payload as delivered.",
@@ -1170,6 +1205,104 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
11701205expect(sendMessage).not.toHaveBeenCalled();
11711206});
117212071208+it("forces message-tool thread completions after transcript-wait wake falls stale", async () => {
1209+const callGateway = createGatewaySequenceMock([
1210+{
1211+result: {
1212+payloads: [],
1213+},
1214+},
1215+{
1216+result: {
1217+payloads: [],
1218+messagingToolSentTargets: [
1219+{
1220+tool: "message",
1221+provider: "slack",
1222+accountId: "acct-1",
1223+to: "channel:C123",
1224+threadId: "171.222",
1225+text: "The background task completed.",
1226+},
1227+],
1228+},
1229+},
1230+]);
1231+const sendMessage = createSendMessageMock();
1232+const queueEmbeddedPiMessageWithOutcome = createQueueOutcomeSequenceMock([
1233+"transcript_commit_wait_unsupported",
1234+"no_active_run",
1235+]);
1236+const result = await deliverSlackThreadAnnouncement({
1237+ callGateway,
1238+ sendMessage,
1239+ queueEmbeddedPiMessageWithOutcome,
1240+sessionId: "requester-session-4",
1241+isActive: true,
1242+expectsCompletionMessage: true,
1243+directIdempotencyKey: "announce-thread-fallback-empty",
1244+internalEvents: [
1245+{
1246+type: "task_completion",
1247+source: "subagent",
1248+childSessionKey: "agent:worker:subagent:child",
1249+childSessionId: "child-session-id",
1250+announceType: "subagent task",
1251+taskLabel: "thread completion smoke",
1252+status: "ok",
1253+statusLabel: "completed successfully",
1254+result: "child completion output",
1255+replyInstruction: "Summarize the result.",
1256+},
1257+],
1258+});
1259+1260+expectRecordFields(result, {
1261+delivered: true,
1262+path: "direct",
1263+});
1264+expect(callGateway).toHaveBeenCalledTimes(2);
1265+expectGatewayAgentParams(callGateway, {
1266+deliver: true,
1267+channel: "slack",
1268+accountId: "acct-1",
1269+to: "channel:C123",
1270+threadId: "171.222",
1271+});
1272+expectRecordFields(mockCallArg(callGateway, 1).params, {
1273+deliver: false,
1274+channel: "slack",
1275+accountId: "acct-1",
1276+to: "channel:C123",
1277+threadId: "171.222",
1278+sourceReplyDeliveryMode: "message_tool_only",
1279+idempotencyKey: "announce-thread-fallback-empty:message-tool",
1280+});
1281+expect(queueEmbeddedPiMessageWithOutcome).toHaveBeenCalledTimes(2);
1282+expect(queueEmbeddedPiMessageWithOutcome).toHaveBeenNthCalledWith(
1283+1,
1284+"requester-session-4",
1285+"child done",
1286+{
1287+debounceMs: 500,
1288+deliveryTimeoutMs: 120_000,
1289+steeringMode: "all",
1290+waitForTranscriptCommit: true,
1291+},
1292+);
1293+expect(queueEmbeddedPiMessageWithOutcome).toHaveBeenNthCalledWith(
1294+2,
1295+"requester-session-4",
1296+"child done",
1297+{
1298+debounceMs: 500,
1299+deliveryTimeoutMs: 120_000,
1300+steeringMode: "all",
1301+},
1302+);
1303+expect(sendMessage).not.toHaveBeenCalled();
1304+});
1305+11731306it("keeps concise requester rewrites primary even when child output is long", async () => {
11741307const callGateway = createGatewayMock({
11751308result: {
@@ -1434,18 +1567,23 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
14341567expect(callGateway).toHaveBeenCalledTimes(1);
14351568});
143615691437-it("reports failure when announce-agent returns no visible output", async () => {
1570+it("reports message-tool failure when stale thread completion remains invisible", async () => {
14381571const callGateway = createGatewayMock({
14391572result: {
14401573payloads: [],
14411574},
14421575});
14431576const sendMessage = createSendMessageMock();
1577+const queueEmbeddedPiMessageWithOutcome = createQueueOutcomeSequenceMock([
1578+"transcript_commit_wait_unsupported",
1579+"no_active_run",
1580+]);
14441581const result = await deliverSlackThreadAnnouncement({
14451582 callGateway,
14461583 sendMessage,
1584+ queueEmbeddedPiMessageWithOutcome,
14471585sessionId: "requester-session-4",
1448-isActive: false,
1586+isActive: true,
14491587expectsCompletionMessage: true,
14501588directIdempotencyKey: "announce-thread-fallback-empty",
14511589internalEvents: [
@@ -1467,9 +1605,9 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
14671605expectRecordFields(result, {
14681606delivered: false,
14691607path: "direct",
1470-error: "completion agent did not produce a visible reply",
1608+error: "completion agent did not deliver through the message tool",
14711609});
1472-expect(callGateway).toHaveBeenCalledTimes(1);
1610+expect(callGateway).toHaveBeenCalledTimes(2);
14731611expect(sendMessage).not.toHaveBeenCalled();
14741612});
14751613@@ -2141,18 +2279,23 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
21412279expect(sendMessage).not.toHaveBeenCalled();
21422280});
214322812144-it("reports channel completion failure when announce-agent returns no visible output", async () => {
2282+it("reports channel message-tool failure when stale completion remains invisible", async () => {
21452283const callGateway = createGatewayMock({
21462284result: {
21472285payloads: [],
21482286},
21492287});
21502288const sendMessage = createSendMessageMock();
2289+const queueEmbeddedPiMessageWithOutcome = createQueueOutcomeSequenceMock([
2290+"transcript_commit_wait_unsupported",
2291+"no_active_run",
2292+]);
21512293const result = await deliverSlackChannelAnnouncement({
21522294 callGateway,
21532295 sendMessage,
2296+ queueEmbeddedPiMessageWithOutcome,
21542297sessionId: "requester-session-channel",
2155-isActive: false,
2298+isActive: true,
21562299expectsCompletionMessage: true,
21572300directIdempotencyKey: "announce-channel-fallback-empty",
21582301internalEvents: [
@@ -2174,9 +2317,9 @@ describe("deliverSubagentAnnouncement completion delivery", () => {
21742317expectRecordFields(result, {
21752318delivered: false,
21762319path: "direct",
2177-error: "completion agent did not produce a visible reply",
2320+error: "completion agent did not deliver through the message tool",
21782321});
2179-expect(callGateway).toHaveBeenCalledTimes(1);
2322+expect(callGateway).toHaveBeenCalledTimes(2);
21802323expect(sendMessage).not.toHaveBeenCalled();
21812324});
21822325此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。