fix: validate Cua window discovery filters and guide recovery

This commit is contained in:
chenmingxuan committed 2026-10-17 13:23:56 +08:00
1 parent c89588a80d
commit 1c1d700e9b
12 files changed
+337 -31

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
"""Opt-in read-only app discovery against an already open, named application."""
import argparse
import asyncio
import json
import os
from pathlib import Path
import jsonschema
from artemis.plugins.models import ServerConfig
from artemis.plugins.session import PluginSession
async def run(executable: Path, app: str, output: Path):
session = await PluginSession(
ServerConfig(command=str(executable)), executable.parent
).start()
try:
schema = next(t.inputSchema for t in session.tools if t.name == "list_windows")
query = {"app": app, "on_screen_only": True}
bad_query = {**query, "pid": 0}
try:
jsonschema.validate(bad_query, schema)
except jsonschema.ValidationError:
pass
else:
raise AssertionError("Host schema still accepts PID 0")
bad = await session.call("list_windows", bad_query)
assert bad.isError
assert bad.structuredContent["artemis"]["execution"] == "not_started"
assert "omit pid" in "\n".join(b.text for b in bad.content if b.type == "text")
jsonschema.validate(query, schema)
discovered = await session.call("list_windows", query)
assert not discovered.isError
windows = discovered.structuredContent["windows"]
assert windows, "Open the intended app before running this opt-in probe"
nullable = {**query, "pid": None}
jsonschema.validate(nullable, schema)
found = await session.call("list_windows", nullable)
assert not found.isError and found.structuredContent["windows"]
assert "pid" not in found.structuredContent["lookup"]["filters"]
# This Python test process is not the requested application. A conflict
# must stay empty and suggest a new app-only query, without a guessed PID.
conflict = await session.call("list_windows", {**query, "pid": os.getpid()})
assert not conflict.isError and conflict.structuredContent["windows"] == []
next_query = conflict.structuredContent["lookup"]["suggested_arguments"]
assert next_query == query
assert "frontmost" not in "\n".join(
b.text for b in conflict.content if b.type == "text"
)
recovered = await session.call("list_windows", next_query)
assert not recovered.isError and recovered.structuredContent["windows"]
window = recovered.structuredContent["windows"][0]
target = {k: window[k] for k in ("pid", "window_id")}
state = await session.call("get_window_state", {**target, "max_elements": 100})
assert not state.isError, state
assert any(b.type == "image" for b in state.content)
assert state.structuredContent["snapshot_id"]
assert state.structuredContent["tree_markdown"]
assert state.structuredContent["artemis"]["source"]["target_id"] == str(
target["window_id"]
)
report = {
"app": app,
"zero_pid_rejected_by_host_and_adapter": True,
"app_only_window_count": len(windows),
"null_pid_omitted": True,
"conflicting_pid_recovered_without_substitution": True,
"screenshot_and_tree": True,
}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, ensure_ascii=False, indent=2), "utf-8")
print(json.dumps(report, ensure_ascii=False))
finally:
await session.aclose()
if __name__ == "__main__":
parser = argparse.ArgumentParser(__doc__)
parser.add_argument("executable", type=Path)
parser.add_argument("--app", required=True)
parser.add_argument("--output", type=Path, required=True)
args = parser.parse_args()
asyncio.run(run(args.executable.resolve(), args.app, args.output))