zhuyuan-ice-gl-zy001/TOOL-SANDBOX/perception-kit/perceive.mjs

120 lines
6.5 KiB
JavaScript
Raw Permalink Normal View History

#!/usr/bin/env node
// perception-kit v1 — 铸渊自有双轨感知工具(神笔马良,宿主无关)
// 协议正本:TOOL-SANDBOX/perception-kit/TCS-PROTOCOL-PERCEPTION-TRANSLATION-V1-0001.tcs
// 用法:
// node perceive.mjs --app "HoloLake" # 对运行中的App窗口做双轨感知
// node perceive.mjs --url http://127.0.0.1:5210 # 对dev server做DOM轨感知
import { execFileSync } from "node:child_process";
import { createHash } from "node:crypto";
import fs from "node:fs";
import path from "node:path";
const KIT_DIR = decodeURIComponent(new URL(".", import.meta.url).pathname);
const OUT_DIR = path.join(KIT_DIR, "perceptions");
fs.mkdirSync(OUT_DIR, { recursive: true });
const shasum = (v) => createHash("sha256").update(v).digest("hex");
const run = (cmd, args, opts = {}) => {
try { return execFileSync(cmd, args, { encoding: "utf8", timeout: 15000, ...opts }); }
catch (e) { return { __error: String(e.message).slice(0, 200) }; }
};
const arg = (name) => {
const i = process.argv.indexOf(name);
return i >= 0 ? process.argv[i + 1] : null;
};
const now = () => new Date().toISOString();
const pid = now().replace(/[-:TZ.]/g, "").slice(0, 14) + "-" + Math.random().toString(36).slice(2, 6);
const receipt = {
schema: "guanghu.perception-receipt/v1",
perception_id: `ZY-PERC-${pid}`,
occurred_at: now(),
operator: "ICE-P-ZY001(铸渊)perception-kit v1",
target: arg("--app") ? `app:${arg("--app")}` : (arg("--url") ? `url:${arg("--url")}` : "unknown"),
semantic_track: {}, translation_track: {}, detector_findings: [], conclusion: "NOT_SEEN_NO_TARGET",
};
// ── 语义轨:进程状态 ─────────────────────────────
const procList = run("/bin/ps", ["-axo", "pid,comm,etime"]);
if (typeof procList === "string") {
const name = (arg("--app") || "HoloLake").toLowerCase();
const procs = procList.split("\n").filter((l) => l.toLowerCase().includes(name));
if (process.env.PERCEIVE_DEBUG) { console.error(`[debug] ps bytes=${procList.length} name=${name} matches=${procs.length}`); console.error(`[debug] head=${JSON.stringify(procList.slice(0,80))} tail=${JSON.stringify(procList.slice(-80))}`); }
receipt.semantic_track.process_state = {
found: procs.length,
sample: procs.slice(0, 5),
};
} else receipt.semantic_track.process_state = procList;
// ── 翻译轨:窗口截图 ─────────────────────────────
if (arg("--app")) {
// 用 CGWindowList 经 python 找窗口 ID(不弹权限窗:仅查询)
const winInfo = run("/usr/bin/python3", ["-c", `
import Quartz, json
wins = []
for w in Quartz.CGWindowListCopyWindowInfo(Quartz.kCGWindowListOptionOnScreenOnly | Quartz.kCGWindowListExcludeDesktopElements, Quartz.kCGNullWindowID):
name = w.get("kCGWindowName", "") or w.get("kCGWindowOwnerName", "")
if "${arg("--app")}".lower() in str(name).lower() or "${arg("--app")}".lower() in str(w.get("kCGWindowOwnerName", "")).lower():
wins.append({"id": w.get("kCGWindowNumber"), "name": name, "owner": w.get("kCGWindowOwnerName"), "bounds": list(w.get("kCGWindowBounds", {}).values())})
print(json.dumps(wins[:5]))
`]);
let win;
try { win = JSON.parse(winInfo); } catch { win = []; }
if (Array.isArray(win) && win.length) {
const shot = path.join(OUT_DIR, `${receipt.perception_id}.png`);
const cap = run("/usr/sbin/screencapture", ["-x", "-l", String(win[0].id), shot]);
if (!cap || cap.__error === undefined || fs.existsSync(shot)) {
const buf = fs.readFileSync(shot);
receipt.translation_track = {
capture_method: `screencapture -l ${win[0].id}`,
window: win[0],
screenshot_path: shot,
screenshot_sha256: shasum(buf),
screenshot_bytes: buf.length,
note: buf.length < 20000 && buf.length > 0 ? "截图异常小,可能无屏幕录制权限(黑屏帧)" : "ok",
};
} else receipt.translation_track = { error: cap.__error };
} else receipt.translation_track = { error: "window_not_found", queried: winInfo };
}
// ── 语义轨:DOM(url模式)+ 重影检测器 ─────────────
if (arg("--url")) {
const html = run("/usr/bin/curl", ["-s", "--max-time", "10", arg("--url")]);
if (typeof html === "string" && html.length) {
receipt.semantic_track.dom_snapshot_digest = {
url: arg("--url"),
bytes: html.length,
sha256: shasum(html),
title: (html.match(/<title[^>]*>([^<]*)<\/title>/i) || [, ""])[1],
element_counts: {
div: (html.match(/<div/g) || []).length,
button: (html.match(/<button/g) || []).length,
section: (html.match(/<section/g) || []).length,
canvas: (html.match(/<canvas/g) || []).length,
},
};
// 重影检测器:两个UI表面容器同时可见
const surfaceMarkers = ["official-world", "domain-home", "channel-world", "world-pool", "starlake", "traditional-surface", "qoder-home"];
const present = surfaceMarkers.filter((m) => html.includes(m));
if (present.length >= 2) {
receipt.detector_findings.push({
detector: "overlap_detector",
verdict: "SURFACES_COEXIST",
detail: `DOM中同时出现多个UI表面标记:${present.join(", ")}——疑似新旧UI叠加(重影),需与截图互证`,
});
}
fs.writeFileSync(path.join(OUT_DIR, `${receipt.perception_id}.dom.html`), html);
} else receipt.semantic_track.dom_snapshot_digest = { error: String(html.__error || "empty") };
}
// ── 结论综合 ─────────────────────────────────
const semanticOk = receipt.semantic_track.process_state?.found > 0 || receipt.semantic_track.dom_snapshot_digest?.sha256;
const shotOk = receipt.translation_track.screenshot_sha256 && receipt.translation_track.note === "ok";
const overlap = receipt.detector_findings.some((d) => d.verdict === "SURFACES_COEXIST");
receipt.conclusion = semanticOk && shotOk ? (overlap ? "SEEN_AND_UNEXPECTED" : "SEEN_AND_EXPECTED")
: (semanticOk || shotOk) ? "PARTIAL_SEEN_ONE_TRACK" : (receipt.translation_track.error ? "NOT_SEEN_CAPTURE_FAILED" : "NOT_SEEN_NO_TARGET");
const outFile = path.join(OUT_DIR, `${receipt.perception_id}.json`);
fs.writeFileSync(outFile, JSON.stringify(receipt, null, 2));
console.log(JSON.stringify({ perception_id: receipt.perception_id, conclusion: receipt.conclusion, semantic_ok: !!semanticOk, translation_ok: !!shotOk, findings: receipt.detector_findings.length, out: outFile }, null, 2));