feat(template): add workflow-template-document with writer/differ roles and moderator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import { tableToModerator } from "@uncaged/workflow-protocol/moderator-table.js";
|
||||||
|
import { validateWorkflowDescriptor } from "@uncaged/workflow-register";
|
||||||
|
import { END, type ModeratorContext, type RoleStep, START } from "@uncaged/workflow-runtime";
|
||||||
|
import { buildDocumentDescriptor } from "../src/descriptor.js";
|
||||||
|
import { documentTable } from "../src/moderator.js";
|
||||||
|
import type { DifferMeta, WriterMeta } from "../src/roles/index.js";
|
||||||
|
import type { DocumentMeta } from "../src/roles.js";
|
||||||
|
|
||||||
|
const documentModerator = tableToModerator(documentTable);
|
||||||
|
|
||||||
|
function makeCtx(
|
||||||
|
steps: ModeratorContext<DocumentMeta>["steps"],
|
||||||
|
): ModeratorContext<DocumentMeta> {
|
||||||
|
return {
|
||||||
|
threadId: "01TEST000000000000000000TR",
|
||||||
|
depth: 0,
|
||||||
|
bundleHash: "TESTHASH00001",
|
||||||
|
start: { role: START, content: "", meta: {}, timestamp: 0, parentState: null },
|
||||||
|
steps,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function writerGenerateStep(): RoleStep<DocumentMeta> {
|
||||||
|
return {
|
||||||
|
role: "writer",
|
||||||
|
contentHash: "STUBHASHWRITER001",
|
||||||
|
meta: { mode: "generate", outputDocx: "/out/output.docx", sourceDocx: null } satisfies WriterMeta,
|
||||||
|
refs: [],
|
||||||
|
timestamp: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function writerEditStep(): RoleStep<DocumentMeta> {
|
||||||
|
return {
|
||||||
|
role: "writer",
|
||||||
|
contentHash: "STUBHASHWRITER002",
|
||||||
|
meta: { mode: "edit", outputDocx: "/out/modified.docx", sourceDocx: "/out/original.docx" } satisfies WriterMeta,
|
||||||
|
refs: [],
|
||||||
|
timestamp: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function differStep(): RoleStep<DocumentMeta> {
|
||||||
|
return {
|
||||||
|
role: "differ",
|
||||||
|
contentHash: "STUBHASHDIFF001",
|
||||||
|
meta: {
|
||||||
|
sourceDocx: "/out/original.docx",
|
||||||
|
modifiedDocx: "/out/modified.docx",
|
||||||
|
diffDocx: "/out/diff.docx",
|
||||||
|
} satisfies DifferMeta,
|
||||||
|
refs: [],
|
||||||
|
timestamp: 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("documentTable", () => {
|
||||||
|
test("START → writer", () => {
|
||||||
|
expect(documentModerator(makeCtx([]))).toBe("writer");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("writer (generate) → END", () => {
|
||||||
|
expect(documentModerator(makeCtx([writerGenerateStep()]))).toBe(END);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("writer (edit) → differ", () => {
|
||||||
|
expect(documentModerator(makeCtx([writerEditStep()]))).toBe("differ");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("differ → END", () => {
|
||||||
|
expect(documentModerator(makeCtx([writerEditStep(), differStep()]))).toBe(END);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buildDocumentDescriptor", () => {
|
||||||
|
test("descriptor passes validation", () => {
|
||||||
|
const descriptor = buildDocumentDescriptor();
|
||||||
|
expect(() => validateWorkflowDescriptor(descriptor)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("descriptor has writer and differ roles", () => {
|
||||||
|
const descriptor = buildDocumentDescriptor();
|
||||||
|
expect(Object.keys(descriptor.roles)).toContain("writer");
|
||||||
|
expect(Object.keys(descriptor.roles)).toContain("differ");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { buildDescriptor } from "@uncaged/workflow-register";
|
||||||
|
import { documentTable } from "./moderator.js";
|
||||||
|
import { DOCUMENT_WORKFLOW_DESCRIPTION, documentRoles } from "./roles.js";
|
||||||
|
|
||||||
|
export function buildDocumentDescriptor() {
|
||||||
|
return buildDescriptor({
|
||||||
|
description: DOCUMENT_WORKFLOW_DESCRIPTION,
|
||||||
|
roles: documentRoles,
|
||||||
|
table: documentTable,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1 +1,27 @@
|
|||||||
export {};
|
import type { WorkflowDefinition } from "@uncaged/workflow-runtime";
|
||||||
|
import { documentTable } from "./moderator.js";
|
||||||
|
import { DOCUMENT_WORKFLOW_DESCRIPTION, type DocumentMeta, documentRoles } from "./roles.js";
|
||||||
|
|
||||||
|
export { buildDocumentDescriptor } from "./descriptor.js";
|
||||||
|
export { documentTable } from "./moderator.js";
|
||||||
|
export {
|
||||||
|
type DifferMeta,
|
||||||
|
differMetaSchema,
|
||||||
|
differRole,
|
||||||
|
type WriterMeta,
|
||||||
|
writerMetaSchema,
|
||||||
|
writerRole,
|
||||||
|
} from "./roles/index.js";
|
||||||
|
export {
|
||||||
|
DOCUMENT_WORKFLOW_DESCRIPTION,
|
||||||
|
type DocumentMeta,
|
||||||
|
type DocumentRoles,
|
||||||
|
documentRoles,
|
||||||
|
} from "./roles.js";
|
||||||
|
export type { DocumentStartInput } from "./types.js";
|
||||||
|
|
||||||
|
export const documentWorkflowDefinition: WorkflowDefinition<DocumentMeta> = {
|
||||||
|
description: DOCUMENT_WORKFLOW_DESCRIPTION,
|
||||||
|
roles: documentRoles,
|
||||||
|
table: documentTable,
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import {
|
||||||
|
END,
|
||||||
|
type ModeratorCondition,
|
||||||
|
type ModeratorTable,
|
||||||
|
START,
|
||||||
|
} from "@uncaged/workflow-runtime";
|
||||||
|
import type { WriterMeta } from "./roles/writer.js";
|
||||||
|
import type { DocumentMeta } from "./roles.js";
|
||||||
|
|
||||||
|
const writerIsEditMode: ModeratorCondition<DocumentMeta> = {
|
||||||
|
name: "writerIsEditMode",
|
||||||
|
description: "Writer ran in edit mode and produced a modified document",
|
||||||
|
check: (ctx) => {
|
||||||
|
const writerStep = ctx.steps.find((s) => s.role === "writer");
|
||||||
|
if (writerStep === undefined) return false;
|
||||||
|
return (writerStep.meta as WriterMeta).mode === "edit";
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const documentTable: ModeratorTable<DocumentMeta> = {
|
||||||
|
[START]: [{ condition: "FALLBACK", role: "writer" }],
|
||||||
|
writer: [
|
||||||
|
{ condition: writerIsEditMode, role: "differ" },
|
||||||
|
{ condition: "FALLBACK", role: END },
|
||||||
|
],
|
||||||
|
differ: [{ condition: "FALLBACK", role: END }],
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { RoleDefinition } from "@uncaged/workflow-runtime";
|
||||||
|
import { type DifferMeta, differRole } from "./roles/differ.js";
|
||||||
|
import { type WriterMeta, writerRole } from "./roles/writer.js";
|
||||||
|
|
||||||
|
export const DOCUMENT_WORKFLOW_DESCRIPTION =
|
||||||
|
"Generates a new Word document from a prompt, or edits an existing one and produces a diff report.";
|
||||||
|
|
||||||
|
export type DocumentMeta = {
|
||||||
|
writer: WriterMeta;
|
||||||
|
differ: DifferMeta;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DocumentRoles = {
|
||||||
|
[K in keyof DocumentMeta]: RoleDefinition<DocumentMeta[K]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const documentRoles: DocumentRoles = {
|
||||||
|
writer: writerRole,
|
||||||
|
differ: differRole,
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import type { RoleDefinition } from "@uncaged/workflow-runtime";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
|
export const differMetaSchema = z.object({
|
||||||
|
sourceDocx: z.string(),
|
||||||
|
modifiedDocx: z.string(),
|
||||||
|
diffDocx: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DifferMeta = z.infer<typeof differMetaSchema>;
|
||||||
|
|
||||||
|
export const differRole: RoleDefinition<DifferMeta> = {
|
||||||
|
description: "Produces a Word-format diff report of the writer's changes (edit mode only).",
|
||||||
|
systemPrompt: "",
|
||||||
|
schema: differMetaSchema,
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type { DifferMeta } from "./differ.js";
|
||||||
|
export { differMetaSchema, differRole } from "./differ.js";
|
||||||
|
export type { WriterMeta } from "./writer.js";
|
||||||
|
export { writerMetaSchema, writerRole } from "./writer.js";
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import type { RoleDefinition } from "@uncaged/workflow-runtime";
|
||||||
|
import * as z from "zod/v4";
|
||||||
|
|
||||||
|
export const writerMetaSchema = z.discriminatedUnion("mode", [
|
||||||
|
z.object({
|
||||||
|
mode: z.literal("generate"),
|
||||||
|
outputDocx: z.string(),
|
||||||
|
sourceDocx: z.null(),
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
mode: z.literal("edit"),
|
||||||
|
outputDocx: z.string(),
|
||||||
|
sourceDocx: z.string(),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type WriterMeta = z.infer<typeof writerMetaSchema>;
|
||||||
|
|
||||||
|
export const writerRole: RoleDefinition<WriterMeta> = {
|
||||||
|
description: "Generates or modifies a Word document via an external agent.",
|
||||||
|
systemPrompt: "",
|
||||||
|
schema: writerMetaSchema,
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type DocumentStartInput = {
|
||||||
|
prompt: string;
|
||||||
|
inputDocx: string | null;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user