FN-7364: hide task chats from common feed by default
Hide task-detail planner chats from the common Chat feed unless a project opts in. - Add a project setting and Settings UI toggle for showing populated task chats in the common feed. - Filter task-planner sessions on chat list routes and avoid stale cached/SSE planner rows in the Chat hook. - Cover default-hidden and opt-in behavior with route, hook, settings, and parity tests. - Document the Chat feed behavior and include a patch changeset. Files changed: .changeset/fn-7364-task-chat-feed-setting.md | 7 ++ docs/dashboard-guide.md | 2 + .../core/src/__tests__/settings-parity.test.ts | 8 +++ packages/core/src/settings-schema.ts | 5 ++ packages/core/src/types.ts | 5 ++ .../__tests__/SettingsModal.general.test.tsx | 18 +++++ .../settings/sections/GeneralSection.tsx | 9 +++ .../dashboard/app/hooks/__tests__/useChat.test.ts | 65 +++++++++++++++++ packages/dashboard/app/hooks/useChat.ts | 22 ++++-- .../dashboard/src/__tests__/chat-routes.test.ts | 82 +++++++++++++++++++++- .../dashboard/src/routes/register-chat-routes.ts | 13 +++- 11 files changed, 227 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-7364 Fusion-Task-Lineage: 79a0706c-ba7f-4417-b88b-c5282ae7877c Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7364-task-chat-feed-setting.md
Normal file
7
.changeset/fn-7364-task-chat-feed-setting.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Hide task planner chats from the common Chat feed unless enabled in Settings.
|
||||
category: fix
|
||||
dev: Adds project setting `showTaskChatsInCommonFeed` and filters task-planner sessions in chat list APIs/client refresh.
|
||||
@@ -403,6 +403,8 @@ Chat view provides project-scoped conversations with agents.
|
||||
- On mobile direct-chat threads, entering a thread and restoring Chat after tab/page visibility returns re-anchors to the newest message (`scrollTop = scrollHeight`) so the view always opens at the live tail.
|
||||
- On mobile direct-chat threads, tapping the active title/identity in the thread header opens a lightweight conversation dropdown so you can switch to another direct session or start a New Chat without backing out to the sidebar list first; long conversation titles now stay readable in the dropdown via wrapped option text and taller touch-friendly rows.
|
||||
- Direct chat sessions can be renamed from the desktop conversation context menu and from the mobile session switcher; blank rename submissions clear the custom title so the default session label is shown again.
|
||||
<!-- FNXC:ChatViewDocs 2026-07-01-00:00: Task-detail planner chats are intentionally hidden from the common Direct feed by default after issue #1850; Settings keeps an opt-in for operators who want populated task-planner sessions restored without adding a mandatory Tasks tab. -->
|
||||
- Task-detail planner Chat conversations stay available from each task's **Chat** tab. They are hidden from the common Direct/common Chat feed by default; enable **Settings → Project General → Show task chats in common Chat feed** to include populated task chats again. Empty task chat sessions stay hidden either way.
|
||||
<!-- FNXC:ChatContextWindow 2026-06-27-00:00: Direct-chat docs must describe the desktop/tablet-only estimated token budget indicator and its intentional absence from mobile, narrow floating chat, and room headers. -->
|
||||
- On desktop/tablet Direct chat, the thread header shows an estimated token count against the active model's known context window (for example `~12.3k / 200k`). It is hidden on mobile, narrow floating chat, rooms, and unknown-context-window models.
|
||||
<!-- FNXC:ChatViewDocs 2026-06-28-14:52: Chat responsive docs must reflect that narrow chat hosts now key bubble width off the ChatView container, not just viewport media, so Quick Chat popups and the right dock on desktop viewports get the same full-width bubbles as phone Chat. -->
|
||||
|
||||
@@ -112,6 +112,14 @@ describe("settings key parity", () => {
|
||||
expect(isGlobalSettingsKey("autoClaimCandidatesInPrompt")).toBe(false);
|
||||
});
|
||||
|
||||
it("defaults task chats out of the common feed and keeps the opt-in project-scoped", () => {
|
||||
expect(DEFAULT_PROJECT_SETTINGS.showTaskChatsInCommonFeed).toBe(false);
|
||||
expect(isProjectSettingsKey("showTaskChatsInCommonFeed")).toBe(true);
|
||||
expect(isGlobalSettingsKey("showTaskChatsInCommonFeed")).toBe(false);
|
||||
expect(PROJECT_SETTINGS_KEYS).toContain("showTaskChatsInCommonFeed");
|
||||
expect(GLOBAL_SETTINGS_KEYS).not.toContain("showTaskChatsInCommonFeed");
|
||||
});
|
||||
|
||||
it("defaults chatAutoCleanupDays to off and keeps it project-scoped", () => {
|
||||
expect(DEFAULT_PROJECT_SETTINGS.chatAutoCleanupDays).toBe(0);
|
||||
expect(isProjectSettingsKey("chatAutoCleanupDays")).toBe(true);
|
||||
|
||||
@@ -546,6 +546,11 @@ export const DEFAULT_PROJECT_SETTINGS = {
|
||||
*/
|
||||
quickChatCloseOnOutsideClick: true,
|
||||
showQuickChatFAB: false,
|
||||
/*
|
||||
FNXC:ChatModal 2026-07-01-00:00:
|
||||
Task-scoped planner chats stay available from each task's Chat tab, but the common Chat feed hides them by default. This project-level opt-in preserves the previous populated-task-chat feed behavior only for operators who request it.
|
||||
*/
|
||||
showTaskChatsInCommonFeed: false,
|
||||
chatAutoCleanupDays: 0,
|
||||
mailAutoCleanupDays: 0,
|
||||
operationalLogRetentionDays: 30,
|
||||
|
||||
@@ -4431,6 +4431,11 @@ export interface ProjectSettings {
|
||||
quickChatCloseOnOutsideClick?: boolean;
|
||||
/** Legacy Quick Chat FAB toggle. Prefer quickChatButtonMode for new callers. */
|
||||
showQuickChatFAB?: boolean;
|
||||
/**
|
||||
* FNXC:ChatModal 2026-07-01-00:00:
|
||||
* Task planner sessions (`task-planner:<taskId>`) are hidden from the common Chat feed by default to keep task-detail planning conversations out of Direct chat clutter. Operators can opt back into the previous shared-feed behavior with this project setting.
|
||||
*/
|
||||
showTaskChatsInCommonFeed?: boolean;
|
||||
/** Number of days of chat inactivity before old chat sessions/rooms are auto-cleaned.
|
||||
* Allowed values: 0 (off, default), 7, 14, 30, 60, 90. Uses updatedAt inactivity age. */
|
||||
chatAutoCleanupDays?: number;
|
||||
|
||||
@@ -729,6 +729,15 @@ describe("SettingsModal", () => {
|
||||
expect(onQuickChatButtonModeChange).toHaveBeenCalledWith("footer");
|
||||
});
|
||||
|
||||
it("defaults task chats common-feed opt-in to unchecked", async () => {
|
||||
renderModal({ initialSection: "general" });
|
||||
await waitForSettingsModalReady();
|
||||
|
||||
const toggle = screen.getByLabelText("Show task chats in common Chat feed") as HTMLInputElement;
|
||||
expect(toggle).toBeInTheDocument();
|
||||
expect(toggle.checked).toBe(false);
|
||||
});
|
||||
|
||||
it.each<PersistSettingInput>([
|
||||
{
|
||||
section: "Project General",
|
||||
@@ -754,6 +763,14 @@ describe("SettingsModal", () => {
|
||||
scope: "project",
|
||||
expectedKey: "quickChatCloseOnOutsideClick",
|
||||
},
|
||||
{
|
||||
section: "Project General",
|
||||
label: "Show task chats in common Chat feed",
|
||||
kind: "checkbox",
|
||||
value: true,
|
||||
scope: "project",
|
||||
expectedKey: "showTaskChatsInCommonFeed",
|
||||
},
|
||||
{
|
||||
section: "Project General",
|
||||
label: "Operational log retention",
|
||||
@@ -824,6 +841,7 @@ describe("SettingsModal", () => {
|
||||
const ephemeralToggle = screen.getByLabelText("Use ephemeral task-worker agents") as HTMLInputElement;
|
||||
expect(ephemeralToggle).toBeInTheDocument();
|
||||
expect(ephemeralToggle.checked).toBe(true);
|
||||
expect(screen.getByLabelText("Show task chats in common Chat feed")).toBeInTheDocument();
|
||||
|
||||
Object.defineProperty(window, "matchMedia", {
|
||||
writable: true,
|
||||
|
||||
@@ -152,6 +152,15 @@ export function GeneralSection({ scopeBanner, form, setForm, projectId, addToast
|
||||
<small>{t("settings.general.quickChatCloseOnOutsideClickHint", "When enabled, clicking outside the Quick Chat window closes it. Disable to keep it open until you close it explicitly.")}</small>
|
||||
</div>
|
||||
<h4 className="settings-section-heading settings-section-heading--spaced">{t("settings.general.chatHistory", "Chat history")}</h4>
|
||||
{/*
|
||||
FNXC:ChatModal 2026-07-01-00:00:
|
||||
Users asked for task-planner chats to stop cluttering the common Direct feed without forcing a new Direct/Rooms/Tasks tab split. Keep the default hidden and expose this project opt-in for operators who want the previous shared-feed behavior.
|
||||
*/}
|
||||
<div className="form-group">
|
||||
<label htmlFor="showTaskChatsInCommonFeed" className="checkbox-label">
|
||||
<input id="showTaskChatsInCommonFeed" type="checkbox" checked={form.showTaskChatsInCommonFeed === true} onChange={(e) => setForm((f) => ({ ...f, showTaskChatsInCommonFeed: e.target.checked }))}/>{t("settings.general.showTaskChatsInCommonFeed", "Show task chats in common Chat feed")}</label>
|
||||
<small>{t("settings.general.showTaskChatsInCommonFeedHint", "When enabled, populated task-detail Chat conversations appear in the common Direct feed. Empty task chats stay hidden.")}</small>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="chatAutoCleanupDays">{t("settings.general.autoCleanupOldChats", "Auto-cleanup old chats")}</label>
|
||||
<select id="chatAutoCleanupDays" className="select" value={form.chatAutoCleanupDays ?? 0} onChange={(e) => setForm((f) => ({ ...f, chatAutoCleanupDays: Number(e.target.value) || 0 }))}>
|
||||
|
||||
@@ -197,6 +197,44 @@ describe("useChat", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("does not hydrate cached task-planner sessions before server settings filtering returns", async () => {
|
||||
const projectId = "proj-cache-task-planner";
|
||||
localStorage.setItem(
|
||||
chatSessionsCacheKey(projectId),
|
||||
JSON.stringify({
|
||||
savedAt: Date.now(),
|
||||
data: [
|
||||
makeSession({ id: "session-direct", agentId: "agent-001", updatedAt: "2026-04-08T00:00:00.000Z" }),
|
||||
makeSession({ id: "session-planner", agentId: "task-planner:FN-7364", updatedAt: "2026-04-09T00:00:00.000Z" }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
let resolveFetch: ((value: { sessions: ChatSession[] }) => void) | undefined;
|
||||
mockFetchChatSessions.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
resolveFetch = resolve;
|
||||
}),
|
||||
);
|
||||
|
||||
const { result } = renderHook(() => useChat(projectId));
|
||||
|
||||
expect(result.current.sessions.map((session) => session.id)).toEqual(["session-direct"]);
|
||||
|
||||
await act(async () => {
|
||||
resolveFetch?.({
|
||||
sessions: [
|
||||
makeSession({ id: "session-planner", agentId: "task-planner:FN-7364", updatedAt: "2026-04-09T00:00:00.000Z" }),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.sessions.map((session) => session.id)).toEqual(["session-planner"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("writes sorted sessions to cache after successful refresh", async () => {
|
||||
const projectId = "proj-write-through";
|
||||
mockFetchChatSessions.mockResolvedValueOnce({
|
||||
@@ -3125,6 +3163,33 @@ describe("useChat", () => {
|
||||
expect(mockFetchChatSessions).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("uses server-filtered refresh instead of directly adding populated task-planner create events", async () => {
|
||||
mockFetchChatSessions
|
||||
.mockResolvedValueOnce({ sessions: [] })
|
||||
.mockResolvedValueOnce({ sessions: [] });
|
||||
|
||||
const { result } = renderHook(() => useChat("proj-123"));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.sessions).toHaveLength(0);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
subscribeHandler["chat:session:created"]?.({
|
||||
data: JSON.stringify({
|
||||
...makeSession({ id: "chat-planner", agentId: "task-planner:FN-7337" }),
|
||||
lastMessagePreview: "Hello",
|
||||
lastMessageAt: "2026-04-08T00:01:00.000Z",
|
||||
}),
|
||||
} as MessageEvent);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockFetchChatSessions).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
expect(result.current.sessions).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("avoids duplicate sessions on chat:session:created", async () => {
|
||||
mockFetchChatSessions.mockResolvedValueOnce({
|
||||
sessions: [makeSession({ id: "session-001", agentId: "agent-001" })],
|
||||
|
||||
@@ -21,8 +21,12 @@ import type { Agent, ChatInFlightGenerationState, ChatMessage } from "@fusion/co
|
||||
const ACTIVE_SESSION_STORAGE_KEY = "kb-chat-active-session";
|
||||
const TASK_PLANNER_CHAT_AGENT_ID_PREFIX = "task-planner:";
|
||||
|
||||
function isTaskPlannerSession(session: ChatSessionInfo): boolean {
|
||||
return session.agentId.startsWith(TASK_PLANNER_CHAT_AGENT_ID_PREFIX);
|
||||
}
|
||||
|
||||
function isEmptyTaskPlannerSession(session: ChatSessionInfo): boolean {
|
||||
return session.agentId.startsWith(TASK_PLANNER_CHAT_AGENT_ID_PREFIX) && !session.lastMessageAt && !session.lastMessagePreview;
|
||||
return isTaskPlannerSession(session) && !session.lastMessageAt && !session.lastMessagePreview;
|
||||
}
|
||||
|
||||
export interface ChatSessionInfo {
|
||||
@@ -297,7 +301,12 @@ export function useChat(
|
||||
return [] as ChatSessionInfo[];
|
||||
}
|
||||
|
||||
return readCache<ChatSessionInfo[]>(cacheKey, { maxAgeMs: SWR_TASKS_MAX_AGE_MS }) ?? [];
|
||||
const cachedSessions = readCache<ChatSessionInfo[]>(cacheKey, { maxAgeMs: SWR_TASKS_MAX_AGE_MS }) ?? [];
|
||||
/*
|
||||
FNXC:ChatModal 2026-07-01-00:00:
|
||||
Server settings decide whether task-planner sessions belong in the common feed. Do not hydrate cached task chats before that filtered list returns, otherwise a stale cache can briefly expose hidden task-detail conversations and their controls.
|
||||
*/
|
||||
return cachedSessions.filter((session) => !isTaskPlannerSession(session));
|
||||
},
|
||||
[getChatSessionsCacheKey],
|
||||
);
|
||||
@@ -1329,10 +1338,13 @@ export function useChat(
|
||||
if (isStale()) return;
|
||||
const session: ChatSessionInfo = JSON.parse(e.data);
|
||||
/*
|
||||
FNXC:TaskDetailPlannerChat 2026-06-30-18:35:
|
||||
Global Chat may list task-planner sessions after user interaction, but SSE creation can arrive before the first message preview. Ignore empty planner-session creates and let the message event refresh the server-filtered list after the user message exists.
|
||||
FNXC:TaskDetailPlannerChat 2026-07-01-00:00:
|
||||
Task-planner visibility is project-settings controlled on the server. Treat any planner SSE create as a refresh hint instead of inserting it directly, so the common feed only shows populated planner sessions when the project explicitly opts in and never shows empty planner rows.
|
||||
*/
|
||||
if (isEmptyTaskPlannerSession(session)) return;
|
||||
if (isTaskPlannerSession(session)) {
|
||||
if (!isEmptyTaskPlannerSession(session)) void refreshSessions();
|
||||
return;
|
||||
}
|
||||
// Avoid duplicates
|
||||
setSessions((prev) => {
|
||||
if (prev.some((s) => s.id === session.id)) return prev;
|
||||
|
||||
@@ -240,6 +240,12 @@ vi.mock("../project-store-resolver.js", () => ({
|
||||
// ── Mock Store ──────────────────────────────────────────────────────────────
|
||||
|
||||
class MockStore extends EventEmitter {
|
||||
settings = { showTaskChatsInCommonFeed: false };
|
||||
|
||||
async getSettings() {
|
||||
return this.settings;
|
||||
}
|
||||
|
||||
getRootDir(): string {
|
||||
return "/tmp/fn-chat-test";
|
||||
}
|
||||
@@ -739,7 +745,7 @@ describe("Chat API Routes", () => {
|
||||
expect((response.body as any).sessions.map((session: any) => session.id)).toEqual(["chat-normal"]);
|
||||
});
|
||||
|
||||
it("shows planner-chat sessions in the global list after a user message exists", async () => {
|
||||
it("hides populated planner-chat sessions in the global list by default", async () => {
|
||||
const normalSession = { ...sampleSession, id: "chat-normal", agentId: "agent-001" };
|
||||
const populatedPlanner = { ...sampleSession, id: "chat-planner", agentId: "task-planner:FN-7337" };
|
||||
const plannerMessage = {
|
||||
@@ -757,8 +763,80 @@ describe("Chat API Routes", () => {
|
||||
const response = await request(app, "GET", "/api/chat/sessions");
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect((response.body as any).sessions.map((session: any) => session.id)).toEqual(["chat-normal", "chat-planner"]);
|
||||
expect((response.body as any).sessions.map((session: any) => session.id)).toEqual(["chat-normal"]);
|
||||
});
|
||||
|
||||
it("shows populated planner-chat sessions in the global list only when the project opts in", async () => {
|
||||
store.settings.showTaskChatsInCommonFeed = true;
|
||||
const normalSession = { ...sampleSession, id: "chat-normal", agentId: "agent-001" };
|
||||
const populatedPlanner = { ...sampleSession, id: "chat-planner", agentId: "task-planner:FN-7337" };
|
||||
const duplicatePlanner = { ...sampleSession, id: "chat-planner-older", agentId: "task-planner:FN-7337" };
|
||||
const emptyPlanner = { ...sampleSession, id: "chat-empty-planner", agentId: "task-planner:FN-7338" };
|
||||
const plannerMessage = {
|
||||
id: "msg-planner",
|
||||
sessionId: "chat-planner",
|
||||
role: "user",
|
||||
content: "What should happen next?",
|
||||
thinkingOutput: null,
|
||||
metadata: null,
|
||||
createdAt: "2026-06-30T18:35:00.000Z",
|
||||
};
|
||||
const duplicateMessage = {
|
||||
...plannerMessage,
|
||||
id: "msg-planner-older",
|
||||
sessionId: "chat-planner-older",
|
||||
content: "Earlier planning note",
|
||||
};
|
||||
mockListSessions.mockReturnValue([normalSession, populatedPlanner, duplicatePlanner, emptyPlanner]);
|
||||
mockGetLastMessageForSessions.mockReturnValue(new Map([
|
||||
["chat-planner", plannerMessage],
|
||||
["chat-planner-older", duplicateMessage],
|
||||
]));
|
||||
|
||||
const response = await request(app, "GET", "/api/chat/sessions");
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect((response.body as any).sessions.map((session: any) => session.id)).toEqual(["chat-normal", "chat-planner", "chat-planner-older"]);
|
||||
expect((response.body as any).sessions[1].lastMessagePreview).toBe("What should happen next?");
|
||||
expect((response.body as any).sessions[2].lastMessagePreview).toBe("Earlier planning note");
|
||||
});
|
||||
|
||||
it("uses the requested project's setting when deciding whether planner-chat sessions appear", async () => {
|
||||
const { createServer } = await import("../server.js");
|
||||
const populatedPlanner = { ...sampleSession, id: "chat-planner", agentId: "task-planner:FN-7337", projectId: "proj-secondary" };
|
||||
const plannerMessage = {
|
||||
id: "msg-planner",
|
||||
sessionId: "chat-planner",
|
||||
role: "user",
|
||||
content: "Project scoped planning",
|
||||
thinkingOutput: null,
|
||||
metadata: null,
|
||||
createdAt: "2026-06-30T18:35:00.000Z",
|
||||
};
|
||||
const engineListSessions = vi.fn().mockReturnValue([populatedPlanner]);
|
||||
const engineChatStore = {
|
||||
...mockChatStoreInstance,
|
||||
listSessions: engineListSessions,
|
||||
getLastMessageForSessions: vi.fn().mockReturnValue(new Map([["chat-planner", plannerMessage]])),
|
||||
};
|
||||
const secondaryStore = new MockStore();
|
||||
secondaryStore.settings.showTaskChatsInCommonFeed = true;
|
||||
const mockEngine = { getTaskStore: () => secondaryStore, getChatStore: () => engineChatStore };
|
||||
const mockEngineManager = {
|
||||
getEngine: vi.fn((id: string) => (id === "proj-secondary" ? mockEngine : undefined)),
|
||||
getAllEngines: vi.fn().mockReturnValue(new Map([["proj-secondary", mockEngine]])),
|
||||
};
|
||||
const appWithEngine = createServer(store as any, {
|
||||
chatStore: mockChatStore as any,
|
||||
chatManager: mockChatManager as any,
|
||||
engineManager: mockEngineManager as any,
|
||||
});
|
||||
|
||||
const response = await request(appWithEngine, "GET", "/api/chat/sessions?projectId=proj-secondary");
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect((response.body as any).sessions.map((session: any) => session.id)).toEqual(["chat-planner"]);
|
||||
expect(engineListSessions).toHaveBeenCalledWith({ projectId: "proj-secondary" });
|
||||
});
|
||||
|
||||
it("preserves explicit planner resume lookup even when the session has no messages", async () => {
|
||||
|
||||
@@ -210,7 +210,7 @@ export function registerChatRoutes(ctx: ApiRoutesContext, deps: ChatRouteDeps):
|
||||
modelProvider?: string;
|
||||
modelId?: string;
|
||||
};
|
||||
const { chatStore } = await resolveScopedChatStore(projectId);
|
||||
const { store: scopedStore, chatStore } = await resolveScopedChatStore(projectId);
|
||||
|
||||
const isResumeLookup = lookup === "resume";
|
||||
const hasModelProvider = typeof modelProvider === "string" && modelProvider.trim().length > 0;
|
||||
@@ -250,11 +250,20 @@ export function registerChatRoutes(ctx: ApiRoutesContext, deps: ChatRouteDeps):
|
||||
const lastMessages = chatStore.getLastMessageForSessions(sessionIds);
|
||||
|
||||
if (!isResumeLookup) {
|
||||
const settings = await scopedStore.getSettings();
|
||||
const showTaskChatsInCommonFeed = settings.showTaskChatsInCommonFeed === true;
|
||||
/*
|
||||
FNXC:TaskDetailPlannerChat 2026-06-30-18:35:
|
||||
Planner-chat sessions may appear in global Chat only after a user has sent at least one message. Lazy creation prevents most empty rows; this server-side guard keeps stale/legacy task-planner rows with no messages out of every global Chat surface while preserving normal direct and room sessions.
|
||||
|
||||
FNXC:ChatModal 2026-07-01-00:00:
|
||||
The common Chat feed now excludes task-planner sessions unless the project setting explicitly opts in. Resume lookups and task-detail Chat routes bypass this common-feed filter so task planning history remains reachable from task detail.
|
||||
*/
|
||||
sessions = sessions.filter((session) => !session.agentId.startsWith(TASK_PLANNER_CHAT_AGENT_ID_PREFIX) || lastMessages.has(session.id));
|
||||
sessions = sessions.filter((session) => {
|
||||
if (!session.agentId.startsWith(TASK_PLANNER_CHAT_AGENT_ID_PREFIX)) return true;
|
||||
if (!showTaskChatsInCommonFeed) return false;
|
||||
return lastMessages.has(session.id);
|
||||
});
|
||||
}
|
||||
|
||||
// Batch-gather generating session IDs to avoid N+1 calls
|
||||
|
||||
Reference in New Issue
Block a user