Files
Artemis-Plugins/scripts/build_computer_use.py
T

197 lines
8.1 KiB
Python

"""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)",
)
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.1",
"-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.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()