






















@@ -1098,6 +1098,174 @@ describe("sessions tools", () => {
10981098});
10991099});
110011001101+it("sessions_send keeps delayed requester replies alive after a wait timeout", async () => {
1102+const calls: Array<{ method?: string; params?: unknown }> = [];
1103+const requesterKey = "agent:main:main";
1104+const targetKey = "agent:director1:main";
1105+let targetWaitCount = 0;
1106+callGatewayMock.mockImplementation(async (opts: unknown) => {
1107+const request = opts as { method?: string; params?: unknown };
1108+calls.push(request);
1109+if (request.method === "agent") {
1110+const params = request.params as { sessionKey?: string } | undefined;
1111+if (params?.sessionKey === targetKey) {
1112+return { runId: "run-target", status: "accepted", acceptedAt: 2000 };
1113+}
1114+if (params?.sessionKey === requesterKey) {
1115+return { runId: "run-requester", status: "accepted", acceptedAt: 2001 };
1116+}
1117+}
1118+if (request.method === "agent.wait") {
1119+const params = request.params as { runId?: string } | undefined;
1120+if (params?.runId === "run-target") {
1121+targetWaitCount += 1;
1122+return targetWaitCount === 1
1123+ ? { runId: "run-target", status: "timeout" }
1124+ : { runId: "run-target", status: "ok" };
1125+}
1126+if (params?.runId === "run-requester") {
1127+return { runId: "run-requester", status: "ok" };
1128+}
1129+}
1130+if (request.method === "chat.history") {
1131+const params = request.params as { sessionKey?: string } | undefined;
1132+if (params?.sessionKey === targetKey && targetWaitCount > 1) {
1133+return {
1134+messages: [
1135+{
1136+role: "assistant",
1137+content: [{ type: "text", text: "late director reply" }],
1138+timestamp: 20,
1139+},
1140+],
1141+};
1142+}
1143+if (params?.sessionKey === requesterKey) {
1144+return {
1145+messages: [
1146+{
1147+role: "assistant",
1148+content: [{ type: "text", text: "requester saw director" }],
1149+timestamp: 21,
1150+},
1151+],
1152+};
1153+}
1154+return { messages: [] };
1155+}
1156+return {};
1157+});
1158+1159+const tool = createOpenClawTools({
1160+agentSessionKey: requesterKey,
1161+agentChannel: "discord",
1162+config: {
1163+ ...TEST_CONFIG,
1164+session: {
1165+ ...TEST_CONFIG.session,
1166+agentToAgent: { maxPingPongTurns: 1 },
1167+},
1168+},
1169+}).find((candidate) => candidate.name === "sessions_send");
1170+expect(tool).toBeDefined();
1171+if (!tool) {
1172+throw new Error("missing sessions_send tool");
1173+}
1174+1175+const result = await tool.execute("call-delayed", {
1176+sessionKey: targetKey,
1177+message: "ping",
1178+timeoutSeconds: 1,
1179+});
1180+expect(result.details).toMatchObject({
1181+status: "accepted",
1182+sessionKey: targetKey,
1183+delivery: { status: "pending", mode: "announce" },
1184+});
1185+1186+await vi.waitFor(
1187+() => {
1188+const requesterReplyCall = calls.find(
1189+(call) =>
1190+call.method === "agent" &&
1191+(call.params as { sessionKey?: string } | undefined)?.sessionKey === requesterKey,
1192+);
1193+expect(requesterReplyCall).toBeDefined();
1194+},
1195+{ timeout: 2_000, interval: 5 },
1196+);
1197+1198+const requesterReplyCall = calls.find(
1199+(call) =>
1200+call.method === "agent" &&
1201+(call.params as { sessionKey?: string } | undefined)?.sessionKey === requesterKey,
1202+);
1203+const replyParams = requesterReplyCall?.params as
1204+| {
1205+extraSystemPrompt?: string;
1206+inputProvenance?: { sourceSessionKey?: string };
1207+message?: string;
1208+sessionKey?: string;
1209+}
1210+| undefined;
1211+expect(replyParams).toMatchObject({
1212+sessionKey: requesterKey,
1213+inputProvenance: { sourceSessionKey: targetKey },
1214+});
1215+expect(replyParams?.message).toContain("late director reply");
1216+expect(replyParams?.extraSystemPrompt).toContain("Agent-to-agent reply step");
1217+expect(replyParams?.extraSystemPrompt).toContain("Current agent: Agent 1 (requester)");
1218+expect(calls.find((call) => call.method === "send")).toBeUndefined();
1219+});
1220+1221+it("sessions_send preserves terminal timeouts without starting A2A", async () => {
1222+const calls: Array<{ method?: string; params?: unknown }> = [];
1223+const requesterKey = "agent:main:main";
1224+const targetKey = "agent:director1:main";
1225+callGatewayMock.mockImplementation(async (opts: unknown) => {
1226+const request = opts as { method?: string; params?: unknown };
1227+calls.push(request);
1228+if (request.method === "agent") {
1229+return { runId: "run-terminal", status: "accepted", acceptedAt: 2000 };
1230+}
1231+if (request.method === "agent.wait") {
1232+return {
1233+runId: "run-terminal",
1234+status: "timeout",
1235+endedAt: 3000,
1236+stopReason: "timeout",
1237+error: "agent run timed out",
1238+};
1239+}
1240+if (request.method === "chat.history") {
1241+return { messages: [] };
1242+}
1243+return {};
1244+});
1245+1246+const tool = createOpenClawTools({
1247+agentSessionKey: requesterKey,
1248+agentChannel: "discord",
1249+}).find((candidate) => candidate.name === "sessions_send");
1250+expect(tool).toBeDefined();
1251+if (!tool) {
1252+throw new Error("missing sessions_send tool");
1253+}
1254+1255+const result = await tool.execute("call-terminal", {
1256+sessionKey: targetKey,
1257+message: "ping",
1258+timeoutSeconds: 1,
1259+});
1260+expect(result.details).toMatchObject({
1261+status: "timeout",
1262+error: "agent run timed out",
1263+sessionKey: targetKey,
1264+});
1265+await new Promise((resolve) => setTimeout(resolve, 0));
1266+expect(calls.filter((call) => call.method === "agent")).toHaveLength(1);
1267+});
1268+11011269it("sessions_send skips duplicate A2A delivery for waited parent-owned native subagents", async () => {
11021270const calls: Array<{ method?: string; params?: unknown }> = [];
11031271const requesterKey = "agent:main:discord:direct:parent";
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。