fix(FN-2116): use sqlite-backed agent storage

This commit is contained in:
gsxdsm
2026-04-18 17:25:17 -07:00
parent e7a09c77a8
commit 301faefbd6
20 changed files with 587 additions and 451 deletions

View File

@@ -202,9 +202,9 @@ function AppInner() {
const skillsEnabled = experimentalFeatures.skillsView === true;
const nodesEnabled = experimentalFeatures.nodesView === true;
const agentsEnabled = experimentalFeatures.agentsView === true;
const agentsEnabled = true;
// Redirect to board if insights/roadmaps view is disabled
// Redirect to board if feature-gated views are disabled.
// Only run after settings have been loaded (experimentalFeatures is non-empty)
useEffect(() => {
if (Object.keys(experimentalFeatures).length === 0) return;

View File

@@ -11,7 +11,7 @@
* Protected features:
* - List view toggle
* - Board view toggle
* - Agents view toggle (via header fallback on mobile when mobile nav is disabled)
* - Agents view toggle
* - Project overview / "All Projects" navigation
* - Secondary features via "More" sheet (settings, git, terminal, etc.)
*/
@@ -111,10 +111,12 @@ describe("Mobile Feature Access Regression Guard", () => {
expect(props.onChangeView).toHaveBeenCalledWith("list");
});
it("agents tab is not rendered in the mobile nav bar", () => {
render(<MobileNavBar {...createDefaultMobileNavProps()} />);
it("agents view is accessible via mobile nav bar", () => {
const props = createDefaultMobileNavProps();
render(<MobileNavBar {...props} />);
expect(screen.queryByTestId("mobile-nav-tab-agents")).toBeNull();
fireEvent.click(screen.getByTestId("mobile-nav-tab-agents"));
expect(props.onChangeView).toHaveBeenCalledWith("agents");
});
it("project list is accessible via header overflow menu on mobile", () => {
@@ -214,7 +216,8 @@ describe("Mobile Feature Access Regression Guard", () => {
fireEvent.click(screen.getByTestId("mobile-nav-tab-tasks"));
expect(mobileNavOnChangeView).toHaveBeenCalledWith("board");
expect(screen.queryByTestId("mobile-nav-tab-agents")).toBeNull();
fireEvent.click(screen.getByTestId("mobile-nav-tab-agents"));
expect(mobileNavOnChangeView).toHaveBeenCalledWith("agents");
mobileNav.unmount();

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import {
Activity,
Bot,
Brain,
ChevronRight,
Clock,
@@ -196,6 +197,18 @@ export function MobileNavBar({
<span className="mobile-nav-tab-label">Tasks</span>
</button>
<button
type="button"
className={`mobile-nav-tab${view === "agents" ? " mobile-nav-tab--active" : ""}`}
data-testid="mobile-nav-tab-agents"
role="tab"
aria-selected={view === "agents"}
onClick={() => onChangeView("agents")}
>
<Bot />
<span className="mobile-nav-tab-label">Agents</span>
</button>
<button
type="button"
className={`mobile-nav-tab${view === "missions" ? " mobile-nav-tab--active" : ""}`}

View File

@@ -1228,7 +1228,7 @@ describe("App view switching", () => {
localStorage.removeItem(taskViewStorageKey());
});
it("does not render agents view button when agentsView experimental feature is disabled", async () => {
it("renders agents view button when agentsView experimental feature is disabled", async () => {
// Override the default mock to exclude agentsView
vi.mocked(fetchSettings).mockResolvedValue({
...defaultSettings,
@@ -1241,7 +1241,7 @@ describe("App view switching", () => {
expect(screen.getByTitle("Board view")).toBeTruthy();
});
expect(screen.queryByTitle("Agents view")).toBeNull();
expect(screen.getByTitle("Agents view")).toBeTruthy();
// Cleanup: restore default mock
vi.mocked(fetchSettings).mockResolvedValue({ ...defaultSettings });

View File

@@ -56,11 +56,11 @@ describe("MobileNavBar", () => {
mockViewport("mobile");
});
it("renders six tab buttons (tasks + missions + chat + mailbox + skills + more) when showSkillsTab is true", () => {
it("renders seven tab buttons (tasks + agents + missions + chat + mailbox + skills + more) when showSkillsTab is true", () => {
render(<MobileNavBar {...createDefaultProps()} showSkillsTab={true} />);
expect(screen.getByTestId("mobile-nav-tab-tasks")).toBeDefined();
expect(screen.queryByTestId("mobile-nav-tab-agents")).toBeNull();
expect(screen.getByTestId("mobile-nav-tab-agents")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-missions")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-chat")).toBeDefined();
expect(screen.getByTestId("mobile-nav-tab-mailbox")).toBeDefined();
@@ -96,6 +96,18 @@ describe("MobileNavBar", () => {
expect(props.onChangeView).toHaveBeenCalledWith("mailbox");
});
it("agents tab calls onChangeView with 'agents'", () => {
const props = createDefaultProps();
render(<MobileNavBar {...props} view="board" />);
fireEvent.click(screen.getByTestId("mobile-nav-tab-agents"));
expect(props.onChangeView).toHaveBeenCalledWith("agents");
});
it("agents tab is active when view is 'agents'", () => {
render(<MobileNavBar {...createDefaultProps()} view="agents" />);
expect(screen.getByTestId("mobile-nav-tab-agents").className).toContain("mobile-nav-tab--active");
});
it("shows mailbox unread badge when mailboxUnreadCount > 0", () => {
render(<MobileNavBar {...createDefaultProps()} mailboxUnreadCount={5} />);
const badge = screen.getByTestId("mobile-nav-tab-mailbox").querySelector(".mobile-nav-tab-badge");