refactor: rename RoleDefinition fields for clarity
- identity → goal - prepare → capabilities (string[]) - execute → procedure - report → output - outputSchema → meta Fixes #364
This commit is contained in:
@@ -211,11 +211,11 @@ describe("cmdThreadRead ### Content section", () => {
|
||||
roles: {
|
||||
writer: {
|
||||
description: "Write",
|
||||
identity: "You are a writer.",
|
||||
prepare: "",
|
||||
execute: "Write content as requested.",
|
||||
report: "Summarize what was written.",
|
||||
outputSchema: "placeholder00" as CasRef,
|
||||
goal: "You are a writer.",
|
||||
capabilities: [],
|
||||
procedure: "Write content as requested.",
|
||||
output: "Summarize what was written.",
|
||||
meta: "placeholder00" as CasRef,
|
||||
},
|
||||
},
|
||||
conditions: {},
|
||||
|
||||
@@ -500,7 +500,7 @@ function formatThreadReadMarkdown(options: {
|
||||
];
|
||||
const roleDef = workflow.roles[item.payload.role];
|
||||
if (roleDef) {
|
||||
const prompt = roleDef.identity;
|
||||
const prompt = roleDef.goal;
|
||||
stepLines.push("", "### Prompt", "", prompt);
|
||||
}
|
||||
if (item.payload.detail) {
|
||||
|
||||
@@ -42,17 +42,17 @@ function isJsonSchema(value: unknown): value is JSONSchema {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
async function resolveOutputSchemaRef(
|
||||
async function resolveMetaRef(
|
||||
uwf: UwfStore,
|
||||
roleName: string,
|
||||
outputSchema: unknown,
|
||||
meta: unknown,
|
||||
): Promise<CasRef> {
|
||||
if (!isJsonSchema(outputSchema)) {
|
||||
fail(`role "${roleName}": outputSchema must be a JSON Schema object`);
|
||||
if (!isJsonSchema(meta)) {
|
||||
fail(`role "${roleName}": meta must be a JSON Schema object`);
|
||||
}
|
||||
const schema: JSONSchema = outputSchema.title === undefined
|
||||
? { ...outputSchema, title: roleName }
|
||||
: outputSchema;
|
||||
const schema: JSONSchema = meta.title === undefined
|
||||
? { ...meta, title: roleName }
|
||||
: meta;
|
||||
return putSchema(uwf.store, schema);
|
||||
}
|
||||
|
||||
@@ -62,18 +62,18 @@ async function materializeWorkflowPayload(
|
||||
): Promise<WorkflowPayload> {
|
||||
const roles: Record<string, RoleDefinition> = {};
|
||||
for (const [roleName, role] of Object.entries(raw.roles)) {
|
||||
const outputSchema = await resolveOutputSchemaRef(
|
||||
const meta = await resolveMetaRef(
|
||||
uwf,
|
||||
`${raw.name}.${roleName}`,
|
||||
role.outputSchema,
|
||||
role.meta,
|
||||
);
|
||||
roles[roleName] = {
|
||||
description: role.description,
|
||||
identity: role.identity,
|
||||
prepare: role.prepare,
|
||||
execute: role.execute,
|
||||
report: role.report,
|
||||
outputSchema,
|
||||
goal: role.goal,
|
||||
capabilities: role.capabilities,
|
||||
procedure: role.procedure,
|
||||
output: role.output,
|
||||
meta,
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -14,15 +14,18 @@ function isRoleDefinition(value: unknown): boolean {
|
||||
if (!isRecord(value)) {
|
||||
return false;
|
||||
}
|
||||
const outputSchema = value.outputSchema;
|
||||
const schemaOk = isRecord(outputSchema) && typeof outputSchema.type === "string";
|
||||
const meta = value.meta;
|
||||
const metaOk = isRecord(meta) && typeof meta.type === "string";
|
||||
const capabilities = value.capabilities;
|
||||
const capabilitiesOk =
|
||||
Array.isArray(capabilities) && capabilities.every((c) => typeof c === "string");
|
||||
return (
|
||||
typeof value.description === "string" &&
|
||||
typeof value.identity === "string" &&
|
||||
typeof value.prepare === "string" &&
|
||||
typeof value.execute === "string" &&
|
||||
typeof value.report === "string" &&
|
||||
schemaOk
|
||||
typeof value.goal === "string" &&
|
||||
capabilitiesOk &&
|
||||
typeof value.procedure === "string" &&
|
||||
typeof value.output === "string" &&
|
||||
metaOk
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,54 +1,71 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
import type { RoleDefinition } from "@uncaged/workflow-protocol";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { buildRolePrompt } from "../src/build-role-prompt.js";
|
||||
|
||||
describe("buildRolePrompt", () => {
|
||||
test("all fields present", () => {
|
||||
const role: RoleDefinition = {
|
||||
description: "A coder",
|
||||
identity: "You are a senior developer.",
|
||||
prepare: "Load cursor-agent skill.",
|
||||
execute: "Implement the feature.",
|
||||
report: "Summarize changes.",
|
||||
outputSchema: "placeholder00000" as string,
|
||||
goal: "You are a senior developer.",
|
||||
capabilities: ["cursor-agent", "file-edit"],
|
||||
procedure: "Implement the feature.",
|
||||
output: "Summarize changes.",
|
||||
meta: "placeholder00000" as string,
|
||||
};
|
||||
const result = buildRolePrompt(role);
|
||||
expect(result).toContain("## Identity");
|
||||
expect(result).toContain("## Goal");
|
||||
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("## Capabilities");
|
||||
expect(result).toContain("- cursor-agent");
|
||||
expect(result).toContain("- file-edit");
|
||||
expect(result).toContain("## Procedure");
|
||||
expect(result).toContain("Implement the feature.");
|
||||
expect(result).toContain("## Report");
|
||||
expect(result).toContain("## Output");
|
||||
expect(result).toContain("Summarize changes.");
|
||||
});
|
||||
|
||||
test("empty fields are omitted", () => {
|
||||
const role: RoleDefinition = {
|
||||
description: "A reviewer",
|
||||
identity: "You are a code reviewer.",
|
||||
prepare: "",
|
||||
execute: "Review the PR diff carefully.",
|
||||
report: "",
|
||||
outputSchema: "placeholder00000" as string,
|
||||
goal: "You are a code reviewer.",
|
||||
capabilities: [],
|
||||
procedure: "Review the PR diff carefully.",
|
||||
output: "",
|
||||
meta: "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).toContain("## Goal");
|
||||
expect(result).toContain("## Procedure");
|
||||
expect(result).not.toContain("## Capabilities");
|
||||
expect(result).not.toContain("## Output");
|
||||
});
|
||||
|
||||
test("all empty returns empty string", () => {
|
||||
const role: RoleDefinition = {
|
||||
description: "Minimal",
|
||||
identity: "",
|
||||
prepare: "",
|
||||
execute: "",
|
||||
report: "",
|
||||
outputSchema: "placeholder00000" as string,
|
||||
goal: "",
|
||||
capabilities: [],
|
||||
procedure: "",
|
||||
output: "",
|
||||
meta: "placeholder00000" as string,
|
||||
};
|
||||
const result = buildRolePrompt(role);
|
||||
expect(result).toBe("");
|
||||
});
|
||||
|
||||
test("capabilities rendered as bullet list", () => {
|
||||
const role: RoleDefinition = {
|
||||
description: "Agent",
|
||||
goal: "",
|
||||
capabilities: ["search", "code", "browse"],
|
||||
procedure: "",
|
||||
output: "",
|
||||
meta: "placeholder00000" as string,
|
||||
};
|
||||
const result = buildRolePrompt(role);
|
||||
expect(result).toContain("## Capabilities");
|
||||
expect(result).toContain("- search");
|
||||
expect(result).toContain("- code");
|
||||
expect(result).toContain("- browse");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,26 +3,27 @@ import type { RoleDefinition } from "@uncaged/workflow-protocol";
|
||||
/**
|
||||
* Build the role prompt from a RoleDefinition.
|
||||
*
|
||||
* Assembles structured sections: Identity, Prepare, Execute, Report.
|
||||
* Empty strings are omitted from the output.
|
||||
* Assembles structured sections: Goal, Capabilities, Procedure, Output.
|
||||
* Empty strings and empty arrays are omitted from the output.
|
||||
*/
|
||||
export function buildRolePrompt(role: RoleDefinition): string {
|
||||
const sections: string[] = [];
|
||||
|
||||
if (role.identity !== "") {
|
||||
sections.push(`## Identity\n\n${role.identity}`);
|
||||
if (role.goal !== "") {
|
||||
sections.push(`## Goal\n\n${role.goal}`);
|
||||
}
|
||||
|
||||
if (role.prepare !== "") {
|
||||
sections.push(`## Prepare\n\n${role.prepare}`);
|
||||
if (role.capabilities.length > 0) {
|
||||
const list = role.capabilities.map((c) => `- ${c}`).join("\n");
|
||||
sections.push(`## Capabilities\n\n${list}`);
|
||||
}
|
||||
|
||||
if (role.execute !== "") {
|
||||
sections.push(`## Execute\n\n${role.execute}`);
|
||||
if (role.procedure !== "") {
|
||||
sections.push(`## Procedure\n\n${role.procedure}`);
|
||||
}
|
||||
|
||||
if (role.report !== "") {
|
||||
sections.push(`## Report\n\n${role.report}`);
|
||||
if (role.output !== "") {
|
||||
sections.push(`## Output\n\n${role.output}`);
|
||||
}
|
||||
|
||||
return sections.join("\n\n");
|
||||
|
||||
@@ -131,13 +131,13 @@ export function createAgent(options: AgentOptions): () => Promise<void> {
|
||||
fail(`unknown role: ${role}`);
|
||||
}
|
||||
|
||||
const outputSchema = getSchema(ctx.meta.store, roleDef.outputSchema);
|
||||
if (outputSchema !== null) {
|
||||
ctx.outputFormatInstruction = buildOutputFormatInstruction(outputSchema);
|
||||
const metaSchema = getSchema(ctx.meta.store, roleDef.meta);
|
||||
if (metaSchema !== null) {
|
||||
ctx.outputFormatInstruction = buildOutputFormatInstruction(metaSchema);
|
||||
}
|
||||
|
||||
const agentResult = await runAgent(options, ctx);
|
||||
const outputHash = await extractOutput(agentResult.output, roleDef.outputSchema, storageRoot, ctx);
|
||||
const outputHash = await extractOutput(agentResult.output, roleDef.meta, storageRoot, ctx);
|
||||
const stepHash = await persistStep({
|
||||
ctx,
|
||||
outputHash,
|
||||
|
||||
@@ -2,14 +2,14 @@ import type { JSONSchema } from "@uncaged/json-cas";
|
||||
|
||||
const ROLE_DEFINITION: JSONSchema = {
|
||||
type: "object",
|
||||
required: ["description", "identity", "prepare", "execute", "report", "outputSchema"],
|
||||
required: ["description", "goal", "capabilities", "procedure", "output", "meta"],
|
||||
properties: {
|
||||
description: { type: "string" },
|
||||
identity: { type: "string" },
|
||||
prepare: { type: "string" },
|
||||
execute: { type: "string" },
|
||||
report: { type: "string" },
|
||||
outputSchema: { type: "string", format: "cas_ref" },
|
||||
goal: { type: "string" },
|
||||
capabilities: { type: "array", items: { type: "string" } },
|
||||
procedure: { type: "string" },
|
||||
output: { type: "string" },
|
||||
meta: { type: "string", format: "cas_ref" },
|
||||
},
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
@@ -18,11 +18,11 @@ export type StepRecord = {
|
||||
|
||||
export type RoleDefinition = {
|
||||
description: string;
|
||||
identity: string;
|
||||
prepare: string;
|
||||
execute: string;
|
||||
report: string;
|
||||
outputSchema: CasRef;
|
||||
goal: string;
|
||||
capabilities: string[];
|
||||
procedure: string;
|
||||
output: string;
|
||||
meta: CasRef;
|
||||
};
|
||||
|
||||
export type Transition = {
|
||||
|
||||
Reference in New Issue
Block a user