mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-08 19:12:22 +00:00
fix(copilot): scope pending cleanup to session owner
This commit is contained in:
@@ -714,6 +714,45 @@ describe("createCopilotAgentHarness", () => {
|
||||
expect(sessionStore.store.delete).not.toHaveBeenCalledWith("oc-sess-reuse");
|
||||
});
|
||||
|
||||
it("reuses a replacement session while an older cleanup is pending", async () => {
|
||||
const cleanup = createDeferred<"aborted" | "completed" | "deadline">();
|
||||
let attempt = 0;
|
||||
mocks.runCopilotAttempt.mockImplementation(async (_params, deps) => {
|
||||
attempt += 1;
|
||||
if (attempt === 1) {
|
||||
deps.onSessionEstablished?.({
|
||||
sdkSessionId: "sdk-sess-old",
|
||||
pooledClient: { key: {} as any, client: {} as any },
|
||||
sessionConfig: TEST_SESSION_CONFIG,
|
||||
});
|
||||
deps.onDeferredCompaction?.({
|
||||
abort: () => undefined,
|
||||
cleanup: cleanup.promise,
|
||||
sdkSessionId: "sdk-sess-old",
|
||||
});
|
||||
} else if (attempt === 2) {
|
||||
deps.onSessionEstablished?.({
|
||||
sdkSessionId: "sdk-sess-replacement",
|
||||
pooledClient: { key: {} as any, client: {} as any },
|
||||
sessionConfig: TEST_SESSION_CONFIG,
|
||||
});
|
||||
}
|
||||
return ATTEMPT_RESULT;
|
||||
});
|
||||
const harness = createCopilotAgentHarness({ pool: makePoolMock() });
|
||||
|
||||
await harness.runAttempt(makeAttemptParams({ runId: "t1" }));
|
||||
await harness.runAttempt(makeAttemptParams({ runId: "t2" }));
|
||||
await harness.runAttempt(makeAttemptParams({ runId: "t3" }));
|
||||
|
||||
const thirdCallParams = mocks.runCopilotAttempt.mock.calls[2]?.[0] as {
|
||||
initialReplayState?: { sdkSessionId?: string };
|
||||
};
|
||||
expect(thirdCallParams.initialReplayState?.sdkSessionId).toBe("sdk-sess-replacement");
|
||||
cleanup.resolve("completed");
|
||||
await flushAsyncWork();
|
||||
});
|
||||
|
||||
it("invalidates the retained SDK binding when deferred compaction is cancelled", async () => {
|
||||
const pool = makePoolMock();
|
||||
const sessionStore = makeSessionStoreMock();
|
||||
|
||||
@@ -95,6 +95,10 @@ type LegacyCopilotSessionBinding = {
|
||||
|
||||
type CopilotAttemptSessionBinding = Pick<CopilotSessionBinding, "compatKey" | "sdkSessionId">;
|
||||
type DeferredCompactionCleanupOutcome = "aborted" | "completed" | "deadline";
|
||||
type DeferredCompactionCleanup = {
|
||||
abort: () => void;
|
||||
sdkSessionId: string;
|
||||
};
|
||||
|
||||
type CopilotSessionBindingStore = Pick<
|
||||
PluginStateSyncKeyedStore<CopilotSessionBinding>,
|
||||
@@ -427,7 +431,7 @@ export function createCopilotAgentHarness(
|
||||
const inFlight = new Set<Promise<unknown>>();
|
||||
const deferredCompactionCleanups = new Map<
|
||||
string,
|
||||
Map<Promise<DeferredCompactionCleanupOutcome>, () => void>
|
||||
Map<Promise<DeferredCompactionCleanupOutcome>, DeferredCompactionCleanup>
|
||||
>();
|
||||
// Maps OpenClaw session id (from AgentHarnessAttemptParams.sessionId) to
|
||||
// the SDK session id + client that owns it. Populated by
|
||||
@@ -454,11 +458,12 @@ export function createCopilotAgentHarness(
|
||||
abort: () => void;
|
||||
cleanup: Promise<DeferredCompactionCleanupOutcome>;
|
||||
sessionId: string;
|
||||
sdkSessionId: string;
|
||||
}): void {
|
||||
const cleanups =
|
||||
deferredCompactionCleanups.get(params.sessionId) ??
|
||||
new Map<Promise<DeferredCompactionCleanupOutcome>, () => void>();
|
||||
cleanups.set(params.cleanup, params.abort);
|
||||
new Map<Promise<DeferredCompactionCleanupOutcome>, DeferredCompactionCleanup>();
|
||||
cleanups.set(params.cleanup, { abort: params.abort, sdkSessionId: params.sdkSessionId });
|
||||
deferredCompactionCleanups.set(params.sessionId, cleanups);
|
||||
void params.cleanup.then(
|
||||
() => removeDeferredCompactionCleanup(params.sessionId, params.cleanup),
|
||||
@@ -480,14 +485,28 @@ export function createCopilotAgentHarness(
|
||||
}
|
||||
}
|
||||
|
||||
function hasPendingDeferredCompactionCleanup(sessionId: string): boolean {
|
||||
const cleanups = deferredCompactionCleanups.get(sessionId);
|
||||
if (!cleanups) {
|
||||
return false;
|
||||
}
|
||||
const currentSdkSessionId =
|
||||
trackedSessions.get(sessionId)?.sdkSessionId ??
|
||||
lookupStoredBinding(options?.sessionStore, sessionId)?.sdkSessionId;
|
||||
return (
|
||||
currentSdkSessionId !== undefined &&
|
||||
[...cleanups.values()].some((cleanup) => cleanup.sdkSessionId === currentSdkSessionId)
|
||||
);
|
||||
}
|
||||
|
||||
async function abortDeferredCompactionCleanups(sessionId: string): Promise<void> {
|
||||
const cleanups = deferredCompactionCleanups.get(sessionId);
|
||||
if (!cleanups) {
|
||||
return;
|
||||
}
|
||||
const pending = [...cleanups.entries()];
|
||||
for (const [, abort] of pending) {
|
||||
abort();
|
||||
for (const [, cleanup] of pending) {
|
||||
cleanup.abort();
|
||||
}
|
||||
await Promise.allSettled(pending.map(([cleanup]) => cleanup));
|
||||
}
|
||||
@@ -553,7 +572,7 @@ export function createCopilotAgentHarness(
|
||||
const currentCompatKey = computeSessionCompatKey(params);
|
||||
const currentCompactKey = computeSessionCompactKey(params);
|
||||
const compactionCleanupPending =
|
||||
openclawSessionId !== undefined && deferredCompactionCleanups.has(openclawSessionId);
|
||||
openclawSessionId !== undefined && hasPendingDeferredCompactionCleanup(openclawSessionId);
|
||||
const tracked =
|
||||
openclawSessionId && !compactionCleanupPending
|
||||
? trackedSessions.get(openclawSessionId)
|
||||
@@ -635,6 +654,7 @@ export function createCopilotAgentHarness(
|
||||
abort,
|
||||
cleanup,
|
||||
sessionId: openclawSessionId,
|
||||
sdkSessionId,
|
||||
});
|
||||
// The attempt retains this SDK session until its background
|
||||
// compaction resolves. Preserve its binding for a successful
|
||||
@@ -730,7 +750,7 @@ export function createCopilotAgentHarness(
|
||||
reason: "missing-required-params",
|
||||
};
|
||||
}
|
||||
if (deferredCompactionCleanups.has(openclawSessionId)) {
|
||||
if (hasPendingDeferredCompactionCleanup(openclawSessionId)) {
|
||||
return {
|
||||
ok: false,
|
||||
compacted: false,
|
||||
|
||||
Reference in New Issue
Block a user