chore: archive tools/ui_inspector snapshot before migration removal.

Keep the last in-template copy on main so history can restore it; product continues in jiangchang-ui-recorder.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-07 11:19:14 +08:00
parent 46bc573777
commit 633ecff4e2
92 changed files with 13704 additions and 288 deletions

View File

@@ -21,14 +21,14 @@ except ImportError:
_mod = importlib.util.module_from_spec(_spec) # type: ignore[arg-type]
assert _spec and _spec.loader
_spec.loader.exec_module(_mod)
__version__ = getattr(_mod, "__version__", "0.8.0")
__version__ = getattr(_mod, "__version__", "0.16.0")
except Exception:
__version__ = "0.8.0"
__version__ = "0.16.0"
def _local_ts() -> str:
now = datetime.now().astimezone()
return now.strftime("%Y%m%dT%H%M%S%z")
"""Local wall-clock stamp for capture folder names: YYYYMMDDHHMMSS."""
return datetime.now().astimezone().strftime("%Y%m%d%H%M%S")
def safe_slug(text: str, *, fallback: str = "session") -> str:
@@ -405,11 +405,14 @@ def build_meta(
scene: str,
skill_root: Optional[str] = None,
extra: Optional[dict[str, Any]] = None,
surface: str = "windows",
driver: str = "uiautomation",
url: str = "",
) -> dict[str, Any]:
now = datetime.now().astimezone()
meta = {
"schema": "openclaw.ui_inspector.capture.v1",
"surface": "windows",
"surface": surface or "windows",
"captured_at": now.isoformat(),
"scene": scene,
"skill_root": skill_root,
@@ -419,6 +422,7 @@ def build_meta(
"process_name": window.get("process_name"),
"pid": window.get("pid"),
"hwnd": window.get("hwnd"),
"url": url or window.get("url") or "",
},
"environment": {
"os": "windows",
@@ -427,7 +431,7 @@ def build_meta(
"tool": {
"name": "openclaw-ui-inspector",
"version": __version__,
"driver": "uiautomation",
"driver": driver or "uiautomation",
},
}
if extra:
@@ -486,14 +490,18 @@ def capture_window_png(hwnd: int, dest: Path) -> bool:
def create_flow(
captures_root: Path,
*,
flow_name: str = "desktop-flow",
flow_name: str = "desktop",
recording_mode: str = "auto",
) -> Path:
"""开始流程:新建一份流程文档目录。"""
"""开始流程:新建一份流程文档目录(v1 steps + 可选 v2 资产目录)。"""
captures_root.mkdir(parents=True, exist_ok=True)
slug = safe_slug(flow_name, fallback="flow")
flow_dir = captures_root / f"{_local_ts()}_{slug}"
flow_dir.mkdir(parents=True, exist_ok=False)
(flow_dir / "steps").mkdir(parents=True, exist_ok=True)
# v2 dual-write directories (empty until recorder writes)
for sub in ("states", "observations", "actions", "elements", "journal", "diagnostics"):
(flow_dir / sub).mkdir(parents=True, exist_ok=True)
now = datetime.now().astimezone().isoformat()
flow = {
"schema": "openclaw.ui_inspector.flow.v1",
@@ -508,6 +516,21 @@ def create_flow(
"name": "openclaw-ui-inspector",
"version": __version__,
},
# Optional v2 pointers — old readers ignore unknown fields.
"v2": {
"recording_mode": recording_mode or "auto",
"action_flow": "action_flow.json",
"element_catalog": "element_catalog.json",
"states_dir": "states/",
"observations_dir": "observations/",
"actions_dir": "actions/",
"elements_dir": "elements/",
"raw_event_journal": "journal/raw_events.jsonl",
"pages": "pages.json",
"diagnostics_dir": "diagnostics/",
"validation": "diagnostics/validation.json",
"replay_report": "diagnostics/replay_report.json",
},
}
write_json(flow_dir / "flow.json", flow)
write_flow_summary(flow_dir, flow)
@@ -565,12 +588,16 @@ def register_step(
window: dict[str, Any],
stats: dict[str, Any],
label: Optional[str] = None,
surface: str = "windows",
url: str = "",
) -> dict[str, Any]:
entry = {
"index": step_index,
"name": step_name,
"label": label or step_name,
"captured_at": datetime.now().astimezone().isoformat(),
"surface": surface or "windows",
"url": url or window.get("url") or "",
"window_title": window.get("title"),
"window_class": window.get("class_name"),
"pid": window.get("pid"),
@@ -621,6 +648,9 @@ def write_flow_summary(flow_dir: Path, flow: dict[str, Any]) -> None:
lines.append("")
if s.get("label"):
lines.append(f"- label: `{s.get('label')}`")
lines.append(f"- surface: `{s.get('surface') or 'windows'}`")
if s.get("url"):
lines.append(f"- url: `{s.get('url')}`")
lines.append(
f"- window: `{s.get('window_title')}` / `{s.get('window_class')}`"
)
@@ -636,7 +666,10 @@ def write_flow_summary(flow_dir: Path, flow: dict[str, Any]) -> None:
"",
"- 一个流程对应本目录一份文档(`flow.json` + `SUMMARY.md` + `steps/`)。",
"- 每拾取一页,在 `steps/` 下新增一步;给 AI 时优先读本 `SUMMARY.md` 与各步 `locatable.json`。",
"- Name ≠ 界面中文时请看 Value。",
"- `surface=windows` 为桌面 UIA;`surface=browser` 为 Playwright 整页 dump。",
"- Name ≠ 界面中文时请看 Value;浏览器步优先看 `primary_locator` / `playwright_candidates`。",
"- v2(若存在):`action_flow.json` + `element_catalog.json` + `states/` + `actions/` + `elements/`。",
"- 给 AI 编程工具时优先提供 `action_flow.json` 与 `element_catalog.json`,诊断再用 steps/ 原始树。",
"",
]
)
@@ -651,13 +684,18 @@ def write_step_summary(
noteworthy: Optional[list[dict[str, Any]]] = None,
) -> None:
app = meta.get("app") or {}
tool = meta.get("tool") or {}
lines = [
"# Step Summary",
"",
f"- captured_at: `{meta.get('captured_at')}`",
f"- surface: `{meta.get('surface')}` driver: `{tool.get('driver')}`",
f"- window: `{app.get('title')}` / `{app.get('class_name')}` pid=`{app.get('pid')}`",
"",
]
if app.get("url"):
lines.append(f"- url: `{app.get('url')}`")
lines.append("")
if stats:
lines.extend(
[
@@ -673,9 +711,11 @@ def write_step_summary(
if noteworthy:
lines.extend(["## Notable", ""])
for item in noteworthy[:40]:
primary = (item.get("primary_locator") or {}).get("expr") or ""
extra = f" locator=`{primary}`" if primary else ""
lines.append(
f"- `{item.get('control_type')}` Name=`{item.get('name')}` "
f"Value=`{item.get('value')}`"
f"Value=`{item.get('value') or item.get('text')}`{extra}"
)
lines.append("")
lines.extend(
@@ -689,6 +729,7 @@ def write_step_summary(
(step_dir / "SUMMARY.md").write_text("\n".join(lines), encoding="utf-8")
# Back-compat aliases used by older smoke scripts
def create_session_dir(captures_root: Path, *, scene: str = "desktop") -> Path:
return create_flow(captures_root, flow_name=scene)