/**
 * Unit tests for the Auto-Session tracking system.
 *
 * Run with: bun test packages/mcp-server/src/__tests__/auto-session.test.ts
 */
import { afterEach, describe, expect, mock, test } from "bun:test";
import { AUTO_START_TRIGGERS, checkInactivity, destroyAutoSession, getActiveSessions, INACTIVITY_TIMEOUT_MS, initAutoSession, markExplicit, shutdownAllSessions, trackActivity, untrack, } from "../auto-session.js";
function makeMockClient() {
    return {
        startAgentSession: mock(async () => ({ session: { id: "sess-1" } })),
        endAgentSession: mock(async () => ({ session: { id: "sess-1" } })),
        getCard: mock(async () => ({
            card: { id: "card-1", title: "Test", labels: [], subtasks: [] },
        })),
        getAgentSession: mock(async () => ({ session: null })),
    };
}
const CARD_A = "aaaaaaaa-1111-1111-1111-111111111111";
const CARD_B = "bbbbbbbb-2222-2222-2222-222222222222";
const CARD_C = "cccccccc-3333-3333-3333-333333333333";
afterEach(() => {
    destroyAutoSession();
});
// ─── Basic auto-start ──────────────────────────────────────────────
describe("auto-start", () => {
    test("triggers session on autoStart=true with cardId", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        expect(client.startAgentSession).toHaveBeenCalledTimes(1);
        expect(client.startAgentSession).toHaveBeenCalledWith(CARD_A, {
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
            status: "working",
        });
        expect(getActiveSessions().size).toBe(1);
        const session = getActiveSessions().get(CARD_A);
        expect(session).toBeDefined();
        expect(session.isExplicit).toBe(false);
        expect(session.agentIdentifier).toBe("auto");
        expect(session.agentName).toBe("Auto-detected Agent");
    });
    test("does NOT trigger on autoStart=false", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A, { autoStart: false, client: client });
        expect(client.startAgentSession).not.toHaveBeenCalled();
        expect(getActiveSessions().size).toBe(0);
    });
    test("does NOT trigger when options is undefined", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A);
        expect(client.startAgentSession).not.toHaveBeenCalled();
        expect(getActiveSessions().size).toBe(0);
    });
    test("does NOT trigger when no client is available", async () => {
        // initAutoSession not called — no clientGetter
        await trackActivity(CARD_A, { autoStart: true });
        expect(getActiveSessions().size).toBe(0);
    });
    test("still tracks locally when startAgentSession API throws", async () => {
        const client = makeMockClient();
        client.startAgentSession = mock(async () => {
            throw new Error("Session already exists");
        });
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        // Should still be tracked despite API error
        expect(getActiveSessions().size).toBe(1);
        expect(getActiveSessions().get(CARD_A).agentIdentifier).toBe("auto");
    });
    test("uses clientGetter when no client in options", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A, { autoStart: true });
        expect(client.startAgentSession).toHaveBeenCalledTimes(1);
        expect(getActiveSessions().size).toBe(1);
    });
});
// ─── Activity tracking ─────────────────────────────────────────────
describe("activity tracking", () => {
    test("repeated activity updates lastActivityAt without re-starting", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        const firstActivity = getActiveSessions().get(CARD_A).lastActivityAt;
        await new Promise((r) => setTimeout(r, 10));
        await trackActivity(CARD_A, { autoStart: true, client: client });
        expect(client.startAgentSession).toHaveBeenCalledTimes(1);
        expect(getActiveSessions().get(CARD_A).lastActivityAt).toBeGreaterThan(firstActivity);
    });
    test("activity on existing session works even without autoStart flag", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        const firstActivity = getActiveSessions().get(CARD_A).lastActivityAt;
        await new Promise((r) => setTimeout(r, 10));
        // Second call with autoStart=false — should still update timestamp
        await trackActivity(CARD_A, { autoStart: false });
        expect(getActiveSessions().get(CARD_A).lastActivityAt).toBeGreaterThan(firstActivity);
    });
    test("activity on existing explicit session updates timestamp", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        markExplicit(CARD_A);
        const firstActivity = getActiveSessions().get(CARD_A).lastActivityAt;
        await new Promise((r) => setTimeout(r, 10));
        await trackActivity(CARD_A, { autoStart: true, client: client });
        expect(getActiveSessions().get(CARD_A).lastActivityAt).toBeGreaterThan(firstActivity);
        // Should NOT have called startAgentSession (session already exists)
        expect(client.startAgentSession).not.toHaveBeenCalled();
    });
});
// ─── Card switching ─────────────────────────────────────────────────
describe("card switching", () => {
    test("switching cards auto-ends previous auto-session", async () => {
        const client = makeMockClient();
        const endCb = mock(async () => { });
        initAutoSession(endCb, () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        expect(getActiveSessions().size).toBe(1);
        await trackActivity(CARD_B, { autoStart: true, client: client });
        expect(client.endAgentSession).toHaveBeenCalledWith(CARD_A, {
            status: "completed",
        });
        expect(endCb).toHaveBeenCalledWith(client, CARD_A, "completed");
        expect(getActiveSessions().has(CARD_A)).toBe(false);
        expect(getActiveSessions().has(CARD_B)).toBe(true);
    });
    test("explicit sessions are NOT auto-ended by card switching", async () => {
        const client = makeMockClient();
        const endCb = mock(async () => { });
        initAutoSession(endCb, () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        markExplicit(CARD_A);
        await trackActivity(CARD_B, { autoStart: true, client: client });
        // Card A should still be tracked (explicit)
        expect(getActiveSessions().has(CARD_A)).toBe(true);
        expect(getActiveSessions().has(CARD_B)).toBe(true);
        expect(client.endAgentSession).not.toHaveBeenCalled();
        expect(endCb).not.toHaveBeenCalled();
    });
    test("switching from card A to B to C ends A then B correctly", async () => {
        const client = makeMockClient();
        const endCb = mock(async () => { });
        initAutoSession(endCb, () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        await trackActivity(CARD_B, { autoStart: true, client: client });
        // A should be ended, B should be active
        expect(client.endAgentSession).toHaveBeenCalledWith(CARD_A, {
            status: "completed",
        });
        expect(getActiveSessions().has(CARD_A)).toBe(false);
        expect(getActiveSessions().has(CARD_B)).toBe(true);
        client.endAgentSession.mockClear();
        endCb.mockClear();
        await trackActivity(CARD_C, { autoStart: true, client: client });
        // B should be ended, C should be active
        expect(client.endAgentSession).toHaveBeenCalledWith(CARD_B, {
            status: "completed",
        });
        expect(getActiveSessions().has(CARD_B)).toBe(false);
        expect(getActiveSessions().has(CARD_C)).toBe(true);
    });
    test("card switch with endAgentSession API error still cleans up tracking", async () => {
        const client = makeMockClient();
        client.endAgentSession = mock(async () => {
            throw new Error("API unavailable");
        });
        const endCb = mock(async () => { });
        initAutoSession(endCb, () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        await trackActivity(CARD_B, { autoStart: true, client: client });
        // Card A should still be removed from tracking despite API error
        expect(getActiveSessions().has(CARD_A)).toBe(false);
        expect(getActiveSessions().has(CARD_B)).toBe(true);
        // Callback should still run even though endAgentSession threw
        expect(endCb).toHaveBeenCalledWith(client, CARD_A, "completed");
    });
    test("card switch with endCallback error still cleans up tracking", async () => {
        const client = makeMockClient();
        const endCb = mock(async () => {
            throw new Error("Pipeline failed");
        });
        initAutoSession(endCb, () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        await trackActivity(CARD_B, { autoStart: true, client: client });
        // Card A should be cleaned up despite callback error
        expect(getActiveSessions().has(CARD_A)).toBe(false);
        expect(getActiveSessions().has(CARD_B)).toBe(true);
        expect(client.endAgentSession).toHaveBeenCalledWith(CARD_A, {
            status: "completed",
        });
    });
    test("card switch with explicit session on A only ends non-explicit sessions", async () => {
        const client = makeMockClient();
        const endCb = mock(async () => { });
        initAutoSession(endCb, () => client);
        // A is explicit, B is auto
        markExplicit(CARD_A);
        await trackActivity(CARD_B, { autoStart: true, client: client });
        // Now switch to C — only B should be ended, A stays
        await trackActivity(CARD_C, { autoStart: true, client: client });
        expect(getActiveSessions().has(CARD_A)).toBe(true); // explicit, untouched
        expect(getActiveSessions().has(CARD_B)).toBe(false); // auto-ended
        expect(getActiveSessions().has(CARD_C)).toBe(true); // new auto
        expect(client.endAgentSession).toHaveBeenCalledWith(CARD_B, {
            status: "completed",
        });
    });
});
// ─── Inactivity timeout ─────────────────────────────────────────────
describe("inactivity timeout", () => {
    test("checkInactivity ends sessions past the timeout", async () => {
        const client = makeMockClient();
        const endCb = mock(async () => { });
        initAutoSession(endCb, () => client);
        // Manually add a session with old lastActivityAt
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 1000,
            lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 1000,
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        checkInactivity();
        // autoEndSession is fire-and-forget in checkInactivity, wait for it
        await new Promise((r) => setTimeout(r, 50));
        expect(client.endAgentSession).toHaveBeenCalledWith(CARD_A, {
            status: "completed",
        });
        expect(getActiveSessions().has(CARD_A)).toBe(false);
    });
    test("checkInactivity does NOT end sessions within timeout", () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: Date.now(),
            lastActivityAt: Date.now(), // just now — well within timeout
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        checkInactivity();
        expect(client.endAgentSession).not.toHaveBeenCalled();
        expect(getActiveSessions().has(CARD_A)).toBe(true);
    });
    test("checkInactivity skips explicit sessions even if timed out", () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 60000,
            lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 60000,
            isExplicit: true,
            agentIdentifier: "explicit",
            agentName: "Explicit Agent",
        });
        checkInactivity();
        expect(client.endAgentSession).not.toHaveBeenCalled();
        expect(getActiveSessions().has(CARD_A)).toBe(true);
    });
    test("checkInactivity handles mix of timed-out and active sessions", async () => {
        const client = makeMockClient();
        const endCb = mock(async () => { });
        initAutoSession(endCb, () => client);
        // CARD_A — timed out
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
            lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        // CARD_B — still active
        getActiveSessions().set(CARD_B, {
            cardId: CARD_B,
            startedAt: Date.now() - 1000,
            lastActivityAt: Date.now() - 1000,
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        // CARD_C — timed out but explicit
        getActiveSessions().set(CARD_C, {
            cardId: CARD_C,
            startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
            lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
            isExplicit: true,
            agentIdentifier: "explicit",
            agentName: "Explicit Agent",
        });
        checkInactivity();
        await new Promise((r) => setTimeout(r, 50));
        // Only CARD_A should be ended
        expect(getActiveSessions().has(CARD_A)).toBe(false);
        expect(getActiveSessions().has(CARD_B)).toBe(true);
        expect(getActiveSessions().has(CARD_C)).toBe(true);
        expect(client.endAgentSession).toHaveBeenCalledTimes(1);
        expect(client.endAgentSession).toHaveBeenCalledWith(CARD_A, {
            status: "completed",
        });
    });
    test("checkInactivity is a no-op when no client is available", () => {
        // No initAutoSession — clientGetter is null
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: 0,
            lastActivityAt: 0,
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        // Should not throw
        checkInactivity();
        // Session should still be there (can't end without client)
        expect(getActiveSessions().has(CARD_A)).toBe(true);
    });
    test("activity resets the inactivity timer", async () => {
        const client = makeMockClient();
        const endCb = mock(async () => { });
        initAutoSession(endCb, () => client);
        // Start with old lastActivityAt
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
            lastActivityAt: Date.now() - INACTIVITY_TIMEOUT_MS - 5000,
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        // Refresh activity
        await trackActivity(CARD_A, { autoStart: false });
        // Now check — should NOT be timed out
        checkInactivity();
        await new Promise((r) => setTimeout(r, 50));
        expect(getActiveSessions().has(CARD_A)).toBe(true);
        expect(client.endAgentSession).not.toHaveBeenCalled();
    });
});
// ─── markExplicit ───────────────────────────────────────────────────
describe("markExplicit", () => {
    test("marks existing auto-session as explicit", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        expect(getActiveSessions().get(CARD_A).isExplicit).toBe(false);
        markExplicit(CARD_A);
        expect(getActiveSessions().get(CARD_A).isExplicit).toBe(true);
        // agentIdentifier should remain "auto" since it was auto-started
        expect(getActiveSessions().get(CARD_A).agentIdentifier).toBe("auto");
    });
    test("creates new tracking entry for unknown card", () => {
        initAutoSession(mock(async () => { }), () => makeMockClient());
        markExplicit(CARD_A);
        expect(getActiveSessions().has(CARD_A)).toBe(true);
        expect(getActiveSessions().get(CARD_A).isExplicit).toBe(true);
        expect(getActiveSessions().get(CARD_A).agentIdentifier).toBe("explicit");
    });
    test("double markExplicit is idempotent", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        markExplicit(CARD_A);
        markExplicit(CARD_A);
        expect(getActiveSessions().get(CARD_A).isExplicit).toBe(true);
        expect(getActiveSessions().size).toBe(1);
    });
});
// ─── untrack ────────────────────────────────────────────────────────
describe("untrack", () => {
    test("removes session from tracking", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        expect(getActiveSessions().has(CARD_A)).toBe(true);
        untrack(CARD_A);
        expect(getActiveSessions().has(CARD_A)).toBe(false);
    });
    test("is a no-op for non-existent cardId", () => {
        initAutoSession(mock(async () => { }), () => makeMockClient());
        // Should not throw
        untrack(CARD_A);
        expect(getActiveSessions().size).toBe(0);
    });
    test("removes explicit sessions too", () => {
        initAutoSession(mock(async () => { }), () => makeMockClient());
        markExplicit(CARD_A);
        expect(getActiveSessions().has(CARD_A)).toBe(true);
        untrack(CARD_A);
        expect(getActiveSessions().has(CARD_A)).toBe(false);
    });
});
// ─── shutdown ───────────────────────────────────────────────────────
describe("shutdown", () => {
    test("ends all active sessions with paused status", async () => {
        const client = makeMockClient();
        const endCb = mock(async () => { });
        initAutoSession(endCb, () => client);
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: Date.now(),
            lastActivityAt: Date.now(),
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        await shutdownAllSessions();
        expect(client.endAgentSession).toHaveBeenCalledWith(CARD_A, {
            status: "paused",
        });
        expect(endCb).toHaveBeenCalledWith(client, CARD_A, "paused");
        expect(getActiveSessions().size).toBe(0);
    });
    test("ends multiple sessions including explicit ones", async () => {
        const client = makeMockClient();
        const endCb = mock(async () => { });
        initAutoSession(endCb, () => client);
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: Date.now(),
            lastActivityAt: Date.now(),
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        getActiveSessions().set(CARD_B, {
            cardId: CARD_B,
            startedAt: Date.now(),
            lastActivityAt: Date.now(),
            isExplicit: true,
            agentIdentifier: "explicit",
            agentName: "Explicit Agent",
        });
        await shutdownAllSessions();
        // Both should be ended on shutdown (even explicit)
        expect(client.endAgentSession).toHaveBeenCalledTimes(2);
        expect(endCb).toHaveBeenCalledTimes(2);
        expect(getActiveSessions().size).toBe(0);
    });
    test("is a no-op when no client is available", async () => {
        // No initAutoSession called
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: Date.now(),
            lastActivityAt: Date.now(),
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        // Should not throw
        await shutdownAllSessions();
        // Session still tracked (couldn't end without client)
        expect(getActiveSessions().has(CARD_A)).toBe(true);
    });
    test("is resilient when endAgentSession throws for some sessions", async () => {
        let callCount = 0;
        const client = makeMockClient();
        client.endAgentSession = mock(async () => {
            callCount++;
            if (callCount === 1)
                throw new Error("Network error");
            return { session: { id: "sess-1" } };
        });
        const endCb = mock(async () => { });
        initAutoSession(endCb, () => client);
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: Date.now(),
            lastActivityAt: Date.now(),
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        getActiveSessions().set(CARD_B, {
            cardId: CARD_B,
            startedAt: Date.now(),
            lastActivityAt: Date.now(),
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        await shutdownAllSessions();
        // Both should be removed from tracking despite first API error
        expect(getActiveSessions().size).toBe(0);
        // Callback should still be called for both
        expect(endCb).toHaveBeenCalledTimes(2);
    });
    test("empty sessions map is a no-op", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await shutdownAllSessions();
        expect(client.endAgentSession).not.toHaveBeenCalled();
    });
});
// ─── destroyAutoSession ─────────────────────────────────────────────
describe("destroyAutoSession", () => {
    test("clears all state", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        expect(getActiveSessions().size).toBe(1);
        destroyAutoSession();
        expect(getActiveSessions().size).toBe(0);
    });
    test("calling destroyAutoSession twice is safe", () => {
        initAutoSession(mock(async () => { }), () => makeMockClient());
        destroyAutoSession();
        destroyAutoSession();
        expect(getActiveSessions().size).toBe(0);
    });
    test("re-initialization after destroy works", async () => {
        const client = makeMockClient();
        initAutoSession(mock(async () => { }), () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        destroyAutoSession();
        // Re-init
        const client2 = makeMockClient();
        initAutoSession(mock(async () => { }), () => client2);
        await trackActivity(CARD_B, { autoStart: true, client: client2 });
        expect(getActiveSessions().size).toBe(1);
        expect(getActiveSessions().has(CARD_B)).toBe(true);
        expect(client2.startAgentSession).toHaveBeenCalledTimes(1);
    });
});
// ─── AUTO_START_TRIGGERS ────────────────────────────────────────────
describe("AUTO_START_TRIGGERS", () => {
    test("contains all card-mutating tools", () => {
        const expected = [
            "harmony_generate_prompt",
            "harmony_update_card",
            "harmony_move_card",
            "harmony_create_subtask",
            "harmony_toggle_subtask",
            "harmony_add_label_to_card",
            "harmony_remove_label_from_card",
        ];
        for (const tool of expected) {
            expect(AUTO_START_TRIGGERS.has(tool)).toBe(true);
        }
        expect(AUTO_START_TRIGGERS.size).toBe(expected.length);
    });
    test("does NOT contain read-only tools", () => {
        const readOnly = [
            "harmony_get_card",
            "harmony_get_card_by_short_id",
            "harmony_get_board",
            "harmony_search_cards",
            "harmony_get_agent_session",
            "harmony_get_card_links",
            "harmony_list_workspaces",
            "harmony_list_projects",
            "harmony_get_context",
        ];
        for (const tool of readOnly) {
            expect(AUTO_START_TRIGGERS.has(tool)).toBe(false);
        }
    });
    test("does NOT contain session management tools", () => {
        expect(AUTO_START_TRIGGERS.has("harmony_start_agent_session")).toBe(false);
        expect(AUTO_START_TRIGGERS.has("harmony_end_agent_session")).toBe(false);
        expect(AUTO_START_TRIGGERS.has("harmony_update_agent_progress")).toBe(false);
    });
});
// ─── INACTIVITY_TIMEOUT_MS ──────────────────────────────────────────
describe("INACTIVITY_TIMEOUT_MS", () => {
    test("is 10 minutes", () => {
        expect(INACTIVITY_TIMEOUT_MS).toBe(10 * 60 * 1000);
    });
});
// ─── Edge cases / race conditions ───────────────────────────────────
describe("edge cases", () => {
    test("trackActivity before initAutoSession uses client from options", async () => {
        // No initAutoSession called
        const client = makeMockClient();
        await trackActivity(CARD_A, { autoStart: true, client: client });
        expect(client.startAgentSession).toHaveBeenCalledTimes(1);
        expect(getActiveSessions().size).toBe(1);
    });
    test("re-initialization clears old interval timer", async () => {
        const client1 = makeMockClient();
        const endCb1 = mock(async () => { });
        initAutoSession(endCb1, () => client1);
        // Re-initialize
        const client2 = makeMockClient();
        const endCb2 = mock(async () => { });
        initAutoSession(endCb2, () => client2);
        // Add stale session and trigger check
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: 0,
            lastActivityAt: 0,
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        checkInactivity();
        await new Promise((r) => setTimeout(r, 50));
        // Should use client2, not client1
        expect(client2.endAgentSession).toHaveBeenCalledTimes(1);
        expect(client1.endAgentSession).not.toHaveBeenCalled();
    });
    test("endCallback throwing does not prevent session removal", async () => {
        const client = makeMockClient();
        const endCb = mock(async () => {
            throw new Error("Callback crash");
        });
        initAutoSession(endCb, () => client);
        getActiveSessions().set(CARD_A, {
            cardId: CARD_A,
            startedAt: 0,
            lastActivityAt: 0,
            isExplicit: false,
            agentIdentifier: "auto",
            agentName: "Auto-detected Agent",
        });
        checkInactivity();
        await new Promise((r) => setTimeout(r, 50));
        // Session should be removed despite callback error
        expect(getActiveSessions().has(CARD_A)).toBe(false);
    });
    test("both endAgentSession and endCallback throwing still cleans up", async () => {
        const client = makeMockClient();
        client.endAgentSession = mock(async () => {
            throw new Error("API down");
        });
        const endCb = mock(async () => {
            throw new Error("Pipeline crash");
        });
        initAutoSession(endCb, () => client);
        await trackActivity(CARD_A, { autoStart: true, client: client });
        await trackActivity(CARD_B, { autoStart: true, client: client });
        // Despite both throwing, tracking should be clean
        expect(getActiveSessions().has(CARD_A)).toBe(false);
        expect(getActiveSessions().has(CARD_B)).toBe(true);
    });
    test("concurrent auto-start on same card only starts once", async () => {
        const client = makeMockClient();
        // Slow startAgentSession to create a window for concurrent calls
        client.startAgentSession = mock(async () => new Promise((resolve) => setTimeout(() => resolve({ session: { id: "sess-1" } }), 20)));
        initAutoSession(mock(async () => { }), () => client);
        // Fire two concurrent auto-starts for the same card
        const p1 = trackActivity(CARD_A, {
            autoStart: true,
            client: client,
        });
        const p2 = trackActivity(CARD_A, {
            autoStart: true,
            client: client,
        });
        await Promise.all([p1, p2]);
        // First call creates session, second call hits early return (existing)
        // but due to async, both might race. At minimum, session should exist once.
        expect(getActiveSessions().size).toBe(1);
        expect(getActiveSessions().has(CARD_A)).toBe(true);
    });
});
