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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user