fix(coding-agent/task): plugged wall-clock timer holes and propagated context stats
- Added defensive abort re-check after registering the abortSignal listener plus a checkAbort() immediately before await session.prompt(...), so a wall-clock timer that fires during pre-prompt setup is no longer lost between listener registration and the prompt call. - Late yield events arriving after a wall-clock timeout no longer flip the result to success: a runtimeLimitExceeded flag derived from the internal abortReason forces wasAborted=true and exitCode=1 regardless of hasYield, while yield payloads remain captured in extractedToolData. - Async task progress now copies contextTokens and contextWindow from the completed SingleResult onto AgentProgress, so backgrounded tasks still surface their context gauge to the UI.
This commit is contained in:
@@ -1162,6 +1162,12 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
},
|
||||
{ once: true, signal: sessionAbortController.signal },
|
||||
);
|
||||
// Defensive: if the wall-clock timer (or external signal) fired during
|
||||
// the awaited setup above, the listener registration races the dispatch
|
||||
// and may not observe the already-fired abort event. Mirror it manually.
|
||||
if (abortSignal.aborted) {
|
||||
void session.abort();
|
||||
}
|
||||
|
||||
const extensionRunner = session.extensionRunner;
|
||||
if (extensionRunner) {
|
||||
@@ -1231,6 +1237,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
}
|
||||
});
|
||||
|
||||
checkAbort();
|
||||
await session.prompt(task, { attribution: "agent" });
|
||||
await session.waitForIdle();
|
||||
|
||||
@@ -1378,12 +1385,23 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
}
|
||||
}
|
||||
|
||||
// Update final progress
|
||||
const wasAborted = abortedViaYield || (!hasYield && (done.aborted || signal?.aborted || false));
|
||||
// Update final progress. A wall-clock timeout always wins: if the runtime
|
||||
// limit fired we report aborted/failed regardless of whether a yield landed
|
||||
// while we were tearing the session down. The yield data is still surfaced
|
||||
// to the caller via `progress.extractedToolData`, but the exit status must
|
||||
// reflect the timeout so on-call doesn't mistake a stuck run for success.
|
||||
const runtimeLimitExceeded = abortReason === "timeout";
|
||||
if (runtimeLimitExceeded && exitCode === 0) {
|
||||
exitCode = 1;
|
||||
}
|
||||
const wasAborted =
|
||||
runtimeLimitExceeded || abortedViaYield || (!hasYield && (done.aborted || signal?.aborted || false));
|
||||
const finalAbortReason = wasAborted
|
||||
? abortedViaYield
|
||||
? yieldAbortReason
|
||||
: (done.abortReason ?? (signal?.aborted ? resolveSignalAbortReason() : resolveAbortReasonText()))
|
||||
? runtimeLimitExceeded
|
||||
? resolveAbortReasonText()
|
||||
: abortedViaYield
|
||||
? yieldAbortReason
|
||||
: (done.abortReason ?? (signal?.aborted ? resolveSignalAbortReason() : resolveAbortReasonText()))
|
||||
: undefined;
|
||||
progress.status = wasAborted ? "aborted" : exitCode === 0 ? "completed" : "failed";
|
||||
scheduleProgress(true);
|
||||
|
||||
Reference in New Issue
Block a user