272 lines
16 KiB
JavaScript
272 lines
16 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { execFileSync } from 'node:child_process';
|
|
|
|
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 = {
|
|
expectedRunId: '', identifier: '', tid: '', ddid: '', departureDate: '',
|
|
expectedNote: '', expectedPayloadSha256: '', platformTarget: DEFAULT_PLATFORM_TARGET,
|
|
cdp: process.env.CDP_CLI || DEFAULT_CDP, timeoutMs: 45_000
|
|
};
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const value = argv[index];
|
|
if (value === '--expected-run-id') args.expectedRunId = argv[++index] || '';
|
|
else if (value === '--identifier') args.identifier = argv[++index] || '';
|
|
else if (value === '--tid') args.tid = argv[++index] || '';
|
|
else if (value === '--ddid') args.ddid = argv[++index] || '';
|
|
else if (value === '--departure-date') args.departureDate = argv[++index] || '';
|
|
else if (value === '--expected-note') args.expectedNote = argv[++index] || '';
|
|
else if (value === '--expected-payload-sha256') args.expectedPayloadSha256 = 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}`);
|
|
}
|
|
const errors = [];
|
|
if (!args.expectedRunId || !args.expectedNote.includes(args.expectedRunId)) errors.push('run_id_mismatch');
|
|
if (!args.identifier.includes('TEST-202609') || !args.expectedNote.includes('TEST-202609')) errors.push('marker_mismatch');
|
|
if (!/^\d+$/.test(args.tid) || !/^\d+$/.test(args.ddid)) errors.push('numeric_ref_missing');
|
|
if (!/^2026-09-\d{2}$/.test(args.departureDate)) errors.push('date_outside_2026_09');
|
|
if (!/^[a-f0-9]{64}$/.test(args.expectedPayloadSha256)) errors.push('payload_hash_invalid');
|
|
if (!Number.isFinite(args.timeoutMs) || args.timeoutMs < 10_000) errors.push('timeout_invalid');
|
|
if (errors.length) throw new Error(`native baseline safety check failed: ${errors.join(',')}`);
|
|
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));
|
|
}
|
|
|
|
const args = parseArgs(process.argv.slice(2));
|
|
parseJson(cdp(args, 'eval', args.platformTarget, `(async()=>JSON.stringify(await sendToExtension('PING',{},3000)))()`), 'extension ping');
|
|
const targetList = parseJson(cdp(args, 'evalraw', args.platformTarget, 'Target.getTargets', '{}'), 'target list');
|
|
const worker = targetList.targetInfos?.find((target) => (
|
|
target.type === 'service_worker' && target.url === `chrome-extension://${EXTENSION_ID}/background.js`
|
|
));
|
|
if (!worker?.targetId) throw new Error('extension_service_worker_missing');
|
|
const attached = parseJson(cdp(args, 'evalraw', args.platformTarget, 'Target.attachToTarget', JSON.stringify({
|
|
targetId: worker.targetId, flatten: false
|
|
})), 'service worker attach');
|
|
if (!attached.sessionId) throw new Error('extension_service_worker_attach_failed');
|
|
|
|
const taskKey = `NATIVE-UPDATE-BASELINE-${args.expectedRunId}-${Date.now()}`;
|
|
const operation = {
|
|
runId: args.expectedRunId,
|
|
identifier: args.identifier,
|
|
tid: args.tid,
|
|
ddid: args.ddid,
|
|
departureDate: args.departureDate,
|
|
expectedNote: args.expectedNote,
|
|
expectedPayloadSha256: args.expectedPayloadSha256
|
|
};
|
|
const expression = `(async()=>{
|
|
const operation=${JSON.stringify(operation)};
|
|
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 candidates=await chrome.scripting.executeScript({
|
|
target:{tabId:tab.id,allFrames:true},world:"MAIN",
|
|
func:(operation)=>{
|
|
const form=document.querySelector('#ListForm,form[name="ListForm"]');
|
|
const ddid=String(form?.querySelector('[name="ddid"]')?.value||"");
|
|
const tid=String(form?.querySelector('[name="tdid"]')?.value||"");
|
|
const note=String(form?.querySelector('[name="xiadanbeizhu"]')?.value||"");
|
|
return {
|
|
pathname:String(location.pathname||""),title:document.title,
|
|
matched:/\\/system\\/business\\/orders_add\\.asp$/i.test(location.pathname||"")
|
|
&&ddid===operation.ddid&&tid===operation.tid&¬e===operation.expectedNote,
|
|
ddid_matched:ddid===operation.ddid,tid_matched:tid===operation.tid,
|
|
note_matched:note===operation.expectedNote,marker_matched:note.includes("TEST-202609")
|
|
};
|
|
},args:[operation]
|
|
});
|
|
const exact=candidates.filter((item)=>item.result?.matched===true);
|
|
if(exact.length!==1)throw new Error("exact_business_frame_count_"+exact.length);
|
|
const frameId=exact[0].frameId;
|
|
const injected=await chrome.scripting.executeScript({
|
|
target:{tabId:tab.id,frameIds:[frameId]},world:"MAIN",
|
|
func:async(operation)=>{
|
|
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 dateYyyyMD=(value)=>String(value||"").split("-").map((part,index)=>index?String(Number(part)):part).join("-");
|
|
const listUrl=new URL("/System/DAT/orders.asp",location.origin);
|
|
listUrl.searchParams.set("Act","JH_OrderList");
|
|
const listParams=new URLSearchParams({
|
|
Tpage:"1",P_Size:"50",riqi:"chufari",S_chufariqi:dateYyyyMD(operation.departureDate),
|
|
S_chufarizhi:dateYyyyMD(operation.departureDate),S_fabudanwei:"老挝联泰",
|
|
S_tuanxuhao:operation.identifier,S_zhuangtai:""
|
|
});
|
|
const listResponse=await fetch(listUrl.href,{
|
|
method:"POST",credentials:"same-origin",
|
|
headers:{"Content-Type":"application/x-www-form-urlencoded; charset=UTF-8"},body:listParams.toString()
|
|
});
|
|
const listText=await listResponse.text();
|
|
const listDocument=new DOMParser().parseFromString("<table><tbody>"+listText+"</tbody></table>","text/html");
|
|
const rows=[...listDocument.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(operation.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 rowTid=(row?.id||"").match(/^tr[_-](\\d+)$/i)?.[1]
|
|
||joined.match(/[?&]tid=(\\d+)/i)?.[1]||"";
|
|
const rowDdid=joined.match(/OPEN_update\\(\\s*["']?(\\d+)["']?\\s*,\\s*["']?0["']?\\s*,/i)?.[1]
|
|
||joined.match(/[?&]ddid=(\\d+)/i)?.[1]||"";
|
|
const evidence=row?((row.innerText||row.textContent||"")+" "+joined).replace(/\\s+/g," "):"";
|
|
const ownership={
|
|
http_status:listResponse.status,candidate_count:rows.length,tid_matched:rowTid===operation.tid,
|
|
ddid_matched:rowDdid===operation.ddid,identifier_matched:evidence.includes(operation.identifier),
|
|
marker_matched:evidence.includes("TEST-202609"),account_matched:evidence.includes("测试ai员工账号"),
|
|
login_timeout:/登录|登陆|login|验证码/i.test(listText),permission_error:/权限|permission|无权/i.test(listText)
|
|
};
|
|
ownership.matched=listResponse.ok&&ownership.candidate_count===1&&ownership.tid_matched&&ownership.ddid_matched
|
|
&&ownership.identifier_matched&&ownership.marker_matched&&ownership.account_matched
|
|
&&!ownership.login_timeout&&!ownership.permission_error;
|
|
if(!ownership.matched)return {status:"blocked",stage:"ownership_preflight",ownership,write_attempted:false,no_erp_write:true};
|
|
|
|
const form=document.querySelector('#ListForm,form[name="ListForm"]');
|
|
const jq=window.jQuery||window.$;
|
|
const note=form?.querySelector('[name="xiadanbeizhu"]');
|
|
const ddid=String(form?.querySelector('[name="ddid"]')?.value||"");
|
|
const tid=String(form?.querySelector('[name="tdid"]')?.value||"");
|
|
if(!form||!jq||typeof window.SubmitInfoForm!=="function"||ddid!==operation.ddid||tid!==operation.tid
|
|
||String(note?.value||"")!==operation.expectedNote||!String(note?.value||"").includes("TEST-202609")){
|
|
return {status:"blocked",stage:"form_preflight",ownership,write_attempted:false,no_erp_write:true};
|
|
}
|
|
const requestBody="Act=DoInfoJH&"+jq(form).serialize();
|
|
const requestHash=await sha256(requestBody);
|
|
if(requestHash!==operation.expectedPayloadSha256){
|
|
return {status:"blocked",stage:"payload_preflight",ownership,request_sha256:requestHash,write_attempted:false,no_erp_write:true};
|
|
}
|
|
|
|
const originalAjax=jq.ajax;
|
|
const originalGlobalEval=jq.globalEval;
|
|
const originalAlert=window.alert;
|
|
const originalDialogAlert=jq.dialog?.alert;
|
|
const alerts=[];
|
|
const records=[];
|
|
let responseScript="";
|
|
window.alert=(message)=>{alerts.push(String(message||""))};
|
|
if(jq.dialog&&typeof originalDialogAlert==="function")jq.dialog.alert=(message)=>{alerts.push(String(message||""))};
|
|
if(typeof originalGlobalEval==="function")jq.globalEval=(code)=>{responseScript+=String(code||"")};
|
|
jq.ajax=function(optionsArg,...rest){
|
|
const config=typeof optionsArg==="string"?{url:optionsArg}:{...(optionsArg||{})};
|
|
const data=String(config.data||"");
|
|
const exactRequest=/\\/System\\/DAT\\/orders\\.asp/i.test(String(config.url||""))&&data===requestBody;
|
|
const record={exact_request:exactRequest,data_type:String(config.dataType||""),method:String(config.type||config.method||""),completed:false,ok:false};
|
|
records.push(record);
|
|
if(!exactRequest){record.completed=true;record.blocked=true;return {readyState:4,status:0,abort(){}}}
|
|
const originalSuccess=config.success;
|
|
const originalError=config.error;
|
|
const originalComplete=config.complete;
|
|
config.success=function(responseText,textStatus){
|
|
record.ok=true;record.text_status=String(textStatus||"");record.response_text=String(responseText||"");
|
|
return originalSuccess?originalSuccess.apply(this,arguments):undefined;
|
|
};
|
|
config.error=function(jqXHR,textStatus){record.ok=false;record.text_status=String(textStatus||"");return originalError?originalError.apply(this,arguments):undefined};
|
|
config.complete=function(jqXHR,textStatus){
|
|
record.completed=true;record.http_status=Number(jqXHR?.status||0);record.response_text=record.response_text||String(jqXHR?.responseText||"");
|
|
record.text_status=record.text_status||String(textStatus||"");
|
|
return originalComplete?originalComplete.apply(this,arguments):undefined;
|
|
};
|
|
return originalAjax.call(this,config,...rest);
|
|
};
|
|
let thrown="";
|
|
try{window.SubmitInfoForm()}catch(error){thrown=String(error?.message||error)}
|
|
const started=Date.now();
|
|
while(Date.now()-started<30000&&!records.some((record)=>record.completed))await new Promise((resolve)=>setTimeout(resolve,250));
|
|
jq.ajax=originalAjax;
|
|
if(typeof originalGlobalEval==="function")jq.globalEval=originalGlobalEval;
|
|
window.alert=originalAlert;
|
|
if(jq.dialog&&typeof originalDialogAlert==="function")jq.dialog.alert=originalDialogAlert;
|
|
|
|
const detailUrl=new URL(location.href);
|
|
detailUrl.searchParams.set("_",String(Date.now()));
|
|
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 persistedNote=String(detailDocument.querySelector('[name="xiadanbeizhu"]')?.value||"");
|
|
const record=records[0]||{};
|
|
const responseText=String(record.response_text||responseScript||"");
|
|
const responseMessage=responseText.replace(/<[^>]+>/g," ").replace(/\\s+/g," ").trim().slice(0,500);
|
|
const requery={
|
|
http_status:detailResponse.status,matched:detailResponse.ok&&persistedNote===operation.expectedNote,
|
|
note_length:persistedNote.length,marker_present:persistedNote.includes("TEST-202609"),
|
|
login_timeout:/登录|登陆|login|验证码/i.test(detailText),permission_error:/权限|permission|无权/i.test(detailText)
|
|
};
|
|
const serverResponse={
|
|
http_status:Number(record.http_status||0),completed:record.completed===true,ok:record.ok===true,
|
|
response_bytes:new Blob([responseText]).size,response_sha256:await sha256(responseText),
|
|
response_message:responseMessage,success_hint:/成功|保存|完成|success|ok/i.test(responseMessage),
|
|
error_hint:/失败|错误|异常|error/i.test(responseMessage)&&!/成功|success|ok/i.test(responseMessage)
|
|
};
|
|
const completed=records.length===1&&record.exact_request===true&&!record.blocked&&!thrown
|
|
&&serverResponse.completed&&serverResponse.ok&&serverResponse.success_hint&&requery.matched;
|
|
return {
|
|
status:completed?"completed":"uncertain",stage:"native_independent_update_baseline",
|
|
resolved_refs:{identifier:operation.identifier,tid:operation.tid,ddid:operation.ddid,departure_date:operation.departureDate},
|
|
ownership,native_request:{action:"DoInfoJH",endpoint:"/System/DAT/orders.asp",method:"POST",data_type:record.data_type,request_sha256:requestHash},
|
|
server_response:serverResponse,requery,write_attempted:true,no_erp_write:false,
|
|
side_effects:["no_procurement","no_payment","no_notification","no_external_send"],manual_review_required:!completed
|
|
};
|
|
},args:[operation]
|
|
});
|
|
const report=injected[0]?.result||{status:"blocked",stage:"injection_result_missing",write_attempted:false,no_erp_write:true};
|
|
const saved=await chrome.storage.local.get("businessTaskResults");
|
|
const resultMap=saved.businessTaskResults||{};
|
|
resultMap[${JSON.stringify(taskKey)}]={...report,message:"native independent update baseline completed"};
|
|
await chrome.storage.local.set({businessTaskResults:resultMap});
|
|
return 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(taskKey)}},3000)))()`
|
|
), 'baseline result');
|
|
result = response?.result || null;
|
|
if (result && ['completed', 'uncertain', 'blocked'].includes(result.status)) break;
|
|
} catch {
|
|
// The bounded native baseline is still running.
|
|
}
|
|
await wait(500);
|
|
}
|
|
try {
|
|
cdp(args, 'evalraw', args.platformTarget, 'Target.detachFromTarget', JSON.stringify({ sessionId: attached.sessionId }));
|
|
} catch {
|
|
// Detachment is cleanup only.
|
|
}
|
|
if (!result) throw new Error(`native update baseline timed out after ${args.timeoutMs}ms; do not retry`);
|
|
console.log(JSON.stringify({ task_key: taskKey, result }, null, 2));
|
|
if (result.status !== 'completed') process.exitCode = 2;
|