feat(FN-4400): complete Step 3 — add heartbeat prompt template resolver
Fusion-Task-Id: FN-4400 Fusion-Task-Lineage: 0d4f9c48-5b8b-49eb-9cf3-d1d585ffe7fc
This commit is contained in:
@@ -1,9 +1,18 @@
|
||||
import type { Agent, ProjectSettings } from "@fusion/core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveHeartbeatScopeDisciplineMode, selectHeartbeatProcedure } from "../heartbeat-procedure-resolver.js";
|
||||
import {
|
||||
resolveHeartbeatPromptTemplate,
|
||||
resolveHeartbeatScopeDisciplineMode,
|
||||
selectHeartbeatProcedure,
|
||||
} from "../heartbeat-procedure-resolver.js";
|
||||
|
||||
const project = (mode?: ProjectSettings["heartbeatScopeDiscipline"]) => ({ heartbeatScopeDiscipline: mode });
|
||||
const agent = (mode?: unknown) => ({ runtimeConfig: mode === undefined ? {} : { heartbeatScopeDiscipline: mode } }) as Pick<Agent, "runtimeConfig">;
|
||||
const projectTemplate = (template?: ProjectSettings["heartbeatPromptTemplate"]) => ({ heartbeatPromptTemplate: template });
|
||||
const templateAgent = (role: Agent["role"], template?: unknown) => ({
|
||||
role,
|
||||
runtimeConfig: template === undefined ? {} : { heartbeatPromptTemplate: template },
|
||||
}) as Pick<Agent, "runtimeConfig" | "role">;
|
||||
|
||||
describe("resolveHeartbeatScopeDisciplineMode", () => {
|
||||
it.each(["strict", "lite", "off"] as const)("prefers agent override: %s", (mode) => {
|
||||
@@ -31,6 +40,39 @@ describe("resolveHeartbeatScopeDisciplineMode", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveHeartbeatPromptTemplate", () => {
|
||||
it("prefers agent override over project and role defaults", () => {
|
||||
expect(resolveHeartbeatPromptTemplate(projectTemplate("default"), templateAgent("triage", "compact"))).toBe("compact");
|
||||
});
|
||||
|
||||
it("uses project setting when agent override is unset", () => {
|
||||
expect(resolveHeartbeatPromptTemplate(projectTemplate("compact"), templateAgent("executor"))).toBe("compact");
|
||||
});
|
||||
|
||||
it.each(["triage", "reviewer", "merger", "scheduler", "engineer", "custom"] as const)(
|
||||
"defaults non-executor role %s to compact",
|
||||
(role) => {
|
||||
expect(resolveHeartbeatPromptTemplate(undefined, templateAgent(role))).toBe("compact");
|
||||
},
|
||||
);
|
||||
|
||||
it("defaults executor role to default", () => {
|
||||
expect(resolveHeartbeatPromptTemplate(undefined, templateAgent("executor"))).toBe("default");
|
||||
});
|
||||
|
||||
it("falls through invalid agent template to project", () => {
|
||||
expect(resolveHeartbeatPromptTemplate(projectTemplate("compact"), templateAgent("executor", "invalid"))).toBe("compact");
|
||||
});
|
||||
|
||||
it("falls through invalid project template to role default", () => {
|
||||
expect(resolveHeartbeatPromptTemplate(projectTemplate("tiny" as never), templateAgent("triage"))).toBe("compact");
|
||||
});
|
||||
|
||||
it("returns default when agent is undefined", () => {
|
||||
expect(resolveHeartbeatPromptTemplate(projectTemplate("tiny" as never), undefined)).toBe("default");
|
||||
});
|
||||
});
|
||||
|
||||
describe("selectHeartbeatProcedure", () => {
|
||||
const procedures = {
|
||||
task: { strict: "task-strict", lite: "task-lite", off: "task-off" },
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import type { Agent, HeartbeatScopeDisciplineMode, ProjectSettings } from "@fusion/core";
|
||||
import type { Agent, HeartbeatPromptTemplate, HeartbeatScopeDisciplineMode, ProjectSettings } from "@fusion/core";
|
||||
|
||||
const VALID_MODES: readonly HeartbeatScopeDisciplineMode[] = ["strict", "lite", "off"] as const;
|
||||
const VALID_TEMPLATES: readonly HeartbeatPromptTemplate[] = ["default", "compact"] as const;
|
||||
|
||||
function isHeartbeatScopeDisciplineMode(value: unknown): value is HeartbeatScopeDisciplineMode {
|
||||
return typeof value === "string" && (VALID_MODES as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
function isHeartbeatPromptTemplate(value: unknown): value is HeartbeatPromptTemplate {
|
||||
return typeof value === "string" && (VALID_TEMPLATES as readonly string[]).includes(value);
|
||||
}
|
||||
|
||||
export function resolveHeartbeatScopeDisciplineMode(
|
||||
projectSettings: Pick<ProjectSettings, "heartbeatScopeDiscipline"> | undefined,
|
||||
agent: Pick<Agent, "runtimeConfig"> | undefined,
|
||||
@@ -24,6 +29,28 @@ export function resolveHeartbeatScopeDisciplineMode(
|
||||
return "strict";
|
||||
}
|
||||
|
||||
export function resolveHeartbeatPromptTemplate(
|
||||
projectSettings: Pick<ProjectSettings, "heartbeatPromptTemplate"> | undefined,
|
||||
agent: Pick<Agent, "runtimeConfig" | "role"> | undefined,
|
||||
): HeartbeatPromptTemplate {
|
||||
const runtimeConfig = agent?.runtimeConfig as Record<string, unknown> | undefined;
|
||||
const agentTemplate = runtimeConfig?.heartbeatPromptTemplate;
|
||||
if (isHeartbeatPromptTemplate(agentTemplate)) {
|
||||
return agentTemplate;
|
||||
}
|
||||
|
||||
const projectTemplate = projectSettings?.heartbeatPromptTemplate;
|
||||
if (isHeartbeatPromptTemplate(projectTemplate)) {
|
||||
return projectTemplate;
|
||||
}
|
||||
|
||||
if (!agent) {
|
||||
return "default";
|
||||
}
|
||||
|
||||
return agent.role === "executor" ? "default" : "compact";
|
||||
}
|
||||
|
||||
export function selectHeartbeatProcedure<T>(
|
||||
mode: HeartbeatScopeDisciplineMode,
|
||||
isNoTaskRun: boolean,
|
||||
|
||||
Reference in New Issue
Block a user