refactor: migrate role handling from tiers to roles module

- Removed the deprecated tiers.ts file and migrated all related functionality to roles/index.js.
- Updated tests and tools to reflect the new role structure, replacing references to "dev", "qa", and "architect" with "developer", "tester", and "architect".
- Adjusted workflow configurations and state management to accommodate the new role naming conventions.
- Enhanced project registration and health check tools to support dynamic role handling.
- Updated task creation, update, and completion processes to align with the new role definitions.
- Improved documentation and comments to clarify role responsibilities and usage.
This commit is contained in:
Lauren ten Hoor
2026-02-15 18:32:10 +08:00
parent 6a99752e5f
commit 0e24a68882
44 changed files with 1162 additions and 762 deletions

View File

@@ -6,7 +6,7 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { HEARTBEAT_DEFAULTS } from "../services/heartbeat.js";
type ModelConfig = { dev: Record<string, string>; qa: Record<string, string>; architect: Record<string, string> };
type ModelConfig = Record<string, Record<string, string>>;
/**
* Write DevClaw model level config to openclaw.json plugins section.

View File

@@ -5,13 +5,13 @@
* Used by both the `setup` tool and the `openclaw devclaw setup` CLI command.
*/
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { DEFAULT_MODELS } from "../tiers.js";
import { getAllDefaultModels } from "../roles/index.js";
import { migrateChannelBinding } from "../binding-manager.js";
import { createAgent, resolveWorkspacePath } from "./agent.js";
import { writePluginConfig } from "./config.js";
import { scaffoldWorkspace } from "./workspace.js";
export type ModelConfig = { dev: Record<string, string>; qa: Record<string, string>; architect: Record<string, string> };
export type ModelConfig = Record<string, Record<string, string>>;
export type SetupOpts = {
/** OpenClaw plugin API for config access. */
@@ -27,7 +27,7 @@ export type SetupOpts = {
/** Override workspace path (auto-detected from agent if not given). */
workspacePath?: string;
/** Model overrides per role.level. Missing levels use defaults. */
models?: { dev?: Partial<Record<string, string>>; qa?: Partial<Record<string, string>>; architect?: Partial<Record<string, string>> };
models?: Record<string, Partial<Record<string, string>>>;
/** Plugin-level project execution mode: parallel or sequential. Default: parallel. */
projectExecution?: "parallel" | "sequential";
};
@@ -113,25 +113,21 @@ async function tryMigrateBinding(
}
function buildModelConfig(overrides?: SetupOpts["models"]): ModelConfig {
const dev: Record<string, string> = { ...DEFAULT_MODELS.dev };
const qa: Record<string, string> = { ...DEFAULT_MODELS.qa };
const architect: Record<string, string> = { ...DEFAULT_MODELS.architect };
const defaults = getAllDefaultModels();
const result: ModelConfig = {};
if (overrides?.dev) {
for (const [level, model] of Object.entries(overrides.dev)) {
if (model) dev[level] = model;
}
for (const [role, levels] of Object.entries(defaults)) {
result[role] = { ...levels };
}
if (overrides?.qa) {
for (const [level, model] of Object.entries(overrides.qa)) {
if (model) qa[level] = model;
}
}
if (overrides?.architect) {
for (const [level, model] of Object.entries(overrides.architect)) {
if (model) architect[level] = model;
if (overrides) {
for (const [role, roleOverrides] of Object.entries(overrides)) {
if (!result[role]) result[role] = {};
for (const [level, model] of Object.entries(roleOverrides)) {
if (model) result[role][level] = model;
}
}
}
return { dev, qa, architect };
return result;
}

View File

@@ -6,14 +6,14 @@
import { runCommand } from "../run-command.js";
export type ModelAssignment = {
dev: {
developer: {
junior: string;
mid: string;
medior: string;
senior: string;
};
qa: {
tester: {
junior: string;
mid: string;
medior: string;
senior: string;
};
architect: {
@@ -37,8 +37,8 @@ export async function selectModelsWithLLM(
if (availableModels.length === 1) {
const model = availableModels[0].model;
return {
dev: { junior: model, mid: model, senior: model },
qa: { junior: model, mid: model, senior: model },
developer: { junior: model, medior: model, senior: model },
tester: { junior: model, medior: model, senior: model },
architect: { junior: model, senior: model },
};
}
@@ -53,27 +53,27 @@ ${modelList}
All roles use the same level scheme based on task complexity:
- **senior** (most capable): Complex architecture, refactoring, critical decisions
- **mid** (balanced): Features, bug fixes, code review, standard tasks
- **medior** (balanced): Features, bug fixes, code review, standard tasks
- **junior** (fast/efficient): Simple fixes, routine tasks
Rules:
1. Prefer same provider for consistency
2. Assign most capable model to senior
3. Assign mid-tier model to mid
3. Assign mid-tier model to medior
4. Assign fastest/cheapest model to junior
5. Consider model version numbers (higher = newer/better)
6. Stable versions (no date) > snapshot versions (with date like 20250514)
Return ONLY a JSON object in this exact format (no markdown, no explanation):
{
"dev": {
"developer": {
"junior": "provider/model-name",
"mid": "provider/model-name",
"medior": "provider/model-name",
"senior": "provider/model-name"
},
"qa": {
"tester": {
"junior": "provider/model-name",
"mid": "provider/model-name",
"medior": "provider/model-name",
"senior": "provider/model-name"
},
"architect": {
@@ -131,18 +131,18 @@ Return ONLY a JSON object in this exact format (no markdown, no explanation):
// Backfill architect if LLM didn't return it (graceful upgrade)
if (!assignment.architect) {
assignment.architect = {
senior: assignment.dev?.senior ?? availableModels[0].model,
junior: assignment.dev?.mid ?? availableModels[0].model,
senior: assignment.developer?.senior ?? availableModels[0].model,
junior: assignment.developer?.medior ?? availableModels[0].model,
};
}
if (
!assignment.dev?.junior ||
!assignment.dev?.mid ||
!assignment.dev?.senior ||
!assignment.qa?.junior ||
!assignment.qa?.mid ||
!assignment.qa?.senior
!assignment.developer?.junior ||
!assignment.developer?.medior ||
!assignment.developer?.senior ||
!assignment.tester?.junior ||
!assignment.tester?.medior ||
!assignment.tester?.senior
) {
console.error("Invalid assignment structure. Got:", assignment);
throw new Error(`Invalid assignment structure from LLM. Missing fields in: ${JSON.stringify(Object.keys(assignment))}`);

View File

@@ -5,14 +5,14 @@
*/
export type ModelAssignment = {
dev: {
developer: {
junior: string;
mid: string;
medior: string;
senior: string;
};
qa: {
tester: {
junior: string;
mid: string;
medior: string;
senior: string;
};
architect: {
@@ -44,8 +44,8 @@ export async function assignModels(
if (authenticated.length === 1) {
const model = authenticated[0].model;
return {
dev: { junior: model, mid: model, senior: model },
qa: { junior: model, mid: model, senior: model },
developer: { junior: model, medior: model, senior: model },
tester: { junior: model, medior: model, senior: model },
architect: { junior: model, senior: model },
};
}
@@ -66,16 +66,16 @@ export async function assignModels(
*/
export function formatAssignment(assignment: ModelAssignment): string {
const lines = [
"| Role | Level | Model |",
"|------|----------|--------------------------|",
`| DEV | senior | ${assignment.dev.senior.padEnd(24)} |`,
`| DEV | mid | ${assignment.dev.mid.padEnd(24)} |`,
`| DEV | junior | ${assignment.dev.junior.padEnd(24)} |`,
`| QA | senior | ${assignment.qa.senior.padEnd(24)} |`,
`| QA | mid | ${assignment.qa.mid.padEnd(24)} |`,
`| QA | junior | ${assignment.qa.junior.padEnd(24)} |`,
`| ARCH | senior | ${assignment.architect.senior.padEnd(24)} |`,
`| ARCH | junior | ${assignment.architect.junior.padEnd(24)} |`,
"| Role | Level | Model |",
"|-----------|----------|--------------------------|",
`| DEVELOPER | senior | ${assignment.developer.senior.padEnd(24)} |`,
`| DEVELOPER | medior | ${assignment.developer.medior.padEnd(24)} |`,
`| DEVELOPER | junior | ${assignment.developer.junior.padEnd(24)} |`,
`| TESTER | senior | ${assignment.tester.senior.padEnd(24)} |`,
`| TESTER | medior | ${assignment.tester.medior.padEnd(24)} |`,
`| TESTER | junior | ${assignment.tester.junior.padEnd(24)} |`,
`| ARCHITECT | senior | ${assignment.architect.senior.padEnd(24)} |`,
`| ARCHITECT | junior | ${assignment.architect.junior.padEnd(24)} |`,
];
return lines.join("\n");
}