feat: enhance workflow and testing infrastructure
- Introduced ExecutionMode type for project execution modes (parallel, sequential). - Updated SetupOpts to use ExecutionMode instead of string literals. - Enhanced workflow states to include a new "In Review" state with appropriate transitions. - Implemented TestHarness for end-to-end testing, including command interception and workspace setup. - Created TestProvider for in-memory issue tracking during tests. - Refactored project registration and setup tools to utilize ExecutionMode. - Updated various tools to ensure compatibility with new workflow and execution modes. - Added new dependencies: cockatiel for resilience and zod for schema validation.
This commit is contained in:
@@ -15,6 +15,7 @@ import { resolveRepoPath } from "../projects.js";
|
||||
import { createProvider } from "../providers/index.js";
|
||||
import { log as auditLog } from "../audit.js";
|
||||
import { getAllRoleIds, getLevelsForRole } from "../roles/index.js";
|
||||
import { ExecutionMode } from "../workflow.js";
|
||||
import { DEFAULT_ROLE_INSTRUCTIONS } from "../templates.js";
|
||||
import { DATA_DIR } from "../setup/migrate-layout.js";
|
||||
|
||||
@@ -84,7 +85,7 @@ export function createProjectRegisterTool() {
|
||||
},
|
||||
roleExecution: {
|
||||
type: "string",
|
||||
enum: ["parallel", "sequential"],
|
||||
enum: Object.values(ExecutionMode),
|
||||
description: "Project-level role execution mode: parallel (DEV and QA can work simultaneously) or sequential (only one role active at a time). Defaults to parallel.",
|
||||
},
|
||||
},
|
||||
@@ -99,7 +100,7 @@ export function createProjectRegisterTool() {
|
||||
const baseBranch = params.baseBranch as string;
|
||||
const deployBranch = (params.deployBranch as string) ?? baseBranch;
|
||||
const deployUrl = (params.deployUrl as string) ?? "";
|
||||
const roleExecution = (params.roleExecution as "parallel" | "sequential") ?? "parallel";
|
||||
const roleExecution = (params.roleExecution as ExecutionMode) ?? ExecutionMode.PARALLEL;
|
||||
const workspaceDir = ctx.workspaceDir;
|
||||
|
||||
if (!workspaceDir) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { jsonResult } from "openclaw/plugin-sdk";
|
||||
import type { ToolContext } from "../types.js";
|
||||
import { runSetup, type SetupOpts } from "../setup/index.js";
|
||||
import { getAllDefaultModels, getAllRoleIds, getLevelsForRole } from "../roles/index.js";
|
||||
import { ExecutionMode } from "../workflow.js";
|
||||
|
||||
export function createSetupTool(api: OpenClawPluginApi) {
|
||||
return (ctx: ToolContext) => ({
|
||||
@@ -51,7 +52,7 @@ export function createSetupTool(api: OpenClawPluginApi) {
|
||||
},
|
||||
projectExecution: {
|
||||
type: "string",
|
||||
enum: ["parallel", "sequential"],
|
||||
enum: Object.values(ExecutionMode),
|
||||
description: "Project execution mode. Default: parallel.",
|
||||
},
|
||||
},
|
||||
@@ -68,8 +69,7 @@ export function createSetupTool(api: OpenClawPluginApi) {
|
||||
workspacePath: params.newAgentName ? undefined : ctx.workspaceDir,
|
||||
models: params.models as SetupOpts["models"],
|
||||
projectExecution: params.projectExecution as
|
||||
| "parallel"
|
||||
| "sequential"
|
||||
| ExecutionMode
|
||||
| undefined,
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { readProjects, getProject } from "../projects.js";
|
||||
import { log as auditLog } from "../audit.js";
|
||||
import { fetchProjectQueues, getTotalQueuedCount, getQueueLabelsWithPriority } from "../services/queue.js";
|
||||
import { requireWorkspaceDir, getPluginConfig } from "../tool-helpers.js";
|
||||
import { loadWorkflow } from "../workflow.js";
|
||||
import { loadWorkflow, ExecutionMode } from "../workflow.js";
|
||||
|
||||
export function createStatusTool(api: OpenClawPluginApi) {
|
||||
return (ctx: ToolContext) => ({
|
||||
@@ -30,7 +30,7 @@ export function createStatusTool(api: OpenClawPluginApi) {
|
||||
const groupId = params.projectGroupId as string | undefined;
|
||||
|
||||
const pluginConfig = getPluginConfig(api);
|
||||
const projectExecution = (pluginConfig?.projectExecution as string) ?? "parallel";
|
||||
const projectExecution = (pluginConfig?.projectExecution as string) ?? ExecutionMode.PARALLEL;
|
||||
|
||||
// Load workspace-level workflow (per-project loaded inside map)
|
||||
const workflow = await loadWorkflow(workspaceDir);
|
||||
@@ -66,7 +66,7 @@ export function createStatusTool(api: OpenClawPluginApi) {
|
||||
return {
|
||||
name: project.name,
|
||||
groupId: pid,
|
||||
roleExecution: project.roleExecution ?? "parallel",
|
||||
roleExecution: project.roleExecution ?? ExecutionMode.PARALLEL,
|
||||
workers,
|
||||
queue: queueCounts,
|
||||
};
|
||||
|
||||
@@ -27,10 +27,11 @@ describe("task_update tool", () => {
|
||||
"Done",
|
||||
"To Improve",
|
||||
"Refining",
|
||||
"In Review",
|
||||
];
|
||||
|
||||
|
||||
// In a real test, we'd verify these against the tool's enum
|
||||
assert.strictEqual(validStates.length, 8);
|
||||
assert.strictEqual(validStates.length, 9);
|
||||
});
|
||||
|
||||
it("validates required parameters", () => {
|
||||
|
||||
@@ -12,10 +12,10 @@ import type { StateLabel } from "../providers/provider.js";
|
||||
import { selectLevel } from "../model-selector.js";
|
||||
import { getWorker } from "../projects.js";
|
||||
import { dispatchTask } from "../dispatch.js";
|
||||
import { findNextIssue, detectRoleFromLabel, detectLevelFromLabels } from "../services/tick.js";
|
||||
import { findNextIssue, detectRoleFromLabel, detectLevelFromLabels } from "../services/queue-scan.js";
|
||||
import { getAllRoleIds, isLevelForRole } from "../roles/index.js";
|
||||
import { requireWorkspaceDir, resolveProject, resolveProvider, getPluginConfig } from "../tool-helpers.js";
|
||||
import { loadWorkflow, getActiveLabel } from "../workflow.js";
|
||||
import { loadWorkflow, getActiveLabel, ExecutionMode } from "../workflow.js";
|
||||
|
||||
export function createWorkStartTool(api: OpenClawPluginApi) {
|
||||
return (ctx: ToolContext) => ({
|
||||
@@ -70,7 +70,7 @@ export function createWorkStartTool(api: OpenClawPluginApi) {
|
||||
// Check worker availability
|
||||
const worker = getWorker(project, role);
|
||||
if (worker.active) throw new Error(`${role.toUpperCase()} already active on ${project.name} (issue: ${worker.issueId})`);
|
||||
if ((project.roleExecution ?? "parallel") === "sequential") {
|
||||
if ((project.roleExecution ?? ExecutionMode.PARALLEL) === ExecutionMode.SEQUENTIAL) {
|
||||
for (const [otherRole, otherWorker] of Object.entries(project.workers)) {
|
||||
if (otherRole !== role && otherWorker.active) {
|
||||
throw new Error(`Sequential roleExecution: ${otherRole.toUpperCase()} is active`);
|
||||
|
||||
Reference in New Issue
Block a user