Merge cf7c6cb94b75b4ec34b63d1d14ae8ca61de1f32b into 598f1826d8b2bc969aace2c6459824737667218c

This commit is contained in:
Stephen Schoettler 2026-03-20 20:20:16 -07:00 committed by GitHub
commit 65b2fe294a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 64 additions and 2 deletions

View File

@ -217,7 +217,7 @@ export function createSessionsSendTool(opts?: {
const timeoutSeconds =
typeof params.timeoutSeconds === "number" && Number.isFinite(params.timeoutSeconds)
? Math.max(0, Math.floor(params.timeoutSeconds))
: 30;
: 90;
const timeoutMs = timeoutSeconds * 1000;
const announceTimeoutMs = timeoutSeconds === 0 ? 30_000 : timeoutMs;
const idempotencyKey = crypto.randomUUID();
@ -321,15 +321,21 @@ export function createSessionsSendTool(opts?: {
} catch (err) {
const messageText =
err instanceof Error ? err.message : typeof err === "string" ? err : "error";
const isTimeout = messageText.includes("gateway timeout");
if (isTimeout) {
// Same late-reply fix: deliver via A2A when gateway-level timeout fires
startA2AFlow(undefined, runId);
}
return jsonResult({
runId,
status: messageText.includes("gateway timeout") ? "timeout" : "error",
status: isTimeout ? "timeout" : "error",
error: messageText,
sessionKey: displayKey,
});
}
if (waitStatus === "timeout") {
startA2AFlow(undefined, runId);
return jsonResult({
runId,
status: "timeout",

View File

@ -9,6 +9,11 @@ vi.mock("../../gateway/call.js", () => ({
callGateway: (opts: unknown) => callGatewayMock(opts),
}));
const runSessionsSendA2AFlowMock = vi.fn();
vi.mock("./sessions-send-tool.a2a.js", () => ({
runSessionsSendA2AFlow: (opts: unknown) => runSessionsSendA2AFlowMock(opts),
}));
type SessionsToolTestConfig = {
session: { scope: "per-sender"; mainKey: string };
tools: {
@ -399,6 +404,7 @@ describe("sessions_list transcriptPath resolution", () => {
describe("sessions_send gating", () => {
beforeEach(() => {
callGatewayMock.mockClear();
runSessionsSendA2AFlowMock.mockClear();
});
it("returns an error when neither sessionKey nor label is provided", async () => {
@ -449,4 +455,54 @@ describe("sessions_send gating", () => {
expect(callGatewayMock.mock.calls[0]?.[0]).toMatchObject({ method: "sessions.list" });
expect(result.details).toMatchObject({ status: "forbidden" });
});
it("sessions_send delivers reply via A2A when wait times out", async () => {
loadConfigMock.mockReturnValue({
session: { scope: "per-sender", mainKey: "main" },
tools: {
agentToAgent: { enabled: true },
sessions: { visibility: "all" },
},
});
const targetKey = "agent:main:discord:dm:user1";
const fakeRunId = "run-timeout-123";
// sessions.list for visibility check
callGatewayMock.mockImplementation((opts: { method: string }) => {
if (opts.method === "sessions.list") {
return Promise.resolve({
path: "/tmp/sessions.json",
sessions: [
{ key: targetKey, kind: "direct" },
{ key: MAIN_AGENT_SESSION_KEY, kind: "direct" },
],
});
}
if (opts.method === "agent") {
return Promise.resolve({ runId: fakeRunId });
}
if (opts.method === "agent.wait") {
return Promise.resolve({ status: "timeout", error: "timed out" });
}
return Promise.resolve({});
});
const tool = createMainSessionsSendTool();
const result = await tool.execute("call-timeout", {
sessionKey: targetKey,
message: "ping",
timeoutSeconds: 5,
});
expect(result.details).toMatchObject({
status: "timeout",
runId: fakeRunId,
});
expect(runSessionsSendA2AFlowMock).toHaveBeenCalledTimes(1);
const a2aArgs = runSessionsSendA2AFlowMock.mock.calls[0]?.[0] as Record<string, unknown>;
expect(a2aArgs.waitRunId).toBe(fakeRunId);
expect(a2aArgs.roundOneReply).toBeUndefined();
});
});