feat(FN-4400): complete Step 4 — add heartbeat prompt trim helpers

Fusion-Task-Id: FN-4400
Fusion-Task-Lineage: 0d4f9c48-5b8b-49eb-9cf3-d1d585ffe7fc
This commit is contained in:
Fusion
2026-05-14 03:41:56 -07:00
committed by gsxdsm
parent a47904d968
commit 5c9a1b0463
2 changed files with 140 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
import { describe, expect, it } from "vitest";
import { trimPromptMd, trimTaskDescription, trimTriggeringComments } from "../heartbeat-prompt-trim.js";
const TASK_MARKER = "… (truncated, use fn_task_show for full)";
const COMMENT_MARKER = "… (older comments hidden, fetch via fn_task_show)";
describe("trimTaskDescription", () => {
it("passes through under-cap text", () => {
expect(trimTaskDescription("abc", "default")).toBe("abc");
});
it("preserves exact-cap default", () => {
const value = "a".repeat(800);
expect(trimTaskDescription(value, "default")).toBe(value);
});
it("truncates over-cap default", () => {
const value = "a".repeat(900);
const trimmed = trimTaskDescription(value, "default");
expect(trimmed.length).toBe(800);
expect(trimmed.endsWith(TASK_MARKER)).toBe(true);
});
it("preserves exact-cap compact", () => {
const value = "b".repeat(400);
expect(trimTaskDescription(value, "compact")).toBe(value);
});
it("truncates over-cap compact", () => {
const value = "b".repeat(500);
const trimmed = trimTaskDescription(value, "compact");
expect(trimmed.length).toBe(400);
expect(trimmed.endsWith(TASK_MARKER)).toBe(true);
});
});
describe("trimPromptMd", () => {
it("returns undefined unchanged", () => {
expect(trimPromptMd(undefined, "default")).toBeUndefined();
});
it("passes through under-cap text", () => {
expect(trimPromptMd("abc", "compact")).toBe("abc");
});
it("preserves exact-cap default", () => {
const value = "x".repeat(4000);
expect(trimPromptMd(value, "default")).toBe(value);
});
it("truncates over-cap default", () => {
const value = "x".repeat(4200);
const trimmed = trimPromptMd(value, "default");
expect(trimmed).toBeDefined();
expect(trimmed!.length).toBe(4000);
expect(trimmed!.endsWith(TASK_MARKER)).toBe(true);
});
it("preserves exact-cap compact", () => {
const value = "y".repeat(1500);
expect(trimPromptMd(value, "compact")).toBe(value);
});
it("truncates over-cap compact", () => {
const value = "y".repeat(1700);
const trimmed = trimPromptMd(value, "compact");
expect(trimmed).toBeDefined();
expect(trimmed!.length).toBe(1500);
expect(trimmed!.endsWith(TASK_MARKER)).toBe(true);
});
});
describe("trimTriggeringComments", () => {
it("passes through empty input", () => {
expect(trimTriggeringComments([], "default")).toEqual([]);
});
it("passes through <=3 lines", () => {
const lines = ["one", "two", "three"];
expect(trimTriggeringComments(lines, "compact")).toEqual(lines);
});
it("takes the last 3 entries without re-sorting", () => {
const lines = ["t1", "t2", "t3", "t4", "t5"];
expect(trimTriggeringComments(lines, "default")).toEqual(["t3", "t4", "t5"]);
});
it("caps joined body at 500 chars and appends marker", () => {
const lines = ["h1", "h2", `A${"z".repeat(600)}`, `B${"z".repeat(600)}`, `C${"z".repeat(600)}`];
const trimmed = trimTriggeringComments(lines, "default");
const joined = trimmed.join("\n");
expect(joined.length).toBe(500);
expect(joined.endsWith(COMMENT_MARKER)).toBe(true);
});
});

View File

@@ -0,0 +1,45 @@
import type { HeartbeatPromptTemplate } from "@fusion/core";
const TASK_DESC_CAP: Record<HeartbeatPromptTemplate, number> = {
default: 800,
compact: 400,
};
const PROMPT_MD_CAP: Record<HeartbeatPromptTemplate, number> = {
default: 4000,
compact: 1500,
};
const TASK_TRUNCATION_MARKER = "… (truncated, use fn_task_show for full)";
const COMMENTS_TRUNCATION_MARKER = "… (older comments hidden, fetch via fn_task_show)";
function truncate(value: string, cap: number, marker: string): string {
if (value.length <= cap) {
return value;
}
const sliceLength = Math.max(0, cap - marker.length);
return `${value.slice(0, sliceLength)}${marker}`;
}
export function trimTaskDescription(description: string, template: HeartbeatPromptTemplate): string {
return truncate(description, TASK_DESC_CAP[template], TASK_TRUNCATION_MARKER);
}
export function trimPromptMd(prompt: string | undefined, template: HeartbeatPromptTemplate): string | undefined {
if (prompt === undefined) {
return undefined;
}
return truncate(prompt, PROMPT_MD_CAP[template], TASK_TRUNCATION_MARKER);
}
export function trimTriggeringComments(lines: string[], _template: HeartbeatPromptTemplate): string[] {
if (lines.length <= 3) {
return lines;
}
const selected = lines.slice(-3);
const joined = selected.join("\n");
if (joined.length <= 500) {
return selected;
}
const truncated = truncate(joined, 500, COMMENTS_TRUNCATION_MARKER);
return truncated.split("\n");
}