9f308fadd2
Rotate AI idle suggestions with optional remote packs, move history/dictionary onto self-sizing Home preview cards, harden clipboard capture/prompting, and simplify keyboard chrome by dropping most liquid-glass shadows.
168 lines
6.8 KiB
Swift
168 lines
6.8 KiB
Swift
// FlowSessionBridge+Mailbox.swift
|
|
// OSGKeyboard · Shared
|
|
|
|
import Foundation
|
|
|
|
extension FlowSessionBridge {
|
|
// MARK: - Typed Flow protocol
|
|
|
|
/// Persists the latest command plus a bounded journal of the newest 12
|
|
/// commands before notifying. Receivers replay by `commandSeq` for
|
|
/// at-least-once handling and use that sequence as the idempotency key.
|
|
public static func writeCommand(_ command: FlowCommand, defaults: UserDefaults? = nil) {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
if let data = FlowSessionBridgeStorage.encode(command) {
|
|
store.set(data, forKey: FlowSessionKeys.flowCommandPayload)
|
|
}
|
|
var journal = FlowSessionBridgeStorage.decode(
|
|
[FlowCommand].self,
|
|
from: store.data(forKey: FlowSessionKeys.flowCommandJournalPayload)
|
|
) ?? []
|
|
if !journal.contains(where: { $0.commandSeq == command.commandSeq }) {
|
|
journal.append(command)
|
|
journal.sort { $0.commandSeq < $1.commandSeq }
|
|
journal = Array(journal.suffix(12))
|
|
if let data = FlowSessionBridgeStorage.encode(journal) {
|
|
store.set(data, forKey: FlowSessionKeys.flowCommandJournalPayload)
|
|
}
|
|
}
|
|
FlowSessionBridgeStorage.flush(store)
|
|
FlowSessionDarwin.postCommandChanged()
|
|
}
|
|
|
|
public static func latestCommand(defaults: UserDefaults? = nil) -> FlowCommand? {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
return FlowSessionBridgeStorage.decode(
|
|
FlowCommand.self,
|
|
from: store.data(forKey: FlowSessionKeys.flowCommandPayload)
|
|
)
|
|
}
|
|
|
|
public static func commands(
|
|
after commandSeq: Int64,
|
|
defaults: UserDefaults? = nil
|
|
) -> [FlowCommand] {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
let journal = FlowSessionBridgeStorage.decode(
|
|
[FlowCommand].self,
|
|
from: store.data(forKey: FlowSessionKeys.flowCommandJournalPayload)
|
|
) ?? []
|
|
return journal
|
|
.filter { $0.commandSeq > commandSeq }
|
|
.sorted { $0.commandSeq < $1.commandSeq }
|
|
}
|
|
|
|
public static func writeStartTransaction(
|
|
_ transaction: FlowStartTransaction,
|
|
defaults: UserDefaults? = nil
|
|
) {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
if let data = FlowSessionBridgeStorage.encode(transaction) {
|
|
store.set(data, forKey: FlowSessionKeys.flowStartTransactionPayload)
|
|
}
|
|
FlowSessionBridgeStorage.flush(store)
|
|
}
|
|
|
|
public static func startTransaction(
|
|
defaults: UserDefaults? = nil
|
|
) -> FlowStartTransaction? {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
return FlowSessionBridgeStorage.decode(
|
|
FlowStartTransaction.self,
|
|
from: store.data(forKey: FlowSessionKeys.flowStartTransactionPayload)
|
|
)
|
|
}
|
|
|
|
public static func clearStartTransaction(defaults: UserDefaults? = nil) {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
store.removeObject(forKey: FlowSessionKeys.flowStartTransactionPayload)
|
|
FlowSessionBridgeStorage.flush(store)
|
|
}
|
|
|
|
/// Publishes only forward progress for one utterance: a terminal status
|
|
/// cannot regress to non-terminal, and non-nil revisions must increase.
|
|
public static func writeResult(_ result: FlowResult, defaults: UserDefaults? = nil) {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
if let existing = FlowSessionBridgeStorage.decode(
|
|
FlowResult.self,
|
|
from: store.data(forKey: FlowSessionKeys.flowResultPayload)
|
|
), existing.sessionId == result.sessionId,
|
|
existing.utteranceId == result.utteranceId,
|
|
isTerminal(existing.status),
|
|
!isTerminal(result.status) {
|
|
return
|
|
}
|
|
if let existing = FlowSessionBridgeStorage.decode(
|
|
FlowResult.self,
|
|
from: store.data(forKey: FlowSessionKeys.flowResultPayload)
|
|
), existing.sessionId == result.sessionId,
|
|
existing.utteranceId == result.utteranceId,
|
|
let existingRevision = existing.revision,
|
|
let incomingRevision = result.revision,
|
|
incomingRevision <= existingRevision {
|
|
return
|
|
}
|
|
if let data = FlowSessionBridgeStorage.encode(result) {
|
|
store.set(data, forKey: FlowSessionKeys.flowResultPayload)
|
|
}
|
|
FlowSessionBridgeStorage.flush(store)
|
|
FlowSessionDarwin.postTranscriptionChanged()
|
|
}
|
|
|
|
public static func latestResult(defaults: UserDefaults? = nil) -> FlowResult? {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
return FlowSessionBridgeStorage.decode(
|
|
FlowResult.self,
|
|
from: store.data(forKey: FlowSessionKeys.flowResultPayload)
|
|
)
|
|
}
|
|
|
|
public static func clearResult(defaults: UserDefaults? = nil) {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
store.removeObject(forKey: FlowSessionKeys.flowResultPayload)
|
|
FlowSessionBridgeStorage.flush(store)
|
|
}
|
|
|
|
public static func writeAck(_ ack: FlowAck, defaults: UserDefaults? = nil) {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
if let data = FlowSessionBridgeStorage.encode(ack) {
|
|
store.set(data, forKey: FlowSessionKeys.flowAckPayload)
|
|
}
|
|
FlowSessionBridgeStorage.flush(store)
|
|
FlowSessionDarwin.postTranscriptionChanged()
|
|
}
|
|
|
|
public static func latestAck(defaults: UserDefaults? = nil) -> FlowAck? {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
return FlowSessionBridgeStorage.decode(
|
|
FlowAck.self,
|
|
from: store.data(forKey: FlowSessionKeys.flowAckPayload)
|
|
)
|
|
}
|
|
|
|
public static func setPendingKeyboardUtteranceId(
|
|
_ id: UUID?,
|
|
defaults: UserDefaults? = nil
|
|
) {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
if let id {
|
|
store.set(id.uuidString, forKey: FlowSessionKeys.pendingKeyboardUtteranceId)
|
|
} else {
|
|
store.removeObject(forKey: FlowSessionKeys.pendingKeyboardUtteranceId)
|
|
}
|
|
FlowSessionBridgeStorage.flush(store)
|
|
}
|
|
|
|
public static func pendingKeyboardUtteranceId(defaults: UserDefaults? = nil) -> UUID? {
|
|
let store = FlowSessionBridgeStorage.resolvedDefaults(defaults)
|
|
guard let raw = store.string(forKey: FlowSessionKeys.pendingKeyboardUtteranceId) else {
|
|
return nil
|
|
}
|
|
return UUID(uuidString: raw)
|
|
}
|
|
|
|
private static func isTerminal(_ status: FlowResult.Status) -> Bool {
|
|
status == .final || status == .error || status == .aborted || status == .timeout
|
|
}
|
|
}
|