test(agents): simplify subagent announce suite imports and call assertions

This commit is contained in:
Peter Steinberger
2026-02-22 09:20:25 +00:00
parent 4520fdda69
commit 703f7213b6

View File

@@ -70,7 +70,7 @@ const defaultOutcomeAnnounce = {
};
async function getSingleAgentCallParams() {
await expect.poll(() => agentSpy.mock.calls.length).toBe(1);
expect(agentSpy).toHaveBeenCalledTimes(1);
const call = agentSpy.mock.calls[0]?.[0] as { params?: Record<string, unknown> };
return call?.params ?? {};
}
@@ -142,8 +142,10 @@ vi.mock("../config/config.js", async (importOriginal) => {
describe("subagent announce formatting", () => {
let previousFastTestEnv: string | undefined;
let runSubagentAnnounceFlow: (typeof import("./subagent-announce.js"))["runSubagentAnnounceFlow"];
beforeAll(() => {
beforeAll(async () => {
({ runSubagentAnnounceFlow } = await import("./subagent-announce.js"));
previousFastTestEnv = process.env.OPENCLAW_TEST_FAST;
});
@@ -188,7 +190,6 @@ describe("subagent announce formatting", () => {
});
it("sends instructional message to main agent with status and findings", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
sessionStore = {
"agent:main:subagent:test": {
sessionId: "child-session-123",
@@ -230,7 +231,6 @@ describe("subagent announce formatting", () => {
});
it("includes success status when outcome is ok", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
// Use waitForCompletion: false so it uses the provided outcome instead of calling agent.wait
await runSubagentAnnounceFlow({
childSessionKey: "agent:main:subagent:test",
@@ -246,7 +246,6 @@ describe("subagent announce formatting", () => {
});
it("uses child-run announce identity for direct idempotency", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
await runSubagentAnnounceFlow({
childSessionKey: "agent:main:subagent:worker",
childRunId: "run-direct-idem",
@@ -267,7 +266,6 @@ describe("subagent announce formatting", () => {
] as const)(
"falls back to latest $role output when assistant reply is empty",
async (testCase) => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
chatHistoryMock.mockResolvedValueOnce({
messages: [
{
@@ -298,7 +296,6 @@ describe("subagent announce formatting", () => {
);
it("uses latest assistant text when it appears after a tool output", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
chatHistoryMock.mockResolvedValueOnce({
messages: [
{
@@ -328,7 +325,6 @@ describe("subagent announce formatting", () => {
});
it("keeps full findings and includes compact stats", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
sessionStore = {
"agent:main:subagent:test": {
sessionId: "child-session-usage",
@@ -365,7 +361,6 @@ describe("subagent announce formatting", () => {
});
it("sends deterministic completion message directly for manual spawn completion", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
sessionStore = {
"agent:main:subagent:test": {
sessionId: "child-session-direct",
@@ -407,7 +402,6 @@ describe("subagent announce formatting", () => {
});
it("keeps completion-mode delivery coordinated when sibling runs are still active", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
sessionStore = {
"agent:main:subagent:test": {
sessionId: "child-session-coordinated",
@@ -448,7 +442,6 @@ describe("subagent announce formatting", () => {
});
it("keeps session-mode completion delivery on the bound destination when sibling runs are active", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
sessionStore = {
"agent:main:subagent:test": {
sessionId: "child-session-bound",
@@ -507,7 +500,6 @@ describe("subagent announce formatting", () => {
});
it("does not duplicate to main channel when two active bound sessions complete from the same requester channel", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
sessionStore = {
"agent:main:subagent:child-a": {
sessionId: "child-session-a",
@@ -598,7 +590,7 @@ describe("subagent announce formatting", () => {
}),
]);
await expect.poll(() => sendSpy.mock.calls.length).toBe(2);
expect(sendSpy).toHaveBeenCalledTimes(2);
expect(agentSpy).not.toHaveBeenCalled();
const directTargets = sendSpy.mock.calls.map(
@@ -611,7 +603,6 @@ describe("subagent announce formatting", () => {
});
it("uses completion direct-send headers for error and timeout outcomes", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
const cases = [
{
childSessionId: "child-session-direct-error",
@@ -674,7 +665,6 @@ describe("subagent announce formatting", () => {
});
it("routes manual completion direct-send using requester thread hints", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
const cases = [
{
childSessionId: "child-session-direct-thread",
@@ -740,7 +730,6 @@ describe("subagent announce formatting", () => {
});
it("uses hook-provided thread target across requester thread variants", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
const cases = [
{
childRunId: "run-direct-thread-bound",
@@ -837,7 +826,6 @@ describe("subagent announce formatting", () => {
},
},
])("keeps requester origin when $name", async ({ childRunId, hookResult }) => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
hasSubagentDeliveryTargetHook = true;
subagentDeliveryTargetHookMock.mockResolvedValueOnce(hookResult);
@@ -865,7 +853,6 @@ describe("subagent announce formatting", () => {
});
it("steers announcements into an active run when queue mode is steer", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(true);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(true);
embeddedRunMock.queueEmbeddedPiMessage.mockReturnValue(true);
@@ -895,7 +882,6 @@ describe("subagent announce formatting", () => {
});
it("queues announce delivery with origin account routing", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(true);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
sessionStore = {
@@ -925,7 +911,6 @@ describe("subagent announce formatting", () => {
});
it("keeps queued idempotency unique for same-ms distinct child runs", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(true);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
sessionStore = {
@@ -969,7 +954,7 @@ describe("subagent announce formatting", () => {
nowSpy.mockRestore();
}
await expect.poll(() => agentSpy.mock.calls.length).toBe(2);
expect(agentSpy).toHaveBeenCalledTimes(2);
const idempotencyKeys = agentSpy.mock.calls
.map((call) => (call[0] as { params?: Record<string, unknown> })?.params?.idempotencyKey)
.filter((value): value is string => typeof value === "string");
@@ -979,7 +964,6 @@ describe("subagent announce formatting", () => {
});
it("prefers direct delivery first for completion-mode and then queues on direct failure", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(true);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
sessionStore = {
@@ -1003,8 +987,8 @@ describe("subagent announce formatting", () => {
});
expect(didAnnounce).toBe(true);
await expect.poll(() => sendSpy.mock.calls.length).toBe(1);
await expect.poll(() => agentSpy.mock.calls.length).toBe(1);
expect(sendSpy).toHaveBeenCalledTimes(1);
expect(agentSpy).toHaveBeenCalledTimes(1);
expect(sendSpy.mock.calls[0]?.[0]).toMatchObject({
method: "send",
params: { sessionKey: "agent:main:main" },
@@ -1020,7 +1004,6 @@ describe("subagent announce formatting", () => {
});
it("returns failure for completion-mode when direct delivery fails and queue fallback is unavailable", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(false);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
sessionStore = {
@@ -1047,7 +1030,6 @@ describe("subagent announce formatting", () => {
});
it("uses assistant output for completion-mode when latest assistant text exists", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
chatHistoryMock.mockResolvedValueOnce({
messages: [
{
@@ -1073,7 +1055,7 @@ describe("subagent announce formatting", () => {
});
expect(didAnnounce).toBe(true);
await expect.poll(() => sendSpy.mock.calls.length).toBe(1);
expect(sendSpy).toHaveBeenCalledTimes(1);
const call = sendSpy.mock.calls[0]?.[0] as { params?: { message?: string } };
const msg = call?.params?.message as string;
expect(msg).toContain("assistant completion text");
@@ -1081,7 +1063,6 @@ describe("subagent announce formatting", () => {
});
it("falls back to latest tool output for completion-mode when assistant output is empty", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
chatHistoryMock.mockResolvedValueOnce({
messages: [
{
@@ -1107,14 +1088,13 @@ describe("subagent announce formatting", () => {
});
expect(didAnnounce).toBe(true);
await expect.poll(() => sendSpy.mock.calls.length).toBe(1);
expect(sendSpy).toHaveBeenCalledTimes(1);
const call = sendSpy.mock.calls[0]?.[0] as { params?: { message?: string } };
const msg = call?.params?.message as string;
expect(msg).toContain("tool output only");
});
it("ignores user text when deriving fallback completion output", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
chatHistoryMock.mockResolvedValueOnce({
messages: [
{
@@ -1136,7 +1116,7 @@ describe("subagent announce formatting", () => {
});
expect(didAnnounce).toBe(true);
await expect.poll(() => sendSpy.mock.calls.length).toBe(1);
expect(sendSpy).toHaveBeenCalledTimes(1);
const call = sendSpy.mock.calls[0]?.[0] as { params?: { message?: string } };
const msg = call?.params?.message as string;
expect(msg).toContain("✅ Subagent main finished");
@@ -1144,7 +1124,6 @@ describe("subagent announce formatting", () => {
});
it("queues announce delivery back into requester subagent session", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(true);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
sessionStore = {
@@ -1166,7 +1145,7 @@ describe("subagent announce formatting", () => {
});
expect(didAnnounce).toBe(true);
await expect.poll(() => agentSpy.mock.calls.length).toBe(1);
expect(agentSpy).toHaveBeenCalledTimes(1);
const call = agentSpy.mock.calls[0]?.[0] as { params?: Record<string, unknown> };
expect(call?.params?.sessionKey).toBe("agent:main:subagent:orchestrator");
@@ -1193,7 +1172,6 @@ describe("subagent announce formatting", () => {
},
},
] as const)("thread routing: $testName", async (testCase) => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(true);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
sessionStore = {
@@ -1224,7 +1202,6 @@ describe("subagent announce formatting", () => {
});
it("splits collect-mode queues when accountId differs", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(true);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
sessionStore = {
@@ -1256,8 +1233,9 @@ describe("subagent announce formatting", () => {
}),
]);
await expect.poll(() => agentSpy.mock.calls.length).toBe(2);
expect(agentSpy).toHaveBeenCalledTimes(2);
await vi.waitFor(() => {
expect(agentSpy).toHaveBeenCalledTimes(2);
});
const accountIds = agentSpy.mock.calls.map(
(call) => (call?.[0] as { params?: { accountId?: string } })?.params?.accountId,
);
@@ -1280,7 +1258,6 @@ describe("subagent announce formatting", () => {
expectedAccountId: "acct-987",
},
] as const)("direct announce: $testName", async (testCase) => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(false);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
@@ -1304,7 +1281,6 @@ describe("subagent announce formatting", () => {
});
it("injects direct announce into requester subagent session instead of chat channel", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(false);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
@@ -1326,7 +1302,6 @@ describe("subagent announce formatting", () => {
});
it("keeps completion-mode announce internal for nested requester subagent sessions", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(false);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
@@ -1354,7 +1329,6 @@ describe("subagent announce formatting", () => {
});
it("retries reading subagent output when early lifecycle completion had no text", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValueOnce(true).mockReturnValue(false);
embeddedRunMock.waitForEmbeddedPiRunEnd.mockResolvedValue(true);
readLatestAssistantReplyMock
@@ -1390,7 +1364,6 @@ describe("subagent announce formatting", () => {
});
it("uses advisory guidance when sibling subagents are still active", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
subagentRegistryMock.countActiveDescendantRuns.mockImplementation((sessionKey: string) =>
sessionKey === "agent:main:main" ? 2 : 0,
);
@@ -1413,7 +1386,6 @@ describe("subagent announce formatting", () => {
});
it("defers announce while finished runs still have active descendants", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
const cases = [
{
childRunId: "run-parent",
@@ -1448,7 +1420,6 @@ describe("subagent announce formatting", () => {
});
it("waits for updated synthesized output before announcing nested subagent completion", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
let historyReads = 0;
chatHistoryMock.mockImplementation(async () => {
historyReads += 1;
@@ -1479,7 +1450,6 @@ describe("subagent announce formatting", () => {
});
it("bubbles child announce to parent requester when requester subagent already ended", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
subagentRegistryMock.isSubagentSessionRunActive.mockReturnValue(false);
subagentRegistryMock.resolveRequesterForChildSession.mockReturnValue({
requesterSessionKey: "agent:main:main",
@@ -1504,7 +1474,6 @@ describe("subagent announce formatting", () => {
});
it("keeps announce retryable when ended requester subagent has no fallback requester", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
subagentRegistryMock.isSubagentSessionRunActive.mockReturnValue(false);
subagentRegistryMock.resolveRequesterForChildSession.mockReturnValue(null);
@@ -1531,7 +1500,6 @@ describe("subagent announce formatting", () => {
});
it("defers announce when child run stays active after settle timeout", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
const cases = [
{
childRunId: "run-child-active",
@@ -1578,7 +1546,6 @@ describe("subagent announce formatting", () => {
});
it("prefers requesterOrigin channel over stale session lastChannel in queued announce", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(true);
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
// Session store has stale whatsapp channel, but the requesterOrigin says bluebubbles.
@@ -1601,7 +1568,7 @@ describe("subagent announce formatting", () => {
});
expect(didAnnounce).toBe(true);
await expect.poll(() => agentSpy.mock.calls.length).toBe(1);
expect(agentSpy).toHaveBeenCalledTimes(1);
const call = agentSpy.mock.calls[0]?.[0] as { params?: Record<string, unknown> };
// The channel should match requesterOrigin, NOT the stale session entry.
@@ -1610,7 +1577,6 @@ describe("subagent announce formatting", () => {
});
it("routes or falls back for ended parent subagent sessions (#18037)", async () => {
const { runSubagentAnnounceFlow } = await import("./subagent-announce.js");
const cases = [
{
name: "routes to parent when parent session still exists",