Files
devclaw-gitea/lib/setup/index.ts
Lauren ten Hoor 0e24a68882 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.
2026-02-15 18:32:10 +08:00

134 lines
4.6 KiB
TypeScript

/**
* setup/index.ts — DevClaw setup orchestrator.
*
* Coordinates: agent creation → model config → workspace scaffolding.
* Used by both the `setup` tool and the `openclaw devclaw setup` CLI command.
*/
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
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 = Record<string, Record<string, string>>;
export type SetupOpts = {
/** OpenClaw plugin API for config access. */
api: OpenClawPluginApi;
/** Create a new agent with this name. Mutually exclusive with agentId. */
newAgentName?: string;
/** Channel binding for new agent. Only used when newAgentName is set. */
channelBinding?: "telegram" | "whatsapp" | null;
/** Migrate channel binding from this agent ID. Only used when newAgentName and channelBinding are set. */
migrateFrom?: string;
/** Use an existing agent by ID. Mutually exclusive with newAgentName. */
agentId?: string;
/** Override workspace path (auto-detected from agent if not given). */
workspacePath?: string;
/** Model overrides per role.level. Missing levels use defaults. */
models?: Record<string, Partial<Record<string, string>>>;
/** Plugin-level project execution mode: parallel or sequential. Default: parallel. */
projectExecution?: "parallel" | "sequential";
};
export type SetupResult = {
agentId: string;
agentCreated: boolean;
workspacePath: string;
models: ModelConfig;
filesWritten: string[];
warnings: string[];
bindingMigrated?: {
from: string;
channel: "telegram" | "whatsapp";
};
};
/**
* Run the full DevClaw setup.
*
* 1. Create agent (optional) or resolve existing workspace
* 2. Merge model config and write to openclaw.json
* 3. Write workspace files (AGENTS.md, HEARTBEAT.md, roles, memory)
*/
export async function runSetup(opts: SetupOpts): Promise<SetupResult> {
const warnings: string[] = [];
const { agentId, workspacePath, agentCreated, bindingMigrated } =
await resolveOrCreateAgent(opts, warnings);
const models = buildModelConfig(opts.models);
await writePluginConfig(opts.api, models, agentId, opts.projectExecution);
const filesWritten = await scaffoldWorkspace(workspacePath);
return { agentId, agentCreated, workspacePath, models, filesWritten, warnings, bindingMigrated };
}
// ---------------------------------------------------------------------------
// Private helpers
// ---------------------------------------------------------------------------
async function resolveOrCreateAgent(
opts: SetupOpts,
warnings: string[],
): Promise<{
agentId: string;
workspacePath: string;
agentCreated: boolean;
bindingMigrated?: SetupResult["bindingMigrated"];
}> {
if (opts.newAgentName) {
const { agentId, workspacePath } = await createAgent(opts.api, opts.newAgentName, opts.channelBinding);
const bindingMigrated = await tryMigrateBinding(opts, agentId, warnings);
return { agentId, workspacePath, agentCreated: true, bindingMigrated };
}
if (opts.agentId) {
const workspacePath = opts.workspacePath ?? resolveWorkspacePath(opts.api, opts.agentId);
return { agentId: opts.agentId, workspacePath, agentCreated: false };
}
if (opts.workspacePath) {
return { agentId: "unknown", workspacePath: opts.workspacePath, agentCreated: false };
}
throw new Error("Setup requires either newAgentName, agentId, or workspacePath");
}
async function tryMigrateBinding(
opts: SetupOpts,
agentId: string,
warnings: string[],
): Promise<SetupResult["bindingMigrated"]> {
if (!opts.migrateFrom || !opts.channelBinding) return undefined;
try {
await migrateChannelBinding(opts.api, opts.channelBinding, opts.migrateFrom, agentId);
return { from: opts.migrateFrom, channel: opts.channelBinding };
} catch (err) {
warnings.push(`Failed to migrate binding from "${opts.migrateFrom}": ${(err as Error).message}`);
return undefined;
}
}
function buildModelConfig(overrides?: SetupOpts["models"]): ModelConfig {
const defaults = getAllDefaultModels();
const result: ModelConfig = {};
for (const [role, levels] of Object.entries(defaults)) {
result[role] = { ...levels };
}
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 result;
}