feat: refresh observer page during web-probe observe (#669)

Co-authored-by: Codex <codex@noreply.local>
This commit is contained in:
Lyon
2026-06-22 18:11:54 +08:00
committed by GitHub
parent 25eee7db21
commit c3f2f5f8d7
2 changed files with 30 additions and 7 deletions
@@ -20,6 +20,7 @@ const targetPath = process.env.UNIDESK_WEB_OBSERVE_TARGET_PATH || "/workbench";
const sampleIntervalMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_SAMPLE_INTERVAL_MS, 5000);
const screenshotIntervalMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_SCREENSHOT_INTERVAL_MS, 300000);
const maxSamples = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_MAX_SAMPLES, 0);
const observerRefreshIntervalMs = positiveInteger(process.env.UNIDESK_WEB_OBSERVE_OBSERVER_REFRESH_INTERVAL_MS, 180000);
const viewport = parseViewport(process.env.UNIDESK_WEB_OBSERVE_VIEWPORT || "1440x900");
const playwrightProxy = proxyConfigFromEnv(baseUrl);
const chromiumLaunchOptions = chromiumLaunchOptionsForProxy(playwrightProxy);
@@ -48,6 +49,7 @@ let browser;
let context;
let page;
let observerPage;
let lastObserverRefreshAtMs = Date.now();
let sampleSeq = 0;
let commandSeq = 0;
let artifactSeq = 0;
@@ -119,7 +121,7 @@ async function writeManifest(extra = {}) {
network: publicNetwork(playwrightProxy),
pageAuthority: { browser: "chromium", context: "shared-auth", pageMode: "dual-control-observer", controlPageId: pageId, observerPageId, continuityBreaksRecorded: true },
pageProvenance: compactPageProvenance(currentPageProvenance),
sampling: { mode: "passive", sampleIntervalMs, screenshotIntervalMs, maxSamples, observerInitiatedDefault: false, responseBodyReadDefault: false },
sampling: { mode: "passive", sampleIntervalMs, screenshotIntervalMs, maxSamples, observerRefreshIntervalMs, observerInitiatedDefault: false, responseBodyReadDefault: false },
commandDirs: dirs,
artifacts: files,
safety: { pureClient: true, inboundApi: false, database: false, queueConsumer: false, k8sWorkload: false, valuesRedacted: true, secretValuesPrinted: false },
@@ -141,6 +143,8 @@ async function writeHeartbeat(extra = {}) {
baseUrl,
currentUrl: currentPageUrl(),
observerUrl: pageUrl(observerPage),
observerRefreshIntervalMs,
lastObserverRefreshAt: Number.isFinite(lastObserverRefreshAtMs) ? new Date(lastObserverRefreshAtMs).toISOString() : null,
pageProvenance: compactPageProvenance(currentPageProvenance),
sampleSeq,
commandSeq,
@@ -259,14 +263,15 @@ async function withObserverSync(result, reason) {
return { ...result, observer: await syncObserverPageToControlSession(reason, result?.sessionId ?? null) };
}
async function syncObserverPageToControlSession(reason, explicitSessionId = null) {
async function syncObserverPageToControlSession(reason, explicitSessionId = null, options = {}) {
if (!observerPage || observerPage.isClosed()) return { ok: false, reason, pageRole: "observer", pageId: observerPageId, failureKind: "observer-page-unavailable" };
const forceRefresh = options?.forceRefresh === true;
const snapshot = await workbenchSessionSnapshot();
const sessionId = explicitSessionId || snapshot?.activeSessionId || snapshot?.routeSessionId || routeSessionIdFromUrl(currentPageUrl());
const target = sessionId ? "/workbench/sessions/" + encodeURIComponent(sessionId) : targetPath;
const beforeUrl = pageUrl(observerPage);
const beforeSessionId = routeSessionIdFromUrl(beforeUrl);
if (sessionId && beforeSessionId === sessionId) return { ok: true, reason, changed: false, sessionId, beforeUrl, afterUrl: beforeUrl, pageRole: "observer", pageId: observerPageId };
if (sessionId && beforeSessionId === sessionId && !forceRefresh) return { ok: true, reason, changed: false, observerRoundTrip: false, sessionId, beforeUrl, afterUrl: beforeUrl, pageRole: "observer", pageId: observerPageId };
let status = null;
let statusText = null;
const response = await observerPage.goto(new URL(target, baseUrl).toString(), { waitUntil: "domcontentloaded", timeout: 45000 }).catch((error) => ({ observerGotoError: errorSummary(error) }));
@@ -274,7 +279,24 @@ async function syncObserverPageToControlSession(reason, explicitSessionId = null
status = typeof response?.status === "function" ? response.status() : null;
statusText = typeof response?.statusText === "function" ? response.statusText() : null;
await observerPage.waitForTimeout(1000);
return { ok: true, reason, changed: true, sessionId: sessionId ?? null, targetPath: target, beforeUrl, afterUrl: pageUrl(observerPage), pageRole: "observer", pageId: observerPageId, httpStatus: status, statusText, valuesRedacted: true };
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 };
}
async function maybeRefreshObserverPage(reason) {
if (!observerPage || observerPage.isClosed()) return null;
if (!observerRefreshIntervalMs || observerRefreshIntervalMs <= 0) return null;
if (Date.now() - lastObserverRefreshAtMs < observerRefreshIntervalMs) return null;
const result = await syncObserverPageToControlSession("observer-periodic-refresh", null, { forceRefresh: true });
await appendJsonl(files.control, eventRecord("observer-periodic-refresh", {
pageRole: "observer",
pageId: observerPageId,
reason,
intervalMs: observerRefreshIntervalMs,
result,
valuesRedacted: true
}));
return result;
}
async function runControlCommand(command, fn) {
@@ -918,6 +940,7 @@ async function preflightSummary() {
}
async function samplePage(reason) {
await maybeRefreshObserverPage(reason);
const groupSeq = sampleSeq + 1;
if (page && !page.isClosed()) await sampleOnePage(page, { reason, groupSeq, pageRole: "control", targetPageId: pageId });
if (observerPage && !observerPage.isClosed()) {
@@ -3825,7 +3848,7 @@ function compactManifest(value) {
function compactHeartbeat(value) {
if (!value) return null;
return { jobId: value.jobId, pid: value.pid, status: value.status, sampleSeq: value.sampleSeq, commandSeq: value.commandSeq, pageId: value.pageId ?? null, observerPageId: value.observerPageId ?? null, currentUrl: value.currentUrl, observerUrl: value.observerUrl ?? null, pageProvenance: value.pageProvenance ?? null, updatedAt: value.updatedAt, uptimeMs: value.uptimeMs };
return { jobId: value.jobId, pid: value.pid, status: value.status, sampleSeq: value.sampleSeq, commandSeq: value.commandSeq, pageId: value.pageId ?? null, observerPageId: value.observerPageId ?? null, currentUrl: value.currentUrl, observerUrl: value.observerUrl ?? null, observerRefreshIntervalMs: value.observerRefreshIntervalMs ?? null, lastObserverRefreshAt: value.lastObserverRefreshAt ?? null, pageProvenance: value.pageProvenance ?? null, updatedAt: value.updatedAt, uptimeMs: value.uptimeMs };
}
function renderTurnTimingTable(sampleMetrics) {