| 1 | import { isHostRecoveryGuidance } from "./hostRecoverySteer"; |
| 2 | import type { WireEvent } from "./types"; |
| 3 | import type { State } from "./useController"; |
| 4 | |
| 5 | // A steer receipt acknowledges the inbox item independently of its display |
| 6 | // row. Canonical records may arrive first, and history must never acknowledge |
| 7 | // another pending item merely because it has the same text. |
| 8 | export function applySteerEvent(state: State, event: WireEvent): State { |
| 9 | const text = event.text ?? ""; |
| 10 | if (isHostRecoveryGuidance(text)) return state; |
| 11 | const id = event.messageId ? `he:m:${event.messageId}` : `s${state.seq}`; |
| 12 | const receipt = { key: event.itemId || event.messageId || id, itemId: event.itemId, text }; |
| 13 | const next = { ...state, guidanceConsumed: receipt }; |
| 14 | // Older servers omit messageId. On transcript-v2 the canonical record still |
| 15 | // owns display; an uncorrelated receipt cannot create a second visible row. |
| 16 | if (state.transcriptProtocol === 2 && !event.messageId) return { ...next, seq: state.seq + 1 }; |
| 17 | if (state.items.some(item => item.id === id)) return next; |
| 18 | return { ...next, seq: state.seq + 1, items: [...state.items, { |
| 19 | kind: "notice" as const, id, level: "info" as const, text: `↪ ${text}`, inboxItemId: event.itemId, |
| 20 | }] }; |
| 21 | } |
| 22 |