refactor: unify RoleDefinition + WorkflowDefinition with description & schema
- Add RoleDefinition<Meta> = { description, run, schema } to core types
- WorkflowDefinition now carries description and RoleDefinition per role
- Add buildDescriptor() in core to derive WorkflowDescriptor from WorkflowDefinition
- Remove buildDescriptorFromRoles / RoleDescriptorInput from workflow-util-role
- Update solve-issue template, examples, and all tests
小橘 <xiaoju@shazhou.work>
This commit is contained in:
@@ -11,8 +11,8 @@ function makeCtx(userContent: string): ThreadContext {
|
||||
meta: { maxRounds: 10 },
|
||||
timestamp: 1,
|
||||
},
|
||||
steps: [],
|
||||
threadId: "01TEST000000000000000000TR",
|
||||
steps: [],
|
||||
threadId: "01TEST000000000000000000TR",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export {
|
||||
buildDescriptorFromRoles,
|
||||
type CreateRoleArgs,
|
||||
createRole,
|
||||
decorateRole,
|
||||
@@ -15,7 +14,6 @@ export {
|
||||
type OnFailOptions,
|
||||
onFail,
|
||||
type RoleDecorator,
|
||||
type RoleDescriptorInput,
|
||||
type WithDryRunOptions,
|
||||
withDryRun,
|
||||
} from "@uncaged/workflow-util-role";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { createCommitterRole } from "../src/committer.js";
|
||||
|
||||
function makeCtx(): ThreadContext {
|
||||
return {
|
||||
threadId: "01TEST00000000000000000000",
|
||||
threadId: "01TEST000000000000000000TR",
|
||||
start: {
|
||||
role: START,
|
||||
content: "do thing",
|
||||
|
||||
@@ -29,7 +29,7 @@ function toolCallResponse(argsJson: string): Response {
|
||||
|
||||
function makeCtx(): ThreadContext {
|
||||
return {
|
||||
threadId: "01TEST00000000000000000000",
|
||||
threadId: "01TEST000000000000000000TR",
|
||||
start: {
|
||||
role: START,
|
||||
content: "task",
|
||||
@@ -106,6 +106,6 @@ describe("createReviewerRole", () => {
|
||||
{ cwd: "/proj" },
|
||||
);
|
||||
await role(makeCtx());
|
||||
expect(seen).toContain("uncaged-workflow thread 01TEST00000000000000000000");
|
||||
expect(seen).toContain("uncaged-workflow thread 01TEST000000000000000000TR");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ function makeCtx(
|
||||
steps: ThreadContext<SolveIssueMeta>["steps"],
|
||||
): ThreadContext<SolveIssueMeta> {
|
||||
return {
|
||||
threadId: "01TEST00000000000000000000",
|
||||
threadId: "01TEST000000000000000000TR",
|
||||
start: makeStart(maxRounds),
|
||||
steps,
|
||||
};
|
||||
@@ -112,13 +112,13 @@ describe("createSolveIssueRoles", () => {
|
||||
extract: null,
|
||||
});
|
||||
|
||||
expect(typeof roles.planner).toBe("function");
|
||||
expect(typeof roles.coder).toBe("function");
|
||||
expect(typeof roles.reviewer).toBe("function");
|
||||
expect(typeof roles.committer).toBe("function");
|
||||
expect(typeof roles.planner.run).toBe("function");
|
||||
expect(typeof roles.coder.run).toBe("function");
|
||||
expect(typeof roles.reviewer.run).toBe("function");
|
||||
expect(typeof roles.committer.run).toBe("function");
|
||||
|
||||
const ctx = makeCtx(10, []);
|
||||
const plannerOut = await roles.planner(ctx as unknown as ThreadContext);
|
||||
const plannerOut = await roles.planner.run(ctx as unknown as ThreadContext);
|
||||
expect(plannerOut.meta.plan).toBe("");
|
||||
expect(Array.isArray(plannerOut.meta.files)).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
import { committerMetaSchema } from "@uncaged/workflow-role-committer";
|
||||
import { reviewerMetaSchema } from "@uncaged/workflow-role-reviewer";
|
||||
import { buildDescriptorFromRoles } from "@uncaged/workflow-util-role";
|
||||
import { buildDescriptor } from "@uncaged/workflow";
|
||||
|
||||
import { coderMetaSchema, plannerMetaSchema } from "./roles.js";
|
||||
import { solveIssueModerator } from "./moderator.js";
|
||||
import {
|
||||
createSolveIssueRoles,
|
||||
SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
||||
type SolveIssueRolesConfig,
|
||||
} from "./roles.js";
|
||||
|
||||
const BUILD_DESCRIPTOR_CONFIG: SolveIssueRolesConfig = {
|
||||
agent: async () => "",
|
||||
workdir: "/tmp/uncaged-workflow-descriptor-stub",
|
||||
extract: null,
|
||||
};
|
||||
|
||||
export function buildSolveIssueDescriptor() {
|
||||
return buildDescriptorFromRoles({
|
||||
description:
|
||||
"Plan, implement, review, and commit changes to resolve an issue end-to-end (planner → coder → reviewer → committer).",
|
||||
roles: {
|
||||
planner: {
|
||||
name: "planner",
|
||||
schema: plannerMetaSchema,
|
||||
description: "Analyzes the issue and proposes plan, files, and approach.",
|
||||
},
|
||||
coder: {
|
||||
name: "coder",
|
||||
schema: coderMetaSchema,
|
||||
description: "Implements the planner output and summarizes touched files.",
|
||||
},
|
||||
reviewer: {
|
||||
name: "reviewer",
|
||||
schema: reviewerMetaSchema,
|
||||
description: "Runs git diff checks and sets approved when the change is ready.",
|
||||
},
|
||||
committer: {
|
||||
name: "committer",
|
||||
schema: committerMetaSchema,
|
||||
description: "Creates branch, commits, and pushes when review passes.",
|
||||
},
|
||||
},
|
||||
return buildDescriptor({
|
||||
description: SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
||||
roles: createSolveIssueRoles(BUILD_DESCRIPTOR_CONFIG),
|
||||
moderator: solveIssueModerator,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { createRoleModerator, type WorkflowFn } from "@uncaged/workflow";
|
||||
import { createRoleModerator, type WorkflowDefinition, type WorkflowFn } from "@uncaged/workflow";
|
||||
|
||||
import { solveIssueModerator } from "./moderator.js";
|
||||
import { createSolveIssueRoles, type SolveIssueMeta, type SolveIssueRolesConfig } from "./roles.js";
|
||||
import {
|
||||
createSolveIssueRoles,
|
||||
SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
||||
type SolveIssueMeta,
|
||||
type SolveIssueRolesConfig,
|
||||
} from "./roles.js";
|
||||
|
||||
export { type CursorAgentConfig, createCursorAgent } from "@uncaged/workflow-agent-cursor";
|
||||
export { buildSolveIssueDescriptor } from "./descriptor.js";
|
||||
@@ -12,18 +17,26 @@ export {
|
||||
createSolveIssueRoles,
|
||||
type PlannerMeta,
|
||||
plannerMetaSchema,
|
||||
SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
||||
type SolveIssueMeta,
|
||||
type SolveIssueRoles,
|
||||
type SolveIssueRolesConfig,
|
||||
} from "./roles.js";
|
||||
|
||||
export function createSolveIssueWorkflowDefinition(
|
||||
config: SolveIssueRolesConfig,
|
||||
): WorkflowDefinition<SolveIssueMeta> {
|
||||
return {
|
||||
description: SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
||||
roles: createSolveIssueRoles(config),
|
||||
moderator: solveIssueModerator,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for a {@link WorkflowFn}: supply an agent and repo paths at runtime, then pass the result
|
||||
* to the bundle `run` export pattern (`createRoleModerator` is already applied).
|
||||
*/
|
||||
export function createSolveIssueRun(config: SolveIssueRolesConfig): WorkflowFn {
|
||||
return createRoleModerator<SolveIssueMeta>({
|
||||
roles: createSolveIssueRoles(config),
|
||||
moderator: solveIssueModerator,
|
||||
});
|
||||
return createRoleModerator(createSolveIssueWorkflowDefinition(config));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import type { AgentFn, Role } from "@uncaged/workflow";
|
||||
import type { AgentFn, RoleDefinition } from "@uncaged/workflow";
|
||||
import { createRole } from "@uncaged/workflow-agent-llm";
|
||||
import { type CommitterMeta, createCommitterRole } from "@uncaged/workflow-role-committer";
|
||||
import { createReviewerRole, type ReviewerMeta } from "@uncaged/workflow-role-reviewer";
|
||||
import {
|
||||
type CommitterMeta,
|
||||
committerMetaSchema,
|
||||
createCommitterRole,
|
||||
} from "@uncaged/workflow-role-committer";
|
||||
import {
|
||||
createReviewerRole,
|
||||
type ReviewerMeta,
|
||||
reviewerMetaSchema,
|
||||
} from "@uncaged/workflow-role-reviewer";
|
||||
import type { LlmProvider } from "@uncaged/workflow-util-role";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
@@ -19,6 +27,9 @@ const CODER_SYSTEM = `You are a **coder**. The previous step produced a plan: re
|
||||
|
||||
Make focused changes, follow project conventions, and explain what you changed.`;
|
||||
|
||||
export const SOLVE_ISSUE_WORKFLOW_DESCRIPTION =
|
||||
"Plan, implement, review, and commit changes to resolve an issue end-to-end (planner → coder → reviewer → committer).";
|
||||
|
||||
export const plannerMetaSchema = z.object({
|
||||
plan: z.string(),
|
||||
files: z.array(z.string()),
|
||||
@@ -80,10 +91,10 @@ function resolveExtract(config: SolveIssueRolesConfig): {
|
||||
}
|
||||
|
||||
export type SolveIssueRoles = {
|
||||
planner: Role<PlannerMeta>;
|
||||
coder: Role<CoderMeta>;
|
||||
reviewer: Role<ReviewerMeta>;
|
||||
committer: Role<CommitterMeta>;
|
||||
planner: RoleDefinition<PlannerMeta>;
|
||||
coder: RoleDefinition<CoderMeta>;
|
||||
reviewer: RoleDefinition<ReviewerMeta>;
|
||||
committer: RoleDefinition<CommitterMeta>;
|
||||
};
|
||||
|
||||
export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssueRoles {
|
||||
@@ -95,7 +106,7 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue
|
||||
cwd: config.workdir,
|
||||
};
|
||||
|
||||
const planner: Role<PlannerMeta> = createRole({
|
||||
const plannerRun = createRole({
|
||||
name: "planner",
|
||||
schema: plannerMetaSchema,
|
||||
systemPrompt: PLANNER_SYSTEM,
|
||||
@@ -107,7 +118,7 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue
|
||||
},
|
||||
});
|
||||
|
||||
const coder: Role<CoderMeta> = createRole({
|
||||
const coderRun = createRole({
|
||||
name: "coder",
|
||||
schema: coderMetaSchema,
|
||||
systemPrompt: CODER_SYSTEM,
|
||||
@@ -119,7 +130,7 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue
|
||||
},
|
||||
});
|
||||
|
||||
const reviewer: Role<ReviewerMeta> = createReviewerRole(
|
||||
const reviewerRun = createReviewerRole(
|
||||
config.agent,
|
||||
{
|
||||
provider: extract.provider,
|
||||
@@ -129,7 +140,7 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue
|
||||
reviewerConfig,
|
||||
);
|
||||
|
||||
const committer: Role<CommitterMeta> = createCommitterRole(
|
||||
const committerRun = createCommitterRole(
|
||||
config.agent,
|
||||
{
|
||||
provider: extract.provider,
|
||||
@@ -139,5 +150,26 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue
|
||||
committerConfig,
|
||||
);
|
||||
|
||||
return { planner, coder, reviewer, committer };
|
||||
return {
|
||||
planner: {
|
||||
description: "Analyzes the issue and proposes plan, files, and approach.",
|
||||
run: plannerRun,
|
||||
schema: plannerMetaSchema,
|
||||
},
|
||||
coder: {
|
||||
description: "Implements the planner output and summarizes touched files.",
|
||||
run: coderRun,
|
||||
schema: coderMetaSchema,
|
||||
},
|
||||
reviewer: {
|
||||
description: "Runs git diff checks and sets approved when the change is ready.",
|
||||
run: reviewerRun,
|
||||
schema: reviewerMetaSchema,
|
||||
},
|
||||
committer: {
|
||||
description: "Creates branch, commits, and pushes when review passes.",
|
||||
run: committerRun,
|
||||
schema: committerMetaSchema,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { validateWorkflowDescriptor } from "@uncaged/workflow";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
import { buildDescriptorFromRoles } from "../src/build-descriptor.js";
|
||||
|
||||
describe("buildDescriptorFromRoles", () => {
|
||||
test("produces a descriptor that validates and includes JSON schemas per role", () => {
|
||||
const schema = z.object({
|
||||
title: z.string(),
|
||||
count: z.number(),
|
||||
});
|
||||
|
||||
const descriptor = buildDescriptorFromRoles({
|
||||
description: "Demo workflow",
|
||||
roles: {
|
||||
analyst: {
|
||||
name: "analyst",
|
||||
schema,
|
||||
description: "Analyzes input",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const validated = validateWorkflowDescriptor(descriptor);
|
||||
expect(validated.ok).toBe(true);
|
||||
if (!validated.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(validated.value.description).toBe("Demo workflow");
|
||||
const analyst = validated.value.roles.analyst;
|
||||
expect(analyst.description).toBe("Analyzes input");
|
||||
expect(analyst.schema.type).toBe("object");
|
||||
const props = analyst.schema.properties as Record<string, unknown>;
|
||||
expect(props.title).toMatchObject({ type: "string" });
|
||||
expect(props.count).toMatchObject({ type: "number" });
|
||||
});
|
||||
|
||||
test("uses empty description when spec.description is null", () => {
|
||||
const descriptor = buildDescriptorFromRoles({
|
||||
description: "W",
|
||||
roles: {
|
||||
x: {
|
||||
name: "x",
|
||||
schema: z.object({ n: z.number() }),
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const validated = validateWorkflowDescriptor(descriptor);
|
||||
expect(validated.ok).toBe(true);
|
||||
if (!validated.ok) {
|
||||
return;
|
||||
}
|
||||
expect(validated.value.roles.x.description).toBe("");
|
||||
});
|
||||
|
||||
test("throws when role key and spec.name diverge", () => {
|
||||
expect(() =>
|
||||
buildDescriptorFromRoles({
|
||||
description: "W",
|
||||
roles: {
|
||||
a: {
|
||||
name: "b",
|
||||
schema: z.object({ n: z.number() }),
|
||||
description: null,
|
||||
},
|
||||
},
|
||||
}),
|
||||
).toThrow(/must match spec.name/);
|
||||
});
|
||||
});
|
||||
@@ -42,8 +42,8 @@ function makeCtx(): ThreadContext {
|
||||
meta: { maxRounds: 10 },
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
steps: [],
|
||||
threadId: "01TEST000000000000000000TR",
|
||||
steps: [],
|
||||
threadId: "01TEST000000000000000000TR",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ function fakeCtx(): ThreadContext {
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
steps: [],
|
||||
threadId: "01TEST000000000000000000TR",
|
||||
steps: [],
|
||||
threadId: "01TEST000000000000000000TR",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import type { WorkflowDescriptor, WorkflowRoleSchema } from "@uncaged/workflow";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
export type RoleDescriptorInput<M extends Record<string, unknown> = Record<string, unknown>> = {
|
||||
name: string;
|
||||
schema: z.ZodType<M>;
|
||||
/** Human-readable role description; use empty string when unknown. */
|
||||
description: string | null;
|
||||
};
|
||||
|
||||
function stripJsonSchemaMeta(json: Record<string, unknown>): WorkflowRoleSchema {
|
||||
const { $schema: _drop, ...rest } = json;
|
||||
return rest as WorkflowRoleSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link WorkflowDescriptor} from role specs, emitting JSON Schema per role via
|
||||
* `z.toJSONSchema`.
|
||||
*/
|
||||
export function buildDescriptorFromRoles(args: {
|
||||
description: string;
|
||||
roles: Record<string, RoleDescriptorInput>;
|
||||
}): WorkflowDescriptor {
|
||||
const roles: WorkflowDescriptor["roles"] = {};
|
||||
for (const [key, spec] of Object.entries(args.roles)) {
|
||||
if (spec.name !== key) {
|
||||
throw new Error(
|
||||
`buildDescriptorFromRoles: role key "${key}" must match spec.name "${spec.name}"`,
|
||||
);
|
||||
}
|
||||
const rawJsonSchema = z.toJSONSchema(spec.schema) as Record<string, unknown>;
|
||||
roles[key] = {
|
||||
description: spec.description === null ? "" : spec.description,
|
||||
schema: stripJsonSchemaMeta(rawJsonSchema),
|
||||
};
|
||||
}
|
||||
return { description: args.description, roles };
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
export { buildDescriptorFromRoles, type RoleDescriptorInput } from "./build-descriptor.js";
|
||||
export { type CreateRoleArgs, createRole } from "./create-role.js";
|
||||
export {
|
||||
decorateRole,
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
import { buildDescriptor } from "../src/build-descriptor.js";
|
||||
import { END } from "../src/types.js";
|
||||
import { validateWorkflowDescriptor } from "../src/workflow-descriptor.js";
|
||||
|
||||
describe("buildDescriptor", () => {
|
||||
test("produces a descriptor that validates and includes JSON schemas per role", () => {
|
||||
const schema = z.object({
|
||||
title: z.string(),
|
||||
count: z.number(),
|
||||
});
|
||||
|
||||
type M = { analyst: z.infer<typeof schema> };
|
||||
|
||||
const descriptor = buildDescriptor<M>({
|
||||
description: "Demo workflow",
|
||||
roles: {
|
||||
analyst: {
|
||||
description: "Analyzes input",
|
||||
schema,
|
||||
run: async () => ({ content: "", meta: { title: "", count: 0 } }),
|
||||
},
|
||||
},
|
||||
moderator: () => END,
|
||||
});
|
||||
|
||||
const validated = validateWorkflowDescriptor(descriptor);
|
||||
expect(validated.ok).toBe(true);
|
||||
if (!validated.ok) {
|
||||
return;
|
||||
}
|
||||
|
||||
expect(validated.value.description).toBe("Demo workflow");
|
||||
const analyst = validated.value.roles.analyst;
|
||||
expect(analyst.description).toBe("Analyzes input");
|
||||
expect(analyst.schema.type).toBe("object");
|
||||
const props = analyst.schema.properties as Record<string, unknown>;
|
||||
expect(props.title).toMatchObject({ type: "string" });
|
||||
expect(props.count).toMatchObject({ type: "number" });
|
||||
});
|
||||
});
|
||||
@@ -2,27 +2,45 @@ import { describe, expect, test } from "bun:test";
|
||||
import { mkdir, mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import * as z from "zod/v4";
|
||||
|
||||
import { createRoleModerator } from "../src/create-role-moderator.js";
|
||||
import { executeThread } from "../src/engine.js";
|
||||
import { createLogger } from "../src/logger.js";
|
||||
import { END } from "../src/types.js";
|
||||
|
||||
const plannerMetaSchema = z.object({
|
||||
plan: z.string(),
|
||||
files: z.array(z.string()),
|
||||
});
|
||||
|
||||
const coderMetaSchema = z.object({
|
||||
diff: z.string(),
|
||||
});
|
||||
|
||||
type DemoMeta = {
|
||||
planner: Record<string, unknown>;
|
||||
coder: Record<string, unknown>;
|
||||
planner: z.infer<typeof plannerMetaSchema>;
|
||||
coder: z.infer<typeof coderMetaSchema>;
|
||||
};
|
||||
|
||||
const demoWorkflow = createRoleModerator<DemoMeta>({
|
||||
roles: {
|
||||
planner: async () => ({
|
||||
content: "plan-body",
|
||||
meta: { plan: "do-it", files: ["a.ts"] },
|
||||
}),
|
||||
coder: async () => ({
|
||||
content: "code-body",
|
||||
meta: { diff: "+ok" },
|
||||
}),
|
||||
planner: {
|
||||
description: "Demo planner",
|
||||
schema: plannerMetaSchema,
|
||||
run: async () => ({
|
||||
content: "plan-body",
|
||||
meta: { plan: "do-it", files: ["a.ts"] },
|
||||
}),
|
||||
},
|
||||
coder: {
|
||||
description: "Demo coder",
|
||||
schema: coderMetaSchema,
|
||||
run: async () => ({
|
||||
content: "code-body",
|
||||
meta: { diff: "+ok" },
|
||||
}),
|
||||
},
|
||||
},
|
||||
moderator: (ctx) => {
|
||||
if (ctx.steps.length === 0) {
|
||||
|
||||
@@ -13,7 +13,11 @@
|
||||
"xxhashjs": "^0.2.2",
|
||||
"yaml": "^2.8.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/acorn": "^6.0.4"
|
||||
"@types/acorn": "^6.0.4",
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import * as z from "zod/v4";
|
||||
|
||||
import type { RoleMeta, WorkflowDefinition } from "./types.js";
|
||||
import type { WorkflowDescriptor, WorkflowRoleSchema } from "./workflow-descriptor.js";
|
||||
|
||||
function stripJsonSchemaMeta(json: Record<string, unknown>): WorkflowRoleSchema {
|
||||
const { $schema: _drop, ...rest } = json;
|
||||
return rest as WorkflowRoleSchema;
|
||||
}
|
||||
|
||||
export function buildDescriptor<M extends RoleMeta>(
|
||||
def: WorkflowDefinition<M>,
|
||||
): WorkflowDescriptor {
|
||||
const roles: WorkflowDescriptor["roles"] = {};
|
||||
for (const [key, roleDef] of Object.entries(def.roles) as Array<
|
||||
[string, { description: string; schema: z.ZodType }]
|
||||
>) {
|
||||
const rawJsonSchema = z.toJSONSchema(roleDef.schema) as Record<string, unknown>;
|
||||
roles[key] = {
|
||||
description: roleDef.description,
|
||||
schema: stripJsonSchemaMeta(rawJsonSchema),
|
||||
};
|
||||
}
|
||||
return { description: def.description, roles };
|
||||
}
|
||||
@@ -65,12 +65,12 @@ export function createRoleModerator<M extends RoleMeta>(
|
||||
return { returnCode: 0, summary: "completed: moderator returned END" };
|
||||
}
|
||||
|
||||
const roleFn = def.roles[next];
|
||||
if (roleFn === undefined) {
|
||||
const roleDef = def.roles[next];
|
||||
if (roleDef === undefined) {
|
||||
return { returnCode: 1, summary: `unknown role: ${next}` };
|
||||
}
|
||||
|
||||
const result = await roleFn(ctx as unknown as ThreadContext);
|
||||
const result = await roleDef.run(ctx as unknown as ThreadContext);
|
||||
const ts = Date.now();
|
||||
const step = {
|
||||
role: next,
|
||||
|
||||
@@ -5,6 +5,7 @@ export {
|
||||
encodeCrockfordBase32Bits,
|
||||
encodeUint64AsCrockford,
|
||||
} from "./base32.js";
|
||||
export { buildDescriptor } from "./build-descriptor.js";
|
||||
export { validateWorkflowBundle, type WorkflowBundleValidationInput } from "./bundle-validator.js";
|
||||
export { createRoleModerator } from "./create-role-moderator.js";
|
||||
export {
|
||||
@@ -53,6 +54,7 @@ export {
|
||||
END,
|
||||
type Moderator,
|
||||
type Role,
|
||||
type RoleDefinition,
|
||||
type RoleMeta,
|
||||
type RoleOutput,
|
||||
type RoleResult,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type * as z from "zod/v4";
|
||||
|
||||
/** Sentinel values for automaton control flow. */
|
||||
export const START = "__start__" as const;
|
||||
export const END = "__end__" as const;
|
||||
@@ -71,6 +73,13 @@ export type Role<Meta extends Record<string, unknown>> = (
|
||||
ctx: ThreadContext,
|
||||
) => Promise<RoleResult<Meta>>;
|
||||
|
||||
/** Role wiring: runtime {@link Role}, JSON Schema for `meta`, and human-readable description. */
|
||||
export type RoleDefinition<Meta extends Record<string, unknown>> = {
|
||||
description: string;
|
||||
run: Role<Meta>;
|
||||
schema: z.ZodType<Meta>;
|
||||
};
|
||||
|
||||
/**
|
||||
* An Agent — raw string output interface for LLM/CLI adapters.
|
||||
* Structured meta is extracted by the role's extract layer.
|
||||
@@ -89,6 +98,7 @@ export type Moderator<M extends RoleMeta> = (
|
||||
|
||||
/** Complete workflow definition as authored by users. */
|
||||
export type WorkflowDefinition<M extends RoleMeta> = {
|
||||
roles: { [K in keyof M & string]: Role<M[K]> };
|
||||
description: string;
|
||||
roles: { [K in keyof M & string]: RoleDefinition<M[K]> };
|
||||
moderator: Moderator<M>;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user