26 KiB
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/recreategeneration + 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_jobsrunning 算進活性。
回滾: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_loopflush_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_jobsrunning/accepted →interrupted(不假裝收回 Docker PID)。 -
LAZYBOY_NATIVE_JOB_CONCURRENCY(預設 2)超額 →QUOTA_EXCEEDED。 -
Artifact:
write_file成功寫computer_artifacts;GET /api/bots/{id}/artifacts列表;下載從 Computerread_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/...,Teamshared/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先定位,第一個失敗就停;可選submitclick。
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/<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 刪 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)通過。
修正:
lazyboy-api測試 binary 結束時 SIGABRT(ortMutex poisoned→release_env_on_exit二次 panic)。根因:memory.rs用catch_unwind包住TextEmbedding::try_new,但ort找不到libonnxruntime.so時是在持有全域鎖的情況下expectpanic,鎖被 poison 後程序退出時.fini_arrayhook 再 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。- Computer 內 background job 的 pid 是錯的(T13 process-tree cancel 實際上沒生效)。
computer_background_launch原本是mkdir -p … && setsid nohup sh … & echo $!,&作用在整個a && blist,$!是 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.rscancel 改用它,且只有樹確實消失才把 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。 - supervisor
job_follow_up沒檢查 job 屬於哪個容器:拿到別的 Computer 的 jobId 時會在自己容器對同號 pid 送kill。現在job.computer_id != container一律回unknown job。 Makefilehelp目標壞掉:新增的test-agent-computer說明被塞進同一個@echo "…造成跨行未閉合字串,make help回Unterminated quoted stringError 2。已拆成兩行@echo。- 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 兩種活性。 flush_outbox去重失效(T17 宣稱的「不重做」實際沒生效):同樣的SELECT 1→i64解碼錯誤讓exists永遠 None,每筆 outbox 都再寫一次run_activity,Activity 出現重複 tool 事件。已改為列存在檢查;查詢出錯時保留 outbox 列到下一輪,不猜。新增 sqlx 測試outbox_flush_does_not_duplicate_recorded_operations。- 背景 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 重寫)
- operationId 全域命名空間 → 跨 bot replay/拒絕:ledger key 改為
{bot_id}:{operation_id},查詢加bot_id條件。Bot B 選到 Bot A 用過的 id,不會拿到 A 的結果,也不會被PAYLOAD_MISMATCH擋。 - 記憶體
LEDGER拆掉:與 DB 兩份狀態不一致(一邊 accepted 一邊沒列)、無上限增長(連非 mutating tool 都建列)。Postgres 是唯一真相;OperationLedger只留 control crate 單元測試用。 bot_computer_id查詢失敗被當「沒有 Computer → Proceed」:現在 DB 錯誤 =JOURNAL_UNAVAILABLE;不需要 Computer 的 mutation(remember/schedule)以computer_id = NULL入帳(migration 023 放寬 NOT NULL)。- 同 id 同 payload 的
accepted列直接 Proceed → 崩潰後重試會做第二次:新增Begin::InProgress→ 工具回UNKNOWN_EFFECT,要求先讀回狀態、換新 id 重試。 finish對非 mutating tool 也寫 DB/outbox:提早 return。exec status改為非 mutating(exec run/cancel仍是)。"xxx failed: …"的傳輸失敗被存成succeeded並永遠 replay:outcome_status辨識這類文字 → 刪除 accepted 列讓同 id 可重試;不寫入結果。- replay 丟掉
pause:Begin::Replay帶回pause。 - outbox 寫
run_activity失敗仍標 delivered:新增monitor::try_record_pool,只有成功才標;且只處理created_at早於 2 秒的列,避免與runs.rs的直接寫入賽跑而重複。 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 已有)。- retention 新增
operation_outbox(delivered,7 天)與computer_operations(非 accepted,90 天)。
Jobs(T13/T14)
- runner-only 容器 PID 1 是
sleep infinity,不收屍:setsid 後的 wrapper 結束會變 zombie,kill -0仍成功 → job 永遠running。修:HostConfig.init = true(docker-init 收屍);status改先讀 exit 檔、再用computer_alive_script(看/proc/*/statusState,Z/X 視為死)。測試computer_alive_script_treats_a_zombie_as_dead本機造真 zombie 驗證。 - launcher 沒吐出 pid 時仍登記 job(pid None)→ 永遠 running、不能 cancel:現在直接回錯誤,不登記幽靈 job。
exec status/cancel不看歸屬與世代:新增job_ownership:別的 bot 的 job →UNKNOWN_JOB;generation小於目前 Computer 世代或已interrupted→ 直接回 interrupted,不去問新容器;persist_job的 upsert 不再把interrupted蓋回running。pin_running_jobs把同 Computer 上其他 bot 的 job 也 pin 了:加bot_id條件。
Tool Manager(T18–T21)
- revoke 後重裝撞
UNIQUE(package_row_id, bot_id)→ 500:ON CONFLICT … DO UPDATE SET status='ready' RETURNING id;切版本走switch_binding(同 tx 內先清掉同 (package_row, bot) 的非 ready 殘留)。 - rollback 會把
revokedbinding 復活:非ready→ 409。 - 執行前不驗 digest:
verified_run_argv在同一條命令內sha256sum -c通過才exec python3;不符 exit 97 →PACKAGE_TAMPERED。測試tampered_package_is_refused_before_it_runs真改檔驗證。 validate_manifest只檢查非空:現要求 64 hex;compute-at-install佔位會被拒。InstallBody只吃package_id、前端送packageId:serderename+alias兩者皆收。refuse_unpinned_npx把@scope/pkg的@當版本 pin:修為去掉 scope 前綴再判斷。
其他
form_fill靜默截斷:>20 欄或欄位缺 name/locator →INVALID_ARGUMENT,不再填半張表回報成功。looks_like_css誤判v1.2 Release/C++ Guide這種標題為 CSS:click/type 先做精確唯一標題比對;找不到才依 locator 形狀決定SELECTOR_UNSUPPORTED/TARGET_NOT_FOUND/TARGET_STALE,並各給對應說明。computers.display_backend從未寫入:boot 轉 running 時寫入DisplayBackend::from_env();health 不再永遠回欄位預設值。delete_bot不清tool_bindings/computer_jobs/computer_operations/computer_artifacts:同 tx 一起刪(Team Computer 的配額不再被已刪 bot 佔用)。wait在PROGRESS_WHEN_REPEATED內,與本文件「wait 不刷新無進展時鐘」相反:改成 heartbeat 語意 —wait只更新last_heartbeat_turn(不會被當 loop halt),但 stale 教練訊息照常觸發;polling_a_build_or_a_queue_is_not_treated_as_spinning同時鎖住兩半契約。- Gmail fixture 沒有真的 undo:新增
undo_plan(before, batches),只還原本次計畫實際改動的 label(原本就有的不拔、原本沒有的不補、人工新加的不動),測試做 plan→undo 往返驗證。 - 前端:
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 可在本環境落地的已接上。下面不是「做完」:
- 真實 Gmail/Outlook OAuth/主信箱:
BLOCKED_EXTERNAL。 - TigerVNC 當預設 + Cua/中文/a11y GUI:
BLOCKED_EXTERNAL;預設仍 Xvfb+x11vnc。 - Postgres sqlx 整合測試此環境未連。
- O04 PTY
terminal.*:DEFERRED — 非互動走exec+jobs+process-tree cancel;TUI 協作仍用 GUIshell。不假裝有 PTY。 - 跨重啟回收容器內仍在跑的 PID:標 interrupted,不是 resume。
- 任意第三方套件商店、完整 OAuth broker:沒有憑證。
- O48 無未測速度倍數。
- 外掛 UI 未在瀏覽器點過(無瀏覽器工具);已
tsc --noEmit。
雙模式:Team/Dedicated 契約不變;沒有 UNIQUE bots.computer_id。stdio MCP 不是 API 子行程。
回滾:還原本輪 crate / migration 022 / lazyboy-screen -ac 註解 / sample-mcp / 前端 plugins+i18n。不要 force push。