diff --git a/packages/cli-workflow/__tests__/commands.test.ts b/packages/cli-workflow/__tests__/commands.test.ts index bcf8abe..bc2d8e6 100644 --- a/packages/cli-workflow/__tests__/commands.test.ts +++ b/packages/cli-workflow/__tests__/commands.test.ts @@ -17,7 +17,7 @@ import { } from "../src/commands/workflow/index.js"; import { addCliArgs } from "./bundle-fixture.js"; -const fixtureDescriptor = `export const descriptor = { description: "fixture", roles: {} }; +const fixtureDescriptor = `export const descriptor = { description: "fixture", roles: {}, graph: { edges: [] } }; `; const wfPutImport = `import { putContentMerkleNode } from "@uncaged/workflow-cas"; @@ -153,6 +153,7 @@ export const run = async function* (input) { return { returnCode: 0, summary: in schema: { type: "object", properties: { greeting: { type: "string" } } }, }, }, + graph: { edges: [] }, }; ${wfPutImport} export const run = async function* (input, options) { diff --git a/packages/cli-workflow/__tests__/fork-cli.test.ts b/packages/cli-workflow/__tests__/fork-cli.test.ts index 599472c..98bffb1 100644 --- a/packages/cli-workflow/__tests__/fork-cli.test.ts +++ b/packages/cli-workflow/__tests__/fork-cli.test.ts @@ -24,6 +24,7 @@ export const descriptor = { coder: { description: "coder", schema: {} }, reviewer: { description: "reviewer", schema: {} }, }, + graph: { edges: [] }, }; export const run = async function* (input, options) { const cas = options.cas; diff --git a/packages/cli-workflow/__tests__/init-template.test.ts b/packages/cli-workflow/__tests__/init-template.test.ts index 5190da8..0fdff01 100644 --- a/packages/cli-workflow/__tests__/init-template.test.ts +++ b/packages/cli-workflow/__tests__/init-template.test.ts @@ -64,6 +64,7 @@ describe("init template", () => { const moder = await readFile(join(tdir, "src", "moderator.ts"), "utf8"); expect(moder).not.toContain("export default"); + expect(moder).toContain("ModeratorTable"); }); test("finds workspace walking up from nested cwd", async () => { diff --git a/packages/cli-workflow/__tests__/init-workspace.test.ts b/packages/cli-workflow/__tests__/init-workspace.test.ts index 93d924c..c29ca5b 100644 --- a/packages/cli-workflow/__tests__/init-workspace.test.ts +++ b/packages/cli-workflow/__tests__/init-workspace.test.ts @@ -82,7 +82,7 @@ describe("init workspace", () => { for (const term of [ "RoleDefinition", "WorkflowDefinition", - "Moderator", + "ModeratorTable", "AgentFn", "ExtractFn", "RoleMeta", diff --git a/packages/cli-workflow/__tests__/thread-cli.test.ts b/packages/cli-workflow/__tests__/thread-cli.test.ts index 28fb944..5e36107 100644 --- a/packages/cli-workflow/__tests__/thread-cli.test.ts +++ b/packages/cli-workflow/__tests__/thread-cli.test.ts @@ -36,6 +36,7 @@ const threadFixtureDescriptor = `export const descriptor = { only: { description: "only", schema: {} }, noop: { description: "noop", schema: {} }, }, + graph: { edges: [] }, }; `; diff --git a/packages/cli-workflow/src/commands/init/templates.ts b/packages/cli-workflow/src/commands/init/templates.ts index b747711..658e56b 100644 --- a/packages/cli-workflow/src/commands/init/templates.ts +++ b/packages/cli-workflow/src/commands/init/templates.ts @@ -57,17 +57,13 @@ export const greeterRole: RoleDefinition = { } export function templateModeratorTs(): string { - return `import { END, type Moderator, type ModeratorContext } from "@uncaged/workflow-runtime"; + return `import { END, START, type ModeratorTable } from "@uncaged/workflow-runtime"; import type { HelloTemplateMeta } from "./roles.js"; -export const helloTemplateModerator: Moderator = ( - ctx: ModeratorContext, -) => { - if (ctx.steps.length === 0) { - return "greeter"; - } - return END; +export const helloTemplateTable: ModeratorTable = { + [START]: [{ condition: "FALLBACK", role: "greeter" }], + greeter: [{ condition: "FALLBACK", role: END }], }; `; } @@ -75,7 +71,7 @@ export const helloTemplateModerator: Moderator = ( export function templateIndexTs(): string { return `import type { WorkflowDefinition } from "@uncaged/workflow-runtime"; -import { helloTemplateModerator } from "./moderator.js"; +import { helloTemplateTable } from "./moderator.js"; import { HELLO_TEMPLATE_DESCRIPTION, type HelloTemplateMeta, @@ -87,14 +83,14 @@ export { type HelloTemplateMeta, greeterRole, } from "./roles.js"; -export { helloTemplateModerator } from "./moderator.js"; +export { helloTemplateTable } from "./moderator.js"; export const helloTemplateWorkflowDefinition: WorkflowDefinition = { description: HELLO_TEMPLATE_DESCRIPTION, roles: { greeter: greeterRole, }, - moderator: helloTemplateModerator, + table: helloTemplateTable, }; `; } diff --git a/packages/cli-workflow/src/commands/init/workspace.ts b/packages/cli-workflow/src/commands/init/workspace.ts index 28a853e..4d0ad50 100644 --- a/packages/cli-workflow/src/commands/init/workspace.ts +++ b/packages/cli-workflow/src/commands/init/workspace.ts @@ -85,7 +85,7 @@ function agentsMd(): string { | 层级 | 目录 / 产物 | 职责 | |------|----------------|------| | **Workspace** | 仓库根(\`package.json\` 含 \`workspaces: ["templates/*", "workflows"]\`) | Bun monorepo:统一管理本地模板包与 workflow 实例 | -| **Template** | \`templates//\`(如 \`src/roles.ts\`、\`src/moderator.ts\`、\`src/index.ts\`) | 纯数据:**WorkflowDefinition**(各 **RoleDefinition** + **Moderator**),**不绑定**具体 Agent | +| **Template** | \`templates//\`(如 \`src/roles.ts\`、\`src/moderator.ts\`、\`src/index.ts\`) | 纯数据:**WorkflowDefinition**(各 **RoleDefinition** + **ModeratorTable**),**不绑定**具体 Agent | | **Workflow instance** | \`workflows/\`(或单独包) | 把模板与运行时 **AgentFn** / **ExtractFn** 组合,产出可注册的 **单文件 ESM bundle**(\`run\` + \`descriptor\` 命名导出) | Init 生成的骨架:\`templates/\` 下放可复用定义,\`workflows/\` 下放绑定与打包入口。 @@ -94,19 +94,19 @@ Init 生成的骨架:\`templates/\` 下放可复用定义,\`workflows/\` 下 - **RoleMeta**:\`Record>\`,角色名 → 该角色结构化 meta 的形状约定。 - **RoleDefinition**:纯数据——\`description\`、\`systemPrompt\`、\`schema\`(Zod v4)。不含执行逻辑。 -- **WorkflowDefinition**:\`description\` + \`roles\`(各角色定义)+ **Moderator**。 -- **Moderator**:\`(ctx: ModeratorContext) => (角色名) | END\`。同步、纯函数,只做路由。 +- **WorkflowDefinition**:\`description\` + \`roles\`(各角色定义)+ **ModeratorTable**(声明式路由表)。 +- **ModeratorTable**:从 \`START\` 与各角色名映射到有序 transition 列表(条件 + 下一角色或 \`END\`);可序列化,供描述符提取 **graph**。 - **AgentFn**:\`(ctx: AgentContext) => Promise\`,原始文本输出;从上下文读取当前角色的 \`systemPrompt\`。 - **ExtractFn**:从 CAS content hash 解析结构化数据(引擎与 Agent 都可使用)。 -引擎循环简述:**Moderator** → 选角色 → **Agent** 产出文本 → **Extract** 写入 **meta** → 追加 step,重复直至 **END**。详见 \`docs/architecture.md\` 中的三阶段说明。 +引擎循环简述:按 **ModeratorTable** 选下一角色 → **Agent** 产出文本 → **Extract** 写入 **meta** → 追加 step,重复直至 **END**。详见 \`docs/architecture.md\` 中的三阶段说明。 ## 3. 开发流程 1. **定义 RoleMeta**:为每个角色约定 meta 的 TypeScript 类型(与 Zod schema 对齐)。 2. **编写 RoleDefinition**:为每个角色写 Zod \`schema\`,补齐 \`systemPrompt\` / \`description\`。 -3. **编写 Moderator**:根据 \`ctx.steps\` 与业务状态返回下一个角色名或 \`END\`。 -4. **组装 WorkflowDefinition**:在模板 \`index\` 中导出 definition(以及必要的角色 / moderator 导出)。 +3. **编写 ModeratorTable**:为 \`START\` 与各角色声明 transition(\`FALLBACK\` 或命名条件 + \`check\`)。 +4. **组装 WorkflowDefinition**:在模板 \`index\` 中导出 definition(以及必要的角色 / table 导出)。 5. **实例化**:在 workflow 包中使用 \`createWorkflow(def, binding)\`(或项目约定的封装)绑定 **AgentFn**;**ExtractFn** 由引擎从 **workflow.yaml** 注入 \`WorkflowRuntime\`。 6. **构建**:打包为单个 **.esm.js** bundle,使用 **uncaged-workflow add** 注册。 @@ -153,7 +153,7 @@ uncaged-workflow add --- -编写新 workflow 时,先对齐 **RoleMeta → RoleDefinition(Zod)→ Moderator → 绑定 → 单文件 bundle**,再对照本节规范自检。 +编写新 workflow 时,先对齐 **RoleMeta → RoleDefinition(Zod)→ ModeratorTable → 绑定 → 单文件 bundle**,再对照本节规范自检。 `; } @@ -164,7 +164,7 @@ Local workflow development workspace (Bun monorepo). ## Layout -- \`templates/\` — reusable workflow definition packages (roles + moderator), no agent binding +- \`templates/\` — reusable workflow definition packages (roles + ModeratorTable), no agent binding - \`workflows/\` — workflow instances that bind templates to agents and export \`run\` + \`descriptor\` ## Commands diff --git a/packages/cli-workflow/src/skill.ts b/packages/cli-workflow/src/skill.ts index 047a064..e38c380 100644 --- a/packages/cli-workflow/src/skill.ts +++ b/packages/cli-workflow/src/skill.ts @@ -189,25 +189,28 @@ export const run: WorkflowRun; ## WorkflowDescriptor -Defines the workflow's metadata and role sequence: +Serialized metadata for the registry (per-role JSON Schema plus a static routing graph): \`\`\`typescript type WorkflowDescriptor = { - name: string; // verb-first kebab-case, e.g. "solve-issue" - description: string; // one-line summary - roles: string[]; // ordered role names, e.g. ["planner", "coder", "reviewer"] + description: string; + roles: Record; + graph: { + edges: Array<{ + from: string; + to: string; + condition: string; + conditionDescription: string | null; + }>; + }; }; \`\`\` ## WorkflowRun -The main function that creates and returns a moderator: +Async generator from \`createWorkflow(definition, binding)\` (**@uncaged/workflow-runtime**) — yields each role output until the workflow completes. -\`\`\`typescript -type WorkflowRun = (ctx: WorkflowContext) => Moderator; -\`\`\` - -The **Moderator** controls the flow — it decides which role runs next, handles retries, and determines when the workflow is complete. +The **ModeratorTable** on **WorkflowDefinition** is declarative routing (from each role and \`START\` to the next role or \`END\`); the engine evaluates conditions at runtime. ## Role Definition @@ -226,7 +229,7 @@ Each role has: # 1. Initialize a workspace uncaged-workflow init workspace my-workflow -# 2. Write your template (roles + moderator + descriptor) +# 2. Write your template (roles + ModeratorTable + descriptor) # 3. Build the ESM bundle bun run build diff --git a/packages/workflow-protocol/package.json b/packages/workflow-protocol/package.json index 28be28f..b35478e 100644 --- a/packages/workflow-protocol/package.json +++ b/packages/workflow-protocol/package.json @@ -6,6 +6,10 @@ ".": { "types": "./dist/index.d.ts", "import": "./src/index.ts" + }, + "./moderator-table.js": { + "types": "./dist/moderator-table.d.ts", + "import": "./src/moderator-table.ts" } }, "peerDependencies": { diff --git a/packages/workflow-protocol/src/index.ts b/packages/workflow-protocol/src/index.ts index 0390a0b..9c63340 100644 --- a/packages/workflow-protocol/src/index.ts +++ b/packages/workflow-protocol/src/index.ts @@ -18,7 +18,6 @@ export type { ExtractResult, FALLBACK, LlmProvider, - Moderator, ModeratorCondition, ModeratorContext, ModeratorTable, @@ -37,6 +36,8 @@ export type { WorkflowDefinition, WorkflowDescriptor, WorkflowFn, + WorkflowGraph, + WorkflowGraphEdge, WorkflowResult, WorkflowRoleDescriptor, WorkflowRoleSchema, @@ -50,7 +51,3 @@ export { END, START } from "./types.js"; // ── Constructor functions ────────────────────────────────────────── export { err, ok } from "./result.js"; - -// ── Moderator Table ──────────────────────────────────────────────── - -export { tableToModerator } from "./moderator-table.js"; diff --git a/packages/workflow-protocol/src/types.ts b/packages/workflow-protocol/src/types.ts index 944a193..9bf6601 100644 --- a/packages/workflow-protocol/src/types.ts +++ b/packages/workflow-protocol/src/types.ts @@ -27,9 +27,22 @@ export type WorkflowRoleDescriptor = { schema: WorkflowRoleSchema; }; +/** Serializable routing edges derived from a moderator transition table. */ +export type WorkflowGraphEdge = { + from: string; + to: string; + condition: string; + conditionDescription: string | null; +}; + +export type WorkflowGraph = { + edges: readonly WorkflowGraphEdge[]; +}; + export type WorkflowDescriptor = { description: string; roles: Record; + graph: WorkflowGraph; }; // ── Role & Thread ────────────────────────────────────────────────── @@ -160,7 +173,7 @@ export type Moderator = ( export type WorkflowDefinition = { description: string; roles: { [K in keyof M & string]: RoleDefinition }; - moderator: Moderator; + table: ModeratorTable; }; // ── Declarative Moderator Table ──────────────────────────────────── diff --git a/packages/workflow-register/src/bundle/build-descriptor.ts b/packages/workflow-register/src/bundle/build-descriptor.ts index 2bf4a7b..1d04a80 100644 --- a/packages/workflow-register/src/bundle/build-descriptor.ts +++ b/packages/workflow-register/src/bundle/build-descriptor.ts @@ -1,12 +1,35 @@ -import type { RoleMeta, WorkflowDefinition } from "@uncaged/workflow-protocol"; +import type { + ModeratorTable, + ModeratorTransition, + RoleMeta, + WorkflowDefinition, + WorkflowDescriptor, + WorkflowGraph, + WorkflowGraphEdge, +} from "@uncaged/workflow-protocol"; +import { END } from "@uncaged/workflow-protocol"; import * as z from "zod/v4"; -import type { WorkflowDescriptor, WorkflowRoleSchema } from "./types.js"; +import type { WorkflowRoleSchema } from "./types.js"; function stripJsonSchemaMeta(json: Record): WorkflowRoleSchema { const { $schema: _drop, ...rest } = json; return rest as WorkflowRoleSchema; } +function graphFromTable(table: ModeratorTable): WorkflowGraph { + const edges: WorkflowGraphEdge[] = []; + const entries = Object.entries(table) as Array<[string, ModeratorTransition[]]>; + for (const [from, transitions] of entries) { + for (const t of transitions) { + const conditionName = t.condition === "FALLBACK" ? "FALLBACK" : t.condition.name; + const conditionDescription = t.condition === "FALLBACK" ? null : t.condition.description; + const to = t.role === END ? END : t.role; + edges.push({ from, to, condition: conditionName, conditionDescription }); + } + } + return { edges }; +} + export function buildDescriptor( def: WorkflowDefinition, ): WorkflowDescriptor { @@ -20,5 +43,9 @@ export function buildDescriptor( schema: stripJsonSchemaMeta(rawJsonSchema), }; } - return { description: def.description, roles }; + return { + description: def.description, + roles, + graph: graphFromTable(def.table), + }; } diff --git a/packages/workflow-register/src/bundle/bundle-validator.ts b/packages/workflow-register/src/bundle/bundle-validator.ts index c456236..7ebc925 100644 --- a/packages/workflow-register/src/bundle/bundle-validator.ts +++ b/packages/workflow-register/src/bundle/bundle-validator.ts @@ -404,7 +404,7 @@ export function validateWorkflowBundle(input: WorkflowBundleValidationInput): Re if (!descriptorExportExists(program)) { return err( - 'workflow bundle must export descriptor (e.g. "export const descriptor = { description, roles }")', + 'workflow bundle must export descriptor (e.g. "export const descriptor = { description, roles, graph }")', ); } diff --git a/packages/workflow-register/src/bundle/index.ts b/packages/workflow-register/src/bundle/index.ts index f9f7e0e..c30c449 100644 --- a/packages/workflow-register/src/bundle/index.ts +++ b/packages/workflow-register/src/bundle/index.ts @@ -9,6 +9,8 @@ export type { ExtractedBundleExports, WorkflowBundleValidationInput, WorkflowDescriptor, + WorkflowGraph, + WorkflowGraphEdge, WorkflowRoleDescriptor, WorkflowRoleSchema, } from "./types.js"; diff --git a/packages/workflow-register/src/bundle/types.ts b/packages/workflow-register/src/bundle/types.ts index f910c97..55ad9f5 100644 --- a/packages/workflow-register/src/bundle/types.ts +++ b/packages/workflow-register/src/bundle/types.ts @@ -3,6 +3,8 @@ import type { WorkflowDescriptor, WorkflowFn } from "@uncaged/workflow-protocol" export type { WorkflowDescriptor, WorkflowFn, + WorkflowGraph, + WorkflowGraphEdge, WorkflowRoleDescriptor, WorkflowRoleSchema, } from "@uncaged/workflow-protocol"; diff --git a/packages/workflow-register/src/bundle/workflow-descriptor.ts b/packages/workflow-register/src/bundle/workflow-descriptor.ts index a1941e1..fc62c62 100644 --- a/packages/workflow-register/src/bundle/workflow-descriptor.ts +++ b/packages/workflow-register/src/bundle/workflow-descriptor.ts @@ -1,6 +1,64 @@ import { err, ok, type Result } from "@uncaged/workflow-util"; -import type { WorkflowDescriptor, WorkflowRoleDescriptor, WorkflowRoleSchema } from "./types.js"; +import type { + WorkflowDescriptor, + WorkflowGraph, + WorkflowGraphEdge, + WorkflowRoleDescriptor, + WorkflowRoleSchema, +} from "./types.js"; + +function validateDescriptorGraphEdge( + item: unknown, + index: number, +): Result { + if (item === null || typeof item !== "object" || Array.isArray(item)) { + return err(`descriptor.graph.edges[${index}] must be a non-array object`); + } + const e = item as Record; + if (typeof e.from !== "string") { + return err(`descriptor.graph.edges[${index}].from must be a string`); + } + if (typeof e.to !== "string") { + return err(`descriptor.graph.edges[${index}].to must be a string`); + } + if (typeof e.condition !== "string") { + return err(`descriptor.graph.edges[${index}].condition must be a string`); + } + const cdRaw = e.conditionDescription; + if (cdRaw !== null && cdRaw !== undefined && typeof cdRaw !== "string") { + return err(`descriptor.graph.edges[${index}].conditionDescription must be a string or null`); + } + const conditionDescription: string | null = cdRaw === undefined || cdRaw === null ? null : cdRaw; + return ok({ + from: e.from, + to: e.to, + condition: e.condition, + conditionDescription, + }); +} + +function validateDescriptorGraph(graphRaw: unknown): Result { + if (graphRaw === null || typeof graphRaw !== "object" || Array.isArray(graphRaw)) { + return err("descriptor.graph must be a non-array object"); + } + const graphRecord = graphRaw as Record; + const edgesRaw = graphRecord.edges; + if (!Array.isArray(edgesRaw)) { + return err("descriptor.graph.edges must be an array"); + } + + const edges: WorkflowGraphEdge[] = []; + for (let i = 0; i < edgesRaw.length; i++) { + const edgeResult = validateDescriptorGraphEdge(edgesRaw[i], i); + if (!edgeResult.ok) { + return edgeResult; + } + edges.push(edgeResult.value); + } + + return ok({ edges }); +} export function validateWorkflowDescriptor(value: unknown): Result { if (value === null || typeof value !== "object" || Array.isArray(value)) { @@ -36,5 +94,10 @@ export function validateWorkflowDescriptor(value: unknown): Result( - def: Pick, "roles" | "moderator">, + def: Pick, "roles"> & { + pickNext: (ctx: ModeratorContext) => (keyof M & string) | typeof END; + }, binding: AgentBinding, params: { thread: ModeratorContext; @@ -67,7 +70,7 @@ async function advanceOneRound( const { thread, runtime } = params; const modCtx: ModeratorContext = thread; - const next = def.moderator(modCtx); + const next = def.pickNext(modCtx); if (!isRoleNext(next)) { return { kind: "complete", @@ -128,16 +131,19 @@ async function advanceOneRound( } /** - * Binds pure role definitions + moderator to runtime agents. + * Binds pure role definitions + moderator table to runtime agents. * Assign with `export const run = createWorkflow(def, binding)`. * * Structured meta extraction is delegated to {@link WorkflowRuntime.extract}, which the * engine resolves from the workflow registry's `extract` scene. */ export function createWorkflow( - def: Pick, "roles" | "moderator">, + def: Pick, "roles" | "table">, binding: AgentBinding, ): WorkflowFn { + const pickNext = tableToModerator(def.table); + const loopDef = { roles: def.roles, pickNext }; + return async function* workflowLoop( thread: ThreadContext, runtime: WorkflowRuntime, @@ -148,7 +154,7 @@ export function createWorkflow( let currentThread = thread as ModeratorContext; while (true) { - const outcome = await advanceOneRound(def, binding, { + const outcome = await advanceOneRound(loopDef, binding, { thread: currentThread, runtime, }); diff --git a/packages/workflow-runtime/src/index.ts b/packages/workflow-runtime/src/index.ts index 20fe8b4..6a97383 100644 --- a/packages/workflow-runtime/src/index.ts +++ b/packages/workflow-runtime/src/index.ts @@ -10,7 +10,6 @@ export type { ExtractResult, FALLBACK, LlmProvider, - Moderator, ModeratorCondition, ModeratorContext, ModeratorTable, @@ -26,9 +25,11 @@ export type { WorkflowDefinition, WorkflowDescriptor, WorkflowFn, + WorkflowGraph, + WorkflowGraphEdge, WorkflowResult, WorkflowRoleDescriptor, WorkflowRoleSchema, WorkflowRuntime, } from "./types.js"; -export { END, START, tableToModerator } from "./types.js"; +export { END, START } from "./types.js"; diff --git a/packages/workflow-runtime/src/types.ts b/packages/workflow-runtime/src/types.ts index 53491ec..fdf17b2 100644 --- a/packages/workflow-runtime/src/types.ts +++ b/packages/workflow-runtime/src/types.ts @@ -12,7 +12,6 @@ export type { ExtractResult, FALLBACK, LlmProvider, - Moderator, ModeratorCondition, ModeratorContext, ModeratorTable, @@ -30,10 +29,12 @@ export type { WorkflowDefinition, WorkflowDescriptor, WorkflowFn, + WorkflowGraph, + WorkflowGraphEdge, WorkflowResult, WorkflowRoleDescriptor, WorkflowRoleSchema, WorkflowRuntime, } from "@uncaged/workflow-protocol"; -export { END, START, tableToModerator } from "@uncaged/workflow-protocol"; +export { END, START } from "@uncaged/workflow-protocol"; diff --git a/packages/workflow-template-develop/__tests__/develop-template.test.ts b/packages/workflow-template-develop/__tests__/develop-template.test.ts index 483bebe..b3ad4a0 100644 --- a/packages/workflow-template-develop/__tests__/develop-template.test.ts +++ b/packages/workflow-template-develop/__tests__/develop-template.test.ts @@ -1,11 +1,14 @@ 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 { buildDevelopDescriptor } from "../src/descriptor.js"; -import { developModerator } from "../src/index.js"; +import { developTable } from "../src/moderator.js"; import type { CommitterMeta, PlannerMeta } from "../src/roles/index.js"; import type { DevelopMeta } from "../src/roles.js"; +const developModerator = tableToModerator(developTable); + const DEFAULT_PHASES: PlannerMeta["phases"] = [ { hash: "4KNMR2PX", @@ -232,6 +235,7 @@ describe("buildDevelopDescriptor", () => { "reviewer", "tester", ]); + expect(validated.value.graph.edges.length).toBeGreaterThan(0); for (const key of ["planner", "coder", "reviewer", "tester", "committer"] as const) { const role = validated.value.roles[key]; expect(role).toBeDefined(); diff --git a/packages/workflow-template-develop/package.json b/packages/workflow-template-develop/package.json index 2130646..3ab1f2f 100644 --- a/packages/workflow-template-develop/package.json +++ b/packages/workflow-template-develop/package.json @@ -15,5 +15,8 @@ "@uncaged/workflow-register": "workspace:*", "@uncaged/workflow-runtime": "workspace:*", "zod": "^4.0.0" + }, + "devDependencies": { + "@uncaged/workflow-protocol": "workspace:*" } } diff --git a/packages/workflow-template-develop/src/descriptor.ts b/packages/workflow-template-develop/src/descriptor.ts index f5683c6..0e4407d 100644 --- a/packages/workflow-template-develop/src/descriptor.ts +++ b/packages/workflow-template-develop/src/descriptor.ts @@ -1,12 +1,12 @@ import { buildDescriptor } from "@uncaged/workflow-register"; -import { developModerator } from "./moderator.js"; +import { developTable } from "./moderator.js"; import { DEVELOP_WORKFLOW_DESCRIPTION, developRoles } from "./roles.js"; export function buildDevelopDescriptor() { return buildDescriptor({ description: DEVELOP_WORKFLOW_DESCRIPTION, roles: developRoles, - moderator: developModerator, + table: developTable, }); } diff --git a/packages/workflow-template-develop/src/index.ts b/packages/workflow-template-develop/src/index.ts index 64937b3..03ceb8d 100644 --- a/packages/workflow-template-develop/src/index.ts +++ b/packages/workflow-template-develop/src/index.ts @@ -1,10 +1,10 @@ import type { WorkflowDefinition } from "@uncaged/workflow-runtime"; -import { developModerator } from "./moderator.js"; +import { developTable } from "./moderator.js"; import { DEVELOP_WORKFLOW_DESCRIPTION, type DevelopMeta, developRoles } from "./roles.js"; export { buildDevelopDescriptor } from "./descriptor.js"; -export { developModerator } from "./moderator.js"; +export { developTable } from "./moderator.js"; export { type CoderMeta, type CommitterMeta, @@ -33,5 +33,5 @@ export { export const developWorkflowDefinition: WorkflowDefinition = { description: DEVELOP_WORKFLOW_DESCRIPTION, roles: developRoles, - moderator: developModerator, + table: developTable, }; diff --git a/packages/workflow-template-develop/src/moderator.ts b/packages/workflow-template-develop/src/moderator.ts index 3ad7053..a2c4d79 100644 --- a/packages/workflow-template-develop/src/moderator.ts +++ b/packages/workflow-template-develop/src/moderator.ts @@ -3,7 +3,6 @@ import { type ModeratorCondition, type ModeratorTable, START, - tableToModerator, } from "@uncaged/workflow-runtime"; import type { DevelopMeta } from "./roles.js"; @@ -88,4 +87,4 @@ const table: ModeratorTable = { committer: [{ condition: "FALLBACK", role: END }], }; -export const developModerator = tableToModerator(table); +export { table as developTable }; 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 5af8e56..851ff5f 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 @@ -4,6 +4,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { createCasStore } from "@uncaged/workflow-cas"; import { createExtract } from "@uncaged/workflow-execute"; +import { tableToModerator } from "@uncaged/workflow-protocol/moderator-table.js"; import { validateWorkflowDescriptor } from "@uncaged/workflow-register"; import { createWorkflow, @@ -14,10 +15,12 @@ import { } from "@uncaged/workflow-runtime"; import { buildSolveIssueDescriptor } from "../src/descriptor.js"; import type { DeveloperMeta } from "../src/developer.js"; -import { solveIssueModerator, solveIssueWorkflowDefinition } from "../src/index.js"; +import { solveIssueTable, solveIssueWorkflowDefinition } from "../src/index.js"; import type { PreparerMeta, SubmitterMeta } from "../src/roles/index.js"; import type { SolveIssueMeta } from "../src/roles.js"; +const solveIssueModerator = tableToModerator(solveIssueTable); + function jsonResponse(payload: Record): Response { return new Response(JSON.stringify(payload), { status: 200, @@ -388,6 +391,7 @@ describe("buildSolveIssueDescriptor", () => { "preparer", "submitter", ]); + expect(validated.value.graph.edges.length).toBe(4); for (const key of ["preparer", "developer", "submitter"] as const) { const role = validated.value.roles[key]; expect(role).toBeDefined(); diff --git a/packages/workflow-template-solve-issue/package.json b/packages/workflow-template-solve-issue/package.json index 2329ad5..04a372c 100644 --- a/packages/workflow-template-solve-issue/package.json +++ b/packages/workflow-template-solve-issue/package.json @@ -18,6 +18,7 @@ }, "devDependencies": { "@uncaged/workflow-cas": "workspace:*", - "@uncaged/workflow-execute": "workspace:*" + "@uncaged/workflow-execute": "workspace:*", + "@uncaged/workflow-protocol": "workspace:*" } } diff --git a/packages/workflow-template-solve-issue/src/descriptor.ts b/packages/workflow-template-solve-issue/src/descriptor.ts index 44a6999..256fae9 100644 --- a/packages/workflow-template-solve-issue/src/descriptor.ts +++ b/packages/workflow-template-solve-issue/src/descriptor.ts @@ -1,12 +1,12 @@ import { buildDescriptor } from "@uncaged/workflow-register"; -import { solveIssueModerator } from "./moderator.js"; +import { solveIssueTable } from "./moderator.js"; import { SOLVE_ISSUE_WORKFLOW_DESCRIPTION, solveIssueRoles } from "./roles.js"; export function buildSolveIssueDescriptor() { return buildDescriptor({ description: SOLVE_ISSUE_WORKFLOW_DESCRIPTION, roles: solveIssueRoles, - moderator: solveIssueModerator, + table: solveIssueTable, }); } diff --git a/packages/workflow-template-solve-issue/src/index.ts b/packages/workflow-template-solve-issue/src/index.ts index 6323d0a..ddfa407 100644 --- a/packages/workflow-template-solve-issue/src/index.ts +++ b/packages/workflow-template-solve-issue/src/index.ts @@ -1,6 +1,6 @@ import type { WorkflowDefinition } from "@uncaged/workflow-runtime"; -import { solveIssueModerator } from "./moderator.js"; +import { solveIssueTable } from "./moderator.js"; import { SOLVE_ISSUE_WORKFLOW_DESCRIPTION, type SolveIssueMeta, solveIssueRoles } from "./roles.js"; export { buildSolveIssueDescriptor } from "./descriptor.js"; @@ -9,7 +9,7 @@ export { developerMetaSchema, developerRole, } from "./developer.js"; -export { solveIssueModerator } from "./moderator.js"; +export { solveIssueTable } from "./moderator.js"; export { type PreparerMeta, preparerMetaSchema, @@ -28,5 +28,5 @@ export { export const solveIssueWorkflowDefinition: WorkflowDefinition = { description: SOLVE_ISSUE_WORKFLOW_DESCRIPTION, roles: solveIssueRoles, - moderator: solveIssueModerator, + table: solveIssueTable, }; diff --git a/packages/workflow-template-solve-issue/src/moderator.ts b/packages/workflow-template-solve-issue/src/moderator.ts index 458d2f6..81dbcde 100644 --- a/packages/workflow-template-solve-issue/src/moderator.ts +++ b/packages/workflow-template-solve-issue/src/moderator.ts @@ -1,4 +1,4 @@ -import { END, type ModeratorTable, START, tableToModerator } from "@uncaged/workflow-runtime"; +import { END, type ModeratorTable, START } from "@uncaged/workflow-runtime"; import type { SolveIssueMeta } from "./roles.js"; @@ -9,4 +9,4 @@ const table: ModeratorTable = { submitter: [{ condition: "FALLBACK", role: END }], }; -export const solveIssueModerator = tableToModerator(table); +export { table as solveIssueTable };