Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
322aa0a
Decode Windows console output in its code page under Python 3.15's de…
JE-Chen Sep 25, 2026
b4d1adf
Map remote desktop input and the broadcast cursor through the capture…
JE-Chen Sep 25, 2026
5d0dbd9
Stamp host annotations with the frame's screen origin and draw them o…
JE-Chen Sep 25, 2026
734a418
Scale screen-coordinate conversions by the device pixel ratio everywh…
JE-Chen Sep 25, 2026
5d848a5
Keep semantic replay on the recorded point for unnamed or off-screen …
JE-Chen Sep 25, 2026
131672b
Contain OpenCV errors and check the unrounded budget in perceptual di…
JE-Chen Sep 25, 2026
75119b6
Read element geometry in every shape through one helper, refuse non-f…
JE-Chen Sep 25, 2026
c996186
Refuse secret:// in AC_resolve_ref and its MCP tools so secret values…
JE-Chen Sep 25, 2026
73441c4
Run nested action lists on the executor that is running them so devic…
JE-Chen Sep 25, 2026
3d7cffa
Name the region convention each Script Builder command actually reads…
JE-Chen Sep 25, 2026
ae6a603
Capture [left, top, right, bottom] regions on every monitor on Window…
JE-Chen Sep 25, 2026
827a5a1
Refuse NaN in drift tests, keep inferred schemas valid for their own …
JE-Chen Sep 25, 2026
0a7394e
Test NaN with math.isnan in json_contract rather than a self-comparis…
JE-Chen Sep 25, 2026
44b716a
Record that grab_logical on macOS returns Retina pixels and sees only…
JE-Chen Sep 25, 2026
a515e61
Grab macOS regions with scale_down so a Retina region stays in points…
JE-Chen Sep 25, 2026
7ee18e5
Read element geometry in any shape and match equal zero-area boxes, k…
JE-Chen Sep 25, 2026
7900099
Read every executor flag by its spelling through one as_bool, since b…
JE-Chen Sep 25, 2026
4258172
Read locate_all_image boxes in AC_grid_cell with box_format ltrb, cle…
JE-Chen Sep 25, 2026
b426e2d
Share one repair store per process without a db, store non-text appro…
JE-Chen Sep 25, 2026
70be5dc
Pin sys.platform in the offscreen screen probes so macOS runners expe…
JE-Chen Sep 25, 2026
322ca5c
Match 16-bit and float images in 8 bits and skip unscorable scales, c…
JE-Chen Sep 26, 2026
655ed44
Fail any headless test whose input reaches the real mouse or keyboard…
JE-Chen Sep 26, 2026
3f3fca0
Share named rate limiters so a reused name takes a new rate, keep the…
JE-Chen Sep 26, 2026
253bf52
Take the running loop with asyncio.get_running_loop() in the WebRTC c…
JE-Chen Sep 26, 2026
89efd92
Answer every MCP tools/call whatever the tool raises, answer plain PO…
JE-Chen Sep 26, 2026
3a483f0
Pin sys.platform in the default field-entry plan test, which macOS ru…
JE-Chen Sep 26, 2026
469ad8c
Refuse a 0 ms auto-click interval, show framework and decode errors i…
JE-Chen Sep 26, 2026
dc94552
Enumerate USB devices off the GUI thread and only once shown, share t…
JE-Chen Sep 26, 2026
f3d53d9
Skip the tabs-menu leak test where qt_material is not installed, and …
JE-Chen Sep 26, 2026
c299bcc
Call the VLM off the GUI thread, and show each engine's own running s…
JE-Chen Sep 26, 2026
8732021
Match JSON Schema patterns as ECMA-262 does, so ^\d+$ no longer accep…
JE-Chen Sep 26, 2026
25c9bc1
Decode a JSONPath surrogate pair written as two escapes into one char…
JE-Chen Sep 26, 2026
9011dc7
Give the fake accessibility backend its base's signatures and take Pa…
JE-Chen Sep 26, 2026
890d29d
Count the Age header in HTTP cache freshness, keep the first of dupli…
JE-Chen Sep 26, 2026
d15f841
Refuse an empty or non-string JWT key, refuse a token whose aud the p…
JE-Chen Sep 26, 2026
9a905b0
Resolve a percent-encoded ../ in URL canonicalisation, never turn a /…
JE-Chen Sep 26, 2026
afa121f
Read cookie Expires with the RFC 6265 date algorithm, let the last va…
JE-Chen Sep 26, 2026
5b18e66
Parse Link headers with the RFC 8288 Appendix B algorithm, so a < in …
JE-Chen Sep 26, 2026
6dad1b9
Keep a tracestate value's leading spaces, discard members that break …
JE-Chen Sep 26, 2026
abe78f9
Keep a .env value that starts with #, keep a multi-line value's trail…
JE-Chen Sep 26, 2026
83c8136
Buffer an unterminated SSE line in pieces instead of re-splitting the…
JE-Chen Sep 26, 2026
d2b25d8
Validate a multipart boundary, refuse or redraw one a part contains s…
JE-Chen Sep 26, 2026
3dbc839
Leave untranslated entries out of a compiled .mo, as msgfmt does, so …
JE-Chen Sep 26, 2026
9765591
Count a yearly BYDAY ordinal within the year alongside BYMONTHDAY, ke…
JE-Chen Sep 26, 2026
a910155
Encode a bytes attribute as OTLP bytesValue instead of its Python rep…
JE-Chen Sep 26, 2026
da7332a
Give French its CLDR ordinals, match a locale by language and fall ba…
JE-Chen Sep 26, 2026
9afe453
Pin ruff's rule set in pyproject.toml so a ruff upgrade does not chan…
JE-Chen Sep 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
237 changes: 237 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ These tools own the generic rules (bare `except`, mutable defaults, unused names

Suppressions need an inline justification — `# noqa: <code> # reason: <why>` or `# nosec B404 # reason: <why>`. Blanket file- or module-level suppressions are forbidden.

A broad `except` (`Exception`, `BaseException`, bare) that swallows rather than re-raises needs `# reason:` on its own `except` line. `test/unit_test/headless/test_broad_except_reasons.py` fails CI on one that does not — the linters cannot: CI runs `ruff` with its default rules (no `BLE`) and does not run pylint.
A broad `except` (`Exception`, `BaseException`, bare) that swallows rather than re-raises needs `# reason:` on its own `except` line. `test/unit_test/headless/test_broad_except_reasons.py` fails CI on one that does not — the linters cannot: CI runs `ruff` with the rule set pinned in `pyproject.toml` (`E4`/`E7`/`E9`/`F` plus `E501`; no `BLE`) and does not run pylint.

## Stage commits, `Progress.md`, `docs/updates/` and `architecture.md`

Expand Down
59 changes: 59 additions & 0 deletions Progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ pip install --dry-run --only-binary=:all: --platform win_arm64 --python-version
- **部分超出螢幕的 `screen_region` 被補黑**:`monitor_layout/logical_frame.py:143` 沒有先和畫面取交集,PIL `crop` 補零,可能回傳螢幕外的命中;寬或高為負時丟裸 `ValueError`。做法:先取交集(回傳裁過的原點),非正的寬高丟框架例外。
- **OCR 跨框比對漏掉從長框中段開始的字串**:`ocr/text_span.py:330` 的視窗超過「目標長度+40」就整個丟掉最左框,即使目標從那框開始;`"Save As"` 在長句框之後就找不到。做法:只有剩下的部分仍不短於目標時才丟左框。
- **負座標的中心點差一**:`wrapper/auto_control_image.py:48`、`:73` 的 `int((x1 + x2) / 2)` 向零截斷。做法:`(x1 + x2) // 2`。
- **Unicode 打字把換行與 Tab 當字元送**:`utils/text_unicode/text_unicode.py:49` `plan_unicode_keys("a\nb\tc")` 送出碼位 10 與 9,多數程式會丟掉 Unicode 的 LF;`write` 早就把它們對到 Return/Tab(`WRITE_CONTROL_KEYS`)。這個模組被 `wrapper/auto_control_keyboard.py:27` 載入。做法:控制空白改成按鍵。
- **鍵盤配置表的 Shift 半邊與非美式鍵**:`utils/keyboard_layout/keyboard_layout.py:98-100` 的 Shift 半邊是死鍵時退回未按 Shift 的字(美式國際配置的 Shift+6 回 `'6'`,契約是回 `None`);`:96` 只翻譯美式鍵碼,德/法/北歐鍵盤的 `VK_OEM_102`(0xE2)與英式 `VK_OEM_8` 永遠沒有標籤;`:68-74` 把原型設在全程序共用的 `ctypes.windll.user32` 上,之後別的呼叫者用 `c_ubyte` 陣列呼叫 `ToUnicodeEx` 會 `ArgumentError`。Jeffrey_RPA 的 `_gui_control.py:3322` 呼叫 `ac.foreground_keyboard_layout()`。做法:死鍵半邊回 `None`;候選鍵碼加上 0xDF、0xE1、0xE2…;改用私有的 `ctypes.WinDLL("user32")`。
- **剪貼簿格式名稱 `None`**:`utils/clipboard_formats/clipboard_formats.py:46` `_coerce` 把 tuple/list 描述的 `None` 名稱變成字串 `"None"`,dict 形式卻是 `""`,`diff_formats` 因此回報有變動。Jeffrey_RPA 的 `_gui_control.py:1126` 呼叫 `ac.clipboard_formats()`。做法:兩種形式都把 `None` 正規化成 `""`。

**解除條件**:Jeffrey_RPA 沒有批次在跑(`webrunner.pid` 的行程不在、Discord bot 停止);改完在 Jeffrey_RPA 跑 `test/test_je_facade.py`。

Expand Down Expand Up @@ -429,6 +432,62 @@ MCP 工具的檔案參數(`path`、`file_path`、`db`、`image_path`、`golden
**為什麼要拍板**:根目錄從哪來(新的環境變數、沿用 `roots/list`、或兩者),唯讀模式要不要預設開啟;
預設開啟會讓現有讀取工作區外檔案的用法失效。

同一個問題也在 `ac_resolve_ref`/`ac_resolve_refs`(`_factories.py:7182`,標為 `READ_ONLY`):`file://` 沒有
`base_dir` 限制,`env://` 可讀任何環境變數,包括放 API 金鑰的那些,結果直接回給模型。`secret://` 已經拒絕;
`env://` 要不要改成允許清單、`file://` 要不要套同一個根目錄,跟上面一起決定。

---

## Windows 的 DPI 感知是系統層級,混合 DPI 的螢幕座標被虛擬化

`DECIDE` — 改成 per-monitor 會移動那些螢幕上的所有座標,Jeffrey_RPA 在那些螢幕上錄的座標與樣板要重錄

`windows/screen/win32_screen.py:50` 在 import 時呼叫 `SetProcessDPIAware()`,那是系統 DPI 感知,不是
per-monitor。DPI 與主螢幕不同的螢幕會被 Windows 虛擬化:本機第二螢幕 125%,實際 1920×1080,但 Win32、
`mss` 與 Qt 都回報 `(1920, -164, 1536, 864)`,截圖是 Windows 縮小過的影像,那個螢幕上的樣板比對與 OCR
用的是模糊的畫面。同檔註解說之後「所有 Win32 座標查詢都會拿到實體像素」,只在主螢幕 DPI 的螢幕上成立。

**做法**:先呼叫 `SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)`,失敗再退回
`SetProcessDPIAware()`,並改正註解;`utils/monitor_layout` 的換算與 `gui/_screen_geometry.py` 一起檢查。

**為什麼要拍板**:這個檔在 Jeffrey_RPA 正在跑的截圖路徑上。換成 per-monitor 之後,縮放螢幕上的座標與截圖
尺寸都會變,既有的樣板和錄好的座標在那些螢幕上會失準。

---

## `pil_screenshot`/`screenshot` 的區域擷取在 Windows 只看得到主螢幕

`BLOCKED` — 要改的是 Jeffrey_RPA 正在跑的截圖路徑(`cv2_utils/screenshot.py`、`wrapper/auto_control_screen.py`、`utils/window_capture/window_capture.py`),依工作區規則在它執行期間不動

`cv2_utils/screenshot.py:58` 把 `screen_region` 交給 `ImageGrab.grab(bbox=...)`,Pillow 在 Windows 沒帶
`all_screens=True` 時只擷取主螢幕再裁切,主螢幕外的部分補黑。分析類指令已改走
`cv2_utils/region_capture.grab_screen_region`(顏色、直方圖、SSIM、對比、顏色等待、QR、VLM、MCP 截圖),
下面這些仍是舊路徑,在主螢幕左側或上方的螢幕得到全黑影像:

- `pil_screenshot(screen_region=...)`、`screenshot(screen_region=...)` 與 `AC_screenshot`。
- `utils/pytest_plugin/keywords.py:41` `keyword_screenshot`(與 `AC_screenshot` 同一語意,一起改)。
- `utils/window_capture/window_capture.py:66` `capture_window`:視窗在副螢幕時截到黑的。
- `utils/set_of_marks/set_of_marks.py:121` 把標記畫在 `pil_screenshot()`(只有主螢幕)上,副螢幕的元件沒有標記。

**做法**:`pil_screenshot` 的區域路徑在 Windows 改走 `grab_screen_region`(它已處理 DPI 與負座標),
`capture_window` 同樣;set-of-marks 改用 `grab_logical(None)` 並把原點加回標記座標。

---

## macOS 的 `grab_logical` 在 Retina 上是像素座標,而且只看得到主螢幕

`BLOCKED` — `utils/monitor_layout/logical_frame.py` 在 Jeffrey_RPA 正在跑的截圖路徑上,依工作區規則在它執行期間不動

`grab_logical` 在 macOS 呼叫 `ImageGrab.grab(all_screens=True)`。讀 Pillow 12.3.0 的 darwin 分支:`all_screens`
不被使用,`screencapture -x` 只擷取主螢幕;Retina 螢幕的影像是點座標的 2 倍(Pillow 文件:「screen captures will
be at 2x if on a Retina screen」,`scale_down=True` 只在帶 `bbox` 時生效)。`logical_virtual_rect` 只讀 Windows 的
`GetSystemMetrics`,所以 macOS 不縮放:樣板比對、OCR 與其他走 `grab_logical` 的定位,在 Retina 上回傳的座標是
滑鼠(Quartz,點座標)的 2 倍,副螢幕上的目標則找不到。GitHub 的 macOS runner 是 1x 虛擬螢幕,CI 測不到。

**做法**:darwin 上以 `CGDisplayBounds`/`CGGetActiveDisplayList` 取得各螢幕的點座標範圍;有 `region` 時交給
`ImageGrab.grab(bbox=..., scale_down=True)`(`screencapture -R` 接受全域點座標,包括負值),整個桌面則逐螢幕擷取、
各自縮到點座標後拼接,原點取所有螢幕的最小 x/y。需要在 Retina Mac 上實測。

---

## 遠端桌面的 viewer 槽位由各面板共用
Expand Down
Loading
Loading