fix: update web probe workbench session endpoints
This commit is contained in:
+96
-123
@@ -721,7 +721,7 @@ function freshSessionCandidate(before, after) {
|
||||
}
|
||||
|
||||
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 selected = await selectSessionViaRoute(page, args, sessionOrConversationId, "web-live-dom-probe-fresh-realign").catch((error) => ({ ok: false, phase: "route-session", error: error instanceof Error ? error.message : String(error) }));
|
||||
const targetUrl = new URL(`/workbench/sessions/${encodeURIComponent(sessionOrConversationId)}`, args.url);
|
||||
targetUrl.searchParams.set("projectId", args.projectId);
|
||||
const target = targetUrl.toString();
|
||||
@@ -760,29 +760,24 @@ async function waitForWorkbenchSessionRouteReady(page, args, timeoutMs) {
|
||||
return { ok: workspace.ok && commandInput.ok, timeoutMs, workspace, commandInput, dom };
|
||||
}
|
||||
|
||||
async function selectConversationViaApi(page, args, conversationId, updatedByClient) {
|
||||
return page.evaluate(async ({ projectId, conversationId, updatedByClient }) => {
|
||||
const workspaceResponse = await fetch(`/v1/workbench/workspace?projectId=${encodeURIComponent(projectId)}`, { credentials: "same-origin" });
|
||||
const workspaceBody = await workspaceResponse.json().catch(() => null);
|
||||
const workspaceId = workspaceBody?.workspace?.workspaceId;
|
||||
if (!workspaceId) return { ok: false, phase: "workspace", status: workspaceResponse.status, workspaceId: null, error: workspaceBody?.error ?? null };
|
||||
const selectResponse = await fetch(`/v1/workbench/workspace/${encodeURIComponent(workspaceId)}/select-conversation`, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ projectId, conversationId, updatedByClient })
|
||||
});
|
||||
const selectBody = await selectResponse.json().catch(() => null);
|
||||
return {
|
||||
ok: selectResponse.ok,
|
||||
phase: "select-conversation",
|
||||
status: selectResponse.status,
|
||||
workspaceId,
|
||||
conversationId: selectBody?.workspace?.selectedConversationId ?? conversationId,
|
||||
revision: selectBody?.workspace?.revision ?? null,
|
||||
error: selectBody?.error ?? null
|
||||
};
|
||||
}, { projectId: args.projectId, conversationId, updatedByClient });
|
||||
async function selectSessionViaRoute(page, args, conversationId, updatedByClient) {
|
||||
const targetUrl = new URL(`/workbench/sessions/${encodeURIComponent(conversationId)}`, args.url);
|
||||
const target = targetUrl.toString();
|
||||
const navigation = await page.goto(target, { waitUntil: "domcontentloaded", timeout: Math.min(args.timeoutMs, 30000) })
|
||||
.then((response) => ({ ok: true, status: response?.status() ?? null, url: page.url() }))
|
||||
.catch((error) => ({ ok: false, status: null, error: error instanceof Error ? error.message : String(error), url: page.url() }));
|
||||
const ready = await waitForWorkbenchSessionRouteReady(page, args, Math.min(args.timeoutMs, 30000));
|
||||
return {
|
||||
ok: navigation.ok && ready.ok,
|
||||
phase: "route-session",
|
||||
status: navigation.status,
|
||||
sessionId: conversationId,
|
||||
conversationId,
|
||||
updatedByClient,
|
||||
target,
|
||||
navigation,
|
||||
ready
|
||||
};
|
||||
}
|
||||
|
||||
function freshSessionAlignment(before, after) {
|
||||
@@ -855,7 +850,7 @@ async function waitForSessionShellReady(page, args, actions) {
|
||||
const workspace = document.querySelector("#workspace");
|
||||
const create = document.querySelector("#session-create");
|
||||
const input = document.querySelector("#command-input");
|
||||
return Boolean(workspace && create && !create.disabled && input && !input.disabled && statusText && !/加载中/u.test(statusText));
|
||||
return Boolean(workspace && create && !create.disabled && input && !input.disabled && !/加载中|等待 workspace/u.test(statusText));
|
||||
}, null, { timeout: timeoutMs }).catch(() => null);
|
||||
const after = await collectSessionState(page, args);
|
||||
const result = { action: "session-shell-ready", ready: Boolean(ready), timeoutMs, before, after };
|
||||
@@ -902,13 +897,15 @@ async function collectSessionState(page, args = null) {
|
||||
};
|
||||
});
|
||||
if (!args) return dom;
|
||||
const workspace = await fetchJsonFromPage(page, `/v1/workbench/workspace?projectId=${encodeURIComponent(args.projectId)}`, { timeoutMs: 8000 });
|
||||
const conversations = await fetchJsonFromPage(page, `/v1/agent/conversations?projectId=${encodeURIComponent(args.projectId)}`, { timeoutMs: 8000 });
|
||||
const currentSessionId = firstNonEmpty(dom.routeConversationId, dom.activeTab?.sessionId, dom.activeTab?.conversationId);
|
||||
const sessions = await fetchJsonFromPage(page, "/v1/workbench/sessions?limit=50", { timeoutMs: 8000 });
|
||||
const sessionDetail = currentSessionId ? await fetchJsonFromPage(page, `/v1/workbench/sessions/${encodeURIComponent(currentSessionId)}`, { timeoutMs: 8000 }) : null;
|
||||
const sessionMessages = currentSessionId ? await fetchJsonFromPage(page, `/v1/workbench/sessions/${encodeURIComponent(currentSessionId)}/messages`, { timeoutMs: 8000 }) : null;
|
||||
return {
|
||||
...dom,
|
||||
api: {
|
||||
workspace: summarizeWorkspaceFetch(workspace),
|
||||
conversations: summarizeConversationsFetch(conversations)
|
||||
workspace: summarizeCurrentSessionFetch(currentSessionId, sessionDetail, sessionMessages),
|
||||
conversations: summarizeWorkbenchSessionsFetch(sessions)
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -961,52 +958,65 @@ async function fetchJsonFromPage(page, requestPath, options = {}) {
|
||||
}, { requestPath, timeoutMs });
|
||||
}
|
||||
|
||||
function summarizeWorkspaceFetch(fetchResult) {
|
||||
const workspace = fetchResult?.json?.workspace ?? fetchResult?.json?.data?.workspace ?? null;
|
||||
const selected = workspace?.selectedConversation ?? null;
|
||||
function summarizeCurrentSessionFetch(sessionId, detailFetch, messagesFetch) {
|
||||
const detailBody = detailFetch?.json?.session ?? detailFetch?.json?.data?.session ?? detailFetch?.json?.data ?? detailFetch?.json ?? null;
|
||||
const messages = messagesFromFetch(messagesFetch);
|
||||
const latestMessage = messages.at(-1) ?? null;
|
||||
return {
|
||||
ok: fetchResult?.ok === true,
|
||||
path: fetchResult?.path ?? null,
|
||||
status: fetchResult?.status ?? null,
|
||||
error: fetchResult?.error ?? null,
|
||||
bodyPreview: fetchResult?.ok === true ? null : fetchResult?.bodyPreview ?? null,
|
||||
workspaceId: workspace?.workspaceId ?? null,
|
||||
projectId: normalizeProbeProjectId(workspace?.projectId) ?? null,
|
||||
revision: workspace?.revision ?? null,
|
||||
selectedConversationId: workspace?.selectedConversationId ?? selected?.conversationId ?? null,
|
||||
selectedAgentSessionId: workspace?.selectedAgentSessionId ?? selected?.sessionId ?? null,
|
||||
activeTraceId: workspace?.activeTraceId ?? selected?.lastTraceId ?? null,
|
||||
selectedConversationStatus: selected?.status ?? workspace?.sessionStatus ?? null,
|
||||
selectedMessagesCount: Array.isArray(selected?.messages) ? selected.messages.length : selected?.messagesCount ?? null
|
||||
ok: detailFetch?.ok === true || messagesFetch?.ok === true,
|
||||
path: detailFetch?.path ?? messagesFetch?.path ?? null,
|
||||
status: detailFetch?.status ?? messagesFetch?.status ?? null,
|
||||
error: detailFetch?.error ?? messagesFetch?.error ?? null,
|
||||
bodyPreview: detailFetch?.ok === true || messagesFetch?.ok === true ? null : detailFetch?.bodyPreview ?? messagesFetch?.bodyPreview ?? null,
|
||||
workspaceId: null,
|
||||
projectId: normalizeProbeProjectId(detailBody?.projectId) ?? null,
|
||||
revision: detailBody?.revision ?? detailBody?.updatedAt ?? null,
|
||||
selectedConversationId: firstNonEmpty(detailBody?.sessionId, detailBody?.id, sessionId),
|
||||
selectedAgentSessionId: firstNonEmpty(detailBody?.sessionId, detailBody?.id, sessionId),
|
||||
activeTraceId: firstNonEmpty(detailBody?.lastTraceId, detailBody?.traceId, latestMessage?.traceId, latestMessage?.runnerTrace?.traceId),
|
||||
selectedConversationStatus: detailBody?.status ?? detailBody?.state ?? latestMessage?.status ?? null,
|
||||
selectedMessagesCount: messages.length
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeConversationsFetch(fetchResult) {
|
||||
const conversations = Array.isArray(fetchResult?.json?.conversations)
|
||||
? fetchResult.json.conversations
|
||||
: Array.isArray(fetchResult?.json?.data?.conversations)
|
||||
? fetchResult.json.data.conversations
|
||||
: [];
|
||||
function summarizeWorkbenchSessionsFetch(fetchResult) {
|
||||
const sessions = sessionsFromFetch(fetchResult);
|
||||
return {
|
||||
ok: fetchResult?.ok === true,
|
||||
path: fetchResult?.path ?? null,
|
||||
status: fetchResult?.status ?? null,
|
||||
error: fetchResult?.error ?? null,
|
||||
bodyPreview: fetchResult?.ok === true ? null : fetchResult?.bodyPreview ?? null,
|
||||
count: conversations.length,
|
||||
items: conversations.slice(0, 16).map((item) => ({
|
||||
conversationId: item?.conversationId ?? null,
|
||||
count: sessions.length,
|
||||
items: sessions.slice(0, 16).map((item) => ({
|
||||
conversationId: firstNonEmpty(item?.sessionId, item?.id),
|
||||
projectId: normalizeProbeProjectId(item?.projectId) ?? null,
|
||||
sessionId: item?.sessionId ?? null,
|
||||
sessionId: firstNonEmpty(item?.sessionId, item?.id),
|
||||
threadId: item?.threadId ?? null,
|
||||
status: item?.status ?? null,
|
||||
messagesCount: Array.isArray(item?.messages) ? item.messages.length : item?.messagesCount ?? null,
|
||||
lastTraceId: item?.lastTraceId ?? null,
|
||||
messagesCount: Array.isArray(item?.messages) ? item.messages.length : item?.messagesCount ?? item?.messageCount ?? null,
|
||||
lastTraceId: firstNonEmpty(item?.lastTraceId, item?.traceId),
|
||||
updatedAt: item?.updatedAt ?? null
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
function sessionsFromFetch(fetchResult) {
|
||||
if (Array.isArray(fetchResult?.json?.sessions)) return fetchResult.json.sessions;
|
||||
if (Array.isArray(fetchResult?.json?.data?.sessions)) return fetchResult.json.data.sessions;
|
||||
if (Array.isArray(fetchResult?.json?.items)) return fetchResult.json.items;
|
||||
if (Array.isArray(fetchResult?.json?.data?.items)) return fetchResult.json.data.items;
|
||||
return [];
|
||||
}
|
||||
|
||||
function messagesFromFetch(fetchResult) {
|
||||
if (Array.isArray(fetchResult?.json?.messages)) return fetchResult.json.messages;
|
||||
if (Array.isArray(fetchResult?.json?.data?.messages)) return fetchResult.json.data.messages;
|
||||
if (Array.isArray(fetchResult?.json?.items)) return fetchResult.json.items;
|
||||
if (Array.isArray(fetchResult?.json?.data?.items)) return fetchResult.json.data.items;
|
||||
return [];
|
||||
}
|
||||
|
||||
async function submitPrompt(page, args, actions) {
|
||||
const readyBefore = await waitForCommandReady(page, args, actions);
|
||||
const fresh = [...actions].reverse().find((item) => item.action === "fresh-session") ?? null;
|
||||
@@ -1280,7 +1290,7 @@ function classifyRunProbeError(error, context = {}) {
|
||||
const login = failureDom.login && typeof failureDom.login === "object" ? failureDom.login : {};
|
||||
if (/\/login(?:\?|$)/u.test(finalUrl) || failureDom.authState === "login" || login.cardVisible === true) return "auth-redirect-login";
|
||||
if (/session_required|session-not-selected/u.test(message)) return "session-not-selected";
|
||||
if (/pre-submit-realign|session-(?:route|workspace|active-tab)-not-aligned|select-conversation/u.test(message)) return "session-not-selected";
|
||||
if (/pre-submit-realign|session-(?:route|workspace|active-tab)-not-aligned|route-session/u.test(message)) return "session-not-selected";
|
||||
if (/composer|command-send|command bar|disabledReason|button_disabled/u.test(message)) return "composer-disabled";
|
||||
if (/fresh session/u.test(message)) return "fresh-session-not-aligned";
|
||||
if (/agent final response did not reach terminal DOM state/u.test(message)) return "agent-terminal-timeout";
|
||||
@@ -1341,66 +1351,21 @@ async function collectCommandState(page) {
|
||||
}
|
||||
|
||||
async function selectConversationInBrowserSession(page, args, actions) {
|
||||
const result = await page.evaluate(async ({ projectId, conversationId }) => {
|
||||
const summarizeAuthSession = (body) => ({
|
||||
const authSession = await page.evaluate(async () => {
|
||||
const response = await fetch("/auth/session", { credentials: "same-origin" });
|
||||
const body = await response.json().catch(() => null);
|
||||
return {
|
||||
status: response.status,
|
||||
authenticated: body?.authenticated ?? null,
|
||||
userId: body?.user?.id ?? body?.actor?.id ?? null,
|
||||
username: body?.user?.username ?? body?.actor?.username ?? null,
|
||||
role: body?.user?.role ?? body?.actor?.role ?? null,
|
||||
status: body?.user?.status ?? body?.actor?.status ?? null
|
||||
});
|
||||
const summarizeWorkspace = (workspace) => workspace ? {
|
||||
workspaceId: workspace.workspaceId ?? null,
|
||||
ownerUserId: workspace.ownerUserId ?? null,
|
||||
projectId: workspace.projectId ?? null,
|
||||
selectedConversationId: workspace.selectedConversationId ?? null,
|
||||
selectedAgentSessionId: workspace.selectedAgentSessionId ?? null,
|
||||
selectedConversationStatus: workspace.selectedConversation?.status ?? null,
|
||||
selectedMessagesCount: workspace.selectedConversation?.messages?.length ?? workspace.selectedConversation?.messagesCount ?? null,
|
||||
revision: workspace.revision ?? null
|
||||
} : null;
|
||||
const sessionResponse = await fetch("/auth/session", { credentials: "same-origin" });
|
||||
const sessionBody = await sessionResponse.json().catch(() => null);
|
||||
const workspaceResponse = await fetch(`/v1/workbench/workspace?projectId=${encodeURIComponent(projectId)}`, { credentials: "same-origin" });
|
||||
const workspaceBody = await workspaceResponse.json().catch(() => null);
|
||||
const workspaceId = workspaceBody?.workspace?.workspaceId;
|
||||
if (!workspaceId) return {
|
||||
ok: false,
|
||||
phase: "workspace",
|
||||
status: workspaceResponse.status,
|
||||
workspaceId: null,
|
||||
authSession: summarizeAuthSession(sessionBody),
|
||||
workspace: summarizeWorkspace(workspaceBody?.workspace),
|
||||
bodyStatus: workspaceBody?.status ?? null,
|
||||
error: workspaceBody?.error ?? null
|
||||
accountStatus: body?.user?.status ?? body?.actor?.status ?? null
|
||||
};
|
||||
const selectResponse = await fetch(`/v1/workbench/workspace/${encodeURIComponent(workspaceId)}/select-conversation`, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ projectId, conversationId, updatedByClient: "web-live-dom-probe" })
|
||||
});
|
||||
const selectBody = await selectResponse.json().catch(() => null);
|
||||
return {
|
||||
ok: selectResponse.ok,
|
||||
phase: "select-conversation",
|
||||
status: selectResponse.status,
|
||||
workspaceId,
|
||||
authSession: summarizeAuthSession(sessionBody),
|
||||
workspace: summarizeWorkspace(workspaceBody?.workspace),
|
||||
responseStatus: selectBody?.status ?? null,
|
||||
error: selectBody?.error ?? null,
|
||||
conversationId: selectBody?.workspace?.selectedConversationId ?? conversationId,
|
||||
selectedMessageCount: selectBody?.workspace?.selectedConversation?.messages?.length ?? selectBody?.workspace?.selectedConversation?.messagesCount ?? null,
|
||||
sessionStatus: selectBody?.workspace?.selectedConversation?.status ?? selectBody?.workspace?.sessionStatus ?? null
|
||||
};
|
||||
}, { projectId: args.projectId, conversationId: args.conversationId });
|
||||
actions.push({ action: "select-conversation", ...result });
|
||||
if (!result.ok) throw new Error(`browser session select-conversation failed: ${JSON.stringify(result)}`);
|
||||
await page.reload({ waitUntil: "domcontentloaded", timeout: args.timeoutMs });
|
||||
await waitForAuthBootstrap(page, args, actions);
|
||||
await page.locator("#workspace").waitFor({ state: "visible", timeout: args.timeoutMs });
|
||||
await page.locator("#command-input").waitFor({ state: "visible", timeout: args.timeoutMs });
|
||||
});
|
||||
const result = await selectSessionViaRoute(page, args, args.conversationId, "web-live-dom-probe");
|
||||
actions.push({ action: "select-session", authSession, ...result });
|
||||
if (!result.ok) throw new Error(`browser session route selection failed: ${JSON.stringify(result)}`);
|
||||
}
|
||||
|
||||
async function waitForMessageCards(page, args, actions) {
|
||||
@@ -1471,19 +1436,27 @@ async function collectDomEvidence(page, args) {
|
||||
const workspace = document.querySelector("#workspace");
|
||||
const list = document.querySelector("#conversation-list");
|
||||
const cards = [...document.querySelectorAll(".message-card")];
|
||||
const conversationResponse = await fetch(`/v1/agent/conversations?projectId=${encodeURIComponent(projectId)}`, { credentials: "same-origin" }).catch(() => null);
|
||||
const conversationBody = conversationResponse ? await conversationResponse.json().catch(() => null) : null;
|
||||
const conversations = Array.isArray(conversationBody?.conversations) ? conversationBody.conversations : [];
|
||||
const sessionsResponse = await fetch("/v1/workbench/sessions?limit=50", { credentials: "same-origin" }).catch(() => null);
|
||||
const sessionsBody = sessionsResponse ? await sessionsResponse.json().catch(() => null) : null;
|
||||
const sessions = Array.isArray(sessionsBody?.sessions)
|
||||
? sessionsBody.sessions
|
||||
: Array.isArray(sessionsBody?.data?.sessions)
|
||||
? sessionsBody.data.sessions
|
||||
: Array.isArray(sessionsBody?.items)
|
||||
? sessionsBody.items
|
||||
: Array.isArray(sessionsBody?.data?.items)
|
||||
? sessionsBody.data.items
|
||||
: [];
|
||||
const sessionTabs = [...document.querySelectorAll("#session-tabs .session-tab")];
|
||||
const activeSessionTab = sessionTabs.find((item) => item.getAttribute("data-active") === "true") ?? null;
|
||||
const routeMatch = window.location.pathname.match(/\/workbench\/sessions\/([^/]+)/u) ?? window.location.pathname.match(/\/workspace\/sessions\/([^/]+)/u);
|
||||
const sessionIds = conversations.map((item) => item?.sessionId).filter((value) => typeof value === "string" && value).slice(0, 8);
|
||||
const conversationTraceIds = conversations.map((item) => item?.lastTraceId).filter((value) => typeof value === "string" && value);
|
||||
const sessionIds = sessions.map((item) => item?.sessionId ?? item?.id).filter((value) => typeof value === "string" && value).slice(0, 8);
|
||||
const sessionTraceIds = sessions.map((item) => item?.lastTraceId ?? item?.traceId).filter((value) => typeof value === "string" && value);
|
||||
const domTraceIds = cards.map((card) => {
|
||||
const trace = card.querySelector(".trace-timeline, .message-trace");
|
||||
return firstTraceId(card.getAttribute("data-trace-id"), trace?.getAttribute("data-trace-id"), trace?.getAttribute("data-traceid"), card.textContent, trace?.textContent, document.querySelector(".composer-mode")?.textContent);
|
||||
}).filter((value) => typeof value === "string" && value);
|
||||
const traceIds = [...new Set([...conversationTraceIds, ...domTraceIds])].slice(0, 8);
|
||||
const traceIds = [...new Set([...sessionTraceIds, ...domTraceIds])].slice(0, 8);
|
||||
const scroll = workspace ? {
|
||||
scrollTop: Math.round(workspace.scrollTop),
|
||||
scrollHeight: Math.round(workspace.scrollHeight),
|
||||
@@ -1516,8 +1489,8 @@ async function collectDomEvidence(page, args) {
|
||||
titles: sessionTabs.map((item) => item.querySelector("strong")?.textContent?.trim() ?? "").slice(0, 8),
|
||||
sessionIds,
|
||||
traceIds,
|
||||
conversationsStatus: conversationResponse?.status ?? null,
|
||||
conversationsCount: conversations.length
|
||||
sessionsStatus: sessionsResponse?.status ?? null,
|
||||
sessionsCount: sessions.length
|
||||
},
|
||||
messageCount: cards.length,
|
||||
messageRoles: cards.map((card) => cardRole(card) ?? "unknown"),
|
||||
@@ -1578,7 +1551,7 @@ function observeBootstrapRoutes(page) {
|
||||
page.on("request", (request) => {
|
||||
try {
|
||||
const url = new URL(request.url());
|
||||
if (!["/auth/bootstrap", "/auth/session", "/auth/workspace-bootstrap", "/v1/workbench/workspace"].includes(url.pathname)) return;
|
||||
if (!["/auth/bootstrap", "/auth/session", "/auth/workspace-bootstrap", "/v1/workbench/sessions"].includes(url.pathname)) return;
|
||||
routes.push({ method: request.method(), path: url.pathname });
|
||||
} catch {
|
||||
// Ignore malformed browser-internal URLs.
|
||||
@@ -1592,7 +1565,7 @@ function summarizeBootstrapRoutes(routes) {
|
||||
return {
|
||||
authBootstrapRequested: paths.includes("/auth/bootstrap"),
|
||||
authSessionRequested: paths.includes("/auth/session"),
|
||||
workbenchWorkspaceRequested: paths.includes("/v1/workbench/workspace"),
|
||||
workbenchSessionsRequested: paths.includes("/v1/workbench/sessions"),
|
||||
legacyWorkspaceBootstrapRequested: paths.includes("/auth/workspace-bootstrap"),
|
||||
routes: routes.slice(0, 20)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user