- Updated WorkerState type to use 'level' instead of 'tier'. - Modified functions related to worker state management, including parseWorkerState, emptyWorkerState, getSessionForLevel, activateWorker, and deactivateWorker to reflect the new terminology. - Adjusted health check logic to utilize 'level' instead of 'tier'. - Refactored tick and setup tools to accommodate the change from 'tier' to 'level', including model configuration and workspace scaffolding. - Updated tests to ensure consistency with the new 'level' terminology. - Revised documentation and comments to reflect the changes in terminology from 'tier' to 'level'.
129 lines
4.4 KiB
TypeScript
129 lines
4.4 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 { DEFAULT_MODELS } from "../tiers.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> };
|
|
|
|
export type SetupOpts = {
|
|
/** 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?: { dev?: Partial<Record<string, string>>; qa?: 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(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.newAgentName, opts.channelBinding);
|
|
const bindingMigrated = await tryMigrateBinding(opts, agentId, warnings);
|
|
return { agentId, workspacePath, agentCreated: true, bindingMigrated };
|
|
}
|
|
|
|
if (opts.agentId) {
|
|
const workspacePath = opts.workspacePath ?? await resolveWorkspacePath(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.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 dev: Record<string, string> = { ...DEFAULT_MODELS.dev };
|
|
const qa: Record<string, string> = { ...DEFAULT_MODELS.qa };
|
|
|
|
if (overrides?.dev) {
|
|
for (const [level, model] of Object.entries(overrides.dev)) {
|
|
if (model) dev[level] = model;
|
|
}
|
|
}
|
|
if (overrides?.qa) {
|
|
for (const [level, model] of Object.entries(overrides.qa)) {
|
|
if (model) qa[level] = model;
|
|
}
|
|
}
|
|
|
|
return { dev, qa };
|
|
}
|