lazyBoy/docs/agent-computer-progress.md

362 lines
26 KiB
Markdown
Raw Normal View History

2026-09-10 14:42:17 +00:00
# Agent Computer progress (V3)
基準 commit計劃`e6afa324530e19922909d4692c28fb005cc05a7a`
本機開始 HEAD`79952b088b394e36d6c3db3952165f1c3fd59961`
未覆蓋使用者未提交修改(`docs/plan/` 仍未追蹤)。
驗證2026-09-10 複查見文末「複查Bug 修正」):`cargo fmt --check`、`cargo clippy --workspace --all-targets -D warnings`、`cargo test --workspace`(含 sqlx全綠、`make test-agent-computer`、`apps/web npm run build` 全部 exit 0。
sqlx 測試用拋棄式 `pgvector/pgvector:pg16``127.0.0.1:5434``docker run --rm --name lazyboy-test-pg …`),不重建正在服務的 `lazyboy-postgres-1`
真實 OAuth主信箱TigerVNC GUI fixture`BLOCKED_EXTERNAL`。
sample MCP 在真實 Computer 容器 `lb-team-space-*``python3 echo_server.py` JSON-RPC 回 `from-computer`。外掛 UI 已 typecheck無瀏覽器工具未點擊。
---
## PR-00 / PR-01
狀態IMPLEMENTED見前次。截圖交付、semantic/vision、原生 fs/exec 短路徑。
---
## PR-02雙模式身份
狀態IMPLEMENTED資料契約idle沒有強制遷移
- `computers.generation`destroy/recreate `generation + 1`(舊 job 不可裝活)。
- Team 多 bot 同 `computer_id``computer_scope_key(Team, space, bot-a) == bot-b`。Dedicated 互斥 scope。
- **沒有**對 `bots.computer_id` 加 UNIQUE。
- `AdapterContext.computer_generation` / `control_epoch`
- Pause scopes`PauseScope::{Agent,Display,Computer}`;停 A 不 bump B 的 display epoch。
- idle reaper 把 `computer_jobs` running 算進活性。
回滾migration 022 + generation SELECT/UPDATE。
---
## PR-03ledger / 脫敏 / activity
狀態IMPLEMENTEDPostgres 為 mutation 來源;本環境未跑 sqlx 整合測試)
- `dispatch`mutating tool 先 `begin`journal insert 失敗則 `JOURNAL_UNAVAILABLE`,不開做。
-`operationId` + payload hash replay 已存結果;不同 hash → `PAYLOAD_MISMATCH`
- `operationId` 不進入 payload hash。
- `finish``computer_operations` + `operation_outbox``worker_loop` `flush_outbox` 只補尚未出現在 `run_activity``operationId`
- 結果與 outbox payload 經過 `redact_text` / `redact_json`
---
## PR-04jobs / cancel / CAS
狀態IMPLEMENTEDRunner 在 API 行程內container recreate 以 generation 中斷)
- `JobSupervisor``process_group(0)`cancel TERM→KILL process group。
- `exec``action=run|status|cancel`、`background` 回 `jobId`
- `write_file.expectedHash``CONFLICT`,不覆蓋。
- 測試:`/bin/true` 真 exit`sleep 30` 可 cancel。
- API 重啟:`computer_jobs` running/accepted → `interrupted`(不假裝收回 Docker PID
- `LAZYBOY_NATIVE_JOB_CONCURRENCY`(預設 2超額 → `QUOTA_EXCEEDED`
- Artifact`write_file` 成功寫 `computer_artifacts``GET /api/bots/{id}/artifacts` 列表;下載從 Computer `read_file` 串回sha256 不符 → `CONFLICT`
O04 PTY `terminal.*`DEFERRED見文末。跨重啟回收仍在跑的容器 PID標 interrupted不是 resume。
---
## PR-05Tool Manager / MCP
狀態IMPLEMENTEDstdio 不在 API spawnsample 在 Computer無第三方商店
- Manifest空 digest / zip-slip / 空 entrypoint 拒絕。
- 安裝≠Ready`AuthRequired` ≠ 可用)。
- `scripts/sample-mcp/echo_server.py` + `manifest.yaml`(本機範例,不是虛構 npm
- REST`GET/POST /api/bots/{id}/tools`、`POST .../tools/call`、`POST .../revoke`。
- 安裝把 echo 寫進 ComputerDedicated `tools/...`Team `shared/tools/...``python3` 在該容器執行 JSON-RPC。
- Agent 工具 `computer_mcp` 走同一條路徑;未 bind → `NOT_BOUND`
- 外掛頁「Computer 套件」可安裝/呼叫/撤銷 echo市集 MCP 文案不再假裝 stdio 都在 Computer。
- MCP `call_for` 先複製 client 再 await`definitions_for(actor)` 不把別人的工具塞進這次 run。
未做任意第三方套件商店、OAuth broker。`/api/mcp-servers` stdio **不再** `spawn` 子行程,只存 metadata 並指向 Computer Tool Manager。HTTP/SSE 仍為遠端 client。真實 Microsoft/Google OAuth`BLOCKED_EXTERNAL`。
---
## PR-06路由 / verifier / wait
狀態IMPLEMENTED決策層
- `choose_route`:有授權 API 不走 browser`POLICY_DENIED` 不繞路。
- `CompletionLayer`transport ≠ effect ≠ task`wait_counts_as_progress() == false`。
- policy`wait` 不再刷新無進展時鐘;`exec` 可。
- `wait_until` 有 deadline未知 disabled 不空轉。
- system promptGmail/Outlook 有 connector 走 API。
---
## PR-07form / takeover scopes
狀態IMPLEMENTEDform 工具已註冊lease/pause 見 PR-02
- `run_form_macro` 失敗即停、逐步 evidence。
- Display writer lease 原本就有pause scope 見 PR-02。
- Agent 工具 `form_fill`:用最新 snapshot locatorelement id`run_form_macro` 先定位,第一個失敗就停;可選 `submit` click。
---
## PR-08A Gmail fixture
狀態IMPLEMENTED純函式 fixture不碰真信箱
- batchModify 同 delta 分組、1000 cap。
- undo 只撤本 plan 的 delta。
- 真實帳號:`BLOCKED_EXTERNAL`。
---
## PR-08B Outlook
狀態IMPLEMENTEDGraph 語意 fixture非官方 npm
- `$batch` 最多 20子項 429/401 分開。
- categories merge不覆蓋無關分類。
- `Mail.ReadWrite` 不自動允許寄信。
- 真實 Graph OAuth`BLOCKED_EXTERNAL`。
---
## PR-09 TigerVNC 候選
狀態IMPLEMENTEDopt-in預設仍 Xvfb+x11vnc
- `DisplayBackend`slot 0 = `:1` / 5900 / 6080T57
- `lazyboy-screen``LAZYBOY_DISPLAY_BACKEND=tigervnc_xvnc`
- Dockerfile 加入 `tigervnc-standalone-server`,失敗則 script fallback Xvfb。
- Cua/中文/a11y GUI 對照:`BLOCKED_EXTERNAL`**未改 default**。
---
## PR-10 設定 / 文件
狀態IMPLEMENTED
`.env.example``LAZYBOY_EXECUTION_PROFILE`、`LAZYBOY_DISPLAY_BACKEND`、`LAZYBOY_BROWSER_BACKEND`、concurrency、tool install flags。
`migrations/022_agent_computer.sql`
---
## O01O48
| ID | 狀態 |
|---|---|
| O01 | IMPLEMENTED |
| O02 | IMPLEMENTED短 exec長 job 見 O04 |
| O03 | IMPLEMENTED + CAS |
| O04 | PARTIALComputer 內 background exec + cancelPTY `terminal.*` DEFERRED |
| O05 | IMPLEMENTEDform macro + `form_fill` 工具) |
| O06 | IMPLEMENTEDstdio 不在 API spawnHTTP/SSE 遠端echo 在 Computer |
| O07 | DEFERRED未量測 docker exec 開銷) |
| O08 | IMPLEMENTED`boot_for(need_gui=false)` 不 ensure_screen`LAZYBOY_RUNNER_ONLY` 不 boot XFCE |
| O09 | IMPLEMENTEDrouter |
| O10 | IMPLEMENTEDrevoke 從下次 schema 拿掉 computer_mcpstdio 不再進 defs |
| O11 | 保留現況 |
| O12 | IMPLEMENTED |
| O13 | DEFERRED |
| O14 | IMPLEMENTEDPR-01 |
| O15 | 保留現況(既有 profile bind |
| O16 | PARTIALverifier 層 + wait 不算進度) |
| O17 | 實驗opt-in |
| O18 | DEFERRED |
| O19 | 保留現況 |
| O20 | 保留現況 |
| O21 | 保留現況 |
| O22O24 | DEFERRED |
| O25 | IMPLEMENTED保留 |
| O26 | PARTIAL既有 screen/profile lease |
| O27 | IMPLEMENTEDepoch 函式) |
| O28 | PARTIALidle 看 runs/skills/jobs2026-09-10 修正守門原本永遠回 false並在判斷前 reconcile job 狀態) |
| O29 | IMPLEMENTEDpackage/binding 分離;共用 bytes 不共享帳號) |
| O30 | IMPLEMENTED`LAZYBOY_NATIVE_JOB_CONCURRENCY`,預設 2配額滿時先向 Computer reconcile 未 poll 的 job不會永久卡死 |
| O31 | DEFERRED |
| O32 | DEFERREDT59保留 `-ac` 直到 Xauthority 接到 Cua/AT-SPIVNC 仍走認證 proxy |
| O33 | IMPLEMENTEDsample MCP 安裝 REST + UI |
| O34 | IMPLEMENTEDdigestzip-slipunpinned npx 拒絕) |
| O35 | IMPLEMENTEDinstalled ≠ readyAuthRequired 不可用) |
| O36 | PARTIAL熱啟用 bindings無獨立 schema cache 服務) |
| O37 | IMPLEMENTED0.0.2 旁置、job pin 舊版、rollbackremove |
| O38 | BLOCKED_EXTERNAL |
| O39O40 | IMPLEMENTED fixture真實帳號 BLOCKED_EXTERNAL |
| O41 | IMPLEMENTEDdispatch 寫 operations + outboxflush 補送) |
| O42 | IMPLEMENTEDerrorCode/jobId/operationId |
| O43 | IMPLEMENTED列表 + 下載;磁碟 hash 不符 CONFLICT |
| O44 | PARTIALverifier 層) |
| O45 | IMPLEMENTEDCAS + operation id |
| O46 | IMPLEMENTED`GET /api/computer/{id}/health`dbsandboxdisplayjobsoperations |
| O47 | DEFERRED |
| O48 | DEFERRED無固定環境雙模式 P95不寫未測倍數 |
---
## T01T64
| ID | 狀態 | 證據/理由 |
|---|---|---|
| T01 | IMPLEMENTED | `should_deliver_observation_image` |
| T02 | IMPLEMENTED | 換模型takeover 強制交付(同真值表) |
| T03 | IMPLEMENTED | `pixel_actions_need_vision`text-only 無圖 |
| T04 | IMPLEMENTED | `typed_browser_errors` |
| T05 | IMPLEMENTED | Team/Dedicated path + echo 路徑測試 |
| T06 | PARTIAL | generation 拒絕舊 job未覆蓋所有偽造 grant 變體 |
| T07 | 保留現況 | task 無 Docker socket未加新 sentinel fixture |
| T08 | IMPLEMENTED | zip-slip`..` path 拒絕 |
| T09 | IMPLEMENTED | invalid UTF-8 binary payload |
| T10 | PARTIAL | 工具錯誤非空成功;磁碟滿未測 |
| T11 | IMPLEMENTED | `stale_hash` CAS CONFLICT |
| T12 | IMPLEMENTED | Supervisor `/exec` 回完整 `CommandResult`reap 讀 `exit` 檔,`false`/缺檔為 Failed/1 |
| T13 | IMPLEMENTED | Computer-side job_op cancel 走 `computer_cancel_script`TERM→KILL 整個 process group驗證後才標 cancelled2026-09-10 修正 pid 錯誤status 缺欄位當 TRANSPORT不發明 running |
| T14 | DEFERRED | API 重啟標 interrupted不 resume 容器 PID |
| T15 | IMPLEMENTED | generation interrupt |
| T16 | IMPLEMENTED | operation_id hashmismatch2026-09-10 第二輪key 改 per-bot、in-flight 同 id 回 `UNKNOWN_EFFECT`、傳輸失敗不存成 succeededsqlx 測試 `ledger_is_per_bot_and_does_not_rerun_in_flight_work` |
| T17 | IMPLEMENTED | outbox flush 補送且不重複sqlx 測試 `outbox_flush_does_not_duplicate_recorded_operations`2026-09-10 修正 `SELECT 1` 解碼 bug |
| T18 | IMPLEMENTED | `journal_failure` 拒 mutation |
| T19 | IMPLEMENTED | actor-scoped MCPstdio 不在 API 持有 client |
| T20 | PARTIAL | NOT_BOUNDrevoke無真實 token revoke |
| T21 | IMPLEMENTED | `canary_is_stripped`2026-09-10 第二輪ledgersnippet 實際套用 `redact_secret_patterns`(之前傳空 canary 等於沒脫敏) |
| T22 | 保留現況 | 既有 prompt 邊界;無新 injection corpus |
| T23 | PARTIAL | 同 display lease 既有Cua+worker 未雙開 fixture |
| T24 | PARTIAL | pause/takeoverHTTP in-flight barrier 未單獨測 |
| T25 | IMPLEMENTED | wait 不算 milestone |
| T26 | IMPLEMENTED | Gmail batchModify fixture |
| T27 | IMPLEMENTED | fixture 429/partial真實帳號 BLOCKED_EXTERNAL |
| T28 | IMPLEMENTED | undo 只撤本 plan |
| T29 | IMPLEMENTED | 無強制拆 Teammigration 不 UNIQUE computer_id |
| T30 | IMPLEMENTED | `boot_for(need_gui=false)``ensure_screen``maybe_ensure_screen` + FakeSandbox native write/read/exec`start.sh` runner-only 不 boot XFCE |
| T31 | PARTIAL | job 配額;無壓力 bench |
| T32 | DEFERRED | 無郵件分類品質集 |
| T33 | PARTIAL | broker 不進 generic env無任意 exec 讀 socket fixture |
| T34 | IMPLEMENTED | artifact 從 Computer 下載 |
| T35 | PARTIAL | CASoperation replayresume 不重播未全測 |
| T36 | PARTIAL | mutating tools 走 ledgerMCP stdio 不再是旁路 spawn |
| T37 | IMPLEMENTED | `team_computers_share`migration 註解+測試 |
| T38 | IMPLEMENTED | Team `bots/<id>/` 路徑 |
| T39 | IMPLEMENTED | shared/ + CAS |
| T40 | PARTIAL | 多 slot 契約;無雙 writer GUI fixture |
| T41 | PARTIAL | display writer lease 既有 |
| T42 | IMPLEMENTED | pause scope 停 A 不 bump B |
| T43 | PARTIAL | idle 看 jobs整機 pause 未單測 recreate 全集 |
| T44 | 保留現況 | 既有 ensure-screen single-flight |
| T45 | IMPLEMENTED | Dedicated 刪 botTeam Computer 存活 |
| T46 | IMPLEMENTED | echo 安裝/呼叫 `executionLocation=assigned_computer` |
| T47 | IMPLEMENTED | binding per bot套件 per computer |
| T48 | IMPLEMENTED | digestzip-slipunpinned npx |
| T49 | IMPLEMENTED | `plan_version_switch` job pin |
| T50 | IMPLEMENTED | revokeremoverevoke 掉 schema |
| T51 | IMPLEMENTED | Outlook fixture非官方 npm |
| T52 | IMPLEMENTED | Graph 子項 429/401 |
| T53 | PARTIAL | fixture 分頁語意;真實 delta BLOCKED_EXTERNAL |
| T54 | IMPLEMENTED | categories merge 不盲覆蓋 |
| T55 | BLOCKED_EXTERNAL | 無 Cua/中文/a11y GUI |
| T56 | PARTIAL | generation bumpbackend 熱切未做 |
| T57 | IMPLEMENTED | slot 0 = :1/5900/6080 |
| T58 | 保留現況 | 既有 noVNC無新 DPI fixture |
| T59 | DEFERRED | 保留 `-ac`VNC 走認證 proxyXauthority 未接到 Cua |
| T60 | IMPLEMENTED | 同 T30native 路徑不 attach viewerGUI 之後才 `lazyboy-screen ensure` |
| T61 | IMPLEMENTED | 未審查 env tokenunpinned npx 拒絕 |
| T62 | 保留現況 | 同 UID 不宣稱強隔離 |
| T63 | PARTIAL | job 配額renderer OOM 未測 |
| T64 | PARTIAL | 套件路徑可重建;舊 job 不裝活 |
## 2026-09-10 複查Bug 修正
本輪先跑計劃第 16 節的完整 gate再修實際發現的 bug未新增功能。
驗證命令與結果(本機,拋棄式 Postgres `pgvector/pgvector:pg16``127.0.0.1:5434`
- `cargo fmt --all -- --check`:原本有 diff`artifacts.rs`、`computer.rs` 等新檔未格式化)→ 已 `cargo fmt --all`,現在 exit 0。
- `cargo clippy --workspace --all-targets -- -D warnings`exit 0。
- `DATABASE_URL=… cargo test --workspace`**全部通過**api 157 passed / 3 ignoredcontrol 150harness 37contracts 7sandbox 7supervisor 2。前次紀錄的「sqlx 未連」現在已實跑14 個 sqlx 測試dbmemorymonitorrouting::fan_out全綠。
- `make test-agent-computer`exit 0。
- `apps/web``npm run build``tsc --noEmit` + vite通過。
修正:
1. **`lazyboy-api` 測試 binary 結束時 SIGABRT**`ort` `Mutex poisoned``release_env_on_exit` 二次 panic。根因`memory.rs` 用 `catch_unwind` 包住 `TextEmbedding::try_new`,但 `ort` 找不到 `libonnxruntime.so` 時是在持有全域鎖的情況下 `expect` panic鎖被 poison 後程序退出時 `.fini_array` hook 再 panic → abort。正式環境沒設 `ORT_DYLIB_PATH`API 正常關機也會變 exit 134。修法`memory.rs` 新增 `preload_onnx_runtime()`,先走 `ort::init_from(path)`(回 `Result`)預載 dylib失敗即標 `Unavailable` 不再進 fastembed`crates/api/Cargo.toml` 直接依賴同版 `ort ="=2.0.0-rc.13"`lock 無新增 crate。測試 `database_enforces_agent_scope_and_queries_do_not_leak` 改成顯式 `ModelState::Unavailable`,不再靠環境缺 ONNX 來觸發;新增 `a_missing_onnx_runtime_is_an_error_not_a_panic`。反向驗證:`ORT_DYLIB_PATH=/nonexistent cargo test -- --ignored memory_model_recovers_after_cache_failure` 只因無模型 fail**不再 abort**。
2. **Computer 內 background job 的 pid 是錯的T13 process-tree cancel 實際上沒生效)**。`computer_background_launch` 原本是 `mkdir -p … && setsid nohup sh … & echo $!``&` 作用在整個 `a && b` list`$!` 是 bash 子 shell 的 pid不是 `setsid` 後的 session leader。後果`status` 只是碰巧能用(子 shell 會 wait`cancel` 只殺子 shell真正的 `sh`→子程序變孤兒繼續跑,之後 `status` 讀不到 exit 檔還會誤報 `failed`。修法:`mkdir` 獨立一行wrapper 先 `printf %s "$$" > …/pid`launcher 等 pid 檔再輸出(`setsid` 若 fork 也正確)。新增 `computer_cancel_script(pid)``kill -TERM -- -pgid` → 最多 1s 等待 → `kill -KILL -- -pgid` → 再驗證exit 0 才算整棵樹已消失。`supervisor/docker.rs` cancel 改用它,且只有樹確實消失才把 job 標 `cancelled`(否則維持 `running`,不假稱取消)。測試:`computer_cancel_script_kills_the_whole_process_tree`(本機真起 `sh -c 'sleep 300; sleep 300'`,用 `pgrep -g` 驗證子 `sleep` 一起死);另在真實 Team 容器 `lb-team-space-c7bace…` 內手動跑 launchercancelpid=pgid=sid`tree gone`。
3. **supervisor `job_follow_up` 沒檢查 job 屬於哪個容器**:拿到別的 Computer 的 jobId 時會在自己容器對同號 pid 送 `kill`。現在 `job.computer_id != container` 一律回 `unknown job`
4. **`Makefile` `help` 目標壞掉**:新增的 `test-agent-computer` 說明被塞進同一個 `@echo "…` 造成跨行未閉合字串,`make help` 回 `Unterminated quoted string` Error 2。已拆成兩行 `@echo`
5. **idle reaper 的「有工作就不暫停」守門一直是死的HEAD 既有 bug**`computer_has_active_work` 把 `SELECT 1`INT4解成 `(i64,)`sqlx 回 `ColumnDecode mismatched types`,被 `matches!(Ok(Some(_)))` 吞成 false → 只要 `computers.updated_at` 十分鐘沒動,正在跑 run 的 Computer 也會被 suspend。改成 `sqlx::query(...).fetch_optional` 只看有沒有列。sqlx 測試 `idle_reaper_reconciles_unpolled_jobs_with_the_computer` 同時覆蓋 run 與 job 兩種活性。
6. **`flush_outbox` 去重失效T17 宣稱的「不重做」實際沒生效)**:同樣的 `SELECT 1`→`i64` 解碼錯誤讓 `exists` 永遠 None每筆 outbox 都再寫一次 `run_activity`Activity 出現重複 tool 事件。已改為列存在檢查;查詢出錯時保留 outbox 列到下一輪,不猜。新增 sqlx 測試 `outbox_flush_does_not_duplicate_recorded_operations`
7. **背景 job 配額會永久卡死**`computer_jobs.status` 只在 agent 呼叫 `exec status` 時更新;兩個跑完但沒人 poll 的 job 會讓該 Computer 之後永遠 `QUOTA_EXCEEDED`idle reaper 也永遠以為有工作。修法:`tools.rs` 配額看起來滿時先 `refresh_running_jobs`(向 Computer 問最舊 16 筆 running 的真實狀態supervisor 回 `unknown job``interrupted`)再重算;`computer.rs` 的 `pause_idle_computers` 在判斷活性前做同樣的 reconcilesuspended 容器無法問,`stop_parked_computers` 不做)。
未修、已知:
- host 端 `JobSupervisor::start``crates/control/src/jobs.rs`)用 piped stdout 但只在子程序結束後 `read_to_end`;輸出超過 pipe buffer~64 KiB的子程序會永久阻塞。目前正式路徑不用它背景 job 全在 Computer 內,`exec_background_does_not_spawn_on_the_api` 有守門),僅測試使用;若之後要在 API 行程內跑 job 必須先改成邊跑邊 drain。
- supervisor 的 `ComputerJobTable` 在記憶體supervisor 重啟後舊 jobId 回 `unknown job`API 側已把 `computer_jobs` 標 interrupted屬同一限制T14 仍 DEFERRED
回滾:還原 `crates/api/src/memory.rs`、`crates/api/Cargo.toml`ort 直接依賴)、`crates/api/src/tools.rs`refresh_running_jobs、`crates/api/src/computer.rs`、`crates/api/src/operations.rs`、`crates/control/src/jobs.rs`、`crates/supervisor/src/docker.rs`、`Makefile` 本節改動即可;無 migration 變動。
## 2026-09-10 複查(第二輪):三路平行 code review 後的修正
三個獨立 reviewledger/outbox/artifactsjobs/tool-manager/install雙模式/pause/前端)共回報 40 餘項重複、已在上一節修掉的略過。以下每項都有測試或反向驗證。gate`cargo fmt --check`、`clippy -D warnings`、`cargo test --workspace`api 162control 154harness 37、`tsc --noEmit` 全綠。
**LedgerT15T17`operations.rs` 重寫)**
1. **operationId 全域命名空間 → 跨 bot replay拒絕**ledger key 改為 `{bot_id}:{operation_id}`,查詢加 `bot_id` 條件。Bot B 選到 Bot A 用過的 id不會拿到 A 的結果,也不會被 `PAYLOAD_MISMATCH` 擋。
2. **記憶體 `LEDGER` 拆掉**:與 DB 兩份狀態不一致(一邊 accepted 一邊沒列)、無上限增長(連非 mutating tool 都建列。Postgres 是唯一真相;`OperationLedger` 只留 control crate 單元測試用。
3. **`bot_computer_id` 查詢失敗被當「沒有 Computer → Proceed」**:現在 DB 錯誤 = `JOURNAL_UNAVAILABLE`;不需要 Computer 的 mutationrememberschedule`computer_id = NULL` 入帳migration 023 放寬 NOT NULL
4. **同 id 同 payload 的 `accepted` 列直接 Proceed → 崩潰後重試會做第二次**:新增 `Begin::InProgress` → 工具回 `UNKNOWN_EFFECT`,要求先讀回狀態、換新 id 重試。
5. **`finish` 對非 mutating tool 也寫 DBoutbox**:提早 return。`exec status` 改為非 mutating`exec run/cancel` 仍是)。
6. **`"xxx failed: …"` 的傳輸失敗被存成 `succeeded` 並永遠 replay**`outcome_status` 辨識這類文字 → 刪除 accepted 列讓同 id 可重試;不寫入結果。
7. **replay 丟掉 `pause`**`Begin::Replay` 帶回 `pause`
8. **outbox 寫 `run_activity` 失敗仍標 delivered**:新增 `monitor::try_record_pool`,只有成功才標;且只處理 `created_at` 早於 2 秒的列,避免與 `runs.rs` 的直接寫入賽跑而重複。
9. **`redact_text(&text, &[])` 是 no-op**:新增 `redact_secret_patterns`Bearer、`password=``token:`、`sk-``ghp_``AKIA``xox``AIza`、PEM 私鑰、JSON secret keyledger 結果與 activity snippet 都經過它。control crate 新增 `regex` 直接依賴lock 已有)。
10. retention 新增 `operation_outbox`delivered7 天)與 `computer_operations`(非 accepted90 天)。
**JobsT13T14**
11. **runner-only 容器 PID 1 是 `sleep infinity`,不收屍**setsid 後的 wrapper 結束會變 zombie`kill -0` 仍成功 → job 永遠 `running`。修:`HostConfig.init = true`docker-init 收屍);`status` 改先讀 exit 檔、再用 `computer_alive_script`(看 `/proc/*/status` StateZ/X 視為死)。測試 `computer_alive_script_treats_a_zombie_as_dead` 本機造真 zombie 驗證。
12. **launcher 沒吐出 pid 時仍登記 jobpid None→ 永遠 running、不能 cancel**:現在直接回錯誤,不登記幽靈 job。
13. **`exec status/cancel` 不看歸屬與世代**:新增 `job_ownership`:別的 bot 的 job → `UNKNOWN_JOB``generation` 小於目前 Computer 世代或已 `interrupted` → 直接回 interrupted不去問新容器`persist_job` 的 upsert 不再把 `interrupted` 蓋回 `running`
14. **`pin_running_jobs` 把同 Computer 上其他 bot 的 job 也 pin 了**:加 `bot_id` 條件。
**Tool ManagerT18T21**
15. **revoke 後重裝撞 `UNIQUE(package_row_id, bot_id)` → 500**`ON CONFLICT … DO UPDATE SET status='ready' RETURNING id`;切版本走 `switch_binding`(同 tx 內先清掉同 (package_row, bot) 的非 ready 殘留)。
16. **rollback 會把 `revoked` binding 復活**:非 `ready` → 409。
17. **執行前不驗 digest**`verified_run_argv` 在同一條命令內 `sha256sum -c` 通過才 `exec python3`;不符 exit 97 → `PACKAGE_TAMPERED`。測試 `tampered_package_is_refused_before_it_runs` 真改檔驗證。
18. **`validate_manifest` 只檢查非空**:現要求 64 hex`compute-at-install` 佔位會被拒。
19. `InstallBody` 只吃 `package_id`、前端送 `packageId`serde `rename` + `alias` 兩者皆收。
20. `refuse_unpinned_npx``@scope/pkg``@` 當版本 pin修為去掉 scope 前綴再判斷。
**其他**
21. **`form_fill` 靜默截斷**>20 欄或欄位缺 namelocator → `INVALID_ARGUMENT`,不再填半張表回報成功。
22. **`looks_like_css` 誤判 `v1.2 Release``C++ Guide` 這種標題為 CSS**clicktype 先做精確唯一標題比對;找不到才依 locator 形狀決定 `SELECTOR_UNSUPPORTED``TARGET_NOT_FOUND``TARGET_STALE`,並各給對應說明。
23. **`computers.display_backend` 從未寫入**boot 轉 running 時寫入 `DisplayBackend::from_env()`health 不再永遠回欄位預設值。
24. **`delete_bot` 不清 `tool_bindings``computer_jobs``computer_operations``computer_artifacts`**:同 tx 一起刪Team Computer 的配額不再被已刪 bot 佔用)。
25. **`wait``PROGRESS_WHEN_REPEATED`與本文件「wait 不刷新無進展時鐘」相反**:改成 heartbeat 語意 — `wait` 只更新 `last_heartbeat_turn`(不會被當 loop halt但 stale 教練訊息照常觸發;`polling_a_build_or_a_queue_is_not_treated_as_spinning` 同時鎖住兩半契約。
26. **Gmail fixture 沒有真的 undo**:新增 `undo_plan(before, batches)`,只還原本次計畫實際改動的 label原本就有的不拔、原本沒有的不補、人工新加的不動測試做 plan→undo 往返驗證。
27. 前端:`RunActivityEntry.operationId` 補型別;`runs.rs` 從工具回傳 JSON 抽 `jobId` 寫進 activitymonitor 原有的 `entry.jobId` 顯示終於有值;並顯示 `op <短 id>`
**Migration**:新增 `migrations/023_agent_computer_fixes.sql``computer_operations.computer_id` DROP NOT NULL、移除多餘 `computer_operations_id_hash`、outbox pending 部分索引、`computer_operations(bot_id, created_at)` 索引)。不改 022已可能套用過
**review 提到但本輪不動、記錄在此**
- Pause scopes`resume_after_pause``route_operation` 有純函式與測試但沒接進 runs 主迴圈takeover 實際只有 GUI 全暫停,沒有 scope 級細分T22T24 應視為 PARTIAL不是 DONE
- `write_file``bash -lc` 字串插值路徑HEAD 既有);`normalize_workspace_path` 有限制字元,本輪未改成 argv。
- supervisor `ComputerJobTable` 仍在記憶體T14 DEFERRED 不變)。
回滾:還原本節列出的檔案,並 `DELETE FROM _sqlx_migrations WHERE version=23``ALTER TABLE computer_operations ALTER COLUMN computer_id SET NOT NULL`(若已無 NULL 列)。
## 誠實未完成
P0/P1 可在本環境落地的已接上。下面不是「做完」:
1. 真實 Gmail/Outlook OAuth主信箱`BLOCKED_EXTERNAL`。
2. TigerVNC 當預設 + Cua/中文/a11y GUI`BLOCKED_EXTERNAL`;預設仍 Xvfb+x11vnc。
3. Postgres sqlx 整合測試此環境未連。
4. **O04 PTY `terminal.*`DEFERRED** — 非互動走 `exec`+jobs+process-tree cancelTUI 協作仍用 GUI `shell`。不假裝有 PTY。
5. 跨重啟回收容器內仍在跑的 PID標 interrupted不是 resume。
6. 任意第三方套件商店、完整 OAuth broker沒有憑證。
7. O48 無未測速度倍數。
8. 外掛 UI 未在瀏覽器點過(無瀏覽器工具);已 `tsc --noEmit`
雙模式Team/Dedicated 契約不變;**沒有** UNIQUE `bots.computer_id`。stdio MCP **不是** API 子行程。
回滾:還原本輪 crate / migration 022 / lazyboy-screen `-ac` 註解 / sample-mcp / 前端 plugins+i18n。不要 force push。