Skip to content

Commit 246a1d5

Browse files
committed
Add owner-bound cooperative task infrastructure
1 parent 3de9619 commit 246a1d5

19 files changed

Lines changed: 1038 additions & 18 deletions

‎CHANGELOG.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ it shipped into a `## [x.y.z] - date` section of their own; the tag's
1313

1414
## Unreleased
1515

16+
- Add owner-bound shared GUI task infrastructure with cooperative cancellation,
17+
typed progress/outcomes, captured context and refreshed identity. Late owner
18+
results are dropped, failures do not retain traceback objects, and a failed
19+
thread start rolls back worker ownership. Panel migration remains pending.
20+
1621
- Stop active diagnostics on direct or explicitly wrapped cancellation and
1722
hard controls, retaining the original signal before later checks or error
1823
reporting. Ordinary failed checks keep their existing report behavior.

‎Progress.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ D2 的原生 EI 釋放驗證與 D3 的錄製/全域停止/Windows 原生崩
2121
- **Remote Desktop 與 Script Builder 仍在啟動時建立**(預設開啟),啟動時間 2.6–2.7 秒裡大半是它們與門面匯入。
2222
- **F1 的遺留面板釋放**:擁有者直接建立的核心表單與 Remote Desktop 在關閉時仍保留 widget;需隨 F3 遷移直接控制項引用、執行中工作與連線生命週期,再啟用銷毀。
2323
- **F2 其餘**:計畫指定的 `WorkspaceShell` 介面、右側可收合詳情,以及各面板的空白/錯誤/permission states 還沒全面交付。
24-
- **F3**(共用 worker、取消、關閉時不碰已銷毀物件、`webrtc_panel.py` 拆分)與 **F4**(啟動/記憶體基準、mixed-DPI、
25-
功能對等測試)尚未開始。
24+
- **F3** 還缺共用 `TaskController` 接入各面板、backend timeout/cancel 傳遞與關閉生命週期遷移,
25+
以及 `webrtc_panel.py` 的 dialog/worker/session 拆分。
26+
- **F4**(啟動/記憶體基準、mixed-DPI、功能對等測試)尚未開始。
2627
- **F3 Windows 原生釋放驗收**:`test/unit_test/headless/test_remote_desktop_host_id.py` 的 freed-thread-state abort 尚缺 Windows 原生堆疊與獨立根因確認。
2728

2829
**只記未完成的事。** 完成的工作記在 [docs/updates/](docs/updates/README.md)(每月一個批次檔,

‎README.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,25 @@ language changes reuse the snapshot without probing. Native portal restore,
514514
compositor restart, capture-result/context integration and cancelable active
515515
diagnostics remain in `Progress.md`.
516516

517+
The GUI task infrastructure provides `gui.task_controller.TaskController`:
518+
`submit(work, owner=widget)` returns a caller-owned `TaskHandle` with
519+
typed `TaskOutcome` and `TaskProgress` observations. Work receives a
520+
Qt-free `CancellationToken` with checkpoints, bounded interruptible waits
521+
and progress reporting. Submission captures context; dispatch refreshes
522+
store-backed identity. Work must receive plain data, avoid widget references
523+
and authorize its own operation. Existing
524+
`DeferredAuthorization.scope()` still requires input privilege, while
525+
`context_scope()` only refreshes/binds identity for independently authorized work.
526+
527+
Owner/handle/controller destruction requests cooperative stop and drops
528+
late delivery. Each task has a separate ID/token; callbacks run on the GUI
529+
thread. Cancellation does not forcibly interrupt an uncooperative call
530+
or relabel a successful committed return. Errors/progress use the captured
531+
secret scope; terminal errors retain no exception traceback. Failed thread
532+
starts roll back both registries and preserve the original failure.
533+
This is shared infrastructure: Diagnostics and other panels still await
534+
controller/timeout/stop integration, tracked in `Progress.md`.
535+
517536
Active diagnostics stop immediately when a check raises explicit cancellation
518537
or a hard control signal, including an explicit exception cause chain.
519538
The original object propagates through the core, SDK, CLI and Diagnostics

‎README/README_zh-CN.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,18 @@ Context 会复制环境/工具数据,可由拥有者提供无原生对象的
464464
截取工具/portal 选择独立诊断,重绘与切换语言只使用现有快照。原生 portal 恢复、compositor 重启、
465465
截取结果与 context 的接合,以及可取消的主动诊断仍列于 `Progress.md`。
466466

467+
GUI 工作基础提供 `gui.task_controller.TaskController`:
468+
`submit(work, owner=widget)` 返回调用者持有的 `TaskHandle`,通过
469+
`TaskOutcome`/`TaskProgress` 传递终态与进度。工作取得不依赖 Qt 的
470+
`CancellationToken`,提供 checkpoint、有界且可被取消唤醒的等待与进度报告。
471+
提交时捕获 context,执行前重新读取 store 用户;工作须持有数据、不引用 widget,并验证自身操作所需的 capability。
472+
现有 `DeferredAuthorization.scope()` 保持输入权限要求,`context_scope()` 仅更新并绑定身份。
473+
474+
拥有者、handle 或 controller 销毁时会请求协作式停止并丢弃延迟结果;每项工作有独立 ID/token,
475+
回调在 GUI 线程执行。不强制中断不配合的调用,也不把已成功提交的结果改标为取消。
476+
错误与进度使用捕获的秘密遮罩 scope,终态不保留异常 traceback;线程启动失败会恢复两层登记并保留原失败。
477+
这是共用基础;Diagnostics 与其余面板的 controller/timeout/停止控制整合仍列于 `Progress.md`。
478+
467479
主动诊断的检查若抛出显式取消或中断控制(含显式的异常原因链),立即停止,
468480
并由核心、SDK、CLI 与 Diagnostics 操作传递原始对象,不记录为普通错误或失败报告行,
469481
也不执行后续检查。普通检查错误仍产生失败行并继续其余项目;GUI 取消后可显式重试。

‎README/README_zh-TW.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,18 @@ Context 會複製環境/工具資料,可由擁有者提供無原生物件的
464464
擷取工具/portal 選擇獨立診斷,重繪與切換語系只使用既有快照。原生 portal 恢復、compositor 重啟、
465465
擷取結果與 context 的接合,以及可取消的主動診斷仍列於 `Progress.md`。
466466

467+
GUI 工作基礎提供 `gui.task_controller.TaskController`:
468+
`submit(work, owner=widget)` 回傳呼叫者持有的 `TaskHandle`,透過
469+
`TaskOutcome`/`TaskProgress` 傳遞終態與進度。工作取得不依賴 Qt 的
470+
`CancellationToken`,提供 checkpoint、有界且可被取消喚醒的等待與進度回報。
471+
提交時捕捉 context,執行前重讀 store 使用者;工作須持有資料、不引用 widget,並驗證自身操作所需的 capability。
472+
既有 `DeferredAuthorization.scope()` 維持輸入權限要求,`context_scope()` 僅更新與綁定身分。
473+
474+
擁有者、handle 或 controller 銷毀時會要求協作式停止並丟棄延遲結果;每項工作有獨立 ID/token,
475+
回呼在 GUI 執行緒執行。不強制中斷不合作的呼叫,也不把已成功提交的結果改標為取消。
476+
錯誤與進度使用捕捉的秘密遮罩 scope,終態不保留例外 traceback;執行緒啟動失敗會回復兩層登記並保留原失敗。
477+
這是共用基礎;Diagnostics 與其餘面板的 controller/timeout/停止控制整合仍列於 `Progress.md`。
478+
467479
主動診斷的檢查若拋出明確取消或中斷控制(含明確的例外原因鏈),立即停止,
468480
並由核心、SDK、CLI 與 Diagnostics 操作傳遞原始物件,不記錄成普通錯誤或失敗報告列,
469481
也不執行後續檢查。普通檢查錯誤仍產生失敗列並繼續其餘項目;GUI 取消後可明確重試。

‎architecture.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ without probing. Live defaults omit addresses, arguments, executable paths
3030
and native error text. XWayland scope is always limited; actual operations
3131
continue to enforce permission/revocation independently of this report.
3232

33+
gui/task_controller.py composes the existing daemon-worker relay rather than
34+
introducing another thread registry. Weak handle delivery and independent
35+
task state prevent owner/handle callback retention. Owner destruction cancels
36+
only its token; controller retirement also cleans jobs after the controller
37+
has gone. Context/identity are refreshed on dispatch, without prescribing
38+
one capability for every task. Each work function authorizes its operation.
39+
Qt-free cancellation/progress live in utils/exception/cancellation.py.
40+
Legacy start_worker retains its API and now rolls back failed thread starts.
41+
Panel integration, backend cancellation/timeout and WebRTC splitting remain open.
42+
3343
The active diagnostics runner separates ordinary failed probes from explicit
3444
direct/wrapped control signals. Controls propagate unchanged before logging
3545
or subsequent checks, across the existing SDK/CLI/GUI entry points. A failed

‎architecture_explore.md‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ iOS(WebDriverAgent)。核心能力是滑鼠/鍵盤控制、影像辨識、
1919

2020
| 指標 | 數值 |
2121
| --- | ---: |
22-
| Python 模組總數(含周邊子專案) | 1,215 |
23-
| 程式碼總行數 | 181,188 |
22+
| Python 模組總數(含周邊子專案) | 1,217 |
23+
| 程式碼總行數 | 181,522 |
2424
| `je_auto_control/utils/` 子套件數 | 311 |
2525
| `AC_*` 動作指令數(`known_commands()` 實測) | 810 |
2626
| 套件門面 `__all__` 公開名稱數 | 1377 |
2727
| GUI 分頁數(`main_widget` 註冊) | 49 |
2828
| MCP 工具數(`build_default_tool_registry()` 實測) | 710 |
29-
| `test_*.py` 測試檔/測試函式 | 838 / 8,751 |
29+
| `test_*.py` 測試檔/測試函式 | 840 / 8,777 |
3030
| 範例腳本 | 42 |
3131

3232
**技術基線**:Python ≥ 3.10、MIT 授權、必要相依只有 `je_open_cv`/`opencv-python`/`pillow`/`mss`/
@@ -306,7 +306,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。
306306

307307
### 5.4.2 框架基礎設施
308308

309-
> 14 個套件、約 3,524 行。
309+
> 14 個套件、約 3,581 行。
310310
311311
| 模組 | 行數 | 職責 |
312312
| --- | ---: | --- |
@@ -315,7 +315,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。
315315
| `utils/critical_exit/` | 132 | 監看緊急停止鍵的守護執行緒,用於中止失控腳本 |
316316
| `utils/diagnostics/` | 339 | 跨子系統的「一切正常嗎」健檢,附 `python -m` 進入點 |
317317
| `utils/dbus_client/` | 742 | 只用標準函式庫的 D-Bus session bus 客戶端。原本在 `linux_wayland/` 為 portal 交握而寫,AT-SPI 無障礙後端成為第二個使用者後搬到這裡(`utils/` 在分層上在各 OS 套件之上) |
318-
| `utils/exception/` | 263 | **例外階層根**。框架錯誤繼承 `AutoControlException`;`control_signals.py` 共用無 executor 相依的取消載體/分類,保留 MCP 與 history 舊匯入別名;另有集中式錯誤訊息(`exception_tags`)。 |
318+
| `utils/exception/` | 320 | **例外階層根**。框架錯誤繼承 `AutoControlException`;`control_signals.py` 共用無 executor 相依的取消載體/分類,保留 MCP 與 history 舊匯入別名;`cancellation.py` 提供 Qt-free 協作式 token 與 immutable progress;另有集中式錯誤訊息(`exception_tags`)。 |
319319
| `utils/failure_bundle/` | 229 | 可攜、已遮蔽的失敗診斷 ZIP(截圖 + 診斷 + log 尾段) |
320320
| `utils/file_process/` | 40 | 目錄檔案列舉(`execute_dir` 的後端) |
321321
| `utils/logging/` | 354 | `autocontrol_logger`、共用 handler、executor/CLI/REST/MCP/背景 run 的 vault 值遮罩與持久化診斷快照 |
@@ -633,7 +633,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。
633633

634634
### 5.4.14 安全、機密與合規
635635

636-
> 13 個套件、約 3,583 行。
636+
> 13 個套件、約 3,589 行。
637637
638638
| 模組 | 行數 | 職責 |
639639
| --- | ---: | --- |
@@ -642,7 +642,7 @@ socket server 有 8 MiB 讀取上限與 30 秒 handler timeout。
642642
| `utils/governance/` | 242 | 治理:maker-checker 核准閘門與即時憑證租約 |
643643
| `utils/license_policy/` | 240 | 以 SBOM 元件評估 SPDX 授權允許/拒絕政策 |
644644
| `utils/provenance/` | 126 | SLSA 建置來源證明(in-toto v1) |
645-
| `utils/rbac/` | 850 | 角色型存取控制:使用者與權杖驗證、REST/MCP/executor 的能力政策,`deferred.py` 將背景工作綁定註冊者並在觸發時重新驗證來源 store;不保存 bearer token。;MCP 敏感讀取獨立於唯讀提示要求 operator |
645+
| `utils/rbac/` | 856 | 角色型存取控制:使用者與權杖驗證、REST/MCP/executor 的能力政策,`deferred.py` 將背景工作綁定註冊者並在觸發時重新驗證來源 store;不保存 bearer token。;MCP 敏感讀取獨立於唯讀提示要求 operator |
646646
| `utils/redaction/` | 508 | 截圖遮蔽層:規則偵測 + 政策 + 協調器(上傳 VLM 前先遮) |
647647
| `utils/sbom/` | 170 | SBOM(CycloneDX)產生 |
648648
| `utils/secret_ref/` | 238 | URI scheme 形式的值參照解析 |
@@ -1043,7 +1043,8 @@ GUI 是**選用 extra**(`pip install je_auto_control[gui]`,只需要 PySide6
10431043
| `_validators.py` | 29 | `int_validator()`/`double_validator()`:以 C locale 驗證的數字輸入框 validator,接受的正是 `int()`/`float()` 讀得懂的寫法(預設 locale 在法文、德文下只收小數逗號)。所有數字 `QLineEdit` 都用它。 |
10441044
| `_screen_geometry.py` | 55 | Qt 邏輯座標與截圖用的原生像素互轉:`native_region()`、`screen_at_native()`、`logical_point()`(每個螢幕的左上角在兩者相同,螢幕內依 device pixel ratio 縮放)。區域選取與主機端標註覆蓋層都用它。 |
10451045
| `_daemon_thread.py` | 79 | `DaemonThread`:`QThread` 的替代品,保留遠端桌面 worker 用到的介面(`start`/`run`/`isRunning`/`wait`/`requestInterruption`/`started`/`finished`),但 `run()` 跑在 daemon `threading.Thread` 上,刪除物件或程式結束都不會銷毀執行中的執行緒。 |
1046-
| `_worker_thread.py` | 216 | `start_worker()`:在 daemon `threading.Thread` 上執行 `QObject` worker 的 `run()`(沒有 `QThread` 可被銷毀),並經由分頁擁有的中繼物件回報結果(回呼一律在 GUI 執行緒;worker 沒處理的例外也送到 `on_fail`);worker 留在模組登錄表直到 GUI 執行緒看到它結束,回傳 `WorkerHandle`(`isRunning()`);程式結束時先呼叫 worker 的 `request_stop()`,最多等 10 秒,仍在跑的隨行程結束。 |
1046+
| `_worker_thread.py` | 222 | `start_worker()`:在 daemon `threading.Thread` 上執行 `QObject` worker 的 `run()`(沒有 `QThread` 可被銷毀),並經由分頁擁有的中繼物件回報結果(回呼一律在 GUI 執行緒;worker 沒處理的例外也送到 `on_fail`);worker 留在模組登錄表直到 GUI 執行緒看到它結束,啟動失敗回復登記並保留原失敗;回傳 `WorkerHandle`(`isRunning()`);程式結束時先呼叫 worker 的 `request_stop()`,最多等 10 秒,仍在跑的隨行程結束。 |
1047+
| `task_controller.py` | 265 | 共用 owner-bound task/typed outcome-progress;獨立 token、弱回呼、dispatch 身分更新與銷毀後結果隔離。面板遷移另列進度。 |
10471048
| `template_candidate_panel.py` | 241 | 預覽原始/候選縮圖、檢視驗證並明確接受/回復。 |
10481049
| `template_candidate_worker.py` | 122 | 背景檔案操作、延後權限檢查與提交前取消;完成寫入不重標為取消。 |
10491050
| `journal_candidate_worker.py` | 147 | 背景產碼、差異呈現、審閱匯出/匯入驗證、權限與取消。 |
@@ -1161,7 +1162,7 @@ GUI 是**選用 extra**(`pip install je_auto_control[gui]`,只需要 PySide6
11611162
| `scripts/dev_release.py` | 只用標準函式庫的發佈輔助腳本 | dev 通道(PyPI 的 `je_auto_control_dev`)的版本與比對:`prepare` 把 `dev.toml` 寫成 `pyproject.toml`,版本取 PyPI 最新一版加一個 patch(`dev.toml` 的版本只是下限,不回寫 repo);`changed dist` 把建好的 wheel 跟 PyPI 最新一版逐檔比對,內容沒變就不上傳。只給 `dev.yml` 的 `publish-dev` job 與拋棄式的 checkout 用,因為 `prepare` 會蓋掉 `pyproject.toml`。 |
11621163
| `docs/` | Sphinx(`API`/`Eng`/`Zh`/`getting_started`) | Read the Docs 文件。 |
11631164
| `architecture_diagram/` | drawio + png | 既有的架構圖原始檔。 |
1164-
| `test/` | `unit_test/headless`(主要)、`unit_test/flow_control`、`integrated_test`、`gui_test`、`manual_test`、`verify`、`test_source` | 838 個 `test_*.py`/8,751 個測試函式。**注意**:`test/unit_test/` 下的 `*_test.py` 是會真的驅動滑鼠鍵盤的手動示範腳本,因此 `pyproject.toml` 把 `python_files` 釘成 `test_*.py`。`unit_test/headless/conftest.py` 有一個 autouse fixture,每個測試結束都沖掉 Qt 排隊中的 `deleteLater()`——不沖會讓殘留的 widget 在後面某個不相干的測試裡被銷毀,曾經整個直譯器 `__fastfail`。`test_doc_counts.py` 守住文件引用的指令/工具/子套件/範例數,`test_doc_line_counts.py` 守住所有行數(`--fix` 可一次重新產生)。 `verify/macos_verify.py` 是在真的 `macos-14` runner 上量測 TCC 到底允許什麼的探針(macOS 是唯一沒有容器可用的支援平台),不被 pytest 收集。 |
1165+
| `test/` | `unit_test/headless`(主要)、`unit_test/flow_control`、`integrated_test`、`gui_test`、`manual_test`、`verify`、`test_source` | 840 個 `test_*.py`/8,777 個測試函式。**注意**:`test/unit_test/` 下的 `*_test.py` 是會真的驅動滑鼠鍵盤的手動示範腳本,因此 `pyproject.toml` 把 `python_files` 釘成 `test_*.py`。`unit_test/headless/conftest.py` 有一個 autouse fixture,每個測試結束都沖掉 Qt 排隊中的 `deleteLater()`——不沖會讓殘留的 widget 在後面某個不相干的測試裡被銷毀,曾經整個直譯器 `__fastfail`。`test_doc_counts.py` 守住文件引用的指令/工具/子套件/範例數,`test_doc_line_counts.py` 守住所有行數(`--fix` 可一次重新產生)。 `verify/macos_verify.py` 是在真的 `macos-14` runner 上量測 TCC 到底允許什麼的探針(macOS 是唯一沒有容器可用的支援平台),不被 pytest 收集。 |
11651166

11661167
---
11671168

@@ -1243,7 +1244,7 @@ socket 預設綁 `127.0.0.1`;資源一律用 `with`。
12431244

12441245
| 層/子系統 | 檔案數 | 行數 |
12451246
| --- | ---: | ---: |
1246-
| `gui/` | 115 | 32,526 |
1247+
| `gui/` | 116 | 32,797 |
12471248
| `utils/mcp_server/` | 43 | 19,925 |
12481249
| `utils/remote_desktop/` | 63 | 14,634 |
12491250
| `utils/executor/` | 8 | 9,757 |
@@ -1263,6 +1264,6 @@ socket 預設綁 `127.0.0.1`;資源一律用 `with`。
12631264
| `osx/` | 17 | 981 |
12641265
| `autocontrol-lsp/` | 8 | 744 |
12651266
| `utils/hotkey/` | 7 | 861 |
1266-
| 其餘模組(約 286 個 `utils/` 子套件 + `android/`/`ios/`/周邊小工具) | 794 | 71,244 |
1267-
| **總計** | **1,208** | **181,101** |
1267+
| 其餘模組(約 286 個 `utils/` 子套件 + `android/`/`ios/`/周邊小工具) | 795 | 71,307 |
1268+
| **總計** | **1,210** | **181,435** |
12681269

‎docs/API_LIFECYCLE.md‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ diagnostics and input/capture operations retain their signatures, authority
3232
checks and failure families. Native restore/restart/context integration
3333
and cancelable active diagnostics remain open in Progress.md.
3434

35+
The GUI task infrastructure provides `gui.task_controller.TaskController`:
36+
`submit(work, owner=widget)` returns a caller-owned `TaskHandle` with
37+
typed `TaskOutcome` and `TaskProgress` observations. Work receives a
38+
Qt-free `CancellationToken` with checkpoints, bounded interruptible waits
39+
and progress reporting. Submission captures context; dispatch refreshes
40+
store-backed identity. Work must receive plain data, avoid widget references
41+
and authorize its own operation. Existing
42+
`DeferredAuthorization.scope()` still requires input privilege, while
43+
`context_scope()` only refreshes/binds identity for independently authorized work.
44+
45+
Owner/handle/controller destruction requests cooperative stop and drops
46+
late delivery. Each task has a separate ID/token; callbacks run on the GUI
47+
thread. Cancellation does not forcibly interrupt an uncooperative call
48+
or relabel a successful committed return. Errors/progress use the captured
49+
secret scope; terminal errors retain no exception traceback. Failed thread
50+
starts roll back both registries and preserve the original failure.
51+
This is shared infrastructure: Diagnostics and other panels still await
52+
controller/timeout/stop integration, tracked in `Progress.md`.
53+
3554
Active diagnostics stop immediately when a check raises explicit cancellation
3655
or a hard control signal, including an explicit exception cause chain.
3756
The original object propagates through the core, SDK, CLI and Diagnostics

0 commit comments

Comments
 (0)