fix(FN-2350): harden agent-generation session disposal diagnostics
- Use shared nonfatal diagnostics wrapper when disposing agent-generation AI sessions - Emit structured error context (sessionId and operation) instead of silently swallowing dispose failures - Add regression coverage verifying generation succeeds while dispose failures are logged as nonfatal diagnostics
This commit is contained in:
@@ -118,6 +118,70 @@ describe("agent-generation module", () => {
|
||||
).rejects.toThrow(SessionNotFoundError);
|
||||
});
|
||||
|
||||
it("emits a nonfatal diagnostic when session dispose throws after successful generation", async () => {
|
||||
vi.resetModules();
|
||||
|
||||
const diagnostics: CapturedDiagnostic[] = [];
|
||||
const diagnosticsModule = await import("./ai-session-diagnostics.js");
|
||||
diagnosticsModule.setDiagnosticsSink((level, scope, message, context) => {
|
||||
diagnostics.push({ level, scope, message, context });
|
||||
});
|
||||
|
||||
vi.doMock("@fusion/engine", () => ({
|
||||
createFnAgent: vi.fn(async () => {
|
||||
const messages: Array<{ role: string; content: string }> = [];
|
||||
return {
|
||||
session: {
|
||||
state: { messages },
|
||||
prompt: vi.fn(async () => {
|
||||
messages.push({
|
||||
role: "assistant",
|
||||
content: JSON.stringify({
|
||||
title: "Generated Agent",
|
||||
icon: "🤖",
|
||||
role: "custom",
|
||||
description: "Generated description",
|
||||
systemPrompt: "Generated prompt",
|
||||
thinkingLevel: "low",
|
||||
maxTurns: 12,
|
||||
}),
|
||||
});
|
||||
}),
|
||||
dispose: vi.fn(() => {
|
||||
throw new Error("dispose failed");
|
||||
}),
|
||||
},
|
||||
};
|
||||
}),
|
||||
}));
|
||||
|
||||
const agentGenerationModule = await import("./agent-generation.js");
|
||||
const session = await agentGenerationModule.startAgentGeneration(getUniqueIp(), "Role requiring generation");
|
||||
const spec = await agentGenerationModule.generateAgentSpec(session.id, "/tmp");
|
||||
|
||||
expect(spec).toMatchObject({
|
||||
title: "Generated Agent",
|
||||
role: "custom",
|
||||
});
|
||||
|
||||
expect(diagnostics).toContainEqual(
|
||||
expect.objectContaining({
|
||||
level: "error",
|
||||
scope: "agent-generation",
|
||||
message: "Failed to dispose agent-generation session",
|
||||
context: expect.objectContaining({
|
||||
sessionId: session.id,
|
||||
operation: "dispose-agent-session",
|
||||
error: expect.objectContaining({
|
||||
message: "dispose failed",
|
||||
}),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
diagnosticsModule.resetDiagnosticsSink();
|
||||
});
|
||||
|
||||
it("logs structured error diagnostics with sessionId and rethrows when AI generation fails", async () => {
|
||||
vi.resetModules();
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
*/
|
||||
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { createSessionDiagnostics } from "./ai-session-diagnostics.js";
|
||||
import { createSessionDiagnostics, nonfatal } from "./ai-session-diagnostics.js";
|
||||
|
||||
// Dynamic import for @fusion/core to get prompt override resolution
|
||||
|
||||
@@ -543,11 +543,15 @@ async function generateSpecWithAI(
|
||||
|
||||
return parseGenerationResponse(responseText);
|
||||
} finally {
|
||||
try {
|
||||
agent.session.dispose?.();
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
nonfatal(
|
||||
() => agent.session.dispose?.(),
|
||||
diagnostics,
|
||||
"Failed to dispose agent-generation session",
|
||||
{
|
||||
sessionId: session.id,
|
||||
operation: "dispose-agent-session",
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user