feat: implement runCommand wrapper and refactor command executions across modules

This commit is contained in:
Lauren ten Hoor
2026-02-13 10:50:35 +08:00
parent e55b7fa555
commit 83f1f1adf0
24 changed files with 209 additions and 178 deletions

View File

@@ -84,7 +84,7 @@ export function registerHeartbeatService(api: OpenClawPluginApi) {
return;
}
const agents = discoverAgents(ctx.config);
const agents = discoverAgents(api.config);
if (agents.length === 0) {
ctx.logger.warn("work_heartbeat service: no DevClaw agents registered");
return;
@@ -117,20 +117,39 @@ export function registerHeartbeatService(api: OpenClawPluginApi) {
/**
* Discover DevClaw agents by scanning which agent workspaces have projects.
* Self-discovering: any agent whose workspace contains projects/projects.json is processed.
* Also checks the default workspace (agents.defaults.workspace) for projects.
*/
function discoverAgents(config: ServiceContext["config"]): Agent[] {
const agentsList = config.agents?.list || [];
function discoverAgents(config: {
agents?: {
list?: Array<{ id: string; workspace?: string }>;
defaults?: { workspace?: string };
};
}): Agent[] {
const seen = new Set<string>();
const agents: Agent[] = [];
return agentsList
.filter((a): a is { id: string; workspace: string } => {
if (!a.workspace) return false;
try {
return fs.existsSync(path.join(a.workspace, "projects", "projects.json"));
} catch {
return false;
// Check explicit agent list
for (const a of config.agents?.list || []) {
if (!a.workspace) continue;
try {
if (fs.existsSync(path.join(a.workspace, "projects", "projects.json"))) {
agents.push({ agentId: a.id, workspace: a.workspace });
seen.add(a.workspace);
}
})
.map((a) => ({ agentId: a.id, workspace: a.workspace }));
} catch { /* skip */ }
}
// Check default workspace (used when no explicit agents are registered)
const defaultWorkspace = config.agents?.defaults?.workspace;
if (defaultWorkspace && !seen.has(defaultWorkspace)) {
try {
if (fs.existsSync(path.join(defaultWorkspace, "projects", "projects.json"))) {
agents.push({ agentId: "main", workspace: defaultWorkspace });
}
} catch { /* skip */ }
}
return agents;
}
/**
@@ -286,7 +305,7 @@ async function performHealthPass(
groupId: string,
project: any,
): Promise<number> {
const { provider } = createProvider({ repo: project.repo });
const { provider } = await createProvider({ repo: project.repo });
let fixedCount = 0;
for (const role of ["dev", "qa"] as const) {

View File

@@ -3,12 +3,9 @@
*
* Replaces 7 if-blocks with a data-driven lookup table.
*/
import { execFile } from "node:child_process";
import { promisify } from "node:util";
import type { StateLabel, IssueProvider } from "../providers/provider.js";
import { deactivateWorker } from "../projects.js";
const execFileAsync = promisify(execFile);
import { runCommand } from "../run-command.js";
export type CompletionRule = {
from: StateLabel;
@@ -84,7 +81,7 @@ export async function executeCompletion(opts: {
// Git pull (dev:done)
if (rule.gitPull) {
try {
await execFileAsync("git", ["pull"], { cwd: repoPath, timeout: 30_000 });
await runCommand(["git", "pull"], { timeoutMs: 30_000, cwd: repoPath });
} catch { /* best-effort */ }
}

View File

@@ -33,7 +33,7 @@ export function getRoleForLabel(label: QueueLabel): "dev" | "qa" {
// ---------------------------------------------------------------------------
export async function fetchProjectQueues(project: Project): Promise<Record<QueueLabel, Issue[]>> {
const { provider } = createProvider({ repo: project.repo });
const { provider } = await createProvider({ repo: project.repo });
const labels: QueueLabel[] = ["To Improve", "To Test", "To Do"];
const queues: Record<QueueLabel, Issue[]> = { "To Improve": [], "To Test": [], "To Do": [] };

View File

@@ -124,7 +124,7 @@ export async function projectTick(opts: {
const project = (await readProjects(workspaceDir)).projects[groupId];
if (!project) return { pickups: [], skipped: [{ reason: `Project not found: ${groupId}` }] };
const provider = opts.provider ?? createProvider({ repo: project.repo }).provider;
const provider = opts.provider ?? (await createProvider({ repo: project.repo })).provider;
const roleExecution = project.roleExecution ?? "parallel";
const roles: Array<"dev" | "qa"> = targetRole ? [targetRole] : ["dev", "qa"];