fix(web-probe): wait observer refresh hydration (#680)
Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
@@ -86,7 +86,7 @@ export function hwlabNodeWebProbeHelp(): Record<string, unknown> {
|
||||
actions: {
|
||||
run: "Run the repo-owned scripts/web-live-dom-probe.mjs helper.",
|
||||
script: "Run caller-provided Playwright JS after CLI-managed /auth/login; the script receives authenticated browser/context/page plus gotoStable/reloadStable/gotoCurrentStable/safeReload/fetchJson/safeFetchJson/fetchApiMatrix/recordStep/collectText/safeEvaluate/waitWorkbenchReady/screenshotOnError/summarizeWorkspace/summarizeConversation helpers and must not handle secrets itself.",
|
||||
observe: "Start, inspect, control, stop, collect, and analyze a pure-client long-running Workbench observer on the target host. The observer has one Playwright page authority, receives commands through stateDir/commands files, writes JSONL artifacts, and does not expose any inbound service API.",
|
||||
observe: "Start, inspect, control, stop, collect, and analyze a pure-client long-running Workbench observer on the target host. The observer runs a control page plus a passive observer page in a shared-auth browser context, receives commands through stateDir/commands files, writes JSONL artifacts, and does not expose any inbound service API.",
|
||||
},
|
||||
notes: [
|
||||
"Prefer --script-file for reusable probes; stdin heredocs remain supported for one-off probes.",
|
||||
|
||||
@@ -278,9 +278,25 @@ async function syncObserverPageToControlSession(reason, explicitSessionId = null
|
||||
if (response?.observerGotoError) return { ok: false, reason, changed: false, sessionId: sessionId ?? null, targetPath: target, beforeUrl, afterUrl: pageUrl(observerPage), pageRole: "observer", pageId: observerPageId, error: response.observerGotoError, valuesRedacted: true };
|
||||
status = typeof response?.status === "function" ? response.status() : null;
|
||||
statusText = typeof response?.statusText === "function" ? response.statusText() : null;
|
||||
await observerPage.waitForTimeout(1000);
|
||||
const hydration = await waitForWorkbenchSessionHydrated(observerPage, sessionId, { timeoutMs: 15000 });
|
||||
lastObserverRefreshAtMs = Date.now();
|
||||
return { ok: true, reason, changed: true, observerRoundTrip: forceRefresh, sessionId: sessionId ?? null, targetPath: target, beforeUrl, afterUrl: pageUrl(observerPage), pageRole: "observer", pageId: observerPageId, httpStatus: status, statusText, valuesRedacted: true };
|
||||
return { ok: true, reason, changed: true, observerRoundTrip: forceRefresh, sessionId: sessionId ?? null, targetPath: target, beforeUrl, afterUrl: pageUrl(observerPage), pageRole: "observer", pageId: observerPageId, httpStatus: status, statusText, hydration, valuesRedacted: true };
|
||||
}
|
||||
|
||||
async function waitForWorkbenchSessionHydrated(targetPage, sessionId, options = {}) {
|
||||
const timeoutMs = Number.isFinite(Number(options.timeoutMs)) ? Number(options.timeoutMs) : 15000;
|
||||
const started = Date.now();
|
||||
const deadline = started + Math.max(1, timeoutMs);
|
||||
let last = null;
|
||||
while (Date.now() <= deadline) {
|
||||
last = await workbenchSessionSnapshot(targetPage);
|
||||
const expected = String(sessionId || "").trim();
|
||||
const routeOk = !expected || last?.routeSessionId === expected;
|
||||
const activeOk = !expected || last?.activeSessionId === expected;
|
||||
if (routeOk && activeOk) return { ok: true, durationMs: Date.now() - started, snapshot: last, valuesRedacted: true };
|
||||
await targetPage.waitForTimeout(250).catch(() => {});
|
||||
}
|
||||
return { ok: false, durationMs: Date.now() - started, snapshot: last, reason: "observer-session-hydration-timeout", expectedSessionId: sessionId || null, valuesRedacted: true };
|
||||
}
|
||||
|
||||
async function maybeRefreshObserverPage(reason) {
|
||||
@@ -696,12 +712,17 @@ async function createSessionFromUi() {
|
||||
};
|
||||
}
|
||||
|
||||
async function workbenchSessionSnapshot() {
|
||||
return page.evaluate(() => {
|
||||
async function workbenchSessionSnapshot(targetPage = page) {
|
||||
return targetPage.evaluate(() => {
|
||||
const activeTab = document.querySelector(".session-tab[data-active='true'], .session-tab[aria-selected='true']");
|
||||
const routeMatch = window.location.pathname.match(/\/workbench\/sessions\/([^/]+)/u) || window.location.pathname.match(/\/workspace\/sessions\/([^/]+)/u);
|
||||
const input = document.querySelector("#command-input");
|
||||
const warning = document.querySelector(".composer-warning")?.textContent?.trim() || null;
|
||||
const visible = (element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = window.getComputedStyle(element);
|
||||
return rect.width > 0 && rect.height > 0 && style.visibility !== "hidden" && style.display !== "none";
|
||||
};
|
||||
return {
|
||||
url: window.location.href,
|
||||
routeSessionId: routeMatch ? decodeURIComponent(routeMatch[1] || "") : null,
|
||||
@@ -709,6 +730,9 @@ async function workbenchSessionSnapshot() {
|
||||
activeConversationId: activeTab?.getAttribute("data-conversation-id") || null,
|
||||
activeStatus: activeTab?.getAttribute("data-status") || null,
|
||||
tabCount: document.querySelectorAll(".session-tab").length,
|
||||
messageCount: Array.from(document.querySelectorAll('[data-testid*="message" i], [class*="message" i], article, [role="article"]')).filter(visible).length,
|
||||
traceRowCount: Array.from(document.querySelectorAll('[data-testid*="trace" i], [class*="trace" i], [data-trace-id], [data-testid*="event" i]')).filter(visible).length,
|
||||
loadingCount: Array.from(document.querySelectorAll('[aria-busy="true"], [data-loading="true"], [class*="loading" i], [data-testid*="loading" i]')).filter(visible).length,
|
||||
composerReady: Boolean(activeTab && input && !input.disabled && !warning),
|
||||
warning
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user