feat(cua): release 0.38.0+artemis.1 with verified shell observation
Upgrade the pinned driver, validate menu and taskbar focus ownership, patch untitled Shell resolution, and remove duplicate scroll scaling. Publish package provenance, verification boundaries and matching marketplace checksum. Go tests, vet, build and byte-level package verification passed.
This commit is contained in:
1 parent
dccddaa7e8
commit
b780a60e8d
20 files changed
+759
-149
No files matched your search
@@ -0,0 +1,163 @@
|
||||
"""Opt-in native-menu and Win+B regression, under the host desktop lease."""
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from artemis.plugins.models import ServerConfig
|
||||
from artemis.plugins.session import PluginSession
|
||||
from artemis.runtime.device_lock import DeviceExecutionLock
|
||||
|
||||
|
||||
async def run(executable: Path, output: Path, taskbar: bool):
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory(prefix="artemis-shell-fixture-") as folder:
|
||||
state_file = Path(folder) / "state.json"
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
"powershell.exe",
|
||||
"-NoProfile",
|
||||
"-File",
|
||||
str(Path(__file__).with_name("shell-fixture.ps1")),
|
||||
"-StateFile",
|
||||
str(state_file),
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
session = None
|
||||
calls = []
|
||||
try:
|
||||
async with asyncio.timeout(15):
|
||||
while not state_file.exists():
|
||||
if proc.returncode is not None:
|
||||
raise RuntimeError((await proc.stderr.read()).decode(errors="replace"))
|
||||
await asyncio.sleep(0.1)
|
||||
state = json.loads(state_file.read_text(encoding="utf-8-sig"))
|
||||
target = {"pid": state["pid"], "window_id": state["hwnd"]}
|
||||
session = await PluginSession(
|
||||
ServerConfig(command=str(executable)), executable.parent
|
||||
).start()
|
||||
|
||||
async def call(name, args):
|
||||
result = await session.call(name, args)
|
||||
calls.append(
|
||||
{
|
||||
"name": name,
|
||||
"arguments": args,
|
||||
"result": result.model_dump(mode="json", by_alias=True, exclude_none=True),
|
||||
}
|
||||
)
|
||||
assert not result.isError, "\n".join(
|
||||
block.text[:1500] for block in result.content if block.type == "text"
|
||||
)
|
||||
return result
|
||||
|
||||
def tree(result):
|
||||
return "\n".join(getattr(block, "text", "") for block in result.content)
|
||||
|
||||
result = await call("get_window_state", target)
|
||||
match = re.search(r'\[(\d+)\][^\n]*"Open native menu"', tree(result))
|
||||
assert match, tree(result)
|
||||
opened = await call(
|
||||
"click",
|
||||
{
|
||||
**target,
|
||||
"element_token": result.structuredContent["snapshot_id"] + ":" + match[1],
|
||||
"count": 1,
|
||||
"delivery_mode": "foreground",
|
||||
},
|
||||
)
|
||||
observation = opened.structuredContent["observation_target"]
|
||||
menu = observation["observed"]
|
||||
assert menu["window_id"] != target["window_id"], observation
|
||||
assert observation["reason"] == "associated_native_keyboard_focus", observation
|
||||
assert opened.structuredContent["artemis"]["source"]["target_id"] == str(
|
||||
menu["window_id"]
|
||||
)
|
||||
observed = await call("get_window_state", menu)
|
||||
assert "Increment once" in tree(observed), tree(observed)
|
||||
png = next(block for block in observed.content if block.type == "image")
|
||||
image = base64.b64decode(png.data, validate=True)
|
||||
(output / "native-menu.png").write_bytes(image)
|
||||
listed = await call("list_windows", {"pid": proc.pid})
|
||||
assert any(
|
||||
window["window_id"] == menu["window_id"] and window.get("window_kind") == "menu"
|
||||
for window in listed.structuredContent["windows"]
|
||||
)
|
||||
# Escape belongs to the owner queue; no fixture or user business action.
|
||||
await call("press_key", {**target, "key": "escape", "delivery_mode": "foreground"})
|
||||
final_state = json.loads(state_file.read_text(encoding="utf-8-sig"))
|
||||
assert final_state["selected_count"] == 0, final_state
|
||||
assert final_state["menu_attempts"] == 1, final_state
|
||||
|
||||
shell_verified = False
|
||||
if taskbar:
|
||||
windows = await call("list_windows", {"app": "explorer", "on_screen_only": True})
|
||||
desktop = next(
|
||||
window
|
||||
for window in windows.structuredContent["windows"]
|
||||
if window.get("class_name") == "Progman"
|
||||
)
|
||||
desktop = {key: desktop[key] for key in ("pid", "window_id")}
|
||||
await call("get_window_state", desktop)
|
||||
key_result = await call(
|
||||
"press_key",
|
||||
{**desktop, "key": "b", "modifiers": ["win"], "delivery_mode": "foreground"},
|
||||
)
|
||||
switched = key_result.structuredContent["observation_target"]
|
||||
assert switched["observed"]["window_id"] != desktop["window_id"], switched
|
||||
assert switched["reason"] == "associated_native_keyboard_focus", switched
|
||||
assert key_result.structuredContent["post_action_focus"]["focus_root"][
|
||||
"class_name"
|
||||
] in {"Shell_TrayWnd", "Shell_SecondaryTrayWnd"}
|
||||
assert key_result.structuredContent["artemis"]["source"]["target_id"] == str(
|
||||
switched["observed"]["window_id"]
|
||||
)
|
||||
shell_verified = True
|
||||
await call("bring_to_front", target)
|
||||
report = {
|
||||
"native_menu_discovered_and_captured": True,
|
||||
"menu_source_matches_actual_hwnd": True,
|
||||
"menu_png_sha256": hashlib.sha256(image).hexdigest(),
|
||||
"win_b_focus_and_capture": shell_verified,
|
||||
"fixture_business_submissions": 0,
|
||||
"calls": len(calls),
|
||||
}
|
||||
(output / "summary.json").write_text(json.dumps(report, indent=2), encoding="utf-8")
|
||||
print(json.dumps(report))
|
||||
finally:
|
||||
# Save only this fixture's protocol evidence; no pairing payloads.
|
||||
(output / "calls.json").write_text(
|
||||
json.dumps(calls, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
if session:
|
||||
await session.aclose()
|
||||
if proc.returncode is None:
|
||||
proc.terminate()
|
||||
await proc.wait()
|
||||
if state_file.exists():
|
||||
(output / "fixture-state.json").write_text(
|
||||
state_file.read_text(encoding="utf-8-sig"), encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(__doc__)
|
||||
parser.add_argument("executable", type=Path)
|
||||
parser.add_argument("--output", type=Path, required=True)
|
||||
parser.add_argument("--taskbar", action="store_true")
|
||||
args = parser.parse_args()
|
||||
lock = DeviceExecutionLock(
|
||||
"cua-shell-regression", description="Cua shell regression", resource_ids=["desktop:local"]
|
||||
)
|
||||
lock.acquire(timeout=0)
|
||||
try:
|
||||
asyncio.run(run(args.executable.resolve(), args.output.resolve(), args.taskbar))
|
||||
finally:
|
||||
lock.release()
|
||||
@@ -0,0 +1,51 @@
|
||||
param([Parameter(Mandatory = $true)][string]$StateFile)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
Add-Type -ReferencedAssemblies System.Windows.Forms,System.Drawing,System.Web.Extensions @'
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Web.Script.Serialization;
|
||||
using System.Windows.Forms;
|
||||
using System.Drawing;
|
||||
public sealed class ArtemisMenuFixture : Form {
|
||||
[DllImport("user32.dll")] static extern IntPtr CreatePopupMenu();
|
||||
[DllImport("user32.dll", CharSet=CharSet.Unicode)] static extern bool AppendMenuW(IntPtr menu, uint flags, UIntPtr id, string text);
|
||||
[DllImport("user32.dll")] static extern uint TrackPopupMenu(IntPtr menu, uint flags, int x, int y, int reserved, IntPtr owner, IntPtr rect);
|
||||
[DllImport("user32.dll")] static extern bool DestroyMenu(IntPtr menu);
|
||||
readonly string stateFile;
|
||||
int count, attempts;
|
||||
long result = -1;
|
||||
string error = "";
|
||||
public ArtemisMenuFixture(string path) {
|
||||
stateFile = path;
|
||||
Text = "ARTEMIS native menu fixture";
|
||||
Size = new Size(560, 360);
|
||||
StartPosition = FormStartPosition.CenterScreen;
|
||||
var button = new Button { Text="Open native menu", Location=new Point(30,30), Size=new Size(240,55) };
|
||||
button.Click += delegate {
|
||||
var menu = CreatePopupMenu();
|
||||
try {
|
||||
attempts++;
|
||||
Save();
|
||||
if (!AppendMenuW(menu, 0, new UIntPtr(1), "Increment once")) throw new Exception("AppendMenuW failed");
|
||||
result = TrackPopupMenu(menu, 0x100, Left+45, Top+140, 0, Handle, IntPtr.Zero);
|
||||
if (result == 1) count++;
|
||||
} catch (Exception ex) { error = ex.Message; }
|
||||
finally { DestroyMenu(menu); Save(); }
|
||||
};
|
||||
Controls.Add(button);
|
||||
Shown += delegate { Save(); };
|
||||
}
|
||||
void Save() {
|
||||
File.WriteAllText(stateFile, new JavaScriptSerializer().Serialize(new Dictionary<string,object> {
|
||||
{"pid",Process.GetCurrentProcess().Id}, {"hwnd",Handle.ToInt64()}, {"selected_count",count},
|
||||
{"menu_attempts",attempts}, {"menu_result",result}, {"menu_error",error}
|
||||
}));
|
||||
}
|
||||
}
|
||||
'@
|
||||
[System.Windows.Forms.Application]::Run([ArtemisMenuFixture]::new($StateFile))
|
||||
Reference in new issue
Block a user