fix(FN-1992): show resolved agent names in chat avatars
- Resolve assistant avatar labels from the active session agent map with fallback handling - Keep the built-in KB agent label as Fusion and fall back to a truncated agent ID when needed - Apply resolved labels to both stored assistant messages and streaming assistant avatars - Add ChatView tests for resolved labels, KB-agent fallback, and streaming avatar rendering - Document the @fusion/engine prebuild prerequisite in .fusion/memory.md
This commit is contained in:
@@ -313,6 +313,7 @@ Dashboard SSE (`/api/events`) streams plugin lifecycle events as normalized `plu
|
||||
- In `TaskCard.tsx`, `isInteractiveTarget` must check `target instanceof Element` (not `HTMLElement`) so SVG elements from lucide-react icons are correctly detected as interactive when inside buttons.
|
||||
- If workspace tests fail resolving `@fusion/core` package exports from `packages/core/dist/index.js` (for example `No matching export ...` in CLI/TUI/package-level tests after adding a new core export), run `pnpm --filter @fusion/core build` before rerunning the suite so ignored `dist/` exports are refreshed.
|
||||
- If CLI tests/build-exe tests fail with `Could not resolve "@fusion/dashboard"` (or Vite reports missing `@fusion/dashboard` entry), build the dashboard package first (`pnpm --filter @fusion/dashboard build`) so `packages/dashboard/dist/index.js` exists for workspace consumers.
|
||||
- If dashboard/TUI tests fail resolving `@fusion/engine` entry exports (for example `Failed to resolve entry for package "@fusion/engine"`), build engine artifacts first (`pnpm --filter @fusion/engine build`) so `packages/engine/dist/index.js` is available for workspace imports.
|
||||
- QuickEntryBox control test IDs are reused in `ListView` integration tests; when control layout changes (for example nested menu → inline buttons), update both `QuickEntryBox.test.tsx` and `ListView.test.tsx` together to avoid cascading failures.
|
||||
- When `InlineCreateCard` layout changes, also check `Column.test.tsx` and `board-mobile.test.tsx` for references to moved/removed test IDs like `inline-create-description-actions`.
|
||||
- When adding portal-based dropdown menus to QuickEntryBox, tests may fail in isolation but pass when run together (test isolation issues). This is because tests share DOM state across describe blocks. Always verify new dropdown tests pass both in isolation (`--testNamePattern`) and when run together.
|
||||
|
||||
@@ -1431,11 +1431,13 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
Promise.resolve().then(async () => {
|
||||
try {
|
||||
const generatedTitle = await options.onSummarize!(input.description);
|
||||
if (generatedTitle) {
|
||||
// Guard against races: fetch current task and only update if no title set
|
||||
const currentTask = await this.getTask(id);
|
||||
const normalizedTitle = generatedTitle?.trim();
|
||||
if (normalizedTitle) {
|
||||
// Guard against races: read directly from SQLite to avoid extra
|
||||
// prompt/step file I/O in this background path.
|
||||
const currentTask = this.readTaskFromDb(id);
|
||||
if (currentTask && !currentTask.title) {
|
||||
await this.updateTask(id, { title: generatedTitle });
|
||||
await this.updateTask(id, { title: normalizedTitle });
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -412,6 +412,12 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
|
||||
);
|
||||
};
|
||||
|
||||
const agentName =
|
||||
agentsMap.get(activeSession?.agentId ?? "")?.name ||
|
||||
(activeSession?.agentId === KB_AGENT_ID
|
||||
? "Fusion"
|
||||
: (activeSession?.agentId?.slice(0, 30) ?? "Fusion"));
|
||||
|
||||
return (
|
||||
<div className="chat-view">
|
||||
{/* Sidebar */}
|
||||
@@ -566,7 +572,7 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
|
||||
{message.role === "assistant" && (
|
||||
<div className="chat-message-avatar">
|
||||
<Bot size={14} />
|
||||
<span>Fusion</span>
|
||||
<span>{agentName}</span>
|
||||
{activeSession && (() => {
|
||||
const modelTag = formatModelTag(activeSession.modelProvider, activeSession.modelId);
|
||||
return modelTag ? <span className="chat-model-tag">{modelTag}</span> : null;
|
||||
@@ -587,7 +593,7 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
|
||||
<div className="chat-message chat-message--assistant chat-message--streaming">
|
||||
<div className="chat-message-avatar">
|
||||
<Bot size={14} />
|
||||
<span>Fusion</span>
|
||||
<span>{agentName}</span>
|
||||
{activeSession && (() => {
|
||||
const modelTag = formatModelTag(activeSession.modelProvider, activeSession.modelId);
|
||||
return modelTag ? <span className="chat-model-tag">{modelTag}</span> : null;
|
||||
|
||||
@@ -181,8 +181,8 @@ describe("ChatView", () => {
|
||||
// Dialog should be open - check for dialog content
|
||||
const dialog = document.querySelector(".chat-new-dialog");
|
||||
expect(dialog).toBeInTheDocument();
|
||||
// Should show Agent label
|
||||
expect(within(dialog!).getByText("Agent")).toBeInTheDocument();
|
||||
// Should show Agent label (current copy: "Agent (optional)")
|
||||
expect(within(dialog!).getByText(/Agent(?:\s*\(optional\))?/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("creates session without model selection (uses default)", async () => {
|
||||
@@ -305,6 +305,60 @@ describe("ChatView", () => {
|
||||
expect(screen.getByText("Hi there!")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows resolved agent name in assistant message avatar", async () => {
|
||||
setupMockChat({
|
||||
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Agent Chat", updatedAt: "2026-04-08T00:00:00.000Z" },
|
||||
messages: [
|
||||
{ id: "msg-001", sessionId: "session-001", role: "assistant", content: "Hello from Alpha", createdAt: "2026-04-08T00:00:00.000Z" },
|
||||
],
|
||||
});
|
||||
|
||||
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
const avatar = document.querySelector(".chat-message-avatar");
|
||||
expect(avatar).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(within(avatar!).getByText("Alpha")).toBeInTheDocument();
|
||||
});
|
||||
expect(within(avatar!).queryByText("Fusion")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows Fusion in assistant message avatar for kb agent sessions", () => {
|
||||
setupMockChat({
|
||||
activeSession: { id: "session-001", agentId: "__kb_agent__", status: "active", title: "Fusion Chat", updatedAt: "2026-04-08T00:00:00.000Z" },
|
||||
messages: [
|
||||
{ id: "msg-001", sessionId: "session-001", role: "assistant", content: "Built-in assistant response", createdAt: "2026-04-08T00:00:00.000Z" },
|
||||
],
|
||||
});
|
||||
|
||||
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
const avatar = document.querySelector(".chat-message-avatar");
|
||||
expect(avatar).toBeInTheDocument();
|
||||
expect(within(avatar!).getByText("Fusion")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows resolved agent name in streaming assistant avatar", async () => {
|
||||
setupMockChat({
|
||||
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Agent Chat", updatedAt: "2026-04-08T00:00:00.000Z" },
|
||||
messages: [
|
||||
{ id: "msg-001", sessionId: "session-001", role: "user", content: "Think", createdAt: "2026-04-08T00:00:00.000Z" },
|
||||
],
|
||||
isStreaming: true,
|
||||
streamingText: "Thinking...",
|
||||
});
|
||||
|
||||
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
const avatar = document.querySelector(".chat-message--streaming .chat-message-avatar");
|
||||
expect(avatar).toBeInTheDocument();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(within(avatar!).getByText("Alpha")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("sends message on Enter key", async () => {
|
||||
const sendMessage = vi.fn();
|
||||
setupMockChat({
|
||||
|
||||
Reference in New Issue
Block a user