#!/usr/bin/env node import { execFileSync } from 'node:child_process'; import { readFile } from 'node:fs/promises'; const DEFAULT_CDP = '/Users/inmanx/.agents/skills/chrome-cdp/scripts/cdp.mjs'; const DEFAULT_PLATFORM_TARGET = 'DA600CC0'; const EXTENSION_ID = 'kafggjjlhebccdgkechmbgflkaifkccf'; function parseArgs(argv) { const args = { targetsFile: '', expectedRunId: '', platformTarget: DEFAULT_PLATFORM_TARGET, cdp: process.env.CDP_CLI || DEFAULT_CDP, timeoutMs: 30_000 }; for (let index = 0; index < argv.length; index += 1) { const value = argv[index]; if (value === '--targets-file') args.targetsFile = argv[++index] || ''; else if (value === '--expected-run-id') args.expectedRunId = argv[++index] || ''; else if (value === '--platform-target') args.platformTarget = argv[++index] || ''; else if (value === '--cdp') args.cdp = argv[++index] || ''; else if (value === '--timeout-ms') args.timeoutMs = Number(argv[++index]); else throw new Error(`unknown argument: ${value}`); } if (!args.targetsFile) throw new Error('--targets-file is required'); if (!args.expectedRunId) throw new Error('--expected-run-id is required'); if (!Number.isFinite(args.timeoutMs) || args.timeoutMs < 5_000) throw new Error('--timeout-ms must be at least 5000'); return args; } function cdp(args, command, target, ...rest) { return execFileSync(process.execPath, [args.cdp, command, target, ...rest], { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 }).trim(); } function parseJson(value, label) { try { return JSON.parse(String(value || '').trim()); } catch { throw new Error(`${label} returned non-JSON output: ${String(value || '').slice(0, 500)}`); } } function wait(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function validateProbe(probe, expectedRunId) { const errors = []; if (probe?.run_id !== expectedRunId) errors.push('run_id_mismatch'); if (probe?.marker !== 'TEST-202609') errors.push('marker_mismatch'); if (probe?.account !== '测试ai员工账号') errors.push('account_mismatch'); if (probe?.expect_absent !== undefined && probe.expect_absent !== true) errors.push('expect_absent_invalid'); if (!Array.isArray(probe?.targets) || !probe.targets.length || probe.targets.length > 20) errors.push('target_count_invalid'); for (const target of probe?.targets || []) { if (!['independent_order', 'shared_plan', 'shared_child_order'].includes(target?.kind)) errors.push('target_kind_invalid'); if (target?.kind === 'shared_child_order') { if (!/^D\d+$/.test(String(target?.identifier || ''))) errors.push('child_identifier_invalid'); if (!String(target?.parent_group_no || '').includes('TEST-202609')) errors.push('target_marker_missing'); if (!/^\d+$/.test(String(target?.tid || '')) || !/^\d+$/.test(String(target?.ddid || ''))) errors.push('child_reference_invalid'); } else if (!String(target?.identifier || '').includes('TEST-202609')) errors.push('target_marker_missing'); if (!/^2026-09-\d{2}$/.test(String(target?.departure_date || ''))) errors.push('target_date_outside_2026_09'); } return [...new Set(errors)]; } function lookupExpression(probeKey, targets, expectAbsent = false) { return `(async()=>{ const tabs=await chrome.tabs.query({url:"https://lwlt.hisy.cc/*"}); const tab=tabs.find((candidate)=>/\\/System\\/Mainlt\\.asp/i.test(candidate.url||""))||tabs[0]; if(!tab?.id)throw new Error("erp_tab_missing"); const injected=await chrome.scripting.executeScript({ target:{tabId:tab.id}, world:"MAIN", func:async(targets,expectAbsent)=>{ const dateYyyyMD=(value)=>String(value||"").split("-").map((part,index)=>index?String(Number(part)):part).join("-"); const sha256=async(value)=>{ const digest=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(value)); return [...new Uint8Array(digest)].map((byte)=>byte.toString(16).padStart(2,"0")).join(""); }; const output=[]; for(const target of targets){ const independent=target.kind==="independent_order"; const endpoint=independent?"/System/DAT/orders.asp":"/System/DAT/plan.asp"; const action=independent?"JH_OrderList":"SP_OrderList"; const searchIdentifier=target.kind==="shared_child_order"?String(target.parent_group_no||""):target.identifier; const url=new URL(endpoint,location.origin); url.searchParams.set("Act",action); const date=dateYyyyMD(target.departure_date); const params=new URLSearchParams({ Tpage:"1",P_Size:"50",riqi:"chufari",S_chufariqi:date,S_chufarizhi:date, S_fabudanwei:"老挝联泰",S_tuanxuhao:searchIdentifier,S_zhuangtai:String(target.parent_status||"") }); const response=await fetch(url.href,{ method:"POST",credentials:"same-origin", headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"}, body:params.toString() }); const text=await response.text(); const documentFragment=new DOMParser().parseFromString(""+text+"
","text/html"); const rows=[...documentFragment.querySelectorAll("tr")].filter((row)=>{ const controls=[...row.querySelectorAll("input,a,button")] .map((element)=>[element.value||"",element.getAttribute("href")||"",element.getAttribute("onclick")||""].join(" ")).join(" "); const evidence=((row.innerText||row.textContent||"")+" "+controls).replace(/\\s+/g," "); const native=/^tr[_-]\\d+$/i.test(row.id||"")||Boolean(row.querySelector("input[name=xuanzeid],input#xuanzeid")); return native&&evidence.includes(target.identifier); }); const row=rows.length===1?rows[0]:null; const actions=row?[...row.querySelectorAll("[onclick],a[href]")] .map((element)=>((element.getAttribute("onclick")||"")+" "+(element.getAttribute("href")||"")).trim()):[]; const joined=actions.join(" "); const tid=(row?.id||"").match(/^tr[_-](\\d+)$/i)?.[1] ||joined.match(/[?&]tid=(\\d+)/i)?.[1] ||joined.match(/OPEN_finance_list\\(\\s*[^,]+,\\s*[\"']?(\\d+)/i)?.[1]||""; const ddid=independent ? joined.match(/OPEN_update\\(\\s*[\"']?(\\d+)[\"']?\\s*,\\s*[\"']?0[\"']?\\s*,/i)?.[1] ||joined.match(/[?&]ddid=(\\d+)/i)?.[1]||"" : ""; const childRefs=target.kind==="shared_plan"&&row&&tid ? [...new Map([...row.querySelectorAll("[onclick]")].map((element)=>{ const match=String(element.getAttribute("onclick")||"").match(/OPEN_update\\(\\s*(\\d+)\\s*,\\s*(\\d+)/i); return match&&match[1]===tid?[match[2],{tid:match[1],ddid:match[2]}]:null; }).filter(Boolean)).values()] : null; const nativeActionSignatures=[...joined.matchAll(/([A-Za-z_]+)\\(([^)]*)\\)/g)].map((match)=>({ name:match[1], numeric_args:(match[2].match(/\\d+/g)||[]) })); const evidence=row?((row.innerText||row.textContent||"")+" "+joined).replace(/\\s+/g," "):""; const markerMatched=evidence.includes("TEST-202609"); const accountMatched=evidence.includes("测试ai员工账号"); const loginTimeout=/登录|登陆|login|验证码/i.test(text); const permissionError=/权限|permission|无权/i.test(text); let childDetail=null; if(target.kind==="shared_child_order"){ const childDdid=String(target.ddid||String(target.identifier||"").replace(/^D/i,"")); const detailUrl=new URL("/System/Business/plan_order.asp",location.origin); detailUrl.searchParams.set("tdid",String(target.tid||tid)); detailUrl.searchParams.set("ddid",childDdid); const detailResponse=await fetch(detailUrl.href,{credentials:"same-origin",cache:"no-store"}); const detailText=await detailResponse.text(); const detailDocument=new DOMParser().parseFromString(detailText,"text/html"); const detailForm=detailDocument.querySelector("#ListForm,form[name=ListForm]"); const detailValue=(name)=>String(detailForm?.elements?.namedItem?.(name)?.value||detailDocument.querySelector('[name="'+name+'"]')?.getAttribute("value")||""); const detailStatus=detailValue("danzhuangtai"); const detailMarkerMatched=detailText.includes("TEST-202609"); const detailAccountMatched=detailText.includes("测试ai员工账号"); const detailTid=detailValue("oldtdid")||detailValue("tdid")||String(target.tid||tid); const detailDdid=detailValue("ddid"); childDetail={ http_status:detailResponse.status,current_status:detailStatus, form_present:Boolean(detailForm),marker_matched:detailMarkerMatched, account_matched:detailAccountMatched,tid_matched:detailTid===String(target.tid||tid), ddid_matched:detailDdid===childDdid,response_sha256:await sha256(detailText), expected_present:!expectAbsent,absence_proven:!detailForm&&!detailMarkerMatched, matched:detailResponse.ok&&(expectAbsent ? !detailForm&&!detailMarkerMatched : Boolean(detailForm)&&detailMarkerMatched&&detailAccountMatched &&detailTid===String(target.tid||tid)&&detailDdid===childDdid), value_redacted:true }; } output.push({ kind:target.kind,identifier:target.identifier,departure_date:target.departure_date, http_status:response.status,byte_length:new Blob([text]).size,response_sha256:await sha256(text), candidate_count:rows.length,tid,ddid,native_action_signatures:independent&&!ddid?nativeActionSignatures:[], child_count:Array.isArray(childRefs)?childRefs.length:null,child_refs:childRefs, marker_matched:markerMatched,account_matched:accountMatched, login_timeout:loginTimeout,permission_error:permissionError,child_detail:childDetail, expected_present:!expectAbsent,present:rows.length===1, matched:expectAbsent ? response.ok&&rows.length===0&&!loginTimeout&&!permissionError&&(!childDetail||childDetail.matched) : response.ok&&rows.length===1&&Boolean(tid)&&(!independent||Boolean(ddid)) &&markerMatched&&accountMatched&&!loginTimeout&&!permissionError &&(!childDetail||childDetail.matched) }); } return {url:location.href,results:output}; }, args:[${JSON.stringify(targets)},${JSON.stringify(expectAbsent)}] }); const report=injected[0]?.result||{results:[]}; const saved=await chrome.storage.local.get("businessTaskResults"); const resultMap=saved.businessTaskResults||{}; resultMap[${JSON.stringify(probeKey)}]={ status:"completed",stage:"read_only_reference_lookup",message:"exact native list lookup completed", write_attempted:false,no_erp_write:true, running_tasks:typeof runningTasks==="undefined"?[]:[...runningTasks],report }; await chrome.storage.local.set({businessTaskResults:resultMap}); return true; })()`; } const args = parseArgs(process.argv.slice(2)); const probe = JSON.parse(await readFile(args.targetsFile, 'utf8')); const safetyErrors = validateProbe(probe, args.expectedRunId); if (safetyErrors.length) throw new Error(`probe safety check failed: ${safetyErrors.join(',')}`); parseJson(cdp(args, 'eval', args.platformTarget, `(async()=>JSON.stringify(await sendToExtension('PING',{},3000)))()`), 'extension ping'); const targetsResult = parseJson(cdp(args, 'evalraw', args.platformTarget, 'Target.getTargets', '{}'), 'target list'); const serviceWorker = targetsResult.targetInfos?.find((target) => ( target.type === 'service_worker' && target.url === `chrome-extension://${EXTENSION_ID}/background.js` )); if (!serviceWorker?.targetId) throw new Error('extension_service_worker_missing'); const attached = parseJson(cdp(args, 'evalraw', args.platformTarget, 'Target.attachToTarget', JSON.stringify({ targetId: serviceWorker.targetId, flatten: false })), 'service worker attach'); if (!attached.sessionId) throw new Error('extension_service_worker_attach_failed'); const probeKey = `READONLY-REFS-${args.expectedRunId}-${Date.now()}`; const expression = lookupExpression(probeKey, probe.targets, probe.expect_absent === true); const message = JSON.stringify({ id: 1, method: 'Runtime.evaluate', params: { expression, awaitPromise: true, returnByValue: true } }); cdp(args, 'evalraw', args.platformTarget, 'Target.sendMessageToTarget', JSON.stringify({ sessionId: attached.sessionId, message })); const startedAt = Date.now(); let result = null; while (Date.now() - startedAt < args.timeoutMs) { try { const response = parseJson(cdp( args, 'eval', args.platformTarget, `(async()=>JSON.stringify(await sendToExtension('GET_TASK_RESULT',{task_id:${JSON.stringify(probeKey)}},3000)))()` ), 'probe result'); result = response?.result || null; if (result?.status === 'completed') break; } catch { // The extension worker may still be completing the bounded read-only lookup. } await wait(500); } try { cdp(args, 'evalraw', args.platformTarget, 'Target.detachFromTarget', JSON.stringify({ sessionId: attached.sessionId })); } catch { // Detachment is cleanup only; it does not affect the recorded read result. } if (!result) throw new Error(`read-only reference probe timed out after ${args.timeoutMs}ms`); const rows = result.report?.results || []; if (rows.length !== probe.targets.length || rows.some((row) => row.matched !== true)) { console.log(JSON.stringify({ status: 'read_only_reference_probe_blocked', probe_key: probeKey, result }, null, 2)); process.exitCode = 2; } else { console.log(JSON.stringify({ status: 'read_only_reference_probe_completed', probe_key: probeKey, result }, null, 2)); }