# 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-03:ledger / 脫敏 / activity 狀態:IMPLEMENTED(Postgres 為 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-04:jobs / cancel / CAS 狀態:IMPLEMENTED(Runner 在 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-05:Tool Manager / MCP 狀態:IMPLEMENTED(stdio 不在 API spawn;sample 在 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 寫進 Computer:Dedicated `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 prompt:Gmail/Outlook 有 connector 走 API。 --- ## PR-07:form / takeover scopes 狀態:IMPLEMENTED(form 工具已註冊;lease/pause 見 PR-02) - `run_form_macro` 失敗即停、逐步 evidence。 - Display writer lease 原本就有;pause scope 見 PR-02。 - Agent 工具 `form_fill`:用最新 snapshot locator/element id;`run_form_macro` 先定位,第一個失敗就停;可選 `submit` click。 --- ## PR-08A Gmail fixture 狀態:IMPLEMENTED(純函式 fixture,不碰真信箱) - batchModify 同 delta 分組、1000 cap。 - undo 只撤本 plan 的 delta。 - 真實帳號:`BLOCKED_EXTERNAL`。 --- ## PR-08B Outlook 狀態:IMPLEMENTED(Graph 語意 fixture,非官方 npm) - `$batch` 最多 20;子項 429/401 分開。 - categories merge,不覆蓋無關分類。 - `Mail.ReadWrite` 不自動允許寄信。 - 真實 Graph OAuth:`BLOCKED_EXTERNAL`。 --- ## PR-09 TigerVNC 候選 狀態:IMPLEMENTED(opt-in,預設仍 Xvfb+x11vnc) - `DisplayBackend`;slot 0 = `:1` / 5900 / 6080(T57)。 - `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`。 --- ## O01–O48 | ID | 狀態 | |---|---| | O01 | IMPLEMENTED | | O02 | IMPLEMENTED(短 exec);長 job 見 O04 | | O03 | IMPLEMENTED + CAS | | O04 | PARTIAL(Computer 內 background exec + cancel;PTY `terminal.*` DEFERRED) | | O05 | IMPLEMENTED(form macro + `form_fill` 工具) | | O06 | IMPLEMENTED(stdio 不在 API spawn;HTTP/SSE 遠端;echo 在 Computer) | | O07 | DEFERRED(未量測 docker exec 開銷) | | O08 | IMPLEMENTED(`boot_for(need_gui=false)` 不 ensure_screen;`LAZYBOY_RUNNER_ONLY` 不 boot XFCE) | | O09 | IMPLEMENTED(router) | | O10 | IMPLEMENTED(revoke 從下次 schema 拿掉 computer_mcp;stdio 不再進 defs) | | O11 | 保留現況 | | O12 | IMPLEMENTED | | O13 | DEFERRED | | O14 | IMPLEMENTED(PR-01) | | O15 | 保留現況(既有 profile bind) | | O16 | PARTIAL(verifier 層 + wait 不算進度) | | O17 | 實驗/opt-in | | O18 | DEFERRED | | O19 | 保留現況 | | O20 | 保留現況 | | O21 | 保留現況 | | O22–O24 | DEFERRED | | O25 | IMPLEMENTED/保留 | | O26 | PARTIAL(既有 screen/profile lease) | | O27 | IMPLEMENTED(epoch 函式) | | O28 | PARTIAL(idle 看 runs/skills/jobs;2026-09-10 修正守門原本永遠回 false,並在判斷前 reconcile job 狀態) | | O29 | IMPLEMENTED(package/binding 分離;共用 bytes 不共享帳號) | | O30 | IMPLEMENTED(`LAZYBOY_NATIVE_JOB_CONCURRENCY`,預設 2;配額滿時先向 Computer reconcile 未 poll 的 job,不會永久卡死) | | O31 | DEFERRED | | O32 | DEFERRED(T59:保留 `-ac` 直到 Xauthority 接到 Cua/AT-SPI;VNC 仍走認證 proxy) | | O33 | IMPLEMENTED(sample MCP 安裝 REST + UI) | | O34 | IMPLEMENTED(digest/zip-slip/unpinned npx 拒絕) | | O35 | IMPLEMENTED(installed ≠ ready;AuthRequired 不可用) | | O36 | PARTIAL(熱啟用 bindings;無獨立 schema cache 服務) | | O37 | IMPLEMENTED(0.0.2 旁置、job pin 舊版、rollback/remove) | | O38 | BLOCKED_EXTERNAL | | O39–O40 | IMPLEMENTED fixture;真實帳號 BLOCKED_EXTERNAL | | O41 | IMPLEMENTED(dispatch 寫 operations + outbox;flush 補送) | | O42 | IMPLEMENTED(errorCode/jobId/operationId) | | O43 | IMPLEMENTED(列表 + 下載;磁碟 hash 不符 CONFLICT) | | O44 | PARTIAL(verifier 層) | | O45 | IMPLEMENTED(CAS + operation id) | | O46 | IMPLEMENTED(`GET /api/computer/{id}/health`:db/sandbox/display/jobs/operations) | | O47 | DEFERRED | | O48 | DEFERRED(無固定環境雙模式 P95;不寫未測倍數) | --- ## T01–T64 | 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,驗證後才標 cancelled;2026-09-10 修正 pid 錯誤);status 缺欄位當 TRANSPORT,不發明 running | | T14 | DEFERRED | API 重啟標 interrupted,不 resume 容器 PID | | T15 | IMPLEMENTED | generation interrupt | | T16 | IMPLEMENTED | operation_id hash/mismatch;2026-09-10 第二輪:key 改 per-bot、in-flight 同 id 回 `UNKNOWN_EFFECT`、傳輸失敗不存成 succeeded(sqlx 測試 `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 MCP;stdio 不在 API 持有 client | | T20 | PARTIAL | NOT_BOUND/revoke;無真實 token revoke | | T21 | IMPLEMENTED | `canary_is_stripped`;2026-09-10 第二輪:ledger/snippet 實際套用 `redact_secret_patterns`(之前傳空 canary 等於沒脫敏) | | T22 | 保留現況 | 既有 prompt 邊界;無新 injection corpus | | T23 | PARTIAL | 同 display lease 既有;Cua+worker 未雙開 fixture | | T24 | PARTIAL | pause/takeover;HTTP 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 | 無強制拆 Team;migration 不 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 | CAS/operation replay;resume 不重播未全測 | | T36 | PARTIAL | mutating tools 走 ledger;MCP stdio 不再是旁路 spawn | | T37 | IMPLEMENTED | `team_computers_share`;migration 註解+測試 | | T38 | IMPLEMENTED | Team `bots//` 路徑 | | 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 刪 bot;Team Computer 存活 | | T46 | IMPLEMENTED | echo 安裝/呼叫 `executionLocation=assigned_computer` | | T47 | IMPLEMENTED | binding per bot;套件 per computer | | T48 | IMPLEMENTED | digest/zip-slip/unpinned npx | | T49 | IMPLEMENTED | `plan_version_switch` job pin | | T50 | IMPLEMENTED | revoke/remove;revoke 掉 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 bump;backend 熱切未做 | | T57 | IMPLEMENTED | slot 0 = :1/5900/6080 | | T58 | 保留現況 | 既有 noVNC;無新 DPI fixture | | T59 | DEFERRED | 保留 `-ac`;VNC 走認證 proxy;Xauthority 未接到 Cua | | T60 | IMPLEMENTED | 同 T30:native 路徑不 attach viewer;GUI 之後才 `lazyboy-screen ensure` | | T61 | IMPLEMENTED | 未審查 env token/unpinned 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 ignored;control 150;harness 37;contracts 7;sandbox 7;supervisor 2)。前次紀錄的「sqlx 未連」現在已實跑;14 個 sqlx 測試(db/memory/monitor/routing::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…` 內手動跑 launcher+cancel:pid=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` 在判斷活性前做同樣的 reconcile(suspended 容器無法問,`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 後的修正 三個獨立 review(ledger/outbox/artifacts;jobs/tool-manager/install;雙模式/pause/前端)共回報 40 餘項;重複、已在上一節修掉的略過。以下每項都有測試或反向驗證。gate:`cargo fmt --check`、`clippy -D warnings`、`cargo test --workspace`(api 162/control 154/harness 37)、`tsc --noEmit` 全綠。 **Ledger(T15–T17,`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 的 mutation(remember/schedule)以 `computer_id = NULL` 入帳(migration 023 放寬 NOT NULL)。 4. **同 id 同 payload 的 `accepted` 列直接 Proceed → 崩潰後重試會做第二次**:新增 `Begin::InProgress` → 工具回 `UNKNOWN_EFFECT`,要求先讀回狀態、換新 id 重試。 5. **`finish` 對非 mutating tool 也寫 DB/outbox**:提早 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 key),ledger 結果與 activity snippet 都經過它。control crate 新增 `regex` 直接依賴(lock 已有)。 10. retention 新增 `operation_outbox`(delivered,7 天)與 `computer_operations`(非 accepted,90 天)。 **Jobs(T13/T14)** 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` State,Z/X 視為死)。測試 `computer_alive_script_treats_a_zombie_as_dead` 本機造真 zombie 驗證。 12. **launcher 沒吐出 pid 時仍登記 job(pid 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 Manager(T18–T21)** 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 欄或欄位缺 name/locator → `INVALID_ARGUMENT`,不再填半張表回報成功。 22. **`looks_like_css` 誤判 `v1.2 Release`/`C++ Guide` 這種標題為 CSS**:click/type 先做精確唯一標題比對;找不到才依 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` 寫進 activity,monitor 原有的 `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 級細分(T22–T24 應視為 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 cancel;TUI 協作仍用 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。