| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "strings" |
| 6 | ) |
| 7 | |
| 8 | func remotePromptCacheKind(kind string) string { |
| 9 | switch kind { |
| 10 | case "ask": |
| 11 | return "ask_request" |
| 12 | case "mcp": |
| 13 | return "mcp_interaction" |
| 14 | case "approval", "plan", "recovery": |
| 15 | return "approval_request" |
| 16 | default: |
| 17 | return "" |
| 18 | } |
| 19 | } |
| 20 | |
| 21 | func pendingFrameMatchesTarget(frame json.RawMessage, target RemotePromptTarget) bool { |
| 22 | var probe struct { |
| 23 | RuntimeEpoch string `json:"runtimeEpoch"` |
| 24 | Approval *struct { |
| 25 | ID, TurnID string |
| 26 | } `json:"approval"` |
| 27 | Ask *struct { |
| 28 | ID, TurnID string |
| 29 | } `json:"ask"` |
| 30 | MCPInteraction *struct { |
| 31 | ID, TurnID string |
| 32 | } `json:"mcpInteraction"` |
| 33 | } |
| 34 | if json.Unmarshal(frame, &probe) != nil { |
| 35 | return false |
| 36 | } |
| 37 | id, turnID := "", "" |
| 38 | switch target.Kind { |
| 39 | case "ask": |
| 40 | if probe.Ask != nil { |
| 41 | id, turnID = probe.Ask.ID, probe.Ask.TurnID |
| 42 | } |
| 43 | case "mcp": |
| 44 | if probe.MCPInteraction != nil { |
| 45 | id, turnID = probe.MCPInteraction.ID, probe.MCPInteraction.TurnID |
| 46 | } |
| 47 | default: |
| 48 | if probe.Approval != nil { |
| 49 | id, turnID = probe.Approval.ID, probe.Approval.TurnID |
| 50 | } |
| 51 | } |
| 52 | return id == target.PromptID && turnID == target.TurnID && probe.RuntimeEpoch == target.RuntimeEpoch |
| 53 | } |
| 54 | |
| 55 | func (a *App) clearRemotePendingPromptExact(target RemotePromptTarget) { |
| 56 | cacheKind := remotePromptCacheKind(strings.TrimSpace(target.Kind)) |
| 57 | if cacheKind == "" { |
| 58 | return |
| 59 | } |
| 60 | a.remoteTabMu.Lock() |
| 61 | var meta TabMeta |
| 62 | changed := false |
| 63 | if tab := a.remoteTabs[target.TabID]; tab != nil && tab.ref.HostID == target.HostID && |
| 64 | tab.session.sessionID == target.SessionID && tab.gen == target.SessionGeneration { |
| 65 | key := cacheKind + ":" + strings.TrimSpace(target.PromptID) |
| 66 | if frame := tab.pendingEvents[key]; pendingFrameMatchesTarget(frame, target) { |
| 67 | delete(tab.pendingEvents, key) |
| 68 | tab.runtime.revision++ |
| 69 | pending := len(tab.pendingEvents) > 0 |
| 70 | changed = tab.runtime.pendingPrompt != pending |
| 71 | tab.runtime.pendingPrompt = pending |
| 72 | meta = remoteTabMetaLocked(tab) |
| 73 | } |
| 74 | } |
| 75 | a.remoteTabMu.Unlock() |
| 76 | if changed { |
| 77 | a.emitRemoteEvent("remote-tab:updated", meta) |
| 78 | } |
| 79 | } |
| 80 |