P0:路徑約定與程式碼不一致
routes/data_routes.py 和 agent_tools/workspace.py 兩處都把
交接文件路徑組成 <project>/project_context.md(根目錄版本),
但 HANDOFF.md 第 36 行和 UI 說明文字都寫 <project>/memory/project_context.md。
掃過 程式倉庫/ 所有 repo,沒有一個根目錄版本,只有 nomad-dashboard
自己有 memory/ 版本。
結果是:選任何專案,GET 回傳 0/0 字——文件不存在、內容全空。這還不是最糟糕的。
POST 用同一組路徑,所以使用者按儲存,會在 repo 根目錄新建孤兒檔。Agent 的
read_handoff_context 工具同樣中招,永遠讀不到使用者儲存的內容,
而 UI 會跳「已成功儲存並同步」。
P1 與 P2:其餘兩層缺陷
P2(沒有更新機制):
fetchHandoff() 只在切分頁、按重新載入、換專案時觸發。對照其他分頁:
ports 有每 5 秒 setInterval、agent 有 5 秒輪詢、healing logs 甚至有 SSE。
handoff 是唯一的例外——使用者看到的「不即時」問題就來自這裡,
但這其實是三個問題裡最輕的一個。
加輪詢還有隱藏的資料遺失路徑:textarea 直接綁值,「使用者正在打字 → Agent 背景更新 → 使用者按儲存」會用舊快照覆蓋 Agent 寫入的內容,且無任何提示。 光加輪詢不加鎖反而會製造新的問題。
P1(ZH 欄位指向不存在的檔案): 全域中文欄固定指向一個路徑,但實際上不存在,所以中文欄常態空白,也沒有任何說明。
修法:一個服務層解決路徑散亂
新增 services/handoff.py 作為路徑的單一真相來源。邏輯是:
優先找 memory/ 目錄下的文件;若根目錄有舊檔則相容回退;
兩處皆無時,新建仍落在 memory/。EN/ZH 一律成對取自同一目錄,
不讓它們因為新舊佈局混合而拆散在不同層級。
即時性加 10 秒輪詢,比對 mtime:textarea 未被編輯就自動吸收
Agent 的更新;已有修改則顯示橫幅讓使用者決定。POST 帶 base_mtime
樂觀鎖,磁碟版本較新時回 409 而非靜默覆寫。P1 改為回傳
en_path/zh_path/en_exists/zh_exists,
前端顯示實際解析路徑與「尚未建立,儲存後將自動建檔」,取代無聲空白。
驗證三條路徑
tests/test_handoff.py 13 個 case 涵蓋舊佈局回退與 EN/ZH 同目錄
不拆散,已掛進 make test。live API:GET nomad-dashboard 由 0/0 字
變 22011/12255 字,路徑落在 memory/,根目錄確認無孤兒檔。
瀏覽器實測:改背景檔後 13 秒自動同步顯示橫幅;先弄髒 textarea 再改背景檔,
編輯內容原封不動轉為警告橫幅;此時按儲存被 409 擋下,grep
確認磁碟檔案未被舊版本覆寫。
關鍵教訓
「不即時」的抱怨底下可能藏著「根本讀不到」:先驗資料是否到得了前端,再談刷新頻率。刷新頻率是表象,資料流通才是前提。
說明文字與程式碼路徑衝突時,說明文字通常才是對的:文件寫的是人類約定的意圖,程式碼可能只是沒對齊的實作。HANDOFF.md 與 UI 文案兩處獨立佐證 memory/,程式碼是孤例。
同一份路徑組法散在多處等於遲早分岔:route 與 agent tool 各寫一份、一起錯。抽成 service 層後兩邊共用,下次改路徑只改一處。
可編輯欄位加背景寫入者必須有樂觀鎖:只加輪詢會製造新的資料遺失路徑,髒值偵測與 mtime 基準要一起上,不能只解其一。
孤兒檔比空白更危險:空白讓使用者知道有問題,孤兒檔加上「儲存成功」的 toast 讓問題完全隱形,直到 Agent 無論如何讀不到才被發現。