diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..e77c92c --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[test] +pathIgnorePatterns = ["dist/**"] diff --git a/examples/hello-world.ts b/examples/hello-world.ts index 777dadb..b79ec03 100644 --- a/examples/hello-world.ts +++ b/examples/hello-world.ts @@ -1,9 +1,14 @@ -import { createRoleModerator, END, type Role } from "@uncaged/workflow"; +import { createRoleModerator, END, type RoleDefinition } from "@uncaged/workflow"; +import * as z from "zod/v4"; type Roles = { greeter: { greeting: string }; }; +const greeterMetaSchema = z.object({ + greeting: z.string(), +}); + export const descriptor = { description: "A simple hello world workflow", roles: { @@ -18,10 +23,14 @@ export const descriptor = { }, }; -const greeter: Role = async (ctx) => ({ - content: `Hello, ${ctx.start.content}`, - meta: { greeting: "Hello!" }, -}); +const greeter: RoleDefinition = { + description: "Generates a greeting", + schema: greeterMetaSchema, + run: async (ctx) => ({ + content: `Hello, ${ctx.start.content}`, + meta: { greeting: "Hello!" }, + }), +}; export const run = createRoleModerator({ roles: { greeter }, diff --git a/examples/package.json b/examples/package.json index 7cca085..f9b7e90 100644 --- a/examples/package.json +++ b/examples/package.json @@ -3,6 +3,7 @@ "private": true, "type": "module", "dependencies": { - "@uncaged/workflow": "workspace:*" + "@uncaged/workflow": "workspace:*", + "zod": "^4.0.0" } } diff --git a/packages/workflow-agent-llm/__tests__/create-llm-adapter.test.ts b/packages/workflow-agent-llm/__tests__/create-llm-adapter.test.ts index 13f1655..34ead8d 100644 --- a/packages/workflow-agent-llm/__tests__/create-llm-adapter.test.ts +++ b/packages/workflow-agent-llm/__tests__/create-llm-adapter.test.ts @@ -11,8 +11,8 @@ function makeCtx(userContent: string): ThreadContext { meta: { maxRounds: 10 }, timestamp: 1, }, - steps: [], - threadId: "01TEST000000000000000000TR", + steps: [], + threadId: "01TEST000000000000000000TR", }; } diff --git a/packages/workflow-agent-llm/src/index.ts b/packages/workflow-agent-llm/src/index.ts index 55be074..bff293a 100644 --- a/packages/workflow-agent-llm/src/index.ts +++ b/packages/workflow-agent-llm/src/index.ts @@ -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"; diff --git a/packages/workflow-role-committer/__tests__/committer.test.ts b/packages/workflow-role-committer/__tests__/committer.test.ts index d62f7fd..3eeb54c 100644 --- a/packages/workflow-role-committer/__tests__/committer.test.ts +++ b/packages/workflow-role-committer/__tests__/committer.test.ts @@ -8,7 +8,7 @@ import { createCommitterRole } from "../src/committer.js"; function makeCtx(): ThreadContext { return { - threadId: "01TEST00000000000000000000", + threadId: "01TEST000000000000000000TR", start: { role: START, content: "do thing", diff --git a/packages/workflow-role-reviewer/__tests__/reviewer.test.ts b/packages/workflow-role-reviewer/__tests__/reviewer.test.ts index 5e79a0d..ff7ae48 100644 --- a/packages/workflow-role-reviewer/__tests__/reviewer.test.ts +++ b/packages/workflow-role-reviewer/__tests__/reviewer.test.ts @@ -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"); }); }); diff --git a/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts b/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts index c215032..2a8efdb 100644 --- a/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts +++ b/packages/workflow-template-solve-issue/__tests__/solve-issue-template.test.ts @@ -25,7 +25,7 @@ function makeCtx( steps: ThreadContext["steps"], ): ThreadContext { 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); }); diff --git a/packages/workflow-template-solve-issue/src/descriptor.ts b/packages/workflow-template-solve-issue/src/descriptor.ts index ab6ffcc..2306fc4 100644 --- a/packages/workflow-template-solve-issue/src/descriptor.ts +++ b/packages/workflow-template-solve-issue/src/descriptor.ts @@ -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, }); } diff --git a/packages/workflow-template-solve-issue/src/index.ts b/packages/workflow-template-solve-issue/src/index.ts index 61b0894..e64a205 100644 --- a/packages/workflow-template-solve-issue/src/index.ts +++ b/packages/workflow-template-solve-issue/src/index.ts @@ -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 { + 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({ - roles: createSolveIssueRoles(config), - moderator: solveIssueModerator, - }); + return createRoleModerator(createSolveIssueWorkflowDefinition(config)); } diff --git a/packages/workflow-template-solve-issue/src/roles.ts b/packages/workflow-template-solve-issue/src/roles.ts index b97c2f4..3a7e0a6 100644 --- a/packages/workflow-template-solve-issue/src/roles.ts +++ b/packages/workflow-template-solve-issue/src/roles.ts @@ -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; - coder: Role; - reviewer: Role; - committer: Role; + planner: RoleDefinition; + coder: RoleDefinition; + reviewer: RoleDefinition; + committer: RoleDefinition; }; export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssueRoles { @@ -95,7 +106,7 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue cwd: config.workdir, }; - const planner: Role = createRole({ + const plannerRun = createRole({ name: "planner", schema: plannerMetaSchema, systemPrompt: PLANNER_SYSTEM, @@ -107,7 +118,7 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue }, }); - const coder: Role = createRole({ + const coderRun = createRole({ name: "coder", schema: coderMetaSchema, systemPrompt: CODER_SYSTEM, @@ -119,7 +130,7 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue }, }); - const reviewer: Role = createReviewerRole( + const reviewerRun = createReviewerRole( config.agent, { provider: extract.provider, @@ -129,7 +140,7 @@ export function createSolveIssueRoles(config: SolveIssueRolesConfig): SolveIssue reviewerConfig, ); - const committer: Role = 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, + }, + }; } diff --git a/packages/workflow-util-role/__tests__/build-descriptor.test.ts b/packages/workflow-util-role/__tests__/build-descriptor.test.ts deleted file mode 100644 index b0ef374..0000000 --- a/packages/workflow-util-role/__tests__/build-descriptor.test.ts +++ /dev/null @@ -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; - 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/); - }); -}); diff --git a/packages/workflow-util-role/__tests__/create-role.test.ts b/packages/workflow-util-role/__tests__/create-role.test.ts index 91b3c22..5505e5c 100644 --- a/packages/workflow-util-role/__tests__/create-role.test.ts +++ b/packages/workflow-util-role/__tests__/create-role.test.ts @@ -42,8 +42,8 @@ function makeCtx(): ThreadContext { meta: { maxRounds: 10 }, timestamp: Date.now(), }, - steps: [], - threadId: "01TEST000000000000000000TR", + steps: [], + threadId: "01TEST000000000000000000TR", }; } diff --git a/packages/workflow-util-role/__tests__/decorators.test.ts b/packages/workflow-util-role/__tests__/decorators.test.ts index 44d600d..270c962 100644 --- a/packages/workflow-util-role/__tests__/decorators.test.ts +++ b/packages/workflow-util-role/__tests__/decorators.test.ts @@ -16,8 +16,8 @@ function fakeCtx(): ThreadContext { }, timestamp: Date.now(), }, - steps: [], - threadId: "01TEST000000000000000000TR", + steps: [], + threadId: "01TEST000000000000000000TR", }; } diff --git a/packages/workflow-util-role/src/build-descriptor.ts b/packages/workflow-util-role/src/build-descriptor.ts deleted file mode 100644 index 8fbd4c2..0000000 --- a/packages/workflow-util-role/src/build-descriptor.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { WorkflowDescriptor, WorkflowRoleSchema } from "@uncaged/workflow"; -import * as z from "zod/v4"; - -export type RoleDescriptorInput = Record> = { - name: string; - schema: z.ZodType; - /** Human-readable role description; use empty string when unknown. */ - description: string | null; -}; - -function stripJsonSchemaMeta(json: Record): 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; -}): 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; - roles[key] = { - description: spec.description === null ? "" : spec.description, - schema: stripJsonSchemaMeta(rawJsonSchema), - }; - } - return { description: args.description, roles }; -} diff --git a/packages/workflow-util-role/src/index.ts b/packages/workflow-util-role/src/index.ts index 9a652e1..c7b735a 100644 --- a/packages/workflow-util-role/src/index.ts +++ b/packages/workflow-util-role/src/index.ts @@ -1,4 +1,3 @@ -export { buildDescriptorFromRoles, type RoleDescriptorInput } from "./build-descriptor.js"; export { type CreateRoleArgs, createRole } from "./create-role.js"; export { decorateRole, diff --git a/packages/workflow/__tests__/build-descriptor.test.ts b/packages/workflow/__tests__/build-descriptor.test.ts new file mode 100644 index 0000000..3ff2619 --- /dev/null +++ b/packages/workflow/__tests__/build-descriptor.test.ts @@ -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 }; + + const descriptor = buildDescriptor({ + 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; + expect(props.title).toMatchObject({ type: "string" }); + expect(props.count).toMatchObject({ type: "number" }); + }); +}); diff --git a/packages/workflow/__tests__/engine.test.ts b/packages/workflow/__tests__/engine.test.ts index f08cb96..c0fe96c 100644 --- a/packages/workflow/__tests__/engine.test.ts +++ b/packages/workflow/__tests__/engine.test.ts @@ -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; - coder: Record; + planner: z.infer; + coder: z.infer; }; const demoWorkflow = createRoleModerator({ 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) { diff --git a/packages/workflow/package.json b/packages/workflow/package.json index f82b3c5..33b62d9 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -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" } } diff --git a/packages/workflow/src/build-descriptor.ts b/packages/workflow/src/build-descriptor.ts new file mode 100644 index 0000000..a5171a5 --- /dev/null +++ b/packages/workflow/src/build-descriptor.ts @@ -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): WorkflowRoleSchema { + const { $schema: _drop, ...rest } = json; + return rest as WorkflowRoleSchema; +} + +export function buildDescriptor( + def: WorkflowDefinition, +): 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; + roles[key] = { + description: roleDef.description, + schema: stripJsonSchemaMeta(rawJsonSchema), + }; + } + return { description: def.description, roles }; +} diff --git a/packages/workflow/src/create-role-moderator.ts b/packages/workflow/src/create-role-moderator.ts index a5ed1af..5d24bf5 100644 --- a/packages/workflow/src/create-role-moderator.ts +++ b/packages/workflow/src/create-role-moderator.ts @@ -65,12 +65,12 @@ export function createRoleModerator( 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, diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index 894f2be..c0638d6 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -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, diff --git a/packages/workflow/src/types.ts b/packages/workflow/src/types.ts index 517b272..a43aa5b 100644 --- a/packages/workflow/src/types.ts +++ b/packages/workflow/src/types.ts @@ -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> = ( ctx: ThreadContext, ) => Promise>; +/** Role wiring: runtime {@link Role}, JSON Schema for `meta`, and human-readable description. */ +export type RoleDefinition> = { + description: string; + run: Role; + schema: z.ZodType; +}; + /** * 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 = ( /** Complete workflow definition as authored by users. */ export type WorkflowDefinition = { - roles: { [K in keyof M & string]: Role }; + description: string; + roles: { [K in keyof M & string]: RoleDefinition }; moderator: Moderator; };