mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-27 01:41:40 +00:00
fix: ignore moved child rows in subagent announces
This commit is contained in:
@@ -94,6 +94,9 @@ const { subagentRegistryMock } = vi.hoisted(() => ({
|
||||
countActiveDescendantRuns: vi.fn((_sessionKey: string) => 0),
|
||||
countPendingDescendantRuns: vi.fn((_sessionKey: string) => 0),
|
||||
countPendingDescendantRunsExcludingRun: vi.fn((_sessionKey: string, _runId: string) => 0),
|
||||
getLatestSubagentRunByChildSessionKey: vi.fn(
|
||||
(_childSessionKey: string): MockSubagentRun | undefined => undefined,
|
||||
),
|
||||
listSubagentRunsForRequester: vi.fn(
|
||||
(_sessionKey: string, _scope?: { requesterRunId?: string }): MockSubagentRun[] => [],
|
||||
),
|
||||
@@ -290,6 +293,9 @@ describe("subagent announce formatting", () => {
|
||||
.mockImplementation((sessionKey: string, _runId: string) =>
|
||||
subagentRegistryMock.countPendingDescendantRuns(sessionKey),
|
||||
);
|
||||
subagentRegistryMock.getLatestSubagentRunByChildSessionKey
|
||||
.mockClear()
|
||||
.mockReturnValue(undefined);
|
||||
subagentRegistryMock.listSubagentRunsForRequester.mockClear().mockReturnValue([]);
|
||||
subagentRegistryMock.replaceSubagentRunAfterSteer.mockClear().mockReturnValue(true);
|
||||
subagentRegistryMock.resolveRequesterForChildSession.mockClear().mockReturnValue(null);
|
||||
@@ -2321,6 +2327,75 @@ describe("subagent announce formatting", () => {
|
||||
expect(msg.match(/1\. child-a/g)?.length ?? 0).toBe(1);
|
||||
});
|
||||
|
||||
it("does not announce a direct child that moved to a newer parent", async () => {
|
||||
subagentRegistryMock.countPendingDescendantRuns.mockReturnValue(0);
|
||||
subagentRegistryMock.listSubagentRunsForRequester.mockImplementation(
|
||||
(sessionKey: string, scope?: { requesterRunId?: string }) => {
|
||||
if (sessionKey !== "agent:main:subagent:old-parent") {
|
||||
return [];
|
||||
}
|
||||
if (scope?.requesterRunId !== "run-old-parent-settled") {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
runId: "run-child-old-parent",
|
||||
childSessionKey: "agent:main:subagent:shared-child",
|
||||
requesterSessionKey: "agent:main:subagent:old-parent",
|
||||
requesterDisplayKey: "old-parent",
|
||||
task: "shared child task",
|
||||
label: "shared-child",
|
||||
cleanup: "keep",
|
||||
createdAt: 10,
|
||||
endedAt: 20,
|
||||
cleanupCompletedAt: 21,
|
||||
frozenResultText: "stale old parent result",
|
||||
outcome: { status: "ok" },
|
||||
},
|
||||
];
|
||||
},
|
||||
);
|
||||
subagentRegistryMock.getLatestSubagentRunByChildSessionKey.mockImplementation(
|
||||
(childSessionKey: string) => {
|
||||
if (childSessionKey !== "agent:main:subagent:shared-child") {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
runId: "run-child-new-parent",
|
||||
childSessionKey,
|
||||
requesterSessionKey: "agent:main:subagent:new-parent",
|
||||
requesterDisplayKey: "new-parent",
|
||||
task: "shared child task",
|
||||
label: "shared-child",
|
||||
cleanup: "keep",
|
||||
createdAt: 11,
|
||||
endedAt: 22,
|
||||
cleanupCompletedAt: 23,
|
||||
frozenResultText: "current new parent result",
|
||||
outcome: { status: "ok" },
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const didAnnounce = await runSubagentAnnounceFlow({
|
||||
childSessionKey: "agent:main:subagent:old-parent",
|
||||
childRunId: "run-old-parent-settled",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "main",
|
||||
...defaultOutcomeAnnounce,
|
||||
expectsCompletionMessage: true,
|
||||
roundOneReply: "old parent fallback reply",
|
||||
});
|
||||
|
||||
expect(didAnnounce).toBe(true);
|
||||
expect(agentSpy).toHaveBeenCalledTimes(1);
|
||||
const call = agentSpy.mock.calls[0]?.[0] as { params?: { message?: string } };
|
||||
const msg = call?.params?.message ?? "";
|
||||
expect(msg).not.toContain("Child completion results:");
|
||||
expect(msg).not.toContain("stale old parent result");
|
||||
expect(msg).toContain("old parent fallback reply");
|
||||
});
|
||||
|
||||
it("wakes an ended orchestrator run with settled child results before any upward announce", async () => {
|
||||
sessionStore = {
|
||||
"agent:main:subagent:parent": {
|
||||
|
||||
@@ -570,6 +570,43 @@ function dedupeLatestChildCompletionRows(
|
||||
return [...latestByChildSessionKey.values()];
|
||||
}
|
||||
|
||||
function filterCurrentDirectChildCompletionRows(
|
||||
children: Array<{
|
||||
runId: string;
|
||||
childSessionKey: string;
|
||||
requesterSessionKey: string;
|
||||
task: string;
|
||||
label?: string;
|
||||
createdAt: number;
|
||||
endedAt?: number;
|
||||
frozenResultText?: string | null;
|
||||
outcome?: SubagentRunOutcome;
|
||||
}>,
|
||||
params: {
|
||||
requesterSessionKey: string;
|
||||
getLatestSubagentRunByChildSessionKey?: (childSessionKey: string) =>
|
||||
| {
|
||||
runId: string;
|
||||
requesterSessionKey: string;
|
||||
}
|
||||
| null
|
||||
| undefined;
|
||||
},
|
||||
) {
|
||||
if (typeof params.getLatestSubagentRunByChildSessionKey !== "function") {
|
||||
return children;
|
||||
}
|
||||
return children.filter((child) => {
|
||||
const latest = params.getLatestSubagentRunByChildSessionKey?.(child.childSessionKey);
|
||||
if (!latest) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
latest.runId === child.runId && latest.requesterSessionKey === params.requesterSessionKey
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function formatDurationShort(valueMs?: number) {
|
||||
if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) {
|
||||
return "n/a";
|
||||
@@ -1418,7 +1455,13 @@ export async function runSubagentAnnounceFlow(params: {
|
||||
);
|
||||
if (Array.isArray(directChildren) && directChildren.length > 0) {
|
||||
childCompletionFindings = buildChildCompletionFindings(
|
||||
dedupeLatestChildCompletionRows(directChildren),
|
||||
dedupeLatestChildCompletionRows(
|
||||
filterCurrentDirectChildCompletionRows(directChildren, {
|
||||
requesterSessionKey: params.childSessionKey,
|
||||
getLatestSubagentRunByChildSessionKey:
|
||||
subagentRegistryRuntime.getLatestSubagentRunByChildSessionKey,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ export {
|
||||
countActiveDescendantRuns,
|
||||
countPendingDescendantRuns,
|
||||
countPendingDescendantRunsExcludingRun,
|
||||
getLatestSubagentRunByChildSessionKey,
|
||||
isSubagentSessionRunActive,
|
||||
listSubagentRunsForRequester,
|
||||
replaceSubagentRunAfterSteer,
|
||||
|
||||
Reference in New Issue
Block a user