feat: add mdtodo web-probe commands
This commit is contained in:
@@ -367,6 +367,9 @@ async function processCommand(command) {
|
||||
case "selectMdtodoFile": return selectMdtodoFile(command);
|
||||
case "selectMdtodoTask": return selectMdtodoTask(command);
|
||||
case "expandMdtodoTask": return expandMdtodoTask(command);
|
||||
case "openMdtodoReportPreview": return openMdtodoReportPreview(command);
|
||||
case "toggleMdtodoReportFullscreen": return toggleMdtodoReportFullscreen(command);
|
||||
case "editMdtodoTaskInline": return editMdtodoTaskInline(command);
|
||||
case "editMdtodoTaskTitle": return editMdtodoTaskTitle(command);
|
||||
case "editMdtodoTaskBody": return editMdtodoTaskBody(command);
|
||||
case "toggleMdtodoTaskStatus": return toggleMdtodoTaskStatus(command);
|
||||
@@ -1117,6 +1120,14 @@ function sessionIdFromAgentSessionPayload(payload) {
|
||||
return match ? match[0] : null;
|
||||
}
|
||||
|
||||
function traceIdFromAgentChatPayload(payload) {
|
||||
const direct = payload?.traceId ?? payload?.turn?.traceId ?? payload?.message?.traceId ?? payload?.data?.traceId ?? payload?.data?.turn?.traceId ?? payload?.data?.message?.traceId;
|
||||
const directText = String(direct || "").trim();
|
||||
if (/^(?:trc_[A-Za-z0-9_-]+|[a-f0-9]{16,64})$/u.test(directText)) return directText;
|
||||
const match = JSON.stringify(payload ?? "").match(/\b(?:trc_[A-Za-z0-9_-]+|[a-f0-9]{16,64})\b/u);
|
||||
return match ? match[0] : null;
|
||||
}
|
||||
|
||||
async function workbenchSessionSnapshot(targetPage = page) {
|
||||
return targetPage.evaluate(() => {
|
||||
const activeTab = document.querySelector(".session-tab[data-active='true'], .session-tab[aria-selected='true']");
|
||||
@@ -1939,7 +1950,7 @@ async function selectMdtodoFile(command) {
|
||||
return clickProjectItemByAttr({
|
||||
type: "selectMdtodoFile",
|
||||
attr: "data-file-ref",
|
||||
value: command.fileRef || command.value || command.text || "",
|
||||
value: command.fileRef || command.filename || command.value || command.text || "",
|
||||
fallbackSelector: '[data-testid="mdtodo-file-list"] [data-file-ref], [data-file-ref]',
|
||||
selectTestId: "mdtodo-file-select"
|
||||
});
|
||||
@@ -2145,7 +2156,9 @@ async function saveMdtodoTaskWithButton(command, type, testId, fields) {
|
||||
const beforeUrl = currentPageUrl();
|
||||
const beforeProject = await projectManagementCommandSnapshot();
|
||||
const selection = await selectTaskIfCommandTargetsOne(command);
|
||||
const inlineEditors = [];
|
||||
for (const field of fields) {
|
||||
if (field.openByDblClickTestId) inlineEditors.push(await ensureMdtodoInlineEditor(field.testId, field.openByDblClickTestId));
|
||||
if (field.kind === "fill") await fillMdtodoField(field.testId, field.value);
|
||||
if (field.kind === "select") {
|
||||
const locator = page.locator('[data-testid="' + cssEscape(field.testId) + '"]').first();
|
||||
@@ -2153,26 +2166,142 @@ async function saveMdtodoTaskWithButton(command, type, testId, fields) {
|
||||
await selectHtmlOptionByValueOrLabel(locator, field.value);
|
||||
}
|
||||
}
|
||||
const save = await clickProjectButtonAndMaybeWait(testId, /^\/v1\/project-management\/mdtodo\/tasks/u);
|
||||
return { beforeUrl, afterUrl: currentPageUrl(), type, selection, save, beforeProject, afterProject: await projectManagementCommandSnapshot(), pageId, valuesRedacted: true };
|
||||
const save = await clickProjectButtonAndMaybeWaitAny(Array.isArray(testId) ? testId : [testId], /^\/v1\/project-management\/mdtodo\/tasks/u);
|
||||
return { beforeUrl, afterUrl: currentPageUrl(), type, selection, inlineEditors, save, beforeProject, afterProject: await projectManagementCommandSnapshot(), pageId, valuesRedacted: true };
|
||||
}
|
||||
|
||||
async function clickProjectButtonAndMaybeWaitAny(testIds, pathPattern) {
|
||||
const candidates = (testIds || []).filter(Boolean);
|
||||
for (const testId of candidates) {
|
||||
const button = page.locator('[data-testid="' + cssEscape(testId) + '"]').first();
|
||||
if (await visibleLocator(button)) return clickProjectButtonAndMaybeWait(testId, pathPattern);
|
||||
}
|
||||
if (candidates.length === 0) throw new Error("clickProjectButtonAndMaybeWaitAny requires at least one data-testid");
|
||||
return clickProjectButtonAndMaybeWait(candidates[0], pathPattern);
|
||||
}
|
||||
|
||||
async function ensureMdtodoInlineEditor(editorTestId, readTestId) {
|
||||
const editor = page.locator('[data-testid="' + cssEscape(editorTestId) + '"]').first();
|
||||
if (await visibleLocator(editor)) return { editorTestId, readTestId, opened: false };
|
||||
const read = page.locator('[data-testid="' + cssEscape(readTestId) + '"]').first();
|
||||
await read.waitFor({ state: "visible", timeout: 10000 });
|
||||
await read.dblclick();
|
||||
await editor.waitFor({ state: "visible", timeout: 10000 });
|
||||
return { editorTestId, readTestId, opened: true };
|
||||
}
|
||||
|
||||
async function editMdtodoTaskTitle(command) {
|
||||
const title = commandValue(command, ["title", "text", "value"]);
|
||||
if (!title) throw new Error("editMdtodoTaskTitle requires --title or --text");
|
||||
return saveMdtodoTaskWithButton(command, "editMdtodoTaskTitle", "mdtodo-edit-save", [{ kind: "fill", testId: "mdtodo-edit-title", value: title }]);
|
||||
return saveMdtodoTaskWithButton(command, "editMdtodoTaskTitle", "mdtodo-edit-save", [{ kind: "fill", testId: "mdtodo-edit-title", value: title, openByDblClickTestId: "mdtodo-title-read" }]);
|
||||
}
|
||||
|
||||
async function editMdtodoTaskBody(command) {
|
||||
const body = commandValue(command, ["text", "body", "value"]);
|
||||
if (!body) throw new Error("editMdtodoTaskBody requires --text or --text-stdin");
|
||||
return saveMdtodoTaskWithButton(command, "editMdtodoTaskBody", "mdtodo-edit-body-save", [{ kind: "fill", testId: "mdtodo-edit-body", value: body }]);
|
||||
return saveMdtodoTaskWithButton(command, "editMdtodoTaskBody", "mdtodo-edit-body-save", [{ kind: "fill", testId: "mdtodo-edit-body", value: body, openByDblClickTestId: "mdtodo-body-rendered" }]);
|
||||
}
|
||||
|
||||
async function toggleMdtodoTaskStatus(command) {
|
||||
const status = commandValue(command, ["status", "value", "text"]);
|
||||
if (!status) throw new Error("toggleMdtodoTaskStatus requires --status");
|
||||
return saveMdtodoTaskWithButton(command, "toggleMdtodoTaskStatus", "mdtodo-edit-save", [{ kind: "select", testId: "mdtodo-edit-status", value: status }]);
|
||||
return saveMdtodoTaskWithButton(command, "toggleMdtodoTaskStatus", ["mdtodo-status-save", "mdtodo-edit-save"], [{ kind: "select", testId: "mdtodo-edit-status", value: status }]);
|
||||
}
|
||||
|
||||
async function editMdtodoTaskInline(command) {
|
||||
const field = commandValue(command, ["field", "value"]).toLowerCase();
|
||||
if (field === "title") {
|
||||
const title = commandValue(command, ["title", "text"]);
|
||||
if (!title) throw new Error("editMdtodoTaskInline --field title requires --title or --text");
|
||||
return saveMdtodoTaskWithButton(command, "editMdtodoTaskInline", "mdtodo-edit-save", [{ kind: "fill", testId: "mdtodo-edit-title", value: title, openByDblClickTestId: "mdtodo-title-read" }]);
|
||||
}
|
||||
if (field === "body" || field === "content") {
|
||||
const body = commandValue(command, ["body", "text"]);
|
||||
if (!body) throw new Error("editMdtodoTaskInline --field body requires --body, --text, or --text-stdin");
|
||||
return saveMdtodoTaskWithButton(command, "editMdtodoTaskInline", "mdtodo-edit-body-save", [{ kind: "fill", testId: "mdtodo-edit-body", value: body, openByDblClickTestId: "mdtodo-body-rendered" }]);
|
||||
}
|
||||
if (field === "status") {
|
||||
const status = commandValue(command, ["status", "text"]);
|
||||
if (!status) throw new Error("editMdtodoTaskInline --field status requires --status or --text");
|
||||
return saveMdtodoTaskWithButton(command, "editMdtodoTaskInline", ["mdtodo-status-save", "mdtodo-edit-save"], [{ kind: "select", testId: "mdtodo-edit-status", value: status }]);
|
||||
}
|
||||
throw new Error("editMdtodoTaskInline requires --field title, body, or status");
|
||||
}
|
||||
|
||||
async function openMdtodoReportPreview(command) {
|
||||
ensureProjectManagementCommand("openMdtodoReportPreview");
|
||||
const beforeUrl = currentPageUrl();
|
||||
const beforeProject = await projectManagementCommandSnapshot();
|
||||
const selection = await selectTaskIfCommandTargetsOne(command);
|
||||
const linkText = commandValue(command, ["link", "value", "text"]);
|
||||
const links = page.locator('[data-testid="mdtodo-report-link"]');
|
||||
await links.first().waitFor({ state: "visible", timeout: 15000 });
|
||||
const count = await links.count();
|
||||
let index = 0;
|
||||
if (linkText) {
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
const item = links.nth(i);
|
||||
const text = await item.textContent().catch(() => "");
|
||||
if (String(text || "").includes(linkText)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const link = links.nth(index);
|
||||
const linkStateRaw = await link.evaluate((element, selectedIndex) => ({
|
||||
index: selectedIndex,
|
||||
disabled: Boolean(element.disabled) || element.getAttribute("aria-disabled") === "true",
|
||||
text: String(element.textContent || "").replace(/\s+/gu, " ").trim().slice(0, 240),
|
||||
valuesRedacted: true
|
||||
}), index).catch((error) => ({ index, error: errorSummary(error), valuesRedacted: true }));
|
||||
const linkState = {
|
||||
...linkStateRaw,
|
||||
textHash: linkStateRaw.text ? sha256Text(linkStateRaw.text) : null,
|
||||
textPreview: linkStateRaw.text ? truncate(linkStateRaw.text, 120) : null,
|
||||
text: undefined,
|
||||
valuesRedacted: true
|
||||
};
|
||||
if (linkState.disabled === true) {
|
||||
const error = new Error("openMdtodoReportPreview selected report link is disabled");
|
||||
error.details = { beforeUrl, linkState, beforeProject, valuesRedacted: true };
|
||||
throw error;
|
||||
}
|
||||
const responsePromise = page.waitForResponse((response) => {
|
||||
try {
|
||||
const request = response.request();
|
||||
return request.method().toUpperCase() === "GET" && new URL(response.url()).pathname === "/v1/project-management/mdtodo/report-preview";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, { timeout: 30000 }).then((response) => ({ observed: true, status: response.status(), path: new URL(response.url()).pathname })).catch((error) => ({ observed: false, waitError: errorSummary(error) }));
|
||||
await link.click();
|
||||
const response = await responsePromise;
|
||||
await page.locator('[data-testid="mdtodo-report-preview"], [data-testid="mdtodo-report-error"]').first().waitFor({ state: "visible", timeout: 10000 }).catch(() => null);
|
||||
return { beforeUrl, afterUrl: currentPageUrl(), type: "openMdtodoReportPreview", selection, linkState, response, beforeProject, afterProject: await projectManagementCommandSnapshot(), pageId, valuesRedacted: true };
|
||||
}
|
||||
|
||||
async function toggleMdtodoReportFullscreen(command) {
|
||||
ensureProjectManagementCommand("toggleMdtodoReportFullscreen");
|
||||
const beforeUrl = currentPageUrl();
|
||||
const beforeProject = await projectManagementCommandSnapshot();
|
||||
const desired = commandValue(command, ["value", "text"]).toLowerCase();
|
||||
const dialog = page.locator('[data-testid="mdtodo-report-fullscreen-dialog"]').first();
|
||||
const initiallyOpen = await visibleLocator(dialog);
|
||||
let action = "open";
|
||||
if (desired === "close" || desired === "off") action = "close";
|
||||
else if (desired === "toggle") action = initiallyOpen ? "close" : "open";
|
||||
if (action === "close") {
|
||||
const closeButton = page.locator('[data-testid="mdtodo-report-fullscreen-dialog"] [aria-label="关闭报告"], [aria-label="关闭报告"]').first();
|
||||
await closeButton.waitFor({ state: "visible", timeout: 10000 });
|
||||
await closeButton.click();
|
||||
} else if (!initiallyOpen) {
|
||||
const button = page.locator('[data-testid="mdtodo-report-fullscreen"]').first();
|
||||
await button.waitFor({ state: "visible", timeout: 10000 });
|
||||
await button.click();
|
||||
}
|
||||
await page.waitForTimeout(500);
|
||||
return { beforeUrl, afterUrl: currentPageUrl(), type: "toggleMdtodoReportFullscreen", action, initiallyOpen, fullscreenOpen: await visibleLocator(dialog), beforeProject, afterProject: await projectManagementCommandSnapshot(), pageId, valuesRedacted: true };
|
||||
}
|
||||
|
||||
async function fillNewTaskDraft(command) {
|
||||
@@ -2253,6 +2382,36 @@ async function launchWorkbenchFromTask(command) {
|
||||
return false;
|
||||
}
|
||||
}, { timeout: 45000 }).catch((error) => ({ waitError: errorSummary(error) }));
|
||||
const chatResponsePromise = page.waitForResponse((response) => {
|
||||
const request = response.request();
|
||||
if (request.method().toUpperCase() !== "POST") return false;
|
||||
try {
|
||||
return new URL(response.url()).pathname === "/v1/agent/chat";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}, { timeout: 30000 }).then(async (response) => {
|
||||
let chatPayload = null;
|
||||
let chatPayloadError = null;
|
||||
try {
|
||||
chatPayload = await response.json();
|
||||
} catch (error) {
|
||||
chatPayloadError = errorSummary(error);
|
||||
}
|
||||
const headers = response.headers();
|
||||
return {
|
||||
observed: true,
|
||||
status: response.status(),
|
||||
statusText: response.statusText(),
|
||||
path: new URL(response.url()).pathname,
|
||||
sessionId: sessionIdFromAgentSessionPayload(chatPayload),
|
||||
traceId: traceIdFromAgentChatPayload(chatPayload),
|
||||
otelTraceId: typeof headers["x-hwlab-otel-trace-id"] === "string" ? headers["x-hwlab-otel-trace-id"] : null,
|
||||
responseParsed: chatPayload !== null,
|
||||
responseParseError: chatPayloadError,
|
||||
valuesRedacted: true
|
||||
};
|
||||
}).catch((error) => ({ observed: false, waitError: errorSummary(error), valuesRedacted: true }));
|
||||
await button.click();
|
||||
const launchResponse = await launchResponsePromise;
|
||||
if (launchResponse?.waitError) {
|
||||
@@ -2281,6 +2440,9 @@ async function launchWorkbenchFromTask(command) {
|
||||
if (workbenchUrl) {
|
||||
await page.waitForFunction((expectedPath) => window.location.pathname === expectedPath, workbenchUrl, { timeout: 20000 }).catch(() => null);
|
||||
}
|
||||
const chat = await chatResponsePromise;
|
||||
await page.waitForTimeout(1500);
|
||||
const workbenchSnapshot = await workbenchSessionSnapshot();
|
||||
return {
|
||||
beforeUrl,
|
||||
afterUrl: currentPageUrl(),
|
||||
@@ -2291,6 +2453,13 @@ async function launchWorkbenchFromTask(command) {
|
||||
sessionId,
|
||||
workbenchUrl,
|
||||
otelTraceId: launchTraceHeader,
|
||||
chatObserved: chat?.observed === true,
|
||||
chatStatus: chat?.status ?? null,
|
||||
chatSessionId: chat?.sessionId ?? null,
|
||||
chatTraceId: chat?.traceId ?? null,
|
||||
chatOtelTraceId: chat?.otelTraceId ?? null,
|
||||
chat,
|
||||
workbenchSnapshot,
|
||||
selectedTask: opaqueIdSummary(projectBeforeClick.selectedTaskRefRaw),
|
||||
projectBeforeClick: sanitizeProjectCommandSnapshot(projectBeforeClick),
|
||||
buttonState,
|
||||
@@ -2321,7 +2490,17 @@ async function projectManagementCommandSnapshot(options = {}) {
|
||||
const fileSelect = document.querySelector('[data-testid="mdtodo-file-select"]');
|
||||
const sourceOptionCount = sourceSelect ? Array.from(sourceSelect.options || []).filter((option) => option.value).length : 0;
|
||||
const fileOptionCount = fileSelect ? Array.from(fileSelect.options || []).filter((option) => option.value).length : 0;
|
||||
const fileOptions = fileSelect ? Array.from(fileSelect.options || []).filter((option) => option.value).map((option) => ({
|
||||
value: option.value,
|
||||
label: text(option),
|
||||
selected: option.selected === true
|
||||
})) : [];
|
||||
const selectedFileOption = fileOptions.find((option) => option.selected) || null;
|
||||
const launch = document.querySelector('[data-testid="mdtodo-workbench-launch"], [data-action="launch-workbench"]');
|
||||
const bodyRendered = document.querySelector('[data-testid="mdtodo-body-rendered"]');
|
||||
const reportPreview = document.querySelector('[data-testid="mdtodo-report-preview"]');
|
||||
const reportFullscreen = document.querySelector('[data-testid="mdtodo-report-fullscreen-dialog"]');
|
||||
const reportLinks = Array.from(document.querySelectorAll('[data-testid="mdtodo-report-link"]')).filter(visible);
|
||||
return {
|
||||
path: window.location.pathname,
|
||||
pageKind: visible(document.querySelector('[data-testid="project-management-mdtodo"]')) ? "project-management-mdtodo" : visible(document.querySelector('[data-testid="project-management-root"]')) ? "project-management-root" : null,
|
||||
@@ -2330,6 +2509,8 @@ async function projectManagementCommandSnapshot(options = {}) {
|
||||
taskCount: Array.from(document.querySelectorAll('[data-task-ref]')).filter(visible).length,
|
||||
selectedSourceIdRaw: selectedSource?.getAttribute("data-source-id") || sourceSelect?.value || null,
|
||||
selectedFileRefRaw: selectedFile?.getAttribute("data-file-ref") || fileSelect?.value || null,
|
||||
selectedFileLabel: selectedFile ? text(selectedFile) : selectedFileOption?.label || null,
|
||||
fileOptionLabels: fileOptions.map((option) => option.label).filter(Boolean).slice(0, 20),
|
||||
selectedTaskRefRaw: selectedTask?.getAttribute("data-task-ref") || null,
|
||||
selectedTaskId: selectedTask?.getAttribute("data-task-id") || selectedTask?.getAttribute("data-rxx-id") || null,
|
||||
selectedTaskStatus: selectedTask?.getAttribute("data-task-status") || null,
|
||||
@@ -2337,7 +2518,13 @@ async function projectManagementCommandSnapshot(options = {}) {
|
||||
fileSelectVisible: visible(fileSelect),
|
||||
sourceConfigVisible: visible(document.querySelector('[data-testid="mdtodo-source-form-hwpod"], [data-testid="mdtodo-source-config-dialog"], [role="dialog"]')),
|
||||
taskEditorVisible: visible(document.querySelector('[data-testid="mdtodo-edit-title"], [data-testid="mdtodo-edit-body"]')),
|
||||
taskBodyVisible: visible(bodyRendered),
|
||||
taskBodyText: visible(bodyRendered) ? text(bodyRendered) : "",
|
||||
newTaskDraftVisible: visible(document.querySelector('[data-testid="mdtodo-new-title"], [data-testid="mdtodo-new-body"]')),
|
||||
reportLinkCount: reportLinks.length,
|
||||
reportPreviewVisible: visible(reportPreview),
|
||||
reportPreviewText: visible(reportPreview) ? text(reportPreview) : "",
|
||||
reportFullscreenVisible: visible(reportFullscreen),
|
||||
launchButtonVisible: visible(launch),
|
||||
launchButtonEnabled: visible(launch) && !launch.disabled && launch.getAttribute("aria-disabled") !== "true",
|
||||
launchButtonText: text(launch),
|
||||
@@ -2352,6 +2539,16 @@ async function projectManagementCommandSnapshot(options = {}) {
|
||||
|
||||
function sanitizeProjectCommandSnapshot(value) {
|
||||
if (!value || typeof value !== "object") return value;
|
||||
const textDigest = (raw, limit = 160) => {
|
||||
const text = String(raw || "");
|
||||
return { textHash: sha256Text(text), textPreview: truncate(text, limit), textBytes: Buffer.byteLength(text), valuesRedacted: true };
|
||||
};
|
||||
const fileLabelLooksDirect = (label) => /^[^/\\]+\.md$/iu.test(String(label || "").trim());
|
||||
const suspiciousFileLabel = (label) => {
|
||||
const text = String(label || "").trim();
|
||||
return Boolean(text && (!fileLabelLooksDirect(text) || /(?:details\/|_Task_Report|_log_|\/)/iu.test(text)));
|
||||
};
|
||||
const fileLabels = Array.isArray(value.fileOptionLabels) ? value.fileOptionLabels.map((item) => String(item || "")).filter(Boolean) : [];
|
||||
return {
|
||||
...value,
|
||||
selectedSourceId: opaqueIdSummary(value.selectedSourceIdRaw),
|
||||
@@ -2360,6 +2557,15 @@ function sanitizeProjectCommandSnapshot(value) {
|
||||
selectedSourceIdRaw: undefined,
|
||||
selectedFileRefRaw: undefined,
|
||||
selectedTaskRefRaw: undefined,
|
||||
selectedFileLabel: value.selectedFileLabel ? textDigest(value.selectedFileLabel, 120) : null,
|
||||
selectedFileLabelLooksDirect: value.selectedFileLabel ? fileLabelLooksDirect(value.selectedFileLabel) : null,
|
||||
fileOptionLabelSamples: fileLabels.slice(0, 8).map((item) => textDigest(item, 120)),
|
||||
fileOptionSuspiciousLabelCount: fileLabels.filter(suspiciousFileLabel).length,
|
||||
fileOptionLabels: undefined,
|
||||
taskBodyText: undefined,
|
||||
taskBody: value.taskBodyText ? textDigest(value.taskBodyText, 200) : null,
|
||||
reportPreviewText: undefined,
|
||||
reportPreview: value.reportPreviewText ? textDigest(value.reportPreviewText, 200) : null,
|
||||
launchButtonTextHash: value.launchButtonText ? sha256Text(value.launchButtonText) : null,
|
||||
launchButtonTextPreview: value.launchButtonText ? truncate(value.launchButtonText, 80) : null,
|
||||
launchButtonText: undefined,
|
||||
@@ -2758,6 +2964,12 @@ async function sampleOnePage(targetPage, { reason, groupSeq, pageRole, targetPag
|
||||
const fileSelect = document.querySelector('[data-testid="mdtodo-file-select"]');
|
||||
const sourceOptionCount = sourceSelect ? Array.from(sourceSelect.options || []).filter((option) => option.value).length : 0;
|
||||
const fileOptionCount = fileSelect ? Array.from(fileSelect.options || []).filter((option) => option.value).length : 0;
|
||||
const fileOptions = fileSelect ? Array.from(fileSelect.options || []).filter((option) => option.value).map((option) => ({
|
||||
value: option.value,
|
||||
label: trim(option.textContent || option.label || "", 180),
|
||||
selected: option.selected === true,
|
||||
})) : [];
|
||||
const selectedFileOption = fileOptions.find((option) => option.selected) || null;
|
||||
const taskItems = Array.from(document.querySelectorAll('[data-testid="mdtodo-task-tree"] [data-task-ref], [data-task-ref]')).filter(visible);
|
||||
const taskCandidates = Array.from(document.querySelectorAll('[data-testid="mdtodo-task-tree"] li, [data-testid="mdtodo-task-tree"] [role="treeitem"], [data-testid="mdtodo-task-tree"] [role="listitem"]')).filter(visible);
|
||||
const selectedSource = document.querySelector('[data-source-id][data-selected="true"], [data-source-id][aria-selected="true"], [data-source-id].selected, [data-source-id].is-selected');
|
||||
@@ -2769,6 +2981,10 @@ async function sampleOnePage(targetPage, { reason, groupSeq, pageRole, targetPag
|
||||
statusCounts[status] = (statusCounts[status] || 0) + 1;
|
||||
}
|
||||
const launch = document.querySelector('[data-testid="mdtodo-workbench-launch"], [data-action="launch-workbench"]');
|
||||
const bodyRendered = document.querySelector('[data-testid="mdtodo-body-rendered"]');
|
||||
const reportPreview = document.querySelector('[data-testid="mdtodo-report-preview"]');
|
||||
const reportFullscreen = document.querySelector('[data-testid="mdtodo-report-fullscreen-dialog"]');
|
||||
const reportLinks = Array.from(document.querySelectorAll('[data-testid="mdtodo-report-link"]')).filter(visible);
|
||||
const blockers = Array.from(document.querySelectorAll('[data-testid="mdtodo-workbench-launch-blocker"], [data-testid="mdtodo-workbench-launch-error"], [role="alert"]')).filter(visible).slice(0, 12).map((element, index) => ({
|
||||
index,
|
||||
testId: element.getAttribute("data-testid"),
|
||||
@@ -2787,14 +3003,22 @@ async function sampleOnePage(targetPage, { reason, groupSeq, pageRole, targetPag
|
||||
taskRefMissingCount: Math.max(0, taskCandidates.length - taskItems.length),
|
||||
selectedSourceId: opaqueDomId(selectedSource?.getAttribute("data-source-id") || sourceSelect?.value),
|
||||
selectedFileRef: opaqueDomId(selectedFile?.getAttribute("data-file-ref") || fileSelect?.value),
|
||||
selectedFileLabel: selectedFile ? trim(selectedFile.textContent || "", 180) : selectedFileOption?.label || null,
|
||||
fileOptionLabels: fileOptions.map((option) => option.label).filter(Boolean).slice(0, 24),
|
||||
selectedTaskRef: opaqueDomId(selectedTask?.getAttribute("data-task-ref")),
|
||||
selectedTaskStatus: selectedTask?.getAttribute("data-task-status") || null,
|
||||
sourceSelectVisible: visible(sourceSelect),
|
||||
fileSelectVisible: visible(fileSelect),
|
||||
sourceConfigVisible: visible(document.querySelector('[data-testid="mdtodo-source-form-hwpod"], [data-testid="mdtodo-source-config-dialog"], [role="dialog"]')),
|
||||
taskEditorVisible: visible(document.querySelector('[data-testid="mdtodo-edit-title"], [data-testid="mdtodo-edit-body"]')),
|
||||
taskBodyVisible: visible(bodyRendered),
|
||||
taskBodyText: visible(bodyRendered) ? trim(bodyRendered.textContent || "", 500) : "",
|
||||
newTaskDraftVisible: visible(document.querySelector('[data-testid="mdtodo-new-title"], [data-testid="mdtodo-new-body"]')),
|
||||
taskStatusCounts: statusCounts,
|
||||
reportLinkCount: reportLinks.length,
|
||||
reportPreviewVisible: visible(reportPreview),
|
||||
reportPreviewText: visible(reportPreview) ? trim(reportPreview.textContent || "", 500) : "",
|
||||
reportFullscreenVisible: visible(reportFullscreen),
|
||||
launchButtonVisible: visible(launch),
|
||||
launchButtonEnabled: visible(launch) && !launch.disabled && launch.getAttribute("aria-disabled") !== "true",
|
||||
launchButtonText: trim(launch?.textContent || "", 120),
|
||||
@@ -2981,6 +3205,12 @@ function digestProjectManagement(value) {
|
||||
const text = String(raw || "");
|
||||
return { textHash: sha256Text(text), textPreview: truncate(text, limit), textBytes: Buffer.byteLength(text), valuesRedacted: true };
|
||||
};
|
||||
const fileLabelLooksDirect = (label) => /^[^/\\]+\.md$/iu.test(String(label || "").trim());
|
||||
const suspiciousFileLabel = (label) => {
|
||||
const text = String(label || "").trim();
|
||||
return Boolean(text && (!fileLabelLooksDirect(text) || /(?:details\/|_Task_Report|_log_|\/)/iu.test(text)));
|
||||
};
|
||||
const fileLabels = Array.isArray(value.fileOptionLabels) ? value.fileOptionLabels.map((item) => String(item || "")).filter(Boolean) : [];
|
||||
return {
|
||||
pageKind: value.pageKind ?? null,
|
||||
configuredPath: value.configuredPath === true,
|
||||
@@ -2992,14 +3222,24 @@ function digestProjectManagement(value) {
|
||||
taskRefMissingCount: Number.isFinite(Number(value.taskRefMissingCount)) ? Number(value.taskRefMissingCount) : 0,
|
||||
selectedSourceId: opaque(value.selectedSourceId),
|
||||
selectedFileRef: opaque(value.selectedFileRef),
|
||||
selectedFileLabel: value.selectedFileLabel ? textDigest(value.selectedFileLabel, 120) : null,
|
||||
selectedFileLabelLooksDirect: value.selectedFileLabel ? fileLabelLooksDirect(value.selectedFileLabel) : null,
|
||||
fileOptionLabelSamples: fileLabels.slice(0, 10).map((item) => textDigest(item, 120)),
|
||||
fileOptionSuspiciousLabelCount: fileLabels.filter(suspiciousFileLabel).length,
|
||||
selectedTaskRef: opaque(value.selectedTaskRef),
|
||||
selectedTaskStatus: value.selectedTaskStatus ?? null,
|
||||
sourceSelectVisible: value.sourceSelectVisible === true,
|
||||
fileSelectVisible: value.fileSelectVisible === true,
|
||||
sourceConfigVisible: value.sourceConfigVisible === true,
|
||||
taskEditorVisible: value.taskEditorVisible === true,
|
||||
taskBodyVisible: value.taskBodyVisible === true,
|
||||
taskBody: value.taskBodyText ? textDigest(value.taskBodyText, 200) : null,
|
||||
newTaskDraftVisible: value.newTaskDraftVisible === true,
|
||||
taskStatusCounts: value.taskStatusCounts && typeof value.taskStatusCounts === "object" ? value.taskStatusCounts : {},
|
||||
reportLinkCount: Number.isFinite(Number(value.reportLinkCount)) ? Number(value.reportLinkCount) : 0,
|
||||
reportPreviewVisible: value.reportPreviewVisible === true,
|
||||
reportPreview: value.reportPreviewText ? textDigest(value.reportPreviewText, 200) : null,
|
||||
reportFullscreenVisible: value.reportFullscreenVisible === true,
|
||||
launchButtonVisible: value.launchButtonVisible === true,
|
||||
launchButtonEnabled: value.launchButtonEnabled === true,
|
||||
launchButtonText: value.launchButtonText ? textDigest(value.launchButtonText, 120) : null,
|
||||
@@ -3111,8 +3351,11 @@ function commandInputSummary(command) {
|
||||
blocking: command.blocking === true ? true : command.blocking === false ? false : null,
|
||||
sourceId: opaque(command.sourceId),
|
||||
fileRef: opaque(command.fileRef),
|
||||
filename: command.filename ? truncate(command.filename, 200) : null,
|
||||
taskRef: opaque(command.taskRef),
|
||||
taskId: command.taskId || null,
|
||||
field: command.field || null,
|
||||
link: command.link ? truncate(command.link, 200) : null,
|
||||
titleHash: command.title ? sha256Text(command.title) : null,
|
||||
titleBytes: command.title ? Buffer.byteLength(command.title) : null,
|
||||
bodyHash: command.body ? sha256Text(command.body) : null,
|
||||
|
||||
Reference in New Issue
Block a user