perception-kit v1 + 2026-08-25 各轮TCS正本/回执/状态(夜间工作)
- TOOL-SANDBOX/perception-kit: 双轨感知工具(协议TCS+GIR+perceive.mjs+验收回执) - ZCODE-DEV-20260825: round1-10 TCS事件正本/编译GIR/人类投影/脑周期状态/待读路线图 - 范围严格限定本session新增路径;既有脏变未触碰;未配置remote,未push
This commit is contained in:
parent
d5c932d8e1
commit
d38861fb9f
210 changed files with 30374 additions and 0 deletions
120
TOOL-SANDBOX/perception-kit/perceive.mjs
Executable file
120
TOOL-SANDBOX/perception-kit/perceive.mjs
Executable file
|
|
@ -0,0 +1,120 @@
|
|||
#!/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));
|
||||
Loading…
Reference in a new issue