From ca583ba98aaa6c6e19c353d0be085ab0ae0e6115 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 26 Jun 2026 13:38:22 +0000 Subject: [PATCH] fix: recover workbench session switchback hydration --- .../hwlab-node-web-observe-runner-source.ts | 57 +++++++++++++++++-- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/scripts/src/hwlab-node-web-observe-runner-source.ts b/scripts/src/hwlab-node-web-observe-runner-source.ts index c2a746ff..18e7bd92 100644 --- a/scripts/src/hwlab-node-web-observe-runner-source.ts +++ b/scripts/src/hwlab-node-web-observe-runner-source.ts @@ -1748,11 +1748,22 @@ async function switchAwayAndBack(command) { const switchAway = await clickSession(alternateSessionId); const awaySettle = await waitForWorkbenchSessionHydrated(page, alternateSessionId, { timeoutMs: 15000 }); const away = awaySettle.snapshot ?? await workbenchSessionSnapshot(); - const switchBack = await gotoTarget("/workbench/sessions/" + encodeURIComponent(canarySessionId)); - const backSettle = await waitForWorkbenchSessionHydrated(page, canarySessionId, { timeoutMs: 15000 }); - const after = backSettle.snapshot ?? await workbenchSessionSnapshot(); - const routeOk = after?.routeSessionId === canarySessionId; - const activeOk = after?.activeSessionId === canarySessionId; + let switchBack = await gotoTarget("/workbench/sessions/" + encodeURIComponent(canarySessionId)); + let backSettle = await waitForWorkbenchSessionHydrated(page, canarySessionId, { timeoutMs: 15000 }); + let after = backSettle.snapshot ?? await workbenchSessionSnapshot(); + let switchBackRecovery = null; + let routeOk = after?.routeSessionId === canarySessionId; + let activeOk = after?.activeSessionId === canarySessionId; + if (!routeOk || !activeOk) { + switchBackRecovery = await recoverControlPageSessionHydration(canarySessionId, backSettle); + if (switchBackRecovery?.ok === true) { + switchBack = { ...switchBack, recoveryApplied: true, recoveryNavigation: switchBackRecovery.navigation, valuesRedacted: true }; + backSettle = switchBackRecovery.settle; + after = switchBackRecovery.snapshot ?? after; + routeOk = after?.routeSessionId === canarySessionId; + activeOk = after?.activeSessionId === canarySessionId; + } + } if (awaySettle.ok !== true) { const error = new Error("switchAwayAndBack did not settle on the alternate session"); error.details = { canarySessionId, alternateSessionId, routeSessionId: away?.routeSessionId ?? null, activeSessionId: away?.activeSessionId ?? null, settle: awaySettle, pageId, valuesRedacted: true }; @@ -1760,7 +1771,7 @@ async function switchAwayAndBack(command) { } if (!routeOk || !activeOk) { const error = new Error("switchAwayAndBack did not return to the canary session"); - error.details = { canarySessionId, alternateSessionId, routeSessionId: after?.routeSessionId ?? null, activeSessionId: after?.activeSessionId ?? null, settle: backSettle, pageId, valuesRedacted: true }; + error.details = { canarySessionId, alternateSessionId, routeSessionId: after?.routeSessionId ?? null, activeSessionId: after?.activeSessionId ?? null, settle: backSettle, recovery: switchBackRecovery, pageId, valuesRedacted: true }; throw error; } return { @@ -1784,6 +1795,7 @@ async function switchAwayAndBack(command) { }, switchBack, backSettle, + switchBackRecovery, routeSessionId: after?.routeSessionId ?? null, activeSessionId: after?.activeSessionId ?? null, routeOk, @@ -1794,6 +1806,39 @@ async function switchAwayAndBack(command) { }; } +async function recoverControlPageSessionHydration(sessionId, previousSettle) { + const observerHydration = observerPage && !observerPage.isClosed() + ? await waitForWorkbenchSessionHydrated(observerPage, sessionId, { timeoutMs: 5000 }) + : { ok: false, reason: "observer-page-unavailable", valuesRedacted: true }; + if (observerHydration.ok !== true) { + return { ok: false, attempted: false, reason: "observer-session-not-hydrated", previousSettle, observerHydration, valuesRedacted: true }; + } + await recreateControlPageForNavigation("switch-back-hydration-retry", 1); + const navigation = await gotoTarget("/workbench/sessions/" + encodeURIComponent(sessionId)); + const settle = await waitForWorkbenchSessionHydrated(page, sessionId, { timeoutMs: 15000 }); + const snapshot = settle.snapshot ?? await workbenchSessionSnapshot(); + const routeOk = snapshot?.routeSessionId === sessionId; + const activeOk = snapshot?.activeSessionId === sessionId; + return { + ok: settle.ok === true && routeOk && activeOk, + attempted: true, + reason: settle.ok === true ? "control-page-recreated" : settle.reason || "control-session-hydration-retry-failed", + previousSettle, + observerHydration, + navigation, + settle, + snapshot: { + routeSessionId: snapshot?.routeSessionId ?? null, + activeSessionId: snapshot?.activeSessionId ?? null, + composerReady: snapshot?.composerReady === true, + messageCount: snapshot?.messageCount ?? null, + valuesRedacted: true, + }, + pageId, + valuesRedacted: true, + }; +} + async function assertSessionInvariant(command) { const beforeUrl = currentPageUrl(); const snapshot = await workbenchSessionSnapshot();