import { execFile } from 'child_process'; import { promisify } from 'util'; const execFileAsync = promisify(execFile); export interface OpenClawAgentInfo { name: string; isDefault?: boolean; identity?: string; workspace?: string; agentDir?: string; model?: string; routingRules?: number; routing?: string; } export async function listOpenClawAgents(logger?: { debug?: (...args: any[]) => void; warn?: (...args: any[]) => void }): Promise { try { const { stdout } = await execFileAsync('openclaw', ['agents', 'list'], { timeout: 15000, maxBuffer: 1024 * 1024, }); return parseOpenClawAgents(stdout); } catch (err) { logger?.warn?.('Failed to run `openclaw agents list`', err); return []; } } export function parseOpenClawAgents(text: string): OpenClawAgentInfo[] { const lines = text.split(/\r?\n/); const out: OpenClawAgentInfo[] = []; let current: OpenClawAgentInfo | null = null; const push = () => { if (current) out.push(current); current = null; }; for (const raw of lines) { const line = raw.trimEnd(); if (!line.trim() || line.startsWith('Agents:') || line.startsWith('Routing rules map') || line.startsWith('Channel status reflects')) continue; if (line.startsWith('- ')) { push(); const m = line.match(/^-\s+(.+?)(?:\s+\((default)\))?$/); current = { name: m?.[1] || line.slice(2).trim(), isDefault: m?.[2] === 'default', }; continue; } if (!current) continue; const trimmed = line.trim(); const idx = trimmed.indexOf(':'); if (idx === -1) continue; const key = trimmed.slice(0, idx).trim(); const value = trimmed.slice(idx + 1).trim(); switch (key) { case 'Identity': current.identity = value; break; case 'Workspace': current.workspace = value; break; case 'Agent dir': current.agentDir = value; break; case 'Model': current.model = value; break; case 'Routing rules': { const n = Number(value); current.routingRules = Number.isFinite(n) ? n : undefined; break; } case 'Routing': current.routing = value; break; default: break; } } push(); return out; }