import { describe, expect, it } from "vitest"; import { agentStoreReducer, buildNewSessionAgentPatch, getFilteredAgents, initialAgentStoreState, type AgentStoreSeed, } from "@/features/agents/state/store"; describe("agent store", () => { it("hydrates agents with defaults and selection", () => { const seed: AgentStoreSeed = { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:main", }; const next = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: [seed], }); expect(next.loading).toBe(false); expect(next.selectedAgentId).toBe("agent-1"); expect(next.agents).toHaveLength(1); expect(next.agents[0].status).toBe("idle"); expect(next.agents[0].thinkingLevel).toBe("high"); expect(next.agents[0].sessionCreated).toBe(false); expect(next.agents[0].outputLines).toEqual([]); }); it("hydrates agents with a requested selection when present", () => { const seeds: AgentStoreSeed[] = [ { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:main", }, { agentId: "agent-2", name: "Agent Two", sessionKey: "agent:agent-2:main", }, ]; const next = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: seeds, selectedAgentId: " agent-2 ", }); expect(next.selectedAgentId).toBe("agent-2"); }); it("keeps existing selection when requested selection is invalid", () => { const seeds: AgentStoreSeed[] = [ { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:main", }, { agentId: "agent-2", name: "Agent Two", sessionKey: "agent:agent-2:main", }, ]; let state = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: seeds, }); state = agentStoreReducer(state, { type: "selectAgent", agentId: "agent-2", }); state = agentStoreReducer(state, { type: "hydrateAgents", agents: seeds, selectedAgentId: "missing-agent", }); expect(state.selectedAgentId).toBe("agent-2"); }); it("builds a patch that resets runtime state for a session reset", () => { const seed: AgentStoreSeed = { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:studio:old-session", }; let state = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: [seed], }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-1", patch: { status: "running", awaitingUserInput: true, hasUnseenActivity: true, outputLines: ["> hello", "response"], lastResult: "response", lastDiff: "diff", runId: "run-1", streamText: "live", thinkingTrace: "thinking", latestOverride: "override", latestOverrideKind: "heartbeat", lastAssistantMessageAt: 1700000000000, lastActivityAt: 1700000000001, latestPreview: "preview", lastUserMessage: "hello", draft: "draft", historyLoadedAt: 1700000000002, }, }); const agent = state.agents.find((entry) => entry.agentId === "agent-1")!; const patch = buildNewSessionAgentPatch(agent); expect(patch.sessionKey).toBe("agent:agent-1:studio:old-session"); expect(patch.status).toBe("idle"); expect(patch.sessionCreated).toBe(true); expect(patch.sessionSettingsSynced).toBe(true); expect(patch.outputLines).toEqual([]); expect(patch.streamText).toBeNull(); expect(patch.thinkingTrace).toBeNull(); expect(patch.lastResult).toBeNull(); expect(patch.lastDiff).toBeNull(); expect(patch.historyLoadedAt).toBeNull(); expect(patch.lastUserMessage).toBeNull(); expect(patch.runId).toBeNull(); expect(patch.runStartedAt).toBeNull(); expect(patch.latestPreview).toBeNull(); expect(patch.latestOverride).toBeNull(); expect(patch.latestOverrideKind).toBeNull(); expect(patch.lastAssistantMessageAt).toBeNull(); expect(patch.awaitingUserInput).toBe(false); expect(patch.hasUnseenActivity).toBe(false); expect(patch.draft).toBe(""); }); it("preserves_session_created_state_across_hydration", () => { const seed: AgentStoreSeed = { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:main", }; let state = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: [seed], }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-1", patch: { sessionCreated: true }, }); state = agentStoreReducer(state, { type: "hydrateAgents", agents: [seed], }); expect(state.agents[0]?.sessionCreated).toBe(true); }); it("resets_runtime_state_when_session_key_changes_on_hydration", () => { const initialSeed: AgentStoreSeed = { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:studio:legacy", }; let state = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: [initialSeed], }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-1", patch: { sessionCreated: true, outputLines: ["> old"], lastResult: "old result", runId: "run-1", }, }); const nextSeed: AgentStoreSeed = { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:main", }; state = agentStoreReducer(state, { type: "hydrateAgents", agents: [nextSeed], }); const next = state.agents[0]; expect(next?.sessionKey).toBe("agent:agent-1:main"); expect(next?.sessionCreated).toBe(false); expect(next?.outputLines).toEqual([]); expect(next?.lastResult).toBeNull(); expect(next?.runId).toBeNull(); }); it("keeps_transcript_references_for_non_transcript_agent_updates", () => { const seed: AgentStoreSeed = { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:main", }; let state = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: [seed], }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-1", patch: { outputLines: ["> hello", "response"] }, }); const beforeDraftUpdate = state.agents[0]; state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-1", patch: { draft: "x" }, }); const afterDraftUpdate = state.agents[0]; expect(afterDraftUpdate.outputLines).toBe(beforeDraftUpdate.outputLines); expect(afterDraftUpdate.transcriptEntries).toBe(beforeDraftUpdate.transcriptEntries); expect(afterDraftUpdate.transcriptSequenceCounter).toBe(beforeDraftUpdate.transcriptSequenceCounter); }); it("tracks_unseen_activity_for_non_selected_agents", () => { const seeds: AgentStoreSeed[] = [ { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:main", }, { agentId: "agent-2", name: "Agent Two", sessionKey: "agent:agent-2:main", }, ]; const hydrated = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: seeds, }); const withActivity = agentStoreReducer(hydrated, { type: "markActivity", agentId: "agent-2", at: 1700000000000, }); const second = withActivity.agents.find((agent) => agent.agentId === "agent-2"); expect(second?.hasUnseenActivity).toBe(true); expect(second?.lastActivityAt).toBe(1700000000000); const selected = agentStoreReducer(withActivity, { type: "selectAgent", agentId: "agent-2", }); const cleared = selected.agents.find((agent) => agent.agentId === "agent-2"); expect(cleared?.hasUnseenActivity).toBe(false); }); it("filters_agents_by_status_and_approvals", () => { const seeds: AgentStoreSeed[] = [ { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:main", }, { agentId: "agent-2", name: "Agent Two", sessionKey: "agent:agent-2:main", }, { agentId: "agent-3", name: "Agent Three", sessionKey: "agent:agent-3:main", }, ]; let state = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: seeds, }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-1", patch: { status: "idle", awaitingUserInput: true }, }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-2", patch: { status: "running" }, }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-3", patch: { status: "error" }, }); expect(getFilteredAgents(state, "all").map((agent) => agent.agentId)).toEqual([ "agent-2", "agent-1", "agent-3", ]); expect(getFilteredAgents(state, "running").map((agent) => agent.agentId)).toEqual([ "agent-2", ]); expect(getFilteredAgents(state, "approvals").map((agent) => agent.agentId)).toEqual([ "agent-1", ]); }); it("clears_unseen_indicator_on_focus", () => { const seeds: AgentStoreSeed[] = [ { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:main", }, { agentId: "agent-2", name: "Agent Two", sessionKey: "agent:agent-2:main", }, ]; let state = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: seeds, }); state = agentStoreReducer(state, { type: "markActivity", agentId: "agent-2", at: 1700000000100, }); const before = state.agents.find((agent) => agent.agentId === "agent-2"); expect(before?.hasUnseenActivity).toBe(true); state = agentStoreReducer(state, { type: "selectAgent", agentId: "agent-2", }); const after = state.agents.find((agent) => agent.agentId === "agent-2"); expect(after?.hasUnseenActivity).toBe(false); }); it("sorts_filtered_agents_by_latest_assistant_message", () => { const seeds: AgentStoreSeed[] = [ { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:main", }, { agentId: "agent-2", name: "Agent Two", sessionKey: "agent:agent-2:main", }, { agentId: "agent-3", name: "Agent Three", sessionKey: "agent:agent-3:main", }, ]; let state = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: seeds, }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-1", patch: { status: "running", lastAssistantMessageAt: 200 }, }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-2", patch: { status: "running", lastAssistantMessageAt: 500 }, }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-3", patch: { status: "running", lastAssistantMessageAt: 300 }, }); expect(getFilteredAgents(state, "all").map((agent) => agent.agentId)).toEqual([ "agent-2", "agent-3", "agent-1", ]); expect(getFilteredAgents(state, "running").map((agent) => agent.agentId)).toEqual([ "agent-2", "agent-3", "agent-1", ]); }); it("prioritizes_running_agents_in_all_filter_even_without_assistant_reply", () => { const seeds: AgentStoreSeed[] = [ { agentId: "agent-1", name: "Agent One", sessionKey: "agent:agent-1:main", }, { agentId: "agent-2", name: "Agent Two", sessionKey: "agent:agent-2:main", }, ]; let state = agentStoreReducer(initialAgentStoreState, { type: "hydrateAgents", agents: seeds, }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-1", patch: { status: "idle", lastAssistantMessageAt: 900 }, }); state = agentStoreReducer(state, { type: "updateAgent", agentId: "agent-2", patch: { status: "running", runStartedAt: 1000, lastAssistantMessageAt: null }, }); expect(getFilteredAgents(state, "all").map((agent) => agent.agentId)).toEqual([ "agent-2", "agent-1", ]); }); });