fix(FN-5256): await disposal across task:moved-away and task:deleted

Close the last fire-and-forget gap from the previous fixes: the task:moved
(away from in-progress) and task:deleted listeners no longer call the
synchronous fire-and-forget `abortInFlightTaskWork`. Instead they track an
awaited disposal promise per task in `pendingTaskDisposals`. The task:moved
(to in-progress) dispatch path awaits any in-flight disposal for the same
task before calling `execute()`, so a fast bounce (in-progress → todo →
in-progress) no longer races the conflict-cleanup path against a still-live
shell.

`awaitAbortInFlightTaskWork` now claims each session surface (activeSessions,
activeStepExecutors, activeWorkflowStepSessions, activeSubagentSessions)
synchronously before awaiting any async abort. This lets concurrent disposal
calls for the same task dedupe naturally — the second call finds the maps
empty and no-ops, preserving the existing single-abort/single-dispose
contract that the soft-delete and user-cancel tests assert.

Adds a regression test in executor-user-cancel covering the re-dispatch
ordering: an immediate task:moved-to-in-progress that follows a still-running
task:moved-away must wait for abort to complete before execute() runs.

The legacy `abortInFlightTaskWork` is removed (no callers).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-05-21 10:14:38 -07:00
parent b4fc54bb20
commit 7d25d98b2f
5 changed files with 151 additions and 102 deletions

View File

@@ -194,7 +194,7 @@ describe("reliability interactions: soft-delete in-flight abort", () => {
});
await store.deleteTask("FN-EXEC");
await Promise.resolve();
await (executor as any).pendingTaskDisposals.get("FN-EXEC");
expect(abort).toHaveBeenCalledTimes(1);
expect(dispose).toHaveBeenCalledTimes(1);
@@ -241,7 +241,7 @@ describe("reliability interactions: soft-delete in-flight abort", () => {
await store.moveTask("FN-RACE", "todo");
await store.deleteTask("FN-RACE");
await Promise.resolve();
await (executor as any).pendingTaskDisposals.get("FN-RACE");
expect(abort).toHaveBeenCalledTimes(1);
expect(dispose).toHaveBeenCalledTimes(1);