Merge pull request #1026 from pikasTech/fix/2206-web-probe-mdtodo

feat: add MDTODO web-probe commands
This commit is contained in:
Lyon
2026-06-26 20:15:50 +08:00
committed by GitHub
8 changed files with 375 additions and 20 deletions
@@ -121,7 +121,12 @@ bun scripts/cli.ts web-probe observe command webobs-xxxx --type probeMdtodoSourc
bun scripts/cli.ts web-probe observe command webobs-xxxx --type reindexMdtodoSource
bun scripts/cli.ts web-probe observe command webobs-xxxx --type selectMdtodoSource --source-id <opaque-source-id>
bun scripts/cli.ts web-probe observe command webobs-xxxx --type selectMdtodoFile --file-ref <opaque-file-ref>
bun scripts/cli.ts web-probe observe command webobs-xxxx --type selectMdtodoFile --filename <direct-mdtodo-file-name.md>
bun scripts/cli.ts web-probe observe command webobs-xxxx --type selectMdtodoTask --task-ref <opaque-task-ref-or-rxx>
bun scripts/cli.ts web-probe observe command webobs-xxxx --type openMdtodoReportPreview --task <rxx-or-task-ref> --link <report-label-or-path-fragment>
bun scripts/cli.ts web-probe observe command webobs-xxxx --type toggleMdtodoReportFullscreen --text toggle
bun scripts/cli.ts web-probe observe command webobs-xxxx --type editMdtodoTaskInline --task <rxx-or-task-ref> --field title --text 'web-probe interactive edit acceptance'
bun scripts/cli.ts web-probe observe command webobs-xxxx --type editMdtodoTaskInline --task <rxx-or-task-ref> --field body --text 'body updated through web-probe command'
bun scripts/cli.ts web-probe observe command webobs-xxxx --type editMdtodoTaskTitle --task <rxx-or-task-ref> --text 'web-probe interactive edit acceptance'
bun scripts/cli.ts web-probe observe command webobs-xxxx --type editMdtodoTaskBody --task <rxx-or-task-ref> --text 'body updated through web-probe command'
bun scripts/cli.ts web-probe observe command webobs-xxxx --type toggleMdtodoTaskStatus --task <rxx-or-task-ref> --status completed
@@ -139,7 +144,7 @@ bun scripts/cli.ts web-probe observe analyze webobs-xxxx
- `web-probe script` 不运行默认探针,必须通过 stdin heredoc 或 `--script-file <path>` 提供脚本;只需要 repo-owned 标准 DOM probe 时使用 `web-probe run`
- `web-probe run|script|observe start` 的默认 URL、browser proxy mode、observe/analyze 报警阈值和 project-management 采样/命令 allowlist 必须来自 `config/hwlab-node-lanes.yaml``webProbe`;需要排除公网/FRP/跨国 proxy 抖动时,在 YAML 里把目标 node/lane 的 `webProbe.defaultOrigin` 配成内部 Service ClusterIP origin,不要在命令行长期手写 `--url` 或裸 Playwright。
- `web-probe observe start` 默认是被动观测:记录 DOM 摘要、自然页面 request/response/requestfailed、截图和 performance 样本,不主动 fetch Workbench API、不切换 control session、不拦截路由、不调用 repair helper。长程 Workbench 观测必须保留 control/observer 双页面模型:control 页面执行显式 commandobserver 页面只同步到同一 session URL 后被动采样,并按默认 180000ms 周期整页刷新同一 session 来模拟用户往返;周期刷新只作用于 observer,不得改变 control active session 或作为通过条件。两页的 `pageRole``pageId``sampleGroupSeq` 必须进入样本和 analyzer 报表。任何 `newSession``selectProvider``sendPrompt``steer``cancel``goto``screenshot``mark``stop` 都必须通过 `observe command` 显式下发,并进入 `control.jsonl`;长 prompt 必须优先用 `sendPrompt --text-stdin``steer --text-stdin`,不要为了绕开 shell quoting 退回裸 Playwright 或临时脚本。
- `web-probe observe start` 默认是被动观测:记录 DOM 摘要、自然页面 request/response/requestfailed、截图和 performance 样本,不主动 fetch Workbench API、不切换 control session、不拦截路由、不调用 repair helper。长程 Workbench 观测必须保留 control/observer 双页面模型:control 页面执行显式 commandobserver 页面只同步到同一 session URL 后被动采样,并按默认 180000ms 周期整页刷新同一 session 来模拟用户往返;周期刷新只作用于 observer,不得改变 control active session 或作为通过条件。两页的 `pageRole``pageId``sampleGroupSeq` 必须进入样本和 analyzer 报表。任何 `newSession``selectProvider``sendPrompt``steer``cancel``goto``screenshot``mark``stop` 都必须通过 `observe command` 显式下发,并进入 `control.jsonl`;长 prompt 必须优先用 `sendPrompt --text-stdin``steer --text-stdin`,不要为了绕开 shell quoting 退回裸 Playwright 或临时脚本。MDTODO 高频操作也必须优先沉淀为 `observe command`;同类动作第二次出现时不要继续写临时 `web-probe script`
- `observe command --type steer``--type cancel` 是显式用户/control actionsteer 复用当前 Workbench composer 的运行中 turn 引导路径,cancel 复用同一 composer 主按钮的取消路径。二者必须进入 `control.jsonl`,不能用后端私有 API、AgentRun direct cancel 或测试后门替代。`configureMdtodoHwpodSource``probeMdtodoSource``reindexMdtodoSource``selectMdtodoSource``selectMdtodoFile``selectMdtodoTask``editMdtodoTaskTitle``editMdtodoTaskBody``toggleMdtodoTaskStatus``addMdtodoSubTask``continueMdtodoTask``deleteMdtodoTask``launchWorkbenchFromMdtodo` 也是显式用户/control action,只能使用页面公开 `data-*` id、正式按钮和 YAML 允许的自然 API;它们通过 public source/file/task id 与 Workbench 关联,不能读取内部 store、私有后端或把 MDTODO 页面包含进 Workbench。
- `observe collect --view turn-summary` 是第一层 CLI 阅读视图:只从 `samples.jsonl``control.jsonl` 和已有 `analysis/report.json` 按需渲染同一 session 的多 turn 摘要,包含用户消息 preview/hash、traceId、状态、耗时/最近更新时间、steer/cancel 标记和 Final Response 摘要。`observe collect --view trace-frame --trace-id <id> --sample-seq <n>` 是第二层 CLI 阅读视图:从同一采样帧渲染单帧 trace 文字截图,并固定输出 `Final Response` 区块。`observe collect --view project-summary|project-mdtodo-summary` 从同一 artifact 渲染项目管理 / MDTODO DOM 采样、Source/File/Task 计数、command/mutation 结果、Workbench launch、捕获到的 `x-hwlab-otel-trace-id` 和 OTel/Tempo drill-down 线索;project collect 的远端 payload 必须保持 bounded compact rows,由本地 renderer 生成表格,避免 `trans` stdout 截断后 JSON parse 失败。collect 视图不是采样器新增保存物,不构成第二事实源。
- `observe start/status/command/collect/analyze` 默认输出包含 `Wrapper contract` 区块;该区块证明 Web 哨兵只能 wrap 现有 observe CLI verb、现有 runner/analyzer 和既有 artifact contract,不新增第二套 Playwright runner、analyzer、状态机或私有 web-probe API。
+4
View File
@@ -202,6 +202,7 @@ lanes:
naturalApiPathPrefixes:
- /v1/project-management/
- /v1/workbench/launches
- /v1/agent/chat
commandAllowlist:
- gotoProjectMdtodo
- openMdtodoSourceConfig
@@ -213,6 +214,9 @@ lanes:
- selectMdtodoFile
- selectMdtodoTask
- expandMdtodoTask
- openMdtodoReportPreview
- toggleMdtodoReportFullscreen
- editMdtodoTaskInline
- editMdtodoTaskTitle
- editMdtodoTaskBody
- toggleMdtodoTaskStatus
+4
View File
@@ -48,6 +48,10 @@ export function hwlabNodeWebProbeHelp(): Record<string, unknown> {
"bun scripts/cli.ts web-probe observe start --node D601 --lane v03 --target-path /workbench --sample-interval-ms 5000",
"bun scripts/cli.ts web-probe observe start --node D601 --lane v03 --target-path /projects/mdtodo --sample-interval-ms 5000",
"bun scripts/cli.ts web-probe observe command webobs-xxxx --type sendPrompt --text 'ping'",
"bun scripts/cli.ts web-probe observe command webobs-xxxx --type selectMdtodoFile --filename 20260609_频率判断_用户反馈.md",
"bun scripts/cli.ts web-probe observe command webobs-xxxx --type openMdtodoReportPreview --task R1 --link R1.1",
"bun scripts/cli.ts web-probe observe command webobs-xxxx --type toggleMdtodoReportFullscreen --text toggle",
"bun scripts/cli.ts web-probe observe command webobs-xxxx --type editMdtodoTaskInline --task R1 --field body --text 'body updated through web-probe command'",
"bun scripts/cli.ts web-probe observe command webobs-xxxx --type launchWorkbenchFromMdtodo --task R1.1",
"bun scripts/cli.ts web-probe observe status webobs-xxxx",
"bun scripts/cli.ts web-probe observe collect webobs-xxxx --view turn-summary",
@@ -1019,6 +1019,17 @@ function buildProjectManagementReport(samples, control, network, pagePerformance
const launchEnabledSamples = projectSamples.filter((sample) => sample.projectManagement?.launchButtonEnabled === true);
const launchVisibleSamples = projectSamples.filter((sample) => sample.projectManagement?.launchButtonVisible === true);
const mdtodoSamples = projectSamples.filter((sample) => sample.projectManagement?.pageKind === "project-management-mdtodo");
const selectedFileLabelBadSamples = projectSamples.filter((sample) => sample.projectManagement?.selectedFileLabel && sample.projectManagement?.selectedFileLabelLooksDirect === false);
const suspiciousFileLabelSamples = projectSamples.filter((sample) => Number(sample.projectManagement?.fileOptionSuspiciousLabelCount ?? 0) > 0);
const bodyVisibleSamples = selectedTaskSamples.filter((sample) => sample.projectManagement?.taskBodyVisible === true && Number(sample.projectManagement?.taskBody?.textBytes ?? 0) > 0);
const reportLinkSamples = projectSamples.filter((sample) => Number(sample.projectManagement?.reportLinkCount ?? 0) > 0);
const reportPreviewSamples = projectSamples.filter((sample) => sample.projectManagement?.reportPreviewVisible === true && Number(sample.projectManagement?.reportPreview?.textBytes ?? 0) > 0);
const reportFullscreenSamples = projectSamples.filter((sample) => sample.projectManagement?.reportFullscreenVisible === true);
const previewCommands = commandRows.filter((item) => item.type === "openMdtodoReportPreview" || item.type === "toggleMdtodoReportFullscreen");
const launchNonEmpty = launchSuccess.filter((item) => item.chatObserved === true && (Number(item.workbenchMessageCount ?? 0) > 0 || Number(item.workbenchTraceRowCount ?? 0) > 0));
const launchEmpty = launchSuccess.filter((item) => item.chatObserved !== true || (Number(item.workbenchMessageCount ?? 0) === 0 && Number(item.workbenchTraceRowCount ?? 0) === 0));
const minMdtodoTaskCount = minNumber(mdtodoSamples.map((sample) => sample.projectManagement?.taskCount));
const maxMdtodoTaskCount = maxNumber(mdtodoSamples.map((sample) => sample.projectManagement?.taskCount));
return {
enabled,
config: config || null,
@@ -1041,8 +1052,18 @@ function buildProjectManagementReport(samples, control, network, pagePerformance
taskRefMissingMax: maxNumber(projectSamples.map((sample) => sample.projectManagement?.taskRefMissingCount)),
latestSelectedTaskRefHash: latestProject?.selectedTaskRef?.hash ?? null,
latestSelectedTaskRefPreview: latestProject?.selectedTaskRef?.preview ?? null,
latestSelectedFileLabelPreview: latestProject?.selectedFileLabel?.textPreview ?? null,
latestSelectedFileLabelLooksDirect: latestProject?.selectedFileLabelLooksDirect ?? null,
selectedFileLabelBadSampleCount: selectedFileLabelBadSamples.length,
fileOptionSuspiciousLabelSampleCount: suspiciousFileLabelSamples.length,
maxFileOptionSuspiciousLabelCount: maxNumber(projectSamples.map((sample) => sample.projectManagement?.fileOptionSuspiciousLabelCount)),
latestSelectedTaskStatus: latestProject?.selectedTaskStatus ?? null,
latestTaskStatusCounts,
selectedTaskBodyVisibleSamples: bodyVisibleSamples.length,
reportLinkVisibleSamples: reportLinkSamples.length,
maxReportLinkCount: maxNumber(projectSamples.map((sample) => sample.projectManagement?.reportLinkCount)),
reportPreviewVisibleSamples: reportPreviewSamples.length,
reportFullscreenVisibleSamples: reportFullscreenSamples.length,
launchButtonVisibleSamples: launchVisibleSamples.length,
launchButtonEnabledSamples: launchEnabledSamples.length,
launchButtonDisabledSamples: Math.max(0, launchVisibleSamples.length - launchEnabledSamples.length),
@@ -1054,7 +1075,12 @@ function buildProjectManagementReport(samples, control, network, pagePerformance
launchCommandCount: launchCommands.length,
launchSuccessCount: launchSuccess.length,
launchFailureCount: launchFailed.length,
launchNonEmptyCount: launchNonEmpty.length,
launchEmptyCount: launchEmpty.length,
launchWithOtelTraceHeaderCount: launchSuccess.filter((item) => item.otelTraceId).length,
reportPreviewCommandCount: previewCommands.length,
mdtodoTaskCountMin: minMdtodoTaskCount,
mdtodoTaskCountMax: maxMdtodoTaskCount,
projectApiEventCount: projectApiEvents.length,
projectApiResponseCount: projectApiResponses.length,
projectApiFailureCount: projectApiFailures.length,
@@ -1075,7 +1101,16 @@ function buildProjectManagementReport(samples, control, network, pagePerformance
taskCount: sample.projectManagement?.taskCount ?? null,
taskRefMissingCount: sample.projectManagement?.taskRefMissingCount ?? null,
selectedTaskRefHash: sample.projectManagement?.selectedTaskRef?.hash ?? null,
selectedFileLabelPreview: sample.projectManagement?.selectedFileLabel?.textPreview ?? null,
selectedFileLabelLooksDirect: sample.projectManagement?.selectedFileLabelLooksDirect ?? null,
fileOptionSuspiciousLabelCount: sample.projectManagement?.fileOptionSuspiciousLabelCount ?? 0,
selectedTaskStatus: sample.projectManagement?.selectedTaskStatus ?? null,
taskBodyVisible: sample.projectManagement?.taskBodyVisible === true,
taskBodyBytes: sample.projectManagement?.taskBody?.textBytes ?? 0,
reportLinkCount: sample.projectManagement?.reportLinkCount ?? 0,
reportPreviewVisible: sample.projectManagement?.reportPreviewVisible === true,
reportPreviewBytes: sample.projectManagement?.reportPreview?.textBytes ?? 0,
reportFullscreenVisible: sample.projectManagement?.reportFullscreenVisible === true,
launchButtonVisible: sample.projectManagement?.launchButtonVisible === true,
launchButtonEnabled: sample.projectManagement?.launchButtonEnabled === true,
blockerCount: sample.projectManagement?.blockerCount ?? 0,
@@ -1106,6 +1141,11 @@ function compactProjectManagementForOutput(report) {
sessionId: item?.sessionId ?? null,
workbenchUrl: item?.workbenchUrl ?? null,
otelTraceId: item?.otelTraceId ?? null,
chatObserved: item?.chatObserved ?? null,
chatStatus: item?.chatStatus ?? null,
chatTraceId: item?.chatTraceId ?? null,
workbenchMessageCount: item?.workbenchMessageCount ?? null,
workbenchTraceRowCount: item?.workbenchTraceRowCount ?? null,
contractVersion: item?.contractVersion ?? null,
selectedTaskRefHash: item?.selectedTaskRefHash ?? null,
errorMessageHash: item?.errorMessageHash ?? null,
@@ -1161,7 +1201,7 @@ function compactProjectManagementForOutput(report) {
function projectManagementCommandRows(control, config) {
const allowed = new Set(config?.commandAllowlist || []);
const mdtodoCommandTypes = new Set(["gotoProjectMdtodo", "openMdtodoSourceConfig", "configureMdtodoHwpodSource", "probeMdtodoSource", "reindexMdtodoSource", "expandMdtodoTask", "editMdtodoTaskTitle", "editMdtodoTaskBody", "toggleMdtodoTaskStatus", "addMdtodoRootTask", "addMdtodoSubTask", "continueMdtodoTask", "deleteMdtodoTask", "launchWorkbenchFromMdtodo"]);
const mdtodoCommandTypes = new Set(["gotoProjectMdtodo", "openMdtodoSourceConfig", "configureMdtodoHwpodSource", "probeMdtodoSource", "reindexMdtodoSource", "expandMdtodoTask", "openMdtodoReportPreview", "toggleMdtodoReportFullscreen", "editMdtodoTaskInline", "editMdtodoTaskTitle", "editMdtodoTaskBody", "toggleMdtodoTaskStatus", "addMdtodoRootTask", "addMdtodoSubTask", "continueMdtodoTask", "deleteMdtodoTask", "launchWorkbenchFromMdtodo"]);
return (control || [])
.filter((item) => allowed.has(item?.type) || mdtodoCommandTypes.has(item?.type) || String(item?.type || "").startsWith("selectMdtodo") || item?.type === "selectProjectSource" || item?.type === "launchWorkbenchFromTask")
.filter((item) => item.phase === "completed" || item.phase === "failed")
@@ -1178,6 +1218,14 @@ function projectManagementCommandRows(control, config) {
sessionId: detail.sessionId ?? error.details?.sessionId ?? null,
workbenchUrl: detail.workbenchUrl ?? error.details?.workbenchUrl ?? null,
otelTraceId: detail.otelTraceId ?? error.details?.otelTraceId ?? null,
chatObserved: detail.chatObserved ?? error.details?.chatObserved ?? null,
chatStatus: detail.chatStatus ?? error.details?.chatStatus ?? null,
chatSessionId: detail.chatSessionId ?? error.details?.chatSessionId ?? null,
chatTraceId: detail.chatTraceId ?? error.details?.chatTraceId ?? null,
chatOtelTraceId: detail.chatOtelTraceId ?? error.details?.chatOtelTraceId ?? null,
workbenchMessageCount: detail.workbenchSnapshot?.messageCount ?? error.details?.workbenchSnapshot?.messageCount ?? null,
workbenchTraceRowCount: detail.workbenchSnapshot?.traceRowCount ?? error.details?.workbenchSnapshot?.traceRowCount ?? null,
workbenchComposerReady: detail.workbenchSnapshot?.composerReady ?? error.details?.workbenchSnapshot?.composerReady ?? null,
contractVersion: detail.contractVersion ?? error.details?.contractVersion ?? null,
selectedTaskRefHash: detail.selectedTask?.hash ?? detail.projectBeforeClick?.selectedTaskRef?.hash ?? null,
errorName: error.name ?? null,
@@ -1238,6 +1286,28 @@ function buildProjectManagementFindings(report) {
if (Number(summary.mdtodoSampleCount ?? 0) > 0 && Number(summary.latestTaskCount ?? 0) > 0 && Number(summary.launchButtonEnabledSamples ?? 0) === 0) {
findings.push({ id: "workbench-launch-button-unavailable", severity: "red", summary: "mdtodo tasks were sampled but the Workbench launch button was never enabled", count: summary.mdtodoSampleCount, latest: report.latest, valuesRedacted: true });
}
if (Number(summary.selectedFileLabelBadSampleCount ?? 0) > 0 || summary.latestSelectedFileLabelLooksDirect === false) {
findings.push({ id: "mdtodo-file-label-not-filename", severity: "red", summary: "MDTODO file dropdown selected label is not a direct markdown filename", count: summary.selectedFileLabelBadSampleCount, latestSelectedFileLabelPreview: summary.latestSelectedFileLabelPreview, samples: report.samples.filter((item) => item.selectedFileLabelLooksDirect === false).slice(0, 12), valuesRedacted: true });
}
if (Number(summary.maxFileOptionSuspiciousLabelCount ?? 0) > 0) {
findings.push({ id: "mdtodo-nondirect-files-visible", severity: "red", summary: "MDTODO file dropdown includes non-direct or report-like markdown labels; docs/MDTODO discovery must be direct files only", count: summary.maxFileOptionSuspiciousLabelCount, samples: report.samples.filter((item) => Number(item.fileOptionSuspiciousLabelCount ?? 0) > 0).slice(0, 12), valuesRedacted: true });
}
if (Number(summary.selectedTaskSampleCount ?? 0) > 0 && Number(summary.selectedTaskBodyVisibleSamples ?? 0) === 0) {
findings.push({ id: "mdtodo-task-body-not-visible", severity: "red", summary: "selected MDTODO task was sampled but no rendered task body was visible", count: summary.selectedTaskSampleCount, samples: report.samples.filter((item) => item.selectedTaskRefHash).slice(-12), valuesRedacted: true });
}
if (Number(summary.maxReportLinkCount ?? 0) > 0 && Number(summary.reportPreviewVisibleSamples ?? 0) === 0) {
const severity = Number(summary.reportPreviewCommandCount ?? 0) > 0 ? "red" : "amber";
findings.push({ id: "mdtodo-report-preview-missing", severity, summary: "MDTODO report links were visible but no markdown report preview was sampled", count: summary.maxReportLinkCount, previewCommandCount: summary.reportPreviewCommandCount, samples: report.samples.filter((item) => Number(item.reportLinkCount ?? 0) > 0).slice(-12), valuesRedacted: true });
}
if (Number(summary.reportPreviewCommandCount ?? 0) > 0 && Number(summary.reportFullscreenVisibleSamples ?? 0) === 0 && report.commands.some((item) => item.type === "toggleMdtodoReportFullscreen" && item.phase === "completed")) {
findings.push({ id: "mdtodo-report-fullscreen-missing", severity: "red", summary: "toggleMdtodoReportFullscreen command completed but fullscreen report dialog was never sampled", count: summary.reportPreviewCommandCount, commands: report.commands.filter((item) => item.type === "toggleMdtodoReportFullscreen").slice(-8), valuesRedacted: true });
}
if (Number(summary.launchEmptyCount ?? 0) > 0) {
findings.push({ id: "mdtodo-workbench-launch-empty", severity: "red", summary: "MDTODO Workbench launch created a session without observing agent chat or visible message/trace content", count: summary.launchEmptyCount, commands: report.launchCommands.filter((item) => item.chatObserved !== true || (Number(item.workbenchMessageCount ?? 0) === 0 && Number(item.workbenchTraceRowCount ?? 0) === 0)).slice(0, 12), valuesRedacted: true });
}
if (Number(summary.mdtodoTaskCountMin ?? 0) > 0 && Number(summary.mdtodoTaskCountMax ?? 0) > 0 && (Number(summary.mdtodoTaskCountMax) - Number(summary.mdtodoTaskCountMin) >= 10 || Number(summary.mdtodoTaskCountMax) / Math.max(1, Number(summary.mdtodoTaskCountMin)) >= 2)) {
findings.push({ id: "mdtodo-task-count-diverged", severity: "amber", summary: "MDTODO task count varied sharply during observation; compare control commands and observer samples for projection divergence", minTaskCount: summary.mdtodoTaskCountMin, maxTaskCount: summary.mdtodoTaskCountMax, samples: report.samples.slice(-20), valuesRedacted: true });
}
if (Number(summary.projectApiFailureCount ?? 0) > 0 || Number(summary.projectApiRequestFailedCount ?? 0) > 0) {
findings.push({ id: "project-management-api-failed", severity: "amber", summary: "natural project-management or Workbench launch API requests failed during observation", count: Number(summary.projectApiFailureCount ?? 0) + Number(summary.projectApiRequestFailedCount ?? 0), groups: report.projectApiByPath.slice(0, 12), valuesRedacted: true });
}
@@ -1264,6 +1334,11 @@ function maxNumber(values) {
return numeric.length > 0 ? Math.max(...numeric) : 0;
}
function minNumber(values) {
const numeric = (values || []).map((value) => Number(value)).filter(Number.isFinite);
return numeric.length > 0 ? Math.min(...numeric) : 0;
}
function buildSessionInvariantFindings(control, manifest = {}) {
const findings = [];
for (const row of control || []) {
+11 -11
View File
@@ -532,24 +532,24 @@ function projectSummaryFromSamples(){
const projectSamples=samples.filter((sample)=>sample?.projectManagement&&typeof sample.projectManagement==='object');
const latest=projectSamples[projectSamples.length-1]||null;
const latestProject=latest?.projectManagement||null;
const projectCommandTypes=new Set(['gotoProjectMdtodo','openMdtodoSourceConfig','configureMdtodoHwpodSource','probeMdtodoSource','reindexMdtodoSource','selectProjectSource','selectMdtodoSource','selectMdtodoFile','selectMdtodoTask','expandMdtodoTask','editMdtodoTaskTitle','editMdtodoTaskBody','toggleMdtodoTaskStatus','addMdtodoRootTask','addMdtodoSubTask','continueMdtodoTask','deleteMdtodoTask','launchWorkbenchFromTask','launchWorkbenchFromMdtodo']);
const mutationTypes=new Set(['configureMdtodoHwpodSource','probeMdtodoSource','reindexMdtodoSource','editMdtodoTaskTitle','editMdtodoTaskBody','toggleMdtodoTaskStatus','addMdtodoRootTask','addMdtodoSubTask','continueMdtodoTask','deleteMdtodoTask']);
const projectCommandTypes=new Set(['gotoProjectMdtodo','openMdtodoSourceConfig','configureMdtodoHwpodSource','probeMdtodoSource','reindexMdtodoSource','selectProjectSource','selectMdtodoSource','selectMdtodoFile','selectMdtodoTask','expandMdtodoTask','openMdtodoReportPreview','toggleMdtodoReportFullscreen','editMdtodoTaskInline','editMdtodoTaskTitle','editMdtodoTaskBody','toggleMdtodoTaskStatus','addMdtodoRootTask','addMdtodoSubTask','continueMdtodoTask','deleteMdtodoTask','launchWorkbenchFromTask','launchWorkbenchFromMdtodo']);
const mutationTypes=new Set(['configureMdtodoHwpodSource','probeMdtodoSource','reindexMdtodoSource','editMdtodoTaskInline','editMdtodoTaskTitle','editMdtodoTaskBody','toggleMdtodoTaskStatus','addMdtodoRootTask','addMdtodoSubTask','continueMdtodoTask','deleteMdtodoTask']);
const commandRows=control.filter((item)=>projectCommandTypes.has(item.type)&&(item.phase==='completed'||item.phase==='failed')).map((item)=>{
const detail=item.detail&&typeof item.detail==='object'?item.detail:{};
const error=detail.error&&typeof detail.error==='object'?detail.error:{};
return {ts:item.ts||null,phase:item.phase||null,type:item.type||null,commandId:item.commandId||null,afterPath:pathOnly(item.afterUrl),selectedTaskHash:detail.selectedTask?.hash??detail.projectBeforeClick?.selectedTaskRef?.hash??detail.afterProject?.selectedTaskRef?.hash??null,status:detail.launchStatus??error.details?.launchStatus??null,message:error.message?short(error.message,160):null,valuesRedacted:true};
return {ts:item.ts||null,phase:item.phase||null,type:item.type||null,commandId:item.commandId||null,afterPath:pathOnly(item.afterUrl),selectedTaskHash:detail.selectedTask?.hash??detail.projectBeforeClick?.selectedTaskRef?.hash??detail.afterProject?.selectedTaskRef?.hash??null,status:detail.launchStatus??error.details?.launchStatus??null,chatObserved:detail.chatObserved??error.details?.chatObserved??null,workbenchMessageCount:detail.workbenchSnapshot?.messageCount??error.details?.workbenchSnapshot?.messageCount??null,workbenchTraceRowCount:detail.workbenchSnapshot?.traceRowCount??error.details?.workbenchSnapshot?.traceRowCount??null,message:error.message?short(error.message,160):null,valuesRedacted:true};
});
const mutations=commandRows.filter((item)=>mutationTypes.has(item.type));
const launches=control.filter((item)=>(item.type==='launchWorkbenchFromTask'||item.type==='launchWorkbenchFromMdtodo')&&(item.phase==='completed'||item.phase==='failed')).map((item)=>{
const detail=item.detail&&typeof item.detail==='object'?item.detail:{};
const error=detail.error&&typeof detail.error==='object'?detail.error:{};
return {ts:item.ts||null,phase:item.phase||null,commandId:item.commandId||null,status:detail.launchStatus??error.details?.launchStatus??null,sessionId:detail.sessionId??error.details?.sessionId??null,workbenchUrl:detail.workbenchUrl??error.details?.workbenchUrl??null,otelTraceId:detail.otelTraceId??error.details?.otelTraceId??null,taskHash:detail.selectedTask?.hash??detail.projectBeforeClick?.selectedTaskRef?.hash??null,message:error.message?short(error.message,160):null,valuesRedacted:true};
return {ts:item.ts||null,phase:item.phase||null,commandId:item.commandId||null,status:detail.launchStatus??error.details?.launchStatus??null,sessionId:detail.sessionId??error.details?.sessionId??null,workbenchUrl:detail.workbenchUrl??error.details?.workbenchUrl??null,otelTraceId:detail.otelTraceId??error.details?.otelTraceId??null,chatObserved:detail.chatObserved??error.details?.chatObserved??null,chatStatus:detail.chatStatus??error.details?.chatStatus??null,chatTraceId:detail.chatTraceId??error.details?.chatTraceId??null,workbenchMessageCount:detail.workbenchSnapshot?.messageCount??error.details?.workbenchSnapshot?.messageCount??null,workbenchTraceRowCount:detail.workbenchSnapshot?.traceRowCount??error.details?.workbenchSnapshot?.traceRowCount??null,taskHash:detail.selectedTask?.hash??detail.projectBeforeClick?.selectedTaskRef?.hash??null,message:error.message?short(error.message,160):null,valuesRedacted:true};
});
const findings=Array.isArray(report.findings)?report.findings.filter((item)=>String(item?.id||item?.kind||'').match(/project-management|mdtodo|workbench-launch/u)).slice(0,20):[];
const summary=report.projectManagement?.summary||{};
const mdtodoSampleCount=projectSamples.filter((sample)=>sample.projectManagement?.pageKind==='project-management-mdtodo').length;
const derived={enabled:summary.enabled===true||projectSamples.length>0,projectSampleCount:Math.max(Number(summary.projectSampleCount??0),projectSamples.length),mdtodoSampleCount:Math.max(Number(summary.mdtodoSampleCount??0),mdtodoSampleCount),latestPageKind:summary.latestPageKind??latestProject?.pageKind??null,latestPath:summary.latestPath??latest?.path??null,latestSeq:summary.latestSeq??latest?.seq??null,latestTs:summary.latestTs??latest?.ts??null,latestSourceCount:summary.latestSourceCount??latestProject?.sourceCount??null,latestFileCount:summary.latestFileCount??latestProject?.fileCount??null,latestTaskCount:summary.latestTaskCount??latestProject?.taskCount??null,latestSelectedTaskRefHash:summary.latestSelectedTaskRefHash??latestProject?.selectedTaskRef?.hash??null,latestSelectedTaskRefPreview:summary.latestSelectedTaskRefPreview??latestProject?.selectedTaskRef?.preview??null,projectCommandCount:commandRows.length,mutationCommandCount:mutations.length,mutationFailureCount:mutations.filter((item)=>item.phase==='failed').length,launchCommandCount:summary.launchCommandCount??launches.length,launchSuccessCount:summary.launchSuccessCount??launches.filter((item)=>Number(item.status)>=200&&Number(item.status)<300).length,launchFailureCount:summary.launchFailureCount??launches.filter((item)=>item.phase==='failed'||Number(item.status)>=400).length,launchWithOtelTraceHeaderCount:summary.launchWithOtelTraceHeaderCount??launches.filter((item)=>item.otelTraceId).length,projectApiResponseCount:summary.projectApiResponseCount??null,projectApiFailureCount:summary.projectApiFailureCount??null,projectApiRequestFailedCount:summary.projectApiRequestFailedCount??null,projectApiSlowPathCount:summary.projectApiSlowPathCount??null,valuesRedacted:true};
return {summary:derived,commands:commandRows.slice(-24),mutations:mutations.slice(-16),launches:launches.slice(-12),findings,sampleRows:projectSamples.slice(-12).map((sample)=>({seq:sample.seq??null,ts:sample.ts??null,pageRole:sample.pageRole??null,path:sample.path??null,pageKind:sample.projectManagement?.pageKind??null,sourceCount:sample.projectManagement?.sourceCount??null,fileCount:sample.projectManagement?.fileCount??null,taskCount:sample.projectManagement?.taskCount??null,selectedTaskRefHash:sample.projectManagement?.selectedTaskRef?.hash??null,selectedTaskStatus:sample.projectManagement?.selectedTaskStatus??null,launchButtonEnabled:sample.projectManagement?.launchButtonEnabled===true,workbenchLinkCount:sample.projectManagement?.workbenchLinkCount??0,valuesRedacted:true})),valuesRedacted:true};
const derived={enabled:summary.enabled===true||projectSamples.length>0,projectSampleCount:Math.max(Number(summary.projectSampleCount??0),projectSamples.length),mdtodoSampleCount:Math.max(Number(summary.mdtodoSampleCount??0),mdtodoSampleCount),latestPageKind:summary.latestPageKind??latestProject?.pageKind??null,latestPath:summary.latestPath??latest?.path??null,latestSeq:summary.latestSeq??latest?.seq??null,latestTs:summary.latestTs??latest?.ts??null,latestSourceCount:summary.latestSourceCount??latestProject?.sourceCount??null,latestFileCount:summary.latestFileCount??latestProject?.fileCount??null,latestTaskCount:summary.latestTaskCount??latestProject?.taskCount??null,latestSelectedTaskRefHash:summary.latestSelectedTaskRefHash??latestProject?.selectedTaskRef?.hash??null,latestSelectedTaskRefPreview:summary.latestSelectedTaskRefPreview??latestProject?.selectedTaskRef?.preview??null,latestSelectedFileLabelPreview:summary.latestSelectedFileLabelPreview??latestProject?.selectedFileLabel?.textPreview??null,latestSelectedFileLabelLooksDirect:summary.latestSelectedFileLabelLooksDirect??latestProject?.selectedFileLabelLooksDirect??null,selectedTaskBodyVisibleSamples:summary.selectedTaskBodyVisibleSamples??projectSamples.filter((sample)=>sample.projectManagement?.taskBodyVisible===true).length,reportLinkVisibleSamples:summary.reportLinkVisibleSamples??projectSamples.filter((sample)=>Number(sample.projectManagement?.reportLinkCount??0)>0).length,reportPreviewVisibleSamples:summary.reportPreviewVisibleSamples??projectSamples.filter((sample)=>sample.projectManagement?.reportPreviewVisible===true).length,reportFullscreenVisibleSamples:summary.reportFullscreenVisibleSamples??projectSamples.filter((sample)=>sample.projectManagement?.reportFullscreenVisible===true).length,projectCommandCount:commandRows.length,mutationCommandCount:mutations.length,mutationFailureCount:mutations.filter((item)=>item.phase==='failed').length,launchCommandCount:summary.launchCommandCount??launches.length,launchSuccessCount:summary.launchSuccessCount??launches.filter((item)=>Number(item.status)>=200&&Number(item.status)<300).length,launchFailureCount:summary.launchFailureCount??launches.filter((item)=>item.phase==='failed'||Number(item.status)>=400).length,launchNonEmptyCount:summary.launchNonEmptyCount??launches.filter((item)=>item.chatObserved===true&&(Number(item.workbenchMessageCount??0)>0||Number(item.workbenchTraceRowCount??0)>0)).length,launchEmptyCount:summary.launchEmptyCount??launches.filter((item)=>item.chatObserved!==true||(Number(item.workbenchMessageCount??0)===0&&Number(item.workbenchTraceRowCount??0)===0)).length,launchWithOtelTraceHeaderCount:summary.launchWithOtelTraceHeaderCount??launches.filter((item)=>item.otelTraceId).length,projectApiResponseCount:summary.projectApiResponseCount??null,projectApiFailureCount:summary.projectApiFailureCount??null,projectApiRequestFailedCount:summary.projectApiRequestFailedCount??null,projectApiSlowPathCount:summary.projectApiSlowPathCount??null,valuesRedacted:true};
return {summary:derived,commands:commandRows.slice(-24),mutations:mutations.slice(-16),launches:launches.slice(-12),findings,sampleRows:projectSamples.slice(-12).map((sample)=>({seq:sample.seq??null,ts:sample.ts??null,pageRole:sample.pageRole??null,path:sample.path??null,pageKind:sample.projectManagement?.pageKind??null,sourceCount:sample.projectManagement?.sourceCount??null,fileCount:sample.projectManagement?.fileCount??null,taskCount:sample.projectManagement?.taskCount??null,selectedFileLabelPreview:sample.projectManagement?.selectedFileLabel?.textPreview??null,selectedFileLabelLooksDirect:sample.projectManagement?.selectedFileLabelLooksDirect??null,selectedTaskRefHash:sample.projectManagement?.selectedTaskRef?.hash??null,selectedTaskStatus:sample.projectManagement?.selectedTaskStatus??null,taskBodyVisible:sample.projectManagement?.taskBodyVisible===true,taskBodyBytes:sample.projectManagement?.taskBody?.textBytes??0,reportLinkCount:sample.projectManagement?.reportLinkCount??0,reportPreviewVisible:sample.projectManagement?.reportPreviewVisible===true,reportPreviewBytes:sample.projectManagement?.reportPreview?.textBytes??0,reportFullscreenVisible:sample.projectManagement?.reportFullscreenVisible===true,launchButtonEnabled:sample.projectManagement?.launchButtonEnabled===true,workbenchLinkCount:sample.projectManagement?.workbenchLinkCount??0,valuesRedacted:true})),valuesRedacted:true};
}
function targetNodeFromStateDir(){
const parts=String(dir||'').split(/[\\\\/]+/u);
@@ -558,11 +558,11 @@ function targetNodeFromStateDir(){
}
function renderProjectSummary(project){
const s=project.summary||{};
const lines=['Project MDTODO observer '+(manifest.jobId||'-'),'=======================================================','enabled='+String(s.enabled===true)+' samples='+String(s.projectSampleCount??0)+' mdtodo='+String(s.mdtodoSampleCount??0)+' latest='+String(s.latestPageKind||'-')+' path='+String(s.latestPath||'-'),'counts source='+String(s.latestSourceCount??'-')+' file='+String(s.latestFileCount??'-')+' task='+String(s.latestTaskCount??'-')+' selectedTask='+String(s.latestSelectedTaskRefHash||'-'),'commands='+String(s.projectCommandCount??0)+' mutations='+String(s.mutationCommandCount??0)+' mutationFailures='+String(s.mutationFailureCount??0),'launch commands='+String(s.launchCommandCount??0)+' success='+String(s.launchSuccessCount??0)+' failure='+String(s.launchFailureCount??0)+' otelTraceHeader='+String(s.launchWithOtelTraceHeaderCount??0),'api responses='+String(s.projectApiResponseCount??'-')+' failures='+String(s.projectApiFailureCount??'-')+'/'+String(s.projectApiRequestFailedCount??'-')+' slowPaths='+String(s.projectApiSlowPathCount??'-'),'','Recent samples'];
for(const row of project.sampleRows.slice(-12)) lines.push('#'+String(row.seq??'-')+' '+String(row.ts||'-')+' '+String(row.pageRole||'-')+' '+String(row.pageKind||'-')+' src='+String(row.sourceCount??'-')+' files='+String(row.fileCount??'-')+' tasks='+String(row.taskCount??'-')+' selected='+String(row.selectedTaskRefHash||'-')+' launch='+String(row.launchButtonEnabled===true)+' links='+String(row.workbenchLinkCount??0));
const lines=['Project MDTODO observer '+(manifest.jobId||'-'),'=======================================================','enabled='+String(s.enabled===true)+' samples='+String(s.projectSampleCount??0)+' mdtodo='+String(s.mdtodoSampleCount??0)+' latest='+String(s.latestPageKind||'-')+' path='+String(s.latestPath||'-'),'counts source='+String(s.latestSourceCount??'-')+' file='+String(s.latestFileCount??'-')+' task='+String(s.latestTaskCount??'-')+' selectedTask='+String(s.latestSelectedTaskRefHash||'-'),'fileLabel='+short(s.latestSelectedFileLabelPreview||'-',80)+' direct='+String(s.latestSelectedFileLabelLooksDirect??'-')+' bodyVisibleSamples='+String(s.selectedTaskBodyVisibleSamples??'-')+' reportPreviewSamples='+String(s.reportPreviewVisibleSamples??'-')+' reportFullscreenSamples='+String(s.reportFullscreenVisibleSamples??'-'),'commands='+String(s.projectCommandCount??0)+' mutations='+String(s.mutationCommandCount??0)+' mutationFailures='+String(s.mutationFailureCount??0),'launch commands='+String(s.launchCommandCount??0)+' success='+String(s.launchSuccessCount??0)+' failure='+String(s.launchFailureCount??0)+' nonEmpty='+String(s.launchNonEmptyCount??'-')+' empty='+String(s.launchEmptyCount??'-')+' otelTraceHeader='+String(s.launchWithOtelTraceHeaderCount??0),'api responses='+String(s.projectApiResponseCount??'-')+' failures='+String(s.projectApiFailureCount??'-')+'/'+String(s.projectApiRequestFailedCount??'-')+' slowPaths='+String(s.projectApiSlowPathCount??'-'),'','Recent samples'];
for(const row of project.sampleRows.slice(-12)) lines.push('#'+String(row.seq??'-')+' '+String(row.ts||'-')+' '+String(row.pageRole||'-')+' '+String(row.pageKind||'-')+' src='+String(row.sourceCount??'-')+' files='+String(row.fileCount??'-')+' tasks='+String(row.taskCount??'-')+' fileLabel='+short(row.selectedFileLabelPreview||'-',50)+' body='+String(row.taskBodyVisible===true)+' report='+String(row.reportPreviewVisible===true)+' fullscreen='+String(row.reportFullscreenVisible===true)+' selected='+String(row.selectedTaskRefHash||'-')+' launch='+String(row.launchButtonEnabled===true)+' links='+String(row.workbenchLinkCount??0));
lines.push('','Launches');
if(project.launches.length===0) lines.push('-');
for(const item of project.launches.slice(-12)) lines.push(String(item.ts||'-')+' '+String(item.phase||'-')+' status='+String(item.status??'-')+' session='+String(item.sessionId||'-')+' otel='+String(item.otelTraceId||'-')+' task='+String(item.taskHash||'-'));
for(const item of project.launches.slice(-12)) lines.push(String(item.ts||'-')+' '+String(item.phase||'-')+' status='+String(item.status??'-')+' session='+String(item.sessionId||'-')+' chat='+String(item.chatObserved===true)+' chatStatus='+String(item.chatStatus??'-')+' msg='+String(item.workbenchMessageCount??'-')+' traceRows='+String(item.workbenchTraceRowCount??'-')+' otel='+String(item.otelTraceId||'-')+' task='+String(item.taskHash||'-'));
lines.push('','MDTODO commands');
if(project.commands.length===0) lines.push('-');
for(const item of project.commands.slice(-24)) lines.push(String(item.ts||'-')+' '+String(item.phase||'-')+' '+String(item.type||'-')+' path='+String(item.afterPath||'-')+' task='+String(item.selectedTaskHash||'-')+' status='+String(item.status??'-')+(item.message?' message='+short(item.message,120):''));
@@ -584,11 +584,11 @@ const rows=turnSummaryRows();
if(view==='project-summary'||view==='project-mdtodo-summary'){
const project=projectSummaryFromSamples();
const projectSummary={...project.summary,latestSelectedTaskRefPreview:short(project.summary?.latestSelectedTaskRefPreview,80)};
const projectSampleRows=project.sampleRows.slice(-4).map((item)=>({seq:item.seq??null,ts:short(item.ts,24),pageRole:short(item.pageRole,18),path:short(item.path,36),pageKind:short(item.pageKind,28),sourceCount:item.sourceCount??null,fileCount:item.fileCount??null,taskCount:item.taskCount??null,selectedTaskRefHash:short(item.selectedTaskRefHash,24),selectedTaskStatus:short(item.selectedTaskStatus,20),launchButtonEnabled:item.launchButtonEnabled===true,workbenchLinkCount:item.workbenchLinkCount??0,valuesRedacted:true}));
const projectSampleRows=project.sampleRows.slice(-4).map((item)=>({seq:item.seq??null,ts:short(item.ts,24),pageRole:short(item.pageRole,18),path:short(item.path,36),pageKind:short(item.pageKind,28),sourceCount:item.sourceCount??null,fileCount:item.fileCount??null,taskCount:item.taskCount??null,selectedFileLabelPreview:short(item.selectedFileLabelPreview,80),selectedFileLabelLooksDirect:item.selectedFileLabelLooksDirect??null,selectedTaskRefHash:short(item.selectedTaskRefHash,24),selectedTaskStatus:short(item.selectedTaskStatus,20),taskBodyVisible:item.taskBodyVisible===true,taskBodyBytes:item.taskBodyBytes??0,reportLinkCount:item.reportLinkCount??0,reportPreviewVisible:item.reportPreviewVisible===true,reportPreviewBytes:item.reportPreviewBytes??0,reportFullscreenVisible:item.reportFullscreenVisible===true,launchButtonEnabled:item.launchButtonEnabled===true,workbenchLinkCount:item.workbenchLinkCount??0,valuesRedacted:true}));
const compactCommand=(item)=>({ts:short(item.ts,24),phase:item.phase??null,type:short(item.type,28),commandId:short(item.commandId,24),afterPath:short(item.afterPath,36),selectedTaskHash:short(item.selectedTaskHash,24),status:item.status??null,message:item.message?short(item.message,80):null,valuesRedacted:true});
const projectCommands=project.commands.slice(-14).map(compactCommand);
const projectMutations=project.mutations.slice(-10).map(compactCommand);
const projectLaunches=project.launches.slice(-4).map((item)=>({ts:short(item.ts,24),phase:item.phase??null,commandId:short(item.commandId,24),status:item.status??null,sessionId:short(item.sessionId,28),workbenchUrl:short(item.workbenchUrl,52),otelTraceId:short(item.otelTraceId,28),taskHash:short(item.taskHash,24),message:item.message?short(item.message,80):null,valuesRedacted:true}));
const projectLaunches=project.launches.slice(-4).map((item)=>({ts:short(item.ts,24),phase:item.phase??null,commandId:short(item.commandId,24),status:item.status??null,sessionId:short(item.sessionId,28),workbenchUrl:short(item.workbenchUrl,52),otelTraceId:short(item.otelTraceId,28),chatObserved:item.chatObserved??null,chatStatus:item.chatStatus??null,chatTraceId:short(item.chatTraceId,28),workbenchMessageCount:item.workbenchMessageCount??null,workbenchTraceRowCount:item.workbenchTraceRowCount??null,taskHash:short(item.taskHash,24),message:item.message?short(item.message,80):null,valuesRedacted:true}));
const projectFindings=project.findings.slice(0,4).map((item)=>({severity:item.severity??item.level??null,id:short(item.id??item.kind??item.code,48),count:item.count??item.sampleCount??null,summary:short(item.summary??item.message,96),valuesRedacted:true}));
console.log(JSON.stringify({ok:true,command:'web-probe-observe collect',view,stateDir:dir,summary:projectSummary,sampleRowCount:project.sampleRows.length,commandCount:project.commands.length,mutationCount:project.mutations.length,launchCount:project.launches.length,findingCount:project.findings.length,sampleRows:projectSampleRows,commands:projectCommands,mutations:projectMutations,launches:projectLaunches,findings:projectFindings,sourceFiles:['samples.jsonl','control.jsonl','analysis/report.json'],valuesRedacted:true}));
process.exit(0);
@@ -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,
+6
View File
@@ -130,10 +130,13 @@ export type NodeWebProbeObserveCommandType =
| "selectMdtodoFile"
| "selectMdtodoTask"
| "expandMdtodoTask"
| "openMdtodoReportPreview"
| "toggleMdtodoReportFullscreen"
| "openMdtodoSourceConfig"
| "configureMdtodoHwpodSource"
| "probeMdtodoSource"
| "reindexMdtodoSource"
| "editMdtodoTaskInline"
| "editMdtodoTaskTitle"
| "editMdtodoTaskBody"
| "toggleMdtodoTaskStatus"
@@ -198,8 +201,11 @@ export interface NodeWebProbeObserveOptions {
commandBlocking: boolean | null;
commandSourceId: string | null;
commandFileRef: string | null;
commandFilename: string | null;
commandTaskRef: string | null;
commandTaskId: string | null;
commandField: string | null;
commandLink: string | null;
commandTitle: string | null;
commandBody: string | null;
commandStatus: string | null;
+19 -1
View File
@@ -209,9 +209,12 @@ export function parseNodeWebProbeObserveOptions(
"--finding-id",
"--source-id",
"--file-ref",
"--filename",
"--task-ref",
"--task",
"--task-id",
"--field",
"--link",
"--title",
"--body",
"--status",
@@ -270,8 +273,11 @@ export function parseNodeWebProbeObserveOptions(
const commandText = commandTextFromStdin ? readFileSync(0, "utf8") : commandTextOption;
const commandSourceId = optionValue(args, "--source-id") ?? null;
const commandFileRef = optionValue(args, "--file-ref") ?? null;
const commandFilename = optionValue(args, "--filename") ?? null;
const commandTaskRef = optionValue(args, "--task-ref") ?? null;
const commandTaskId = optionValue(args, "--task-id") ?? optionValue(args, "--task") ?? null;
const commandField = optionValue(args, "--field") ?? null;
const commandLink = optionValue(args, "--link") ?? null;
const commandTitle = optionValue(args, "--title") ?? null;
const commandBody = optionValue(args, "--body") ?? null;
const commandStatus = optionValue(args, "--status") ?? null;
@@ -296,8 +302,11 @@ export function parseNodeWebProbeObserveOptions(
["--finding-id", commandFindingId],
["--source-id", commandSourceId],
["--file-ref", commandFileRef],
["--filename", commandFilename],
["--task-ref", commandTaskRef],
["--task/--task-id", commandTaskId],
["--field", commandField],
["--link", commandLink],
["--title", commandTitle],
["--body", commandBody],
["--status", commandStatus],
@@ -359,8 +368,11 @@ export function parseNodeWebProbeObserveOptions(
commandBlocking,
commandSourceId,
commandFileRef,
commandFilename,
commandTaskRef,
commandTaskId,
commandField,
commandLink,
commandTitle,
commandBody,
commandStatus,
@@ -391,10 +403,13 @@ export function parseNodeWebProbeObserveCommandType(value: string): NodeWebProbe
|| value === "selectMdtodoFile"
|| value === "selectMdtodoTask"
|| value === "expandMdtodoTask"
|| value === "openMdtodoReportPreview"
|| value === "toggleMdtodoReportFullscreen"
|| value === "openMdtodoSourceConfig"
|| value === "configureMdtodoHwpodSource"
|| value === "probeMdtodoSource"
|| value === "reindexMdtodoSource"
|| value === "editMdtodoTaskInline"
|| value === "editMdtodoTaskTitle"
|| value === "editMdtodoTaskBody"
|| value === "toggleMdtodoTaskStatus"
@@ -408,7 +423,7 @@ export function parseNodeWebProbeObserveCommandType(value: string): NodeWebProbe
|| value === "mark"
|| value === "stop"
) return value;
throw new Error(`web-probe observe command --type must be login, preflight, goto, gotoProjectMdtodo, newSession, sendPrompt, steer, cancel, selectProvider, clickSession, refreshCurrentSession, switchAwayAndBack, assertSessionInvariant, selectProjectSource, selectMdtodoSource, selectMdtodoFile, selectMdtodoTask, expandMdtodoTask, openMdtodoSourceConfig, configureMdtodoHwpodSource, probeMdtodoSource, reindexMdtodoSource, editMdtodoTaskTitle, editMdtodoTaskBody, toggleMdtodoTaskStatus, addMdtodoRootTask, addMdtodoSubTask, continueMdtodoTask, deleteMdtodoTask, launchWorkbenchFromTask, launchWorkbenchFromMdtodo, screenshot, mark, or stop; got ${value}`);
throw new Error(`web-probe observe command --type must be login, preflight, goto, gotoProjectMdtodo, newSession, sendPrompt, steer, cancel, selectProvider, clickSession, refreshCurrentSession, switchAwayAndBack, assertSessionInvariant, selectProjectSource, selectMdtodoSource, selectMdtodoFile, selectMdtodoTask, expandMdtodoTask, openMdtodoReportPreview, toggleMdtodoReportFullscreen, openMdtodoSourceConfig, configureMdtodoHwpodSource, probeMdtodoSource, reindexMdtodoSource, editMdtodoTaskInline, editMdtodoTaskTitle, editMdtodoTaskBody, toggleMdtodoTaskStatus, addMdtodoRootTask, addMdtodoSubTask, continueMdtodoTask, deleteMdtodoTask, launchWorkbenchFromTask, launchWorkbenchFromMdtodo, screenshot, mark, or stop; got ${value}`);
}
export function parseWebProbeBrowserProxyMode(value: string | undefined): WebProbeBrowserProxyMode {
@@ -1369,8 +1384,11 @@ export function runNodeWebProbeObserveCommand(options: NodeWebProbeObserveOption
blocking: options.commandBlocking,
sourceId: options.commandSourceId,
fileRef: options.commandFileRef,
filename: options.commandFilename,
taskRef: options.commandTaskRef,
taskId: options.commandTaskId,
field: options.commandField,
link: options.commandLink,
title: options.commandTitle,
body: options.commandBody,
status: options.commandStatus,