工作中氣泡顯示工具動作與助理說明

摘要:
running 狀態的氣泡改成有東西就播報:優先顯示助理剛說那句話,否則顯示正在用的工具與對象。

根本原因:
使用者希望氣泡能像 telegram bot 那樣顯示 AI 的思考過程。
實際查證後思考文字拿不到——Claude Code 從 2.1.238 起不再把 thinking 寫進 transcript,
只留加密簽章(本 session 169 個 thinking 區塊文字全為空字串);
比對同樣用 claude-opus-5 但版本為 2.1.237 的舊 session 則存得到,
確認是 Claude Code 版本差異而非模型差異。Claude Code 也沒有對外事件串流,
所以 hook 拿不到思考內容,這點與 Codex CLI 的 --json reasoning 事件不同。
而參考的 cluemarket-tg-bot 其實也沒顯示思考:它的變數雖名為 lastThinkingText,
抓的卻是 text 區塊,另外兩個是寫死的「思考中」字串與工具標籤。
原本 running 狀態除了關閉舊氣泡外沒有任何資訊。

影響:
工作期間看不出 Claude Code 正在做什麼,只知道她在忙。

修法:
- hook 補送 toolTarget(依 command / file_path / pattern / url 等順序取一個欄位並截斷,
  避免 Write 這類工具把整份檔案內容塞進 payload)、agentType 與 transcriptPath。
- main.js 新增 latestNarration:由 transcript 尾端 192 KB 反向找最後一個非空的
  assistant text 區塊,依檔案大小快取,transcript 長到數 MB 也不會變慢;
  收到事件時把 narration 與其 uuid 併進 payload 再轉給 renderer。
- renderer 新增 sayActivity:有新的助理說明就顯示第一句,否則顯示工具標籤加對象。
  同一段說明只播報一次;waiting 時不播報以免蓋掉固定氣泡。
  另加 stripMarkdown,否則助理文字的 **粗體**、反引號、標題與連結語法會直接出現在氣泡裡。
- README 新增「能顯示什麼、不能顯示什麼」一節說明思考文字為何拿不到。

驗證:
- 單元測試以 DOM stub 載入完整 renderer.js 並抽出 main.js 的 latestNarration,15 項全過:
  對真實 transcript 取得助理說明與 uuid、快取一致、路徑不存在或為 null 時回 null 不拋錯、
  firstSentence 取第一句與忽略空行、Bash 取指令前段、檔案路徑只取檔名、過長截斷、
  新說明更新 id、同段不重複、waiting 時不播報、空 payload 不拋錯。
- markdown 清理另 7 項全過:粗體、反引號、標題、清單、連結、程式碼區塊、無標記純文字。
- 實機啟動並以真實事件驗證:events.log 顯示 hook 確實帶出 toolTarget 與 transcriptPath;
  截圖確認氣泡顯示「⚙️ 執行 npm run dist」,以及傳入真實 transcript 後顯示「💬 助理說明」。
  markdown 殘留問題就是在這一步的截圖中發現並修掉的。
- 以 ELECTRON_RUN_AS_NODE 讀打包後 app.asar 與 app.asar.unpacked,確認版本 2.0.4
  與九項改動都在出貨檔案裡。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-24 10:16:43 +08:00
co-authored by Claude Fable 5
parent f1e7aca797
commit 95c9b97eb7
6 changed files with 158 additions and 4 deletions
+26 -1
View File
@@ -20,6 +20,7 @@
- [新增 / 切換寵物](#新增--切換寵物)
- [設定檔](#設定檔)
- [架構與檔案](#架構與檔案)
- [能顯示什麼、不能顯示什麼](#能顯示什麼不能顯示什麼)
- [與 Codex 的對照](#與-codex-的對照)
- [HTTP API](#http-api)
- [打包成 exe](#打包成-exe)
@@ -40,6 +41,7 @@
- **不搶焦點** — 視窗設為 non-focusable,點她不會讓終端機失焦。
- **拖曳 / 點擊 / 右鍵** — 拖曳時依方向播 running-left / running-right 並記住位置;點一下揮手;右鍵或系統匣開選單。
- **對話氣泡** — 完成、需要授權、有問題要問你、工具失敗、整理記憶中……一眼看出 Claude Code 在等什麼。
- **工作中即時播報** — 氣泡會顯示正在用的工具與對象(`⚙️ 執行 npm run dist``📖 讀 renderer.js`),以及助理剛說的那句話(`💬 …`)。**注意:這不是思考過程**,原因見[能顯示什麼、不能顯示什麼](#能顯示什麼不能顯示什麼)。
- **多 session** — 同時開好幾個 Claude Code 時,以 `waiting > running > review > idle` 的優先序顯示最需要你注意的那個。
- **Codex 寵物自動出現** — 預設也掃描 `~/.codex/pets`Codex hatch 出來的寵物直接在選單裡切換。
- **Hook 零負擔** — hook 腳本約 100 ms 完成、寵物沒開也靜默 exit 0,不會拖慢或卡住 Claude Code。
@@ -109,7 +111,7 @@ npm run autostart:install # (可選)開機自動啟動
| (程式啟動) | 該寵物第一次出現 | `waving` × 3 → 慢速 `idle` | 👋 嗨,我是〈寵物名〉(8 秒) |
| `SessionStart` | `source = startup` | `idle` | 👋 嗨!(專案) |
| `UserPromptSubmit` | | `running` | (關閉舊氣泡) |
| `PreToolUse` | 一般工具 | `running` | |
| `PreToolUse` | 一般工具 | `running` | 有新的助理說明就顯示 `💬 …`,否則顯示 `⚙️ 執行 …` 之類的工具動作 |
| `PreToolUse` | `AskUserQuestion` | `waiting` | ❓ 有問題想問你 |
| `PreToolUse` | `ExitPlanMode` | `waiting` | 📋 計畫等你確認 |
| `PostToolUse` | | `running` | |
@@ -244,6 +246,29 @@ Claude Code ──hookstdin JSON)──▶ hook/claude-pet-hook.js ──PO
- **Spritesheet 以 data URL 傳給 renderer**,避免 `file://` 跨來源污染 canvas 導致 `getImageData` 失敗。
- **hook 合併規則**:以指令中是否含 `claude-pet-hook` 辨識自己的項目,重裝只替換自己的、不動別人的。
## 能顯示什麼、不能顯示什麼
氣泡**顯示不了 Claude 的思考過程**。Claude Code 從 **2.1.238** 起就不再把 thinking 的文字寫進 transcript,只留加密簽章:
```json
{ "type": "thinking", "thinking": "", "signature": "CAQSwgQKEAgRGAI4AUII…" }
```
(2.1.237 以前有文字。這是 Claude Code 版本的差異,跟模型無關——同樣是 `claude-opus-5`
2.1.237 的 session 存得到、2.1.241 的存不到。)Claude Code 也沒有對外的事件串流,
所以 hook 拿不到思考內容。這點跟 Codex 不同:Codex CLI 的 `--json` 會直接送出 `reasoning` 事件。
拿得到的是這些,氣泡就顯示這些:
| 來源 | 內容 | 從哪來 |
|---|---|---|
| 工具動作 | `⚙️ 執行 npm run dist``📖 讀 renderer.js` | hook 的 `tool_name``tool_input` |
| 助理說明 | 助理剛講那段話的第一句 | 由 `transcript_path` 讀 transcript 尾端的 `text` 區塊 |
同一段說明只播報一次(以 transcript 的 uuid 判斷),markdown 標記會先清掉,
等待授權時不播報以免蓋掉固定氣泡。讀 transcript 只讀最後 192 KB 並依檔案大小快取,
不會因為 transcript 長到幾 MB 就變慢。
## 與 Codex 的對照
所有數值都是從 Codex app`WindowsApps\OpenAI.Codex_*\app\resources\app.asar`)的 `codex-pet-assets``avatar-overlay-native-frame` 與主程序讀出來的。