fix: align web probe fresh session by session route (#1497)

This commit is contained in:
Lyon
2026-06-18 17:33:00 +08:00
committed by GitHub
parent 9849f7da76
commit c1fa33bf35
+58 -20
View File
@@ -649,7 +649,7 @@ async function createFreshSession(page, args, actions) {
const alignment = await waitForFreshSessionAlignment(page, args, before, timeoutMs);
const after = alignment.after;
actions.push({ action: "fresh-session", selector, settled: Boolean(settled), aligned: alignment.ok, timeoutMs, shellReady, before, after, alignment });
if (!settled || !alignment.ok) throw new Error(`fresh session did not settle: ${JSON.stringify({ settled: Boolean(settled), alignment, timeoutMs })}`);
if (!alignment.ok) throw new Error(`fresh session did not settle: ${JSON.stringify({ settled: Boolean(settled), alignment, timeoutMs })}`);
return true;
}
@@ -661,16 +661,16 @@ async function waitForFreshSessionAlignment(page, args, before, timeoutMs) {
while (Date.now() - startedAt < timeoutMs) {
const check = freshSessionAlignment(before, after);
bestCandidate = bestCandidate ?? freshSessionCandidate(before, after);
attempts.push({ elapsedMs: Date.now() - startedAt, ok: check.ok, reason: check.reason, conversationId: check.conversationId, routeConversationId: check.routeConversationId, selectedConversationId: check.selectedConversationId, apiConversationPresent: check.apiConversationPresent });
attempts.push(freshSessionAttemptSummary(check, Date.now() - startedAt));
if (check.ok) return { ok: true, reason: null, attempts, after, check };
await page.waitForTimeout(500);
after = await collectSessionState(page, args);
}
const check = freshSessionAlignment(before, after);
bestCandidate = bestCandidate ?? freshSessionCandidate(before, after);
attempts.push({ elapsedMs: Date.now() - startedAt, ok: check.ok, reason: check.reason, conversationId: check.conversationId, routeConversationId: check.routeConversationId, selectedConversationId: check.selectedConversationId, apiConversationPresent: check.apiConversationPresent });
if (!check.ok && bestCandidate?.conversationId) {
const repair = await realignFreshSession(page, args, bestCandidate.conversationId);
attempts.push(freshSessionAttemptSummary(check, Date.now() - startedAt));
if (!check.ok && bestCandidate?.id) {
const repair = await realignFreshSession(page, args, bestCandidate.id);
after = await collectSessionState(page, args);
const repaired = freshSessionAlignment(before, after);
return { ok: repaired.ok, reason: repaired.reason, attempts, after, check: repaired, repair };
@@ -678,19 +678,39 @@ async function waitForFreshSessionAlignment(page, args, before, timeoutMs) {
return { ok: false, reason: check.reason, attempts, after, check };
}
function freshSessionCandidate(before, after) {
const previousConversationId = firstNonEmpty(before?.routeConversationId, before?.api?.workspace?.selectedConversationId, before?.activeTab?.conversationId);
const items = Array.isArray(after?.api?.conversations?.items) ? after.api.conversations.items : [];
return items.find((item) => item.conversationId && item.conversationId !== previousConversationId && Number(item.messagesCount ?? 0) === 0) ?? null;
function freshSessionAttemptSummary(check, elapsedMs) {
return {
elapsedMs,
ok: check.ok,
reason: check.reason,
id: check.id,
routeId: check.routeId,
selectedConversationId: check.selectedConversationId,
activeTabSessionId: check.activeTabSessionId,
activeTabConversationId: check.activeTabConversationId,
apiConversationPresent: check.apiConversationPresent,
workspaceApiAvailable: check.workspaceApiAvailable,
conversationsApiAvailable: check.conversationsApiAvailable
};
}
async function realignFreshSession(page, args, conversationId) {
const selected = await selectConversationViaApi(page, args, conversationId, "web-live-dom-probe-fresh-realign");
const target = new URL(`/workbench/sessions/${encodeURIComponent(conversationId)}`, args.url).toString();
function freshSessionCandidate(before, after) {
const previousConversationId = sessionAuthorityId(before);
const items = Array.isArray(after?.api?.conversations?.items) ? after.api.conversations.items : [];
const apiCandidate = items.find((item) => item.conversationId && item.conversationId !== previousConversationId && Number(item.messagesCount ?? 0) === 0);
if (apiCandidate) return { ...apiCandidate, id: apiCandidate.conversationId, source: "api-conversations" };
const tabs = Array.isArray(after?.sessionTabs) ? after.sessionTabs : [];
const tabCandidate = tabs.find((item) => firstNonEmpty(item.sessionId, item.conversationId) && firstNonEmpty(item.sessionId, item.conversationId) !== previousConversationId && item.running !== "true");
return tabCandidate ? { ...tabCandidate, id: firstNonEmpty(tabCandidate.sessionId, tabCandidate.conversationId), source: "session-tab" } : null;
}
async function realignFreshSession(page, args, sessionOrConversationId) {
const selected = await selectConversationViaApi(page, args, sessionOrConversationId, "web-live-dom-probe-fresh-realign").catch((error) => ({ ok: false, phase: "select-conversation", error: error instanceof Error ? error.message : String(error) }));
const target = new URL(`/workbench/sessions/${encodeURIComponent(sessionOrConversationId)}`, args.url).toString();
await page.goto(target, { waitUntil: "domcontentloaded", timeout: args.timeoutMs });
await page.locator("#workspace").waitFor({ state: "visible", timeout: args.timeoutMs });
await page.locator("#command-input").waitFor({ state: "visible", timeout: args.timeoutMs });
return { action: "fresh-session-realign", conversationId, target, selected };
return { action: "fresh-session-realign", id: sessionOrConversationId, target, selected };
}
async function selectConversationViaApi(page, args, conversationId, updatedByClient) {
@@ -719,15 +739,19 @@ async function selectConversationViaApi(page, args, conversationId, updatedByCli
}
function freshSessionAlignment(before, after) {
const previousConversationId = firstNonEmpty(before?.routeConversationId, before?.api?.workspace?.selectedConversationId, before?.activeTab?.conversationId);
const conversationId = firstNonEmpty(after?.routeConversationId, after?.api?.workspace?.selectedConversationId, after?.activeTab?.conversationId);
const previousConversationId = sessionAuthorityId(before);
const conversationId = sessionAuthorityId(after);
const selectedConversationId = after?.api?.workspace?.selectedConversationId ?? null;
const routeConversationId = after?.routeConversationId ?? null;
const apiConversation = conversationId ? after?.api?.conversations?.items?.find((item) => item.conversationId === conversationId) ?? null : null;
const activeTabSessionId = after?.activeTab?.sessionId ?? null;
const activeTabConversationId = after?.activeTab?.conversationId ?? null;
const workspaceApiAvailable = after?.api?.workspace?.ok === true;
const conversationsApiAvailable = after?.api?.conversations?.ok === true;
const apiConversation = conversationId ? after?.api?.conversations?.items?.find((item) => item.conversationId === conversationId || item.sessionId === conversationId) ?? null : null;
const routeMatches = Boolean(routeConversationId && conversationId && routeConversationId === conversationId);
const workspaceMatches = Boolean(selectedConversationId && conversationId && selectedConversationId === conversationId);
const apiConversationPresent = Boolean(apiConversation);
const activeTabMatches = !after?.activeTab?.conversationId || after.activeTab.conversationId === conversationId;
const workspaceMatches = workspaceApiAvailable ? Boolean(selectedConversationId && conversationId && selectedConversationId === conversationId) : true;
const apiConversationPresent = conversationsApiAvailable ? Boolean(apiConversation) : true;
const activeTabMatches = Boolean(!activeTabSessionId && !activeTabConversationId) || activeTabSessionId === conversationId || activeTabConversationId === conversationId;
const changed = Boolean(conversationId && conversationId !== previousConversationId);
const commandReady = after?.command?.inputDisabled === false;
const empty = after?.messageCount === 0;
@@ -740,7 +764,11 @@ function freshSessionAlignment(before, after) {
: !activeTabMatches ? "fresh-session-active-tab-not-aligned"
: !commandReady ? "fresh-session-command-not-ready"
: "fresh-session-not-empty";
return { ok, reason, previousConversationId, conversationId, routeConversationId, selectedConversationId, apiConversationPresent, activeTabConversationId: after?.activeTab?.conversationId ?? null, commandReady, command: after?.command ?? null, messageCount: after?.messageCount ?? null };
return { ok, reason, previousConversationId, conversationId, id: conversationId, routeId: routeConversationId, routeConversationId, selectedConversationId, workspaceApiAvailable, conversationsApiAvailable, apiConversationPresent, activeTabConversationId, activeTabSessionId, commandReady, command: after?.command ?? null, messageCount: after?.messageCount ?? null };
}
function sessionAuthorityId(state) {
return firstNonEmpty(state?.routeConversationId, state?.api?.workspace?.selectedConversationId, state?.activeTab?.conversationId, state?.activeTab?.sessionId);
}
async function waitForSessionShellReady(page, args, actions) {
@@ -763,6 +791,14 @@ async function waitForSessionShellReady(page, args, actions) {
async function collectSessionState(page, args = null) {
const dom = await page.evaluate(() => {
const activeTab = document.querySelector(".session-tab[data-active='true'], .session-tab[aria-selected='true']");
const sessionTabs = [...document.querySelectorAll(".session-tab")].map((tab) => ({
title: tab.querySelector("strong")?.textContent?.trim() ?? tab.getAttribute("title") ?? null,
conversationId: tab.getAttribute("data-conversation-id") ?? null,
sessionId: tab.getAttribute("data-session-id") ?? null,
status: tab.getAttribute("data-status") ?? null,
running: tab.getAttribute("data-running") ?? null,
active: tab.getAttribute("data-active") === "true" || tab.getAttribute("aria-selected") === "true"
}));
const routeMatch = window.location.pathname.match(/\/workbench\/sessions\/([^/]+)/u) ?? window.location.pathname.match(/\/workspace\/sessions\/([^/]+)/u);
return {
finalUrl: window.location.href,
@@ -778,6 +814,7 @@ async function collectSessionState(page, args = null) {
running: activeTab.getAttribute("data-running") ?? null
} : null,
tabCount: document.querySelectorAll(".session-tab").length,
sessionTabs: sessionTabs.slice(0, 20),
messageCount: document.querySelectorAll(".message-card").length,
emptyVisible: Boolean(document.querySelector("[data-conversation-empty]")),
command: {
@@ -1088,6 +1125,7 @@ function compactSessionState(state) {
selectedConversationId: state.api?.workspace?.selectedConversationId ?? null,
revision: state.api?.workspace?.revision ?? null,
activeTab: state.activeTab ?? null,
sessionTabs: Array.isArray(state.sessionTabs) ? state.sessionTabs.slice(0, 6) : [],
messageCount: state.messageCount ?? null,
command: state.command ?? null,
conversationsCount: state.api?.conversations?.count ?? null