feat: add buildRolePrompt in agent-kit, integrate with uwf-hermes
- New buildRolePrompt() in workflow-agent-kit: four-phase prompt assembly with fallback to systemPrompt - Export from agent-kit index - Update uwf-hermes to use buildRolePrompt instead of raw systemPrompt - Add tests for all modes: four-phase, legacy, mixed Refs #359, #362
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { spawn } from "node:child_process";
|
||||
|
||||
import { type AgentContext, type AgentRunResult, createAgent } from "@uncaged/workflow-agent-kit";
|
||||
import { type AgentContext, type AgentRunResult, buildRolePrompt, createAgent } from "@uncaged/workflow-agent-kit";
|
||||
|
||||
import {
|
||||
loadHermesSession,
|
||||
@@ -34,12 +34,12 @@ function buildHistorySummary(steps: AgentContext["steps"]): string {
|
||||
/** Assemble system prompt, task, and prior step outputs for Hermes. */
|
||||
export function buildHermesPrompt(ctx: AgentContext): string {
|
||||
const roleDef = ctx.workflow.roles[ctx.role];
|
||||
const systemPrompt = roleDef?.systemPrompt ?? "";
|
||||
const rolePrompt = roleDef !== undefined ? buildRolePrompt(roleDef) : "";
|
||||
const parts: string[] = [];
|
||||
if (ctx.outputFormatInstruction !== undefined && ctx.outputFormatInstruction !== "") {
|
||||
parts.push(ctx.outputFormatInstruction, "");
|
||||
}
|
||||
parts.push(systemPrompt, "", "## Task", ctx.start.prompt);
|
||||
parts.push(rolePrompt, "", "## Task", ctx.start.prompt);
|
||||
const historyBlock = buildHistorySummary(ctx.steps);
|
||||
if (historyBlock !== "") {
|
||||
parts.push("", historyBlock);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import type { RoleDefinition } from "@uncaged/workflow-protocol";
|
||||
import { buildRolePrompt } from "../src/build-role-prompt.js";
|
||||
|
||||
describe("buildRolePrompt", () => {
|
||||
test("four-phase: all fields present", () => {
|
||||
const role: RoleDefinition = {
|
||||
description: "A coder",
|
||||
systemPrompt: "legacy prompt",
|
||||
identity: "You are a senior developer.",
|
||||
prepare: "Load cursor-agent skill.",
|
||||
execute: "Implement the feature.",
|
||||
report: "Summarize changes.",
|
||||
outputSchema: "placeholder00000" as string,
|
||||
};
|
||||
const result = buildRolePrompt(role);
|
||||
expect(result).toContain("## Identity");
|
||||
expect(result).toContain("You are a senior developer.");
|
||||
expect(result).toContain("## Prepare");
|
||||
expect(result).toContain("Load cursor-agent skill.");
|
||||
expect(result).toContain("## Execute");
|
||||
expect(result).toContain("Implement the feature.");
|
||||
expect(result).toContain("## Report");
|
||||
expect(result).toContain("Summarize changes.");
|
||||
expect(result).not.toContain("legacy prompt");
|
||||
});
|
||||
|
||||
test("legacy: only systemPrompt", () => {
|
||||
const role: RoleDefinition = {
|
||||
description: "A planner",
|
||||
systemPrompt: "You are a planning agent.",
|
||||
identity: null,
|
||||
prepare: null,
|
||||
execute: null,
|
||||
report: null,
|
||||
outputSchema: "placeholder00000" as string,
|
||||
};
|
||||
const result = buildRolePrompt(role);
|
||||
expect(result).toBe("You are a planning agent.");
|
||||
});
|
||||
|
||||
test("legacy: no fields at all", () => {
|
||||
const role = {
|
||||
description: "Minimal",
|
||||
outputSchema: "placeholder00000",
|
||||
} as unknown as RoleDefinition;
|
||||
const result = buildRolePrompt(role);
|
||||
expect(result).toBe("");
|
||||
});
|
||||
|
||||
test("mixed: identity present, partial other fields", () => {
|
||||
const role: RoleDefinition = {
|
||||
description: "A reviewer",
|
||||
systemPrompt: "legacy",
|
||||
identity: "You are a code reviewer.",
|
||||
prepare: null,
|
||||
execute: "Review the PR diff carefully.",
|
||||
report: null,
|
||||
outputSchema: "placeholder00000" as string,
|
||||
};
|
||||
const result = buildRolePrompt(role);
|
||||
expect(result).toContain("## Identity");
|
||||
expect(result).toContain("## Execute");
|
||||
expect(result).not.toContain("## Prepare");
|
||||
expect(result).not.toContain("## Report");
|
||||
expect(result).not.toContain("legacy");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { RoleDefinition } from "@uncaged/workflow-protocol";
|
||||
|
||||
/**
|
||||
* Build the role prompt from a RoleDefinition.
|
||||
*
|
||||
* Four-phase mode (identity/prepare/execute/report present):
|
||||
* Assembles structured sections for each phase.
|
||||
*
|
||||
* Legacy mode (only systemPrompt):
|
||||
* Returns systemPrompt as-is.
|
||||
*
|
||||
* When both are present, four-phase fields take priority.
|
||||
*/
|
||||
export function buildRolePrompt(role: RoleDefinition): string {
|
||||
const hasFourPhase =
|
||||
role.identity !== null &&
|
||||
role.identity !== undefined &&
|
||||
role.identity !== "";
|
||||
|
||||
if (!hasFourPhase) {
|
||||
return role.systemPrompt ?? "";
|
||||
}
|
||||
|
||||
const sections: string[] = [];
|
||||
|
||||
sections.push(`## Identity\n\n${role.identity}`);
|
||||
|
||||
if (role.prepare !== null && role.prepare !== undefined && role.prepare !== "") {
|
||||
sections.push(`## Prepare\n\n${role.prepare}`);
|
||||
}
|
||||
|
||||
if (role.execute !== null && role.execute !== undefined && role.execute !== "") {
|
||||
sections.push(`## Execute\n\n${role.execute}`);
|
||||
}
|
||||
|
||||
if (role.report !== null && role.report !== undefined && role.report !== "") {
|
||||
sections.push(`## Report\n\n${role.report}`);
|
||||
}
|
||||
|
||||
return sections.join("\n\n");
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export {
|
||||
resolveModel,
|
||||
} from "./extract.js";
|
||||
export { buildOutputFormatInstruction } from "./build-output-format-instruction.js";
|
||||
export { buildRolePrompt } from "./build-role-prompt.js";
|
||||
export type { FrontmatterFastPathResult } from "./frontmatter.js";
|
||||
export { tryFrontmatterFastPath } from "./frontmatter.js";
|
||||
export { createAgent } from "./run.js";
|
||||
|
||||
Reference in New Issue
Block a user