mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 09:52:25 +00:00
fix: steer ended subagent orchestrators with live descendants
This commit is contained in:
@@ -643,7 +643,8 @@ export async function steerControlledSubagentRun(params: {
|
||||
error: "Leaf subagents cannot control other sessions.",
|
||||
};
|
||||
}
|
||||
if (params.entry.endedAt) {
|
||||
const targetHasPendingDescendants = countPendingDescendantRuns(params.entry.childSessionKey) > 0;
|
||||
if (params.entry.endedAt && !targetHasPendingDescendants) {
|
||||
return {
|
||||
status: "done",
|
||||
runId: params.entry.runId,
|
||||
@@ -660,7 +661,13 @@ export async function steerControlledSubagentRun(params: {
|
||||
};
|
||||
}
|
||||
const currentEntry = getSubagentRunByChildSessionKey(params.entry.childSessionKey);
|
||||
if (!currentEntry || currentEntry.runId !== params.entry.runId || currentEntry.endedAt) {
|
||||
const currentHasPendingDescendants =
|
||||
currentEntry && countPendingDescendantRuns(currentEntry.childSessionKey) > 0;
|
||||
if (
|
||||
!currentEntry ||
|
||||
currentEntry.runId !== params.entry.runId ||
|
||||
(currentEntry.endedAt && !currentHasPendingDescendants)
|
||||
) {
|
||||
return {
|
||||
status: "done",
|
||||
runId: params.entry.runId,
|
||||
|
||||
@@ -33,9 +33,6 @@ export async function handleSubagentsSendAction(
|
||||
if ("reply" in targetResolution) {
|
||||
return targetResolution.reply;
|
||||
}
|
||||
if (steerRequested && targetResolution.entry.endedAt) {
|
||||
return stopWithText(`${formatRunLabel(targetResolution.entry)} is already finished.`);
|
||||
}
|
||||
|
||||
const controller = resolveCommandSubagentController(params, ctx.requesterKey);
|
||||
|
||||
|
||||
@@ -2186,6 +2186,63 @@ describe("handleCommands subagents", () => {
|
||||
expect(trackedRuns[0].endedAt).toBeUndefined();
|
||||
});
|
||||
|
||||
it("steers ended orchestrators that are still waiting on active descendants", async () => {
|
||||
callGatewayMock.mockImplementation(async (opts: unknown) => {
|
||||
const request = opts as { method?: string };
|
||||
if (request.method === "agent") {
|
||||
return { runId: "run-steer-ended-parent" };
|
||||
}
|
||||
return {};
|
||||
});
|
||||
const parentKey = "agent:main:subagent:orchestrator-ended";
|
||||
const childKey = "agent:main:subagent:orchestrator-ended:subagent:child";
|
||||
const storePath = path.join(testWorkspaceDir, "sessions-subagents-steer-ended-parent.json");
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
store[parentKey] = {
|
||||
sessionId: "ended-parent-session",
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
store[childKey] = {
|
||||
sessionId: "active-child-session",
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
});
|
||||
addSubagentRunForTests({
|
||||
runId: "run-ended-parent",
|
||||
childSessionKey: parentKey,
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "main",
|
||||
task: "orchestrate child workers",
|
||||
cleanup: "keep",
|
||||
createdAt: Date.now() - 120_000,
|
||||
startedAt: Date.now() - 120_000,
|
||||
endedAt: Date.now() - 110_000,
|
||||
outcome: { status: "ok" },
|
||||
});
|
||||
addSubagentRunForTests({
|
||||
runId: "run-active-child",
|
||||
childSessionKey: childKey,
|
||||
requesterSessionKey: parentKey,
|
||||
requesterDisplayKey: "subagent:orchestrator-ended",
|
||||
task: "child worker still running",
|
||||
cleanup: "keep",
|
||||
createdAt: Date.now() - 60_000,
|
||||
startedAt: Date.now() - 60_000,
|
||||
});
|
||||
const cfg = {
|
||||
commands: { text: true },
|
||||
channels: { whatsapp: { allowFrom: ["*"] } },
|
||||
session: { store: storePath },
|
||||
} as OpenClawConfig;
|
||||
const params = buildParams("/steer 1 regroup around the remaining child work", cfg);
|
||||
const result = await handleCommands(params);
|
||||
|
||||
expect(result.shouldContinue).toBe(false);
|
||||
expect(result.reply?.text).toContain("steered");
|
||||
const trackedRuns = listSubagentRunsForRequester("agent:main:main");
|
||||
expect(trackedRuns[0].runId).toBe("run-steer-ended-parent");
|
||||
});
|
||||
|
||||
it("restores announce behavior when /steer replacement dispatch fails", async () => {
|
||||
callGatewayMock.mockImplementation(async (opts: unknown) => {
|
||||
const request = opts as { method?: string };
|
||||
|
||||
Reference in New Issue
Block a user