feat(FN-5374): raise room transcript defaults and align compaction settings

Raised room transcript defaults (`messagesBefore` and `daysBefore`) in the core settings schema and updated project-level setting defaults, with corresponding documentation refresh in the settings reference. Added full test coverage for room compaction defaults, pinned room default settings in Setti

Fusion-Task-Id: FN-5374
This commit is contained in:
Fusion (runfusion.ai)
2026-05-21 07:00:48 -07:00
committed by gsxdsm
parent e12adeb3fa
commit 5f9f777630
10 changed files with 56 additions and 32 deletions

View File

@@ -96,6 +96,18 @@ describe("settings key parity", () => {
expect(isGlobalSettingsKey("chatAutoCleanupDays")).toBe(false);
});
it("keeps room compaction defaults project-scoped with expanded retention", () => {
expect(DEFAULT_PROJECT_SETTINGS.chatRoomRecentVerbatimMessages).toBe(25);
expect(DEFAULT_PROJECT_SETTINGS.chatRoomCompactionFetchLimit).toBe(200);
expect(DEFAULT_PROJECT_SETTINGS.chatRoomSummaryMaxChars).toBe(3_000);
expect(isProjectSettingsKey("chatRoomRecentVerbatimMessages")).toBe(true);
expect(isProjectSettingsKey("chatRoomCompactionFetchLimit")).toBe(true);
expect(isProjectSettingsKey("chatRoomSummaryMaxChars")).toBe(true);
expect(isGlobalSettingsKey("chatRoomRecentVerbatimMessages")).toBe(false);
expect(isGlobalSettingsKey("chatRoomCompactionFetchLimit")).toBe(false);
expect(isGlobalSettingsKey("chatRoomSummaryMaxChars")).toBe(false);
});
it("defaults mailAutoCleanupDays to off and keeps it project-scoped", () => {
expect(DEFAULT_PROJECT_SETTINGS.mailAutoCleanupDays).toBe(0);
expect(isProjectSettingsKey("mailAutoCleanupDays")).toBe(true);

View File

@@ -389,9 +389,9 @@ export const DEFAULT_PROJECT_SETTINGS = {
showQuickChatFAB: false,
chatAutoCleanupDays: 0,
mailAutoCleanupDays: 0,
chatRoomRecentVerbatimMessages: 12,
chatRoomCompactionFetchLimit: 80,
chatRoomSummaryMaxChars: 1_500,
chatRoomRecentVerbatimMessages: 25,
chatRoomCompactionFetchLimit: 200,
chatRoomSummaryMaxChars: 3_000,
researchSettings: {
enabled: true,
searchProvider: undefined,

View File

@@ -2385,13 +2385,13 @@ export function SettingsModal({
type="number"
min="1"
className="input"
placeholder="12"
placeholder="25"
value={form.chatRoomRecentVerbatimMessages ?? ""}
onChange={(e) =>
setForm((f) => ({ ...f, chatRoomRecentVerbatimMessages: Number(e.target.value) || undefined }))
}
/>
<small>Number of most-recent chat-room messages kept verbatim in the responder transcript. Older messages are compacted into a summary block. Default: 12.</small>
<small>Number of most-recent chat-room messages kept verbatim in the responder transcript. Older messages are compacted into a summary block. Default: 25.</small>
</div>
<div className="form-group">
<label htmlFor="chatRoomCompactionFetchLimit">Room compaction fetch limit</label>
@@ -2400,13 +2400,13 @@ export function SettingsModal({
type="number"
min="1"
className="input"
placeholder="80"
placeholder="200"
value={form.chatRoomCompactionFetchLimit ?? ""}
onChange={(e) =>
setForm((f) => ({ ...f, chatRoomCompactionFetchLimit: Number(e.target.value) || undefined }))
}
/>
<small>Upper bound on messages fetched from the room store for compaction consideration. Default: 80.</small>
<small>Upper bound on messages fetched from the room store for compaction consideration. Default: 200.</small>
</div>
<div className="form-group">
<label htmlFor="chatRoomSummaryMaxChars">Room summary max characters</label>
@@ -2415,13 +2415,13 @@ export function SettingsModal({
type="number"
min="200"
className="input"
placeholder="1500"
placeholder="3000"
value={form.chatRoomSummaryMaxChars ?? ""}
onChange={(e) =>
setForm((f) => ({ ...f, chatRoomSummaryMaxChars: Number(e.target.value) || undefined }))
}
/>
<small>Hard cap on the synthesized "Earlier room context" summary block. Default: 1500.</small>
<small>Hard cap on the synthesized "Earlier room context" summary block. Default: 3000.</small>
</div>
<h4 className="settings-section-heading settings-section-heading--spaced">Capacity Risk Banner</h4>
<div className="form-group">

View File

@@ -1032,9 +1032,9 @@ describe("SettingsModal", () => {
const fetchLimitInput = screen.getByLabelText("Room compaction fetch limit") as HTMLInputElement;
const summaryMaxInput = screen.getByLabelText("Room summary max characters") as HTMLInputElement;
expect(recentInput.placeholder).toBe("12");
expect(fetchLimitInput.placeholder).toBe("80");
expect(summaryMaxInput.placeholder).toBe("1500");
expect(recentInput.placeholder).toBe("25");
expect(fetchLimitInput.placeholder).toBe("200");
expect(summaryMaxInput.placeholder).toBe("3000");
await userEvent.type(recentInput, "7");
await userEvent.type(fetchLimitInput, "60");

View File

@@ -29,7 +29,7 @@ describe("buildCompactedRoomTranscript", () => {
expect(transcript.match(/\[LATEST USER MESSAGE — ANSWER THIS\]/g)).toHaveLength(1);
});
it("prepends a compacted summary and keeps the last 12 messages verbatim", () => {
it("prepends a compacted summary and keeps the last 25 messages verbatim by default", () => {
const messages = Array.from({ length: 30 }, (_, index) => {
const olderUserLengths = [40, 80, 120, 160, 200, 220, 60, 70, 90];
const content = index < 18 && index % 2 === 0
@@ -42,7 +42,7 @@ describe("buildCompactedRoomTranscript", () => {
const transcript = buildCompactedRoomTranscript(messages, latestUserMessageId);
expect(transcript).toContain("## Earlier room context (compacted)");
expect(transcript).toContain("- Span: 18 messages from 2026-01-01T00:00:00.000Z to 2026-01-01T00:00:17.000Z");
expect(transcript).toContain("- Span: 5 messages from 2026-01-01T00:00:00.000Z to 2026-01-01T00:00:04.000Z");
expect(transcript).toContain("- Participants: User, Agent agent-a");
const [summaryBlock] = transcript.split("\n\n");
const highlightLines = summaryBlock.split("\n").filter((line) => line.startsWith(" - "));
@@ -50,9 +50,10 @@ describe("buildCompactedRoomTranscript", () => {
const highlightTimestamps = highlightLines.map((line) => line.match(/\[(.*?)\]/)?.[1] ?? "");
expect(highlightTimestamps).toEqual([...highlightTimestamps].sort());
for (let index = 18; index < 30; index += 1) {
for (let index = 5; index < 30; index += 1) {
expect(transcript).toContain(`- [2026-01-01T00:00:${String(index).padStart(2, "0")}.000Z]`);
}
expect(transcript).not.toContain("- [2026-01-01T00:00:04.000Z] (user) User:");
expect(transcript).toContain("(user) User: message-28 [LATEST USER MESSAGE — ANSWER THIS]");
});
@@ -65,7 +66,7 @@ describe("buildCompactedRoomTranscript", () => {
const transcript = buildCompactedRoomTranscript(messages, "msg-29");
expect(transcript.length).toBeLessThanOrEqual(8000);
expect(transcript.length).toBeLessThanOrEqual(20000);
expect(transcript.match(/\[LATEST USER MESSAGE — ANSWER THIS\]/g)).toHaveLength(1);
expect(transcript).toContain("message-29-");
});
@@ -81,7 +82,7 @@ describe("buildCompactedRoomTranscript", () => {
content: `recent-${index}`,
}));
const transcript = buildCompactedRoomTranscript([...olderMessages, ...recentMessages], "msg-29");
const transcript = buildCompactedRoomTranscript([...olderMessages, ...recentMessages], "msg-29", { recentVerbatim: 12 });
const [summaryBlock] = transcript.split("\n\n");
const highlightLines = summaryBlock.split("\n").filter((line) => line.startsWith(" - "));
@@ -89,8 +90,8 @@ describe("buildCompactedRoomTranscript", () => {
expect(summaryBlock).toContain("- Span: 18 messages");
expect(summaryBlock).toContain("- Participants: User");
expect(summaryBlock).toContain("- Highlights:");
expect(summaryBlock.length).toBeLessThanOrEqual(1500);
expect(highlightLines.length).toBeLessThan(5);
expect(summaryBlock.length).toBeLessThanOrEqual(3000);
expect(highlightLines.length).toBeLessThanOrEqual(5);
});
it("keeps the total transcript under the overall cap", () => {
@@ -102,7 +103,7 @@ describe("buildCompactedRoomTranscript", () => {
const transcript = buildCompactedRoomTranscript(messages, "msg-79");
expect(transcript.length).toBeLessThanOrEqual(8000);
expect(transcript.length).toBeLessThanOrEqual(20000);
expect(transcript).toContain("message-79-");
});
@@ -149,13 +150,13 @@ describe("buildCompactedRoomTranscript", () => {
}));
const settings = await (manager as any).getRoomCompactionSettings();
expect(settings).toEqual({ recentVerbatim: 12, fetchLimit: 80, summaryMaxChars: 1500 });
expect(settings).toEqual({ recentVerbatim: 25, fetchLimit: 200, summaryMaxChars: 3000 });
const managerWithThrow = new ChatManager({} as any, "/tmp", undefined, undefined, async () => {
throw new Error("boom");
});
const fallbackSettings = await (managerWithThrow as any).getRoomCompactionSettings();
expect(fallbackSettings).toEqual({ recentVerbatim: 12, fetchLimit: 80, summaryMaxChars: 1500 });
expect(fallbackSettings).toEqual({ recentVerbatim: 25, fetchLimit: 200, summaryMaxChars: 3000 });
});
it("computes unique participant labels from older messages", () => {
@@ -172,7 +173,7 @@ describe("buildCompactedRoomTranscript", () => {
content: `recent-${index}`,
}));
const transcript = buildCompactedRoomTranscript([...older, ...recent], "msg-16");
const transcript = buildCompactedRoomTranscript([...older, ...recent], "msg-16", { recentVerbatim: 12 });
expect(transcript).toContain("- Participants: User, Agent agent-a, System, Assistant, Agent agent-b");
});

View File

@@ -252,7 +252,7 @@ describe("Chat orchestration — rooms (FN-3805..FN-3811 contract)", () => {
const prompt = promptSpy.mock.calls[0]?.[0] as string;
expect(prompt).toContain("## Earlier room context (compacted)");
expect(prompt).toContain("- Span: 18 messages from 2026-01-01T00:00:00.000Z to 2026-01-01T00:00:17.000Z");
expect(prompt).toContain("- Span: 5 messages from 2026-01-01T00:00:00.000Z to 2026-01-01T00:00:04.000Z");
expect(prompt).toContain("history-item-28");
expect(prompt).toContain(" - [2026-01-01T00:00:00.000Z] User: history-item-0");
expect(prompt).not.toContain("- [2026-01-01T00:00:00.000Z] (user) User: history-item-0");

View File

@@ -138,11 +138,11 @@ const MAX_MESSAGES_PER_IP_PER_MINUTE = 30;
/** Maximum file size for # mentions (50KB). Files larger than this are skipped. */
const MAX_REFERENCED_FILE_SIZE = 50 * 1024;
const ROOM_AMBIENT_MAX_RESPONDERS = 5;
const DEFAULT_ROOM_THREAD_RECENT_VERBATIM_MESSAGES = 12;
const DEFAULT_ROOM_THREAD_COMPACTION_FETCH_LIMIT = 80;
const ROOM_THREAD_CONTEXT_MAX_CHARS = 8_000;
const DEFAULT_ROOM_THREAD_RECENT_VERBATIM_MESSAGES = 25;
const DEFAULT_ROOM_THREAD_COMPACTION_FETCH_LIMIT = 200;
const ROOM_THREAD_CONTEXT_MAX_CHARS = 20_000;
const ROOM_THREAD_MESSAGE_CONTENT_MAX_CHARS = 1_200;
const DEFAULT_ROOM_THREAD_SUMMARY_MAX_CHARS = 1_500;
const DEFAULT_ROOM_THREAD_SUMMARY_MAX_CHARS = 3_000;
const IN_FLIGHT_PERSIST_DEBOUNCE_MS = 200;
type RoomTranscriptMessage = Pick<ChatRoomMessage, "id" | "role" | "content" | "createdAt" | "senderAgentId">;

View File

@@ -119,7 +119,13 @@ describe("reliability interactions: same-agent duplicate intake", () => {
source: { sourceType: "agent_heartbeat", sourceAgentId: "agent-x" },
});
vi.spyOn(fx.store, "listTasks").mockRejectedValueOnce(new Error("boom"));
const originalListTasks = fx.store.listTasks.bind(fx.store);
vi.spyOn(fx.store, "listTasks").mockImplementation(async (options) => {
if (options?.slim === true && options?.includeArchived === false) {
throw new Error("boom");
}
return originalListTasks(options);
});
const b = await fx.store.createTask({
title: "fix: baseline clone",