返回列表

部落格

通知不該只打開 App,它應該把你帶回正確的 Session

推播通知不是「送到了」就算完成。對多會話 AI 程式工作來說,通知必須帶著正確的 server、session 與目前狀態一起到達。1.6.6 修的就是這條上下文回流鏈路。

發布於 標籤: product / workflow / notifications / engineering
部落格

繼續查看該欄目下的產品與工程更新。

瀏覽此分類

我們通常把通知理解成一個送達問題:到了沒有、快不快、能不能上鎖屏。對遠端 AI 程式工作來說,這個定義太淺了。

如果一則通知只能做到「把 App 打開」,那它並沒有真正閉合回饋環。你還是得自己重建現場:這是哪台機器、哪個會話、Claude 是在要求批准還是回報完成,以及它對應的是你現在看的任務,還是稍早留在背景跑的另一個 session。

這不是文案問題,也不是轉場動畫問題,而是一個完整的狀態同步問題。

為什麼這在 AI 程式場景裡更嚴重

在很多一般 App 裡,一則通知通常只對應一個物件:一封郵件、一則訊息、一個行事曆事件。你點進去之後,目標實體天然就是明確的。

遠端 AI 程式工作不是這樣。

你可能同時有三個 session 在跑:

  • 一個在重構某個模組
  • 一個在等測試結果
  • 另一個停在權限批准上

在這種情況下,「Claude 需要你」這句話根本不夠。真正重要的是:

  • 哪台 server 發出這則通知
  • 哪個 session 觸發了它
  • 那個 session 目前處於什麼狀態
  • 使用者現在是否已經在看這個 session

只要其中一項缺失,通知就會退化成模糊提醒。它能打斷你,卻不能幫你決策。

通知失效,不只是不見,也可能是帶你到錯的地方

很多系統把通知可靠性理解成「盡量不要掉」。但在多會話工作流裡,另一種一樣糟糕的失敗方式是:通知到了,卻把你帶回錯誤的上下文。

這類失敗比完全收不到更隱蔽,也常常更危險。

因為使用者會以為自己已經回到正確位置,接著就在錯誤的 session 上繼續操作。對遠端 AI 程式工作來說,這可能意味著:

  • 在錯誤的 repo 裡回覆 Claude
  • 給錯誤的任務做批准
  • 把另一個 session 的狀態誤判成目前任務的狀態

從互動層看,這像是一次普通跳轉錯誤;從產品層看,它破壞的是整套遠端工作流的信任。

要讓通知真正有用,上下文必須活過整條鏈路

一則有用的通知,不只是一個 APNs payload。它必須穿過完整鏈路,而且在每一段都不能遺失身分:

  1. 當 server hook 事件產生時,要知道是哪個 tmux_session 觸發
  2. 當 server 廣播通知時,要把 session 一起帶出去
  3. 當 push relay 去重時,不能只按 project 去重,不然不同 session 會互相吞掉
  4. 當 App 冷啟動時,要能把 push 裡的 server identity 映射回本地儲存的 server
  5. 當最終導覽發生時,要先回到正確 server,再選中正確 session,而不是落到預設頁

只要其中一段退化成「盡量猜」,最後行為就會不穩定。

這也是為什麼 1.6.6 裡和通知相關的改動,看起來分散在 iOS、Mac、server 與 push relay,但它們本質上都在修同一件事:把通知從泛泛提醒,變成可執行的上下文入口。

舊模型到底是怎麼壞掉的

如果把問題抽象一下,舊模型主要有四種常見失效方式。

1. 事件源有 session,通知卻沒有 session

Server 上的 hook 事件明明知道自己來自某個 tmux_session,但通知廣播只說「有一則通知」,沒有把 session 一起傳下去。到了客戶端,系統就只能退化成「先回當前頁再說」。

這會製造一個很危險的錯覺:通知點開後的確有跳,看起來像功能存在,但實際落點常常是錯的。

2. 前景可用,不代表冷啟動可用

很多通知跳轉在 App 還活著時都沒問題,真正難的是冷啟動。因為這時 App 還沒恢復完整狀態。

在那一刻,server 列表可能還在載入,導覽狀態可能還不存在,本地 ID 也未必和 push payload 裡的識別一致。如果太早驅動導覽,通常只會得到兩種結果:

  • 什麼都沒跳
  • 有跳,但跳到錯的 server 或 session

3. 去重邏輯錯把兩個 session 當成同一事件

如果 push relay 的 dedupe key 只包含 server + project + event,沒有把 session 算進去,那麼同一個專案裡兩個 session 在接近時間內觸發事件時,後一則通知就可能被前一則吞掉。

這不會長得像 crash,卻會長成最難查的使用者抱怨:「明明完成了,為什麼我沒收到通知?」

4. 客戶端把「目前可見 session」誤當成「通知來源 session」

這是多會話產品裡很典型的建模錯誤。UI 目前聚焦在哪個 session,和剛才發出通知的是哪個 session,本來就不是同一概念。如果狀態層沒有把它們拆開,跳錯幾乎是必然結果。

1.6.6 真正修的不是一個按鈕,而是一條路徑

這次版本裡真正重要的,不是「支援點擊通知跳轉」這句話,而是幾個原本斷開的約束終於被串了起來。

1. 通知現在會攜帶 session 身分

以前一個事件明明屬於 session B,但客戶端只知道「有通知發生」,最後就退回 session A。1.6.6 讓 session 身分穿過通知廣播與 push payload。現在通知不再只是「Claude 有事找你」,而是「repo-x / session-y 有事找你」。

真正難的不是頁面怎麼跳,而是身分怎麼對。push 帶來的是 server 端 identity;App 裡保存的是本地物件 ID。只有把這兩套身分系統對上,冷啟動跳轉才算真的成立。

換句話說,問題不在「怎麼切頁」,而在「系統怎麼證明這就是同一台 server」。

3. Push 投遞不再把 session 當成附屬欄位

如果 dedupe key 裡沒有 session,那兩個不同 session 就可能互相壓掉。這說明了一件事:在 AI 工作流裡,session 不是附屬 metadata,而是通知語義本身的一部分。

4. 本地通知、遠端 push 與頁面導覽終於開始說同一種語言

通知鏈路最怕的是三層各說各話:

  • server 說「這是 hook 事件」

  • push 服務說「這是一則 project 級通知」

  • 客戶端說「那我先回當前頁」

    1.6.6 最重要的價值之一,就是這三層開始對齊到同一個目標:把使用者帶回產生該事件的 server 與 session。

為什麼這是產品問題,不只是工程問題

表面上我們一直在談 ID、payload 與 deep link。但底下其實有一個更大的產品判斷:

通知是否應該被設計成「把使用者帶回需要做決策的正確現場」的機制?

如果答案是是,那送達率就不夠了。一則通知必須同時滿足三個條件:

  • 它有到
  • 它落在對的地方
  • 它落地之後,使用者可以立刻採取下一步動作

用這個標準來看,很多產品裡的通知其實只做到第一條。

使用者真正感受到的變化是什麼

如果完全不看內部實作,只從日常使用來看,1.6.6 至少改善了四個結果:

  1. 點開通知後,更少落到錯的 session
  2. 從鎖屏或已終止狀態喚起 App 時,更少遺失上下文
  3. 瞬時網路失敗更不容易直接吃掉通知
  4. Live Activity 在異常路徑裡的狀態更可信,即使 force-quit 邊界仍然存在

這四件事單看都不花俏,但它們一起回答了一個更重要的問題:你到底敢不敢真的把多個 AI session 掛在背景跑。

可靠也意味著要誠實面對邊界

1.6.6 也重寫了 Live Activity 的清理路徑:重新啟動時會回收陳舊 activity、清理孤兒參照,並降低短暫斷線後錯誤顯示「Finished」的機率。

但有一個邊界情況我們沒有假裝它已經被完全解決:如果使用者強制結束 iOS App,Live Activity 仍可能停留到下次啟動或系統 timeout。

如果我們把這件事包裝成「完全修好」,文案也許會比較漂亮,但產品會變得更不可信。對開發者工具尤其如此,因為你賣的不是氣氛,而是可預期性。

真正的目標不是更多通知

好的遠端 AI 產品,不應該要求使用者每次收到通知後都重新理解一次「剛才到底發生了什麼」。

通知真正該做的,是提前幫你把恢復上下文這件事做掉。

對遠端 AI 程式工作來說,送達只是入場券。通知能不能帶著完整上下文一起到達,才決定了它究竟是有用輸入,還是又一次打斷。

1.6.6 沒有發明通知,也沒有發明多會話。它做的是更基礎、也更重要的一件事:讓這兩者終於能正確協同工作。

立即體驗 Tactic Remote

用手機遠端操作程式開發 Agent

連線到 Mac、Windows 或 Linux 電腦上的 Claude Code、Codex 等 Agent,在 iPhone 或 iPad 上查看進度並傳送下一個請求。