從 local-tool 複製出獨立的「visionA Agent」桌面應用(A3 純橋樑: tunnel client + 配對 UI + 設定,不開 HTTP port、不做本機裝置/推論 UI)。 Bundle ID 與 local-tool 不同(com.innovedus.visiona-agent vs visiona-local), 雙 app 可共存。fork 後不主動 sync,需要時手動 cherry-pick。 Backend / Wails Go(AB1-AB13): - internal/tunnel:6 狀態機(Idle/Connecting/Connected/Reconnecting/Failed/Stopped) + Pair/Unpair/Reconnect/Disconnect binding + ClientHooks event - internal/auth:encrypted file token store(AES-GCM + scrypt + machineID fallback salt + 13 tests) - internal/config:YAML validation + atomic write + 11 tests - internal/log:ring buffer + ExportLog 升級 zip - visionA-backend /api/pairing/exchange:SessionTokenStore + 17 new tests - 三平台 build 驗證(macOS DMG 160 MB / Windows EXE / Linux AppImage) - end-to-end 5 milestone 全綠(pairing → tunnel → forward → reuse 防護 → tunnel drop failover) Frontend / Next.js(AF1-AF7,沿用 visionA-frontend 基礎): - AppShell + Header + TabNav(StatusView / PairView / SettingsView 三 tab) - ConnectionStatusBadge 5 種狀態 - TokenInput regex 驗證 + 7 種錯誤 + 0.5s auto-switch 到狀態頁 - 設定頁 4 區塊(含重新配對 AlertDialog) - agent-api.ts 封裝 Wails bindings(mock/real 雙實作)+ 90 tests Phase 0.7 review-driven fix(Round 2): - A1 Session fixation 防護(RotateSessionID) - A3 mock pairing 預設改 false(必須明確 opt-in)+ startup log - A4 Pair 失敗後 state 清理矩陣(exchange/Save/Start fail 各自終態) - A5 Pair/Unpair/Reconnect lifecycleMu + 50 goroutine race test - F1 重新配對次按鈕 / F2 PairView Esc cancel / F3 Wails BrowserOpenURL / F4 Settings draft 持久 + 未儲存 badge 驗證:agent backend go test -race -count=3 ./... 4 packages 全綠 / agent frontend pnpm test 119 tests 全綠 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
59 lines
2.0 KiB
Go
59 lines
2.0 KiB
Go
package ws
|
||
|
||
// system_ws.go — MAJ-4 補丁:/ws/system WebSocket endpoint
|
||
//
|
||
// 對應 TDD v2/server-lifecycle.md §2.3(Minor 4)與 v2/web-ui-offline-overlay.md §3.2a。
|
||
//
|
||
// 用途:讓瀏覽器 tab 訂閱 server 的 `server:shutdown-imminent` 廣播,
|
||
// 以便在 Wails 關閉 / Restart 流程 SIGTERM 前立即顯示 Offline Overlay,
|
||
// 避免只靠 health polling 導致的 15 秒延遲與 race。
|
||
//
|
||
// 室(room)名稱:`system`
|
||
// - Hub.BroadcastToRoom("system", payload) 由 system_handler.ShutdownNotify 呼叫
|
||
// - 目前只支援單一類型事件(server:shutdown-imminent),未來若有其他 system 事件可共用此 room
|
||
//
|
||
// 設計注意:
|
||
// - 不向新 client 送任何歷史訊息(shutdown event 必須「當下」收到才有意義,
|
||
// 補送一個過期的 shutdown 訊息會誤導前端)。
|
||
// - Read pump 只用於偵測 client 主動斷線,不處理任何 client → server 訊息。
|
||
// - 沿用 upgrader + CheckOrigin(loopback 白名單)。
|
||
|
||
import (
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/gorilla/websocket"
|
||
)
|
||
|
||
// SystemEventsHandler 處理 /ws/system 的 WebSocket 升級與訂閱。
|
||
func SystemEventsHandler(hub *Hub) gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||
if err != nil {
|
||
return
|
||
}
|
||
defer conn.Close()
|
||
|
||
client := &Client{Conn: conn, Send: make(chan []byte, 16)}
|
||
sub := &Subscription{Client: client, Room: "system"}
|
||
hub.RegisterSync(sub)
|
||
defer hub.Unregister(sub)
|
||
|
||
// Read pump — 唯一目的:偵測 client 斷線(close frame / socket error)→ 觸發 conn.Close
|
||
// 使得 write pump 的 range client.Send 結束後能乾淨退出。
|
||
go func() {
|
||
defer conn.Close()
|
||
for {
|
||
if _, _, err := conn.ReadMessage(); err != nil {
|
||
return
|
||
}
|
||
}
|
||
}()
|
||
|
||
// Write pump — 把 Hub 塞進 client.Send 的訊息送出去。
|
||
for msg := range client.Send {
|
||
if err := conn.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||
return
|
||
}
|
||
}
|
||
}
|
||
}
|