refactor: replace Moderator function with ModeratorTable in WorkflowDefinition (#200)

- WorkflowDefinition.moderator → WorkflowDefinition.table (ModeratorTable)
- Moderator type + tableToModerator no longer exported from protocol/runtime
- tableToModerator internalized in workflow-execute engine layer
- WorkflowDescriptor gains graph: WorkflowGraph (auto-extracted from table)
- buildDescriptor extracts serializable graph edges from ModeratorTable
- validateWorkflowDescriptor validates graph structure
- All templates (develop, solve-issue) export table directly
- CLI init scaffold updated to use ModeratorTable
- 99 tests pass, 0 failures
This commit is contained in:
2026-05-12 10:01:30 +08:00
parent 0fe17b0fb2
commit db45089922
30 changed files with 202 additions and 70 deletions
@@ -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) {
@@ -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;
@@ -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 () => {
@@ -82,7 +82,7 @@ describe("init workspace", () => {
for (const term of [
"RoleDefinition",
"WorkflowDefinition",
"Moderator",
"ModeratorTable",
"AgentFn",
"ExtractFn",
"RoleMeta",
@@ -36,6 +36,7 @@ const threadFixtureDescriptor = `export const descriptor = {
only: { description: "only", schema: {} },
noop: { description: "noop", schema: {} },
},
graph: { edges: [] },
};
`;
@@ -57,17 +57,13 @@ export const greeterRole: RoleDefinition<HelloTemplateMeta["greeter"]> = {
}
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<HelloTemplateMeta> = (
ctx: ModeratorContext<HelloTemplateMeta>,
) => {
if (ctx.steps.length === 0) {
return "greeter";
}
return END;
export const helloTemplateTable: ModeratorTable<HelloTemplateMeta> = {
[START]: [{ condition: "FALLBACK", role: "greeter" }],
greeter: [{ condition: "FALLBACK", role: END }],
};
`;
}
@@ -75,7 +71,7 @@ export const helloTemplateModerator: Moderator<HelloTemplateMeta> = (
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<HelloTemplateMeta> = {
description: HELLO_TEMPLATE_DESCRIPTION,
roles: {
greeter: greeterRole,
},
moderator: helloTemplateModerator,
table: helloTemplateTable,
};
`;
}
@@ -85,7 +85,7 @@ function agentsMd(): string {
| 层级 | 目录 / 产物 | 职责 |
|------|----------------|------|
| **Workspace** | 仓库根(\`package.json\`\`workspaces: ["templates/*", "workflows"]\`) | Bun monorepo:统一管理本地模板包与 workflow 实例 |
| **Template** | \`templates/<name>/\`(如 \`src/roles.ts\`\`src/moderator.ts\`\`src/index.ts\`) | 纯数据:**WorkflowDefinition**(各 **RoleDefinition** + **Moderator**),**不绑定**具体 Agent |
| **Template** | \`templates/<name>/\`(如 \`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<string, Record<string, unknown>>\`,角色名 → 该角色结构化 meta 的形状约定。
- **RoleDefinition<Meta>**:纯数据——\`description\`\`systemPrompt\`\`schema\`(Zod v4)。不含执行逻辑。
- **WorkflowDefinition<M extends RoleMeta>**:\`description\` + \`roles\`(各角色定义)+ **Moderator**
- **Moderator**:\`(ctx: ModeratorContext<M>) => (角色名) | END\`。同步、纯函数,只做路由
- **WorkflowDefinition<M extends RoleMeta>**:\`description\` + \`roles\`(各角色定义)+ **ModeratorTable**(声明式路由表)
- **ModeratorTable**:\`START\` 与各角色名映射到有序 transition 列表(条件 + 下一角色或 \`END\`);可序列化,供描述符提取 **graph**
- **AgentFn**:\`(ctx: AgentContext) => Promise<string>\`,原始文本输出;从上下文读取当前角色的 \`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 <name> <path/to/bundle.esm.js>
---
编写新 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
+14 -11
View File
@@ -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<string, { description: string; schema: unknown /* JSON Schema */ }>;
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
+4
View File
@@ -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": {
+2 -5
View File
@@ -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";
+14 -1
View File
@@ -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<string, WorkflowRoleDescriptor>;
graph: WorkflowGraph;
};
// ── Role & Thread ──────────────────────────────────────────────────
@@ -160,7 +173,7 @@ export type Moderator<M extends RoleMeta> = (
export type WorkflowDefinition<M extends RoleMeta> = {
description: string;
roles: { [K in keyof M & string]: RoleDefinition<M[K]> };
moderator: Moderator<M>;
table: ModeratorTable<M>;
};
// ── Declarative Moderator Table ────────────────────────────────────
@@ -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<string, unknown>): WorkflowRoleSchema {
const { $schema: _drop, ...rest } = json;
return rest as WorkflowRoleSchema;
}
function graphFromTable<M extends RoleMeta>(table: ModeratorTable<M>): WorkflowGraph {
const edges: WorkflowGraphEdge[] = [];
const entries = Object.entries(table) as Array<[string, ModeratorTransition<M>[]]>;
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<M extends RoleMeta>(
def: WorkflowDefinition<M>,
): WorkflowDescriptor {
@@ -20,5 +43,9 @@ export function buildDescriptor<M extends RoleMeta>(
schema: stripJsonSchemaMeta(rawJsonSchema),
};
}
return { description: def.description, roles };
return {
description: def.description,
roles,
graph: graphFromTable(def.table),
};
}
@@ -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 }")',
);
}
@@ -9,6 +9,8 @@ export type {
ExtractedBundleExports,
WorkflowBundleValidationInput,
WorkflowDescriptor,
WorkflowGraph,
WorkflowGraphEdge,
WorkflowRoleDescriptor,
WorkflowRoleSchema,
} from "./types.js";
@@ -3,6 +3,8 @@ import type { WorkflowDescriptor, WorkflowFn } from "@uncaged/workflow-protocol"
export type {
WorkflowDescriptor,
WorkflowFn,
WorkflowGraph,
WorkflowGraphEdge,
WorkflowRoleDescriptor,
WorkflowRoleSchema,
} from "@uncaged/workflow-protocol";
@@ -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<WorkflowGraphEdge, string> {
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<string, unknown>;
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<WorkflowGraph, string> {
if (graphRaw === null || typeof graphRaw !== "object" || Array.isArray(graphRaw)) {
return err("descriptor.graph must be a non-array object");
}
const graphRecord = graphRaw as Record<string, unknown>;
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<WorkflowDescriptor, string> {
if (value === null || typeof value !== "object" || Array.isArray(value)) {
@@ -36,5 +94,10 @@ export function validateWorkflowDescriptor(value: unknown): Result<WorkflowDescr
};
}
return ok({ description, roles });
const graphResult = validateDescriptorGraph(root.graph);
if (!graphResult.ok) {
return graphResult;
}
return ok({ description, roles, graph: graphResult.value });
}
+2
View File
@@ -3,6 +3,8 @@ export type {
ExtractedBundleExports,
WorkflowBundleValidationInput,
WorkflowDescriptor,
WorkflowGraph,
WorkflowGraphEdge,
WorkflowRoleDescriptor,
WorkflowRoleSchema,
} from "./bundle/index.js";
@@ -1,4 +1,5 @@
import { putContentNodeWithRefs } from "@uncaged/workflow-cas";
import { tableToModerator } from "@uncaged/workflow-protocol/moderator-table.js";
import type * as z from "zod/v4";
import {
@@ -57,7 +58,9 @@ function agentForRole(binding: AgentBinding, roleName: string): AgentFn {
}
async function advanceOneRound<M extends RoleMeta>(
def: Pick<WorkflowDefinition<M>, "roles" | "moderator">,
def: Pick<WorkflowDefinition<M>, "roles"> & {
pickNext: (ctx: ModeratorContext<M>) => (keyof M & string) | typeof END;
},
binding: AgentBinding,
params: {
thread: ModeratorContext<M>;
@@ -67,7 +70,7 @@ async function advanceOneRound<M extends RoleMeta>(
const { thread, runtime } = params;
const modCtx: ModeratorContext<M> = thread;
const next = def.moderator(modCtx);
const next = def.pickNext(modCtx);
if (!isRoleNext(next)) {
return {
kind: "complete",
@@ -128,16 +131,19 @@ async function advanceOneRound<M extends RoleMeta>(
}
/**
* 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<M extends RoleMeta>(
def: Pick<WorkflowDefinition<M>, "roles" | "moderator">,
def: Pick<WorkflowDefinition<M>, "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<M extends RoleMeta>(
let currentThread = thread as ModeratorContext<M>;
while (true) {
const outcome = await advanceOneRound(def, binding, {
const outcome = await advanceOneRound(loopDef, binding, {
thread: currentThread,
runtime,
});
+3 -2
View File
@@ -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";
+3 -2
View File
@@ -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";
@@ -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();
@@ -15,5 +15,8 @@
"@uncaged/workflow-register": "workspace:*",
"@uncaged/workflow-runtime": "workspace:*",
"zod": "^4.0.0"
},
"devDependencies": {
"@uncaged/workflow-protocol": "workspace:*"
}
}
@@ -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,
});
}
@@ -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<DevelopMeta> = {
description: DEVELOP_WORKFLOW_DESCRIPTION,
roles: developRoles,
moderator: developModerator,
table: developTable,
};
@@ -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<DevelopMeta> = {
committer: [{ condition: "FALLBACK", role: END }],
};
export const developModerator = tableToModerator(table);
export { table as developTable };
@@ -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<string, unknown>): 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();
@@ -18,6 +18,7 @@
},
"devDependencies": {
"@uncaged/workflow-cas": "workspace:*",
"@uncaged/workflow-execute": "workspace:*"
"@uncaged/workflow-execute": "workspace:*",
"@uncaged/workflow-protocol": "workspace:*"
}
}
@@ -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,
});
}
@@ -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<SolveIssueMeta> = {
description: SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
roles: solveIssueRoles,
moderator: solveIssueModerator,
table: solveIssueTable,
};
@@ -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<SolveIssueMeta> = {
submitter: [{ condition: "FALLBACK", role: END }],
};
export const solveIssueModerator = tableToModerator(table);
export { table as solveIssueTable };