refactor(FN-2162): rename kb-agent identifiers to fn-agent

- Rename core loader, dashboard server chat/planning routes, and frontend agent IDs/storage keys from kb-agent to fn-agent naming
- Update dashboard hooks and components (agent list, chat view, quick chat) to use the new fn agent key prefixes consistently
- Refresh engine, dashboard, core, and CLI tests/mocks to remove remaining kb-agent route and temp prefix references
- Update storage/gap-analysis docs to reflect fn agent key names and add a @gsxdsm/fusion patch changeset for the rename
This commit is contained in:
Fusion
2026-04-19 19:40:10 -07:00
committed by gsxdsm
parent 0aa1360a18
commit 0b9bf62a2a
51 changed files with 878 additions and 873 deletions

View File

@@ -7,7 +7,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
ChatManager,
__setBuildAgentChatPrompt,
__setCreateKbAgent,
__setCreateFnAgent,
__resetChatState,
chatStreamManager,
} from "../chat.js";
@@ -207,7 +207,7 @@ describe("ChatManager.sendMessage", () => {
]);
let createOptions: any;
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
createOptions = options;
return {
session: {
@@ -241,7 +241,7 @@ describe("ChatManager.sendMessage", () => {
},
]);
__setCreateKbAgent(async () => {
__setCreateFnAgent(async () => {
return {
session: {
prompt: vi.fn().mockResolvedValue(undefined),
@@ -275,7 +275,7 @@ describe("ChatManager.sendMessage", () => {
let onThinkingCb: ((delta: string) => void) | undefined;
let onTextCb: ((delta: string) => void) | undefined;
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
onThinkingCb = options.onThinking;
onTextCb = options.onText;
@@ -311,7 +311,7 @@ describe("ChatManager.sendMessage", () => {
it("creates chat agents with the full coding toolset", async () => {
let createOptions: any;
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
createOptions = options;
return {
session: {
@@ -334,7 +334,7 @@ describe("ChatManager.sendMessage", () => {
let onThinkingCb: ((delta: string) => void) | undefined;
let onTextCb: ((delta: string) => void) | undefined;
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
onThinkingCb = options.onThinking;
onTextCb = options.onText;
@@ -367,7 +367,7 @@ describe("ChatManager.sendMessage", () => {
events.push(event);
});
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
return {
session: {
prompt: vi.fn().mockImplementation(async () => {
@@ -400,7 +400,7 @@ describe("ChatManager.sendMessage", () => {
});
it("does not persist empty assistant response on immediate failure", async () => {
__setCreateKbAgent(async () => {
__setCreateFnAgent(async () => {
return {
session: {
prompt: vi.fn().mockRejectedValue(new Error("Immediate failure")),
@@ -418,7 +418,7 @@ describe("ChatManager.sendMessage", () => {
});
it("persists thinking output even when no text was generated", async () => {
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
return {
session: {
prompt: vi.fn().mockImplementation(async () => {
@@ -448,7 +448,7 @@ describe("ChatManager.sendMessage", () => {
});
it("uses accumulated text as primary source over state.messages extraction", async () => {
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
return {
session: {
prompt: vi.fn().mockImplementation(async () => {
@@ -479,7 +479,7 @@ describe("ChatManager.sendMessage", () => {
});
it("falls back to state.messages when accumulated text is empty", async () => {
__setCreateKbAgent(async () => {
__setCreateFnAgent(async () => {
return {
session: {
prompt: vi.fn().mockImplementation(async () => {
@@ -507,7 +507,7 @@ describe("ChatManager.sendMessage", () => {
});
it("handles array content format in state.messages extraction", async () => {
__setCreateKbAgent(async () => {
__setCreateFnAgent(async () => {
return {
session: {
prompt: vi.fn().mockImplementation(async () => {
@@ -541,7 +541,7 @@ describe("ChatManager.sendMessage", () => {
});
it("persists user message before AI response", async () => {
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
return {
session: {
prompt: vi.fn().mockImplementation(async () => {
@@ -573,7 +573,7 @@ describe("ChatManager.sendMessage", () => {
it("passes enriched system prompt with agent soul when agent context is available", async () => {
let createOptions: any;
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
createOptions = options;
return {
session: {
@@ -605,7 +605,7 @@ describe("ChatManager.sendMessage", () => {
});
let createOptions: any;
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
createOptions = options;
return {
session: {
@@ -628,7 +628,7 @@ describe("ChatManager.sendMessage", () => {
mockAgentStore.getAgent.mockResolvedValue(null);
let createOptions: any;
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
createOptions = options;
return {
session: {
@@ -658,7 +658,7 @@ describe("ChatManager.sendMessage", () => {
{ role: "user", content: "Current question" },
]);
__setCreateKbAgent(async () => {
__setCreateFnAgent(async () => {
return {
session: {
prompt: promptSpy,
@@ -686,7 +686,7 @@ describe("ChatManager.sendMessage", () => {
it("generates title when session has no title", async () => {
mockSummarizeTitle.mockResolvedValue("Short Title");
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
return {
session: {
prompt: vi.fn().mockImplementation(async () => {
@@ -720,7 +720,7 @@ describe("ChatManager.sendMessage", () => {
it("uses truncated content when summarizeTitle returns null", async () => {
mockSummarizeTitle.mockResolvedValue(null);
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
return {
session: {
prompt: vi.fn().mockImplementation(async () => {
@@ -755,7 +755,7 @@ describe("ChatManager.sendMessage", () => {
title: "Existing Title",
});
__setCreateKbAgent(async (options: any) => {
__setCreateFnAgent(async (options: any) => {
return {
session: {
prompt: vi.fn().mockImplementation(async () => {
@@ -813,7 +813,7 @@ describe("ChatManager.sendMessage", () => {
it("cancelled generation does not persist assistant message", async () => {
let rejectPrompt: ((reason?: unknown) => void) | undefined;
__setCreateKbAgent(async () => {
__setCreateFnAgent(async () => {
return {
session: {
prompt: vi.fn().mockImplementation(() => {
@@ -846,7 +846,7 @@ describe("ChatManager.sendMessage", () => {
it("cancelled generation broadcasts error event with cancellation message", async () => {
let rejectPrompt: ((reason?: unknown) => void) | undefined;
__setCreateKbAgent(async () => {
__setCreateFnAgent(async () => {
return {
session: {
prompt: vi.fn().mockImplementation(() => {
@@ -883,7 +883,7 @@ describe("ChatManager.sendMessage", () => {
throw new Error("dispose failed");
});
__setCreateKbAgent(async () => {
__setCreateFnAgent(async () => {
return {
session: {
prompt: vi.fn().mockResolvedValue(undefined),

View File

@@ -115,7 +115,7 @@ function extractSSEPayload(sseChunk: string): unknown {
const mockInit = vi.fn().mockResolvedValue(undefined);
// Create mock functions before vi.mock
const { mockCreateKbAgent, mockChatStreamManager, mockSendMessage, mockCancelGeneration } = vi.hoisted(() => {
const { mockCreateFnAgent, mockChatStreamManager, mockSendMessage, mockCancelGeneration } = vi.hoisted(() => {
// Store subscribers per session for broadcast simulation
const subscribers = new Map<string, Set<(event: any, eventId?: number) => void>>();
@@ -171,7 +171,7 @@ const { mockCreateKbAgent, mockChatStreamManager, mockSendMessage, mockCancelGen
};
return {
mockCreateKbAgent: vi.fn(),
mockCreateFnAgent: vi.fn(),
mockSendMessage: vi.fn(),
mockCancelGeneration: vi.fn(),
mockChatStreamManager: chatStreamManager,
@@ -180,7 +180,7 @@ const { mockCreateKbAgent, mockChatStreamManager, mockSendMessage, mockCancelGen
// Mock @fusion/engine to prevent createFnAgent resolution
vi.mock("@fusion/engine", () => ({
createFnAgent: mockCreateKbAgent,
createFnAgent: mockCreateFnAgent,
}));
// Mock ChatStore
@@ -232,7 +232,7 @@ vi.mock("../chat.js", () => {
chatStreamManager: mockChatStreamManager,
checkRateLimit: vi.fn().mockReturnValue(true),
getRateLimitResetTime: vi.fn().mockReturnValue(null),
__setCreateKbAgent: vi.fn(),
__setCreateFnAgent: vi.fn(),
__resetChatState: vi.fn(),
};
});
@@ -242,7 +242,7 @@ vi.mock("../planning.js", () => {
return {
getSession: vi.fn(),
cleanupSession: vi.fn(),
__setCreateKbAgent: vi.fn(),
__setCreateFnAgent: vi.fn(),
__resetPlanningState: vi.fn(),
setAiSessionStore: vi.fn(),
rehydrateFromStore: vi.fn().mockReturnValue(0),

View File

@@ -58,7 +58,7 @@ describe("POST /api/agents/export", () => {
beforeEach(async () => {
vi.clearAllMocks();
testDir = mkdtempSync(join(tmpdir(), "kb-agent-export-route-"));
testDir = mkdtempSync(join(tmpdir(), "fn-agent-export-route-"));
mockInit.mockResolvedValue(undefined);
mockListAgents.mockResolvedValue([

View File

@@ -74,7 +74,7 @@ describe("POST /api/agents/import", () => {
beforeEach(async () => {
vi.clearAllMocks();
testDir = mkdtempSync(join(tmpdir(), "kb-agent-import-route-"));
testDir = mkdtempSync(join(tmpdir(), "fn-agent-import-route-"));
mockInit.mockResolvedValue(undefined);
mockListAgents.mockResolvedValue([]);

View File

@@ -14,7 +14,7 @@ import { Database } from "@fusion/core";
import { AiSessionStore } from "../ai-session-store.js";
import {
__resetPlanningState,
__setCreateKbAgent,
__setCreateFnAgent,
createSession,
getSession,
planningStreamManager,
@@ -40,12 +40,12 @@ import {
submitMissionInterviewResponse,
} from "../mission-interview.js";
const { mockCreateKbAgent } = vi.hoisted(() => ({
mockCreateKbAgent: vi.fn(),
const { mockCreateFnAgent } = vi.hoisted(() => ({
mockCreateFnAgent: vi.fn(),
}));
vi.mock("@fusion/engine", () => ({
createFnAgent: mockCreateKbAgent,
createFnAgent: mockCreateFnAgent,
}));
function makeTmpDir(): string {
@@ -100,7 +100,7 @@ describe("session error recovery", () => {
});
afterEach(async () => {
__setCreateKbAgent(undefined as any);
__setCreateFnAgent(undefined as any);
__resetPlanningState();
__resetSubtaskBreakdownState();
__resetMissionInterviewState();
@@ -121,7 +121,7 @@ describe("session error recovery", () => {
});
unsubscribe();
__setCreateKbAgent(
__setCreateFnAgent(
async () =>
createMockAgent([
JSON.stringify({
@@ -158,7 +158,7 @@ describe("session error recovery", () => {
expect(JSON.parse(persistedError?.conversationHistory ?? "[]")).toHaveLength(1);
expect(errorEvents.length).toBeGreaterThan(0);
__setCreateKbAgent(
__setCreateFnAgent(
async () =>
createMockAgent([
JSON.stringify({
@@ -181,7 +181,7 @@ describe("session error recovery", () => {
it("captures subtask generation errors, broadcasts SSE error, and retries to completion", async () => {
const subtaskErrors: string[] = [];
mockCreateKbAgent.mockImplementationOnce(async () => createMockAgent(["{not-json"]));
mockCreateFnAgent.mockImplementationOnce(async () => createMockAgent(["{not-json"]));
const session = await createSubtaskSession("Subtask error flow", undefined, "/tmp/project");
@@ -198,7 +198,7 @@ describe("session error recovery", () => {
expect(String(persistedError?.error).length).toBeGreaterThan(0);
expect(subtaskErrors.length).toBeGreaterThan(0);
mockCreateKbAgent.mockImplementationOnce(async () =>
mockCreateFnAgent.mockImplementationOnce(async () =>
createMockAgent([
JSON.stringify({
subtasks: [
@@ -226,7 +226,7 @@ describe("session error recovery", () => {
it("captures mission parse failure with history preserved and recovers via retry", async () => {
const missionErrors: string[] = [];
mockCreateKbAgent.mockImplementation(async () =>
mockCreateFnAgent.mockImplementation(async () =>
createMockAgent([
JSON.stringify({
type: "question",
@@ -254,7 +254,7 @@ describe("session error recovery", () => {
expect(JSON.parse(persistedError?.conversationHistory ?? "[]")).toHaveLength(1);
expect(missionErrors.length).toBeGreaterThan(0);
mockCreateKbAgent.mockImplementation(async () =>
mockCreateFnAgent.mockImplementation(async () =>
createMockAgent([
JSON.stringify({
type: "question",

View File

@@ -14,7 +14,7 @@ import { Database } from "@fusion/core";
import { AiSessionStore, type AiSessionRow } from "../ai-session-store.js";
import {
__resetPlanningState,
__setCreateKbAgent,
__setCreateFnAgent,
cancelSession,
createSession,
setAiSessionStore as setPlanningAiSessionStore,
@@ -33,12 +33,12 @@ import {
submitMissionInterviewResponse,
} from "../mission-interview.js";
const { mockCreateKbAgent } = vi.hoisted(() => ({
mockCreateKbAgent: vi.fn(),
const { mockCreateFnAgent } = vi.hoisted(() => ({
mockCreateFnAgent: vi.fn(),
}));
vi.mock("@fusion/engine", () => ({
createFnAgent: mockCreateKbAgent,
createFnAgent: mockCreateFnAgent,
}));
function makeTmpDir(): string {
@@ -93,7 +93,7 @@ describe("session persistence round-trip", () => {
});
afterEach(async () => {
__setCreateKbAgent(undefined as any);
__setCreateFnAgent(undefined as any);
__resetPlanningState();
__resetSubtaskBreakdownState();
__resetMissionInterviewState();
@@ -108,7 +108,7 @@ describe("session persistence round-trip", () => {
});
it("persists planning session transitions across generating → awaiting_input → complete", async () => {
__setCreateKbAgent(
__setCreateFnAgent(
async () =>
createMockAgent([
JSON.stringify({
@@ -226,7 +226,7 @@ describe("session persistence round-trip", () => {
});
it("persists completed subtask results into AiSessionStore result JSON", async () => {
mockCreateKbAgent.mockImplementation(async () =>
mockCreateFnAgent.mockImplementation(async () =>
createMockAgent([
JSON.stringify({
subtasks: [
@@ -261,7 +261,7 @@ describe("session persistence round-trip", () => {
});
it("persists mission interview history and result round-trip", async () => {
mockCreateKbAgent.mockImplementation(async () =>
mockCreateFnAgent.mockImplementation(async () =>
createMockAgent([
JSON.stringify({
type: "question",
@@ -305,7 +305,7 @@ describe("session persistence round-trip", () => {
});
it("removes persisted rows when a planning session is cancelled", async () => {
__setCreateKbAgent(
__setCreateFnAgent(
async () =>
createMockAgent([
JSON.stringify({

View File

@@ -17,7 +17,7 @@ import { request, get } from "../test-request.js";
import { AiSessionStore, type AiSessionRow } from "../ai-session-store.js";
import {
__resetPlanningState,
__setCreateKbAgent,
__setCreateFnAgent,
createSession,
submitResponse,
setAiSessionStore as setPlanningAiSessionStore,
@@ -36,12 +36,12 @@ import {
setAiSessionStore as setMissionAiSessionStore,
} from "../mission-interview.js";
const { mockCreateKbAgent } = vi.hoisted(() => ({
mockCreateKbAgent: vi.fn(),
const { mockCreateFnAgent } = vi.hoisted(() => ({
mockCreateFnAgent: vi.fn(),
}));
vi.mock("@fusion/engine", () => ({
createFnAgent: mockCreateKbAgent,
createFnAgent: mockCreateFnAgent,
}));
function makePlanningAgent(responses: string[]) {
@@ -105,7 +105,7 @@ describe("session reconnect + replay", () => {
});
afterEach(async () => {
__setCreateKbAgent(undefined as any);
__setCreateFnAgent(undefined as any);
__resetPlanningState();
__resetSubtaskBreakdownState();
__resetMissionInterviewState();
@@ -140,7 +140,7 @@ describe("session reconnect + replay", () => {
}),
];
__setCreateKbAgent(async () => makePlanningAgent(planningResponses));
__setCreateFnAgent(async () => makePlanningAgent(planningResponses));
const { sessionId } = await createSession("127.0.0.11", "Build reconnect tests", undefined, "/tmp/project");
await submitResponse(sessionId, { "q-1": "medium" }, "/tmp/project");
@@ -163,7 +163,7 @@ describe("session reconnect + replay", () => {
});
it("replays subtask buffered events using Last-Event-ID header", async () => {
mockCreateKbAgent.mockImplementation(async () =>
mockCreateFnAgent.mockImplementation(async () =>
makePlanningAgent([
JSON.stringify({
subtasks: [
@@ -205,7 +205,7 @@ describe("session reconnect + replay", () => {
});
it("replays mission interview buffered events using query lastEventId", async () => {
mockCreateKbAgent.mockImplementation(async () =>
mockCreateFnAgent.mockImplementation(async () =>
makePlanningAgent([
JSON.stringify({
type: "question",

View File

@@ -14,7 +14,7 @@ import { Database } from "@fusion/core";
import { AiSessionStore, type AiSessionRow } from "../ai-session-store.js";
import {
__resetPlanningState,
__setCreateKbAgent,
__setCreateFnAgent,
createSession,
getSession,
setAiSessionStore as setPlanningAiSessionStore,
@@ -33,12 +33,12 @@ import {
submitMissionInterviewResponse,
} from "../mission-interview.js";
const { mockCreateKbAgent } = vi.hoisted(() => ({
mockCreateKbAgent: vi.fn(),
const { mockCreateFnAgent } = vi.hoisted(() => ({
mockCreateFnAgent: vi.fn(),
}));
vi.mock("@fusion/engine", () => ({
createFnAgent: mockCreateKbAgent,
createFnAgent: mockCreateFnAgent,
}));
function makeTmpDir(): string {
@@ -93,7 +93,7 @@ describe("session resume + history restore", () => {
});
afterEach(async () => {
__setCreateKbAgent(undefined as any);
__setCreateFnAgent(undefined as any);
__resetPlanningState();
__resetSubtaskBreakdownState();
__resetMissionInterviewState();
@@ -142,7 +142,7 @@ describe("session resume + history restore", () => {
}),
]);
const createFnAgentSpy = vi.fn(async () => resumedAgent);
__setCreateKbAgent(createFnAgentSpy as any);
__setCreateFnAgent(createFnAgentSpy as any);
const restored = getSession(row.id);
expect(restored).toBeDefined();
@@ -198,7 +198,7 @@ describe("session resume + history restore", () => {
}),
]);
const createFnAgentSpy = vi.fn(async () => resumedAgent);
mockCreateKbAgent.mockImplementation(createFnAgentSpy);
mockCreateFnAgent.mockImplementation(createFnAgentSpy);
const restored = getMissionInterviewSession(row.id);
expect(restored).toBeDefined();
@@ -268,7 +268,7 @@ describe("session resume + history restore", () => {
});
it("starts fresh planning and mission sessions with empty history", async () => {
__setCreateKbAgent(
__setCreateFnAgent(
async () =>
createMockAgent([
JSON.stringify({
@@ -278,7 +278,7 @@ describe("session resume + history restore", () => {
]),
);
mockCreateKbAgent.mockImplementation(async () =>
mockCreateFnAgent.mockImplementation(async () =>
createMockAgent([
JSON.stringify({
type: "question",