feat(FN-2099): merge fusion/fn-2099
This commit is contained in:
@@ -34,6 +34,7 @@ vi.mock("./pi.js", () => ({
|
|||||||
|
|
||||||
// Import the mocked functions for test control
|
// Import the mocked functions for test control
|
||||||
import { createKbAgent } from "./pi.js";
|
import { createKbAgent } from "./pi.js";
|
||||||
|
import { heartbeatLog } from "./logger.js";
|
||||||
const mockedCreateKbAgent = vi.mocked(createKbAgent);
|
const mockedCreateKbAgent = vi.mocked(createKbAgent);
|
||||||
|
|
||||||
// Mock store factory
|
// Mock store factory
|
||||||
@@ -721,6 +722,109 @@ describe("HeartbeatMonitor", () => {
|
|||||||
customMonitor.stop();
|
customMonitor.stop();
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("logs warning when session dispose throws during termination", async () => {
|
||||||
|
const warnSpy = vi.mocked(heartbeatLog.warn);
|
||||||
|
warnSpy.mockClear();
|
||||||
|
const session: AgentSession = {
|
||||||
|
dispose: vi.fn(() => {
|
||||||
|
throw new Error("dispose exploded");
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const updateAgentState = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const localStore = createMockStore({ updateAgentState });
|
||||||
|
const onTerminated = vi.fn();
|
||||||
|
const customMonitor = new HeartbeatMonitor({
|
||||||
|
store: localStore,
|
||||||
|
heartbeatTimeoutMs: 5000,
|
||||||
|
pollIntervalMs: 1000,
|
||||||
|
onTerminated,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.useFakeTimers({ shouldAdvanceTime: true });
|
||||||
|
customMonitor.start();
|
||||||
|
customMonitor.trackAgent("agent-001", session, "run-001");
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(10100);
|
||||||
|
await vi.advanceTimersByTimeAsync(100);
|
||||||
|
|
||||||
|
const warnMessages = warnSpy.mock.calls.map(([message]) => String(message));
|
||||||
|
expect(warnMessages.some((message) => message.includes("Error disposing session for agent-001") && message.includes("dispose exploded"))).toBe(true);
|
||||||
|
expect(updateAgentState).toHaveBeenCalledWith("agent-001", "terminated");
|
||||||
|
expect(onTerminated).toHaveBeenCalledWith("agent-001");
|
||||||
|
expect(customMonitor.getTrackedAgents()).toHaveLength(0);
|
||||||
|
|
||||||
|
customMonitor.stop();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs warning when updateAgentState throws during termination", async () => {
|
||||||
|
const warnSpy = vi.mocked(heartbeatLog.warn);
|
||||||
|
warnSpy.mockClear();
|
||||||
|
const session = createMockSession();
|
||||||
|
const localStore = createMockStore({
|
||||||
|
updateAgentState: vi.fn().mockRejectedValue(new Error("db connection lost")),
|
||||||
|
});
|
||||||
|
const onTerminated = vi.fn();
|
||||||
|
const customMonitor = new HeartbeatMonitor({
|
||||||
|
store: localStore,
|
||||||
|
heartbeatTimeoutMs: 5000,
|
||||||
|
pollIntervalMs: 1000,
|
||||||
|
onTerminated,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.useFakeTimers({ shouldAdvanceTime: true });
|
||||||
|
customMonitor.start();
|
||||||
|
customMonitor.trackAgent("agent-001", session, "run-001");
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(10100);
|
||||||
|
await vi.advanceTimersByTimeAsync(100);
|
||||||
|
|
||||||
|
const warnMessages = warnSpy.mock.calls.map(([message]) => String(message));
|
||||||
|
expect(warnMessages.some((message) => message.includes("Error terminating agent agent-001") && message.includes("db connection lost"))).toBe(true);
|
||||||
|
expect(onTerminated).toHaveBeenCalledWith("agent-001");
|
||||||
|
expect(customMonitor.getTrackedAgents()).toHaveLength(0);
|
||||||
|
|
||||||
|
customMonitor.stop();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("logs warnings from both dispose and state update when both fail", async () => {
|
||||||
|
const warnSpy = vi.mocked(heartbeatLog.warn);
|
||||||
|
warnSpy.mockClear();
|
||||||
|
const session: AgentSession = {
|
||||||
|
dispose: vi.fn(() => {
|
||||||
|
throw new Error("dispose exploded");
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
const localStore = createMockStore({
|
||||||
|
updateAgentState: vi.fn().mockRejectedValue(new Error("db connection lost")),
|
||||||
|
});
|
||||||
|
const onTerminated = vi.fn();
|
||||||
|
const customMonitor = new HeartbeatMonitor({
|
||||||
|
store: localStore,
|
||||||
|
heartbeatTimeoutMs: 5000,
|
||||||
|
pollIntervalMs: 1000,
|
||||||
|
onTerminated,
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.useFakeTimers({ shouldAdvanceTime: true });
|
||||||
|
customMonitor.start();
|
||||||
|
customMonitor.trackAgent("agent-001", session, "run-001");
|
||||||
|
|
||||||
|
vi.advanceTimersByTime(10100);
|
||||||
|
await vi.advanceTimersByTimeAsync(100);
|
||||||
|
|
||||||
|
const warnMessages = warnSpy.mock.calls.map(([message]) => String(message));
|
||||||
|
expect(warnMessages).toHaveLength(2);
|
||||||
|
expect(warnMessages.some((message) => message.includes("Error disposing session for agent-001") && message.includes("dispose exploded"))).toBe(true);
|
||||||
|
expect(warnMessages.some((message) => message.includes("Error terminating agent agent-001") && message.includes("db connection lost"))).toBe(true);
|
||||||
|
expect(onTerminated).toHaveBeenCalledWith("agent-001");
|
||||||
|
expect(customMonitor.getTrackedAgents()).toHaveLength(0);
|
||||||
|
|
||||||
|
customMonitor.stop();
|
||||||
|
vi.useRealTimers();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("untrackAgent", () => {
|
describe("untrackAgent", () => {
|
||||||
|
|||||||
@@ -1567,14 +1567,14 @@ export class HeartbeatMonitor {
|
|||||||
tracked.session.dispose();
|
tracked.session.dispose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Log but don't stop termination
|
// Log but don't stop termination
|
||||||
console.error(`[HeartbeatMonitor] Error disposing session for ${tracked.agentId}:`, err);
|
heartbeatLog.warn(`Error disposing session for ${tracked.agentId}: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update agent state to terminated
|
// Update agent state to terminated
|
||||||
try {
|
try {
|
||||||
await this.store.updateAgentState(tracked.agentId, "terminated");
|
await this.store.updateAgentState(tracked.agentId, "terminated");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[HeartbeatMonitor] Error terminating agent ${tracked.agentId}:`, err);
|
heartbeatLog.warn(`Error terminating agent ${tracked.agentId}: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from tracking
|
// Remove from tracking
|
||||||
|
|||||||
Reference in New Issue
Block a user