c9789c2148
* Add agents * Agent * Added agents management * Polish agent wizard and release blockers. Finalize the office agent management flow by aligning the gateway fallback behavior, cleaning up UI semantics, and updating tests so the branch is ready to ship. Made-with: Cursor --------- Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com> Co-authored-by: iamlukethedev <lucas.guilherme@smartwayslfl.com>
221 lines
7.0 KiB
TypeScript
221 lines
7.0 KiB
TypeScript
import { createElement } from "react";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
|
|
import type { AgentState } from "@/features/agents/state/store";
|
|
import { AgentBrainPanel } from "@/features/agents/components/AgentInspectPanels";
|
|
import type { GatewayClient } from "@/lib/gateway/GatewayClient";
|
|
|
|
const createAgent = (agentId: string, name: string, sessionKey: string): AgentState => ({
|
|
agentId,
|
|
name,
|
|
sessionKey,
|
|
status: "idle",
|
|
sessionCreated: true,
|
|
awaitingUserInput: false,
|
|
hasUnseenActivity: false,
|
|
outputLines: [],
|
|
lastResult: null,
|
|
lastDiff: null,
|
|
runId: null,
|
|
runStartedAt: null,
|
|
streamText: null,
|
|
thinkingTrace: null,
|
|
latestOverride: null,
|
|
latestOverrideKind: null,
|
|
lastAssistantMessageAt: null,
|
|
lastActivityAt: null,
|
|
latestPreview: null,
|
|
lastUserMessage: null,
|
|
draft: "",
|
|
sessionSettingsSynced: true,
|
|
historyLoadedAt: null,
|
|
historyFetchLimit: null,
|
|
historyFetchedCount: null,
|
|
historyMaybeTruncated: false,
|
|
toolCallingEnabled: true,
|
|
showThinkingTraces: true,
|
|
model: null,
|
|
thinkingLevel: null,
|
|
avatarSeed: `seed-${agentId}`,
|
|
avatarUrl: null,
|
|
});
|
|
|
|
const createMockClient = () => {
|
|
const filesByAgent: Record<string, Record<string, string>> = {
|
|
"agent-1": {
|
|
"AGENTS.md": "alpha agents",
|
|
"SOUL.md": "# SOUL.md - Who You Are\n\n## Core Truths\n\nBe useful.",
|
|
"IDENTITY.md": "# IDENTITY.md - Who Am I?\n\n- Name: Alpha\n- Creature: droid\n- Vibe: calm\n- Emoji: 🤖\n",
|
|
"USER.md": "# USER.md - About Your Human\n\n- Name: George\n- What to call them: GP\n\n## Context\n\nBuilding Claw3D.",
|
|
"TOOLS.md": "tool notes",
|
|
"HEARTBEAT.md": "heartbeat notes",
|
|
"MEMORY.md": "durable memory",
|
|
},
|
|
"agent-2": {
|
|
"AGENTS.md": "beta agents",
|
|
},
|
|
};
|
|
|
|
const calls: Array<{ method: string; params: unknown }> = [];
|
|
|
|
const client = {
|
|
call: vi.fn(async (method: string, params: unknown) => {
|
|
calls.push({ method, params });
|
|
if (method === "agents.files.get") {
|
|
const record = params && typeof params === "object" ? (params as Record<string, unknown>) : {};
|
|
const agentId = typeof record.agentId === "string" ? record.agentId : "";
|
|
const name = typeof record.name === "string" ? record.name : "";
|
|
const content = filesByAgent[agentId]?.[name];
|
|
if (typeof content !== "string") {
|
|
return { file: { name, missing: true } };
|
|
}
|
|
return { file: { name, missing: false, content } };
|
|
}
|
|
if (method === "agents.files.set") {
|
|
const record = params && typeof params === "object" ? (params as Record<string, unknown>) : {};
|
|
const agentId = typeof record.agentId === "string" ? record.agentId : "";
|
|
const name = typeof record.name === "string" ? record.name : "";
|
|
const content = typeof record.content === "string" ? record.content : "";
|
|
if (!filesByAgent[agentId]) {
|
|
filesByAgent[agentId] = {};
|
|
}
|
|
filesByAgent[agentId][name] = content;
|
|
return { ok: true };
|
|
}
|
|
return {};
|
|
}),
|
|
} as unknown as GatewayClient;
|
|
|
|
return { client, calls, filesByAgent };
|
|
};
|
|
|
|
describe("AgentBrainPanel", () => {
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
it("renders_behavior_sections_and_loads_agent_files", async () => {
|
|
const { client } = createMockClient();
|
|
const agents = [
|
|
createAgent("agent-1", "Alpha", "session-1"),
|
|
createAgent("agent-2", "Beta", "session-2"),
|
|
];
|
|
|
|
render(
|
|
createElement(AgentBrainPanel, {
|
|
client,
|
|
agents,
|
|
selectedAgentId: "agent-1",
|
|
})
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("heading", { name: "SOUL.md" })).toBeInTheDocument();
|
|
});
|
|
|
|
expect(screen.getByRole("heading", { name: "AGENTS.md" })).toBeInTheDocument();
|
|
expect(screen.getByRole("heading", { name: "USER.md" })).toBeInTheDocument();
|
|
expect(screen.getByRole("heading", { name: "IDENTITY.md" })).toBeInTheDocument();
|
|
expect(screen.getByLabelText("AGENTS.md")).toHaveValue("alpha agents");
|
|
expect(screen.getByLabelText("SOUL.md")).toHaveValue(
|
|
"# SOUL.md - Who You Are\n\n## Core Truths\n\nBe useful."
|
|
);
|
|
expect(screen.getByLabelText("Name")).toHaveValue("Alpha");
|
|
});
|
|
|
|
it("shows_actionable_message_when_session_key_missing", async () => {
|
|
const { client } = createMockClient();
|
|
const agents = [createAgent("", "Alpha", "session-1")];
|
|
|
|
render(
|
|
createElement(AgentBrainPanel, {
|
|
client,
|
|
agents,
|
|
selectedAgentId: "",
|
|
})
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText("Agent ID is missing for this agent.")).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
it("saves_updated_behavior_files", async () => {
|
|
const { client, calls, filesByAgent } = createMockClient();
|
|
const agents = [createAgent("agent-1", "Alpha", "session-1")];
|
|
|
|
render(
|
|
createElement(AgentBrainPanel, {
|
|
client,
|
|
agents,
|
|
selectedAgentId: "agent-1",
|
|
})
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByLabelText("AGENTS.md")).toBeInTheDocument();
|
|
});
|
|
|
|
fireEvent.change(screen.getByLabelText("AGENTS.md"), {
|
|
target: { value: "alpha directives updated" },
|
|
});
|
|
|
|
const saveButton = screen.getByRole("button", { name: "Save" });
|
|
expect(saveButton).not.toBeDisabled();
|
|
fireEvent.click(saveButton);
|
|
|
|
await waitFor(() => {
|
|
expect(calls.some((entry) => entry.method === "agents.files.set")).toBe(true);
|
|
});
|
|
expect(filesByAgent["agent-1"]["AGENTS.md"]).toBe("alpha directives updated");
|
|
});
|
|
|
|
it("calls_cancel_without_writing_files", async () => {
|
|
const { client, calls } = createMockClient();
|
|
const agents = [createAgent("agent-1", "Alpha", "session-1")];
|
|
const onCancel = vi.fn();
|
|
|
|
render(
|
|
createElement(AgentBrainPanel, {
|
|
client,
|
|
agents,
|
|
selectedAgentId: "agent-1",
|
|
onCancel,
|
|
})
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByLabelText("Name")).toBeInTheDocument();
|
|
});
|
|
|
|
fireEvent.change(screen.getByLabelText("Name"), {
|
|
target: { value: "Alpha Prime" },
|
|
});
|
|
expect(screen.getByLabelText("Name")).toHaveValue("Alpha Prime");
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
expect(onCancel).toHaveBeenCalledTimes(1);
|
|
expect(calls.some((entry) => entry.method === "agents.files.set")).toBe(false);
|
|
});
|
|
|
|
it("does_not_render_legacy_name_editor_controls", async () => {
|
|
const { client } = createMockClient();
|
|
const agents = [createAgent("agent-1", "Alpha", "session-1")];
|
|
|
|
render(
|
|
createElement(AgentBrainPanel, {
|
|
client,
|
|
agents,
|
|
selectedAgentId: "agent-1",
|
|
})
|
|
);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByRole("heading", { name: "SOUL.md" })).toBeInTheDocument();
|
|
});
|
|
expect(screen.queryByLabelText("Agent name")).not.toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: "Update Name" })).not.toBeInTheDocument();
|
|
});
|
|
});
|