openclaw/apps/web/lib/subagent-registry.ts
kumarabhirup 8838cc4e16
feat(chat): add runs API, subagent registry, and cascade stop
Backend support for multi-session chat: /api/chat/runs endpoint for parent/subagent run status, subagent registry for reading run state, and cascade stop to abort child sessions when stopping a parent.
2026-03-15 00:30:43 -07:00

55 lines
1.5 KiB
TypeScript

import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { resolveOpenClawStateDir } from "./workspace";
export type SubagentRegistryEntry = {
runId: string;
childSessionKey: string;
requesterSessionKey: string;
task: string;
label?: string;
createdAt?: number;
endedAt?: number;
outcome?: { status: string; error?: string };
};
export function readSubagentRegistry(): SubagentRegistryEntry[] {
const registryPath = join(resolveOpenClawStateDir(), "subagents", "runs.json");
if (!existsSync(registryPath)) {
return [];
}
try {
const raw = JSON.parse(readFileSync(registryPath, "utf-8")) as {
runs?: Record<string, SubagentRegistryEntry>;
};
return Object.values(raw.runs ?? {});
} catch {
return [];
}
}
export function resolveSubagentStatus(
entry: SubagentRegistryEntry,
): "running" | "completed" | "error" {
if (typeof entry.endedAt !== "number") {
return "running";
}
if (entry.outcome?.status === "error") {
return "error";
}
return "completed";
}
export function listSubagentsForRequesterSession(
requesterSessionKey: string,
): Array<SubagentRegistryEntry & { status: "running" | "completed" | "error" }> {
return readSubagentRegistry()
.filter((entry) => entry.requesterSessionKey === requesterSessionKey)
.map((entry) => ({
...entry,
status: resolveSubagentStatus(entry),
}))
.toSorted((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0));
}