Merge pull request 'refactor: replace Moderator function with ModeratorTable in WorkflowDefinition' (#201) from refactor/200-moderator-table into main
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user