昨天把交給 agent 之前該準備的東西都講完了:probe / validate / run --chapter 三個指令,加上 TESTID.md、UI-MAP.md、QUIRKS.md、schema.json 四份上下文。
今天把這套東西實際跑一次。
範例專案 auto-manual-gen 的 manifest 目前有四章 (overview / live-monitor / layout-preset / settings),任務是加第五章:說明怎麼新增一台攝影機。
丟給 agent 的任務大致是這樣:
讀 apps/demo-stream-app/TESTID.md、agent/UI-MAP.md、agent/QUIRKS.md、
manifest/schema.json,以及 manifest/20-live-monitor.yaml 當格式範例。
新增一章 manifest/50-camera-add.yaml(id: camera-add, order: 50),
說明「如何新增一台攝影機」。需要探勘畫面就用 npm run probe,
寫完先跑 npm run validate,再跑 npm run manual -- --chapter camera-add --mode web。
兩個指令都過了再停,不要改 runner/ 底下任何檔案。
(只要大方向差不多,prompt 怎麼下應該影響不大)
最後那句話是重要的,要明確劃出 agent 可以動的範圍。它的工作是產出設定檔,不是在腳本跑不過的時候回頭改產線。

Day 16 說過這個迴圈長什麼樣子:探勘 → 寫一章 → validate → run --chapter → 失敗就讀錯誤訊息修、再跑一次。這中間 agent 確實被擋下來過幾次,但那些訊息是寫給 agent 看的,不是寫給人看的,人類只要看最後的新檔案就好。
進到 review 的,是這樣一份新檔案:
# manifest/50-camera-add.yaml(新檔案)
id: camera-add
title: 新增攝影機
order: 50
steps:
- { action: waitFor, testid: camera-list-skeleton, state: detached }
- { action: waitFor, testid: camera-list }
- { action: click, testid: camera-add }
- { action: waitFor, testid: camera-dialog }
- action: screenshot
name: camera-add-01
clip: { testid: camera-dialog, padding: 12 }
annotate:
- { key: name, testid: camera-dialog-name, legend: 顯示名稱 }
- { key: zone, testid: camera-dialog-zone, legend: 安裝位置 }
- { key: source, testid: camera-dialog-source, legend: 串流位址 }
- { key: enabled, testid: camera-dialog-enabled, legend: 啟用推論 }
- { action: fill, testid: camera-dialog-name, text: 大門西側 }
- action: screenshot
name: camera-add-02
clip: { testid: camera-dialog, padding: 12 }
annotate:
- { key: confirm, testid: camera-dialog-confirm, legend: 確認新增 }
兩張截圖是真的拍出來的:


這其實就是 Day 14 選宣告式設定檔的好處:新增一章就是新增一個 yaml 檔案,不動到任何既有內容,review 的人看兩張圖跟二十幾行 YAML 就能判斷這一章對不對。如果當初走的是「請 AI 寫一支 TypeScript 腳本」,這裡要 review 的會是一份幾百行、跟舊版看不出關係的新腳本。
機器已經確認過「動詞合法、selector 找得到、圖拍得出來」,所以 review 的重點只剩下機器判斷不了的部分。
不知道大家對於上面的範例有沒有覺得有什麼可以優化的地方?
其實已經挺好了,我覺得只有兩個很小的細節想微調:
操作順序不夠真實
腳本能跑,不代表這是使用者真的會走的流程。這一章只填了顯示名稱就示範送出,RTSP 位址那欄其實只是 placeholder 而已,不是實際填入的值。雖然能跑,但真實情境裡新增一台攝影機至少該連同 RTSP 位址一起填,不然可能會有讀者以為那樣就算設定完成了。
legend 的用詞跟畫面上的字不一致
manifest 裡 source 欄位標的是「串流位址」,但對話框上寫的其實是「RTSP 位址」。AI agent 有時候會「順手把文案換一個說法」,這件事在撰寫正文時會變成一個嚴重的問題。
這兩件事都不是加幾條規則就能自動判斷的,它們需要的是「知道這個產品怎麼被使用、畫面上實際寫的是什麼字」,那是人才有的上下文。於是又丟了第二個 prompt 回去:
review 了 manifest/50-camera-add.yaml 的截圖,兩點要改:
1. source 欄位的 legend 改成「RTSP 位址」,跟畫面上的字對齊
2. 新增流程除了填顯示名稱,也要示範填 RTSP 位址,
並且實際按下確認鍵、等 toast 出現後再多拍一張,
讓這一章示範完整跑完一次新增流程長什麼樣子
改完重跑 npm run manual -- --chapter camera-add --mode web。
改動只有這幾行:
- { key: name, testid: camera-dialog-name, legend: 顯示名稱 }
- { key: zone, testid: camera-dialog-zone, legend: 安裝位置 }
- - { key: source, testid: camera-dialog-source, legend: 串流位址 }
+ - { key: source, testid: camera-dialog-source, legend: RTSP 位址 }
- { key: enabled, testid: camera-dialog-enabled, legend: 啟用推論 }
- { action: fill, testid: camera-dialog-name, text: 大門西側 }
+ - { action: fill, testid: camera-dialog-source, text: 'rtsp://10.0.4.115/live' }
- action: screenshot
name: camera-add-02
clip: { testid: camera-dialog, padding: 12 }
annotate:
- { key: confirm, testid: camera-dialog-confirm, legend: 確認新增 }
+
+ - { action: click, testid: camera-dialog-confirm }
+ - { action: waitFor, testid: toast }
+
+ - action: screenshot
+ name: camera-add-03
+ clip: { testid: toast-host, padding: 12 }
重跑之後,第一張圖其實沒變,畢竟 legend 只是 metadata,annotate 畫的是編號圓標,不會把文字印到圖上,所以「串流位址」改成「RTSP 位址」這件事只會出現在 YAML diff 裡,圖片本身看不出差異。真正有變化的是第二張,以及多了第三張:


第三張圖能穩定拍到,靠的是 Day 15 就寫進 QUIRKS.md 的那條規則:toast 3 秒後自動消失,waitFor: toast 抓到語意訊號就立刻按快門,不要在中間插入其他等待。這條規則沒寫進上下文的話,agent 大概率會用固定延遲賭時間,直到後面去翻程式碼才發現問題。
如果各位想自己試試看,可以把範例專案 clone 下來、切到 chore/day17 分支就能重現這兩個版本。人工審查前與微調後各留了一個 tag,對應文章裡的兩組截圖:
npm run demo # 另開一個終端機,Web 模式跑起來
# agent 的第一版:只示範填顯示名稱
git checkout day17-agent-v1 -- manifest/50-camera-add.yaml
npm run validate -- --chapter camera-add
npm run manual -- --chapter camera-add --mode web # 產出 camera-add-01 / -02
# 人工審查後微調:補 RTSP 位址、修正 legend、示範按下確認鍵
git checkout day17 -- manifest/50-camera-add.yaml
npm run manual -- --chapter camera-add --mode web # 多出 camera-add-03
probe 也是這次順便補上的,想看 agent 探勘畫面時看到的東西長什麼樣,直接試:
npm run probe -- --mode web --after click:camera-add
如果想換一章比較硬的自己試試看,可以換成「刪除一台攝影機」——那一章會遇到巢狀對話框(刪除確認框疊在攝影機對話框之上),也會遇到刪除按鈕只在編輯模式才存在的問題,
QUIRKS.md裡都寫了,但沒實際跑一次很難有感覺。
這是最常見的幻覺形式。即使給了完整清單,它還是可能「猜」出一個聽起來很合理、實際不存在的 testid (e.g. 確認鍵被猜成 camera-dialog-save)。
防範方式是兩層都要有:validate 擋格式,run 擋存在性。只靠其中一層都會漏,schema 不知道畫面上有什麼,而 runner 要等到開機才知道。
很容易想直接說「幫我把整本手冊的 manifest 寫出來」,但這樣做有兩個問題:失敗的時候不知道是哪一章壞了,而且 review 的人要一次面對二、三十個新檔案。
一次一章,每一章都走完「產出 → 驗證 → 實跑 → review」,錯誤才不會累積。這跟 Day 15 把重跑單位設計成「一章」是同一個理由。
實際做下來,UI-MAP.md、QUIRKS.md 這幾份文件花掉的時間,比 agent 產出設定檔的時間多得多。
但這個成本只付一次,而且下一章、下一次改版、甚至新人進來都還在用。相對地,如果省掉這一步,省下的時間會以「agent 每一章都猜錯、每一章都要人重看」的形式,逐漸還回去。
今天這一章從頭到尾,AI agent 自己搞定一切,把設定檔寫出來了。人真正花時間做的,是機器判斷不了的那兩件事:操作順序像不像真人會做的事、文案用詞跟畫面對不對得上。看完、提出來、再丟回去微調一次,AI agent 就把它改好了。
不過,目前為止,AI 產出的還只是「怎麼拍」的設定檔,使用手冊還有同等重要的另一半:文字說明。這部分就明天接著討論啦!