"""Build the pinned Windows plugin; never execute npm installation hooks.""" import argparse import base64 import hashlib import json import os import subprocess import tarfile import tempfile import zipfile from pathlib import Path ROOT = Path(__file__).resolve().parents[1] INTEGRITY = "KvrauHDzUMAaX/u735OnfQ15JTE38OjUxkt00ebRccKhY/ARkqJVCLUsJqZxxGsTBwjeldUn4VlOm4iPlFV/FA==" SOURCE_COMMIT = "7d8cb3fa5618d1ed8ec71773434ac3677f1922c9" RUNTIME_SHA256 = "e7c6f6cca76d7aa97dbd1c5f77f088a74f596e34cb2556d4270dd05febd20284" def build_windows( source: Path, go: str, adapter: Path, output: Path ) -> dict[str, bytes]: revision = subprocess.check_output( ["git", "rev-parse", "HEAD"], cwd=source, text=True ).strip() if revision != SOURCE_COMMIT: raise ValueError("上游源码提交不匹配") folder = source / "apps" / "OpenComputerUseWindows" files = { name: (folder / name).read_bytes() for name in ("main.go", "main_test.go", "go.mod", "runtime.ps1") } for name, value in files.items(): committed = subprocess.check_output( ["git", "show", f"HEAD:apps/OpenComputerUseWindows/{name}"], cwd=source ) if value.replace(b"\r\n", b"\n") != committed.replace(b"\r\n", b"\n"): raise ValueError("上游源码含未记录的修改") if hashlib.sha256(files["runtime.ps1"]).hexdigest() != RUNTIME_SHA256: # Accommodate Git's CRLF checkout without accepting a different source. normalized = files["runtime.ps1"].replace(b"\r\n", b"\n") if hashlib.sha256(normalized).hexdigest() != RUNTIME_SHA256: raise ValueError("窗口运行时源码校验失败") runtime = files["runtime.ps1"].decode("utf-8") start = runtime.index("function Capture-WindowPngBase64(") end = runtime.index("function Get-FocusedSummary", start) runtime = ( runtime[:start] + adapter.read_text(encoding="utf-8") + "\n\n" + adapter.with_name("window-input.ps1").read_text(encoding="utf-8") + "\n\n" + runtime[end:] ) marker = "public static class OCUWin32 {" runtime = runtime.replace( marker, marker + '\n [DllImport("user32.dll")]\n public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdc, uint flags);\n [DllImport("user32.dll")]\n public static extern IntPtr SetThreadDpiAwarenessContext(IntPtr context);\n [DllImport("user32.dll")]\n public static extern IntPtr ChildWindowFromPointEx(IntPtr parent, POINT point, uint flags);', ) runtime = runtime.replace( "$WM_SETTEXT = 0x000C", "[void][OCUWin32]::SetThreadDpiAwarenessContext([IntPtr](-4))\n\n$WM_SETTEXT = 0x000C", ) mouse_signature = "function Send-MouseClick([IntPtr]$hwnd, [int]$screenX, [int]$screenY, [string]$button, [int]$count) {" runtime = runtime.replace( mouse_signature, mouse_signature + "\n $hwnd = Resolve-InputWindow $hwnd $screenX $screenY", ) runtime = runtime.replace( "screenshotPngBase64 = Capture-WindowPngBase64 $bounds", "screenshotPngBase64 = Capture-WindowPngBase64 $bounds ([IntPtr]$process.MainWindowHandle)", ) snapshot_start = runtime.index("function Build-Snapshot(") snapshot_end = runtime.index("function List-Apps", snapshot_start) runtime = ( runtime[:snapshot_start] + adapter.with_name("window-snapshot.ps1").read_text(encoding="utf-8") + "\n\n" + runtime[snapshot_end:] ) runtime = runtime.replace( marker, marker + """ [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int w, int height, uint flags); [DllImport("user32.dll")] public static extern int GetWindowLong(IntPtr h, int index); [DllImport("user32.dll")] public static extern IntPtr GetWindow(IntPtr h, uint command); [DllImport("user32.dll")] public static extern bool IsWindow(IntPtr h); [DllImport("dwmapi.dll")] public static extern int DwmFlush(); """, ) files["runtime.ps1"] = runtime.encode("utf-8") # Advertise only methods this Windows runtime actually implements. main = files["main.go"].decode("utf-8") main = main.replace( 'Click implementation: auto (default), accessibility, app_post, sky_click, or global. Accessibility requires element_index. Windows supports app_post through HWND messages and does not currently support sky_click or global.", clickMethodValues)', 'Click implementation: auto (default), accessibility or app_post. Accessibility requires element_index.", []string{"auto", "accessibility", "app_post"})', ) files["main.go"] = main.encode("utf-8") files["main_test.go"] = files["main_test.go"].replace( b'strings.Join(values, ",") != "auto,accessibility,app_post,sky_click,global"', b'strings.Join(values, ",") != "auto,accessibility,app_post"', ) with tempfile.TemporaryDirectory(prefix="artemis-ocu-build-") as temporary: build = Path(temporary) for name, value in files.items(): (build / name).write_bytes(value) environment = { **os.environ, "CGO_ENABLED": "0", "GOOS": "windows", "GOARCH": "amd64", "GOTOOLCHAIN": "local", } for command in ( [go, "test", "./..."], [ go, "build", "-trimpath", "-buildvcs=false", "-ldflags=-s -w -X main.version=1.1.0-artemis.2", "-o", str(output), ".", ], ): result = subprocess.run( command, cwd=build, env=environment, timeout=300, capture_output=True, text=True, check=False, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0), ) print(result.stdout, end="") if result.returncode: raise RuntimeError(result.stderr or result.stdout or "Go build failed") return files def main(): parser = argparse.ArgumentParser() parser.add_argument("npm_tarball", type=Path, help="open-computer-use-1.1.0.tgz") parser.add_argument( "--upstream-source", type=Path, required=True, help="Pinned upstream Git checkout", ) parser.add_argument( "--go", default="go", help="Go 1.22+ executable (build host only)" ) parser.add_argument("--output", type=Path, default=ROOT / "artifacts") args = parser.parse_args() data = args.npm_tarball.read_bytes() if base64.b64encode(hashlib.sha512(data).digest()).decode() != INTEGRITY: raise ValueError("上游 npm 1.1.0 安装包完整性校验失败") source = ROOT / "plugins" / "open-computer-use" args.output.mkdir(parents=True, exist_ok=True) target = args.output / "open-computer-use-1.0.0-windows-amd64.zip" executable = args.output / "open-computer-use.exe" sources = build_windows( args.upstream_source, args.go, source / "window-capture.ps1", executable ) with ( tarfile.open(args.npm_tarball) as npm, zipfile.ZipFile(target, "w", zipfile.ZIP_DEFLATED) as archive, ): for name in ("plugin.json", "SKILL.md"): archive.write(source / name, name) for original, destination in ( ("LICENSE", "LICENSE"), ("THIRD_PARTY_NOTICES.md", "THIRD_PARTY_NOTICES.md"), ): stream = npm.extractfile("package/" + original) if stream is None: raise ValueError("上游包缺少必需文件") archive.writestr(destination, stream.read()) archive.write(executable, "bin/open-computer-use.exe") for name, value in sources.items(): archive.writestr("source/" + name, value) archive.write( source / "window-capture.ps1", "source/artemis-window-capture.ps1" ) archive.write(source / "window-input.ps1", "source/artemis-window-input.ps1") archive.write( source / "window-snapshot.ps1", "source/artemis-window-snapshot.ps1" ) archive.writestr( "provenance.json", json.dumps( { "npm_package": "open-computer-use@1.1.0", "source": "https://github.com/iFurySt/open-codex-computer-use", "integrity": "sha512-" + INTEGRITY, "source_commit": SOURCE_COMMIT, "adapter": "ARTEMIS window-owned PrintWindow capture; no screen-region fallback", "binary_sha256": hashlib.sha256( executable.read_bytes() ).hexdigest(), }, indent=2, ), ) digest = hashlib.sha256(target.read_bytes()).hexdigest() target.with_suffix(".sha256").write_text( f"{digest} {target.name}\n", encoding="utf-8" ) print(target) print(digest) if __name__ == "__main__": main()