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:
@@ -17,7 +17,7 @@ import {
|
|||||||
} from "../src/commands/workflow/index.js";
|
} from "../src/commands/workflow/index.js";
|
||||||
import { addCliArgs } from "./bundle-fixture.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";
|
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" } } },
|
schema: { type: "object", properties: { greeting: { type: "string" } } },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
graph: { edges: [] },
|
||||||
};
|
};
|
||||||
${wfPutImport}
|
${wfPutImport}
|
||||||
export const run = async function* (input, options) {
|
export const run = async function* (input, options) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const descriptor = {
|
|||||||
coder: { description: "coder", schema: {} },
|
coder: { description: "coder", schema: {} },
|
||||||
reviewer: { description: "reviewer", schema: {} },
|
reviewer: { description: "reviewer", schema: {} },
|
||||||
},
|
},
|
||||||
|
graph: { edges: [] },
|
||||||
};
|
};
|
||||||
export const run = async function* (input, options) {
|
export const run = async function* (input, options) {
|
||||||
const cas = options.cas;
|
const cas = options.cas;
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ describe("init template", () => {
|
|||||||
|
|
||||||
const moder = await readFile(join(tdir, "src", "moderator.ts"), "utf8");
|
const moder = await readFile(join(tdir, "src", "moderator.ts"), "utf8");
|
||||||
expect(moder).not.toContain("export default");
|
expect(moder).not.toContain("export default");
|
||||||
|
expect(moder).toContain("ModeratorTable");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("finds workspace walking up from nested cwd", async () => {
|
test("finds workspace walking up from nested cwd", async () => {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ describe("init workspace", () => {
|
|||||||
for (const term of [
|
for (const term of [
|
||||||
"RoleDefinition",
|
"RoleDefinition",
|
||||||
"WorkflowDefinition",
|
"WorkflowDefinition",
|
||||||
"Moderator",
|
"ModeratorTable",
|
||||||
"AgentFn",
|
"AgentFn",
|
||||||
"ExtractFn",
|
"ExtractFn",
|
||||||
"RoleMeta",
|
"RoleMeta",
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const threadFixtureDescriptor = `export const descriptor = {
|
|||||||
only: { description: "only", schema: {} },
|
only: { description: "only", schema: {} },
|
||||||
noop: { description: "noop", schema: {} },
|
noop: { description: "noop", schema: {} },
|
||||||
},
|
},
|
||||||
|
graph: { edges: [] },
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -57,17 +57,13 @@ export const greeterRole: RoleDefinition<HelloTemplateMeta["greeter"]> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function templateModeratorTs(): string {
|
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";
|
import type { HelloTemplateMeta } from "./roles.js";
|
||||||
|
|
||||||
export const helloTemplateModerator: Moderator<HelloTemplateMeta> = (
|
export const helloTemplateTable: ModeratorTable<HelloTemplateMeta> = {
|
||||||
ctx: ModeratorContext<HelloTemplateMeta>,
|
[START]: [{ condition: "FALLBACK", role: "greeter" }],
|
||||||
) => {
|
greeter: [{ condition: "FALLBACK", role: END }],
|
||||||
if (ctx.steps.length === 0) {
|
|
||||||
return "greeter";
|
|
||||||
}
|
|
||||||
return END;
|
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -75,7 +71,7 @@ export const helloTemplateModerator: Moderator<HelloTemplateMeta> = (
|
|||||||
export function templateIndexTs(): string {
|
export function templateIndexTs(): string {
|
||||||
return `import type { WorkflowDefinition } from "@uncaged/workflow-runtime";
|
return `import type { WorkflowDefinition } from "@uncaged/workflow-runtime";
|
||||||
|
|
||||||
import { helloTemplateModerator } from "./moderator.js";
|
import { helloTemplateTable } from "./moderator.js";
|
||||||
import {
|
import {
|
||||||
HELLO_TEMPLATE_DESCRIPTION,
|
HELLO_TEMPLATE_DESCRIPTION,
|
||||||
type HelloTemplateMeta,
|
type HelloTemplateMeta,
|
||||||
@@ -87,14 +83,14 @@ export {
|
|||||||
type HelloTemplateMeta,
|
type HelloTemplateMeta,
|
||||||
greeterRole,
|
greeterRole,
|
||||||
} from "./roles.js";
|
} from "./roles.js";
|
||||||
export { helloTemplateModerator } from "./moderator.js";
|
export { helloTemplateTable } from "./moderator.js";
|
||||||
|
|
||||||
export const helloTemplateWorkflowDefinition: WorkflowDefinition<HelloTemplateMeta> = {
|
export const helloTemplateWorkflowDefinition: WorkflowDefinition<HelloTemplateMeta> = {
|
||||||
description: HELLO_TEMPLATE_DESCRIPTION,
|
description: HELLO_TEMPLATE_DESCRIPTION,
|
||||||
roles: {
|
roles: {
|
||||||
greeter: greeterRole,
|
greeter: greeterRole,
|
||||||
},
|
},
|
||||||
moderator: helloTemplateModerator,
|
table: helloTemplateTable,
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ function agentsMd(): string {
|
|||||||
| 层级 | 目录 / 产物 | 职责 |
|
| 层级 | 目录 / 产物 | 职责 |
|
||||||
|------|----------------|------|
|
|------|----------------|------|
|
||||||
| **Workspace** | 仓库根(\`package.json\` 含 \`workspaces: ["templates/*", "workflows"]\`) | Bun monorepo:统一管理本地模板包与 workflow 实例 |
|
| **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\` 命名导出) |
|
| **Workflow instance** | \`workflows/\`(或单独包) | 把模板与运行时 **AgentFn** / **ExtractFn** 组合,产出可注册的 **单文件 ESM bundle**(\`run\` + \`descriptor\` 命名导出) |
|
||||||
|
|
||||||
Init 生成的骨架:\`templates/\` 下放可复用定义,\`workflows/\` 下放绑定与打包入口。
|
Init 生成的骨架:\`templates/\` 下放可复用定义,\`workflows/\` 下放绑定与打包入口。
|
||||||
@@ -94,19 +94,19 @@ Init 生成的骨架:\`templates/\` 下放可复用定义,\`workflows/\` 下
|
|||||||
|
|
||||||
- **RoleMeta**:\`Record<string, Record<string, unknown>>\`,角色名 → 该角色结构化 meta 的形状约定。
|
- **RoleMeta**:\`Record<string, Record<string, unknown>>\`,角色名 → 该角色结构化 meta 的形状约定。
|
||||||
- **RoleDefinition<Meta>**:纯数据——\`description\`、\`systemPrompt\`、\`schema\`(Zod v4)。不含执行逻辑。
|
- **RoleDefinition<Meta>**:纯数据——\`description\`、\`systemPrompt\`、\`schema\`(Zod v4)。不含执行逻辑。
|
||||||
- **WorkflowDefinition<M extends RoleMeta>**:\`description\` + \`roles\`(各角色定义)+ **Moderator**。
|
- **WorkflowDefinition<M extends RoleMeta>**:\`description\` + \`roles\`(各角色定义)+ **ModeratorTable**(声明式路由表)。
|
||||||
- **Moderator**:\`(ctx: ModeratorContext<M>) => (角色名) | END\`。同步、纯函数,只做路由。
|
- **ModeratorTable**:从 \`START\` 与各角色名映射到有序 transition 列表(条件 + 下一角色或 \`END\`);可序列化,供描述符提取 **graph**。
|
||||||
- **AgentFn**:\`(ctx: AgentContext) => Promise<string>\`,原始文本输出;从上下文读取当前角色的 \`systemPrompt\`。
|
- **AgentFn**:\`(ctx: AgentContext) => Promise<string>\`,原始文本输出;从上下文读取当前角色的 \`systemPrompt\`。
|
||||||
- **ExtractFn**:从 CAS content hash 解析结构化数据(引擎与 Agent 都可使用)。
|
- **ExtractFn**:从 CAS content hash 解析结构化数据(引擎与 Agent 都可使用)。
|
||||||
|
|
||||||
引擎循环简述:**Moderator** → 选角色 → **Agent** 产出文本 → **Extract** 写入 **meta** → 追加 step,重复直至 **END**。详见 \`docs/architecture.md\` 中的三阶段说明。
|
引擎循环简述:按 **ModeratorTable** 选下一角色 → **Agent** 产出文本 → **Extract** 写入 **meta** → 追加 step,重复直至 **END**。详见 \`docs/architecture.md\` 中的三阶段说明。
|
||||||
|
|
||||||
## 3. 开发流程
|
## 3. 开发流程
|
||||||
|
|
||||||
1. **定义 RoleMeta**:为每个角色约定 meta 的 TypeScript 类型(与 Zod schema 对齐)。
|
1. **定义 RoleMeta**:为每个角色约定 meta 的 TypeScript 类型(与 Zod schema 对齐)。
|
||||||
2. **编写 RoleDefinition**:为每个角色写 Zod \`schema\`,补齐 \`systemPrompt\` / \`description\`。
|
2. **编写 RoleDefinition**:为每个角色写 Zod \`schema\`,补齐 \`systemPrompt\` / \`description\`。
|
||||||
3. **编写 Moderator**:根据 \`ctx.steps\` 与业务状态返回下一个角色名或 \`END\`。
|
3. **编写 ModeratorTable**:为 \`START\` 与各角色声明 transition(\`FALLBACK\` 或命名条件 + \`check\`)。
|
||||||
4. **组装 WorkflowDefinition**:在模板 \`index\` 中导出 definition(以及必要的角色 / moderator 导出)。
|
4. **组装 WorkflowDefinition**:在模板 \`index\` 中导出 definition(以及必要的角色 / table 导出)。
|
||||||
5. **实例化**:在 workflow 包中使用 \`createWorkflow(def, binding)\`(或项目约定的封装)绑定 **AgentFn**;**ExtractFn** 由引擎从 **workflow.yaml** 注入 \`WorkflowRuntime\`。
|
5. **实例化**:在 workflow 包中使用 \`createWorkflow(def, binding)\`(或项目约定的封装)绑定 **AgentFn**;**ExtractFn** 由引擎从 **workflow.yaml** 注入 \`WorkflowRuntime\`。
|
||||||
6. **构建**:打包为单个 **.esm.js** bundle,使用 **uncaged-workflow add** 注册。
|
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
|
## 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\`
|
- \`workflows/\` — workflow instances that bind templates to agents and export \`run\` + \`descriptor\`
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|||||||
@@ -189,25 +189,28 @@ export const run: WorkflowRun;
|
|||||||
|
|
||||||
## WorkflowDescriptor
|
## WorkflowDescriptor
|
||||||
|
|
||||||
Defines the workflow's metadata and role sequence:
|
Serialized metadata for the registry (per-role JSON Schema plus a static routing graph):
|
||||||
|
|
||||||
\`\`\`typescript
|
\`\`\`typescript
|
||||||
type WorkflowDescriptor = {
|
type WorkflowDescriptor = {
|
||||||
name: string; // verb-first kebab-case, e.g. "solve-issue"
|
description: string;
|
||||||
description: string; // one-line summary
|
roles: Record<string, { description: string; schema: unknown /* JSON Schema */ }>;
|
||||||
roles: string[]; // ordered role names, e.g. ["planner", "coder", "reviewer"]
|
graph: {
|
||||||
|
edges: Array<{
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
condition: string;
|
||||||
|
conditionDescription: string | null;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
\`\`\`
|
\`\`\`
|
||||||
|
|
||||||
## WorkflowRun
|
## 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
|
The **ModeratorTable** on **WorkflowDefinition** is declarative routing (from each role and \`START\` to the next role or \`END\`); the engine evaluates conditions at runtime.
|
||||||
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.
|
|
||||||
|
|
||||||
## Role Definition
|
## Role Definition
|
||||||
|
|
||||||
@@ -226,7 +229,7 @@ Each role has:
|
|||||||
# 1. Initialize a workspace
|
# 1. Initialize a workspace
|
||||||
uncaged-workflow init workspace my-workflow
|
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
|
# 3. Build the ESM bundle
|
||||||
bun run build
|
bun run build
|
||||||
|
|||||||
@@ -6,6 +6,10 @@
|
|||||||
".": {
|
".": {
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"import": "./src/index.ts"
|
"import": "./src/index.ts"
|
||||||
|
},
|
||||||
|
"./moderator-table.js": {
|
||||||
|
"types": "./dist/moderator-table.d.ts",
|
||||||
|
"import": "./src/moderator-table.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export type {
|
|||||||
ExtractResult,
|
ExtractResult,
|
||||||
FALLBACK,
|
FALLBACK,
|
||||||
LlmProvider,
|
LlmProvider,
|
||||||
Moderator,
|
|
||||||
ModeratorCondition,
|
ModeratorCondition,
|
||||||
ModeratorContext,
|
ModeratorContext,
|
||||||
ModeratorTable,
|
ModeratorTable,
|
||||||
@@ -37,6 +36,8 @@ export type {
|
|||||||
WorkflowDefinition,
|
WorkflowDefinition,
|
||||||
WorkflowDescriptor,
|
WorkflowDescriptor,
|
||||||
WorkflowFn,
|
WorkflowFn,
|
||||||
|
WorkflowGraph,
|
||||||
|
WorkflowGraphEdge,
|
||||||
WorkflowResult,
|
WorkflowResult,
|
||||||
WorkflowRoleDescriptor,
|
WorkflowRoleDescriptor,
|
||||||
WorkflowRoleSchema,
|
WorkflowRoleSchema,
|
||||||
@@ -50,7 +51,3 @@ export { END, START } from "./types.js";
|
|||||||
// ── Constructor functions ──────────────────────────────────────────
|
// ── Constructor functions ──────────────────────────────────────────
|
||||||
|
|
||||||
export { err, ok } from "./result.js";
|
export { err, ok } from "./result.js";
|
||||||
|
|
||||||
// ── Moderator Table ────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export { tableToModerator } from "./moderator-table.js";
|
|
||||||
|
|||||||
@@ -27,9 +27,22 @@ export type WorkflowRoleDescriptor = {
|
|||||||
schema: WorkflowRoleSchema;
|
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 = {
|
export type WorkflowDescriptor = {
|
||||||
description: string;
|
description: string;
|
||||||
roles: Record<string, WorkflowRoleDescriptor>;
|
roles: Record<string, WorkflowRoleDescriptor>;
|
||||||
|
graph: WorkflowGraph;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Role & Thread ──────────────────────────────────────────────────
|
// ── Role & Thread ──────────────────────────────────────────────────
|
||||||
@@ -160,7 +173,7 @@ export type Moderator<M extends RoleMeta> = (
|
|||||||
export type WorkflowDefinition<M extends RoleMeta> = {
|
export type WorkflowDefinition<M extends RoleMeta> = {
|
||||||
description: string;
|
description: string;
|
||||||
roles: { [K in keyof M & string]: RoleDefinition<M[K]> };
|
roles: { [K in keyof M & string]: RoleDefinition<M[K]> };
|
||||||
moderator: Moderator<M>;
|
table: ModeratorTable<M>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Declarative Moderator Table ────────────────────────────────────
|
// ── 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 * 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 {
|
function stripJsonSchemaMeta(json: Record<string, unknown>): WorkflowRoleSchema {
|
||||||
const { $schema: _drop, ...rest } = json;
|
const { $schema: _drop, ...rest } = json;
|
||||||
return rest as WorkflowRoleSchema;
|
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>(
|
export function buildDescriptor<M extends RoleMeta>(
|
||||||
def: WorkflowDefinition<M>,
|
def: WorkflowDefinition<M>,
|
||||||
): WorkflowDescriptor {
|
): WorkflowDescriptor {
|
||||||
@@ -20,5 +43,9 @@ export function buildDescriptor<M extends RoleMeta>(
|
|||||||
schema: stripJsonSchemaMeta(rawJsonSchema),
|
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)) {
|
if (!descriptorExportExists(program)) {
|
||||||
return err(
|
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,
|
ExtractedBundleExports,
|
||||||
WorkflowBundleValidationInput,
|
WorkflowBundleValidationInput,
|
||||||
WorkflowDescriptor,
|
WorkflowDescriptor,
|
||||||
|
WorkflowGraph,
|
||||||
|
WorkflowGraphEdge,
|
||||||
WorkflowRoleDescriptor,
|
WorkflowRoleDescriptor,
|
||||||
WorkflowRoleSchema,
|
WorkflowRoleSchema,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { WorkflowDescriptor, WorkflowFn } from "@uncaged/workflow-protocol"
|
|||||||
export type {
|
export type {
|
||||||
WorkflowDescriptor,
|
WorkflowDescriptor,
|
||||||
WorkflowFn,
|
WorkflowFn,
|
||||||
|
WorkflowGraph,
|
||||||
|
WorkflowGraphEdge,
|
||||||
WorkflowRoleDescriptor,
|
WorkflowRoleDescriptor,
|
||||||
WorkflowRoleSchema,
|
WorkflowRoleSchema,
|
||||||
} from "@uncaged/workflow-protocol";
|
} from "@uncaged/workflow-protocol";
|
||||||
|
|||||||
@@ -1,6 +1,64 @@
|
|||||||
import { err, ok, type Result } from "@uncaged/workflow-util";
|
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> {
|
export function validateWorkflowDescriptor(value: unknown): Result<WorkflowDescriptor, string> {
|
||||||
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
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,
|
ExtractedBundleExports,
|
||||||
WorkflowBundleValidationInput,
|
WorkflowBundleValidationInput,
|
||||||
WorkflowDescriptor,
|
WorkflowDescriptor,
|
||||||
|
WorkflowGraph,
|
||||||
|
WorkflowGraphEdge,
|
||||||
WorkflowRoleDescriptor,
|
WorkflowRoleDescriptor,
|
||||||
WorkflowRoleSchema,
|
WorkflowRoleSchema,
|
||||||
} from "./bundle/index.js";
|
} from "./bundle/index.js";
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { putContentNodeWithRefs } from "@uncaged/workflow-cas";
|
import { putContentNodeWithRefs } from "@uncaged/workflow-cas";
|
||||||
|
import { tableToModerator } from "@uncaged/workflow-protocol/moderator-table.js";
|
||||||
import type * as z from "zod/v4";
|
import type * as z from "zod/v4";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -57,7 +58,9 @@ function agentForRole(binding: AgentBinding, roleName: string): AgentFn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function advanceOneRound<M extends RoleMeta>(
|
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,
|
binding: AgentBinding,
|
||||||
params: {
|
params: {
|
||||||
thread: ModeratorContext<M>;
|
thread: ModeratorContext<M>;
|
||||||
@@ -67,7 +70,7 @@ async function advanceOneRound<M extends RoleMeta>(
|
|||||||
const { thread, runtime } = params;
|
const { thread, runtime } = params;
|
||||||
const modCtx: ModeratorContext<M> = thread;
|
const modCtx: ModeratorContext<M> = thread;
|
||||||
|
|
||||||
const next = def.moderator(modCtx);
|
const next = def.pickNext(modCtx);
|
||||||
if (!isRoleNext(next)) {
|
if (!isRoleNext(next)) {
|
||||||
return {
|
return {
|
||||||
kind: "complete",
|
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)`.
|
* Assign with `export const run = createWorkflow(def, binding)`.
|
||||||
*
|
*
|
||||||
* Structured meta extraction is delegated to {@link WorkflowRuntime.extract}, which the
|
* Structured meta extraction is delegated to {@link WorkflowRuntime.extract}, which the
|
||||||
* engine resolves from the workflow registry's `extract` scene.
|
* engine resolves from the workflow registry's `extract` scene.
|
||||||
*/
|
*/
|
||||||
export function createWorkflow<M extends RoleMeta>(
|
export function createWorkflow<M extends RoleMeta>(
|
||||||
def: Pick<WorkflowDefinition<M>, "roles" | "moderator">,
|
def: Pick<WorkflowDefinition<M>, "roles" | "table">,
|
||||||
binding: AgentBinding,
|
binding: AgentBinding,
|
||||||
): WorkflowFn {
|
): WorkflowFn {
|
||||||
|
const pickNext = tableToModerator(def.table);
|
||||||
|
const loopDef = { roles: def.roles, pickNext };
|
||||||
|
|
||||||
return async function* workflowLoop(
|
return async function* workflowLoop(
|
||||||
thread: ThreadContext,
|
thread: ThreadContext,
|
||||||
runtime: WorkflowRuntime,
|
runtime: WorkflowRuntime,
|
||||||
@@ -148,7 +154,7 @@ export function createWorkflow<M extends RoleMeta>(
|
|||||||
let currentThread = thread as ModeratorContext<M>;
|
let currentThread = thread as ModeratorContext<M>;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const outcome = await advanceOneRound(def, binding, {
|
const outcome = await advanceOneRound(loopDef, binding, {
|
||||||
thread: currentThread,
|
thread: currentThread,
|
||||||
runtime,
|
runtime,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export type {
|
|||||||
ExtractResult,
|
ExtractResult,
|
||||||
FALLBACK,
|
FALLBACK,
|
||||||
LlmProvider,
|
LlmProvider,
|
||||||
Moderator,
|
|
||||||
ModeratorCondition,
|
ModeratorCondition,
|
||||||
ModeratorContext,
|
ModeratorContext,
|
||||||
ModeratorTable,
|
ModeratorTable,
|
||||||
@@ -26,9 +25,11 @@ export type {
|
|||||||
WorkflowDefinition,
|
WorkflowDefinition,
|
||||||
WorkflowDescriptor,
|
WorkflowDescriptor,
|
||||||
WorkflowFn,
|
WorkflowFn,
|
||||||
|
WorkflowGraph,
|
||||||
|
WorkflowGraphEdge,
|
||||||
WorkflowResult,
|
WorkflowResult,
|
||||||
WorkflowRoleDescriptor,
|
WorkflowRoleDescriptor,
|
||||||
WorkflowRoleSchema,
|
WorkflowRoleSchema,
|
||||||
WorkflowRuntime,
|
WorkflowRuntime,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
export { END, START, tableToModerator } from "./types.js";
|
export { END, START } from "./types.js";
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export type {
|
|||||||
ExtractResult,
|
ExtractResult,
|
||||||
FALLBACK,
|
FALLBACK,
|
||||||
LlmProvider,
|
LlmProvider,
|
||||||
Moderator,
|
|
||||||
ModeratorCondition,
|
ModeratorCondition,
|
||||||
ModeratorContext,
|
ModeratorContext,
|
||||||
ModeratorTable,
|
ModeratorTable,
|
||||||
@@ -30,10 +29,12 @@ export type {
|
|||||||
WorkflowDefinition,
|
WorkflowDefinition,
|
||||||
WorkflowDescriptor,
|
WorkflowDescriptor,
|
||||||
WorkflowFn,
|
WorkflowFn,
|
||||||
|
WorkflowGraph,
|
||||||
|
WorkflowGraphEdge,
|
||||||
WorkflowResult,
|
WorkflowResult,
|
||||||
WorkflowRoleDescriptor,
|
WorkflowRoleDescriptor,
|
||||||
WorkflowRoleSchema,
|
WorkflowRoleSchema,
|
||||||
WorkflowRuntime,
|
WorkflowRuntime,
|
||||||
} from "@uncaged/workflow-protocol";
|
} 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 { describe, expect, test } from "bun:test";
|
||||||
|
import { tableToModerator } from "@uncaged/workflow-protocol/moderator-table.js";
|
||||||
import { validateWorkflowDescriptor } from "@uncaged/workflow-register";
|
import { validateWorkflowDescriptor } from "@uncaged/workflow-register";
|
||||||
import { END, type ModeratorContext, type RoleStep, START } from "@uncaged/workflow-runtime";
|
import { END, type ModeratorContext, type RoleStep, START } from "@uncaged/workflow-runtime";
|
||||||
import { buildDevelopDescriptor } from "../src/descriptor.js";
|
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 { CommitterMeta, PlannerMeta } from "../src/roles/index.js";
|
||||||
import type { DevelopMeta } from "../src/roles.js";
|
import type { DevelopMeta } from "../src/roles.js";
|
||||||
|
|
||||||
|
const developModerator = tableToModerator(developTable);
|
||||||
|
|
||||||
const DEFAULT_PHASES: PlannerMeta["phases"] = [
|
const DEFAULT_PHASES: PlannerMeta["phases"] = [
|
||||||
{
|
{
|
||||||
hash: "4KNMR2PX",
|
hash: "4KNMR2PX",
|
||||||
@@ -232,6 +235,7 @@ describe("buildDevelopDescriptor", () => {
|
|||||||
"reviewer",
|
"reviewer",
|
||||||
"tester",
|
"tester",
|
||||||
]);
|
]);
|
||||||
|
expect(validated.value.graph.edges.length).toBeGreaterThan(0);
|
||||||
for (const key of ["planner", "coder", "reviewer", "tester", "committer"] as const) {
|
for (const key of ["planner", "coder", "reviewer", "tester", "committer"] as const) {
|
||||||
const role = validated.value.roles[key];
|
const role = validated.value.roles[key];
|
||||||
expect(role).toBeDefined();
|
expect(role).toBeDefined();
|
||||||
|
|||||||
@@ -15,5 +15,8 @@
|
|||||||
"@uncaged/workflow-register": "workspace:*",
|
"@uncaged/workflow-register": "workspace:*",
|
||||||
"@uncaged/workflow-runtime": "workspace:*",
|
"@uncaged/workflow-runtime": "workspace:*",
|
||||||
"zod": "^4.0.0"
|
"zod": "^4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@uncaged/workflow-protocol": "workspace:*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { buildDescriptor } from "@uncaged/workflow-register";
|
import { buildDescriptor } from "@uncaged/workflow-register";
|
||||||
|
|
||||||
import { developModerator } from "./moderator.js";
|
import { developTable } from "./moderator.js";
|
||||||
import { DEVELOP_WORKFLOW_DESCRIPTION, developRoles } from "./roles.js";
|
import { DEVELOP_WORKFLOW_DESCRIPTION, developRoles } from "./roles.js";
|
||||||
|
|
||||||
export function buildDevelopDescriptor() {
|
export function buildDevelopDescriptor() {
|
||||||
return buildDescriptor({
|
return buildDescriptor({
|
||||||
description: DEVELOP_WORKFLOW_DESCRIPTION,
|
description: DEVELOP_WORKFLOW_DESCRIPTION,
|
||||||
roles: developRoles,
|
roles: developRoles,
|
||||||
moderator: developModerator,
|
table: developTable,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { WorkflowDefinition } from "@uncaged/workflow-runtime";
|
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";
|
import { DEVELOP_WORKFLOW_DESCRIPTION, type DevelopMeta, developRoles } from "./roles.js";
|
||||||
|
|
||||||
export { buildDevelopDescriptor } from "./descriptor.js";
|
export { buildDevelopDescriptor } from "./descriptor.js";
|
||||||
export { developModerator } from "./moderator.js";
|
export { developTable } from "./moderator.js";
|
||||||
export {
|
export {
|
||||||
type CoderMeta,
|
type CoderMeta,
|
||||||
type CommitterMeta,
|
type CommitterMeta,
|
||||||
@@ -33,5 +33,5 @@ export {
|
|||||||
export const developWorkflowDefinition: WorkflowDefinition<DevelopMeta> = {
|
export const developWorkflowDefinition: WorkflowDefinition<DevelopMeta> = {
|
||||||
description: DEVELOP_WORKFLOW_DESCRIPTION,
|
description: DEVELOP_WORKFLOW_DESCRIPTION,
|
||||||
roles: developRoles,
|
roles: developRoles,
|
||||||
moderator: developModerator,
|
table: developTable,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import {
|
|||||||
type ModeratorCondition,
|
type ModeratorCondition,
|
||||||
type ModeratorTable,
|
type ModeratorTable,
|
||||||
START,
|
START,
|
||||||
tableToModerator,
|
|
||||||
} from "@uncaged/workflow-runtime";
|
} from "@uncaged/workflow-runtime";
|
||||||
|
|
||||||
import type { DevelopMeta } from "./roles.js";
|
import type { DevelopMeta } from "./roles.js";
|
||||||
@@ -88,4 +87,4 @@ const table: ModeratorTable<DevelopMeta> = {
|
|||||||
committer: [{ condition: "FALLBACK", role: END }],
|
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 { join } from "node:path";
|
||||||
import { createCasStore } from "@uncaged/workflow-cas";
|
import { createCasStore } from "@uncaged/workflow-cas";
|
||||||
import { createExtract } from "@uncaged/workflow-execute";
|
import { createExtract } from "@uncaged/workflow-execute";
|
||||||
|
import { tableToModerator } from "@uncaged/workflow-protocol/moderator-table.js";
|
||||||
import { validateWorkflowDescriptor } from "@uncaged/workflow-register";
|
import { validateWorkflowDescriptor } from "@uncaged/workflow-register";
|
||||||
import {
|
import {
|
||||||
createWorkflow,
|
createWorkflow,
|
||||||
@@ -14,10 +15,12 @@ import {
|
|||||||
} from "@uncaged/workflow-runtime";
|
} from "@uncaged/workflow-runtime";
|
||||||
import { buildSolveIssueDescriptor } from "../src/descriptor.js";
|
import { buildSolveIssueDescriptor } from "../src/descriptor.js";
|
||||||
import type { DeveloperMeta } from "../src/developer.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 { PreparerMeta, SubmitterMeta } from "../src/roles/index.js";
|
||||||
import type { SolveIssueMeta } from "../src/roles.js";
|
import type { SolveIssueMeta } from "../src/roles.js";
|
||||||
|
|
||||||
|
const solveIssueModerator = tableToModerator(solveIssueTable);
|
||||||
|
|
||||||
function jsonResponse(payload: Record<string, unknown>): Response {
|
function jsonResponse(payload: Record<string, unknown>): Response {
|
||||||
return new Response(JSON.stringify(payload), {
|
return new Response(JSON.stringify(payload), {
|
||||||
status: 200,
|
status: 200,
|
||||||
@@ -388,6 +391,7 @@ describe("buildSolveIssueDescriptor", () => {
|
|||||||
"preparer",
|
"preparer",
|
||||||
"submitter",
|
"submitter",
|
||||||
]);
|
]);
|
||||||
|
expect(validated.value.graph.edges.length).toBe(4);
|
||||||
for (const key of ["preparer", "developer", "submitter"] as const) {
|
for (const key of ["preparer", "developer", "submitter"] as const) {
|
||||||
const role = validated.value.roles[key];
|
const role = validated.value.roles[key];
|
||||||
expect(role).toBeDefined();
|
expect(role).toBeDefined();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@uncaged/workflow-cas": "workspace:*",
|
"@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 { buildDescriptor } from "@uncaged/workflow-register";
|
||||||
|
|
||||||
import { solveIssueModerator } from "./moderator.js";
|
import { solveIssueTable } from "./moderator.js";
|
||||||
import { SOLVE_ISSUE_WORKFLOW_DESCRIPTION, solveIssueRoles } from "./roles.js";
|
import { SOLVE_ISSUE_WORKFLOW_DESCRIPTION, solveIssueRoles } from "./roles.js";
|
||||||
|
|
||||||
export function buildSolveIssueDescriptor() {
|
export function buildSolveIssueDescriptor() {
|
||||||
return buildDescriptor({
|
return buildDescriptor({
|
||||||
description: SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
description: SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
||||||
roles: solveIssueRoles,
|
roles: solveIssueRoles,
|
||||||
moderator: solveIssueModerator,
|
table: solveIssueTable,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { WorkflowDefinition } from "@uncaged/workflow-runtime";
|
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";
|
import { SOLVE_ISSUE_WORKFLOW_DESCRIPTION, type SolveIssueMeta, solveIssueRoles } from "./roles.js";
|
||||||
|
|
||||||
export { buildSolveIssueDescriptor } from "./descriptor.js";
|
export { buildSolveIssueDescriptor } from "./descriptor.js";
|
||||||
@@ -9,7 +9,7 @@ export {
|
|||||||
developerMetaSchema,
|
developerMetaSchema,
|
||||||
developerRole,
|
developerRole,
|
||||||
} from "./developer.js";
|
} from "./developer.js";
|
||||||
export { solveIssueModerator } from "./moderator.js";
|
export { solveIssueTable } from "./moderator.js";
|
||||||
export {
|
export {
|
||||||
type PreparerMeta,
|
type PreparerMeta,
|
||||||
preparerMetaSchema,
|
preparerMetaSchema,
|
||||||
@@ -28,5 +28,5 @@ export {
|
|||||||
export const solveIssueWorkflowDefinition: WorkflowDefinition<SolveIssueMeta> = {
|
export const solveIssueWorkflowDefinition: WorkflowDefinition<SolveIssueMeta> = {
|
||||||
description: SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
description: SOLVE_ISSUE_WORKFLOW_DESCRIPTION,
|
||||||
roles: solveIssueRoles,
|
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";
|
import type { SolveIssueMeta } from "./roles.js";
|
||||||
|
|
||||||
@@ -9,4 +9,4 @@ const table: ModeratorTable<SolveIssueMeta> = {
|
|||||||
submitter: [{ condition: "FALLBACK", role: END }],
|
submitter: [{ condition: "FALLBACK", role: END }],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const solveIssueModerator = tableToModerator(table);
|
export { table as solveIssueTable };
|
||||||
|
|||||||
Reference in New Issue
Block a user