fix(agents): avoid synthetic tool-result writes on idle-timeout cleanup

This commit is contained in:
Vignesh Natarajan
2026-03-05 19:29:18 -08:00
parent 81b93b9ce0
commit 4daaea1190
8 changed files with 71 additions and 7 deletions

View File

@@ -817,6 +817,7 @@ export async function compactEmbeddedPiSessionDirect(
await flushPendingToolResultsAfterIdle({
agent: session?.agent,
sessionManager,
clearPendingOnTimeout: true,
});
session.dispose();
}

View File

@@ -1338,6 +1338,7 @@ export async function runEmbeddedAttempt(
await flushPendingToolResultsAfterIdle({
agent: activeSession?.agent,
sessionManager,
clearPendingOnTimeout: true,
});
activeSession.dispose();
throw err;
@@ -1904,6 +1905,7 @@ export async function runEmbeddedAttempt(
await flushPendingToolResultsAfterIdle({
agent: session?.agent,
sessionManager,
clearPendingOnTimeout: true,
});
session?.dispose();
releaseWsSession(params.sessionId);

View File

@@ -4,6 +4,7 @@ type IdleAwareAgent = {
type ToolResultFlushManager = {
flushPendingToolResults?: (() => void) | undefined;
clearPendingToolResults?: (() => void) | undefined;
};
export const DEFAULT_WAIT_FOR_IDLE_TIMEOUT_MS = 30_000;
@@ -11,23 +12,27 @@ export const DEFAULT_WAIT_FOR_IDLE_TIMEOUT_MS = 30_000;
async function waitForAgentIdleBestEffort(
agent: IdleAwareAgent | null | undefined,
timeoutMs: number,
): Promise<void> {
): Promise<boolean> {
const waitForIdle = agent?.waitForIdle;
if (typeof waitForIdle !== "function") {
return;
return false;
}
const idleResolved = Symbol("idle");
const idleTimedOut = Symbol("timeout");
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
try {
await Promise.race([
waitForIdle.call(agent),
new Promise<void>((resolve) => {
timeoutHandle = setTimeout(resolve, timeoutMs);
const outcome = await Promise.race([
waitForIdle.call(agent).then(() => idleResolved),
new Promise<symbol>((resolve) => {
timeoutHandle = setTimeout(() => resolve(idleTimedOut), timeoutMs);
timeoutHandle.unref?.();
}),
]);
return outcome === idleTimedOut;
} catch {
// Best-effort during cleanup.
return false;
} finally {
if (timeoutHandle) {
clearTimeout(timeoutHandle);
@@ -39,7 +44,15 @@ export async function flushPendingToolResultsAfterIdle(opts: {
agent: IdleAwareAgent | null | undefined;
sessionManager: ToolResultFlushManager | null | undefined;
timeoutMs?: number;
clearPendingOnTimeout?: boolean;
}): Promise<void> {
await waitForAgentIdleBestEffort(opts.agent, opts.timeoutMs ?? DEFAULT_WAIT_FOR_IDLE_TIMEOUT_MS);
const timedOut = await waitForAgentIdleBestEffort(
opts.agent,
opts.timeoutMs ?? DEFAULT_WAIT_FOR_IDLE_TIMEOUT_MS,
);
if (timedOut && opts.clearPendingOnTimeout && opts.sessionManager?.clearPendingToolResults) {
opts.sessionManager.clearPendingToolResults();
return;
}
opts.sessionManager?.flushPendingToolResults?.();
}