Files
devclaw-gitea/lib/tools/work-finish.ts
Lauren ten Hoor be8e0f4db1 refactor: Centralize role types and configuration (#190)
Creates a single source of truth for all worker roles via lib/roles/.

## New: lib/roles/

- **registry.ts** — All role definitions (dev, qa, architect) with
  levels, models, emoji, completion results, session key patterns
- **types.ts** — RoleConfig interface
- **selectors.ts** — Query helpers: getRole(), getLevelsForRole(),
  resolveModel(), isValidResult(), roleForLevel(), etc.
- **index.ts** — Barrel exports

## Migrated Files

- **lib/tiers.ts** — Now delegates to registry (backward compat kept)
- **lib/dispatch.ts** — Uses registry for emoji resolution
- **lib/bootstrap-hook.ts** — Uses registry for session key pattern
- **lib/services/tick.ts** — Uses registry for level detection
- **lib/services/heartbeat.ts** — Uses registry for role iteration
- **lib/tools/health.ts** — Uses registry for role iteration
- **lib/tools/work-start.ts** — Uses registry for role enum
- **lib/tools/work-finish.ts** — Uses registry for result validation
- **lib/tools/project-register.ts** — Uses registry for level lists

## Key Benefits

- Adding a new role = add entry to registry.ts (single file)
- No more scattered role unions ("dev" | "qa" | "architect")
- Type-safe role/level/result validation from registry
- Session key pattern auto-generated from registry
- All 64 tests passing (22 new registry tests + 42 existing)
2026-02-14 17:15:54 +08:00

87 lines
3.8 KiB
TypeScript

/**
* work_finish — Complete a task (DEV done, QA pass/fail/refine/blocked).
*
* Delegates side-effects to pipeline service: label transition, state update,
* issue close/reopen, notifications, and audit logging.
*/
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { jsonResult } from "openclaw/plugin-sdk";
import type { ToolContext } from "../types.js";
import { getWorker, resolveRepoPath } from "../projects.js";
import { executeCompletion, getRule, NEXT_STATE } from "../services/pipeline.js";
import { log as auditLog } from "../audit.js";
import { requireWorkspaceDir, resolveProject, resolveProvider, getPluginConfig } from "../tool-helpers.js";
import { getAllRoleIds, isValidResult, getCompletionResults } from "../roles/index.js";
export function createWorkFinishTool(api: OpenClawPluginApi) {
return (ctx: ToolContext) => ({
name: "work_finish",
label: "Work Finish",
description: `Complete a task: DEV done/blocked, QA pass/fail/refine/blocked. Handles label transition, state update, issue close/reopen, notifications, and audit logging.`,
parameters: {
type: "object",
required: ["role", "result", "projectGroupId"],
properties: {
role: { type: "string", enum: getAllRoleIds(), description: "Worker role" },
result: { type: "string", enum: ["done", "pass", "fail", "refine", "blocked"], description: "Completion result" },
projectGroupId: { type: "string", description: "Project group ID" },
summary: { type: "string", description: "Brief summary" },
prUrl: { type: "string", description: "PR/MR URL (auto-detected if omitted)" },
},
},
async execute(_id: string, params: Record<string, unknown>) {
const role = params.role as "dev" | "qa" | "architect";
const result = params.result as string;
const groupId = params.projectGroupId as string;
const summary = params.summary as string | undefined;
const prUrl = params.prUrl as string | undefined;
const workspaceDir = requireWorkspaceDir(ctx);
// Validate role:result using registry
if (!isValidResult(role, result)) {
const valid = getCompletionResults(role);
throw new Error(`${role.toUpperCase()} cannot complete with "${result}". Valid results: ${valid.join(", ")}`);
}
if (!getRule(role, result))
throw new Error(`Invalid completion: ${role}:${result}`);
// Resolve project + worker
const { project } = await resolveProject(workspaceDir, groupId);
const worker = getWorker(project, role);
if (!worker.active) throw new Error(`${role.toUpperCase()} worker not active on ${project.name}`);
const issueId = worker.issueId ? Number(worker.issueId.split(",")[0]) : null;
if (!issueId) throw new Error(`No issueId for active ${role.toUpperCase()} on ${project.name}`);
const { provider } = await resolveProvider(project);
const repoPath = resolveRepoPath(project.repo);
const issue = await provider.getIssue(issueId);
const pluginConfig = getPluginConfig(api);
// Execute completion (pipeline service handles notification with runtime)
const completion = await executeCompletion({
workspaceDir, groupId, role, result, issueId, summary, prUrl, provider, repoPath,
projectName: project.name,
channel: project.channel,
pluginConfig,
runtime: api.runtime,
});
const output: Record<string, unknown> = {
success: true, project: project.name, groupId, issueId, role, result,
...completion,
};
// Audit
await auditLog(workspaceDir, "work_finish", {
project: project.name, groupId, issue: issueId, role, result,
summary: summary ?? null, labelTransition: completion.labelTransition,
});
return jsonResult(output);
},
});
}