fix(clawsweeper): address review for automerge-openclaw-openclaw-82891 (1)

This commit is contained in:
clawsweeper
2026-05-17 11:28:33 +00:00
parent 4513cae5e7
commit fd2fe46e98
2 changed files with 55 additions and 0 deletions

View File

@@ -96,6 +96,26 @@ function expectFields(actual: Record<string, unknown>, expected: Record<string,
}
}
function trackSessionWriteLocks(): string[] {
const events: string[] = [];
hoisted.acquireSessionWriteLockMock.mockImplementation(async () => {
const lockId = hoisted.acquireSessionWriteLockMock.mock.calls.length;
events.push(`acquire-${lockId}`);
return {
release: async () => {
events.push(`release-${lockId}`);
},
};
});
return events;
}
function expectInitialLockReleasedBeforePostTurnWrite(events: string[]) {
expect(events.indexOf("release-1")).toBeGreaterThan(events.indexOf("acquire-1"));
expect(events.indexOf("acquire-2")).toBeGreaterThan(events.indexOf("release-1"));
expect(events.indexOf("release-2")).toBeGreaterThan(events.indexOf("acquire-2"));
}
function createTestContextEngine(params: Partial<AttemptContextEngine>): AttemptContextEngine {
return {
info: {
@@ -773,6 +793,7 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
});
it("skips blank visible prompts with replay history before provider submission", async () => {
const lockEvents = trackSessionWriteLocks();
const sessionPrompt = vi.fn(async () => {
throw new Error("blank prompt should not be submitted");
});
@@ -809,6 +830,35 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
"prompt skipped event",
);
expect(requireRecord(skipped.data, "prompt skipped data").reason).toBe("blank_user_prompt");
expectInitialLockReleasedBeforePostTurnWrite(lockEvents);
});
it("releases the initial session lock before before_agent_run block finalizers", async () => {
const lockEvents = trackSessionWriteLocks();
const sessionPrompt = vi.fn(async () => {
throw new Error("blocked prompt should not be submitted");
});
const runBeforeAgentRun = vi.fn(async () => ({
pluginId: "test-policy",
decision: { outcome: "block", reason: "Blocked by test policy." },
}));
hoisted.getGlobalHookRunnerMock.mockReturnValue({
hasHooks: vi.fn((name: string) => name === "before_agent_run"),
runBeforeAgentRun,
});
const result = await createContextEngineAttemptRunner({
contextEngine: createContextEngineBootstrapAndAssemble(),
sessionKey,
tempPaths,
sessionPrompt,
});
expect(runBeforeAgentRun).toHaveBeenCalledTimes(1);
expect(sessionPrompt).not.toHaveBeenCalled();
expect(result.finalPromptText).toBeUndefined();
expect(result.promptErrorSource).toBe("hook:before_agent_run");
expectInitialLockReleasedBeforePostTurnWrite(lockEvents);
});
it("uses assembled context as the default precheck authority", async () => {
@@ -846,6 +896,7 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
});
it("honors context engines that opt into preassembly overflow authority", async () => {
const lockEvents = trackSessionWriteLocks();
let sawPrompt = false;
const hugeHistory = "large raw history ".repeat(2_000);
@@ -878,6 +929,7 @@ describe("runEmbeddedAttempt context engine sessionKey forwarding", () => {
expect(result.promptErrorSource).toBe("precheck");
expect(result.preflightRecovery?.route).toBe("compact_only");
expect(hoisted.preemptiveCompactionCalls.at(-1)).toHaveProperty("unwindowedMessages");
expectInitialLockReleasedBeforePostTurnWrite(lockEvents);
});
it("snapshots pre-assembly messages before assemble even when the engine windows in place", async () => {

View File

@@ -4099,6 +4099,9 @@ export async function runEmbeddedAttempt(
});
}
await sessionLockController.waitForSessionEvents(activeSession);
await sessionLockController.releaseForPrompt();
// Capture snapshot before compaction wait so we have complete messages if timeout occurs
// Check compaction state before and after to avoid race condition where compaction starts during capture
// Use session state (not subscription) for snapshot decisions - need instantaneous compaction status