openclaw/src/agents/pi-embedded-runner/sessions-yield.orchestration.test.ts
2026-03-18 01:06:43 +00:00

93 lines
3.2 KiB
TypeScript

/**
* Integration test proving that sessions_yield produces a clean end_turn exit
* with no pending tool calls, so the parent session is idle when subagent
* results arrive.
*/
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
import { makeAttemptResult } from "./run.overflow-compaction.fixture.js";
import {
loadRunOverflowCompactionHarness,
mockedGlobalHookRunner,
mockedRunEmbeddedAttempt,
overflowBaseRunParams,
} from "./run.overflow-compaction.harness.js";
import { isEmbeddedPiRunActive, queueEmbeddedPiMessage } from "./runs.js";
let runEmbeddedPiAgent: typeof import("./run.js").runEmbeddedPiAgent;
describe("sessions_yield orchestration", () => {
beforeAll(async () => {
({ runEmbeddedPiAgent } = await loadRunOverflowCompactionHarness());
});
beforeEach(() => {
mockedRunEmbeddedAttempt.mockReset();
mockedGlobalHookRunner.hasHooks.mockImplementation(() => false);
});
it("parent session is idle after yield — end_turn, no pendingToolCalls", async () => {
const sessionId = "yield-parent-session";
// Simulate an attempt where sessions_yield was called
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({
promptError: null,
sessionIdUsed: sessionId,
yieldDetected: true,
}),
);
const result = await runEmbeddedPiAgent({
...overflowBaseRunParams,
sessionId,
runId: "run-yield-orchestration",
});
// 1. Run completed with end_turn (yield causes clean exit)
expect(result.meta.stopReason).toBe("end_turn");
// 2. No pending tool calls (yield is NOT a client tool call)
expect(result.meta.pendingToolCalls).toBeUndefined();
// 3. Parent session is IDLE (not in ACTIVE_EMBEDDED_RUNS)
expect(isEmbeddedPiRunActive(sessionId)).toBe(false);
// 4. Steer would fail (message delivery must take direct path, not steer)
expect(queueEmbeddedPiMessage(sessionId, "subagent result")).toBe(false);
});
it("clientToolCall takes precedence over yieldDetected", async () => {
// Edge case: both flags set (shouldn't happen, but clientToolCall wins)
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
makeAttemptResult({
promptError: null,
yieldDetected: true,
clientToolCall: { name: "hosted_tool", params: { arg: "value" } },
}),
);
const result = await runEmbeddedPiAgent({
...overflowBaseRunParams,
runId: "run-yield-vs-client-tool",
});
// clientToolCall wins — tool_calls stopReason, pendingToolCalls populated
expect(result.meta.stopReason).toBe("tool_calls");
expect(result.meta.pendingToolCalls).toHaveLength(1);
expect(result.meta.pendingToolCalls![0].name).toBe("hosted_tool");
});
it("normal attempt without yield has no stopReason override", async () => {
mockedRunEmbeddedAttempt.mockResolvedValueOnce(makeAttemptResult({ promptError: null }));
const result = await runEmbeddedPiAgent({
...overflowBaseRunParams,
runId: "run-no-yield",
});
// Neither clientToolCall nor yieldDetected → stopReason is undefined
expect(result.meta.stopReason).toBeUndefined();
expect(result.meta.pendingToolCalls).toBeUndefined();
});
});