lazyBoy/docs/agent-computer-progress.md

362 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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。