4fa4f13558
Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
433 lines
12 KiB
TypeScript
433 lines
12 KiB
TypeScript
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",
|
|
]);
|
|
});
|
|
});
|