From cab5a26961378f9142b52e9166f3cc668d95d3b0 Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Mon, 5 Oct 2026 06:53:54 +0330 Subject: [PATCH 01/14] proto: reserve disjoint grid transcription contracts --- .../Sources/InlineProtocol/client.pb.swift | 18 +- .../Sources/InlineProtocol/core.pb.swift | 1164 ++++++++++++++++- crates/protocol/proto/core.proto | 78 ++ packages/protocol/src/client.ts | 13 +- packages/protocol/src/core.ts | 981 +++++++++++++- .../protocol/tests/grid-transcription.test.ts | 111 ++ proto/client.proto | 1 + proto/core.proto | 78 ++ proto/server.proto | 1 + 9 files changed, 2377 insertions(+), 68 deletions(-) create mode 100644 packages/protocol/tests/grid-transcription.test.ts diff --git a/apple/InlineKit/Sources/InlineProtocol/client.pb.swift b/apple/InlineKit/Sources/InlineProtocol/client.pb.swift index 35f1d66c7..b15df44d2 100644 --- a/apple/InlineKit/Sources/InlineProtocol/client.pb.swift +++ b/apple/InlineKit/Sources/InlineProtocol/client.pb.swift @@ -75,6 +75,15 @@ public nonisolated struct Client_MessageContentPayload: @unchecked Sendable { /// Clears the value of `subthread`. Subsequent reads from it will return its default value. public mutating func clearSubthread() {_uniqueStorage()._subthread = nil} + public var countsAsUnread: Bool { + get {_storage._countsAsUnread ?? false} + set {_uniqueStorage()._countsAsUnread = newValue} + } + /// Returns true if `countsAsUnread` has been explicitly set. + public var hasCountsAsUnread: Bool {_storage._countsAsUnread != nil} + /// Clears the value of `countsAsUnread`. Subsequent reads from it will return its default value. + public mutating func clearCountsAsUnread() {_uniqueStorage()._countsAsUnread = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -139,7 +148,7 @@ fileprivate nonisolated let _protobuf_package = "client" nonisolated extension Client_MessageContentPayload: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".MessageContentPayload" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}voice\0\u{1}actions\0\u{1}replies\0\u{3}service_message\0\u{1}subthread\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}voice\0\u{1}actions\0\u{1}replies\0\u{3}service_message\0\u{1}subthread\0\u{3}counts_as_unread\0") fileprivate class _StorageClass { var _voice: Client_MessageVoiceContent? = nil @@ -147,6 +156,7 @@ nonisolated extension Client_MessageContentPayload: SwiftProtobuf.Message, Swift var _replies: MessageReplies? = nil var _serviceMessage: MessageService? = nil var _subthread: MessageSubthread? = nil + var _countsAsUnread: Bool? = nil // This property is used as the initial default value for new instances of the type. // The type itself is protecting the reference to its storage via CoW semantics. @@ -162,6 +172,7 @@ nonisolated extension Client_MessageContentPayload: SwiftProtobuf.Message, Swift _replies = source._replies _serviceMessage = source._serviceMessage _subthread = source._subthread + _countsAsUnread = source._countsAsUnread } } @@ -185,6 +196,7 @@ nonisolated extension Client_MessageContentPayload: SwiftProtobuf.Message, Swift case 3: try { try decoder.decodeSingularMessageField(value: &_storage._replies) }() case 4: try { try decoder.decodeSingularMessageField(value: &_storage._serviceMessage) }() case 5: try { try decoder.decodeSingularMessageField(value: &_storage._subthread) }() + case 6: try { try decoder.decodeSingularBoolField(value: &_storage._countsAsUnread) }() default: break } } @@ -212,6 +224,9 @@ nonisolated extension Client_MessageContentPayload: SwiftProtobuf.Message, Swift try { if let v = _storage._subthread { try visitor.visitSingularMessageField(value: v, fieldNumber: 5) } }() + try { if let v = _storage._countsAsUnread { + try visitor.visitSingularBoolField(value: v, fieldNumber: 6) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -226,6 +241,7 @@ nonisolated extension Client_MessageContentPayload: SwiftProtobuf.Message, Swift if _storage._replies != rhs_storage._replies {return false} if _storage._serviceMessage != rhs_storage._serviceMessage {return false} if _storage._subthread != rhs_storage._subthread {return false} + if _storage._countsAsUnread != rhs_storage._countsAsUnread {return false} return true } if !storagesAreEqual {return false} diff --git a/apple/InlineKit/Sources/InlineProtocol/core.pb.swift b/apple/InlineKit/Sources/InlineProtocol/core.pb.swift index 9ae1c7a6c..6ef6b319a 100644 --- a/apple/InlineKit/Sources/InlineProtocol/core.pb.swift +++ b/apple/InlineKit/Sources/InlineProtocol/core.pb.swift @@ -70,6 +70,52 @@ public nonisolated enum DialogFollowMode: SwiftProtobuf.Enum, Swift.CaseIterable } +public nonisolated enum GridTranscriptMessageKind: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case gridTranscriptTurn // = 0 + case gridTranscriptStarted // = 1 + case gridTranscriptStopped // = 2 + case gridTranscriptInterrupted // = 3 + case gridTranscriptLink // = 4 + case UNRECOGNIZED(Int) + + public init() { + self = .gridTranscriptTurn + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .gridTranscriptTurn + case 1: self = .gridTranscriptStarted + case 2: self = .gridTranscriptStopped + case 3: self = .gridTranscriptInterrupted + case 4: self = .gridTranscriptLink + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .gridTranscriptTurn: return 0 + case .gridTranscriptStarted: return 1 + case .gridTranscriptStopped: return 2 + case .gridTranscriptInterrupted: return 3 + case .gridTranscriptLink: return 4 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [GridTranscriptMessageKind] = [ + .gridTranscriptTurn, + .gridTranscriptStarted, + .gridTranscriptStopped, + .gridTranscriptInterrupted, + .gridTranscriptLink, + ] + +} + /// Agent providers that implement Inline's session continuity contract. The /// numeric values are stored by the server; additions must be append-only. public nonisolated enum AgentSessionProvider: SwiftProtobuf.Enum, Swift.CaseIterable { @@ -525,6 +571,9 @@ public nonisolated enum Method: SwiftProtobuf.Enum, Swift.CaseIterable { case requestBotFilesystem // = 143 case answerBotFilesystem // = 144 case setSpacePhoto // = 145 + case openGridThread // = 147 + case setGridTranscription // = 148 + case listGridTranscripts // = 149 case UNRECOGNIZED(Int) public init() { @@ -678,6 +727,9 @@ public nonisolated enum Method: SwiftProtobuf.Enum, Swift.CaseIterable { case 143: self = .requestBotFilesystem case 144: self = .answerBotFilesystem case 145: self = .setSpacePhoto + case 147: self = .openGridThread + case 148: self = .setGridTranscription + case 149: self = .listGridTranscripts default: self = .UNRECOGNIZED(rawValue) } } @@ -829,6 +881,9 @@ public nonisolated enum Method: SwiftProtobuf.Enum, Swift.CaseIterable { case .requestBotFilesystem: return 143 case .answerBotFilesystem: return 144 case .setSpacePhoto: return 145 + case .openGridThread: return 147 + case .setGridTranscription: return 148 + case .listGridTranscripts: return 149 case .UNRECOGNIZED(let i): return i } } @@ -980,6 +1035,97 @@ public nonisolated enum Method: SwiftProtobuf.Enum, Swift.CaseIterable { .requestBotFilesystem, .answerBotFilesystem, .setSpacePhoto, + .openGridThread, + .setGridTranscription, + .listGridTranscripts, + ] + +} + +public nonisolated enum GridTranscriptionState: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case gridTranscriptionUnspecified // = 0 + case gridTranscriptionStarting // = 1 + case gridTranscriptionActive // = 2 + case gridTranscriptionStopping // = 3 + case gridTranscriptionStopped // = 4 + case gridTranscriptionInterrupted // = 5 + case UNRECOGNIZED(Int) + + public init() { + self = .gridTranscriptionUnspecified + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .gridTranscriptionUnspecified + case 1: self = .gridTranscriptionStarting + case 2: self = .gridTranscriptionActive + case 3: self = .gridTranscriptionStopping + case 4: self = .gridTranscriptionStopped + case 5: self = .gridTranscriptionInterrupted + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .gridTranscriptionUnspecified: return 0 + case .gridTranscriptionStarting: return 1 + case .gridTranscriptionActive: return 2 + case .gridTranscriptionStopping: return 3 + case .gridTranscriptionStopped: return 4 + case .gridTranscriptionInterrupted: return 5 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [GridTranscriptionState] = [ + .gridTranscriptionUnspecified, + .gridTranscriptionStarting, + .gridTranscriptionActive, + .gridTranscriptionStopping, + .gridTranscriptionStopped, + .gridTranscriptionInterrupted, + ] + +} + +public nonisolated enum GridTranscriptDestination: SwiftProtobuf.Enum, Swift.CaseIterable { + public typealias RawValue = Int + case gridTranscriptLast // = 0 + case gridTranscriptNew // = 1 + case gridTranscriptExisting // = 2 + case UNRECOGNIZED(Int) + + public init() { + self = .gridTranscriptLast + } + + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .gridTranscriptLast + case 1: self = .gridTranscriptNew + case 2: self = .gridTranscriptExisting + default: self = .UNRECOGNIZED(rawValue) + } + } + + public var rawValue: Int { + switch self { + case .gridTranscriptLast: return 0 + case .gridTranscriptNew: return 1 + case .gridTranscriptExisting: return 2 + case .UNRECOGNIZED(let i): return i + } + } + + // The compiler won't synthesize support with the UNRECOGNIZED case. + public static let allCases: [GridTranscriptDestination] = [ + .gridTranscriptLast, + .gridTranscriptNew, + .gridTranscriptExisting, ] } @@ -3494,11 +3640,20 @@ public nonisolated struct MessageService: Sendable { set {event = .pinnedMessage(newValue)} } + public var gridTranscript: MessageServiceGridTranscript { + get { + if case .gridTranscript(let v)? = event {return v} + return MessageServiceGridTranscript() + } + set {event = .gridTranscript(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public nonisolated enum OneOf_Event: Equatable, Sendable { case threadBacklink(MessageServiceThreadBacklink) case pinnedMessage(MessageServicePinnedMessage) + case gridTranscript(MessageServiceGridTranscript) } @@ -3558,6 +3713,33 @@ public nonisolated struct MessageServicePinnedMessage: Sendable { fileprivate var _messageID: Int64? = nil } +public nonisolated struct MessageServiceGridTranscript: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var runID: String = String() + + public var segmentID: String = String() + + public var speakerUserID: Int64 { + get {_speakerUserID ?? 0} + set {_speakerUserID = newValue} + } + /// Returns true if `speakerUserID` has been explicitly set. + public var hasSpeakerUserID: Bool {self._speakerUserID != nil} + /// Clears the value of `speakerUserID`. Subsequent reads from it will return its default value. + public mutating func clearSpeakerUserID() {self._speakerUserID = nil} + + public var kind: GridTranscriptMessageKind = .gridTranscriptTurn + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _speakerUserID: Int64? = nil +} + /// Structural rich content projected alongside Message.message and /// Message.entities. Text-bearing blocks use UTF-16 ranges into Message.message; /// inline entities remain owned by Message.entities so there is one text/entity @@ -4537,6 +4719,16 @@ public nonisolated struct Message: @unchecked Sendable { /// Clears the value of `subthread`. Subsequent reads from it will return its default value. public mutating func clearSubthread() {_uniqueStorage()._subthread = nil} + /// Explicit false suppresses unread for generated/historical rows. + public var countsAsUnread: Bool { + get {_storage._countsAsUnread ?? false} + set {_uniqueStorage()._countsAsUnread = newValue} + } + /// Returns true if `countsAsUnread` has been explicitly set. + public var hasCountsAsUnread: Bool {_storage._countsAsUnread != nil} + /// Clears the value of `countsAsUnread`. Subsequent reads from it will return its default value. + public mutating func clearCountsAsUnread() {_uniqueStorage()._countsAsUnread = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -8232,6 +8424,30 @@ public nonisolated struct RpcCall: Sendable { set {input = .setSpacePhoto(newValue)} } + public var openGridThread: OpenGridThreadInput { + get { + if case .openGridThread(let v)? = input {return v} + return OpenGridThreadInput() + } + set {input = .openGridThread(newValue)} + } + + public var setGridTranscription: SetGridTranscriptionInput { + get { + if case .setGridTranscription(let v)? = input {return v} + return SetGridTranscriptionInput() + } + set {input = .setGridTranscription(newValue)} + } + + public var listGridTranscripts: ListGridTranscriptsInput { + get { + if case .listGridTranscripts(let v)? = input {return v} + return ListGridTranscriptsInput() + } + set {input = .listGridTranscripts(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public nonisolated enum OneOf_Input: Equatable, Sendable { @@ -8379,6 +8595,9 @@ public nonisolated struct RpcCall: Sendable { case requestBotFilesystem(RequestBotFilesystemInput) case answerBotFilesystem(AnswerBotFilesystemInput) case setSpacePhoto(SetSpacePhotoInput) + case openGridThread(OpenGridThreadInput) + case setGridTranscription(SetGridTranscriptionInput) + case listGridTranscripts(ListGridTranscriptsInput) } @@ -9552,6 +9771,30 @@ public nonisolated struct RpcResult: @unchecked Sendable { set {_uniqueStorage()._result = .setSpacePhoto(newValue)} } + public var openGridThread: OpenGridThreadResult { + get { + if case .openGridThread(let v)? = _storage._result {return v} + return OpenGridThreadResult() + } + set {_uniqueStorage()._result = .openGridThread(newValue)} + } + + public var setGridTranscription: SetGridTranscriptionResult { + get { + if case .setGridTranscription(let v)? = _storage._result {return v} + return SetGridTranscriptionResult() + } + set {_uniqueStorage()._result = .setGridTranscription(newValue)} + } + + public var listGridTranscripts: ListGridTranscriptsResult { + get { + if case .listGridTranscripts(let v)? = _storage._result {return v} + return ListGridTranscriptsResult() + } + set {_uniqueStorage()._result = .listGridTranscripts(newValue)} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public nonisolated enum OneOf_Result: Equatable, Sendable { @@ -9699,6 +9942,9 @@ public nonisolated struct RpcResult: @unchecked Sendable { case requestBotFilesystem(RequestBotFilesystemResult) case answerBotFilesystem(AnswerBotFilesystemResult) case setSpacePhoto(SetSpacePhotoResult) + case openGridThread(OpenGridThreadResult) + case setGridTranscription(SetGridTranscriptionResult) + case listGridTranscripts(ListGridTranscriptsResult) } @@ -10198,49 +10444,255 @@ public nonisolated struct GridConnection: Sendable { public init() {} } -public nonisolated struct GridRoom: Sendable { +public nonisolated struct GridRoom: @unchecked Sendable { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - public var id: Int64 = 0 + public var id: Int64 { + get {_storage._id} + set {_uniqueStorage()._id = newValue} + } - public var spaceID: Int64 = 0 + public var spaceID: Int64 { + get {_storage._spaceID} + set {_uniqueStorage()._spaceID = newValue} + } - public var createdByUserID: Int64 = 0 + public var createdByUserID: Int64 { + get {_storage._createdByUserID} + set {_uniqueStorage()._createdByUserID = newValue} + } public var title: String { - get {_title ?? String()} - set {_title = newValue} + get {_storage._title ?? String()} + set {_uniqueStorage()._title = newValue} } /// Returns true if `title` has been explicitly set. - public var hasTitle: Bool {self._title != nil} + public var hasTitle: Bool {_storage._title != nil} /// Clears the value of `title`. Subsequent reads from it will return its default value. - public mutating func clearTitle() {self._title = nil} + public mutating func clearTitle() {_uniqueStorage()._title = nil} - public var locked: Bool = false + public var locked: Bool { + get {_storage._locked} + set {_uniqueStorage()._locked = newValue} + } - public var createdAt: Int64 = 0 + public var createdAt: Int64 { + get {_storage._createdAt} + set {_uniqueStorage()._createdAt = newValue} + } - public var updatedAt: Int64 = 0 + public var updatedAt: Int64 { + get {_storage._updatedAt} + set {_uniqueStorage()._updatedAt = newValue} + } - public var avatars: [GridAvatar] = [] + public var avatars: [GridAvatar] { + get {_storage._avatars} + set {_uniqueStorage()._avatars = newValue} + } public var connection: GridConnection { - get {_connection ?? GridConnection()} - set {_connection = newValue} + get {_storage._connection ?? GridConnection()} + set {_uniqueStorage()._connection = newValue} } /// Returns true if `connection` has been explicitly set. - public var hasConnection: Bool {self._connection != nil} + public var hasConnection: Bool {_storage._connection != nil} /// Clears the value of `connection`. Subsequent reads from it will return its default value. - public mutating func clearConnection() {self._connection = nil} + public mutating func clearConnection() {_uniqueStorage()._connection = nil} + + public var roomThreadID: Int64 { + get {_storage._roomThreadID ?? 0} + set {_uniqueStorage()._roomThreadID = newValue} + } + /// Returns true if `roomThreadID` has been explicitly set. + public var hasRoomThreadID: Bool {_storage._roomThreadID != nil} + /// Clears the value of `roomThreadID`. Subsequent reads from it will return its default value. + public mutating func clearRoomThreadID() {_uniqueStorage()._roomThreadID = nil} + + public var transcriptionAvailable: Bool { + get {_storage._transcriptionAvailable} + set {_uniqueStorage()._transcriptionAvailable = newValue} + } + + public var transcription: GridTranscription { + get {_storage._transcription ?? GridTranscription()} + set {_uniqueStorage()._transcription = newValue} + } + /// Returns true if `transcription` has been explicitly set. + public var hasTranscription: Bool {_storage._transcription != nil} + /// Clears the value of `transcription`. Subsequent reads from it will return its default value. + public mutating func clearTranscription() {_uniqueStorage()._transcription = nil} public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} - fileprivate var _title: String? = nil - fileprivate var _connection: GridConnection? = nil + fileprivate var _storage = _StorageClass.defaultInstance +} + +public nonisolated struct GridTranscription: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var runID: String = String() + + public var state: GridTranscriptionState = .gridTranscriptionUnspecified + + public var transcriptChatID: Int64 { + get {_transcriptChatID ?? 0} + set {_transcriptChatID = newValue} + } + /// Returns true if `transcriptChatID` has been explicitly set. + public var hasTranscriptChatID: Bool {self._transcriptChatID != nil} + /// Clears the value of `transcriptChatID`. Subsequent reads from it will return its default value. + public mutating func clearTranscriptChatID() {self._transcriptChatID = nil} + + public var revision: Int32 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _transcriptChatID: Int64? = nil +} + +public nonisolated struct OpenGridThreadInput: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var roomID: Int64 = 0 + + public var expectedMembershipID: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public nonisolated struct OpenGridThreadResult: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var chatID: Int64 = 0 + + public var updates: [Update] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public nonisolated struct SetGridTranscriptionInput: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var roomID: Int64 = 0 + + public var expectedMembershipID: String = String() + + public var expectedGeneration: Int32 = 0 + + public var expectedRunID: String { + get {_expectedRunID ?? String()} + set {_expectedRunID = newValue} + } + /// Returns true if `expectedRunID` has been explicitly set. + public var hasExpectedRunID: Bool {self._expectedRunID != nil} + /// Clears the value of `expectedRunID`. Subsequent reads from it will return its default value. + public mutating func clearExpectedRunID() {self._expectedRunID = nil} + + public var expectedRevision: Int32 = 0 + + public var enabled: Bool = false + + public var destination: GridTranscriptDestination = .gridTranscriptLast + + public var transcriptChatID: Int64 { + get {_transcriptChatID ?? 0} + set {_transcriptChatID = newValue} + } + /// Returns true if `transcriptChatID` has been explicitly set. + public var hasTranscriptChatID: Bool {self._transcriptChatID != nil} + /// Clears the value of `transcriptChatID`. Subsequent reads from it will return its default value. + public mutating func clearTranscriptChatID() {self._transcriptChatID = nil} + + public var requestID: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _expectedRunID: String? = nil + fileprivate var _transcriptChatID: Int64? = nil +} + +public nonisolated struct SetGridTranscriptionResult: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var grid: Grid { + get {_grid ?? Grid()} + set {_grid = newValue} + } + /// Returns true if `grid` has been explicitly set. + public var hasGrid: Bool {self._grid != nil} + /// Clears the value of `grid`. Subsequent reads from it will return its default value. + public mutating func clearGrid() {self._grid = nil} + + public var updates: [Update] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _grid: Grid? = nil +} + +public nonisolated struct ListGridTranscriptsInput: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var roomID: Int64 = 0 + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public nonisolated struct GridTranscriptDestinationInfo: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var transcriptChatID: Int64 = 0 + + public var title: String = String() + + public var busy: Bool = false + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public nonisolated struct ListGridTranscriptsResult: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var transcripts: [GridTranscriptDestinationInfo] = [] + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} } /// Replaceable current state for one Space's implicit Grid. @@ -22101,6 +22553,10 @@ nonisolated extension DialogFollowMode: SwiftProtobuf._ProtoNameProviding { public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0DIALOG_FOLLOW_MODE_UNSPECIFIED\0\u{1}FOLLOWING\0\u{1}UNFOLLOWED\0") } +nonisolated extension GridTranscriptMessageKind: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0GRID_TRANSCRIPT_TURN\0\u{1}GRID_TRANSCRIPT_STARTED\0\u{1}GRID_TRANSCRIPT_STOPPED\0\u{1}GRID_TRANSCRIPT_INTERRUPTED\0\u{1}GRID_TRANSCRIPT_LINK\0") +} + nonisolated extension AgentSessionProvider: SwiftProtobuf._ProtoNameProviding { public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0AGENT_SESSION_PROVIDER_UNSPECIFIED\0\u{1}AGENT_SESSION_PROVIDER_CODEX\0\u{1}AGENT_SESSION_PROVIDER_CODEX_CLOUD\0\u{1}AGENT_SESSION_PROVIDER_CLAUDE\0\u{1}AGENT_SESSION_PROVIDER_OPEN_CODE\0\u{1}AGENT_SESSION_PROVIDER_AMP\0") } @@ -22130,7 +22586,15 @@ nonisolated extension AgentSessionMessageSyncState: SwiftProtobuf._ProtoNameProv } nonisolated extension Method: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNSPECIFIED\0\u{1}GET_ME\0\u{1}SEND_MESSAGE\0\u{1}GET_PEER_PHOTO\0\u{1}DELETE_MESSAGES\0\u{1}GET_CHAT_HISTORY\0\u{1}ADD_REACTION\0\u{1}DELETE_REACTION\0\u{1}EDIT_MESSAGE\0\u{1}CREATE_CHAT\0\u{1}GET_SPACE_MEMBERS\0\u{1}DELETE_CHAT\0\u{1}INVITE_TO_SPACE\0\u{1}GET_CHAT_PARTICIPANTS\0\u{1}ADD_CHAT_PARTICIPANT\0\u{1}REMOVE_CHAT_PARTICIPANT\0\u{1}TRANSLATE_MESSAGES\0\u{1}GET_CHATS\0\u{1}UPDATE_USER_SETTINGS\0\u{1}GET_USER_SETTINGS\0\u{1}SEND_COMPOSE_ACTION\0\u{1}CREATE_BOT\0\u{1}DELETE_MEMBER\0\u{1}MARK_AS_UNREAD\0\u{1}GET_UPDATES_STATE\0\u{1}GET_CHAT\0\u{1}GET_UPDATES\0\u{1}UPDATE_MEMBER_ACCESS\0\u{1}SEARCH_MESSAGES\0\u{1}FORWARD_MESSAGES\0\u{1}UPDATE_CHAT_VISIBILITY\0\u{1}PIN_MESSAGE\0\u{1}UPDATE_CHAT_INFO\0\u{1}LIST_BOTS\0\u{1}REVEAL_BOT_TOKEN\0\u{1}MOVE_THREAD\0\u{1}ROTATE_BOT_TOKEN\0\u{1}UPDATE_BOT_PROFILE\0\u{1}GET_MESSAGES\0\u{1}UPDATE_DIALOG_NOTIFICATION_SETTINGS\0\u{1}READ_MESSAGES\0\u{1}REGISTER_DEVICE\0\u{1}CREATE_SUBTHREAD\0\u{1}GET_BOT_COMMANDS\0\u{1}SET_BOT_COMMANDS\0\u{1}GET_PEER_BOT_COMMANDS\0\u{1}SHOW_IN_CHAT_LIST\0\u{1}RESERVE_CHAT_IDS\0\u{1}INVOKE_MESSAGE_ACTION\0\u{1}ANSWER_MESSAGE_ACTION\0\u{1}REVOKE_SESSION\0\u{1}UPDATE_DIALOG_OPEN\0\u{1}UPDATE_DIALOG_ORDER\0\u{1}CLEAR_CHAT_HISTORY\0\u{1}DELETE_BOT\0\u{1}DELETE_MESSAGE_ATTACHMENT\0\u{1}SET_BOT_AVATAR\0\u{1}CLEAR_BOT_AVATAR\0\u{1}GET_BOT_PRESENCE\0\u{1}SET_BOT_PRESENCE_STATE\0\u{1}UPDATE_DIALOG_FOLLOW_MODE\0\u{1}GET_SESSIONS\0\u{1}CHECK_USERNAME\0\u{1}CHANGE_USERNAME\0\u{1}UPDATE_PROFILE\0\u{1}GET_SPACE_URL_PREVIEW_EXCLUSIONS\0\u{1}ADD_SPACE_URL_PREVIEW_EXCLUSION\0\u{1}REMOVE_SPACE_URL_PREVIEW_EXCLUSION\0\u{1}GET_USER_GROUPS\0\u{1}CREATE_USER_GROUP\0\u{1}UPDATE_USER_GROUP\0\u{1}DELETE_USER_GROUP\0\u{1}GET_SPACE_SETTINGS\0\u{1}TOGGLE_SPACE_GRID\0\u{1}GET_THREAD_REFERENCES\0\u{1}GET_THREAD_SUBTHREADS\0\u{1}GET_PEER_BOTS\0\u{1}GET_MY_BOT_CAPABILITIES\0\u{1}SET_MY_BOT_CAPABILITIES\0\u{1}REQUEST_BOT_CHAT_SETTINGS\0\u{1}INVOKE_BOT_CHAT_SETTINGS_ITEM\0\u{1}ANSWER_BOT_CHAT_SETTINGS\0\u{2}\u{2}GET_GRID\0\u{1}CREATE_GRID_ROOM\0\u{1}JOIN_GRID_ROOM\0\u{1}LEAVE_GRID_ROOM\0\u{1}SET_GRID_ROOM_TITLE\0\u{1}SET_GRID_ROOM_LOCKED\0\u{1}DELETE_GRID_ROOM\0\u{1}PREPARE_GRID_CONNECTION\0\u{1}SET_GRID_AVATAR_MICROPHONE_ENABLED\0\u{1}GET_GRID_HOME\0\u{1}CREATE_CLI_SESSION\0\u{1}SET_PROFILE_PHOTO\0\u{1}GET_EXTERNAL_PROFILE_PHOTO\0\u{1}GET_CHAT_TRANSCRIPT\0\u{1}SEARCH_EXTERNAL_RESOURCES\0\u{1}JOIN_PUBLIC_SPACE\0\u{1}COLLAPSE_HISTORY\0\u{1}LIST_CONNECTORS\0\u{1}PREPARE_CONNECTOR_OAUTH\0\u{1}DISCONNECT_CONNECTOR\0\u{1}SEARCH_USERS\0\u{1}INVITE_TO_INLINE\0\u{1}RESOLVE_URL_PREVIEW\0\u{1}CREATE_BOT_AGENT\0\u{1}GET_BOT_AGENT\0\u{1}LIST_BOT_AGENTS\0\u{1}CREATE_SPACE\0\u{1}DELETE_SPACE\0\u{1}LEAVE_SPACE\0\u{1}GET_CONNECTOR_CONFIG\0\u{1}SET_CONNECTOR_CONFIG\0\u{1}CREATE_EXTERNAL_TASK\0\u{1}UNREGISTER_DEVICE\0\u{1}LOG_OUT\0\u{1}CREATE_UPLOAD\0\u{1}SAVE_UPLOAD_PART\0\u{1}GET_UPLOAD_STATE\0\u{1}FINISH_UPLOAD\0\u{1}CANCEL_UPLOAD\0\u{1}UPDATE_SESSION\0\u{1}UPDATE_DIALOG_ARCHIVED\0\u{1}CREATE_DIALOG_FOLDER\0\u{1}UPDATE_DIALOG_FOLDER\0\u{1}DELETE_DIALOG_FOLDER\0\u{1}GET_SPACE\0\u{1}CONNECT_AGENT_SESSION\0\u{1}SYNC_AGENT_SESSION_MESSAGES\0\u{1}GET_AGENT_SESSION\0\u{1}UPDATE_BOT_AGENT\0\u{1}DELETE_BOT_AGENT\0\u{1}JOIN_SPACE_BY_INVITE_TOKEN\0\u{1}GET_SPACE_INVITE_LINK\0\u{1}SET_SPACE_INVITE_LINK_ENABLED\0\u{1}GET_FILE_PART\0\u{1}ACKNOWLEDGE_MESSAGES\0\u{1}GET_USERS\0\u{1}GET_BOT_SKILLS\0\u{1}GET_BOT_CONFIGURATION_CATALOG\0\u{1}UPDATE_DIALOG_TRANSLATION\0\u{1}TRANSCRIBE_VOICE_DRAFT\0\u{1}REQUEST_BOT_FILESYSTEM\0\u{1}ANSWER_BOT_FILESYSTEM\0\u{1}SET_SPACE_PHOTO\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNSPECIFIED\0\u{1}GET_ME\0\u{1}SEND_MESSAGE\0\u{1}GET_PEER_PHOTO\0\u{1}DELETE_MESSAGES\0\u{1}GET_CHAT_HISTORY\0\u{1}ADD_REACTION\0\u{1}DELETE_REACTION\0\u{1}EDIT_MESSAGE\0\u{1}CREATE_CHAT\0\u{1}GET_SPACE_MEMBERS\0\u{1}DELETE_CHAT\0\u{1}INVITE_TO_SPACE\0\u{1}GET_CHAT_PARTICIPANTS\0\u{1}ADD_CHAT_PARTICIPANT\0\u{1}REMOVE_CHAT_PARTICIPANT\0\u{1}TRANSLATE_MESSAGES\0\u{1}GET_CHATS\0\u{1}UPDATE_USER_SETTINGS\0\u{1}GET_USER_SETTINGS\0\u{1}SEND_COMPOSE_ACTION\0\u{1}CREATE_BOT\0\u{1}DELETE_MEMBER\0\u{1}MARK_AS_UNREAD\0\u{1}GET_UPDATES_STATE\0\u{1}GET_CHAT\0\u{1}GET_UPDATES\0\u{1}UPDATE_MEMBER_ACCESS\0\u{1}SEARCH_MESSAGES\0\u{1}FORWARD_MESSAGES\0\u{1}UPDATE_CHAT_VISIBILITY\0\u{1}PIN_MESSAGE\0\u{1}UPDATE_CHAT_INFO\0\u{1}LIST_BOTS\0\u{1}REVEAL_BOT_TOKEN\0\u{1}MOVE_THREAD\0\u{1}ROTATE_BOT_TOKEN\0\u{1}UPDATE_BOT_PROFILE\0\u{1}GET_MESSAGES\0\u{1}UPDATE_DIALOG_NOTIFICATION_SETTINGS\0\u{1}READ_MESSAGES\0\u{1}REGISTER_DEVICE\0\u{1}CREATE_SUBTHREAD\0\u{1}GET_BOT_COMMANDS\0\u{1}SET_BOT_COMMANDS\0\u{1}GET_PEER_BOT_COMMANDS\0\u{1}SHOW_IN_CHAT_LIST\0\u{1}RESERVE_CHAT_IDS\0\u{1}INVOKE_MESSAGE_ACTION\0\u{1}ANSWER_MESSAGE_ACTION\0\u{1}REVOKE_SESSION\0\u{1}UPDATE_DIALOG_OPEN\0\u{1}UPDATE_DIALOG_ORDER\0\u{1}CLEAR_CHAT_HISTORY\0\u{1}DELETE_BOT\0\u{1}DELETE_MESSAGE_ATTACHMENT\0\u{1}SET_BOT_AVATAR\0\u{1}CLEAR_BOT_AVATAR\0\u{1}GET_BOT_PRESENCE\0\u{1}SET_BOT_PRESENCE_STATE\0\u{1}UPDATE_DIALOG_FOLLOW_MODE\0\u{1}GET_SESSIONS\0\u{1}CHECK_USERNAME\0\u{1}CHANGE_USERNAME\0\u{1}UPDATE_PROFILE\0\u{1}GET_SPACE_URL_PREVIEW_EXCLUSIONS\0\u{1}ADD_SPACE_URL_PREVIEW_EXCLUSION\0\u{1}REMOVE_SPACE_URL_PREVIEW_EXCLUSION\0\u{1}GET_USER_GROUPS\0\u{1}CREATE_USER_GROUP\0\u{1}UPDATE_USER_GROUP\0\u{1}DELETE_USER_GROUP\0\u{1}GET_SPACE_SETTINGS\0\u{1}TOGGLE_SPACE_GRID\0\u{1}GET_THREAD_REFERENCES\0\u{1}GET_THREAD_SUBTHREADS\0\u{1}GET_PEER_BOTS\0\u{1}GET_MY_BOT_CAPABILITIES\0\u{1}SET_MY_BOT_CAPABILITIES\0\u{1}REQUEST_BOT_CHAT_SETTINGS\0\u{1}INVOKE_BOT_CHAT_SETTINGS_ITEM\0\u{1}ANSWER_BOT_CHAT_SETTINGS\0\u{2}\u{2}GET_GRID\0\u{1}CREATE_GRID_ROOM\0\u{1}JOIN_GRID_ROOM\0\u{1}LEAVE_GRID_ROOM\0\u{1}SET_GRID_ROOM_TITLE\0\u{1}SET_GRID_ROOM_LOCKED\0\u{1}DELETE_GRID_ROOM\0\u{1}PREPARE_GRID_CONNECTION\0\u{1}SET_GRID_AVATAR_MICROPHONE_ENABLED\0\u{1}GET_GRID_HOME\0\u{1}CREATE_CLI_SESSION\0\u{1}SET_PROFILE_PHOTO\0\u{1}GET_EXTERNAL_PROFILE_PHOTO\0\u{1}GET_CHAT_TRANSCRIPT\0\u{1}SEARCH_EXTERNAL_RESOURCES\0\u{1}JOIN_PUBLIC_SPACE\0\u{1}COLLAPSE_HISTORY\0\u{1}LIST_CONNECTORS\0\u{1}PREPARE_CONNECTOR_OAUTH\0\u{1}DISCONNECT_CONNECTOR\0\u{1}SEARCH_USERS\0\u{1}INVITE_TO_INLINE\0\u{1}RESOLVE_URL_PREVIEW\0\u{1}CREATE_BOT_AGENT\0\u{1}GET_BOT_AGENT\0\u{1}LIST_BOT_AGENTS\0\u{1}CREATE_SPACE\0\u{1}DELETE_SPACE\0\u{1}LEAVE_SPACE\0\u{1}GET_CONNECTOR_CONFIG\0\u{1}SET_CONNECTOR_CONFIG\0\u{1}CREATE_EXTERNAL_TASK\0\u{1}UNREGISTER_DEVICE\0\u{1}LOG_OUT\0\u{1}CREATE_UPLOAD\0\u{1}SAVE_UPLOAD_PART\0\u{1}GET_UPLOAD_STATE\0\u{1}FINISH_UPLOAD\0\u{1}CANCEL_UPLOAD\0\u{1}UPDATE_SESSION\0\u{1}UPDATE_DIALOG_ARCHIVED\0\u{1}CREATE_DIALOG_FOLDER\0\u{1}UPDATE_DIALOG_FOLDER\0\u{1}DELETE_DIALOG_FOLDER\0\u{1}GET_SPACE\0\u{1}CONNECT_AGENT_SESSION\0\u{1}SYNC_AGENT_SESSION_MESSAGES\0\u{1}GET_AGENT_SESSION\0\u{1}UPDATE_BOT_AGENT\0\u{1}DELETE_BOT_AGENT\0\u{1}JOIN_SPACE_BY_INVITE_TOKEN\0\u{1}GET_SPACE_INVITE_LINK\0\u{1}SET_SPACE_INVITE_LINK_ENABLED\0\u{1}GET_FILE_PART\0\u{1}ACKNOWLEDGE_MESSAGES\0\u{1}GET_USERS\0\u{1}GET_BOT_SKILLS\0\u{1}GET_BOT_CONFIGURATION_CATALOG\0\u{1}UPDATE_DIALOG_TRANSLATION\0\u{1}TRANSCRIBE_VOICE_DRAFT\0\u{1}REQUEST_BOT_FILESYSTEM\0\u{1}ANSWER_BOT_FILESYSTEM\0\u{1}SET_SPACE_PHOTO\0\u{2}\u{2}OPEN_GRID_THREAD\0\u{1}SET_GRID_TRANSCRIPTION\0\u{1}LIST_GRID_TRANSCRIPTS\0") +} + +nonisolated extension GridTranscriptionState: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0GRID_TRANSCRIPTION_UNSPECIFIED\0\u{1}GRID_TRANSCRIPTION_STARTING\0\u{1}GRID_TRANSCRIPTION_ACTIVE\0\u{1}GRID_TRANSCRIPTION_STOPPING\0\u{1}GRID_TRANSCRIPTION_STOPPED\0\u{1}GRID_TRANSCRIPTION_INTERRUPTED\0") +} + +nonisolated extension GridTranscriptDestination: SwiftProtobuf._ProtoNameProviding { + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0GRID_TRANSCRIPT_LAST\0\u{1}GRID_TRANSCRIPT_NEW\0\u{1}GRID_TRANSCRIPT_EXISTING\0") } nonisolated extension GridConnectionUnavailableReason: SwiftProtobuf._ProtoNameProviding { @@ -24684,7 +25148,7 @@ nonisolated extension MessageActionToast: SwiftProtobuf.Message, SwiftProtobuf._ nonisolated extension MessageService: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "MessageService" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}thread_backlink\0\u{3}pinned_message\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}thread_backlink\0\u{3}pinned_message\0\u{3}grid_transcript\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -24718,6 +25182,19 @@ nonisolated extension MessageService: SwiftProtobuf.Message, SwiftProtobuf._Mess self.event = .pinnedMessage(v) } }() + case 3: try { + var v: MessageServiceGridTranscript? + var hadOneofValue = false + if let current = self.event { + hadOneofValue = true + if case .gridTranscript(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.event = .gridTranscript(v) + } + }() default: break } } @@ -24737,6 +25214,10 @@ nonisolated extension MessageService: SwiftProtobuf.Message, SwiftProtobuf._Mess guard case .pinnedMessage(let v)? = self.event else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 2) }() + case .gridTranscript?: try { + guard case .gridTranscript(let v)? = self.event else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -24822,6 +25303,55 @@ nonisolated extension MessageServicePinnedMessage: SwiftProtobuf.Message, SwiftP } } +nonisolated extension MessageServiceGridTranscript: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "MessageServiceGridTranscript" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}run_id\0\u{3}segment_id\0\u{3}speaker_user_id\0\u{1}kind\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.runID) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.segmentID) }() + case 3: try { try decoder.decodeSingularInt64Field(value: &self._speakerUserID) }() + case 4: try { try decoder.decodeSingularEnumField(value: &self.kind) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.runID.isEmpty { + try visitor.visitSingularStringField(value: self.runID, fieldNumber: 1) + } + if !self.segmentID.isEmpty { + try visitor.visitSingularStringField(value: self.segmentID, fieldNumber: 2) + } + try { if let v = self._speakerUserID { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 3) + } }() + if self.kind != .gridTranscriptTurn { + try visitor.visitSingularEnumField(value: self.kind, fieldNumber: 4) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: MessageServiceGridTranscript, rhs: MessageServiceGridTranscript) -> Bool { + if lhs.runID != rhs.runID {return false} + if lhs.segmentID != rhs.segmentID {return false} + if lhs._speakerUserID != rhs._speakerUserID {return false} + if lhs.kind != rhs.kind {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + nonisolated extension BlockContent: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "BlockContent" public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}blocks\0") @@ -25735,7 +26265,7 @@ nonisolated extension BlockTableRow: SwiftProtobuf.Message, SwiftProtobuf._Messa nonisolated extension Message: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "Message" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{3}from_id\0\u{3}peer_id\0\u{3}chat_id\0\u{1}message\0\u{1}out\0\u{1}date\0\u{1}mentioned\0\u{3}reply_to_msg_id\0\u{1}media\0\u{3}edit_date\0\u{3}grouped_id\0\u{1}attachments\0\u{1}reactions\0\u{3}is_sticker\0\u{1}entities\0\u{3}send_mode\0\u{3}fwd_from\0\u{1}replies\0\u{1}actions\0\u{1}rev\0\u{3}service_message\0\u{3}block_content\0\u{3}agent_session\0\u{1}subthread\0\u{4}W]\u{1}has_link\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{3}from_id\0\u{3}peer_id\0\u{3}chat_id\0\u{1}message\0\u{1}out\0\u{1}date\0\u{1}mentioned\0\u{3}reply_to_msg_id\0\u{1}media\0\u{3}edit_date\0\u{3}grouped_id\0\u{1}attachments\0\u{1}reactions\0\u{3}is_sticker\0\u{1}entities\0\u{3}send_mode\0\u{3}fwd_from\0\u{1}replies\0\u{1}actions\0\u{1}rev\0\u{3}service_message\0\u{3}block_content\0\u{3}agent_session\0\u{1}subthread\0\u{3}counts_as_unread\0\u{4}V]\u{1}has_link\0") fileprivate class _StorageClass { var _id: Int64 = 0 @@ -25764,6 +26294,7 @@ nonisolated extension Message: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl var _blockContent: BlockContent? = nil var _agentSession: AgentSessionMessageInfo? = nil var _subthread: MessageSubthread? = nil + var _countsAsUnread: Bool? = nil // This property is used as the initial default value for new instances of the type. // The type itself is protecting the reference to its storage via CoW semantics. @@ -25800,6 +26331,7 @@ nonisolated extension Message: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl _blockContent = source._blockContent _agentSession = source._agentSession _subthread = source._subthread + _countsAsUnread = source._countsAsUnread } } @@ -25843,6 +26375,7 @@ nonisolated extension Message: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl case 23: try { try decoder.decodeSingularMessageField(value: &_storage._blockContent) }() case 24: try { try decoder.decodeSingularMessageField(value: &_storage._agentSession) }() case 25: try { try decoder.decodeSingularMessageField(value: &_storage._subthread) }() + case 26: try { try decoder.decodeSingularBoolField(value: &_storage._countsAsUnread) }() case 6000: try { try decoder.decodeSingularBoolField(value: &_storage._hasLink_p) }() default: break } @@ -25931,6 +26464,9 @@ nonisolated extension Message: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl try { if let v = _storage._subthread { try visitor.visitSingularMessageField(value: v, fieldNumber: 25) } }() + try { if let v = _storage._countsAsUnread { + try visitor.visitSingularBoolField(value: v, fieldNumber: 26) + } }() try { if let v = _storage._hasLink_p { try visitor.visitSingularBoolField(value: v, fieldNumber: 6000) } }() @@ -25969,6 +26505,7 @@ nonisolated extension Message: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl if _storage._blockContent != rhs_storage._blockContent {return false} if _storage._agentSession != rhs_storage._agentSession {return false} if _storage._subthread != rhs_storage._subthread {return false} + if _storage._countsAsUnread != rhs_storage._countsAsUnread {return false} return true } if !storagesAreEqual {return false} @@ -29348,7 +29885,7 @@ nonisolated extension RpcError.Code: SwiftProtobuf._ProtoNameProviding { nonisolated extension RpcCall: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "RpcCall" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}method\0\u{1}getMe\0\u{1}getPeerPhoto\0\u{1}deleteMessages\0\u{1}sendMessage\0\u{1}getChatHistory\0\u{1}addReaction\0\u{1}deleteReaction\0\u{1}editMessage\0\u{1}createChat\0\u{1}getSpaceMembers\0\u{1}deleteChat\0\u{1}inviteToSpace\0\u{1}getChatParticipants\0\u{1}addChatParticipant\0\u{1}removeChatParticipant\0\u{1}translateMessages\0\u{1}getChats\0\u{1}updateUserSettings\0\u{1}getUserSettings\0\u{1}sendComposeAction\0\u{1}createBot\0\u{1}deleteMember\0\u{1}markAsUnread\0\u{1}getUpdatesState\0\u{1}getChat\0\u{1}getUpdates\0\u{1}updateMemberAccess\0\u{1}searchMessages\0\u{1}forwardMessages\0\u{1}updateChatVisibility\0\u{1}pinMessage\0\u{1}updateChatInfo\0\u{1}listBots\0\u{1}revealBotToken\0\u{1}moveThread\0\u{1}rotateBotToken\0\u{1}updateBotProfile\0\u{1}getMessages\0\u{1}updateDialogNotificationSettings\0\u{1}readMessages\0\u{1}registerDevice\0\u{1}createSubthread\0\u{1}getBotCommands\0\u{1}setBotCommands\0\u{1}getPeerBotCommands\0\u{1}showInChatList\0\u{1}reserveChatIds\0\u{1}invokeMessageAction\0\u{1}answerMessageAction\0\u{1}revokeSession\0\u{1}updateDialogOpen\0\u{1}updateDialogOrder\0\u{1}clearChatHistory\0\u{1}deleteBot\0\u{1}deleteMessageAttachment\0\u{1}setBotAvatar\0\u{1}clearBotAvatar\0\u{1}getBotPresence\0\u{1}setBotPresenceState\0\u{1}updateDialogFollowMode\0\u{1}getSessions\0\u{1}checkUsername\0\u{1}changeUsername\0\u{1}updateProfile\0\u{1}getSpaceUrlPreviewExclusions\0\u{1}addSpaceUrlPreviewExclusion\0\u{1}removeSpaceUrlPreviewExclusion\0\u{1}getUserGroups\0\u{1}createUserGroup\0\u{1}updateUserGroup\0\u{1}deleteUserGroup\0\u{1}getSpaceSettings\0\u{1}toggleSpaceGrid\0\u{1}getThreadReferences\0\u{1}getThreadSubthreads\0\u{1}getPeerBots\0\u{1}getMyBotCapabilities\0\u{1}setMyBotCapabilities\0\u{1}requestBotChatSettings\0\u{1}invokeBotChatSettingsItem\0\u{1}answerBotChatSettings\0\u{2}\u{2}getGrid\0\u{1}createGridRoom\0\u{1}joinGridRoom\0\u{1}leaveGridRoom\0\u{1}setGridRoomTitle\0\u{1}setGridRoomLocked\0\u{1}deleteGridRoom\0\u{1}prepareGridConnection\0\u{1}setGridAvatarMicrophoneEnabled\0\u{1}getGridHome\0\u{1}createCliSession\0\u{1}setProfilePhoto\0\u{1}getExternalProfilePhoto\0\u{1}getChatTranscript\0\u{1}searchExternalResources\0\u{1}joinPublicSpace\0\u{1}collapseHistory\0\u{1}listConnectors\0\u{1}prepareConnectorOAuth\0\u{1}disconnectConnector\0\u{1}searchUsers\0\u{1}inviteToInline\0\u{1}resolveUrlPreview\0\u{1}createBotAgent\0\u{1}getBotAgent\0\u{1}listBotAgents\0\u{1}createSpace\0\u{1}deleteSpace\0\u{1}leaveSpace\0\u{1}getConnectorConfig\0\u{1}setConnectorConfig\0\u{1}createExternalTask\0\u{1}unregisterDevice\0\u{1}logOut\0\u{1}createUpload\0\u{1}saveUploadPart\0\u{1}getUploadState\0\u{1}finishUpload\0\u{1}cancelUpload\0\u{1}updateSession\0\u{1}updateDialogArchived\0\u{1}createDialogFolder\0\u{1}updateDialogFolder\0\u{1}deleteDialogFolder\0\u{1}getSpace\0\u{1}connectAgentSession\0\u{1}syncAgentSessionMessages\0\u{1}getAgentSession\0\u{1}updateBotAgent\0\u{1}deleteBotAgent\0\u{1}joinSpaceByInviteToken\0\u{1}getSpaceInviteLink\0\u{1}setSpaceInviteLinkEnabled\0\u{1}getFilePart\0\u{1}acknowledgeMessages\0\u{1}getUsers\0\u{1}getBotSkills\0\u{1}getBotConfigurationCatalog\0\u{1}updateDialogTranslation\0\u{1}transcribeVoiceDraft\0\u{1}requestBotFilesystem\0\u{1}answerBotFilesystem\0\u{1}setSpacePhoto\0\u{c}S\u{1}\u{1}") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}method\0\u{1}getMe\0\u{1}getPeerPhoto\0\u{1}deleteMessages\0\u{1}sendMessage\0\u{1}getChatHistory\0\u{1}addReaction\0\u{1}deleteReaction\0\u{1}editMessage\0\u{1}createChat\0\u{1}getSpaceMembers\0\u{1}deleteChat\0\u{1}inviteToSpace\0\u{1}getChatParticipants\0\u{1}addChatParticipant\0\u{1}removeChatParticipant\0\u{1}translateMessages\0\u{1}getChats\0\u{1}updateUserSettings\0\u{1}getUserSettings\0\u{1}sendComposeAction\0\u{1}createBot\0\u{1}deleteMember\0\u{1}markAsUnread\0\u{1}getUpdatesState\0\u{1}getChat\0\u{1}getUpdates\0\u{1}updateMemberAccess\0\u{1}searchMessages\0\u{1}forwardMessages\0\u{1}updateChatVisibility\0\u{1}pinMessage\0\u{1}updateChatInfo\0\u{1}listBots\0\u{1}revealBotToken\0\u{1}moveThread\0\u{1}rotateBotToken\0\u{1}updateBotProfile\0\u{1}getMessages\0\u{1}updateDialogNotificationSettings\0\u{1}readMessages\0\u{1}registerDevice\0\u{1}createSubthread\0\u{1}getBotCommands\0\u{1}setBotCommands\0\u{1}getPeerBotCommands\0\u{1}showInChatList\0\u{1}reserveChatIds\0\u{1}invokeMessageAction\0\u{1}answerMessageAction\0\u{1}revokeSession\0\u{1}updateDialogOpen\0\u{1}updateDialogOrder\0\u{1}clearChatHistory\0\u{1}deleteBot\0\u{1}deleteMessageAttachment\0\u{1}setBotAvatar\0\u{1}clearBotAvatar\0\u{1}getBotPresence\0\u{1}setBotPresenceState\0\u{1}updateDialogFollowMode\0\u{1}getSessions\0\u{1}checkUsername\0\u{1}changeUsername\0\u{1}updateProfile\0\u{1}getSpaceUrlPreviewExclusions\0\u{1}addSpaceUrlPreviewExclusion\0\u{1}removeSpaceUrlPreviewExclusion\0\u{1}getUserGroups\0\u{1}createUserGroup\0\u{1}updateUserGroup\0\u{1}deleteUserGroup\0\u{1}getSpaceSettings\0\u{1}toggleSpaceGrid\0\u{1}getThreadReferences\0\u{1}getThreadSubthreads\0\u{1}getPeerBots\0\u{1}getMyBotCapabilities\0\u{1}setMyBotCapabilities\0\u{1}requestBotChatSettings\0\u{1}invokeBotChatSettingsItem\0\u{1}answerBotChatSettings\0\u{2}\u{2}getGrid\0\u{1}createGridRoom\0\u{1}joinGridRoom\0\u{1}leaveGridRoom\0\u{1}setGridRoomTitle\0\u{1}setGridRoomLocked\0\u{1}deleteGridRoom\0\u{1}prepareGridConnection\0\u{1}setGridAvatarMicrophoneEnabled\0\u{1}getGridHome\0\u{1}createCliSession\0\u{1}setProfilePhoto\0\u{1}getExternalProfilePhoto\0\u{1}getChatTranscript\0\u{1}searchExternalResources\0\u{1}joinPublicSpace\0\u{1}collapseHistory\0\u{1}listConnectors\0\u{1}prepareConnectorOAuth\0\u{1}disconnectConnector\0\u{1}searchUsers\0\u{1}inviteToInline\0\u{1}resolveUrlPreview\0\u{1}createBotAgent\0\u{1}getBotAgent\0\u{1}listBotAgents\0\u{1}createSpace\0\u{1}deleteSpace\0\u{1}leaveSpace\0\u{1}getConnectorConfig\0\u{1}setConnectorConfig\0\u{1}createExternalTask\0\u{1}unregisterDevice\0\u{1}logOut\0\u{1}createUpload\0\u{1}saveUploadPart\0\u{1}getUploadState\0\u{1}finishUpload\0\u{1}cancelUpload\0\u{1}updateSession\0\u{1}updateDialogArchived\0\u{1}createDialogFolder\0\u{1}updateDialogFolder\0\u{1}deleteDialogFolder\0\u{1}getSpace\0\u{1}connectAgentSession\0\u{1}syncAgentSessionMessages\0\u{1}getAgentSession\0\u{1}updateBotAgent\0\u{1}deleteBotAgent\0\u{1}joinSpaceByInviteToken\0\u{1}getSpaceInviteLink\0\u{1}setSpaceInviteLinkEnabled\0\u{1}getFilePart\0\u{1}acknowledgeMessages\0\u{1}getUsers\0\u{1}getBotSkills\0\u{1}getBotConfigurationCatalog\0\u{1}updateDialogTranslation\0\u{1}transcribeVoiceDraft\0\u{1}requestBotFilesystem\0\u{1}answerBotFilesystem\0\u{1}setSpacePhoto\0\u{2}\u{2}openGridThread\0\u{1}setGridTranscription\0\u{1}listGridTranscripts\0\u{c}S\u{1}\u{1}") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -31229,6 +31766,45 @@ nonisolated extension RpcCall: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl self.input = .setSpacePhoto(v) } }() + case 148: try { + var v: OpenGridThreadInput? + var hadOneofValue = false + if let current = self.input { + hadOneofValue = true + if case .openGridThread(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.input = .openGridThread(v) + } + }() + case 149: try { + var v: SetGridTranscriptionInput? + var hadOneofValue = false + if let current = self.input { + hadOneofValue = true + if case .setGridTranscription(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.input = .setGridTranscription(v) + } + }() + case 150: try { + var v: ListGridTranscriptsInput? + var hadOneofValue = false + if let current = self.input { + hadOneofValue = true + if case .listGridTranscripts(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.input = .listGridTranscripts(v) + } + }() default: break } } @@ -31819,6 +32395,18 @@ nonisolated extension RpcCall: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl guard case .setSpacePhoto(let v)? = self.input else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 146) }() + case .openGridThread?: try { + guard case .openGridThread(let v)? = self.input else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 148) + }() + case .setGridTranscription?: try { + guard case .setGridTranscription(let v)? = self.input else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 149) + }() + case .listGridTranscripts?: try { + guard case .listGridTranscripts(let v)? = self.input else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 150) + }() case nil: break } try unknownFields.traverse(visitor: &visitor) @@ -31834,7 +32422,7 @@ nonisolated extension RpcCall: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl nonisolated extension RpcResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "RpcResult" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}req_msg_id\0\u{1}getMe\0\u{1}getPeerPhoto\0\u{1}deleteMessages\0\u{1}sendMessage\0\u{1}getChatHistory\0\u{1}addReaction\0\u{1}deleteReaction\0\u{1}editMessage\0\u{1}createChat\0\u{1}getSpaceMembers\0\u{1}deleteChat\0\u{1}inviteToSpace\0\u{1}getChatParticipants\0\u{1}addChatParticipant\0\u{1}removeChatParticipant\0\u{1}translateMessages\0\u{1}getChats\0\u{1}updateUserSettings\0\u{1}getUserSettings\0\u{1}sendComposeAction\0\u{1}createBot\0\u{1}deleteMember\0\u{1}markAsUnread\0\u{1}getUpdatesState\0\u{1}getChat\0\u{1}getUpdates\0\u{1}updateMemberAccess\0\u{1}searchMessages\0\u{1}forwardMessages\0\u{1}updateChatVisibility\0\u{1}pinMessage\0\u{1}updateChatInfo\0\u{1}listBots\0\u{1}revealBotToken\0\u{1}moveThread\0\u{1}rotateBotToken\0\u{1}updateBotProfile\0\u{1}getMessages\0\u{1}updateDialogNotificationSettings\0\u{1}readMessages\0\u{1}registerDevice\0\u{1}createSubthread\0\u{1}getBotCommands\0\u{1}setBotCommands\0\u{1}getPeerBotCommands\0\u{1}showInChatList\0\u{1}reserveChatIds\0\u{1}invokeMessageAction\0\u{1}answerMessageAction\0\u{1}revokeSession\0\u{1}updateDialogOpen\0\u{1}updateDialogOrder\0\u{1}clearChatHistory\0\u{1}deleteBot\0\u{1}deleteMessageAttachment\0\u{1}setBotAvatar\0\u{1}clearBotAvatar\0\u{1}getBotPresence\0\u{1}setBotPresenceState\0\u{1}updateDialogFollowMode\0\u{1}getSessions\0\u{1}checkUsername\0\u{1}changeUsername\0\u{1}updateProfile\0\u{1}getSpaceUrlPreviewExclusions\0\u{1}addSpaceUrlPreviewExclusion\0\u{1}removeSpaceUrlPreviewExclusion\0\u{1}getUserGroups\0\u{1}createUserGroup\0\u{1}updateUserGroup\0\u{1}deleteUserGroup\0\u{1}getSpaceSettings\0\u{1}toggleSpaceGrid\0\u{1}getThreadReferences\0\u{1}getThreadSubthreads\0\u{1}getPeerBots\0\u{1}getMyBotCapabilities\0\u{1}setMyBotCapabilities\0\u{1}requestBotChatSettings\0\u{1}invokeBotChatSettingsItem\0\u{1}answerBotChatSettings\0\u{2}\u{2}getGrid\0\u{1}createGridRoom\0\u{1}joinGridRoom\0\u{1}leaveGridRoom\0\u{1}setGridRoomTitle\0\u{1}setGridRoomLocked\0\u{1}deleteGridRoom\0\u{1}prepareGridConnection\0\u{1}setGridAvatarMicrophoneEnabled\0\u{1}getGridHome\0\u{1}createCliSession\0\u{1}setProfilePhoto\0\u{1}getExternalProfilePhoto\0\u{1}getChatTranscript\0\u{1}searchExternalResources\0\u{1}joinPublicSpace\0\u{1}collapseHistory\0\u{1}listConnectors\0\u{1}prepareConnectorOAuth\0\u{1}disconnectConnector\0\u{1}searchUsers\0\u{1}inviteToInline\0\u{1}resolveUrlPreview\0\u{1}createBotAgent\0\u{1}getBotAgent\0\u{1}listBotAgents\0\u{1}createSpace\0\u{1}deleteSpace\0\u{1}leaveSpace\0\u{1}getConnectorConfig\0\u{1}setConnectorConfig\0\u{1}createExternalTask\0\u{1}unregisterDevice\0\u{1}logOut\0\u{1}createUpload\0\u{1}saveUploadPart\0\u{1}getUploadState\0\u{1}finishUpload\0\u{1}cancelUpload\0\u{1}updateSession\0\u{1}updateDialogArchived\0\u{1}createDialogFolder\0\u{1}updateDialogFolder\0\u{1}deleteDialogFolder\0\u{1}getSpace\0\u{1}connectAgentSession\0\u{1}syncAgentSessionMessages\0\u{1}getAgentSession\0\u{1}updateBotAgent\0\u{1}deleteBotAgent\0\u{1}joinSpaceByInviteToken\0\u{1}getSpaceInviteLink\0\u{1}setSpaceInviteLinkEnabled\0\u{1}getFilePart\0\u{1}acknowledgeMessages\0\u{1}getUsers\0\u{1}getBotSkills\0\u{1}getBotConfigurationCatalog\0\u{1}updateDialogTranslation\0\u{1}transcribeVoiceDraft\0\u{1}requestBotFilesystem\0\u{1}answerBotFilesystem\0\u{1}setSpacePhoto\0\u{c}S\u{1}\u{1}") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}req_msg_id\0\u{1}getMe\0\u{1}getPeerPhoto\0\u{1}deleteMessages\0\u{1}sendMessage\0\u{1}getChatHistory\0\u{1}addReaction\0\u{1}deleteReaction\0\u{1}editMessage\0\u{1}createChat\0\u{1}getSpaceMembers\0\u{1}deleteChat\0\u{1}inviteToSpace\0\u{1}getChatParticipants\0\u{1}addChatParticipant\0\u{1}removeChatParticipant\0\u{1}translateMessages\0\u{1}getChats\0\u{1}updateUserSettings\0\u{1}getUserSettings\0\u{1}sendComposeAction\0\u{1}createBot\0\u{1}deleteMember\0\u{1}markAsUnread\0\u{1}getUpdatesState\0\u{1}getChat\0\u{1}getUpdates\0\u{1}updateMemberAccess\0\u{1}searchMessages\0\u{1}forwardMessages\0\u{1}updateChatVisibility\0\u{1}pinMessage\0\u{1}updateChatInfo\0\u{1}listBots\0\u{1}revealBotToken\0\u{1}moveThread\0\u{1}rotateBotToken\0\u{1}updateBotProfile\0\u{1}getMessages\0\u{1}updateDialogNotificationSettings\0\u{1}readMessages\0\u{1}registerDevice\0\u{1}createSubthread\0\u{1}getBotCommands\0\u{1}setBotCommands\0\u{1}getPeerBotCommands\0\u{1}showInChatList\0\u{1}reserveChatIds\0\u{1}invokeMessageAction\0\u{1}answerMessageAction\0\u{1}revokeSession\0\u{1}updateDialogOpen\0\u{1}updateDialogOrder\0\u{1}clearChatHistory\0\u{1}deleteBot\0\u{1}deleteMessageAttachment\0\u{1}setBotAvatar\0\u{1}clearBotAvatar\0\u{1}getBotPresence\0\u{1}setBotPresenceState\0\u{1}updateDialogFollowMode\0\u{1}getSessions\0\u{1}checkUsername\0\u{1}changeUsername\0\u{1}updateProfile\0\u{1}getSpaceUrlPreviewExclusions\0\u{1}addSpaceUrlPreviewExclusion\0\u{1}removeSpaceUrlPreviewExclusion\0\u{1}getUserGroups\0\u{1}createUserGroup\0\u{1}updateUserGroup\0\u{1}deleteUserGroup\0\u{1}getSpaceSettings\0\u{1}toggleSpaceGrid\0\u{1}getThreadReferences\0\u{1}getThreadSubthreads\0\u{1}getPeerBots\0\u{1}getMyBotCapabilities\0\u{1}setMyBotCapabilities\0\u{1}requestBotChatSettings\0\u{1}invokeBotChatSettingsItem\0\u{1}answerBotChatSettings\0\u{2}\u{2}getGrid\0\u{1}createGridRoom\0\u{1}joinGridRoom\0\u{1}leaveGridRoom\0\u{1}setGridRoomTitle\0\u{1}setGridRoomLocked\0\u{1}deleteGridRoom\0\u{1}prepareGridConnection\0\u{1}setGridAvatarMicrophoneEnabled\0\u{1}getGridHome\0\u{1}createCliSession\0\u{1}setProfilePhoto\0\u{1}getExternalProfilePhoto\0\u{1}getChatTranscript\0\u{1}searchExternalResources\0\u{1}joinPublicSpace\0\u{1}collapseHistory\0\u{1}listConnectors\0\u{1}prepareConnectorOAuth\0\u{1}disconnectConnector\0\u{1}searchUsers\0\u{1}inviteToInline\0\u{1}resolveUrlPreview\0\u{1}createBotAgent\0\u{1}getBotAgent\0\u{1}listBotAgents\0\u{1}createSpace\0\u{1}deleteSpace\0\u{1}leaveSpace\0\u{1}getConnectorConfig\0\u{1}setConnectorConfig\0\u{1}createExternalTask\0\u{1}unregisterDevice\0\u{1}logOut\0\u{1}createUpload\0\u{1}saveUploadPart\0\u{1}getUploadState\0\u{1}finishUpload\0\u{1}cancelUpload\0\u{1}updateSession\0\u{1}updateDialogArchived\0\u{1}createDialogFolder\0\u{1}updateDialogFolder\0\u{1}deleteDialogFolder\0\u{1}getSpace\0\u{1}connectAgentSession\0\u{1}syncAgentSessionMessages\0\u{1}getAgentSession\0\u{1}updateBotAgent\0\u{1}deleteBotAgent\0\u{1}joinSpaceByInviteToken\0\u{1}getSpaceInviteLink\0\u{1}setSpaceInviteLinkEnabled\0\u{1}getFilePart\0\u{1}acknowledgeMessages\0\u{1}getUsers\0\u{1}getBotSkills\0\u{1}getBotConfigurationCatalog\0\u{1}updateDialogTranslation\0\u{1}transcribeVoiceDraft\0\u{1}requestBotFilesystem\0\u{1}answerBotFilesystem\0\u{1}setSpacePhoto\0\u{2}\u{2}openGridThread\0\u{1}setGridTranscription\0\u{1}listGridTranscripts\0\u{c}S\u{1}\u{1}") fileprivate class _StorageClass { var _reqMsgID: UInt64 = 0 @@ -33742,6 +34330,45 @@ nonisolated extension RpcResult: SwiftProtobuf.Message, SwiftProtobuf._MessageIm _storage._result = .setSpacePhoto(v) } }() + case 148: try { + var v: OpenGridThreadResult? + var hadOneofValue = false + if let current = _storage._result { + hadOneofValue = true + if case .openGridThread(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._result = .openGridThread(v) + } + }() + case 149: try { + var v: SetGridTranscriptionResult? + var hadOneofValue = false + if let current = _storage._result { + hadOneofValue = true + if case .setGridTranscription(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._result = .setGridTranscription(v) + } + }() + case 150: try { + var v: ListGridTranscriptsResult? + var hadOneofValue = false + if let current = _storage._result { + hadOneofValue = true + if case .listGridTranscripts(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._result = .listGridTranscripts(v) + } + }() default: break } } @@ -34334,6 +34961,18 @@ nonisolated extension RpcResult: SwiftProtobuf.Message, SwiftProtobuf._MessageIm guard case .setSpacePhoto(let v)? = _storage._result else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 146) }() + case .openGridThread?: try { + guard case .openGridThread(let v)? = _storage._result else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 148) + }() + case .setGridTranscription?: try { + guard case .setGridTranscription(let v)? = _storage._result else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 149) + }() + case .listGridTranscripts?: try { + guard case .listGridTranscripts(let v)? = _storage._result else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 150) + }() case nil: break } } @@ -35152,7 +35791,154 @@ nonisolated extension GridConnection: SwiftProtobuf.Message, SwiftProtobuf._Mess nonisolated extension GridRoom: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "GridRoom" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{3}space_id\0\u{3}created_by_user_id\0\u{1}title\0\u{1}locked\0\u{3}created_at\0\u{3}updated_at\0\u{1}avatars\0\u{1}connection\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}id\0\u{3}space_id\0\u{3}created_by_user_id\0\u{1}title\0\u{1}locked\0\u{3}created_at\0\u{3}updated_at\0\u{1}avatars\0\u{1}connection\0\u{3}room_thread_id\0\u{3}transcription_available\0\u{1}transcription\0") + + fileprivate class _StorageClass { + var _id: Int64 = 0 + var _spaceID: Int64 = 0 + var _createdByUserID: Int64 = 0 + var _title: String? = nil + var _locked: Bool = false + var _createdAt: Int64 = 0 + var _updatedAt: Int64 = 0 + var _avatars: [GridAvatar] = [] + var _connection: GridConnection? = nil + var _roomThreadID: Int64? = nil + var _transcriptionAvailable: Bool = false + var _transcription: GridTranscription? = nil + + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _id = source._id + _spaceID = source._spaceID + _createdByUserID = source._createdByUserID + _title = source._title + _locked = source._locked + _createdAt = source._createdAt + _updatedAt = source._updatedAt + _avatars = source._avatars + _connection = source._connection + _roomThreadID = source._roomThreadID + _transcriptionAvailable = source._transcriptionAvailable + _transcription = source._transcription + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt64Field(value: &_storage._id) }() + case 2: try { try decoder.decodeSingularInt64Field(value: &_storage._spaceID) }() + case 3: try { try decoder.decodeSingularInt64Field(value: &_storage._createdByUserID) }() + case 4: try { try decoder.decodeSingularStringField(value: &_storage._title) }() + case 5: try { try decoder.decodeSingularBoolField(value: &_storage._locked) }() + case 6: try { try decoder.decodeSingularInt64Field(value: &_storage._createdAt) }() + case 7: try { try decoder.decodeSingularInt64Field(value: &_storage._updatedAt) }() + case 8: try { try decoder.decodeRepeatedMessageField(value: &_storage._avatars) }() + case 9: try { try decoder.decodeSingularMessageField(value: &_storage._connection) }() + case 10: try { try decoder.decodeSingularInt64Field(value: &_storage._roomThreadID) }() + case 11: try { try decoder.decodeSingularBoolField(value: &_storage._transcriptionAvailable) }() + case 12: try { try decoder.decodeSingularMessageField(value: &_storage._transcription) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if _storage._id != 0 { + try visitor.visitSingularInt64Field(value: _storage._id, fieldNumber: 1) + } + if _storage._spaceID != 0 { + try visitor.visitSingularInt64Field(value: _storage._spaceID, fieldNumber: 2) + } + if _storage._createdByUserID != 0 { + try visitor.visitSingularInt64Field(value: _storage._createdByUserID, fieldNumber: 3) + } + try { if let v = _storage._title { + try visitor.visitSingularStringField(value: v, fieldNumber: 4) + } }() + if _storage._locked != false { + try visitor.visitSingularBoolField(value: _storage._locked, fieldNumber: 5) + } + if _storage._createdAt != 0 { + try visitor.visitSingularInt64Field(value: _storage._createdAt, fieldNumber: 6) + } + if _storage._updatedAt != 0 { + try visitor.visitSingularInt64Field(value: _storage._updatedAt, fieldNumber: 7) + } + if !_storage._avatars.isEmpty { + try visitor.visitRepeatedMessageField(value: _storage._avatars, fieldNumber: 8) + } + try { if let v = _storage._connection { + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + } }() + try { if let v = _storage._roomThreadID { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 10) + } }() + if _storage._transcriptionAvailable != false { + try visitor.visitSingularBoolField(value: _storage._transcriptionAvailable, fieldNumber: 11) + } + try { if let v = _storage._transcription { + try visitor.visitSingularMessageField(value: v, fieldNumber: 12) + } }() + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: GridRoom, rhs: GridRoom) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._id != rhs_storage._id {return false} + if _storage._spaceID != rhs_storage._spaceID {return false} + if _storage._createdByUserID != rhs_storage._createdByUserID {return false} + if _storage._title != rhs_storage._title {return false} + if _storage._locked != rhs_storage._locked {return false} + if _storage._createdAt != rhs_storage._createdAt {return false} + if _storage._updatedAt != rhs_storage._updatedAt {return false} + if _storage._avatars != rhs_storage._avatars {return false} + if _storage._connection != rhs_storage._connection {return false} + if _storage._roomThreadID != rhs_storage._roomThreadID {return false} + if _storage._transcriptionAvailable != rhs_storage._transcriptionAvailable {return false} + if _storage._transcription != rhs_storage._transcription {return false} + return true + } + if !storagesAreEqual {return false} + } + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +nonisolated extension GridTranscription: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "GridTranscription" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}run_id\0\u{1}state\0\u{3}transcript_chat_id\0\u{1}revision\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -35160,15 +35946,10 @@ nonisolated extension GridRoom: SwiftProtobuf.Message, SwiftProtobuf._MessageImp // allocates stack space for every case branch when no optimizations are // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { - case 1: try { try decoder.decodeSingularInt64Field(value: &self.id) }() - case 2: try { try decoder.decodeSingularInt64Field(value: &self.spaceID) }() - case 3: try { try decoder.decodeSingularInt64Field(value: &self.createdByUserID) }() - case 4: try { try decoder.decodeSingularStringField(value: &self._title) }() - case 5: try { try decoder.decodeSingularBoolField(value: &self.locked) }() - case 6: try { try decoder.decodeSingularInt64Field(value: &self.createdAt) }() - case 7: try { try decoder.decodeSingularInt64Field(value: &self.updatedAt) }() - case 8: try { try decoder.decodeRepeatedMessageField(value: &self.avatars) }() - case 9: try { try decoder.decodeSingularMessageField(value: &self._connection) }() + case 1: try { try decoder.decodeSingularStringField(value: &self.runID) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self.state) }() + case 3: try { try decoder.decodeSingularInt64Field(value: &self._transcriptChatID) }() + case 4: try { try decoder.decodeSingularInt32Field(value: &self.revision) }() default: break } } @@ -35179,46 +35960,309 @@ nonisolated extension GridRoom: SwiftProtobuf.Message, SwiftProtobuf._MessageImp // allocates stack space for every if/case branch local when no optimizations // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and // https://github.com/apple/swift-protobuf/issues/1182 - if self.id != 0 { - try visitor.visitSingularInt64Field(value: self.id, fieldNumber: 1) + if !self.runID.isEmpty { + try visitor.visitSingularStringField(value: self.runID, fieldNumber: 1) } - if self.spaceID != 0 { - try visitor.visitSingularInt64Field(value: self.spaceID, fieldNumber: 2) + if self.state != .gridTranscriptionUnspecified { + try visitor.visitSingularEnumField(value: self.state, fieldNumber: 2) } - if self.createdByUserID != 0 { - try visitor.visitSingularInt64Field(value: self.createdByUserID, fieldNumber: 3) + try { if let v = self._transcriptChatID { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 3) + } }() + if self.revision != 0 { + try visitor.visitSingularInt32Field(value: self.revision, fieldNumber: 4) } - try { if let v = self._title { + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: GridTranscription, rhs: GridTranscription) -> Bool { + if lhs.runID != rhs.runID {return false} + if lhs.state != rhs.state {return false} + if lhs._transcriptChatID != rhs._transcriptChatID {return false} + if lhs.revision != rhs.revision {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +nonisolated extension OpenGridThreadInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "OpenGridThreadInput" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}room_id\0\u{3}expected_membership_id\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt64Field(value: &self.roomID) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.expectedMembershipID) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.roomID != 0 { + try visitor.visitSingularInt64Field(value: self.roomID, fieldNumber: 1) + } + if !self.expectedMembershipID.isEmpty { + try visitor.visitSingularStringField(value: self.expectedMembershipID, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: OpenGridThreadInput, rhs: OpenGridThreadInput) -> Bool { + if lhs.roomID != rhs.roomID {return false} + if lhs.expectedMembershipID != rhs.expectedMembershipID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +nonisolated extension OpenGridThreadResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "OpenGridThreadResult" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}chat_id\0\u{1}updates\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt64Field(value: &self.chatID) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.updates) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.chatID != 0 { + try visitor.visitSingularInt64Field(value: self.chatID, fieldNumber: 1) + } + if !self.updates.isEmpty { + try visitor.visitRepeatedMessageField(value: self.updates, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: OpenGridThreadResult, rhs: OpenGridThreadResult) -> Bool { + if lhs.chatID != rhs.chatID {return false} + if lhs.updates != rhs.updates {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +nonisolated extension SetGridTranscriptionInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "SetGridTranscriptionInput" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}room_id\0\u{3}expected_membership_id\0\u{3}expected_generation\0\u{3}expected_run_id\0\u{3}expected_revision\0\u{1}enabled\0\u{1}destination\0\u{3}transcript_chat_id\0\u{3}request_id\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt64Field(value: &self.roomID) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.expectedMembershipID) }() + case 3: try { try decoder.decodeSingularInt32Field(value: &self.expectedGeneration) }() + case 4: try { try decoder.decodeSingularStringField(value: &self._expectedRunID) }() + case 5: try { try decoder.decodeSingularInt32Field(value: &self.expectedRevision) }() + case 6: try { try decoder.decodeSingularBoolField(value: &self.enabled) }() + case 7: try { try decoder.decodeSingularEnumField(value: &self.destination) }() + case 8: try { try decoder.decodeSingularInt64Field(value: &self._transcriptChatID) }() + case 9: try { try decoder.decodeSingularStringField(value: &self.requestID) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if self.roomID != 0 { + try visitor.visitSingularInt64Field(value: self.roomID, fieldNumber: 1) + } + if !self.expectedMembershipID.isEmpty { + try visitor.visitSingularStringField(value: self.expectedMembershipID, fieldNumber: 2) + } + if self.expectedGeneration != 0 { + try visitor.visitSingularInt32Field(value: self.expectedGeneration, fieldNumber: 3) + } + try { if let v = self._expectedRunID { try visitor.visitSingularStringField(value: v, fieldNumber: 4) } }() - if self.locked != false { - try visitor.visitSingularBoolField(value: self.locked, fieldNumber: 5) + if self.expectedRevision != 0 { + try visitor.visitSingularInt32Field(value: self.expectedRevision, fieldNumber: 5) } - if self.createdAt != 0 { - try visitor.visitSingularInt64Field(value: self.createdAt, fieldNumber: 6) + if self.enabled != false { + try visitor.visitSingularBoolField(value: self.enabled, fieldNumber: 6) } - if self.updatedAt != 0 { - try visitor.visitSingularInt64Field(value: self.updatedAt, fieldNumber: 7) + if self.destination != .gridTranscriptLast { + try visitor.visitSingularEnumField(value: self.destination, fieldNumber: 7) } - if !self.avatars.isEmpty { - try visitor.visitRepeatedMessageField(value: self.avatars, fieldNumber: 8) + try { if let v = self._transcriptChatID { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 8) + } }() + if !self.requestID.isEmpty { + try visitor.visitSingularStringField(value: self.requestID, fieldNumber: 9) } - try { if let v = self._connection { - try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: SetGridTranscriptionInput, rhs: SetGridTranscriptionInput) -> Bool { + if lhs.roomID != rhs.roomID {return false} + if lhs.expectedMembershipID != rhs.expectedMembershipID {return false} + if lhs.expectedGeneration != rhs.expectedGeneration {return false} + if lhs._expectedRunID != rhs._expectedRunID {return false} + if lhs.expectedRevision != rhs.expectedRevision {return false} + if lhs.enabled != rhs.enabled {return false} + if lhs.destination != rhs.destination {return false} + if lhs._transcriptChatID != rhs._transcriptChatID {return false} + if lhs.requestID != rhs.requestID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +nonisolated extension SetGridTranscriptionResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "SetGridTranscriptionResult" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}grid\0\u{1}updates\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularMessageField(value: &self._grid) }() + case 2: try { try decoder.decodeRepeatedMessageField(value: &self.updates) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._grid { + try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() + if !self.updates.isEmpty { + try visitor.visitRepeatedMessageField(value: self.updates, fieldNumber: 2) + } try unknownFields.traverse(visitor: &visitor) } - public static func ==(lhs: GridRoom, rhs: GridRoom) -> Bool { - if lhs.id != rhs.id {return false} - if lhs.spaceID != rhs.spaceID {return false} - if lhs.createdByUserID != rhs.createdByUserID {return false} - if lhs._title != rhs._title {return false} - if lhs.locked != rhs.locked {return false} - if lhs.createdAt != rhs.createdAt {return false} - if lhs.updatedAt != rhs.updatedAt {return false} - if lhs.avatars != rhs.avatars {return false} - if lhs._connection != rhs._connection {return false} + public static func ==(lhs: SetGridTranscriptionResult, rhs: SetGridTranscriptionResult) -> Bool { + if lhs._grid != rhs._grid {return false} + if lhs.updates != rhs.updates {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +nonisolated extension ListGridTranscriptsInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "ListGridTranscriptsInput" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}room_id\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt64Field(value: &self.roomID) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.roomID != 0 { + try visitor.visitSingularInt64Field(value: self.roomID, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: ListGridTranscriptsInput, rhs: ListGridTranscriptsInput) -> Bool { + if lhs.roomID != rhs.roomID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +nonisolated extension GridTranscriptDestinationInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "GridTranscriptDestinationInfo" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}transcript_chat_id\0\u{1}title\0\u{1}busy\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt64Field(value: &self.transcriptChatID) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.title) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.busy) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if self.transcriptChatID != 0 { + try visitor.visitSingularInt64Field(value: self.transcriptChatID, fieldNumber: 1) + } + if !self.title.isEmpty { + try visitor.visitSingularStringField(value: self.title, fieldNumber: 2) + } + if self.busy != false { + try visitor.visitSingularBoolField(value: self.busy, fieldNumber: 3) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: GridTranscriptDestinationInfo, rhs: GridTranscriptDestinationInfo) -> Bool { + if lhs.transcriptChatID != rhs.transcriptChatID {return false} + if lhs.title != rhs.title {return false} + if lhs.busy != rhs.busy {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +nonisolated extension ListGridTranscriptsResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "ListGridTranscriptsResult" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}transcripts\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedMessageField(value: &self.transcripts) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.transcripts.isEmpty { + try visitor.visitRepeatedMessageField(value: self.transcripts, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: ListGridTranscriptsResult, rhs: ListGridTranscriptsResult) -> Bool { + if lhs.transcripts != rhs.transcripts {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/crates/protocol/proto/core.proto b/crates/protocol/proto/core.proto index fbbd81eb1..e3269be83 100644 --- a/crates/protocol/proto/core.proto +++ b/crates/protocol/proto/core.proto @@ -439,6 +439,7 @@ message MessageService { oneof event { MessageServiceThreadBacklink thread_backlink = 1; MessageServicePinnedMessage pinned_message = 2; + MessageServiceGridTranscript grid_transcript = 3; } } @@ -452,6 +453,21 @@ message MessageServicePinnedMessage { optional int64 message_id = 1; } +enum GridTranscriptMessageKind { + GRID_TRANSCRIPT_TURN = 0; + GRID_TRANSCRIPT_STARTED = 1; + GRID_TRANSCRIPT_STOPPED = 2; + GRID_TRANSCRIPT_INTERRUPTED = 3; + GRID_TRANSCRIPT_LINK = 4; +} + +message MessageServiceGridTranscript { + string run_id = 1; + string segment_id = 2; + optional int64 speaker_user_id = 3; + GridTranscriptMessageKind kind = 4; +} + // Structural rich content projected alongside Message.message and // Message.entities. Text-bearing blocks use UTF-16 ranges into Message.message; // inline entities remain owned by Message.entities so there is one text/entity @@ -677,6 +693,9 @@ message Message { // Canonical card projection for anchored replies and materialized subthreads. optional MessageSubthread subthread = 25; + + // Explicit false suppresses unread for generated/historical rows. + optional bool counts_as_unread = 26; } // Agent providers that implement Inline's session continuity contract. The @@ -1602,6 +1621,9 @@ enum Method { REQUEST_BOT_FILESYSTEM = 143; ANSWER_BOT_FILESYSTEM = 144; SET_SPACE_PHOTO = 145; + OPEN_GRID_THREAD = 147; + SET_GRID_TRANSCRIPTION = 148; + LIST_GRID_TRANSCRIPTS = 149; } message RpcCall { @@ -1752,6 +1774,9 @@ message RpcCall { RequestBotFilesystemInput requestBotFilesystem = 144; AnswerBotFilesystemInput answerBotFilesystem = 145; SetSpacePhotoInput setSpacePhoto = 146; + OpenGridThreadInput openGridThread = 148; + SetGridTranscriptionInput setGridTranscription = 149; + ListGridTranscriptsInput listGridTranscripts = 150; } } @@ -1904,6 +1929,9 @@ message RpcResult { RequestBotFilesystemResult requestBotFilesystem = 144; AnswerBotFilesystemResult answerBotFilesystem = 145; SetSpacePhotoResult setSpacePhoto = 146; + OpenGridThreadResult openGridThread = 148; + SetGridTranscriptionResult setGridTranscription = 149; + ListGridTranscriptsResult listGridTranscripts = 150; } } @@ -2078,7 +2106,57 @@ message GridRoom { int64 updated_at = 7; repeated GridAvatar avatars = 8; optional GridConnection connection = 9; + optional int64 room_thread_id = 10; + bool transcription_available = 11; + optional GridTranscription transcription = 12; +} + +enum GridTranscriptionState { + GRID_TRANSCRIPTION_UNSPECIFIED = 0; + GRID_TRANSCRIPTION_STARTING = 1; + GRID_TRANSCRIPTION_ACTIVE = 2; + GRID_TRANSCRIPTION_STOPPING = 3; + GRID_TRANSCRIPTION_STOPPED = 4; + GRID_TRANSCRIPTION_INTERRUPTED = 5; +} + +message GridTranscription { + string run_id = 1; + GridTranscriptionState state = 2; + optional int64 transcript_chat_id = 3; + int32 revision = 4; +} + +enum GridTranscriptDestination { + GRID_TRANSCRIPT_LAST = 0; + GRID_TRANSCRIPT_NEW = 1; + GRID_TRANSCRIPT_EXISTING = 2; +} + +message OpenGridThreadInput { + int64 room_id = 1; + string expected_membership_id = 2; +} +message OpenGridThreadResult { int64 chat_id = 1; repeated Update updates = 2; } +message SetGridTranscriptionInput { + int64 room_id = 1; + string expected_membership_id = 2; + int32 expected_generation = 3; + optional string expected_run_id = 4; + int32 expected_revision = 5; + bool enabled = 6; + GridTranscriptDestination destination = 7; + optional int64 transcript_chat_id = 8; + string request_id = 9; +} +message SetGridTranscriptionResult { Grid grid = 1; repeated Update updates = 2; } +message ListGridTranscriptsInput { int64 room_id = 1; } +message GridTranscriptDestinationInfo { + int64 transcript_chat_id = 1; + string title = 2; + bool busy = 3; } +message ListGridTranscriptsResult { repeated GridTranscriptDestinationInfo transcripts = 1; } // Replaceable current state for one Space's implicit Grid. message Grid { diff --git a/packages/protocol/src/client.ts b/packages/protocol/src/client.ts index 1e66d47a7..eb5c409a7 100644 --- a/packages/protocol/src/client.ts +++ b/packages/protocol/src/client.ts @@ -39,6 +39,10 @@ export interface MessageContentPayload { * @generated from protobuf field: MessageSubthread subthread = 5; */ subthread?: MessageSubthread; + /** + * @generated from protobuf field: optional bool counts_as_unread = 6; + */ + countsAsUnread?: boolean; } /** * @generated from protobuf message client.MessageVoiceContent @@ -102,7 +106,8 @@ class MessageContentPayload$Type extends MessageType { { no: 2, name: "actions", kind: "message", T: () => MessageActions }, { no: 3, name: "replies", kind: "message", T: () => MessageReplies }, { no: 4, name: "service_message", kind: "message", T: () => MessageService }, - { no: 5, name: "subthread", kind: "message", T: () => MessageSubthread } + { no: 5, name: "subthread", kind: "message", T: () => MessageSubthread }, + { no: 6, name: "counts_as_unread", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ } ]); } create(value?: PartialMessage): MessageContentPayload { @@ -131,6 +136,9 @@ class MessageContentPayload$Type extends MessageType { case /* MessageSubthread subthread */ 5: message.subthread = MessageSubthread.internalBinaryRead(reader, reader.uint32(), options, message.subthread); break; + case /* optional bool counts_as_unread */ 6: + message.countsAsUnread = reader.bool(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -158,6 +166,9 @@ class MessageContentPayload$Type extends MessageType { /* MessageSubthread subthread = 5; */ if (message.subthread) MessageSubthread.internalBinaryWrite(message.subthread, writer.tag(5, WireType.LengthDelimited).fork(), options).join(); + /* optional bool counts_as_unread = 6; */ + if (message.countsAsUnread !== undefined) + writer.tag(6, WireType.Varint).bool(message.countsAsUnread); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); diff --git a/packages/protocol/src/core.ts b/packages/protocol/src/core.ts index 29274bbac..daeaba6aa 100644 --- a/packages/protocol/src/core.ts +++ b/packages/protocol/src/core.ts @@ -1183,6 +1183,12 @@ export interface MessageService { * @generated from protobuf field: MessageServicePinnedMessage pinned_message = 2; */ pinnedMessage: MessageServicePinnedMessage; + } | { + oneofKind: "gridTranscript"; + /** + * @generated from protobuf field: MessageServiceGridTranscript grid_transcript = 3; + */ + gridTranscript: MessageServiceGridTranscript; } | { oneofKind: undefined; }; @@ -1211,6 +1217,27 @@ export interface MessageServicePinnedMessage { */ messageId?: bigint; } +/** + * @generated from protobuf message MessageServiceGridTranscript + */ +export interface MessageServiceGridTranscript { + /** + * @generated from protobuf field: string run_id = 1; + */ + runId: string; + /** + * @generated from protobuf field: string segment_id = 2; + */ + segmentId: string; + /** + * @generated from protobuf field: optional int64 speaker_user_id = 3; + */ + speakerUserId?: bigint; + /** + * @generated from protobuf field: GridTranscriptMessageKind kind = 4; + */ + kind: GridTranscriptMessageKind; +} /** * Structural rich content projected alongside Message.message and * Message.entities. Text-bearing blocks use UTF-16 ranges into Message.message; @@ -1822,6 +1849,12 @@ export interface Message { * @generated from protobuf field: optional MessageSubthread subthread = 25; */ subthread?: MessageSubthread; + /** + * Explicit false suppresses unread for generated/historical rows. + * + * @generated from protobuf field: optional bool counts_as_unread = 26; + */ + countsAsUnread?: boolean; } /** * @generated from protobuf message AgentSessionMessageInfo @@ -4573,6 +4606,24 @@ export interface RpcCall { * @generated from protobuf field: SetSpacePhotoInput setSpacePhoto = 146; */ setSpacePhoto: SetSpacePhotoInput; + } | { + oneofKind: "openGridThread"; + /** + * @generated from protobuf field: OpenGridThreadInput openGridThread = 148; + */ + openGridThread: OpenGridThreadInput; + } | { + oneofKind: "setGridTranscription"; + /** + * @generated from protobuf field: SetGridTranscriptionInput setGridTranscription = 149; + */ + setGridTranscription: SetGridTranscriptionInput; + } | { + oneofKind: "listGridTranscripts"; + /** + * @generated from protobuf field: ListGridTranscriptsInput listGridTranscripts = 150; + */ + listGridTranscripts: ListGridTranscriptsInput; } | { oneofKind: undefined; }; @@ -5452,6 +5503,24 @@ export interface RpcResult { * @generated from protobuf field: SetSpacePhotoResult setSpacePhoto = 146; */ setSpacePhoto: SetSpacePhotoResult; + } | { + oneofKind: "openGridThread"; + /** + * @generated from protobuf field: OpenGridThreadResult openGridThread = 148; + */ + openGridThread: OpenGridThreadResult; + } | { + oneofKind: "setGridTranscription"; + /** + * @generated from protobuf field: SetGridTranscriptionResult setGridTranscription = 149; + */ + setGridTranscription: SetGridTranscriptionResult; + } | { + oneofKind: "listGridTranscripts"; + /** + * @generated from protobuf field: ListGridTranscriptsResult listGridTranscripts = 150; + */ + listGridTranscripts: ListGridTranscriptsResult; } | { oneofKind: undefined; }; @@ -5885,6 +5954,154 @@ export interface GridRoom { * @generated from protobuf field: optional GridConnection connection = 9; */ connection?: GridConnection; + /** + * @generated from protobuf field: optional int64 room_thread_id = 10; + */ + roomThreadId?: bigint; + /** + * @generated from protobuf field: bool transcription_available = 11; + */ + transcriptionAvailable: boolean; + /** + * @generated from protobuf field: optional GridTranscription transcription = 12; + */ + transcription?: GridTranscription; +} +/** + * @generated from protobuf message GridTranscription + */ +export interface GridTranscription { + /** + * @generated from protobuf field: string run_id = 1; + */ + runId: string; + /** + * @generated from protobuf field: GridTranscriptionState state = 2; + */ + state: GridTranscriptionState; + /** + * @generated from protobuf field: optional int64 transcript_chat_id = 3; + */ + transcriptChatId?: bigint; + /** + * @generated from protobuf field: int32 revision = 4; + */ + revision: number; +} +/** + * @generated from protobuf message OpenGridThreadInput + */ +export interface OpenGridThreadInput { + /** + * @generated from protobuf field: int64 room_id = 1; + */ + roomId: bigint; + /** + * @generated from protobuf field: string expected_membership_id = 2; + */ + expectedMembershipId: string; +} +/** + * @generated from protobuf message OpenGridThreadResult + */ +export interface OpenGridThreadResult { + /** + * @generated from protobuf field: int64 chat_id = 1; + */ + chatId: bigint; + /** + * @generated from protobuf field: repeated Update updates = 2; + */ + updates: Update[]; +} +/** + * @generated from protobuf message SetGridTranscriptionInput + */ +export interface SetGridTranscriptionInput { + /** + * @generated from protobuf field: int64 room_id = 1; + */ + roomId: bigint; + /** + * @generated from protobuf field: string expected_membership_id = 2; + */ + expectedMembershipId: string; + /** + * @generated from protobuf field: int32 expected_generation = 3; + */ + expectedGeneration: number; + /** + * @generated from protobuf field: optional string expected_run_id = 4; + */ + expectedRunId?: string; + /** + * @generated from protobuf field: int32 expected_revision = 5; + */ + expectedRevision: number; + /** + * @generated from protobuf field: bool enabled = 6; + */ + enabled: boolean; + /** + * @generated from protobuf field: GridTranscriptDestination destination = 7; + */ + destination: GridTranscriptDestination; + /** + * @generated from protobuf field: optional int64 transcript_chat_id = 8; + */ + transcriptChatId?: bigint; + /** + * @generated from protobuf field: string request_id = 9; + */ + requestId: string; +} +/** + * @generated from protobuf message SetGridTranscriptionResult + */ +export interface SetGridTranscriptionResult { + /** + * @generated from protobuf field: Grid grid = 1; + */ + grid?: Grid; + /** + * @generated from protobuf field: repeated Update updates = 2; + */ + updates: Update[]; +} +/** + * @generated from protobuf message ListGridTranscriptsInput + */ +export interface ListGridTranscriptsInput { + /** + * @generated from protobuf field: int64 room_id = 1; + */ + roomId: bigint; +} +/** + * @generated from protobuf message GridTranscriptDestinationInfo + */ +export interface GridTranscriptDestinationInfo { + /** + * @generated from protobuf field: int64 transcript_chat_id = 1; + */ + transcriptChatId: bigint; + /** + * @generated from protobuf field: string title = 2; + */ + title: string; + /** + * @generated from protobuf field: bool busy = 3; + */ + busy: boolean; +} +/** + * @generated from protobuf message ListGridTranscriptsResult + */ +export interface ListGridTranscriptsResult { + /** + * @generated from protobuf field: repeated GridTranscriptDestinationInfo transcripts = 1; + */ + transcripts: GridTranscriptDestinationInfo[]; } /** * Replaceable current state for one Space's implicit Grid. @@ -13578,6 +13795,31 @@ export enum DialogFollowMode { */ UNFOLLOWED = 2 } +/** + * @generated from protobuf enum GridTranscriptMessageKind + */ +export enum GridTranscriptMessageKind { + /** + * @generated from protobuf enum value: GRID_TRANSCRIPT_TURN = 0; + */ + GRID_TRANSCRIPT_TURN = 0, + /** + * @generated from protobuf enum value: GRID_TRANSCRIPT_STARTED = 1; + */ + GRID_TRANSCRIPT_STARTED = 1, + /** + * @generated from protobuf enum value: GRID_TRANSCRIPT_STOPPED = 2; + */ + GRID_TRANSCRIPT_STOPPED = 2, + /** + * @generated from protobuf enum value: GRID_TRANSCRIPT_INTERRUPTED = 3; + */ + GRID_TRANSCRIPT_INTERRUPTED = 3, + /** + * @generated from protobuf enum value: GRID_TRANSCRIPT_LINK = 4; + */ + GRID_TRANSCRIPT_LINK = 4 +} /** * Agent providers that implement Inline's session continuity contract. The * numeric values are stored by the server; additions must be append-only. @@ -14323,7 +14565,65 @@ export enum Method { /** * @generated from protobuf enum value: SET_SPACE_PHOTO = 145; */ - SET_SPACE_PHOTO = 145 + SET_SPACE_PHOTO = 145, + /** + * @generated from protobuf enum value: OPEN_GRID_THREAD = 147; + */ + OPEN_GRID_THREAD = 147, + /** + * @generated from protobuf enum value: SET_GRID_TRANSCRIPTION = 148; + */ + SET_GRID_TRANSCRIPTION = 148, + /** + * @generated from protobuf enum value: LIST_GRID_TRANSCRIPTS = 149; + */ + LIST_GRID_TRANSCRIPTS = 149 +} +/** + * @generated from protobuf enum GridTranscriptionState + */ +export enum GridTranscriptionState { + /** + * @generated from protobuf enum value: GRID_TRANSCRIPTION_UNSPECIFIED = 0; + */ + GRID_TRANSCRIPTION_UNSPECIFIED = 0, + /** + * @generated from protobuf enum value: GRID_TRANSCRIPTION_STARTING = 1; + */ + GRID_TRANSCRIPTION_STARTING = 1, + /** + * @generated from protobuf enum value: GRID_TRANSCRIPTION_ACTIVE = 2; + */ + GRID_TRANSCRIPTION_ACTIVE = 2, + /** + * @generated from protobuf enum value: GRID_TRANSCRIPTION_STOPPING = 3; + */ + GRID_TRANSCRIPTION_STOPPING = 3, + /** + * @generated from protobuf enum value: GRID_TRANSCRIPTION_STOPPED = 4; + */ + GRID_TRANSCRIPTION_STOPPED = 4, + /** + * @generated from protobuf enum value: GRID_TRANSCRIPTION_INTERRUPTED = 5; + */ + GRID_TRANSCRIPTION_INTERRUPTED = 5 +} +/** + * @generated from protobuf enum GridTranscriptDestination + */ +export enum GridTranscriptDestination { + /** + * @generated from protobuf enum value: GRID_TRANSCRIPT_LAST = 0; + */ + GRID_TRANSCRIPT_LAST = 0, + /** + * @generated from protobuf enum value: GRID_TRANSCRIPT_NEW = 1; + */ + GRID_TRANSCRIPT_NEW = 1, + /** + * @generated from protobuf enum value: GRID_TRANSCRIPT_EXISTING = 2; + */ + GRID_TRANSCRIPT_EXISTING = 2 } /** * @generated from protobuf enum GridConnectionUnavailableReason @@ -17505,7 +17805,8 @@ class MessageService$Type extends MessageType { constructor() { super("MessageService", [ { no: 1, name: "thread_backlink", kind: "message", oneof: "event", T: () => MessageServiceThreadBacklink }, - { no: 2, name: "pinned_message", kind: "message", oneof: "event", T: () => MessageServicePinnedMessage } + { no: 2, name: "pinned_message", kind: "message", oneof: "event", T: () => MessageServicePinnedMessage }, + { no: 3, name: "grid_transcript", kind: "message", oneof: "event", T: () => MessageServiceGridTranscript } ]); } create(value?: PartialMessage): MessageService { @@ -17532,6 +17833,12 @@ class MessageService$Type extends MessageType { pinnedMessage: MessageServicePinnedMessage.internalBinaryRead(reader, reader.uint32(), options, (message.event as any).pinnedMessage) }; break; + case /* MessageServiceGridTranscript grid_transcript */ 3: + message.event = { + oneofKind: "gridTranscript", + gridTranscript: MessageServiceGridTranscript.internalBinaryRead(reader, reader.uint32(), options, (message.event as any).gridTranscript) + }; + break; default: let u = options.readUnknownField; if (u === "throw") @@ -17550,6 +17857,9 @@ class MessageService$Type extends MessageType { /* MessageServicePinnedMessage pinned_message = 2; */ if (message.event.oneofKind === "pinnedMessage") MessageServicePinnedMessage.internalBinaryWrite(message.event.pinnedMessage, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* MessageServiceGridTranscript grid_transcript = 3; */ + if (message.event.oneofKind === "gridTranscript") + MessageServiceGridTranscript.internalBinaryWrite(message.event.gridTranscript, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -17660,6 +17970,76 @@ class MessageServicePinnedMessage$Type extends MessageType { + constructor() { + super("MessageServiceGridTranscript", [ + { no: 1, name: "run_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "segment_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "speaker_user_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "kind", kind: "enum", T: () => ["GridTranscriptMessageKind", GridTranscriptMessageKind] } + ]); + } + create(value?: PartialMessage): MessageServiceGridTranscript { + const message = globalThis.Object.create((this.messagePrototype!)); + message.runId = ""; + message.segmentId = ""; + message.kind = 0; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MessageServiceGridTranscript): MessageServiceGridTranscript { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string run_id */ 1: + message.runId = reader.string(); + break; + case /* string segment_id */ 2: + message.segmentId = reader.string(); + break; + case /* optional int64 speaker_user_id */ 3: + message.speakerUserId = reader.int64().toBigInt(); + break; + case /* GridTranscriptMessageKind kind */ 4: + message.kind = reader.int32(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MessageServiceGridTranscript, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string run_id = 1; */ + if (message.runId !== "") + writer.tag(1, WireType.LengthDelimited).string(message.runId); + /* string segment_id = 2; */ + if (message.segmentId !== "") + writer.tag(2, WireType.LengthDelimited).string(message.segmentId); + /* optional int64 speaker_user_id = 3; */ + if (message.speakerUserId !== undefined) + writer.tag(3, WireType.Varint).int64(message.speakerUserId); + /* GridTranscriptMessageKind kind = 4; */ + if (message.kind !== 0) + writer.tag(4, WireType.Varint).int32(message.kind); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MessageServiceGridTranscript + */ +export const MessageServiceGridTranscript = new MessageServiceGridTranscript$Type(); +// @generated message type with reflection information, may provide speed optimized methods class BlockContent$Type extends MessageType { constructor() { super("BlockContent", [ @@ -18745,7 +19125,8 @@ class Message$Type extends MessageType { { no: 22, name: "service_message", kind: "message", T: () => MessageService }, { no: 23, name: "block_content", kind: "message", T: () => BlockContent }, { no: 24, name: "agent_session", kind: "message", T: () => AgentSessionMessageInfo }, - { no: 25, name: "subthread", kind: "message", T: () => MessageSubthread } + { no: 25, name: "subthread", kind: "message", T: () => MessageSubthread }, + { no: 26, name: "counts_as_unread", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ } ]); } create(value?: PartialMessage): Message { @@ -18842,6 +19223,9 @@ class Message$Type extends MessageType { case /* optional MessageSubthread subthread */ 25: message.subthread = MessageSubthread.internalBinaryRead(reader, reader.uint32(), options, message.subthread); break; + case /* optional bool counts_as_unread */ 26: + message.countsAsUnread = reader.bool(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -18932,6 +19316,9 @@ class Message$Type extends MessageType { /* optional MessageSubthread subthread = 25; */ if (message.subthread) MessageSubthread.internalBinaryWrite(message.subthread, writer.tag(25, WireType.LengthDelimited).fork(), options).join(); + /* optional bool counts_as_unread = 26; */ + if (message.countsAsUnread !== undefined) + writer.tag(26, WireType.Varint).bool(message.countsAsUnread); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -23355,7 +23742,10 @@ class RpcCall$Type extends MessageType { { no: 143, name: "transcribeVoiceDraft", kind: "message", oneof: "input", T: () => TranscribeVoiceDraftInput }, { no: 144, name: "requestBotFilesystem", kind: "message", oneof: "input", T: () => RequestBotFilesystemInput }, { no: 145, name: "answerBotFilesystem", kind: "message", oneof: "input", T: () => AnswerBotFilesystemInput }, - { no: 146, name: "setSpacePhoto", kind: "message", oneof: "input", T: () => SetSpacePhotoInput } + { no: 146, name: "setSpacePhoto", kind: "message", oneof: "input", T: () => SetSpacePhotoInput }, + { no: 148, name: "openGridThread", kind: "message", oneof: "input", T: () => OpenGridThreadInput }, + { no: 149, name: "setGridTranscription", kind: "message", oneof: "input", T: () => SetGridTranscriptionInput }, + { no: 150, name: "listGridTranscripts", kind: "message", oneof: "input", T: () => ListGridTranscriptsInput } ]); } create(value?: PartialMessage): RpcCall { @@ -24238,6 +24628,24 @@ class RpcCall$Type extends MessageType { setSpacePhoto: SetSpacePhotoInput.internalBinaryRead(reader, reader.uint32(), options, (message.input as any).setSpacePhoto) }; break; + case /* OpenGridThreadInput openGridThread */ 148: + message.input = { + oneofKind: "openGridThread", + openGridThread: OpenGridThreadInput.internalBinaryRead(reader, reader.uint32(), options, (message.input as any).openGridThread) + }; + break; + case /* SetGridTranscriptionInput setGridTranscription */ 149: + message.input = { + oneofKind: "setGridTranscription", + setGridTranscription: SetGridTranscriptionInput.internalBinaryRead(reader, reader.uint32(), options, (message.input as any).setGridTranscription) + }; + break; + case /* ListGridTranscriptsInput listGridTranscripts */ 150: + message.input = { + oneofKind: "listGridTranscripts", + listGridTranscripts: ListGridTranscriptsInput.internalBinaryRead(reader, reader.uint32(), options, (message.input as any).listGridTranscripts) + }; + break; default: let u = options.readUnknownField; if (u === "throw") @@ -24685,6 +25093,15 @@ class RpcCall$Type extends MessageType { /* SetSpacePhotoInput setSpacePhoto = 146; */ if (message.input.oneofKind === "setSpacePhoto") SetSpacePhotoInput.internalBinaryWrite(message.input.setSpacePhoto, writer.tag(146, WireType.LengthDelimited).fork(), options).join(); + /* OpenGridThreadInput openGridThread = 148; */ + if (message.input.oneofKind === "openGridThread") + OpenGridThreadInput.internalBinaryWrite(message.input.openGridThread, writer.tag(148, WireType.LengthDelimited).fork(), options).join(); + /* SetGridTranscriptionInput setGridTranscription = 149; */ + if (message.input.oneofKind === "setGridTranscription") + SetGridTranscriptionInput.internalBinaryWrite(message.input.setGridTranscription, writer.tag(149, WireType.LengthDelimited).fork(), options).join(); + /* ListGridTranscriptsInput listGridTranscripts = 150; */ + if (message.input.oneofKind === "listGridTranscripts") + ListGridTranscriptsInput.internalBinaryWrite(message.input.listGridTranscripts, writer.tag(150, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -24843,7 +25260,10 @@ class RpcResult$Type extends MessageType { { no: 143, name: "transcribeVoiceDraft", kind: "message", oneof: "result", T: () => TranscribeVoiceDraftResult }, { no: 144, name: "requestBotFilesystem", kind: "message", oneof: "result", T: () => RequestBotFilesystemResult }, { no: 145, name: "answerBotFilesystem", kind: "message", oneof: "result", T: () => AnswerBotFilesystemResult }, - { no: 146, name: "setSpacePhoto", kind: "message", oneof: "result", T: () => SetSpacePhotoResult } + { no: 146, name: "setSpacePhoto", kind: "message", oneof: "result", T: () => SetSpacePhotoResult }, + { no: 148, name: "openGridThread", kind: "message", oneof: "result", T: () => OpenGridThreadResult }, + { no: 149, name: "setGridTranscription", kind: "message", oneof: "result", T: () => SetGridTranscriptionResult }, + { no: 150, name: "listGridTranscripts", kind: "message", oneof: "result", T: () => ListGridTranscriptsResult } ]); } create(value?: PartialMessage): RpcResult { @@ -25726,6 +26146,24 @@ class RpcResult$Type extends MessageType { setSpacePhoto: SetSpacePhotoResult.internalBinaryRead(reader, reader.uint32(), options, (message.result as any).setSpacePhoto) }; break; + case /* OpenGridThreadResult openGridThread */ 148: + message.result = { + oneofKind: "openGridThread", + openGridThread: OpenGridThreadResult.internalBinaryRead(reader, reader.uint32(), options, (message.result as any).openGridThread) + }; + break; + case /* SetGridTranscriptionResult setGridTranscription */ 149: + message.result = { + oneofKind: "setGridTranscription", + setGridTranscription: SetGridTranscriptionResult.internalBinaryRead(reader, reader.uint32(), options, (message.result as any).setGridTranscription) + }; + break; + case /* ListGridTranscriptsResult listGridTranscripts */ 150: + message.result = { + oneofKind: "listGridTranscripts", + listGridTranscripts: ListGridTranscriptsResult.internalBinaryRead(reader, reader.uint32(), options, (message.result as any).listGridTranscripts) + }; + break; default: let u = options.readUnknownField; if (u === "throw") @@ -26173,6 +26611,15 @@ class RpcResult$Type extends MessageType { /* SetSpacePhotoResult setSpacePhoto = 146; */ if (message.result.oneofKind === "setSpacePhoto") SetSpacePhotoResult.internalBinaryWrite(message.result.setSpacePhoto, writer.tag(146, WireType.LengthDelimited).fork(), options).join(); + /* OpenGridThreadResult openGridThread = 148; */ + if (message.result.oneofKind === "openGridThread") + OpenGridThreadResult.internalBinaryWrite(message.result.openGridThread, writer.tag(148, WireType.LengthDelimited).fork(), options).join(); + /* SetGridTranscriptionResult setGridTranscription = 149; */ + if (message.result.oneofKind === "setGridTranscription") + SetGridTranscriptionResult.internalBinaryWrite(message.result.setGridTranscription, writer.tag(149, WireType.LengthDelimited).fork(), options).join(); + /* ListGridTranscriptsResult listGridTranscripts = 150; */ + if (message.result.oneofKind === "listGridTranscripts") + ListGridTranscriptsResult.internalBinaryWrite(message.result.listGridTranscripts, writer.tag(150, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -27313,7 +27760,10 @@ class GridRoom$Type extends MessageType { { no: 6, name: "created_at", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, { no: 7, name: "updated_at", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, { no: 8, name: "avatars", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => GridAvatar }, - { no: 9, name: "connection", kind: "message", T: () => GridConnection } + { no: 9, name: "connection", kind: "message", T: () => GridConnection }, + { no: 10, name: "room_thread_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 11, name: "transcription_available", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 12, name: "transcription", kind: "message", T: () => GridTranscription } ]); } create(value?: PartialMessage): GridRoom { @@ -27325,6 +27775,7 @@ class GridRoom$Type extends MessageType { message.createdAt = 0n; message.updatedAt = 0n; message.avatars = []; + message.transcriptionAvailable = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -27361,6 +27812,15 @@ class GridRoom$Type extends MessageType { case /* optional GridConnection connection */ 9: message.connection = GridConnection.internalBinaryRead(reader, reader.uint32(), options, message.connection); break; + case /* optional int64 room_thread_id */ 10: + message.roomThreadId = reader.int64().toBigInt(); + break; + case /* bool transcription_available */ 11: + message.transcriptionAvailable = reader.bool(); + break; + case /* optional GridTranscription transcription */ 12: + message.transcription = GridTranscription.internalBinaryRead(reader, reader.uint32(), options, message.transcription); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -27400,6 +27860,15 @@ class GridRoom$Type extends MessageType { /* optional GridConnection connection = 9; */ if (message.connection) GridConnection.internalBinaryWrite(message.connection, writer.tag(9, WireType.LengthDelimited).fork(), options).join(); + /* optional int64 room_thread_id = 10; */ + if (message.roomThreadId !== undefined) + writer.tag(10, WireType.Varint).int64(message.roomThreadId); + /* bool transcription_available = 11; */ + if (message.transcriptionAvailable !== false) + writer.tag(11, WireType.Varint).bool(message.transcriptionAvailable); + /* optional GridTranscription transcription = 12; */ + if (message.transcription) + GridTranscription.internalBinaryWrite(message.transcription, writer.tag(12, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -27411,6 +27880,506 @@ class GridRoom$Type extends MessageType { */ export const GridRoom = new GridRoom$Type(); // @generated message type with reflection information, may provide speed optimized methods +class GridTranscription$Type extends MessageType { + constructor() { + super("GridTranscription", [ + { no: 1, name: "run_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "state", kind: "enum", T: () => ["GridTranscriptionState", GridTranscriptionState] }, + { no: 3, name: "transcript_chat_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "revision", kind: "scalar", T: 5 /*ScalarType.INT32*/ } + ]); + } + create(value?: PartialMessage): GridTranscription { + const message = globalThis.Object.create((this.messagePrototype!)); + message.runId = ""; + message.state = 0; + message.revision = 0; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GridTranscription): GridTranscription { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string run_id */ 1: + message.runId = reader.string(); + break; + case /* GridTranscriptionState state */ 2: + message.state = reader.int32(); + break; + case /* optional int64 transcript_chat_id */ 3: + message.transcriptChatId = reader.int64().toBigInt(); + break; + case /* int32 revision */ 4: + message.revision = reader.int32(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: GridTranscription, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string run_id = 1; */ + if (message.runId !== "") + writer.tag(1, WireType.LengthDelimited).string(message.runId); + /* GridTranscriptionState state = 2; */ + if (message.state !== 0) + writer.tag(2, WireType.Varint).int32(message.state); + /* optional int64 transcript_chat_id = 3; */ + if (message.transcriptChatId !== undefined) + writer.tag(3, WireType.Varint).int64(message.transcriptChatId); + /* int32 revision = 4; */ + if (message.revision !== 0) + writer.tag(4, WireType.Varint).int32(message.revision); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message GridTranscription + */ +export const GridTranscription = new GridTranscription$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class OpenGridThreadInput$Type extends MessageType { + constructor() { + super("OpenGridThreadInput", [ + { no: 1, name: "room_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "expected_membership_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): OpenGridThreadInput { + const message = globalThis.Object.create((this.messagePrototype!)); + message.roomId = 0n; + message.expectedMembershipId = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: OpenGridThreadInput): OpenGridThreadInput { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 room_id */ 1: + message.roomId = reader.int64().toBigInt(); + break; + case /* string expected_membership_id */ 2: + message.expectedMembershipId = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: OpenGridThreadInput, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 room_id = 1; */ + if (message.roomId !== 0n) + writer.tag(1, WireType.Varint).int64(message.roomId); + /* string expected_membership_id = 2; */ + if (message.expectedMembershipId !== "") + writer.tag(2, WireType.LengthDelimited).string(message.expectedMembershipId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message OpenGridThreadInput + */ +export const OpenGridThreadInput = new OpenGridThreadInput$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class OpenGridThreadResult$Type extends MessageType { + constructor() { + super("OpenGridThreadResult", [ + { no: 1, name: "chat_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "updates", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Update } + ]); + } + create(value?: PartialMessage): OpenGridThreadResult { + const message = globalThis.Object.create((this.messagePrototype!)); + message.chatId = 0n; + message.updates = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: OpenGridThreadResult): OpenGridThreadResult { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 chat_id */ 1: + message.chatId = reader.int64().toBigInt(); + break; + case /* repeated Update updates */ 2: + message.updates.push(Update.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: OpenGridThreadResult, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 chat_id = 1; */ + if (message.chatId !== 0n) + writer.tag(1, WireType.Varint).int64(message.chatId); + /* repeated Update updates = 2; */ + for (let i = 0; i < message.updates.length; i++) + Update.internalBinaryWrite(message.updates[i], writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message OpenGridThreadResult + */ +export const OpenGridThreadResult = new OpenGridThreadResult$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class SetGridTranscriptionInput$Type extends MessageType { + constructor() { + super("SetGridTranscriptionInput", [ + { no: 1, name: "room_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "expected_membership_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "expected_generation", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, + { no: 4, name: "expected_run_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, + { no: 5, name: "expected_revision", kind: "scalar", T: 5 /*ScalarType.INT32*/ }, + { no: 6, name: "enabled", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 7, name: "destination", kind: "enum", T: () => ["GridTranscriptDestination", GridTranscriptDestination] }, + { no: 8, name: "transcript_chat_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 9, name: "request_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): SetGridTranscriptionInput { + const message = globalThis.Object.create((this.messagePrototype!)); + message.roomId = 0n; + message.expectedMembershipId = ""; + message.expectedGeneration = 0; + message.expectedRevision = 0; + message.enabled = false; + message.destination = 0; + message.requestId = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SetGridTranscriptionInput): SetGridTranscriptionInput { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 room_id */ 1: + message.roomId = reader.int64().toBigInt(); + break; + case /* string expected_membership_id */ 2: + message.expectedMembershipId = reader.string(); + break; + case /* int32 expected_generation */ 3: + message.expectedGeneration = reader.int32(); + break; + case /* optional string expected_run_id */ 4: + message.expectedRunId = reader.string(); + break; + case /* int32 expected_revision */ 5: + message.expectedRevision = reader.int32(); + break; + case /* bool enabled */ 6: + message.enabled = reader.bool(); + break; + case /* GridTranscriptDestination destination */ 7: + message.destination = reader.int32(); + break; + case /* optional int64 transcript_chat_id */ 8: + message.transcriptChatId = reader.int64().toBigInt(); + break; + case /* string request_id */ 9: + message.requestId = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: SetGridTranscriptionInput, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 room_id = 1; */ + if (message.roomId !== 0n) + writer.tag(1, WireType.Varint).int64(message.roomId); + /* string expected_membership_id = 2; */ + if (message.expectedMembershipId !== "") + writer.tag(2, WireType.LengthDelimited).string(message.expectedMembershipId); + /* int32 expected_generation = 3; */ + if (message.expectedGeneration !== 0) + writer.tag(3, WireType.Varint).int32(message.expectedGeneration); + /* optional string expected_run_id = 4; */ + if (message.expectedRunId !== undefined) + writer.tag(4, WireType.LengthDelimited).string(message.expectedRunId); + /* int32 expected_revision = 5; */ + if (message.expectedRevision !== 0) + writer.tag(5, WireType.Varint).int32(message.expectedRevision); + /* bool enabled = 6; */ + if (message.enabled !== false) + writer.tag(6, WireType.Varint).bool(message.enabled); + /* GridTranscriptDestination destination = 7; */ + if (message.destination !== 0) + writer.tag(7, WireType.Varint).int32(message.destination); + /* optional int64 transcript_chat_id = 8; */ + if (message.transcriptChatId !== undefined) + writer.tag(8, WireType.Varint).int64(message.transcriptChatId); + /* string request_id = 9; */ + if (message.requestId !== "") + writer.tag(9, WireType.LengthDelimited).string(message.requestId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message SetGridTranscriptionInput + */ +export const SetGridTranscriptionInput = new SetGridTranscriptionInput$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class SetGridTranscriptionResult$Type extends MessageType { + constructor() { + super("SetGridTranscriptionResult", [ + { no: 1, name: "grid", kind: "message", T: () => Grid }, + { no: 2, name: "updates", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Update } + ]); + } + create(value?: PartialMessage): SetGridTranscriptionResult { + const message = globalThis.Object.create((this.messagePrototype!)); + message.updates = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SetGridTranscriptionResult): SetGridTranscriptionResult { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* Grid grid */ 1: + message.grid = Grid.internalBinaryRead(reader, reader.uint32(), options, message.grid); + break; + case /* repeated Update updates */ 2: + message.updates.push(Update.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: SetGridTranscriptionResult, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* Grid grid = 1; */ + if (message.grid) + Grid.internalBinaryWrite(message.grid, writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + /* repeated Update updates = 2; */ + for (let i = 0; i < message.updates.length; i++) + Update.internalBinaryWrite(message.updates[i], writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message SetGridTranscriptionResult + */ +export const SetGridTranscriptionResult = new SetGridTranscriptionResult$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ListGridTranscriptsInput$Type extends MessageType { + constructor() { + super("ListGridTranscriptsInput", [ + { no: 1, name: "room_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): ListGridTranscriptsInput { + const message = globalThis.Object.create((this.messagePrototype!)); + message.roomId = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ListGridTranscriptsInput): ListGridTranscriptsInput { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 room_id */ 1: + message.roomId = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: ListGridTranscriptsInput, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 room_id = 1; */ + if (message.roomId !== 0n) + writer.tag(1, WireType.Varint).int64(message.roomId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message ListGridTranscriptsInput + */ +export const ListGridTranscriptsInput = new ListGridTranscriptsInput$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class GridTranscriptDestinationInfo$Type extends MessageType { + constructor() { + super("GridTranscriptDestinationInfo", [ + { no: 1, name: "transcript_chat_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "title", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "busy", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } + ]); + } + create(value?: PartialMessage): GridTranscriptDestinationInfo { + const message = globalThis.Object.create((this.messagePrototype!)); + message.transcriptChatId = 0n; + message.title = ""; + message.busy = false; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GridTranscriptDestinationInfo): GridTranscriptDestinationInfo { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* int64 transcript_chat_id */ 1: + message.transcriptChatId = reader.int64().toBigInt(); + break; + case /* string title */ 2: + message.title = reader.string(); + break; + case /* bool busy */ 3: + message.busy = reader.bool(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: GridTranscriptDestinationInfo, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* int64 transcript_chat_id = 1; */ + if (message.transcriptChatId !== 0n) + writer.tag(1, WireType.Varint).int64(message.transcriptChatId); + /* string title = 2; */ + if (message.title !== "") + writer.tag(2, WireType.LengthDelimited).string(message.title); + /* bool busy = 3; */ + if (message.busy !== false) + writer.tag(3, WireType.Varint).bool(message.busy); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message GridTranscriptDestinationInfo + */ +export const GridTranscriptDestinationInfo = new GridTranscriptDestinationInfo$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class ListGridTranscriptsResult$Type extends MessageType { + constructor() { + super("ListGridTranscriptsResult", [ + { no: 1, name: "transcripts", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => GridTranscriptDestinationInfo } + ]); + } + create(value?: PartialMessage): ListGridTranscriptsResult { + const message = globalThis.Object.create((this.messagePrototype!)); + message.transcripts = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ListGridTranscriptsResult): ListGridTranscriptsResult { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated GridTranscriptDestinationInfo transcripts */ 1: + message.transcripts.push(GridTranscriptDestinationInfo.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: ListGridTranscriptsResult, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated GridTranscriptDestinationInfo transcripts = 1; */ + for (let i = 0; i < message.transcripts.length; i++) + GridTranscriptDestinationInfo.internalBinaryWrite(message.transcripts[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message ListGridTranscriptsResult + */ +export const ListGridTranscriptsResult = new ListGridTranscriptsResult$Type(); +// @generated message type with reflection information, may provide speed optimized methods class Grid$Type extends MessageType { constructor() { super("Grid", [ diff --git a/packages/protocol/tests/grid-transcription.test.ts b/packages/protocol/tests/grid-transcription.test.ts new file mode 100644 index 000000000..3081e448b --- /dev/null +++ b/packages/protocol/tests/grid-transcription.test.ts @@ -0,0 +1,111 @@ +import { describe, expect, test } from "bun:test" +import * as P from "../src/core.js" + +// Distinct payloads protect field numbers, oneof dispatch, nested values, +// optional false and int64 values beyond JavaScript number precision. +const cases = [ + { + method: "OPEN_GRID_THREAD", + field: "openGridThread", + value: 147, + tag: 148, + request: { + roomId: "9007199254740995", + expectedMembershipId: "66666666-7777-4888-8999-aaaaaaaaaaaa" + }, + result: { + chatId: "9007199254740997" + } + }, + { + method: "SET_GRID_TRANSCRIPTION", + field: "setGridTranscription", + value: 148, + tag: 149, + request: { + roomId: "9007199254740995", + expectedMembershipId: "66666666-7777-4888-8999-aaaaaaaaaaaa", + expectedGeneration: 23, + expectedRunId: "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff", + expectedRevision: 37, + enabled: true, + destination: "GRID_TRANSCRIPT_EXISTING", + transcriptChatId: "9007199254740997", + requestId: "12121212-3434-4567-89ab-cdcdcdcdcdcd" + }, + result: { + grid: { + spaceId: "9007199254740993", + enabled: true, + currentRoomId: "9007199254740995", + revision: "9007199254740999", + rooms: [ + { + id: "9007199254740995", + spaceId: "9007199254740993", + roomThreadId: "9007199254741001", + transcriptionAvailable: true, + transcription: { + runId: "bbbbbbbb-cccc-4ddd-8eee-ffffffffffff", + state: "GRID_TRANSCRIPTION_STOPPING", + transcriptChatId: "9007199254740997", + revision: 37 + } + } + ] + } + } + }, + { + method: "LIST_GRID_TRANSCRIPTS", + field: "listGridTranscripts", + value: 149, + tag: 150, + request: { + roomId: "9007199254740995" + }, + result: { + transcripts: [ + { + transcriptChatId: "9007199254740997", + title: "Planning discussion", + busy: true + }, + { + transcriptChatId: "9007199254741003", + title: "Earlier session", + busy: false + } + ] + } + } +] as const + +describe("grid transcription allocation", () => { + for (const { method, field, value, tag, request, result } of cases) { + test(`${method} request and result binary round trip`, () => { + expect(P.Method[method]).toBe(value) + expect(P.RpcCall.fields.find((f) => f.localName === field)?.no).toBe(tag) + expect(P.RpcResult.fields.find((f) => f.localName === field)?.no).toBe(tag) + const call = P.RpcCall.fromJson({ method, [field]: request }) + expect(call.input.oneofKind).toBe(field) + const decodedCall = P.RpcCall.fromBinary(P.RpcCall.toBinary(call)) + expect(decodedCall).toEqual(call) + expect(P.RpcCall.toJsonString(decodedCall)).toEqual(P.RpcCall.toJsonString(call)) + const response = P.RpcResult.fromJson({ reqMsgId: "9007199254740993", [field]: result }) + expect(response.result.oneofKind).toBe(field) + const decodedResult = P.RpcResult.fromBinary(P.RpcResult.toBinary(response)) + expect(decodedResult).toEqual(response) + expect(P.RpcResult.toJsonString(decodedResult)).toEqual(P.RpcResult.toJsonString(response)) + }) + } + test("both namespaces stay unique and previous methods survive", () => { + for (const type of [P.RpcCall, P.RpcResult]) { + const tags = type.fields.map((f) => f.no) + expect(new Set(tags).size).toBe(tags.length) + } + expect(P.Method.GET_GRID).toBe(83) + expect(P.Method.SEARCH_MESSAGES).toBe(28) + expect(P.ConnectionInit.fields.some((f) => f.localName === "supportsWorking")).toBe(true) + }) +}) diff --git a/proto/client.proto b/proto/client.proto index 1bfd9f851..c834cec95 100644 --- a/proto/client.proto +++ b/proto/client.proto @@ -10,6 +10,7 @@ message MessageContentPayload { MessageReplies replies = 3; MessageService service_message = 4; MessageSubthread subthread = 5; + optional bool counts_as_unread = 6; } message MessageVoiceContent { diff --git a/proto/core.proto b/proto/core.proto index fbbd81eb1..e3269be83 100644 --- a/proto/core.proto +++ b/proto/core.proto @@ -439,6 +439,7 @@ message MessageService { oneof event { MessageServiceThreadBacklink thread_backlink = 1; MessageServicePinnedMessage pinned_message = 2; + MessageServiceGridTranscript grid_transcript = 3; } } @@ -452,6 +453,21 @@ message MessageServicePinnedMessage { optional int64 message_id = 1; } +enum GridTranscriptMessageKind { + GRID_TRANSCRIPT_TURN = 0; + GRID_TRANSCRIPT_STARTED = 1; + GRID_TRANSCRIPT_STOPPED = 2; + GRID_TRANSCRIPT_INTERRUPTED = 3; + GRID_TRANSCRIPT_LINK = 4; +} + +message MessageServiceGridTranscript { + string run_id = 1; + string segment_id = 2; + optional int64 speaker_user_id = 3; + GridTranscriptMessageKind kind = 4; +} + // Structural rich content projected alongside Message.message and // Message.entities. Text-bearing blocks use UTF-16 ranges into Message.message; // inline entities remain owned by Message.entities so there is one text/entity @@ -677,6 +693,9 @@ message Message { // Canonical card projection for anchored replies and materialized subthreads. optional MessageSubthread subthread = 25; + + // Explicit false suppresses unread for generated/historical rows. + optional bool counts_as_unread = 26; } // Agent providers that implement Inline's session continuity contract. The @@ -1602,6 +1621,9 @@ enum Method { REQUEST_BOT_FILESYSTEM = 143; ANSWER_BOT_FILESYSTEM = 144; SET_SPACE_PHOTO = 145; + OPEN_GRID_THREAD = 147; + SET_GRID_TRANSCRIPTION = 148; + LIST_GRID_TRANSCRIPTS = 149; } message RpcCall { @@ -1752,6 +1774,9 @@ message RpcCall { RequestBotFilesystemInput requestBotFilesystem = 144; AnswerBotFilesystemInput answerBotFilesystem = 145; SetSpacePhotoInput setSpacePhoto = 146; + OpenGridThreadInput openGridThread = 148; + SetGridTranscriptionInput setGridTranscription = 149; + ListGridTranscriptsInput listGridTranscripts = 150; } } @@ -1904,6 +1929,9 @@ message RpcResult { RequestBotFilesystemResult requestBotFilesystem = 144; AnswerBotFilesystemResult answerBotFilesystem = 145; SetSpacePhotoResult setSpacePhoto = 146; + OpenGridThreadResult openGridThread = 148; + SetGridTranscriptionResult setGridTranscription = 149; + ListGridTranscriptsResult listGridTranscripts = 150; } } @@ -2078,7 +2106,57 @@ message GridRoom { int64 updated_at = 7; repeated GridAvatar avatars = 8; optional GridConnection connection = 9; + optional int64 room_thread_id = 10; + bool transcription_available = 11; + optional GridTranscription transcription = 12; +} + +enum GridTranscriptionState { + GRID_TRANSCRIPTION_UNSPECIFIED = 0; + GRID_TRANSCRIPTION_STARTING = 1; + GRID_TRANSCRIPTION_ACTIVE = 2; + GRID_TRANSCRIPTION_STOPPING = 3; + GRID_TRANSCRIPTION_STOPPED = 4; + GRID_TRANSCRIPTION_INTERRUPTED = 5; +} + +message GridTranscription { + string run_id = 1; + GridTranscriptionState state = 2; + optional int64 transcript_chat_id = 3; + int32 revision = 4; +} + +enum GridTranscriptDestination { + GRID_TRANSCRIPT_LAST = 0; + GRID_TRANSCRIPT_NEW = 1; + GRID_TRANSCRIPT_EXISTING = 2; +} + +message OpenGridThreadInput { + int64 room_id = 1; + string expected_membership_id = 2; +} +message OpenGridThreadResult { int64 chat_id = 1; repeated Update updates = 2; } +message SetGridTranscriptionInput { + int64 room_id = 1; + string expected_membership_id = 2; + int32 expected_generation = 3; + optional string expected_run_id = 4; + int32 expected_revision = 5; + bool enabled = 6; + GridTranscriptDestination destination = 7; + optional int64 transcript_chat_id = 8; + string request_id = 9; +} +message SetGridTranscriptionResult { Grid grid = 1; repeated Update updates = 2; } +message ListGridTranscriptsInput { int64 room_id = 1; } +message GridTranscriptDestinationInfo { + int64 transcript_chat_id = 1; + string title = 2; + bool busy = 3; } +message ListGridTranscriptsResult { repeated GridTranscriptDestinationInfo transcripts = 1; } // Replaceable current state for one Space's implicit Grid. message Grid { diff --git a/proto/server.proto b/proto/server.proto index f6c1220d8..72cf8fe73 100644 --- a/proto/server.proto +++ b/proto/server.proto @@ -376,6 +376,7 @@ message SystemMessage { oneof event { ThreadBacklinkSystemMessage thread_backlink = 1; PinnedMessageSystemMessage pinned_message = 2; + MessageServiceGridTranscript grid_transcript = 3; } } From 7f9832c49be1f38d4f01425f40fbb826e84e685b Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Mon, 5 Oct 2026 06:53:54 +0330 Subject: [PATCH 02/14] server: add authorized grid transcript threads and ownership fences --- server/drizzle/0154_grid-call-identity.sql | 1 + server/drizzle/0155_grid-transcription.sql | 72 + server/drizzle/meta/0154_snapshot.json | 12710 +++++++++++++++ server/drizzle/meta/0155_snapshot.json | 13146 ++++++++++++++++ server/drizzle/meta/_journal.json | 16 +- server/scripts/contracts/route-manifest.ts | 3 + .../fixtures/effect-route-manifest.json | 30 +- server/src/core/http/candidateApplication.ts | 48 +- .../http/gridTranscription.effect.bun.test.ts | 26 + .../src/core/http/gridTranscription.effect.ts | 33 + server/src/db/models/chats.ts | 5 +- server/src/db/models/messages.ts | 32 +- server/src/db/schema/grid.ts | 122 +- server/src/functions/_functions.ts | 4 + server/src/functions/grid.ts | 159 +- server/src/functions/gridTranscription.ts | 437 + server/src/functions/messages.deleteChat.ts | 3 + .../src/functions/messages.deleteMessage.ts | 26 +- server/src/functions/messages.editMessage.ts | 3 + server/src/functions/messages.moveThread.ts | 3 + .../messages.removeChatParticipant.ts | 5 + .../messages.updateChatVisibility.ts | 5 + server/src/methods/deleteSpace.ts | 7 +- server/src/modules/grid/roomLifecycle.ts | 25 +- .../grid/transcription/control.test.ts | 796 + .../grid/transcription/messages.test.ts | 276 + .../modules/grid/transcription/messages.ts | 147 + .../src/modules/grid/transcription/service.ts | 5 + .../src/modules/grid/transcription/state.ts | 363 + .../src/modules/grid/transcription/threads.ts | 220 + .../src/modules/grid/transcription/worker.ts | 564 + server/src/modules/historyClear/data.ts | 10 +- server/src/modules/message/activity.ts | 7 + server/src/protocol/server.ts | 19 +- .../realtime/encoders/encodeMessage.test.ts | 26 + server/src/realtime/encoders/encodeMessage.ts | 25 +- server/src/realtime/handlers/_rpc.ts | 21 + server/src/realtime/handlers/grid.ts | 15 + server/src/utils/httpPrivacy.test.ts | 1 + server/src/utils/httpPrivacy.ts | 1 + 40 files changed, 29357 insertions(+), 60 deletions(-) create mode 100644 server/drizzle/0154_grid-call-identity.sql create mode 100644 server/drizzle/0155_grid-transcription.sql create mode 100644 server/drizzle/meta/0154_snapshot.json create mode 100644 server/drizzle/meta/0155_snapshot.json create mode 100644 server/src/core/http/gridTranscription.effect.bun.test.ts create mode 100644 server/src/core/http/gridTranscription.effect.ts create mode 100644 server/src/functions/gridTranscription.ts create mode 100644 server/src/modules/grid/transcription/control.test.ts create mode 100644 server/src/modules/grid/transcription/messages.test.ts create mode 100644 server/src/modules/grid/transcription/messages.ts create mode 100644 server/src/modules/grid/transcription/service.ts create mode 100644 server/src/modules/grid/transcription/state.ts create mode 100644 server/src/modules/grid/transcription/threads.ts create mode 100644 server/src/modules/grid/transcription/worker.ts create mode 100644 server/src/modules/message/activity.ts diff --git a/server/drizzle/0154_grid-call-identity.sql b/server/drizzle/0154_grid-call-identity.sql new file mode 100644 index 000000000..f7c608991 --- /dev/null +++ b/server/drizzle/0154_grid-call-identity.sql @@ -0,0 +1 @@ +ALTER TABLE "grid_presence" ADD COLUMN "call_id" uuid DEFAULT gen_random_uuid() NOT NULL; \ No newline at end of file diff --git a/server/drizzle/0155_grid-transcription.sql b/server/drizzle/0155_grid-transcription.sql new file mode 100644 index 000000000..9d5386cd8 --- /dev/null +++ b/server/drizzle/0155_grid-transcription.sql @@ -0,0 +1,72 @@ +CREATE TABLE "grid_transcription_runs" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "source_room_id" integer NOT NULL, + "space_id" integer NOT NULL, + "room_chat_id" integer NOT NULL, + "transcript_chat_id" integer NOT NULL, + "destination_parent_chat_id" integer NOT NULL, + "original_anchor_id" integer NOT NULL, + "room_link_message_id" integer, + "actor_user_id" integer NOT NULL, + "request_id" uuid NOT NULL, + "model" varchar(32) NOT NULL, + "state" varchar(16) NOT NULL, + "generation" integer NOT NULL, + "provider_target" varchar(255) NOT NULL, + "revision" integer DEFAULT 1 NOT NULL, + "claim_epoch" integer DEFAULT 0 NOT NULL, + "worker_id" varchar(80), + "lease_expires_at" timestamp (3), + "expires_at" timestamp (3) NOT NULL, + "stop_requested_at" timestamp (3), + "interruption_reason" varchar(80), + "created_at" timestamp (3) DEFAULT now() NOT NULL, + CONSTRAINT "grid_transcription_state_check" CHECK ("grid_transcription_runs"."state" in ('starting','active','stopping','stopped','interrupted')) +); +--> statement-breakpoint +CREATE TABLE "grid_transcription_segments" ( + "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, + "run_id" uuid NOT NULL, + "claim_epoch" integer NOT NULL, + "speaker_user_id" integer NOT NULL, + "membership_id" uuid NOT NULL, + "track_sid" varchar(128) NOT NULL, + "source_turn_key" varchar(128) NOT NULL, + "state" varchar(16) DEFAULT 'admitted' NOT NULL, + "message_id" integer, + "admitted_at" timestamp (3) DEFAULT now() NOT NULL, + CONSTRAINT "grid_transcription_segment_state_check" CHECK ("grid_transcription_segments"."state" in ('admitted','finalized','discarded')) +); +--> statement-breakpoint +CREATE TABLE "grid_transcription_space_revisions" ( + "space_id" integer PRIMARY KEY NOT NULL, + "revision" bigint DEFAULT 0 NOT NULL +); +--> statement-breakpoint +CREATE TABLE "grid_transcription_worker" ( + "id" integer PRIMARY KEY NOT NULL, + "worker_id" varchar(80) NOT NULL, + "model" varchar(32) NOT NULL, + "heartbeat_at" timestamp (3) NOT NULL +); +--> statement-breakpoint +ALTER TABLE "grid_rooms" ADD COLUMN "room_thread_id" integer;--> statement-breakpoint +ALTER TABLE "grid_transcription_segments" ADD CONSTRAINT "grid_transcription_segments_run_id_grid_transcription_runs_id_fk" FOREIGN KEY ("run_id") REFERENCES "public"."grid_transcription_runs"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "grid_transcription_request_unique" ON "grid_transcription_runs" USING btree ("actor_user_id","request_id");--> statement-breakpoint +CREATE UNIQUE INDEX "grid_transcription_active_room_unique" ON "grid_transcription_runs" USING btree ("source_room_id") WHERE "grid_transcription_runs"."state" in ('starting','active','stopping');--> statement-breakpoint +CREATE UNIQUE INDEX "grid_transcription_active_destination_unique" ON "grid_transcription_runs" USING btree ("transcript_chat_id") WHERE "grid_transcription_runs"."state" in ('starting','active','stopping');--> statement-breakpoint +CREATE INDEX "grid_transcription_space_recent_idx" ON "grid_transcription_runs" USING btree ("space_id","created_at");--> statement-breakpoint +CREATE INDEX "grid_transcription_source_room_idx" ON "grid_transcription_runs" USING btree ("source_room_id");--> statement-breakpoint +CREATE UNIQUE INDEX "grid_transcription_segment_source_unique" ON "grid_transcription_segments" USING btree ("run_id","track_sid","source_turn_key");--> statement-breakpoint +CREATE FUNCTION "bump_grid_transcription_revision"() RETURNS trigger AS $$ +BEGIN + INSERT INTO "grid_transcription_space_revisions" ("space_id", "revision") VALUES (NEW."space_id", 1) + ON CONFLICT ("space_id") DO UPDATE + SET "revision" = "grid_transcription_space_revisions"."revision" + 1; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; +--> statement-breakpoint +CREATE TRIGGER "grid_transcription_revision_changed" +AFTER INSERT OR UPDATE OF "state", "revision" ON "grid_transcription_runs" +FOR EACH ROW EXECUTE FUNCTION "bump_grid_transcription_revision"(); diff --git a/server/drizzle/meta/0154_snapshot.json b/server/drizzle/meta/0154_snapshot.json new file mode 100644 index 000000000..885acf6ab --- /dev/null +++ b/server/drizzle/meta/0154_snapshot.json @@ -0,0 +1,12710 @@ +{ + "id": "c0fd85d8-6fc2-45d5-9db9-f630a77657fe", + "prevId": "f092d048-4f44-45b4-9f34-913ad0a74c2b", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_zone": { + "name": "time_zone", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.there_users": { + "name": "there_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "time_zone": { + "name": "time_zone", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "there_users_email_unique": { + "name": "there_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "default": "nextval('user_id')" + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "phone_verified": { + "name": "phone_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "online": { + "name": "online", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_online": { + "name": "last_online", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "photo_file_id": { + "name": "photo_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pending_setup": { + "name": "pending_setup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "time_zone": { + "name": "time_zone", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "share_time_zone": { + "name": "share_time_zone", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "appear_in_global_search": { + "name": "appear_in_global_search", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "next_thread_number": { + "name": "next_thread_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "bot": { + "name": "bot", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "bot_creator_id": { + "name": "bot_creator_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "update_seq": { + "name": "update_seq", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_update_date": { + "name": "last_update_date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "users_username_unique": { + "name": "users_username_unique", + "columns": [ + { + "expression": "lower(\"username\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users_photo_file_id_files_id_fk": { + "name": "users_photo_file_id_files_id_fk", + "tableFrom": "users", + "tableTo": "files", + "columnsFrom": [ + "photo_file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "users_bot_creator_id_users_id_fk": { + "name": "users_bot_creator_id_users_id_fk", + "tableFrom": "users", + "tableTo": "users", + "columnsFrom": [ + "bot_creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "users_phone_number_unique": { + "name": "users_phone_number_unique", + "nullsNotDistinct": false, + "columns": [ + "phone_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "revoked": { + "name": "revoked", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_active": { + "name": "last_active", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "personal_data_encrypted": { + "name": "personal_data_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "personal_data_iv": { + "name": "personal_data_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "personal_data_tag": { + "name": "personal_data_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "applePushToken": { + "name": "applePushToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "apple_push_token_encrypted": { + "name": "apple_push_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "apple_push_token_iv": { + "name": "apple_push_token_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "apple_push_token_tag": { + "name": "apple_push_token_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "push_notification_provider": { + "name": "push_notification_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "push_content_key_public": { + "name": "push_content_key_public", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "push_content_key_id": { + "name": "push_content_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "push_content_key_algorithm": { + "name": "push_content_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "push_content_version": { + "name": "push_content_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_type": { + "name": "client_type", + "type": "client_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "clientVersion": { + "name": "clientVersion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "osVersion": { + "name": "osVersion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sessions_token_hash_idx": { + "name": "sessions_token_hash_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "device_id_user_unique": { + "name": "device_id_user_unique", + "nullsNotDistinct": false, + "columns": [ + "device_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.superadmin_sessions": { + "name": "superadmin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "step_up_at": { + "name": "step_up_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "idle_expires_at": { + "name": "idle_expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent_hash": { + "name": "user_agent_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "superadmin_sessions_user_id_users_id_fk": { + "name": "superadmin_sessions_user_id_users_id_fk", + "tableFrom": "superadmin_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "superadmin_sessions_token_hash_unique": { + "name": "superadmin_sessions_token_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "token_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.login_codes": { + "name": "login_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "challenge_id": { + "name": "challenge_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "smallint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "login_codes_challenge_id_unique": { + "name": "login_codes_challenge_id_unique", + "columns": [ + { + "expression": "challenge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_codes_email_expires_idx": { + "name": "login_codes_email_expires_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_codes_phone_expires_idx": { + "name": "login_codes_phone_expires_idx", + "columns": [ + { + "expression": "phone_number", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "login_codes_code_or_hash_present": { + "name": "login_codes_code_or_hash_present", + "value": "\"login_codes\".\"code\" is not null or \"login_codes\".\"code_hash\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.invite_codes": { + "name": "invite_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "redeemed_by_user_id": { + "name": "redeemed_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "invite_codes_code_unique": { + "name": "invite_codes_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invite_codes_owner_user_id_idx": { + "name": "invite_codes_owner_user_id_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invite_codes_redeemed_by_user_id_idx": { + "name": "invite_codes_redeemed_by_user_id_idx", + "columns": [ + { + "expression": "redeemed_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invite_codes_owner_user_id_users_id_fk": { + "name": "invite_codes_owner_user_id_users_id_fk", + "tableFrom": "invite_codes", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "invite_codes_created_by_user_id_users_id_fk": { + "name": "invite_codes_created_by_user_id_users_id_fk", + "tableFrom": "invite_codes", + "tableTo": "users", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "invite_codes_redeemed_by_user_id_users_id_fk": { + "name": "invite_codes_redeemed_by_user_id_users_id_fk", + "tableFrom": "invite_codes", + "tableTo": "users", + "columnsFrom": [ + "redeemed_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.superadmin_users": { + "name": "superadmin_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password_set_at": { + "name": "password_set_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "totp_secret_encrypted": { + "name": "totp_secret_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "totp_secret_iv": { + "name": "totp_secret_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "totp_secret_tag": { + "name": "totp_secret_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "totp_enabled_at": { + "name": "totp_enabled_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "totp_last_used_at": { + "name": "totp_last_used_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "failed_login_attempts": { + "name": "failed_login_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_login_attempt_at": { + "name": "last_login_attempt_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "login_locked_until": { + "name": "login_locked_until", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "superadmin_users_user_id_users_id_fk": { + "name": "superadmin_users_user_id_users_id_fk", + "tableFrom": "superadmin_users", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "superadmin_users_email_unique": { + "name": "superadmin_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "superadmin_users_user_id_unique": { + "name": "superadmin_users_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.spaces": { + "name": "spaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "photo_file_unique_id": { + "name": "photo_file_unique_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "is_pro": { + "name": "is_pro", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "handle": { + "name": "handle", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "creatorId": { + "name": "creatorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "can_public_join": { + "name": "can_public_join", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted": { + "name": "deleted", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "next_thread_number": { + "name": "next_thread_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "update_seq": { + "name": "update_seq", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_update_date": { + "name": "last_update_date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "grid_revision": { + "name": "grid_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "spaces_handle_unique": { + "name": "spaces_handle_unique", + "columns": [ + { + "expression": "lower(\"handle\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "spaces_creatorId_users_id_fk": { + "name": "spaces_creatorId_users_id_fk", + "tableFrom": "spaces", + "tableTo": "users", + "columnsFrom": [ + "creatorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "spaces_photo_file_unique_id_fkey": { + "name": "spaces_photo_file_unique_id_fkey", + "tableFrom": "spaces", + "tableTo": "files", + "columnsFrom": [ + "photo_file_unique_id" + ], + "columnsTo": [ + "file_unique_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "spaces_public_join_handle_check": { + "name": "spaces_public_join_handle_check", + "value": "not \"spaces\".\"can_public_join\" or (\"spaces\".\"is_public\" and \"spaces\".\"handle\" is not null and \"spaces\".\"handle\" ~ '^[A-Za-z0-9][A-Za-z0-9_-]{1,63}$')" + } + }, + "isRLSEnabled": false + }, + "public.space_invite_links": { + "name": "space_invite_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "token_encrypted": { + "name": "token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "space_invite_links_token_hash_unique": { + "name": "space_invite_links_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "space_invite_links_active_space_unique": { + "name": "space_invite_links_active_space_unique", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"space_invite_links\".\"revoked_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "space_invite_links_space_id_idx": { + "name": "space_invite_links_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "space_invite_links_created_by_user_id_idx": { + "name": "space_invite_links_created_by_user_id_idx", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "space_invite_links_space_id_spaces_id_fk": { + "name": "space_invite_links_space_id_spaces_id_fk", + "tableFrom": "space_invite_links", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "space_invite_links_created_by_user_id_users_id_fk": { + "name": "space_invite_links_created_by_user_id_users_id_fk", + "tableFrom": "space_invite_links", + "tableTo": "users", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "space_invite_links_token_hash_length": { + "name": "space_invite_links_token_hash_length", + "value": "octet_length(\"space_invite_links\".\"token_hash\") = 32" + }, + "space_invite_links_expiry_check": { + "name": "space_invite_links_expiry_check", + "value": "\"space_invite_links\".\"expires_at\" > \"space_invite_links\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.space_join_blocks": { + "name": "space_join_blocks", + "schema": "", + "columns": { + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "space_join_blocks_user_id_idx": { + "name": "space_join_blocks_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "space_join_blocks_space_id_spaces_id_fk": { + "name": "space_join_blocks_space_id_spaces_id_fk", + "tableFrom": "space_join_blocks", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "space_join_blocks_user_id_users_id_fk": { + "name": "space_join_blocks_user_id_users_id_fk", + "tableFrom": "space_join_blocks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "space_join_blocks_pkey": { + "name": "space_join_blocks_pkey", + "columns": [ + "space_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.members": { + "name": "members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "member_roles", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'member'" + }, + "can_access_public_chats": { + "name": "can_access_public_chats", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "invited_by": { + "name": "invited_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "members_user_id_users_id_fk": { + "name": "members_user_id_users_id_fk", + "tableFrom": "members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "members_space_id_spaces_id_fk": { + "name": "members_space_id_spaces_id_fk", + "tableFrom": "members", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "members_invited_by_users_id_fk": { + "name": "members_invited_by_users_id_fk", + "tableFrom": "members", + "tableTo": "users", + "columnsFrom": [ + "invited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "members_user_id_space_id_unique": { + "name": "members_user_id_space_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "space_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_participants": { + "name": "chat_participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "chat_participants_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_participants_chat_id_chats_id_fk": { + "name": "chat_participants_chat_id_chats_id_fk", + "tableFrom": "chat_participants", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_participants_user_id_users_id_fk": { + "name": "chat_participants_user_id_users_id_fk", + "tableFrom": "chat_participants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_participant": { + "name": "unique_participant", + "nullsNotDistinct": false, + "columns": [ + "chat_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chats": { + "name": "chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "chats_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "type": { + "name": "type", + "type": "chat_types", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title_hash": { + "name": "title_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "auto_title_generated": { + "name": "auto_title_generated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_msg_id": { + "name": "last_msg_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "message_id_counter": { + "name": "message_id_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_thread": { + "name": "public_thread", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "thread_number": { + "name": "thread_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_untitled": { + "name": "is_untitled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "parent_chat_id": { + "name": "parent_chat_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_user_id": { + "name": "min_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_user_id": { + "name": "max_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_context": { + "name": "agent_context", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "update_seq": { + "name": "update_seq", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_update_date": { + "name": "last_update_date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chats_title_hash_idx": { + "name": "chats_title_hash_idx", + "columns": [ + { + "expression": "title_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_thread_number_unique": { + "name": "user_thread_number_unique", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chats\".\"space_id\" is null and \"chats\".\"thread_number\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chats_parent_chat_id_idx": { + "name": "chats_parent_chat_id_idx", + "columns": [ + { + "expression": "parent_chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chats_space_id_spaces_id_fk": { + "name": "chats_space_id_spaces_id_fk", + "tableFrom": "chats", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chats_created_by_users_id_fk": { + "name": "chats_created_by_users_id_fk", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chats_parent_chat_id_chats_id_fk": { + "name": "chats_parent_chat_id_chats_id_fk", + "tableFrom": "chats", + "tableTo": "chats", + "columnsFrom": [ + "parent_chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chats_min_user_id_users_id_fk": { + "name": "chats_min_user_id_users_id_fk", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "min_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chats_max_user_id_users_id_fk": { + "name": "chats_max_user_id_users_id_fk", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "max_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "last_msg_id_fk": { + "name": "last_msg_id_fk", + "tableFrom": "chats", + "tableTo": "messages", + "columnsFrom": [ + "id", + "last_msg_id" + ], + "columnsTo": [ + "chat_id", + "message_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "parent_message_id_fk": { + "name": "parent_message_id_fk", + "tableFrom": "chats", + "tableTo": "messages", + "columnsFrom": [ + "parent_chat_id", + "parent_message_id" + ], + "columnsTo": [ + "chat_id", + "message_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_ids_unique": { + "name": "user_ids_unique", + "nullsNotDistinct": false, + "columns": [ + "min_user_id", + "max_user_id" + ] + }, + "space_thread_number_unique": { + "name": "space_thread_number_unique", + "nullsNotDistinct": false, + "columns": [ + "space_id", + "thread_number" + ] + }, + "reply_thread_parent_unique": { + "name": "reply_thread_parent_unique", + "nullsNotDistinct": false, + "columns": [ + "parent_chat_id", + "parent_message_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "user_ids_check": { + "name": "user_ids_check", + "value": "\"chats\".\"min_user_id\" <= \"chats\".\"max_user_id\"" + }, + "parent_message_requires_parent_chat": { + "name": "parent_message_requires_parent_chat", + "value": "\"chats\".\"parent_message_id\" is null or \"chats\".\"parent_chat_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "global_id": { + "name": "global_id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "random_id": { + "name": "random_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "text_encrypted": { + "name": "text_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "text_iv": { + "name": "text_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "text_tag": { + "name": "text_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "entities_encrypted": { + "name": "entities_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "entities_iv": { + "name": "entities_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "entities_tag": { + "name": "entities_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "actions_encrypted": { + "name": "actions_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "actions_iv": { + "name": "actions_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "actions_tag": { + "name": "actions_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "system_message_encrypted": { + "name": "system_message_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "system_message_iv": { + "name": "system_message_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "system_message_tag": { + "name": "system_message_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "from_id": { + "name": "from_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "edit_date": { + "name": "edit_date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "rev": { + "name": "rev", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "block_content_id": { + "name": "block_content_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "reply_to_msg_id": { + "name": "reply_to_msg_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fwd_from_peer_user_id": { + "name": "fwd_from_peer_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fwd_from_peer_chat_id": { + "name": "fwd_from_peer_chat_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fwd_from_message_id": { + "name": "fwd_from_message_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fwd_from_sender_id": { + "name": "fwd_from_sender_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "grouped_id": { + "name": "grouped_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "video_id": { + "name": "video_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "voice_id": { + "name": "voice_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_sticker": { + "name": "is_sticker", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "has_link": { + "name": "has_link", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "counts_as_unread": { + "name": "counts_as_unread", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "msg_id_per_chat_index": { + "name": "msg_id_per_chat_index", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_chat_id_message_id_desc_idx": { + "name": "messages_chat_id_message_id_desc_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unread_count_index": { + "name": "unread_count_index", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_block_content_id_idx": { + "name": "messages_block_content_id_idx", + "columns": [ + { + "expression": "block_content_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_activity_date_from_id_idx": { + "name": "messages_activity_date_from_id_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"messages\".\"system_message_encrypted\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_chat_id_chats_id_fk": { + "name": "messages_chat_id_chats_id_fk", + "tableFrom": "messages", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_from_id_users_id_fk": { + "name": "messages_from_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "from_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_block_content_id_block_contents_id_fk": { + "name": "messages_block_content_id_block_contents_id_fk", + "tableFrom": "messages", + "tableTo": "block_contents", + "columnsFrom": [ + "block_content_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "messages_photo_id_photos_id_fk": { + "name": "messages_photo_id_photos_id_fk", + "tableFrom": "messages", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_video_id_videos_id_fk": { + "name": "messages_video_id_videos_id_fk", + "tableFrom": "messages", + "tableTo": "videos", + "columnsFrom": [ + "video_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_document_id_documents_id_fk": { + "name": "messages_document_id_documents_id_fk", + "tableFrom": "messages", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_voice_id_voices_id_fk": { + "name": "messages_voice_id_voices_id_fk", + "tableFrom": "messages", + "tableTo": "voices", + "columnsFrom": [ + "voice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_file_id_files_id_fk": { + "name": "messages_file_id_files_id_fk", + "tableFrom": "messages", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "msg_id_per_chat_unique": { + "name": "msg_id_per_chat_unique", + "nullsNotDistinct": false, + "columns": [ + "message_id", + "chat_id" + ] + }, + "random_id_per_sender_unique": { + "name": "random_id_per_sender_unique", + "nullsNotDistinct": false, + "columns": [ + "random_id", + "from_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.block_content_image_jobs": { + "name": "block_content_image_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "block_content_image_jobs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "content_id": { + "name": "content_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "expected_revision": { + "name": "expected_revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "block_path": { + "name": "block_path", + "type": "integer[]", + "primaryKey": false, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "hash_version": { + "name": "hash_version", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_encrypted": { + "name": "source_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_iv": { + "name": "source_iv", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_tag": { + "name": "source_tag", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "available_at": { + "name": "available_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lease_token": { + "name": "lease_token", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "staged_object_path_encrypted": { + "name": "staged_object_path_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "staged_object_path_iv": { + "name": "staged_object_path_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "staged_object_path_tag": { + "name": "staged_object_path_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "block_content_image_jobs_ready_idx": { + "name": "block_content_image_jobs_ready_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "block_content_image_jobs_revision_idx": { + "name": "block_content_image_jobs_revision_idx", + "columns": [ + { + "expression": "content_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expected_revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "block_content_image_jobs_path_idx": { + "name": "block_content_image_jobs_path_idx", + "columns": [ + { + "expression": "content_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "block_content_image_jobs_content_id_block_contents_id_fk": { + "name": "block_content_image_jobs_content_id_block_contents_id_fk", + "tableFrom": "block_content_image_jobs", + "tableTo": "block_contents", + "columnsFrom": [ + "content_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "block_content_image_jobs_photo_id_photos_id_fk": { + "name": "block_content_image_jobs_photo_id_photos_id_fk", + "tableFrom": "block_content_image_jobs", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "block_content_image_jobs_state_check": { + "name": "block_content_image_jobs_state_check", + "value": "\"block_content_image_jobs\".\"state\" in ('pending', 'processing', 'ready', 'failed', 'canceled')" + }, + "block_content_image_jobs_attempts_check": { + "name": "block_content_image_jobs_attempts_check", + "value": "\"block_content_image_jobs\".\"attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.block_contents": { + "name": "block_contents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "block_contents_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "payload_encrypted": { + "name": "payload_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "payload_iv": { + "name": "payload_iv", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "payload_tag": { + "name": "payload_tag", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.thread_graph_links": { + "name": "thread_graph_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "thread_graph_link_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "thread_graph_scope_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "from_chat_id": { + "name": "from_chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "from_message_global_id": { + "name": "from_message_global_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "from_message_id": { + "name": "from_message_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "from_message_revision": { + "name": "from_message_revision", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "entity_index": { + "name": "entity_index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "to_chat_id": { + "name": "to_chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "backlink_message_global_id": { + "name": "backlink_message_global_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "link_text_hmac": { + "name": "link_text_hmac", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "target_title_hmac": { + "name": "target_title_hmac", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "thread_graph_links_scope_to_chat_idx": { + "name": "thread_graph_links_scope_to_chat_idx", + "columns": [ + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "thread_graph_links_scope_from_chat_idx": { + "name": "thread_graph_links_scope_from_chat_idx", + "columns": [ + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "thread_graph_links_from_message_idx": { + "name": "thread_graph_links_from_message_idx", + "columns": [ + { + "expression": "from_message_global_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "thread_graph_links_backlink_message_idx": { + "name": "thread_graph_links_backlink_message_idx", + "columns": [ + { + "expression": "backlink_message_global_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "thread_graph_links_active_idx": { + "name": "thread_graph_links_active_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"thread_graph_links\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tgl_from_chat_fk": { + "name": "tgl_from_chat_fk", + "tableFrom": "thread_graph_links", + "tableTo": "chats", + "columnsFrom": [ + "from_chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tgl_from_message_fk": { + "name": "tgl_from_message_fk", + "tableFrom": "thread_graph_links", + "tableTo": "messages", + "columnsFrom": [ + "from_message_global_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tgl_to_chat_fk": { + "name": "tgl_to_chat_fk", + "tableFrom": "thread_graph_links", + "tableTo": "chats", + "columnsFrom": [ + "to_chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tgl_backlink_message_fk": { + "name": "tgl_backlink_message_fk", + "tableFrom": "thread_graph_links", + "tableTo": "messages", + "columnsFrom": [ + "backlink_message_global_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "thread_graph_links_dedupe_key_unique": { + "name": "thread_graph_links_dedupe_key_unique", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subthread_parent_messages": { + "name": "subthread_parent_messages", + "schema": "", + "columns": { + "child_chat_id": { + "name": "child_chat_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "parent_message_global_id": { + "name": "parent_message_global_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "subthread_parent_messages_child_chat_fk": { + "name": "subthread_parent_messages_child_chat_fk", + "tableFrom": "subthread_parent_messages", + "tableTo": "chats", + "columnsFrom": [ + "child_chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "subthread_parent_messages_parent_message_fk": { + "name": "subthread_parent_messages_parent_message_fk", + "tableFrom": "subthread_parent_messages", + "tableTo": "messages", + "columnsFrom": [ + "parent_message_global_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "subthread_parent_messages_parent_message_unique": { + "name": "subthread_parent_messages_parent_message_unique", + "nullsNotDistinct": false, + "columns": [ + "parent_message_global_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dialogs": { + "name": "dialogs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "peer_user_id": { + "name": "peer_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "read_inbox_max_id": { + "name": "read_inbox_max_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "read_outbox_max_id": { + "name": "read_outbox_max_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "draft": { + "name": "draft", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "sidebar_visible": { + "name": "sidebar_visible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "chat_list_hidden": { + "name": "chat_list_hidden", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "open": { + "name": "open", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "opened_date": { + "name": "opened_date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pinned_order": { + "name": "pinned_order", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unread_mark": { + "name": "unread_mark", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "notification_settings": { + "name": "notification_settings", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "follow_mode": { + "name": "follow_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "collapsed_max_id": { + "name": "collapsed_max_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "translation_enabled": { + "name": "translation_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dialogs_user_id_chat_id_idx": { + "name": "dialogs_user_id_chat_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dialogs_user_id_peer_user_id_idx": { + "name": "dialogs_user_id_peer_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "peer_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dialogs_user_id_order_idx": { + "name": "dialogs_user_id_order_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dialogs_user_id_pinned_order_idx": { + "name": "dialogs_user_id_pinned_order_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pinned_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dialogs_user_id_folder_id_order_idx": { + "name": "dialogs_user_id_folder_id_order_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dialogs_user_id_users_id_fk": { + "name": "dialogs_user_id_users_id_fk", + "tableFrom": "dialogs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dialogs_chat_id_chats_id_fk": { + "name": "dialogs_chat_id_chats_id_fk", + "tableFrom": "dialogs", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dialogs_peer_user_id_users_id_fk": { + "name": "dialogs_peer_user_id_users_id_fk", + "tableFrom": "dialogs", + "tableTo": "users", + "columnsFrom": [ + "peer_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dialogs_space_id_spaces_id_fk": { + "name": "dialogs_space_id_spaces_id_fk", + "tableFrom": "dialogs", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dialogs_folder_id_user_id_dialog_folders_id_user_id_fk": { + "name": "dialogs_folder_id_user_id_dialog_folders_id_user_id_fk", + "tableFrom": "dialogs", + "tableTo": "dialog_folders", + "columnsFrom": [ + "folder_id", + "user_id" + ], + "columnsTo": [ + "id", + "user_id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_id_user_id_unique": { + "name": "chat_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "chat_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_acknowledgements": { + "name": "chat_acknowledgements", + "schema": "", + "columns": { + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "max_id": { + "name": "max_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cleared": { + "name": "cleared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "chat_acknowledgements_user_id_idx": { + "name": "chat_acknowledgements_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_acknowledgements_chat_id_chats_id_fk": { + "name": "chat_acknowledgements_chat_id_chats_id_fk", + "tableFrom": "chat_acknowledgements", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_acknowledgements_user_id_users_id_fk": { + "name": "chat_acknowledgements_user_id_users_id_fk", + "tableFrom": "chat_acknowledgements", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_acknowledgements_chat_id_user_id_pk": { + "name": "chat_acknowledgements_chat_id_user_id_pk", + "columns": [ + "chat_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dialog_folders": { + "name": "dialog_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_order": { + "name": "pinned_order", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dialog_folders_user_id_order_idx": { + "name": "dialog_folders_user_id_order_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dialog_folders_user_id_pinned_order_idx": { + "name": "dialog_folders_user_id_pinned_order_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pinned_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dialog_folders_user_id_users_id_fk": { + "name": "dialog_folders_user_id_users_id_fk", + "tableFrom": "dialog_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dialog_folders_id_user_id_unique": { + "name": "dialog_folders_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_id_reservation": { + "name": "chat_id_reservation", + "schema": "", + "columns": { + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_id_reservation_user_id_users_id_fk": { + "name": "chat_id_reservation_user_id_users_id_fk", + "tableFrom": "chat_id_reservation", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reactions": { + "name": "reactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "reactions_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "message_id": { + "name": "message_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emoji_hash": { + "name": "emoji_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reactions_chat_id_chats_id_fk": { + "name": "reactions_chat_id_chats_id_fk", + "tableFrom": "reactions", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reactions_user_id_users_id_fk": { + "name": "reactions_user_id_users_id_fk", + "tableFrom": "reactions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_id_fk": { + "name": "message_id_fk", + "tableFrom": "reactions", + "tableTo": "messages", + "columnsFrom": [ + "chat_id", + "message_id" + ], + "columnsTo": [ + "chat_id", + "message_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reactions_identity_emoji_hash_unique": { + "name": "reactions_identity_emoji_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "chat_id", + "message_id", + "user_id", + "emoji_hash" + ] + }, + "unique_reaction_per_emoji": { + "name": "unique_reaction_per_emoji", + "nullsNotDistinct": false, + "columns": [ + "chat_id", + "message_id", + "user_id", + "emoji" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "file_unique_id": { + "name": "file_unique_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path_encrypted": { + "name": "path_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "path_iv": { + "name": "path_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "path_tag": { + "name": "path_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cdn": { + "name": "cdn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "file_type": { + "name": "file_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "video_duration": { + "name": "video_duration", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "thumb_size": { + "name": "thumb_size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumb_for": { + "name": "thumb_for", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "bytes_encrypted": { + "name": "bytes_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "bytes_iv": { + "name": "bytes_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "bytes_tag": { + "name": "bytes_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "name_encrypted": { + "name": "name_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "name_iv": { + "name": "name_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "name_tag": { + "name": "name_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "files_user_id_users_id_fk": { + "name": "files_user_id_users_id_fk", + "tableFrom": "files", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "files_thumb_for_files_id_fk": { + "name": "files_thumb_for_files_id_fk", + "tableFrom": "files", + "tableTo": "files", + "columnsFrom": [ + "thumb_for" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "files_file_unique_id_unique": { + "name": "files_file_unique_id_unique", + "nullsNotDistinct": false, + "columns": [ + "file_unique_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_oauth_states": { + "name": "integration_oauth_states", + "schema": "", + "columns": { + "state_hash": { + "name": "state_hash", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "callback_scheme": { + "name": "callback_scheme", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "integration_oauth_states_user": { + "name": "integration_oauth_states_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "integration_oauth_states_expires": { + "name": "integration_oauth_states_expires", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_oauth_states_user_id_users_id_fk": { + "name": "integration_oauth_states_user_id_users_id_fk", + "tableFrom": "integration_oauth_states", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_oauth_states_space_id_spaces_id_fk": { + "name": "integration_oauth_states_space_id_spaces_id_fk", + "tableFrom": "integration_oauth_states", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integrations": { + "name": "integrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "access_token_iv": { + "name": "access_token_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "access_token_tag": { + "name": "access_token_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "notion_database_id": { + "name": "notion_database_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linear_team_id": { + "name": "linear_team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "integrations_space_provider_unique": { + "name": "integrations_space_provider_unique", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integrations_user_id_users_id_fk": { + "name": "integrations_user_id_users_id_fk", + "tableFrom": "integrations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "integrations_space_id_spaces_id_fk": { + "name": "integrations_space_id_spaces_id_fk", + "tableFrom": "integrations", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_tokens": { + "name": "bot_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_encrypted": { + "name": "token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "token_iv": { + "name": "token_iv", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "token_tag": { + "name": "token_tag", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_tokens_bot_user_id_unique": { + "name": "bot_tokens_bot_user_id_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_tokens_bot_user_id_users_id_fk": { + "name": "bot_tokens_bot_user_id_users_id_fk", + "tableFrom": "bot_tokens", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "bot_tokens_session_id_sessions_id_fk": { + "name": "bot_tokens_session_id_sessions_id_fk", + "tableFrom": "bot_tokens", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_commands": { + "name": "bot_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_commands_bot_user_id_command_unique": { + "name": "bot_commands_bot_user_id_command_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "command", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_commands_bot_user_id_users_id_fk": { + "name": "bot_commands_bot_user_id_users_id_fk", + "tableFrom": "bot_commands", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_skills": { + "name": "bot_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_skills_bot_user_id_key_unique": { + "name": "bot_skills_bot_user_id_key_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_skills_bot_user_id_users_id_fk": { + "name": "bot_skills_bot_user_id_users_id_fk", + "tableFrom": "bot_skills", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_agents": { + "name": "bot_agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "emoji": { + "name": "emoji", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skill_key": { + "name": "skill_key", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "instructions_encrypted": { + "name": "instructions_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_agents_bot_user_id_idx": { + "name": "bot_agents_bot_user_id_idx", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_agents_bot_user_id_users_id_fk": { + "name": "bot_agents_bot_user_id_users_id_fk", + "tableFrom": "bot_agents", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_capabilities": { + "name": "bot_capabilities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_capabilities_bot_user_id_kind_unique": { + "name": "bot_capabilities_bot_user_id_kind_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_capabilities_bot_user_id_users_id_fk": { + "name": "bot_capabilities_bot_user_id_users_id_fk", + "tableFrom": "bot_capabilities", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_avatar_assets": { + "name": "bot_avatar_assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "bot_avatar_assets_bot_user_id_users_id_fk": { + "name": "bot_avatar_assets_bot_user_id_users_id_fk", + "tableFrom": "bot_avatar_assets", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "bot_avatar_assets_file_id_files_id_fk": { + "name": "bot_avatar_assets_file_id_files_id_fk", + "tableFrom": "bot_avatar_assets", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "bot_avatar_assets_bot_user_id_unique": { + "name": "bot_avatar_assets_bot_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "bot_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_message_routes": { + "name": "bot_message_routes", + "schema": "", + "columns": { + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "activation_reason": { + "name": "activation_reason", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "bot_message_routes_identity_unique": { + "name": "bot_message_routes_identity_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_message_routes_message_idx": { + "name": "bot_message_routes_message_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_message_routes_expiry_idx": { + "name": "bot_message_routes_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_message_routes_bot_user_id_users_id_fk": { + "name": "bot_message_routes_bot_user_id_users_id_fk", + "tableFrom": "bot_message_routes", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_update_streams": { + "name": "bot_update_streams", + "schema": "", + "columns": { + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "next_update_id": { + "name": "next_update_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "acknowledged_update_id": { + "name": "acknowledged_update_id", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allowed_updates": { + "name": "allowed_updates", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "message_trigger": { + "name": "message_trigger", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'mentions'" + }, + "webhook_url": { + "name": "webhook_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "webhook_secret_encrypted": { + "name": "webhook_secret_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "poll_lease_token": { + "name": "poll_lease_token", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "poll_lease_expires_at": { + "name": "poll_lease_expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "config_generation": { + "name": "config_generation", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pending_update_count": { + "name": "pending_update_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pending_payload_bytes": { + "name": "pending_payload_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "delivery_locked_at": { + "name": "delivery_locked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropped_update_count": { + "name": "dropped_update_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "bot_update_streams_bot_user_id_users_id_fk": { + "name": "bot_update_streams_bot_user_id_users_id_fk", + "tableFrom": "bot_update_streams", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_updates": { + "name": "bot_updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "bot_updates_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "update_id": { + "name": "update_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "update_type": { + "name": "update_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "payload_encrypted": { + "name": "payload_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "payload_byte_count": { + "name": "payload_byte_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_event_id": { + "name": "source_event_id", + "type": "varchar(160)", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "claim_generation": { + "name": "claim_generation", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "claim_expires_at": { + "name": "claim_expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_updates_bot_update_id_unique": { + "name": "bot_updates_bot_update_id_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "update_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_updates_bot_source_event_unique": { + "name": "bot_updates_bot_source_event_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_updates_pending_idx": { + "name": "bot_updates_pending_idx", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "update_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_updates_expiry_idx": { + "name": "bot_updates_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_updates_delivery_idx": { + "name": "bot_updates_delivery_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claim_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "update_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_updates_bot_user_id_users_id_fk": { + "name": "bot_updates_bot_user_id_users_id_fk", + "tableFrom": "bot_updates", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "documents_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "file_name": { + "name": "file_name", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "file_name_iv": { + "name": "file_name_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "file_name_tag": { + "name": "file_name_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "documents_file_id_files_id_fk": { + "name": "documents_file_id_files_id_fk", + "tableFrom": "documents", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_photo_id_photos_id_fk": { + "name": "documents_photo_id_photos_id_fk", + "tableFrom": "documents", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.photo_sizes": { + "name": "photo_sizes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "photo_sizes_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "photo_sizes_photo_id_idx": { + "name": "photo_sizes_photo_id_idx", + "columns": [ + { + "expression": "photo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "photo_sizes_file_id_files_id_fk": { + "name": "photo_sizes_file_id_files_id_fk", + "tableFrom": "photo_sizes", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "photo_sizes_photo_id_photos_id_fk": { + "name": "photo_sizes_photo_id_photos_id_fk", + "tableFrom": "photo_sizes", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.photos": { + "name": "photos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "photos_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "stripped": { + "name": "stripped", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "stripped_iv": { + "name": "stripped_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "stripped_tag": { + "name": "stripped_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.videos": { + "name": "videos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "videos_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_animated": { + "name": "is_animated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "has_audio": { + "name": "has_audio", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "videos_file_id_files_id_fk": { + "name": "videos_file_id_files_id_fk", + "tableFrom": "videos", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "videos_photo_id_photos_id_fk": { + "name": "videos_photo_id_photos_id_fk", + "tableFrom": "videos", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.voices": { + "name": "voices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "voices_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "waveform": { + "name": "waveform", + "type": "bytea", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "voices_file_id_files_id_fk": { + "name": "voices_file_id_files_id_fk", + "tableFrom": "voices", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_tasks": { + "name": "external_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "external_tasks_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "application": { + "name": "application", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_user_id": { + "name": "assigned_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "connector_space_id": { + "name": "connector_space_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "source_message_id": { + "name": "source_message_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "number": { + "name": "number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_iv": { + "name": "title_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_tag": { + "name": "title_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_tasks_provider_user_source_space_unique": { + "name": "external_tasks_provider_user_source_space_unique", + "columns": [ + { + "expression": "source_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assigned_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connector_space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_tasks_assigned_user_id_users_id_fk": { + "name": "external_tasks_assigned_user_id_users_id_fk", + "tableFrom": "external_tasks", + "tableTo": "users", + "columnsFrom": [ + "assigned_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "external_tasks_connector_space_id_spaces_id_fk": { + "name": "external_tasks_connector_space_id_spaces_id_fk", + "tableFrom": "external_tasks", + "tableTo": "spaces", + "columnsFrom": [ + "connector_space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "external_tasks_source_message_id_messages_global_id_fk": { + "name": "external_tasks_source_message_id_messages_global_id_fk", + "tableFrom": "external_tasks", + "tableTo": "messages", + "columnsFrom": [ + "source_message_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_attachments": { + "name": "message_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "message_attachments_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "message_id": { + "name": "message_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "external_task_id": { + "name": "external_task_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "url_preview_id": { + "name": "url_preview_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "message_attachments_message_id_idx": { + "name": "message_attachments_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_attachments_message_id_messages_global_id_fk": { + "name": "message_attachments_message_id_messages_global_id_fk", + "tableFrom": "message_attachments", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_attachments_external_task_id_external_tasks_id_fk": { + "name": "message_attachments_external_task_id_external_tasks_id_fk", + "tableFrom": "message_attachments", + "tableTo": "external_tasks", + "columnsFrom": [ + "external_task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "message_attachments_url_preview_id_url_preview_id_fk": { + "name": "message_attachments_url_preview_id_url_preview_id_fk", + "tableFrom": "message_attachments", + "tableTo": "url_preview", + "columnsFrom": [ + "url_preview_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.url_preview": { + "name": "url_preview", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "url_preview_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "url": { + "name": "url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "url_iv": { + "name": "url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "url_tag": { + "name": "url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "site_name": { + "name": "site_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'generic'" + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_iv": { + "name": "title_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_tag": { + "name": "title_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description_iv": { + "name": "description_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description_tag": { + "name": "description_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_iv": { + "name": "author_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_tag": { + "name": "author_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "media_kind": { + "name": "media_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "author_photo_id": { + "name": "author_photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "video_id": { + "name": "video_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_id": { + "name": "cache_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "external_url": { + "name": "external_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_url_iv": { + "name": "external_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_url_tag": { + "name": "external_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_mime_type": { + "name": "external_mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_width": { + "name": "external_width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_height": { + "name": "external_height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_duration": { + "name": "external_duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_url": { + "name": "embed_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_url_iv": { + "name": "embed_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_url_tag": { + "name": "embed_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_type": { + "name": "embed_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embed_width": { + "name": "embed_width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_height": { + "name": "embed_height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_duration": { + "name": "embed_duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "has_large_media": { + "name": "has_large_media", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_large_media": { + "name": "show_large_media", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "url_preview_photo_id_photos_id_fk": { + "name": "url_preview_photo_id_photos_id_fk", + "tableFrom": "url_preview", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_author_photo_id_photos_id_fk": { + "name": "url_preview_author_photo_id_photos_id_fk", + "tableFrom": "url_preview", + "tableTo": "photos", + "columnsFrom": [ + "author_photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_video_id_videos_id_fk": { + "name": "url_preview_video_id_videos_id_fk", + "tableFrom": "url_preview", + "tableTo": "videos", + "columnsFrom": [ + "video_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_document_id_documents_id_fk": { + "name": "url_preview_document_id_documents_id_fk", + "tableFrom": "url_preview", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_cache_id_url_preview_cache_id_fk": { + "name": "url_preview_cache_id_url_preview_cache_id_fk", + "tableFrom": "url_preview", + "tableTo": "url_preview_cache", + "columnsFrom": [ + "cache_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.url_preview_cache": { + "name": "url_preview_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "url_preview_cache_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "url_hash": { + "name": "url_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "hash_version": { + "name": "hash_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "url": { + "name": "url", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "url_iv": { + "name": "url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "url_tag": { + "name": "url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "final_url": { + "name": "final_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "final_url_iv": { + "name": "final_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "final_url_tag": { + "name": "final_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'generic'" + }, + "site_name": { + "name": "site_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_iv": { + "name": "title_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_tag": { + "name": "title_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description_iv": { + "name": "description_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description_tag": { + "name": "description_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_iv": { + "name": "author_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_tag": { + "name": "author_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "image_url_hash": { + "name": "image_url_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "image_url_iv": { + "name": "image_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "image_url_tag": { + "name": "image_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_image_url_hash": { + "name": "author_image_url_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_image_url": { + "name": "author_image_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_image_url_iv": { + "name": "author_image_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_image_url_tag": { + "name": "author_image_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "media_kind": { + "name": "media_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "author_photo_id": { + "name": "author_photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "video_id": { + "name": "video_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "external_url": { + "name": "external_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_url_iv": { + "name": "external_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_url_tag": { + "name": "external_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_mime_type": { + "name": "external_mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_width": { + "name": "external_width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_height": { + "name": "external_height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_duration": { + "name": "external_duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_url": { + "name": "embed_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_url_iv": { + "name": "embed_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_url_tag": { + "name": "embed_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_type": { + "name": "embed_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embed_width": { + "name": "embed_width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_height": { + "name": "embed_height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_duration": { + "name": "embed_duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "has_large_media": { + "name": "has_large_media", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_large_media": { + "name": "show_large_media", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "url_preview_cache_url_hash_unique": { + "name": "url_preview_cache_url_hash_unique", + "columns": [ + { + "expression": "url_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "url_preview_cache_image_url_hash_idx": { + "name": "url_preview_cache_image_url_hash_idx", + "columns": [ + { + "expression": "image_url_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "url_preview_cache_author_image_url_hash_idx": { + "name": "url_preview_cache_author_image_url_hash_idx", + "columns": [ + { + "expression": "author_image_url_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "url_preview_cache_expires_at_idx": { + "name": "url_preview_cache_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "url_preview_cache_last_used_at_idx": { + "name": "url_preview_cache_last_used_at_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "url_preview_cache_photo_id_photos_id_fk": { + "name": "url_preview_cache_photo_id_photos_id_fk", + "tableFrom": "url_preview_cache", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_cache_author_photo_id_photos_id_fk": { + "name": "url_preview_cache_author_photo_id_photos_id_fk", + "tableFrom": "url_preview_cache", + "tableTo": "photos", + "columnsFrom": [ + "author_photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_cache_video_id_videos_id_fk": { + "name": "url_preview_cache_video_id_videos_id_fk", + "tableFrom": "url_preview_cache", + "tableTo": "videos", + "columnsFrom": [ + "video_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_cache_document_id_documents_id_fk": { + "name": "url_preview_cache_document_id_documents_id_fk", + "tableFrom": "url_preview_cache", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.space_url_preview_exclusions": { + "name": "space_url_preview_exclusions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "space_url_preview_exclusions_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path_prefix": { + "name": "path_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "space_url_preview_exclusions_space_host_path_unique": { + "name": "space_url_preview_exclusions_space_host_path_unique", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "space_url_preview_exclusions_space_host_idx": { + "name": "space_url_preview_exclusions_space_host_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "space_url_preview_exclusions_created_by_idx": { + "name": "space_url_preview_exclusions_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "space_url_preview_exclusions_space_id_spaces_id_fk": { + "name": "space_url_preview_exclusions_space_id_spaces_id_fk", + "tableFrom": "space_url_preview_exclusions", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "space_url_preview_exclusions_created_by_users_id_fk": { + "name": "space_url_preview_exclusions_created_by_users_id_fk", + "tableFrom": "space_url_preview_exclusions", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_participant_groups": { + "name": "chat_participant_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_participant_groups_chat_group_unique": { + "name": "chat_participant_groups_chat_group_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_participant_groups_chat_id_idx": { + "name": "chat_participant_groups_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_participant_groups_group_id_idx": { + "name": "chat_participant_groups_group_id_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_participant_groups_chat_id_chats_id_fk": { + "name": "chat_participant_groups_chat_id_chats_id_fk", + "tableFrom": "chat_participant_groups", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_participant_groups_group_id_user_groups_id_fk": { + "name": "chat_participant_groups_group_id_user_groups_id_fk", + "tableFrom": "chat_participant_groups", + "tableTo": "user_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_group_members": { + "name": "user_group_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_group_members_group_user_unique": { + "name": "user_group_members_group_user_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_group_members_group_id_idx": { + "name": "user_group_members_group_id_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_group_members_user_id_idx": { + "name": "user_group_members_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_group_members_group_id_user_groups_id_fk": { + "name": "user_group_members_group_id_user_groups_id_fk", + "tableFrom": "user_group_members", + "tableTo": "user_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_group_members_user_id_users_id_fk": { + "name": "user_group_members_user_id_users_id_fk", + "tableFrom": "user_group_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_groups": { + "name": "user_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(80)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_groups_space_name_unique": { + "name": "user_groups_space_name_unique", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_groups_space_id_idx": { + "name": "user_groups_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_groups_created_by_idx": { + "name": "user_groups_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_groups_space_id_spaces_id_fk": { + "name": "user_groups_space_id_spaces_id_fk", + "tableFrom": "user_groups", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_groups_created_by_users_id_fk": { + "name": "user_groups_created_by_users_id_fk", + "tableFrom": "user_groups", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_translations": { + "name": "message_translations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "message_translations_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "msg_rev": { + "name": "msg_rev", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "message_id": { + "name": "message_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "translation": { + "name": "translation", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "translation_iv": { + "name": "translation_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "translation_tag": { + "name": "translation_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "entities": { + "name": "entities", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "message_translations_chat_id_chats_id_fk": { + "name": "message_translations_chat_id_chats_id_fk", + "tableFrom": "message_translations", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_id_message_id_fk": { + "name": "chat_id_message_id_fk", + "tableFrom": "message_translations", + "tableTo": "messages", + "columnsFrom": [ + "chat_id", + "message_id" + ], + "columnsTo": [ + "chat_id", + "message_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_message_chat_language": { + "name": "unique_message_chat_language", + "nullsNotDistinct": false, + "columns": [ + "message_id", + "chat_id", + "language" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_settings": { + "name": "user_settings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "general_encrypted": { + "name": "general_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "general_iv": { + "name": "general_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "general_tag": { + "name": "general_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_settings_user_id_users_id_fk": { + "name": "user_settings_user_id_users_id_fk", + "tableFrom": "user_settings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.space_settings": { + "name": "space_settings", + "schema": "", + "columns": { + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "space_settings_space_id_spaces_id_fk": { + "name": "space_settings_space_id_spaces_id_fk", + "tableFrom": "space_settings", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.grid_presence": { + "name": "grid_presence", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "owner_session_id": { + "name": "owner_session_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "microphone_enabled": { + "name": "microphone_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "microphone_revision": { + "name": "microphone_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "media_membership_id": { + "name": "media_membership_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "call_id": { + "name": "call_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "grid_presence_room_id_idx": { + "name": "grid_presence_room_id_idx", + "columns": [ + { + "expression": "room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_presence_owner_session_id_idx": { + "name": "grid_presence_owner_session_id_idx", + "columns": [ + { + "expression": "owner_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_presence_lease_expires_at_idx": { + "name": "grid_presence_lease_expires_at_idx", + "columns": [ + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "grid_presence_user_id_users_id_fk": { + "name": "grid_presence_user_id_users_id_fk", + "tableFrom": "grid_presence", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grid_presence_room_id_grid_rooms_id_fk": { + "name": "grid_presence_room_id_grid_rooms_id_fk", + "tableFrom": "grid_presence", + "tableTo": "grid_rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grid_presence_owner_session_id_sessions_id_fk": { + "name": "grid_presence_owner_session_id_sessions_id_fk", + "tableFrom": "grid_presence", + "tableTo": "sessions", + "columnsFrom": [ + "owner_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "grid_presence_microphone_revision_check": { + "name": "grid_presence_microphone_revision_check", + "value": "\"grid_presence\".\"microphone_revision\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.grid_provider_effects": { + "name": "grid_provider_effects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "deduplication_key": { + "name": "deduplication_key", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "connection_generation": { + "name": "connection_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_target": { + "name": "provider_target", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "participant_identity": { + "name": "participant_identity", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "available_at": { + "name": "available_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "claim_token": { + "name": "claim_token", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "claim_expires_at": { + "name": "claim_expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "grid_provider_effects_deduplication_key_unique": { + "name": "grid_provider_effects_deduplication_key_unique", + "columns": [ + { + "expression": "deduplication_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_provider_effects_ready_idx": { + "name": "grid_provider_effects_ready_idx", + "columns": [ + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claim_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "grid_provider_effects_kind_check": { + "name": "grid_provider_effects_kind_check", + "value": "\"grid_provider_effects\".\"kind\" in ('close_connection', 'revoke_participant')" + }, + "grid_provider_effects_connection_generation_check": { + "name": "grid_provider_effects_connection_generation_check", + "value": "\"grid_provider_effects\".\"connection_generation\" >= 0" + }, + "grid_provider_effects_attempts_check": { + "name": "grid_provider_effects_attempts_check", + "value": "\"grid_provider_effects\".\"attempts\" >= 0" + }, + "grid_provider_effects_participant_check": { + "name": "grid_provider_effects_participant_check", + "value": "(\"grid_provider_effects\".\"kind\" = 'revoke_participant' and \"grid_provider_effects\".\"user_id\" is not null) or (\"grid_provider_effects\".\"kind\" = 'close_connection' and \"grid_provider_effects\".\"user_id\" is null)" + } + }, + "isRLSEnabled": false + }, + "public.grid_rooms": { + "name": "grid_rooms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "connection_generation": { + "name": "connection_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "connection_started_at": { + "name": "connection_started_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "grid_rooms_space_id_idx": { + "name": "grid_rooms_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_rooms_created_by_user_id_idx": { + "name": "grid_rooms_created_by_user_id_idx", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_rooms_space_title_unique": { + "name": "grid_rooms_space_title_unique", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"title\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"grid_rooms\".\"title\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "grid_rooms_space_id_spaces_id_fk": { + "name": "grid_rooms_space_id_spaces_id_fk", + "tableFrom": "grid_rooms", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grid_rooms_created_by_user_id_users_id_fk": { + "name": "grid_rooms_created_by_user_id_users_id_fk", + "tableFrom": "grid_rooms", + "tableTo": "users", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "grid_rooms_connection_generation_check": { + "name": "grid_rooms_connection_generation_check", + "value": "\"grid_rooms\".\"connection_generation\" >= 0" + }, + "grid_rooms_title_not_empty_check": { + "name": "grid_rooms_title_not_empty_check", + "value": "\"grid_rooms\".\"title\" is null or length(\"grid_rooms\".\"title\") > 0" + } + }, + "isRLSEnabled": false + }, + "public.updates": { + "name": "updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "updates_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "bucket": { + "name": "bucket", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "bytea", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "updates_bucket_idx": { + "name": "updates_bucket_idx", + "columns": [ + { + "expression": "bucket", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "updates_date_idx": { + "name": "updates_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "updates_unique": { + "name": "updates_unique", + "nullsNotDistinct": false, + "columns": [ + "bucket", + "entity_id", + "seq" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_tokens": { + "name": "oauth_access_tokens", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_access_tokens_grant_idx": { + "name": "oauth_access_tokens_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_tokens_expiry_idx": { + "name": "oauth_access_tokens_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_access_tokens_grant_id_oauth_grants_id_fk": { + "name": "oauth_access_tokens_grant_id_oauth_grants_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_auth_codes": { + "name": "oauth_auth_codes", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_auth_codes_expiry_idx": { + "name": "oauth_auth_codes_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_auth_codes_grant_idx": { + "name": "oauth_auth_codes_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_auth_codes_used_idx": { + "name": "oauth_auth_codes_used_idx", + "columns": [ + { + "expression": "used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_auth_codes_grant_id_oauth_grants_id_fk": { + "name": "oauth_auth_codes_grant_id_oauth_grants_id_fk", + "tableFrom": "oauth_auth_codes", + "tableTo": "oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "oauth_auth_codes_client_id_oauth_clients_client_id_fk": { + "name": "oauth_auth_codes_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_auth_codes", + "tableTo": "oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_auth_requests": { + "name": "oauth_auth_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://mcp.inline.chat'" + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "csrf_token": { + "name": "csrf_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "challenge_token": { + "name": "challenge_token", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "inline_user_id": { + "name": "inline_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_method": { + "name": "auth_method", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "inline_token_encrypted": { + "name": "inline_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_auth_requests_expiry_idx": { + "name": "oauth_auth_requests_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_auth_requests_client_idx": { + "name": "oauth_auth_requests_client_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_auth_requests_challenge_unique": { + "name": "oauth_auth_requests_challenge_unique", + "columns": [ + { + "expression": "challenge_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_auth_requests_client_id_oauth_clients_client_id_fk": { + "name": "oauth_auth_requests_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_auth_requests", + "tableTo": "oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "oauth_auth_requests_inline_user_id_users_id_fk": { + "name": "oauth_auth_requests_inline_user_id_users_id_fk", + "tableFrom": "oauth_auth_requests", + "tableTo": "users", + "columnsFrom": [ + "inline_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_clients": { + "name": "oauth_clients", + "schema": "", + "columns": { + "client_id": { + "name": "client_id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "redirect_uris_json": { + "name": "redirect_uris_json", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oauth_clients_date_idx": { + "name": "oauth_clients_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_grants": { + "name": "oauth_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "inline_user_id": { + "name": "inline_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://mcp.inline.chat'" + }, + "space_ids_json": { + "name": "space_ids_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "allow_dms": { + "name": "allow_dms", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_home_threads": { + "name": "allow_home_threads", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inline_token_encrypted": { + "name": "inline_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_grants_client_idx": { + "name": "oauth_grants_client_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_grants_inline_user_idx": { + "name": "oauth_grants_inline_user_idx", + "columns": [ + { + "expression": "inline_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_grants_revoked_idx": { + "name": "oauth_grants_revoked_idx", + "columns": [ + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_grants_client_id_oauth_clients_client_id_fk": { + "name": "oauth_grants_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_grants", + "tableTo": "oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "oauth_grants_inline_user_id_users_id_fk": { + "name": "oauth_grants_inline_user_id_users_id_fk", + "tableFrom": "oauth_grants", + "tableTo": "users", + "columnsFrom": [ + "inline_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_refresh_tokens": { + "name": "oauth_refresh_tokens", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "replaced_by_hash": { + "name": "replaced_by_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_refresh_tokens_grant_idx": { + "name": "oauth_refresh_tokens_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_tokens_expiry_idx": { + "name": "oauth_refresh_tokens_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_tokens_revoked_idx": { + "name": "oauth_refresh_tokens_revoked_idx", + "columns": [ + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_refresh_tokens_grant_id_oauth_grants_id_fk": { + "name": "oauth_refresh_tokens_grant_id_oauth_grants_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_event_subscriptions": { + "name": "mcp_event_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(80)", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(80)", + "primaryKey": false, + "notNull": true + }, + "selector": { + "name": "selector", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "callback_url": { + "name": "callback_url", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "secret_encrypted": { + "name": "secret_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "previous_secret_encrypted": { + "name": "previous_secret_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "previous_secret_until": { + "name": "previous_secret_until", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "cursor_seq": { + "name": "cursor_seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "gap_seq": { + "name": "gap_seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pending_encrypted": { + "name": "pending_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "pending_seq": { + "name": "pending_seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lease_owner": { + "name": "lease_owner", + "type": "varchar(36)", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stopped": { + "name": "stopped", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_events_due_idx": { + "name": "mcp_events_due_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_event_subscriptions\".\"stopped\" = false AND \"mcp_event_subscriptions\".\"gap_seq\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_events_grant_idx": { + "name": "mcp_events_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_events_expiry_idx": { + "name": "mcp_events_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_event_subscriptions_grant_id_oauth_grants_id_fk": { + "name": "mcp_event_subscriptions_grant_id_oauth_grants_id_fk", + "tableFrom": "mcp_event_subscriptions", + "tableTo": "oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_events_cursor_nonnegative": { + "name": "mcp_events_cursor_nonnegative", + "value": "\"mcp_event_subscriptions\".\"cursor_seq\" >= 0 AND (\"mcp_event_subscriptions\".\"gap_seq\" IS NULL OR \"mcp_event_subscriptions\".\"gap_seq\" >= \"mcp_event_subscriptions\".\"cursor_seq\")" + }, + "mcp_events_pending_pair": { + "name": "mcp_events_pending_pair", + "value": "(\"mcp_event_subscriptions\".\"pending_encrypted\" IS NULL) = (\"mcp_event_subscriptions\".\"pending_seq\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.account_identities": { + "name": "account_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "subject_hash": { + "name": "subject_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "account_identities_provider_subject_unique": { + "name": "account_identities_provider_subject_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_identities_user_idx": { + "name": "account_identities_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_identities_user_id_users_id_fk": { + "name": "account_identities_user_id_users_id_fk", + "tableFrom": "account_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_auth_attempts": { + "name": "provider_auth_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "purpose": { + "name": "purpose", + "type": "provider_auth_purpose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "provider_auth_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending_provider'" + }, + "state_hash": { + "name": "state_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "nonce_hash": { + "name": "nonce_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "nonce_encrypted": { + "name": "nonce_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "pkce_verifier_encrypted": { + "name": "pkce_verifier_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "continuation_hash": { + "name": "continuation_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "subject_hash": { + "name": "subject_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "pending_profile_encrypted": { + "name": "pending_profile_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "confirmation_email": { + "name": "confirmation_email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "challenge_token": { + "name": "challenge_token", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "app_callback_scheme": { + "name": "app_callback_scheme", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "app_code_challenge": { + "name": "app_code_challenge", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "oauth_auth_request_id": { + "name": "oauth_auth_request_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "login_transaction_id": { + "name": "login_transaction_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "client": { + "name": "client", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "inline_user_id": { + "name": "inline_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "inline_token_encrypted": { + "name": "inline_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "ticket_hash": { + "name": "ticket_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "provider_auth_attempts_state_unique": { + "name": "provider_auth_attempts_state_unique", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_auth_attempts_ticket_unique": { + "name": "provider_auth_attempts_ticket_unique", + "columns": [ + { + "expression": "ticket_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_auth_attempts_expiry_idx": { + "name": "provider_auth_attempts_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_auth_attempts_oauth_request_idx": { + "name": "provider_auth_attempts_oauth_request_idx", + "columns": [ + { + "expression": "oauth_auth_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_auth_attempts_inline_user_idx": { + "name": "provider_auth_attempts_inline_user_idx", + "columns": [ + { + "expression": "inline_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_auth_attempts_login_transaction_idx": { + "name": "provider_auth_attempts_login_transaction_idx", + "columns": [ + { + "expression": "login_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_auth_attempts_inline_user_id_users_id_fk": { + "name": "provider_auth_attempts_inline_user_id_users_id_fk", + "tableFrom": "provider_auth_attempts", + "tableTo": "users", + "columnsFrom": [ + "inline_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "provider_auth_attempts_oauth_request_fk": { + "name": "provider_auth_attempts_oauth_request_fk", + "tableFrom": "provider_auth_attempts", + "tableTo": "oauth_auth_requests", + "columnsFrom": [ + "oauth_auth_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "provider_auth_attempts_login_transaction_fk": { + "name": "provider_auth_attempts_login_transaction_fk", + "tableFrom": "provider_auth_attempts", + "tableTo": "login_transactions", + "columnsFrom": [ + "login_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.login_transactions": { + "name": "login_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "capability_hash": { + "name": "capability_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "browser_csrf_hash": { + "name": "browser_csrf_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "target_kind": { + "name": "target_kind", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "inline_protocol_auth_key_id": { + "name": "inline_protocol_auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "oauth_auth_request_id": { + "name": "oauth_auth_request_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "native_app_auth_request_id": { + "name": "native_app_auth_request_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "verification_code": { + "name": "verification_code", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "pending_identifier": { + "name": "pending_identifier", + "type": "varchar(320)", + "primaryKey": false, + "notNull": false + }, + "challenge_token": { + "name": "challenge_token", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "client": { + "name": "client", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_method": { + "name": "auth_method", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "login_transactions_capability_unique": { + "name": "login_transactions_capability_unique", + "columns": [ + { + "expression": "capability_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_transactions_expiry_idx": { + "name": "login_transactions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_transactions_user_idx": { + "name": "login_transactions_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_transactions_inline_protocol_key_unique": { + "name": "login_transactions_inline_protocol_key_unique", + "columns": [ + { + "expression": "inline_protocol_auth_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"login_transactions\".\"status\" in ('pending', 'claimed')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_transactions_oauth_request_unique": { + "name": "login_transactions_oauth_request_unique", + "columns": [ + { + "expression": "oauth_auth_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"login_transactions\".\"status\" in ('pending', 'claimed')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_transactions_native_app_request_unique": { + "name": "login_transactions_native_app_request_unique", + "columns": [ + { + "expression": "native_app_auth_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"login_transactions\".\"status\" in ('pending', 'claimed')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "login_transactions_inline_protocol_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk": { + "name": "login_transactions_inline_protocol_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk", + "tableFrom": "login_transactions", + "tableTo": "inline_protocol_auth_keys", + "columnsFrom": [ + "inline_protocol_auth_key_id" + ], + "columnsTo": [ + "auth_key_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "login_transactions_oauth_auth_request_id_oauth_auth_requests_id_fk": { + "name": "login_transactions_oauth_auth_request_id_oauth_auth_requests_id_fk", + "tableFrom": "login_transactions", + "tableTo": "oauth_auth_requests", + "columnsFrom": [ + "oauth_auth_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "login_transactions_native_app_auth_request_id_native_app_auth_requests_id_fk": { + "name": "login_transactions_native_app_auth_request_id_native_app_auth_requests_id_fk", + "tableFrom": "login_transactions", + "tableTo": "native_app_auth_requests", + "columnsFrom": [ + "native_app_auth_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "login_transactions_user_id_users_id_fk": { + "name": "login_transactions_user_id_users_id_fk", + "tableFrom": "login_transactions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "login_transactions_capability_hash_length": { + "name": "login_transactions_capability_hash_length", + "value": "octet_length(\"login_transactions\".\"capability_hash\") = 32" + }, + "login_transactions_browser_csrf_hash_length": { + "name": "login_transactions_browser_csrf_hash_length", + "value": "\"login_transactions\".\"browser_csrf_hash\" is null or octet_length(\"login_transactions\".\"browser_csrf_hash\") = 32" + }, + "login_transactions_status_valid": { + "name": "login_transactions_status_valid", + "value": "\"login_transactions\".\"status\" in ('pending', 'claimed', 'complete', 'cancelled')" + }, + "login_transactions_method_valid": { + "name": "login_transactions_method_valid", + "value": "\"login_transactions\".\"auth_method\" is null or \"login_transactions\".\"auth_method\" in ('email', 'phone', 'google', 'apple')" + }, + "login_transactions_typed_target": { + "name": "login_transactions_typed_target", + "value": "(\n (\"login_transactions\".\"target_kind\" = 'inline_protocol_key')::int +\n (\"login_transactions\".\"target_kind\" = 'oauth_authorization')::int +\n (\"login_transactions\".\"target_kind\" = 'native_app')::int\n ) = 1 and\n (\"login_transactions\".\"inline_protocol_auth_key_id\" is not null)::int +\n (\"login_transactions\".\"oauth_auth_request_id\" is not null)::int +\n (\"login_transactions\".\"native_app_auth_request_id\" is not null)::int = 1 and\n (\"login_transactions\".\"target_kind\" = 'inline_protocol_key') = (\"login_transactions\".\"inline_protocol_auth_key_id\" is not null) and\n (\"login_transactions\".\"target_kind\" = 'oauth_authorization') = (\"login_transactions\".\"oauth_auth_request_id\" is not null) and\n (\"login_transactions\".\"target_kind\" = 'native_app') = (\"login_transactions\".\"native_app_auth_request_id\" is not null)\n " + } + }, + "isRLSEnabled": false + }, + "public.native_app_auth_requests": { + "name": "native_app_auth_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "callback_scheme": { + "name": "callback_scheme", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "code_challenge": { + "name": "code_challenge", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "client": { + "name": "client", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_method": { + "name": "auth_method", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "ticket_hash": { + "name": "ticket_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "proven_at": { + "name": "proven_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "native_app_auth_requests_expiry_idx": { + "name": "native_app_auth_requests_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_app_auth_requests_ticket_unique": { + "name": "native_app_auth_requests_ticket_unique", + "columns": [ + { + "expression": "ticket_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_app_auth_requests_user_id_users_id_fk": { + "name": "native_app_auth_requests_user_id_users_id_fk", + "tableFrom": "native_app_auth_requests", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "native_app_auth_requests_method_valid": { + "name": "native_app_auth_requests_method_valid", + "value": "\"native_app_auth_requests\".\"auth_method\" is null or \"native_app_auth_requests\".\"auth_method\" in ('email', 'phone', 'google', 'apple')" + } + }, + "isRLSEnabled": false + }, + "public.email_campaign_recipients": { + "name": "email_campaign_recipients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "campaign_id": { + "name": "campaign_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email_key": { + "name": "email_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "email_encrypted": { + "name": "email_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "name_encrypted": { + "name": "name_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "unsubscribe_token_hash": { + "name": "unsubscribe_token_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "unsubscribe_token_encrypted": { + "name": "unsubscribe_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "sources": { + "name": "sources", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "provider": { + "name": "provider", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "contacted_at": { + "name": "contacted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_campaign_recipients_campaign_email_unique": { + "name": "email_campaign_recipients_campaign_email_unique", + "columns": [ + { + "expression": "campaign_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_campaign_recipients_unsubscribe_token_unique": { + "name": "email_campaign_recipients_unsubscribe_token_unique", + "columns": [ + { + "expression": "unsubscribe_token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_campaign_recipients_campaign_status_idx": { + "name": "email_campaign_recipients_campaign_status_idx", + "columns": [ + { + "expression": "campaign_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_campaign_recipients_email_key_idx": { + "name": "email_campaign_recipients_email_key_idx", + "columns": [ + { + "expression": "email_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_campaign_recipients_campaign_id_email_campaigns_id_fk": { + "name": "email_campaign_recipients_campaign_id_email_campaigns_id_fk", + "tableFrom": "email_campaign_recipients", + "tableTo": "email_campaigns", + "columnsFrom": [ + "campaign_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_campaign_recipients_status_check": { + "name": "email_campaign_recipients_status_check", + "value": "\"email_campaign_recipients\".\"status\" in ('pending', 'provider_synced', 'sending', 'provider_accepted', 'unknown', 'suppressed')" + } + }, + "isRLSEnabled": false + }, + "public.email_campaigns": { + "name": "email_campaigns", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "series_key": { + "name": "series_key", + "type": "varchar(160)", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "varchar(240)", + "primaryKey": false, + "notNull": true + }, + "preview_text": { + "name": "preview_text", + "type": "varchar(240)", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "audience": { + "name": "audience", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "from_address": { + "name": "from_address", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true, + "default": "'team@inline.chat'" + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'frozen'" + }, + "recipient_count": { + "name": "recipient_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "provider_segment_id": { + "name": "provider_segment_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "provider_campaign_id": { + "name": "provider_campaign_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "visible_unsubscribe": { + "name": "visible_unsubscribe", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "unsubscribe_override_reason": { + "name": "unsubscribe_override_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "test_sent_at": { + "name": "test_sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "frozen_at": { + "name": "frozen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "email_campaigns_name_unique": { + "name": "email_campaigns_name_unique", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_campaigns_created_at_idx": { + "name": "email_campaigns_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_campaigns_created_by_user_id_users_id_fk": { + "name": "email_campaigns_created_by_user_id_users_id_fk", + "tableFrom": "email_campaigns", + "tableTo": "users", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_campaigns_provider_check": { + "name": "email_campaigns_provider_check", + "value": "\"email_campaigns\".\"provider\" is null or \"email_campaigns\".\"provider\" in ('resend', 'ses')" + }, + "email_campaigns_from_address_check": { + "name": "email_campaigns_from_address_check", + "value": "\"email_campaigns\".\"from_address\" in ('team@inline.chat', 'founders@inline.chat', 'mo@inline.chat')" + }, + "email_campaigns_status_check": { + "name": "email_campaigns_status_check", + "value": "\"email_campaigns\".\"status\" in ('frozen', 'sending', 'paused', 'completed')" + } + }, + "isRLSEnabled": false + }, + "public.email_suppressions": { + "name": "email_suppressions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email_key": { + "name": "email_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "email_encrypted": { + "name": "email_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_suppressions_email_key_unique": { + "name": "email_suppressions_email_key_unique", + "columns": [ + { + "expression": "email_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_suppressions_reason_check": { + "name": "email_suppressions_reason_check", + "value": "\"email_suppressions\".\"reason\" in ('unsubscribe', 'manual', 'bounce', 'complaint', 'invalid')" + } + }, + "isRLSEnabled": false + }, + "public.server_config": { + "name": "server_config", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(160)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "server_config_updated_by_user_id_users_id_fk": { + "name": "server_config_updated_by_user_id_users_id_fk", + "tableFrom": "server_config", + "tableTo": "users", + "columnsFrom": [ + "updated_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "server_config_version_check": { + "name": "server_config_version_check", + "value": "\"server_config\".\"version\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.reserved_usernames": { + "name": "reserved_usernames", + "schema": "", + "columns": { + "username": { + "name": "username", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inline_protocol_auth_challenges": { + "name": "inline_protocol_auth_challenges", + "schema": "", + "columns": { + "challenge_id": { + "name": "challenge_id", + "type": "bytea", + "primaryKey": true, + "notNull": true + }, + "auth_key_id": { + "name": "auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "identifier_encrypted": { + "name": "identifier_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "identifier_hash": { + "name": "identifier_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "code_mac": { + "name": "code_mac", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "pepper_key_id": { + "name": "pepper_key_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "delivery": { + "name": "delivery", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "client": { + "name": "client", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "network_hash": { + "name": "network_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "device_hash": { + "name": "device_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inline_protocol_auth_challenges_key_created_idx": { + "name": "inline_protocol_auth_challenges_key_created_idx", + "columns": [ + { + "expression": "auth_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_auth_challenges_identifier_created_idx": { + "name": "inline_protocol_auth_challenges_identifier_created_idx", + "columns": [ + { + "expression": "identifier_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_auth_challenges_expiry_idx": { + "name": "inline_protocol_auth_challenges_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inline_protocol_auth_challenges_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk": { + "name": "inline_protocol_auth_challenges_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk", + "tableFrom": "inline_protocol_auth_challenges", + "tableTo": "inline_protocol_auth_keys", + "columnsFrom": [ + "auth_key_id" + ], + "columnsTo": [ + "auth_key_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_protocol_auth_challenges_id_length": { + "name": "inline_protocol_auth_challenges_id_length", + "value": "octet_length(\"inline_protocol_auth_challenges\".\"challenge_id\") = 32" + }, + "inline_protocol_auth_challenges_key_id_length": { + "name": "inline_protocol_auth_challenges_key_id_length", + "value": "octet_length(\"inline_protocol_auth_challenges\".\"auth_key_id\") = 8" + }, + "inline_protocol_auth_challenges_identifier_hash_length": { + "name": "inline_protocol_auth_challenges_identifier_hash_length", + "value": "octet_length(\"inline_protocol_auth_challenges\".\"identifier_hash\") = 32" + }, + "inline_protocol_auth_challenges_code_mac_length": { + "name": "inline_protocol_auth_challenges_code_mac_length", + "value": "octet_length(\"inline_protocol_auth_challenges\".\"code_mac\") = 32" + } + }, + "isRLSEnabled": false + }, + "public.inline_protocol_auth_keys": { + "name": "inline_protocol_auth_keys", + "schema": "", + "columns": { + "auth_key_id": { + "name": "auth_key_id", + "type": "bytea", + "primaryKey": true, + "notNull": true + }, + "auth_key_encrypted": { + "name": "auth_key_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "key_encryption_key_id": { + "name": "key_encryption_key_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "current_server_salt": { + "name": "current_server_salt", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "previous_server_salt": { + "name": "previous_server_salt", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "server_salt_updated_at": { + "name": "server_salt_updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "account_session_id": { + "name": "account_session_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "authorized_at": { + "name": "authorized_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inline_protocol_auth_keys_user_idx": { + "name": "inline_protocol_auth_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_auth_keys_session_idx": { + "name": "inline_protocol_auth_keys_session_idx", + "columns": [ + { + "expression": "account_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_auth_keys_expiry_idx": { + "name": "inline_protocol_auth_keys_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inline_protocol_auth_keys_user_id_users_id_fk": { + "name": "inline_protocol_auth_keys_user_id_users_id_fk", + "tableFrom": "inline_protocol_auth_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "inline_protocol_auth_keys_account_session_id_sessions_id_fk": { + "name": "inline_protocol_auth_keys_account_session_id_sessions_id_fk", + "tableFrom": "inline_protocol_auth_keys", + "tableTo": "sessions", + "columnsFrom": [ + "account_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_protocol_auth_keys_id_length": { + "name": "inline_protocol_auth_keys_id_length", + "value": "octet_length(\"inline_protocol_auth_keys\".\"auth_key_id\") = 8" + }, + "inline_protocol_auth_keys_encrypted_length": { + "name": "inline_protocol_auth_keys_encrypted_length", + "value": "octet_length(\"inline_protocol_auth_keys\".\"auth_key_encrypted\") = 284" + } + }, + "isRLSEnabled": false + }, + "public.inline_protocol_requests": { + "name": "inline_protocol_requests", + "schema": "", + "columns": { + "auth_key_id": { + "name": "auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "protocol_session_id": { + "name": "protocol_session_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "request_digest": { + "name": "request_digest", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "result_body": { + "name": "result_body", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "result_format": { + "name": "result_format", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "inline_protocol_requests_expiry_idx": { + "name": "inline_protocol_requests_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inline_protocol_requests_pk": { + "name": "inline_protocol_requests_pk", + "columns": [ + "auth_key_id", + "protocol_session_id", + "message_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_protocol_requests_key_id_length": { + "name": "inline_protocol_requests_key_id_length", + "value": "octet_length(\"inline_protocol_requests\".\"auth_key_id\") = 8" + }, + "inline_protocol_requests_digest_length": { + "name": "inline_protocol_requests_digest_length", + "value": "octet_length(\"inline_protocol_requests\".\"request_digest\") = 32" + }, + "inline_protocol_requests_result_length": { + "name": "inline_protocol_requests_result_length", + "value": "\"inline_protocol_requests\".\"result_body\" is null or octet_length(\"inline_protocol_requests\".\"result_body\") <= 16777278" + }, + "inline_protocol_requests_result_format": { + "name": "inline_protocol_requests_result_format", + "value": "(\"inline_protocol_requests\".\"result_format\" = 0 and (\"inline_protocol_requests\".\"result_body\" is null or octet_length(\"inline_protocol_requests\".\"result_body\") <= 16777216)) or (\"inline_protocol_requests\".\"result_format\" = 1 and \"inline_protocol_requests\".\"result_body\" is not null and octet_length(\"inline_protocol_requests\".\"result_body\") >= 31)" + } + }, + "isRLSEnabled": false + }, + "public.inline_protocol_uploads": { + "name": "inline_protocol_uploads", + "schema": "", + "columns": { + "upload_id": { + "name": "upload_id", + "type": "bytea", + "primaryKey": true, + "notNull": true + }, + "capability_hash": { + "name": "capability_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "permanent_auth_key_id": { + "name": "permanent_auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "issuing_temporary_auth_key_id": { + "name": "issuing_temporary_auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "account_session_id": { + "name": "account_session_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "byte_count": { + "name": "byte_count", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "lock_token": { + "name": "lock_token", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "file_unique_id": { + "name": "file_unique_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inline_protocol_uploads_capability_unique": { + "name": "inline_protocol_uploads_capability_unique", + "columns": [ + { + "expression": "capability_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_uploads_session_idx": { + "name": "inline_protocol_uploads_session_idx", + "columns": [ + { + "expression": "account_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_uploads_expiry_idx": { + "name": "inline_protocol_uploads_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inline_protocol_uploads_permanent_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk": { + "name": "inline_protocol_uploads_permanent_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk", + "tableFrom": "inline_protocol_uploads", + "tableTo": "inline_protocol_auth_keys", + "columnsFrom": [ + "permanent_auth_key_id" + ], + "columnsTo": [ + "auth_key_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inline_protocol_uploads_user_id_users_id_fk": { + "name": "inline_protocol_uploads_user_id_users_id_fk", + "tableFrom": "inline_protocol_uploads", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inline_protocol_uploads_account_session_id_sessions_id_fk": { + "name": "inline_protocol_uploads_account_session_id_sessions_id_fk", + "tableFrom": "inline_protocol_uploads", + "tableTo": "sessions", + "columnsFrom": [ + "account_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_protocol_uploads_id_length": { + "name": "inline_protocol_uploads_id_length", + "value": "octet_length(\"inline_protocol_uploads\".\"upload_id\") = 16" + }, + "inline_protocol_uploads_capability_hash_length": { + "name": "inline_protocol_uploads_capability_hash_length", + "value": "octet_length(\"inline_protocol_uploads\".\"capability_hash\") = 32" + }, + "inline_protocol_uploads_permanent_key_length": { + "name": "inline_protocol_uploads_permanent_key_length", + "value": "octet_length(\"inline_protocol_uploads\".\"permanent_auth_key_id\") = 8" + }, + "inline_protocol_uploads_temporary_key_length": { + "name": "inline_protocol_uploads_temporary_key_length", + "value": "octet_length(\"inline_protocol_uploads\".\"issuing_temporary_auth_key_id\") = 8" + }, + "inline_protocol_uploads_sha_length": { + "name": "inline_protocol_uploads_sha_length", + "value": "octet_length(\"inline_protocol_uploads\".\"sha256\") = 32" + }, + "inline_protocol_uploads_byte_count_positive": { + "name": "inline_protocol_uploads_byte_count_positive", + "value": "\"inline_protocol_uploads\".\"byte_count\" > 0" + }, + "inline_protocol_uploads_status_valid": { + "name": "inline_protocol_uploads_status_valid", + "value": "\"inline_protocol_uploads\".\"status\" in ('pending', 'uploading', 'complete', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.inline_upload_parts": { + "name": "inline_upload_parts", + "schema": "", + "columns": { + "upload_id": { + "name": "upload_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "part_index": { + "name": "part_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "byte_count": { + "name": "byte_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "stored_byte_count": { + "name": "stored_byte_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "stored_sha256": { + "name": "stored_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "inline_upload_parts_upload_id_inline_uploads_id_fk": { + "name": "inline_upload_parts_upload_id_inline_uploads_id_fk", + "tableFrom": "inline_upload_parts", + "tableTo": "inline_uploads", + "columnsFrom": [ + "upload_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "inline_upload_parts_pk": { + "name": "inline_upload_parts_pk", + "columns": [ + "upload_id", + "part_index" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_upload_parts_index_valid": { + "name": "inline_upload_parts_index_valid", + "value": "\"inline_upload_parts\".\"part_index\" >= 0" + }, + "inline_upload_parts_byte_count_valid": { + "name": "inline_upload_parts_byte_count_valid", + "value": "\"inline_upload_parts\".\"byte_count\" between 1 and 524288" + }, + "inline_upload_parts_sha_length": { + "name": "inline_upload_parts_sha_length", + "value": "octet_length(\"inline_upload_parts\".\"sha256\") = 32" + }, + "inline_upload_parts_stored_byte_count_valid": { + "name": "inline_upload_parts_stored_byte_count_valid", + "value": "\"inline_upload_parts\".\"stored_byte_count\" > 0" + }, + "inline_upload_parts_stored_sha_length": { + "name": "inline_upload_parts_stored_sha_length", + "value": "octet_length(\"inline_upload_parts\".\"stored_sha256\") = 32" + } + }, + "isRLSEnabled": false + }, + "public.inline_upload_storage_parts": { + "name": "inline_upload_storage_parts", + "schema": "", + "columns": { + "upload_id": { + "name": "upload_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "storage_upload_id": { + "name": "storage_upload_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "part_number": { + "name": "part_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "stored_byte_count": { + "name": "stored_byte_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "stored_sha256": { + "name": "stored_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "inline_upload_storage_parts_upload_id_inline_uploads_id_fk": { + "name": "inline_upload_storage_parts_upload_id_inline_uploads_id_fk", + "tableFrom": "inline_upload_storage_parts", + "tableTo": "inline_uploads", + "columnsFrom": [ + "upload_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "inline_upload_storage_parts_pk": { + "name": "inline_upload_storage_parts_pk", + "columns": [ + "upload_id", + "part_number" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_upload_storage_parts_number_valid": { + "name": "inline_upload_storage_parts_number_valid", + "value": "\"inline_upload_storage_parts\".\"part_number\" between 1 and 1000" + }, + "inline_upload_storage_parts_byte_count_valid": { + "name": "inline_upload_storage_parts_byte_count_valid", + "value": "\"inline_upload_storage_parts\".\"stored_byte_count\" > 0" + }, + "inline_upload_storage_parts_sha_length": { + "name": "inline_upload_storage_parts_sha_length", + "value": "octet_length(\"inline_upload_storage_parts\".\"stored_sha256\") = 32" + }, + "inline_upload_storage_parts_etag_valid": { + "name": "inline_upload_storage_parts_etag_valid", + "value": "length(\"inline_upload_storage_parts\".\"etag\") between 1 and 128" + } + }, + "isRLSEnabled": false + }, + "public.inline_uploads": { + "name": "inline_uploads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "inline_uploads_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "upload_id": { + "name": "upload_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "client_upload_id": { + "name": "client_upload_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "permanent_auth_key_id": { + "name": "permanent_auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "account_session_id": { + "name": "account_session_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "byte_count": { + "name": "byte_count", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "thumbnail_file_unique_id": { + "name": "thumbnail_file_unique_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "video_width": { + "name": "video_width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "video_height": { + "name": "video_height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_animated": { + "name": "is_animated", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "has_audio": { + "name": "has_audio", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "waveform": { + "name": "waveform", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "part_size": { + "name": "part_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "part_count": { + "name": "part_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'uploading'" + }, + "failure_code": { + "name": "failure_code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "failure_retryable": { + "name": "failure_retryable", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "lock_token": { + "name": "lock_token", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "storage_format": { + "name": "storage_format", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "storage_upload_id": { + "name": "storage_upload_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retry_at": { + "name": "retry_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "result_file_unique_id": { + "name": "result_file_unique_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "result_media_id": { + "name": "result_media_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_part_at": { + "name": "last_part_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "hard_expires_at": { + "name": "hard_expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inline_uploads_upload_id_unique": { + "name": "inline_uploads_upload_id_unique", + "columns": [ + { + "expression": "upload_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_session_client_id_unique": { + "name": "inline_uploads_session_client_id_unique", + "columns": [ + { + "expression": "account_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_upload_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_permanent_key_idx": { + "name": "inline_uploads_permanent_key_idx", + "columns": [ + { + "expression": "permanent_auth_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_user_status_idx": { + "name": "inline_uploads_user_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_session_created_idx": { + "name": "inline_uploads_session_created_idx", + "columns": [ + { + "expression": "account_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_expiry_idx": { + "name": "inline_uploads_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_processing_retry_idx": { + "name": "inline_uploads_processing_retry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inline_uploads_permanent_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk": { + "name": "inline_uploads_permanent_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk", + "tableFrom": "inline_uploads", + "tableTo": "inline_protocol_auth_keys", + "columnsFrom": [ + "permanent_auth_key_id" + ], + "columnsTo": [ + "auth_key_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inline_uploads_user_id_users_id_fk": { + "name": "inline_uploads_user_id_users_id_fk", + "tableFrom": "inline_uploads", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inline_uploads_account_session_id_sessions_id_fk": { + "name": "inline_uploads_account_session_id_sessions_id_fk", + "tableFrom": "inline_uploads", + "tableTo": "sessions", + "columnsFrom": [ + "account_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_uploads_id_length": { + "name": "inline_uploads_id_length", + "value": "octet_length(\"inline_uploads\".\"upload_id\") = 16" + }, + "inline_uploads_client_id_length": { + "name": "inline_uploads_client_id_length", + "value": "octet_length(\"inline_uploads\".\"client_upload_id\") = 16" + }, + "inline_uploads_permanent_key_length": { + "name": "inline_uploads_permanent_key_length", + "value": "octet_length(\"inline_uploads\".\"permanent_auth_key_id\") = 8" + }, + "inline_uploads_sha_length": { + "name": "inline_uploads_sha_length", + "value": "octet_length(\"inline_uploads\".\"sha256\") = 32" + }, + "inline_uploads_byte_count_positive": { + "name": "inline_uploads_byte_count_positive", + "value": "\"inline_uploads\".\"byte_count\" > 0" + }, + "inline_uploads_part_size_valid": { + "name": "inline_uploads_part_size_valid", + "value": "\"inline_uploads\".\"part_size\" = 524288" + }, + "inline_uploads_part_count_valid": { + "name": "inline_uploads_part_count_valid", + "value": "\"inline_uploads\".\"part_count\" between 1 and 1000" + }, + "inline_uploads_kind_valid": { + "name": "inline_uploads_kind_valid", + "value": "\"inline_uploads\".\"kind\" in ('photo', 'video', 'document', 'voice')" + }, + "inline_uploads_status_valid": { + "name": "inline_uploads_status_valid", + "value": "\"inline_uploads\".\"status\" in ('uploading', 'processing', 'complete', 'failed', 'canceled')" + }, + "inline_uploads_storage_format_valid": { + "name": "inline_uploads_storage_format_valid", + "value": "\"inline_uploads\".\"storage_format\" is null or \"inline_uploads\".\"storage_format\" = 'identity_v1'" + }, + "inline_uploads_storage_session_valid": { + "name": "inline_uploads_storage_session_valid", + "value": "\"inline_uploads\".\"storage_upload_id\" is null or \"inline_uploads\".\"storage_format\" is not null" + }, + "inline_uploads_attempts_valid": { + "name": "inline_uploads_attempts_valid", + "value": "\"inline_uploads\".\"attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.agent_session_messages": { + "name": "agent_session_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "agent_session_id": { + "name": "agent_session_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "source_key_hash": { + "name": "source_key_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "item_key_hash": { + "name": "item_key_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "source_ref_encrypted": { + "name": "source_ref_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "revision_ref_encrypted": { + "name": "revision_ref_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "message_global_id": { + "name": "message_global_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "relation": { + "name": "relation", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "complete": { + "name": "complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_session_messages_source_unique": { + "name": "agent_session_messages_source_unique", + "columns": [ + { + "expression": "agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_messages_item_unique": { + "name": "agent_session_messages_item_unique", + "columns": [ + { + "expression": "agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_session_messages\".\"item_key_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_messages_message_unique": { + "name": "agent_session_messages_message_unique", + "columns": [ + { + "expression": "agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_global_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_session_messages\".\"message_global_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_messages_session_idx": { + "name": "agent_session_messages_session_idx", + "columns": [ + { + "expression": "agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_session_messages_agent_session_id_agent_sessions_id_fk": { + "name": "agent_session_messages_agent_session_id_agent_sessions_id_fk", + "tableFrom": "agent_session_messages", + "tableTo": "agent_sessions", + "columnsFrom": [ + "agent_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_session_messages_message_global_id_messages_global_id_fk": { + "name": "agent_session_messages_message_global_id_messages_global_id_fk", + "tableFrom": "agent_session_messages", + "tableTo": "messages", + "columnsFrom": [ + "message_global_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_sessions": { + "name": "agent_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "session_key_hash": { + "name": "session_key_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "instance_ref_encrypted": { + "name": "instance_ref_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "session_ref_encrypted": { + "name": "session_ref_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "project_ref_encrypted": { + "name": "project_ref_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "status_message_global_id": { + "name": "status_message_global_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_sessions_external_unique": { + "name": "agent_sessions_external_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "session_key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_sessions_chat_bot_unique": { + "name": "agent_sessions_chat_bot_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_sessions_owner_idx": { + "name": "agent_sessions_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_sessions_status_message_unique": { + "name": "agent_sessions_status_message_unique", + "columns": [ + { + "expression": "status_message_global_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_sessions\".\"status_message_global_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_sessions_chat_id_chats_id_fk": { + "name": "agent_sessions_chat_id_chats_id_fk", + "tableFrom": "agent_sessions", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_sessions_bot_user_id_users_id_fk": { + "name": "agent_sessions_bot_user_id_users_id_fk", + "tableFrom": "agent_sessions", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_sessions_owner_user_id_users_id_fk": { + "name": "agent_sessions_owner_user_id_users_id_fk", + "tableFrom": "agent_sessions", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_sessions_status_message_global_id_messages_global_id_fk": { + "name": "agent_sessions_status_message_global_id_messages_global_id_fk", + "tableFrom": "agent_sessions", + "tableTo": "messages", + "columnsFrom": [ + "status_message_global_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_delivery_budgets": { + "name": "auth_delivery_budgets", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(96)", + "primaryKey": true, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "auth_delivery_budgets_expiry_idx": { + "name": "auth_delivery_budgets_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auth_delivery_budgets_attempts_positive": { + "name": "auth_delivery_budgets_attempts_positive", + "value": "\"auth_delivery_budgets\".\"attempts\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.recent_realtime_buckets": { + "name": "recent_realtime_buckets", + "schema": "", + "columns": { + "bucket": { + "name": "bucket", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "recent_realtime_buckets_expiry_idx": { + "name": "recent_realtime_buckets_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "recent_realtime_buckets_bucket_entity_id_pk": { + "name": "recent_realtime_buckets_bucket_entity_id_pk", + "columns": [ + "bucket", + "entity_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "recent_realtime_buckets_bucket_valid": { + "name": "recent_realtime_buckets_bucket_valid", + "value": "\"recent_realtime_buckets\".\"bucket\" in (1, 2, 3)" + }, + "recent_realtime_buckets_entity_positive": { + "name": "recent_realtime_buckets_entity_positive", + "value": "\"recent_realtime_buckets\".\"entity_id\" > 0" + }, + "recent_realtime_buckets_seq_positive": { + "name": "recent_realtime_buckets_seq_positive", + "value": "\"recent_realtime_buckets\".\"seq\" > 0" + } + }, + "isRLSEnabled": false + } + }, + "enums": { + "public.client_type": { + "name": "client_type", + "schema": "public", + "values": [ + "ios", + "macos", + "web", + "api", + "android", + "windows", + "linux", + "cli" + ] + }, + "public.member_roles": { + "name": "member_roles", + "schema": "public", + "values": [ + "owner", + "admin", + "member" + ] + }, + "public.chat_types": { + "name": "chat_types", + "schema": "public", + "values": [ + "private", + "thread" + ] + }, + "public.thread_graph_link_kind": { + "name": "thread_graph_link_kind", + "schema": "public", + "values": [ + "thread_link", + "reply_thread" + ] + }, + "public.thread_graph_scope_type": { + "name": "thread_graph_scope_type", + "schema": "public", + "values": [ + "user", + "space" + ] + }, + "public.account_provider": { + "name": "account_provider", + "schema": "public", + "values": [ + "google", + "apple" + ] + }, + "public.provider_auth_purpose": { + "name": "provider_auth_purpose", + "schema": "public", + "values": [ + "app", + "mcp_oauth", + "hosted_login" + ] + }, + "public.provider_auth_status": { + "name": "provider_auth_status", + "schema": "public", + "values": [ + "pending_provider", + "pending_invite", + "pending_email", + "complete", + "used" + ] + } + }, + "schemas": {}, + "sequences": { + "public.user_id": { + "name": "user_id", + "schema": "public", + "increment": "3", + "startWith": "1000", + "minValue": "1000", + "maxValue": "9223372036854775807", + "cache": "100", + "cycle": false + } + }, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/server/drizzle/meta/0155_snapshot.json b/server/drizzle/meta/0155_snapshot.json new file mode 100644 index 000000000..079c31e0f --- /dev/null +++ b/server/drizzle/meta/0155_snapshot.json @@ -0,0 +1,13146 @@ +{ + "id": "7ff6e5b3-b5de-4524-8496-b95b209fba36", + "prevId": "c0fd85d8-6fc2-45d5-9db9-f630a77657fe", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "time_zone": { + "name": "time_zone", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.there_users": { + "name": "there_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "time_zone": { + "name": "time_zone", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "there_users_email_unique": { + "name": "there_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "default": "nextval('user_id')" + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "phone_verified": { + "name": "phone_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "first_name": { + "name": "first_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "deleted": { + "name": "deleted", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "online": { + "name": "online", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "last_online": { + "name": "last_online", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "photo_file_id": { + "name": "photo_file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pending_setup": { + "name": "pending_setup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "time_zone": { + "name": "time_zone", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "share_time_zone": { + "name": "share_time_zone", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "appear_in_global_search": { + "name": "appear_in_global_search", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "next_thread_number": { + "name": "next_thread_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "bot": { + "name": "bot", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "bot_creator_id": { + "name": "bot_creator_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "update_seq": { + "name": "update_seq", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_update_date": { + "name": "last_update_date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "users_username_unique": { + "name": "users_username_unique", + "columns": [ + { + "expression": "lower(\"username\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "users_photo_file_id_files_id_fk": { + "name": "users_photo_file_id_files_id_fk", + "tableFrom": "users", + "tableTo": "files", + "columnsFrom": [ + "photo_file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "users_bot_creator_id_users_id_fk": { + "name": "users_bot_creator_id_users_id_fk", + "tableFrom": "users", + "tableTo": "users", + "columnsFrom": [ + "bot_creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "users_phone_number_unique": { + "name": "users_phone_number_unique", + "nullsNotDistinct": false, + "columns": [ + "phone_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "revoked": { + "name": "revoked", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_active": { + "name": "last_active", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "personal_data_encrypted": { + "name": "personal_data_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "personal_data_iv": { + "name": "personal_data_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "personal_data_tag": { + "name": "personal_data_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "applePushToken": { + "name": "applePushToken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "apple_push_token_encrypted": { + "name": "apple_push_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "apple_push_token_iv": { + "name": "apple_push_token_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "apple_push_token_tag": { + "name": "apple_push_token_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "push_notification_provider": { + "name": "push_notification_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "push_content_key_public": { + "name": "push_content_key_public", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "push_content_key_id": { + "name": "push_content_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "push_content_key_algorithm": { + "name": "push_content_key_algorithm", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "push_content_version": { + "name": "push_content_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "device_id": { + "name": "device_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_type": { + "name": "client_type", + "type": "client_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "clientVersion": { + "name": "clientVersion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "osVersion": { + "name": "osVersion", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sessions_token_hash_idx": { + "name": "sessions_token_hash_idx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "device_id_user_unique": { + "name": "device_id_user_unique", + "nullsNotDistinct": false, + "columns": [ + "device_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.superadmin_sessions": { + "name": "superadmin_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "step_up_at": { + "name": "step_up_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "idle_expires_at": { + "name": "idle_expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent_hash": { + "name": "user_agent_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "superadmin_sessions_user_id_users_id_fk": { + "name": "superadmin_sessions_user_id_users_id_fk", + "tableFrom": "superadmin_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "superadmin_sessions_token_hash_unique": { + "name": "superadmin_sessions_token_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "token_hash" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.login_codes": { + "name": "login_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "challenge_id": { + "name": "challenge_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "smallint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "login_codes_challenge_id_unique": { + "name": "login_codes_challenge_id_unique", + "columns": [ + { + "expression": "challenge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_codes_email_expires_idx": { + "name": "login_codes_email_expires_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_codes_phone_expires_idx": { + "name": "login_codes_phone_expires_idx", + "columns": [ + { + "expression": "phone_number", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "login_codes_code_or_hash_present": { + "name": "login_codes_code_or_hash_present", + "value": "\"login_codes\".\"code\" is not null or \"login_codes\".\"code_hash\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.invite_codes": { + "name": "invite_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(8)", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "redeemed_by_user_id": { + "name": "redeemed_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "note": { + "name": "note", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "invite_codes_code_unique": { + "name": "invite_codes_code_unique", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invite_codes_owner_user_id_idx": { + "name": "invite_codes_owner_user_id_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invite_codes_redeemed_by_user_id_idx": { + "name": "invite_codes_redeemed_by_user_id_idx", + "columns": [ + { + "expression": "redeemed_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invite_codes_owner_user_id_users_id_fk": { + "name": "invite_codes_owner_user_id_users_id_fk", + "tableFrom": "invite_codes", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "invite_codes_created_by_user_id_users_id_fk": { + "name": "invite_codes_created_by_user_id_users_id_fk", + "tableFrom": "invite_codes", + "tableTo": "users", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "invite_codes_redeemed_by_user_id_users_id_fk": { + "name": "invite_codes_redeemed_by_user_id_users_id_fk", + "tableFrom": "invite_codes", + "tableTo": "users", + "columnsFrom": [ + "redeemed_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.superadmin_users": { + "name": "superadmin_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password_set_at": { + "name": "password_set_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "totp_secret_encrypted": { + "name": "totp_secret_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "totp_secret_iv": { + "name": "totp_secret_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "totp_secret_tag": { + "name": "totp_secret_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "totp_enabled_at": { + "name": "totp_enabled_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "totp_last_used_at": { + "name": "totp_last_used_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "failed_login_attempts": { + "name": "failed_login_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_login_attempt_at": { + "name": "last_login_attempt_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "login_locked_until": { + "name": "login_locked_until", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "disabled_at": { + "name": "disabled_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "superadmin_users_user_id_users_id_fk": { + "name": "superadmin_users_user_id_users_id_fk", + "tableFrom": "superadmin_users", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "superadmin_users_email_unique": { + "name": "superadmin_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "superadmin_users_user_id_unique": { + "name": "superadmin_users_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.spaces": { + "name": "spaces", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "photo_file_unique_id": { + "name": "photo_file_unique_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "is_pro": { + "name": "is_pro", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "handle": { + "name": "handle", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "creatorId": { + "name": "creatorId", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "can_public_join": { + "name": "can_public_join", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted": { + "name": "deleted", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "next_thread_number": { + "name": "next_thread_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "update_seq": { + "name": "update_seq", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_update_date": { + "name": "last_update_date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "grid_revision": { + "name": "grid_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "spaces_handle_unique": { + "name": "spaces_handle_unique", + "columns": [ + { + "expression": "lower(\"handle\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "spaces_creatorId_users_id_fk": { + "name": "spaces_creatorId_users_id_fk", + "tableFrom": "spaces", + "tableTo": "users", + "columnsFrom": [ + "creatorId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "spaces_photo_file_unique_id_fkey": { + "name": "spaces_photo_file_unique_id_fkey", + "tableFrom": "spaces", + "tableTo": "files", + "columnsFrom": [ + "photo_file_unique_id" + ], + "columnsTo": [ + "file_unique_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "spaces_public_join_handle_check": { + "name": "spaces_public_join_handle_check", + "value": "not \"spaces\".\"can_public_join\" or (\"spaces\".\"is_public\" and \"spaces\".\"handle\" is not null and \"spaces\".\"handle\" ~ '^[A-Za-z0-9][A-Za-z0-9_-]{1,63}$')" + } + }, + "isRLSEnabled": false + }, + "public.space_invite_links": { + "name": "space_invite_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "token_encrypted": { + "name": "token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "space_invite_links_token_hash_unique": { + "name": "space_invite_links_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "space_invite_links_active_space_unique": { + "name": "space_invite_links_active_space_unique", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"space_invite_links\".\"revoked_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "space_invite_links_space_id_idx": { + "name": "space_invite_links_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "space_invite_links_created_by_user_id_idx": { + "name": "space_invite_links_created_by_user_id_idx", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "space_invite_links_space_id_spaces_id_fk": { + "name": "space_invite_links_space_id_spaces_id_fk", + "tableFrom": "space_invite_links", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "space_invite_links_created_by_user_id_users_id_fk": { + "name": "space_invite_links_created_by_user_id_users_id_fk", + "tableFrom": "space_invite_links", + "tableTo": "users", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "space_invite_links_token_hash_length": { + "name": "space_invite_links_token_hash_length", + "value": "octet_length(\"space_invite_links\".\"token_hash\") = 32" + }, + "space_invite_links_expiry_check": { + "name": "space_invite_links_expiry_check", + "value": "\"space_invite_links\".\"expires_at\" > \"space_invite_links\".\"created_at\"" + } + }, + "isRLSEnabled": false + }, + "public.space_join_blocks": { + "name": "space_join_blocks", + "schema": "", + "columns": { + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "space_join_blocks_user_id_idx": { + "name": "space_join_blocks_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "space_join_blocks_space_id_spaces_id_fk": { + "name": "space_join_blocks_space_id_spaces_id_fk", + "tableFrom": "space_join_blocks", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "space_join_blocks_user_id_users_id_fk": { + "name": "space_join_blocks_user_id_users_id_fk", + "tableFrom": "space_join_blocks", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "space_join_blocks_pkey": { + "name": "space_join_blocks_pkey", + "columns": [ + "space_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.members": { + "name": "members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "member_roles", + "typeSchema": "public", + "primaryKey": false, + "notNull": false, + "default": "'member'" + }, + "can_access_public_chats": { + "name": "can_access_public_chats", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "invited_by": { + "name": "invited_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "members_user_id_users_id_fk": { + "name": "members_user_id_users_id_fk", + "tableFrom": "members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "members_space_id_spaces_id_fk": { + "name": "members_space_id_spaces_id_fk", + "tableFrom": "members", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "members_invited_by_users_id_fk": { + "name": "members_invited_by_users_id_fk", + "tableFrom": "members", + "tableTo": "users", + "columnsFrom": [ + "invited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "members_user_id_space_id_unique": { + "name": "members_user_id_space_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "space_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_participants": { + "name": "chat_participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "chat_participants_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_participants_chat_id_chats_id_fk": { + "name": "chat_participants_chat_id_chats_id_fk", + "tableFrom": "chat_participants", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_participants_user_id_users_id_fk": { + "name": "chat_participants_user_id_users_id_fk", + "tableFrom": "chat_participants", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_participant": { + "name": "unique_participant", + "nullsNotDistinct": false, + "columns": [ + "chat_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chats": { + "name": "chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "byDefault", + "name": "chats_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "type": { + "name": "type", + "type": "chat_types", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title_hash": { + "name": "title_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "auto_title_generated": { + "name": "auto_title_generated", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_msg_id": { + "name": "last_msg_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "message_id_counter": { + "name": "message_id_counter", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "public_thread": { + "name": "public_thread", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "thread_number": { + "name": "thread_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_untitled": { + "name": "is_untitled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "parent_chat_id": { + "name": "parent_chat_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "min_user_id": { + "name": "min_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_user_id": { + "name": "max_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_context": { + "name": "agent_context", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "update_seq": { + "name": "update_seq", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_update_date": { + "name": "last_update_date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chats_title_hash_idx": { + "name": "chats_title_hash_idx", + "columns": [ + { + "expression": "title_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_thread_number_unique": { + "name": "user_thread_number_unique", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "thread_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chats\".\"space_id\" is null and \"chats\".\"thread_number\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chats_parent_chat_id_idx": { + "name": "chats_parent_chat_id_idx", + "columns": [ + { + "expression": "parent_chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chats_space_id_spaces_id_fk": { + "name": "chats_space_id_spaces_id_fk", + "tableFrom": "chats", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chats_created_by_users_id_fk": { + "name": "chats_created_by_users_id_fk", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chats_parent_chat_id_chats_id_fk": { + "name": "chats_parent_chat_id_chats_id_fk", + "tableFrom": "chats", + "tableTo": "chats", + "columnsFrom": [ + "parent_chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chats_min_user_id_users_id_fk": { + "name": "chats_min_user_id_users_id_fk", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "min_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chats_max_user_id_users_id_fk": { + "name": "chats_max_user_id_users_id_fk", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "max_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "last_msg_id_fk": { + "name": "last_msg_id_fk", + "tableFrom": "chats", + "tableTo": "messages", + "columnsFrom": [ + "id", + "last_msg_id" + ], + "columnsTo": [ + "chat_id", + "message_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "parent_message_id_fk": { + "name": "parent_message_id_fk", + "tableFrom": "chats", + "tableTo": "messages", + "columnsFrom": [ + "parent_chat_id", + "parent_message_id" + ], + "columnsTo": [ + "chat_id", + "message_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_ids_unique": { + "name": "user_ids_unique", + "nullsNotDistinct": false, + "columns": [ + "min_user_id", + "max_user_id" + ] + }, + "space_thread_number_unique": { + "name": "space_thread_number_unique", + "nullsNotDistinct": false, + "columns": [ + "space_id", + "thread_number" + ] + }, + "reply_thread_parent_unique": { + "name": "reply_thread_parent_unique", + "nullsNotDistinct": false, + "columns": [ + "parent_chat_id", + "parent_message_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "user_ids_check": { + "name": "user_ids_check", + "value": "\"chats\".\"min_user_id\" <= \"chats\".\"max_user_id\"" + }, + "parent_message_requires_parent_chat": { + "name": "parent_message_requires_parent_chat", + "value": "\"chats\".\"parent_message_id\" is null or \"chats\".\"parent_chat_id\" is not null" + } + }, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "global_id": { + "name": "global_id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "random_id": { + "name": "random_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "text": { + "name": "text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "text_encrypted": { + "name": "text_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "text_iv": { + "name": "text_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "text_tag": { + "name": "text_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "entities_encrypted": { + "name": "entities_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "entities_iv": { + "name": "entities_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "entities_tag": { + "name": "entities_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "actions_encrypted": { + "name": "actions_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "actions_iv": { + "name": "actions_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "actions_tag": { + "name": "actions_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "system_message_encrypted": { + "name": "system_message_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "system_message_iv": { + "name": "system_message_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "system_message_tag": { + "name": "system_message_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "from_id": { + "name": "from_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "edit_date": { + "name": "edit_date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "rev": { + "name": "rev", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "block_content_id": { + "name": "block_content_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "reply_to_msg_id": { + "name": "reply_to_msg_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fwd_from_peer_user_id": { + "name": "fwd_from_peer_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fwd_from_peer_chat_id": { + "name": "fwd_from_peer_chat_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fwd_from_message_id": { + "name": "fwd_from_message_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fwd_from_sender_id": { + "name": "fwd_from_sender_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "grouped_id": { + "name": "grouped_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "video_id": { + "name": "video_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "voice_id": { + "name": "voice_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_sticker": { + "name": "is_sticker", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "has_link": { + "name": "has_link", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "counts_as_unread": { + "name": "counts_as_unread", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "msg_id_per_chat_index": { + "name": "msg_id_per_chat_index", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_chat_id_message_id_desc_idx": { + "name": "messages_chat_id_message_id_desc_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unread_count_index": { + "name": "unread_count_index", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_block_content_id_idx": { + "name": "messages_block_content_id_idx", + "columns": [ + { + "expression": "block_content_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_activity_date_from_id_idx": { + "name": "messages_activity_date_from_id_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"messages\".\"system_message_encrypted\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_chat_id_chats_id_fk": { + "name": "messages_chat_id_chats_id_fk", + "tableFrom": "messages", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_from_id_users_id_fk": { + "name": "messages_from_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "from_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_block_content_id_block_contents_id_fk": { + "name": "messages_block_content_id_block_contents_id_fk", + "tableFrom": "messages", + "tableTo": "block_contents", + "columnsFrom": [ + "block_content_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "messages_photo_id_photos_id_fk": { + "name": "messages_photo_id_photos_id_fk", + "tableFrom": "messages", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_video_id_videos_id_fk": { + "name": "messages_video_id_videos_id_fk", + "tableFrom": "messages", + "tableTo": "videos", + "columnsFrom": [ + "video_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_document_id_documents_id_fk": { + "name": "messages_document_id_documents_id_fk", + "tableFrom": "messages", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_voice_id_voices_id_fk": { + "name": "messages_voice_id_voices_id_fk", + "tableFrom": "messages", + "tableTo": "voices", + "columnsFrom": [ + "voice_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_file_id_files_id_fk": { + "name": "messages_file_id_files_id_fk", + "tableFrom": "messages", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "msg_id_per_chat_unique": { + "name": "msg_id_per_chat_unique", + "nullsNotDistinct": false, + "columns": [ + "message_id", + "chat_id" + ] + }, + "random_id_per_sender_unique": { + "name": "random_id_per_sender_unique", + "nullsNotDistinct": false, + "columns": [ + "random_id", + "from_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.block_content_image_jobs": { + "name": "block_content_image_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "block_content_image_jobs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "content_id": { + "name": "content_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "expected_revision": { + "name": "expected_revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "block_path": { + "name": "block_path", + "type": "integer[]", + "primaryKey": false, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "hash_version": { + "name": "hash_version", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_encrypted": { + "name": "source_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_iv": { + "name": "source_iv", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_tag": { + "name": "source_tag", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "available_at": { + "name": "available_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lease_token": { + "name": "lease_token", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "staged_object_path_encrypted": { + "name": "staged_object_path_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "staged_object_path_iv": { + "name": "staged_object_path_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "staged_object_path_tag": { + "name": "staged_object_path_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "block_content_image_jobs_ready_idx": { + "name": "block_content_image_jobs_ready_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "block_content_image_jobs_revision_idx": { + "name": "block_content_image_jobs_revision_idx", + "columns": [ + { + "expression": "content_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expected_revision", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "block_content_image_jobs_path_idx": { + "name": "block_content_image_jobs_path_idx", + "columns": [ + { + "expression": "content_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "block_content_image_jobs_content_id_block_contents_id_fk": { + "name": "block_content_image_jobs_content_id_block_contents_id_fk", + "tableFrom": "block_content_image_jobs", + "tableTo": "block_contents", + "columnsFrom": [ + "content_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "block_content_image_jobs_photo_id_photos_id_fk": { + "name": "block_content_image_jobs_photo_id_photos_id_fk", + "tableFrom": "block_content_image_jobs", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "block_content_image_jobs_state_check": { + "name": "block_content_image_jobs_state_check", + "value": "\"block_content_image_jobs\".\"state\" in ('pending', 'processing', 'ready', 'failed', 'canceled')" + }, + "block_content_image_jobs_attempts_check": { + "name": "block_content_image_jobs_attempts_check", + "value": "\"block_content_image_jobs\".\"attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.block_contents": { + "name": "block_contents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "block_contents_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "payload_encrypted": { + "name": "payload_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "payload_iv": { + "name": "payload_iv", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "payload_tag": { + "name": "payload_tag", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.thread_graph_links": { + "name": "thread_graph_links", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "thread_graph_link_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "thread_graph_scope_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "from_chat_id": { + "name": "from_chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "from_message_global_id": { + "name": "from_message_global_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "from_message_id": { + "name": "from_message_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "from_message_revision": { + "name": "from_message_revision", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "entity_index": { + "name": "entity_index", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "to_chat_id": { + "name": "to_chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "backlink_message_global_id": { + "name": "backlink_message_global_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "link_text_hmac": { + "name": "link_text_hmac", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "target_title_hmac": { + "name": "target_title_hmac", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "thread_graph_links_scope_to_chat_idx": { + "name": "thread_graph_links_scope_to_chat_idx", + "columns": [ + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "thread_graph_links_scope_from_chat_idx": { + "name": "thread_graph_links_scope_from_chat_idx", + "columns": [ + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "from_chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "thread_graph_links_from_message_idx": { + "name": "thread_graph_links_from_message_idx", + "columns": [ + { + "expression": "from_message_global_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "thread_graph_links_backlink_message_idx": { + "name": "thread_graph_links_backlink_message_idx", + "columns": [ + { + "expression": "backlink_message_global_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "thread_graph_links_active_idx": { + "name": "thread_graph_links_active_idx", + "columns": [ + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"thread_graph_links\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tgl_from_chat_fk": { + "name": "tgl_from_chat_fk", + "tableFrom": "thread_graph_links", + "tableTo": "chats", + "columnsFrom": [ + "from_chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tgl_from_message_fk": { + "name": "tgl_from_message_fk", + "tableFrom": "thread_graph_links", + "tableTo": "messages", + "columnsFrom": [ + "from_message_global_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tgl_to_chat_fk": { + "name": "tgl_to_chat_fk", + "tableFrom": "thread_graph_links", + "tableTo": "chats", + "columnsFrom": [ + "to_chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tgl_backlink_message_fk": { + "name": "tgl_backlink_message_fk", + "tableFrom": "thread_graph_links", + "tableTo": "messages", + "columnsFrom": [ + "backlink_message_global_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "thread_graph_links_dedupe_key_unique": { + "name": "thread_graph_links_dedupe_key_unique", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subthread_parent_messages": { + "name": "subthread_parent_messages", + "schema": "", + "columns": { + "child_chat_id": { + "name": "child_chat_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "parent_message_global_id": { + "name": "parent_message_global_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "subthread_parent_messages_child_chat_fk": { + "name": "subthread_parent_messages_child_chat_fk", + "tableFrom": "subthread_parent_messages", + "tableTo": "chats", + "columnsFrom": [ + "child_chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "subthread_parent_messages_parent_message_fk": { + "name": "subthread_parent_messages_parent_message_fk", + "tableFrom": "subthread_parent_messages", + "tableTo": "messages", + "columnsFrom": [ + "parent_message_global_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "subthread_parent_messages_parent_message_unique": { + "name": "subthread_parent_messages_parent_message_unique", + "nullsNotDistinct": false, + "columns": [ + "parent_message_global_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dialogs": { + "name": "dialogs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "peer_user_id": { + "name": "peer_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "read_inbox_max_id": { + "name": "read_inbox_max_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "read_outbox_max_id": { + "name": "read_outbox_max_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "draft": { + "name": "draft", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived": { + "name": "archived", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "sidebar_visible": { + "name": "sidebar_visible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "chat_list_hidden": { + "name": "chat_list_hidden", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "open": { + "name": "open", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "opened_date": { + "name": "opened_date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pinned_order": { + "name": "pinned_order", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unread_mark": { + "name": "unread_mark", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "notification_settings": { + "name": "notification_settings", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "follow_mode": { + "name": "follow_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "collapsed_max_id": { + "name": "collapsed_max_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "translation_enabled": { + "name": "translation_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dialogs_user_id_chat_id_idx": { + "name": "dialogs_user_id_chat_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dialogs_user_id_peer_user_id_idx": { + "name": "dialogs_user_id_peer_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "peer_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dialogs_user_id_order_idx": { + "name": "dialogs_user_id_order_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dialogs_user_id_pinned_order_idx": { + "name": "dialogs_user_id_pinned_order_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pinned_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dialogs_user_id_folder_id_order_idx": { + "name": "dialogs_user_id_folder_id_order_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dialogs_user_id_users_id_fk": { + "name": "dialogs_user_id_users_id_fk", + "tableFrom": "dialogs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dialogs_chat_id_chats_id_fk": { + "name": "dialogs_chat_id_chats_id_fk", + "tableFrom": "dialogs", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dialogs_peer_user_id_users_id_fk": { + "name": "dialogs_peer_user_id_users_id_fk", + "tableFrom": "dialogs", + "tableTo": "users", + "columnsFrom": [ + "peer_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dialogs_space_id_spaces_id_fk": { + "name": "dialogs_space_id_spaces_id_fk", + "tableFrom": "dialogs", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dialogs_folder_id_user_id_dialog_folders_id_user_id_fk": { + "name": "dialogs_folder_id_user_id_dialog_folders_id_user_id_fk", + "tableFrom": "dialogs", + "tableTo": "dialog_folders", + "columnsFrom": [ + "folder_id", + "user_id" + ], + "columnsTo": [ + "id", + "user_id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "chat_id_user_id_unique": { + "name": "chat_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "chat_id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_acknowledgements": { + "name": "chat_acknowledgements", + "schema": "", + "columns": { + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "max_id": { + "name": "max_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cleared": { + "name": "cleared", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "chat_acknowledgements_user_id_idx": { + "name": "chat_acknowledgements_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_acknowledgements_chat_id_chats_id_fk": { + "name": "chat_acknowledgements_chat_id_chats_id_fk", + "tableFrom": "chat_acknowledgements", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_acknowledgements_user_id_users_id_fk": { + "name": "chat_acknowledgements_user_id_users_id_fk", + "tableFrom": "chat_acknowledgements", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "chat_acknowledgements_chat_id_user_id_pk": { + "name": "chat_acknowledgements_chat_id_user_id_pk", + "columns": [ + "chat_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dialog_folders": { + "name": "dialog_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "order": { + "name": "order", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_order": { + "name": "pinned_order", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "dialog_folders_user_id_order_idx": { + "name": "dialog_folders_user_id_order_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dialog_folders_user_id_pinned_order_idx": { + "name": "dialog_folders_user_id_pinned_order_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pinned_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dialog_folders_user_id_users_id_fk": { + "name": "dialog_folders_user_id_users_id_fk", + "tableFrom": "dialog_folders", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dialog_folders_id_user_id_unique": { + "name": "dialog_folders_id_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "id", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_id_reservation": { + "name": "chat_id_reservation", + "schema": "", + "columns": { + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "chat_id_reservation_user_id_users_id_fk": { + "name": "chat_id_reservation_user_id_users_id_fk", + "tableFrom": "chat_id_reservation", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.reactions": { + "name": "reactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "reactions_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "message_id": { + "name": "message_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "emoji_hash": { + "name": "emoji_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "reactions_chat_id_chats_id_fk": { + "name": "reactions_chat_id_chats_id_fk", + "tableFrom": "reactions", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "reactions_user_id_users_id_fk": { + "name": "reactions_user_id_users_id_fk", + "tableFrom": "reactions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_id_fk": { + "name": "message_id_fk", + "tableFrom": "reactions", + "tableTo": "messages", + "columnsFrom": [ + "chat_id", + "message_id" + ], + "columnsTo": [ + "chat_id", + "message_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "reactions_identity_emoji_hash_unique": { + "name": "reactions_identity_emoji_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "chat_id", + "message_id", + "user_id", + "emoji_hash" + ] + }, + "unique_reaction_per_emoji": { + "name": "unique_reaction_per_emoji", + "nullsNotDistinct": false, + "columns": [ + "chat_id", + "message_id", + "user_id", + "emoji" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.files": { + "name": "files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "file_unique_id": { + "name": "file_unique_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path_encrypted": { + "name": "path_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "path_iv": { + "name": "path_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "path_tag": { + "name": "path_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cdn": { + "name": "cdn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "file_type": { + "name": "file_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "video_duration": { + "name": "video_duration", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "thumb_size": { + "name": "thumb_size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thumb_for": { + "name": "thumb_for", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "bytes_encrypted": { + "name": "bytes_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "bytes_iv": { + "name": "bytes_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "bytes_tag": { + "name": "bytes_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "name_encrypted": { + "name": "name_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "name_iv": { + "name": "name_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "name_tag": { + "name": "name_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "files_user_id_users_id_fk": { + "name": "files_user_id_users_id_fk", + "tableFrom": "files", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "files_thumb_for_files_id_fk": { + "name": "files_thumb_for_files_id_fk", + "tableFrom": "files", + "tableTo": "files", + "columnsFrom": [ + "thumb_for" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "files_file_unique_id_unique": { + "name": "files_file_unique_id_unique", + "nullsNotDistinct": false, + "columns": [ + "file_unique_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integration_oauth_states": { + "name": "integration_oauth_states", + "schema": "", + "columns": { + "state_hash": { + "name": "state_hash", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "callback_scheme": { + "name": "callback_scheme", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "integration_oauth_states_user": { + "name": "integration_oauth_states_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "integration_oauth_states_expires": { + "name": "integration_oauth_states_expires", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integration_oauth_states_user_id_users_id_fk": { + "name": "integration_oauth_states_user_id_users_id_fk", + "tableFrom": "integration_oauth_states", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "integration_oauth_states_space_id_spaces_id_fk": { + "name": "integration_oauth_states_space_id_spaces_id_fk", + "tableFrom": "integration_oauth_states", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.integrations": { + "name": "integrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "access_token_iv": { + "name": "access_token_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "access_token_tag": { + "name": "access_token_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "notion_database_id": { + "name": "notion_database_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linear_team_id": { + "name": "linear_team_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "integrations_space_provider_unique": { + "name": "integrations_space_provider_unique", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "integrations_user_id_users_id_fk": { + "name": "integrations_user_id_users_id_fk", + "tableFrom": "integrations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "integrations_space_id_spaces_id_fk": { + "name": "integrations_space_id_spaces_id_fk", + "tableFrom": "integrations", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_tokens": { + "name": "bot_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_encrypted": { + "name": "token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "token_iv": { + "name": "token_iv", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "token_tag": { + "name": "token_tag", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_tokens_bot_user_id_unique": { + "name": "bot_tokens_bot_user_id_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_tokens_bot_user_id_users_id_fk": { + "name": "bot_tokens_bot_user_id_users_id_fk", + "tableFrom": "bot_tokens", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "bot_tokens_session_id_sessions_id_fk": { + "name": "bot_tokens_session_id_sessions_id_fk", + "tableFrom": "bot_tokens", + "tableTo": "sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_commands": { + "name": "bot_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_commands_bot_user_id_command_unique": { + "name": "bot_commands_bot_user_id_command_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "command", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_commands_bot_user_id_users_id_fk": { + "name": "bot_commands_bot_user_id_users_id_fk", + "tableFrom": "bot_commands", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_skills": { + "name": "bot_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_skills_bot_user_id_key_unique": { + "name": "bot_skills_bot_user_id_key_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_skills_bot_user_id_users_id_fk": { + "name": "bot_skills_bot_user_id_users_id_fk", + "tableFrom": "bot_skills", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_agents": { + "name": "bot_agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "handle": { + "name": "handle", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "emoji": { + "name": "emoji", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "skill_key": { + "name": "skill_key", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "instructions_encrypted": { + "name": "instructions_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_agents_bot_user_id_idx": { + "name": "bot_agents_bot_user_id_idx", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_agents_bot_user_id_users_id_fk": { + "name": "bot_agents_bot_user_id_users_id_fk", + "tableFrom": "bot_agents", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_capabilities": { + "name": "bot_capabilities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_capabilities_bot_user_id_kind_unique": { + "name": "bot_capabilities_bot_user_id_kind_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_capabilities_bot_user_id_users_id_fk": { + "name": "bot_capabilities_bot_user_id_users_id_fk", + "tableFrom": "bot_capabilities", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_avatar_assets": { + "name": "bot_avatar_assets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "bot_avatar_assets_bot_user_id_users_id_fk": { + "name": "bot_avatar_assets_bot_user_id_users_id_fk", + "tableFrom": "bot_avatar_assets", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "bot_avatar_assets_file_id_files_id_fk": { + "name": "bot_avatar_assets_file_id_files_id_fk", + "tableFrom": "bot_avatar_assets", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "bot_avatar_assets_bot_user_id_unique": { + "name": "bot_avatar_assets_bot_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "bot_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_message_routes": { + "name": "bot_message_routes", + "schema": "", + "columns": { + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "activation_reason": { + "name": "activation_reason", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "bot_message_routes_identity_unique": { + "name": "bot_message_routes_identity_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_message_routes_message_idx": { + "name": "bot_message_routes_message_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_message_routes_expiry_idx": { + "name": "bot_message_routes_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_message_routes_bot_user_id_users_id_fk": { + "name": "bot_message_routes_bot_user_id_users_id_fk", + "tableFrom": "bot_message_routes", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_update_streams": { + "name": "bot_update_streams", + "schema": "", + "columns": { + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "next_update_id": { + "name": "next_update_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "acknowledged_update_id": { + "name": "acknowledged_update_id", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allowed_updates": { + "name": "allowed_updates", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "message_trigger": { + "name": "message_trigger", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'mentions'" + }, + "webhook_url": { + "name": "webhook_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "webhook_secret_encrypted": { + "name": "webhook_secret_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "poll_lease_token": { + "name": "poll_lease_token", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "poll_lease_expires_at": { + "name": "poll_lease_expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "config_generation": { + "name": "config_generation", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "pending_update_count": { + "name": "pending_update_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "pending_payload_bytes": { + "name": "pending_payload_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "delivery_locked_at": { + "name": "delivery_locked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dropped_update_count": { + "name": "dropped_update_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "bot_update_streams_bot_user_id_users_id_fk": { + "name": "bot_update_streams_bot_user_id_users_id_fk", + "tableFrom": "bot_update_streams", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_updates": { + "name": "bot_updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "bot_updates_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "update_id": { + "name": "update_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "update_type": { + "name": "update_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "payload_encrypted": { + "name": "payload_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "payload_byte_count": { + "name": "payload_byte_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_event_id": { + "name": "source_event_id", + "type": "varchar(160)", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "claim_generation": { + "name": "claim_generation", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "claim_expires_at": { + "name": "claim_expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bot_updates_bot_update_id_unique": { + "name": "bot_updates_bot_update_id_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "update_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_updates_bot_source_event_unique": { + "name": "bot_updates_bot_source_event_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_updates_pending_idx": { + "name": "bot_updates_pending_idx", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "update_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_updates_expiry_idx": { + "name": "bot_updates_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bot_updates_delivery_idx": { + "name": "bot_updates_delivery_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claim_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "update_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_updates_bot_user_id_users_id_fk": { + "name": "bot_updates_bot_user_id_users_id_fk", + "tableFrom": "bot_updates", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.documents": { + "name": "documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "documents_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "file_name": { + "name": "file_name", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "file_name_iv": { + "name": "file_name_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "file_name_tag": { + "name": "file_name_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "documents_file_id_files_id_fk": { + "name": "documents_file_id_files_id_fk", + "tableFrom": "documents", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "documents_photo_id_photos_id_fk": { + "name": "documents_photo_id_photos_id_fk", + "tableFrom": "documents", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.photo_sizes": { + "name": "photo_sizes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "photo_sizes_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size": { + "name": "size", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "photo_sizes_photo_id_idx": { + "name": "photo_sizes_photo_id_idx", + "columns": [ + { + "expression": "photo_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "photo_sizes_file_id_files_id_fk": { + "name": "photo_sizes_file_id_files_id_fk", + "tableFrom": "photo_sizes", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "photo_sizes_photo_id_photos_id_fk": { + "name": "photo_sizes_photo_id_photos_id_fk", + "tableFrom": "photo_sizes", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.photos": { + "name": "photos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "photos_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "stripped": { + "name": "stripped", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "stripped_iv": { + "name": "stripped_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "stripped_tag": { + "name": "stripped_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.videos": { + "name": "videos", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "videos_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_animated": { + "name": "is_animated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "has_audio": { + "name": "has_audio", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "videos_file_id_files_id_fk": { + "name": "videos_file_id_files_id_fk", + "tableFrom": "videos", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "videos_photo_id_photos_id_fk": { + "name": "videos_photo_id_photos_id_fk", + "tableFrom": "videos", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.voices": { + "name": "voices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "voices_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "file_id": { + "name": "file_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "waveform": { + "name": "waveform", + "type": "bytea", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "voices_file_id_files_id_fk": { + "name": "voices_file_id_files_id_fk", + "tableFrom": "voices", + "tableTo": "files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_tasks": { + "name": "external_tasks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "external_tasks_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "application": { + "name": "application", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_user_id": { + "name": "assigned_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "connector_space_id": { + "name": "connector_space_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "source_message_id": { + "name": "source_message_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "number": { + "name": "number", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_iv": { + "name": "title_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_tag": { + "name": "title_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_tasks_provider_user_source_space_unique": { + "name": "external_tasks_provider_user_source_space_unique", + "columns": [ + { + "expression": "source_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "assigned_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "application", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connector_space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "external_tasks_assigned_user_id_users_id_fk": { + "name": "external_tasks_assigned_user_id_users_id_fk", + "tableFrom": "external_tasks", + "tableTo": "users", + "columnsFrom": [ + "assigned_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "external_tasks_connector_space_id_spaces_id_fk": { + "name": "external_tasks_connector_space_id_spaces_id_fk", + "tableFrom": "external_tasks", + "tableTo": "spaces", + "columnsFrom": [ + "connector_space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "external_tasks_source_message_id_messages_global_id_fk": { + "name": "external_tasks_source_message_id_messages_global_id_fk", + "tableFrom": "external_tasks", + "tableTo": "messages", + "columnsFrom": [ + "source_message_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_attachments": { + "name": "message_attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "message_attachments_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "message_id": { + "name": "message_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "external_task_id": { + "name": "external_task_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "url_preview_id": { + "name": "url_preview_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "message_attachments_message_id_idx": { + "name": "message_attachments_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "message_attachments_message_id_messages_global_id_fk": { + "name": "message_attachments_message_id_messages_global_id_fk", + "tableFrom": "message_attachments", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_attachments_external_task_id_external_tasks_id_fk": { + "name": "message_attachments_external_task_id_external_tasks_id_fk", + "tableFrom": "message_attachments", + "tableTo": "external_tasks", + "columnsFrom": [ + "external_task_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "message_attachments_url_preview_id_url_preview_id_fk": { + "name": "message_attachments_url_preview_id_url_preview_id_fk", + "tableFrom": "message_attachments", + "tableTo": "url_preview", + "columnsFrom": [ + "url_preview_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.url_preview": { + "name": "url_preview", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "url_preview_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "url": { + "name": "url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "url_iv": { + "name": "url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "url_tag": { + "name": "url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "site_name": { + "name": "site_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'generic'" + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_iv": { + "name": "title_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_tag": { + "name": "title_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description_iv": { + "name": "description_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description_tag": { + "name": "description_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_iv": { + "name": "author_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_tag": { + "name": "author_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "media_kind": { + "name": "media_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "author_photo_id": { + "name": "author_photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "video_id": { + "name": "video_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_id": { + "name": "cache_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "external_url": { + "name": "external_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_url_iv": { + "name": "external_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_url_tag": { + "name": "external_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_mime_type": { + "name": "external_mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_width": { + "name": "external_width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_height": { + "name": "external_height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_duration": { + "name": "external_duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_url": { + "name": "embed_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_url_iv": { + "name": "embed_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_url_tag": { + "name": "embed_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_type": { + "name": "embed_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embed_width": { + "name": "embed_width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_height": { + "name": "embed_height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_duration": { + "name": "embed_duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "has_large_media": { + "name": "has_large_media", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_large_media": { + "name": "show_large_media", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "url_preview_photo_id_photos_id_fk": { + "name": "url_preview_photo_id_photos_id_fk", + "tableFrom": "url_preview", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_author_photo_id_photos_id_fk": { + "name": "url_preview_author_photo_id_photos_id_fk", + "tableFrom": "url_preview", + "tableTo": "photos", + "columnsFrom": [ + "author_photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_video_id_videos_id_fk": { + "name": "url_preview_video_id_videos_id_fk", + "tableFrom": "url_preview", + "tableTo": "videos", + "columnsFrom": [ + "video_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_document_id_documents_id_fk": { + "name": "url_preview_document_id_documents_id_fk", + "tableFrom": "url_preview", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_cache_id_url_preview_cache_id_fk": { + "name": "url_preview_cache_id_url_preview_cache_id_fk", + "tableFrom": "url_preview", + "tableTo": "url_preview_cache", + "columnsFrom": [ + "cache_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.url_preview_cache": { + "name": "url_preview_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "url_preview_cache_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "url_hash": { + "name": "url_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "hash_version": { + "name": "hash_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "url": { + "name": "url", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "url_iv": { + "name": "url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "url_tag": { + "name": "url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "final_url": { + "name": "final_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "final_url_iv": { + "name": "final_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "final_url_tag": { + "name": "final_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'generic'" + }, + "site_name": { + "name": "site_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "media_type": { + "name": "media_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_iv": { + "name": "title_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "title_tag": { + "name": "title_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description_iv": { + "name": "description_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "description_tag": { + "name": "description_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author": { + "name": "author", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_iv": { + "name": "author_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_tag": { + "name": "author_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "image_url_hash": { + "name": "image_url_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "image_url_iv": { + "name": "image_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "image_url_tag": { + "name": "image_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_image_url_hash": { + "name": "author_image_url_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_image_url": { + "name": "author_image_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_image_url_iv": { + "name": "author_image_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "author_image_url_tag": { + "name": "author_image_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "media_kind": { + "name": "media_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "photo_id": { + "name": "photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "author_photo_id": { + "name": "author_photo_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "video_id": { + "name": "video_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "document_id": { + "name": "document_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "external_url": { + "name": "external_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_url_iv": { + "name": "external_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_url_tag": { + "name": "external_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "external_mime_type": { + "name": "external_mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_width": { + "name": "external_width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_height": { + "name": "external_height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "external_duration": { + "name": "external_duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_url": { + "name": "embed_url", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_url_iv": { + "name": "embed_url_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_url_tag": { + "name": "embed_url_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "embed_type": { + "name": "embed_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "embed_width": { + "name": "embed_width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_height": { + "name": "embed_height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "embed_duration": { + "name": "embed_duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "has_large_media": { + "name": "has_large_media", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "show_large_media": { + "name": "show_large_media", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "url_preview_cache_url_hash_unique": { + "name": "url_preview_cache_url_hash_unique", + "columns": [ + { + "expression": "url_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "url_preview_cache_image_url_hash_idx": { + "name": "url_preview_cache_image_url_hash_idx", + "columns": [ + { + "expression": "image_url_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "url_preview_cache_author_image_url_hash_idx": { + "name": "url_preview_cache_author_image_url_hash_idx", + "columns": [ + { + "expression": "author_image_url_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "url_preview_cache_expires_at_idx": { + "name": "url_preview_cache_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "url_preview_cache_last_used_at_idx": { + "name": "url_preview_cache_last_used_at_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "url_preview_cache_photo_id_photos_id_fk": { + "name": "url_preview_cache_photo_id_photos_id_fk", + "tableFrom": "url_preview_cache", + "tableTo": "photos", + "columnsFrom": [ + "photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_cache_author_photo_id_photos_id_fk": { + "name": "url_preview_cache_author_photo_id_photos_id_fk", + "tableFrom": "url_preview_cache", + "tableTo": "photos", + "columnsFrom": [ + "author_photo_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_cache_video_id_videos_id_fk": { + "name": "url_preview_cache_video_id_videos_id_fk", + "tableFrom": "url_preview_cache", + "tableTo": "videos", + "columnsFrom": [ + "video_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "url_preview_cache_document_id_documents_id_fk": { + "name": "url_preview_cache_document_id_documents_id_fk", + "tableFrom": "url_preview_cache", + "tableTo": "documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.space_url_preview_exclusions": { + "name": "space_url_preview_exclusions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "space_url_preview_exclusions_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "host": { + "name": "host", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path_prefix": { + "name": "path_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "space_url_preview_exclusions_space_host_path_unique": { + "name": "space_url_preview_exclusions_space_host_path_unique", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "path_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "space_url_preview_exclusions_space_host_idx": { + "name": "space_url_preview_exclusions_space_host_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "host", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "space_url_preview_exclusions_created_by_idx": { + "name": "space_url_preview_exclusions_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "space_url_preview_exclusions_space_id_spaces_id_fk": { + "name": "space_url_preview_exclusions_space_id_spaces_id_fk", + "tableFrom": "space_url_preview_exclusions", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "space_url_preview_exclusions_created_by_users_id_fk": { + "name": "space_url_preview_exclusions_created_by_users_id_fk", + "tableFrom": "space_url_preview_exclusions", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat_participant_groups": { + "name": "chat_participant_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "chat_participant_groups_chat_group_unique": { + "name": "chat_participant_groups_chat_group_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_participant_groups_chat_id_idx": { + "name": "chat_participant_groups_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_participant_groups_group_id_idx": { + "name": "chat_participant_groups_group_id_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_participant_groups_chat_id_chats_id_fk": { + "name": "chat_participant_groups_chat_id_chats_id_fk", + "tableFrom": "chat_participant_groups", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_participant_groups_group_id_user_groups_id_fk": { + "name": "chat_participant_groups_group_id_user_groups_id_fk", + "tableFrom": "chat_participant_groups", + "tableTo": "user_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_group_members": { + "name": "user_group_members", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_group_members_group_user_unique": { + "name": "user_group_members_group_user_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_group_members_group_id_idx": { + "name": "user_group_members_group_id_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_group_members_user_id_idx": { + "name": "user_group_members_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_group_members_group_id_user_groups_id_fk": { + "name": "user_group_members_group_id_user_groups_id_fk", + "tableFrom": "user_group_members", + "tableTo": "user_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_group_members_user_id_users_id_fk": { + "name": "user_group_members_user_id_users_id_fk", + "tableFrom": "user_group_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_groups": { + "name": "user_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(80)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_groups_space_name_unique": { + "name": "user_groups_space_name_unique", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_groups_space_id_idx": { + "name": "user_groups_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_groups_created_by_idx": { + "name": "user_groups_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_groups_space_id_spaces_id_fk": { + "name": "user_groups_space_id_spaces_id_fk", + "tableFrom": "user_groups", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_groups_created_by_users_id_fk": { + "name": "user_groups_created_by_users_id_fk", + "tableFrom": "user_groups", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_translations": { + "name": "message_translations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "message_translations_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "msg_rev": { + "name": "msg_rev", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "message_id": { + "name": "message_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "translation": { + "name": "translation", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "translation_iv": { + "name": "translation_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "translation_tag": { + "name": "translation_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "entities": { + "name": "entities", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "message_translations_chat_id_chats_id_fk": { + "name": "message_translations_chat_id_chats_id_fk", + "tableFrom": "message_translations", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chat_id_message_id_fk": { + "name": "chat_id_message_id_fk", + "tableFrom": "message_translations", + "tableTo": "messages", + "columnsFrom": [ + "chat_id", + "message_id" + ], + "columnsTo": [ + "chat_id", + "message_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_message_chat_language": { + "name": "unique_message_chat_language", + "nullsNotDistinct": false, + "columns": [ + "message_id", + "chat_id", + "language" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_settings": { + "name": "user_settings", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "general_encrypted": { + "name": "general_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "general_iv": { + "name": "general_iv", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "general_tag": { + "name": "general_tag", + "type": "bytea", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_settings_user_id_users_id_fk": { + "name": "user_settings_user_id_users_id_fk", + "tableFrom": "user_settings", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.space_settings": { + "name": "space_settings", + "schema": "", + "columns": { + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "space_settings_space_id_spaces_id_fk": { + "name": "space_settings_space_id_spaces_id_fk", + "tableFrom": "space_settings", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.grid_presence": { + "name": "grid_presence", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "owner_session_id": { + "name": "owner_session_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "microphone_enabled": { + "name": "microphone_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "microphone_revision": { + "name": "microphone_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "media_membership_id": { + "name": "media_membership_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "call_id": { + "name": "call_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "grid_presence_room_id_idx": { + "name": "grid_presence_room_id_idx", + "columns": [ + { + "expression": "room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_presence_owner_session_id_idx": { + "name": "grid_presence_owner_session_id_idx", + "columns": [ + { + "expression": "owner_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_presence_lease_expires_at_idx": { + "name": "grid_presence_lease_expires_at_idx", + "columns": [ + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "grid_presence_user_id_users_id_fk": { + "name": "grid_presence_user_id_users_id_fk", + "tableFrom": "grid_presence", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grid_presence_room_id_grid_rooms_id_fk": { + "name": "grid_presence_room_id_grid_rooms_id_fk", + "tableFrom": "grid_presence", + "tableTo": "grid_rooms", + "columnsFrom": [ + "room_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grid_presence_owner_session_id_sessions_id_fk": { + "name": "grid_presence_owner_session_id_sessions_id_fk", + "tableFrom": "grid_presence", + "tableTo": "sessions", + "columnsFrom": [ + "owner_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "grid_presence_microphone_revision_check": { + "name": "grid_presence_microphone_revision_check", + "value": "\"grid_presence\".\"microphone_revision\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.grid_provider_effects": { + "name": "grid_provider_effects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "deduplication_key": { + "name": "deduplication_key", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "room_id": { + "name": "room_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "connection_generation": { + "name": "connection_generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_target": { + "name": "provider_target", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "participant_identity": { + "name": "participant_identity", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "available_at": { + "name": "available_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "claim_token": { + "name": "claim_token", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "claim_expires_at": { + "name": "claim_expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "grid_provider_effects_deduplication_key_unique": { + "name": "grid_provider_effects_deduplication_key_unique", + "columns": [ + { + "expression": "deduplication_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_provider_effects_ready_idx": { + "name": "grid_provider_effects_ready_idx", + "columns": [ + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claim_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "grid_provider_effects_kind_check": { + "name": "grid_provider_effects_kind_check", + "value": "\"grid_provider_effects\".\"kind\" in ('close_connection', 'revoke_participant')" + }, + "grid_provider_effects_connection_generation_check": { + "name": "grid_provider_effects_connection_generation_check", + "value": "\"grid_provider_effects\".\"connection_generation\" >= 0" + }, + "grid_provider_effects_attempts_check": { + "name": "grid_provider_effects_attempts_check", + "value": "\"grid_provider_effects\".\"attempts\" >= 0" + }, + "grid_provider_effects_participant_check": { + "name": "grid_provider_effects_participant_check", + "value": "(\"grid_provider_effects\".\"kind\" = 'revoke_participant' and \"grid_provider_effects\".\"user_id\" is not null) or (\"grid_provider_effects\".\"kind\" = 'close_connection' and \"grid_provider_effects\".\"user_id\" is null)" + } + }, + "isRLSEnabled": false + }, + "public.grid_rooms": { + "name": "grid_rooms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "room_thread_id": { + "name": "room_thread_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "connection_generation": { + "name": "connection_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "connection_started_at": { + "name": "connection_started_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "grid_rooms_space_id_idx": { + "name": "grid_rooms_space_id_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_rooms_created_by_user_id_idx": { + "name": "grid_rooms_created_by_user_id_idx", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_rooms_space_title_unique": { + "name": "grid_rooms_space_title_unique", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"title\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"grid_rooms\".\"title\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "grid_rooms_space_id_spaces_id_fk": { + "name": "grid_rooms_space_id_spaces_id_fk", + "tableFrom": "grid_rooms", + "tableTo": "spaces", + "columnsFrom": [ + "space_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "grid_rooms_created_by_user_id_users_id_fk": { + "name": "grid_rooms_created_by_user_id_users_id_fk", + "tableFrom": "grid_rooms", + "tableTo": "users", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "grid_rooms_connection_generation_check": { + "name": "grid_rooms_connection_generation_check", + "value": "\"grid_rooms\".\"connection_generation\" >= 0" + }, + "grid_rooms_title_not_empty_check": { + "name": "grid_rooms_title_not_empty_check", + "value": "\"grid_rooms\".\"title\" is null or length(\"grid_rooms\".\"title\") > 0" + } + }, + "isRLSEnabled": false + }, + "public.updates": { + "name": "updates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "updates_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "bucket": { + "name": "bucket", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "bytea", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "updates_bucket_idx": { + "name": "updates_bucket_idx", + "columns": [ + { + "expression": "bucket", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "updates_date_idx": { + "name": "updates_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "updates_unique": { + "name": "updates_unique", + "nullsNotDistinct": false, + "columns": [ + "bucket", + "entity_id", + "seq" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_tokens": { + "name": "oauth_access_tokens", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_access_tokens_grant_idx": { + "name": "oauth_access_tokens_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_tokens_expiry_idx": { + "name": "oauth_access_tokens_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_access_tokens_grant_id_oauth_grants_id_fk": { + "name": "oauth_access_tokens_grant_id_oauth_grants_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_auth_codes": { + "name": "oauth_auth_codes", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_auth_codes_expiry_idx": { + "name": "oauth_auth_codes_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_auth_codes_grant_idx": { + "name": "oauth_auth_codes_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_auth_codes_used_idx": { + "name": "oauth_auth_codes_used_idx", + "columns": [ + { + "expression": "used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_auth_codes_grant_id_oauth_grants_id_fk": { + "name": "oauth_auth_codes_grant_id_oauth_grants_id_fk", + "tableFrom": "oauth_auth_codes", + "tableTo": "oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "oauth_auth_codes_client_id_oauth_clients_client_id_fk": { + "name": "oauth_auth_codes_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_auth_codes", + "tableTo": "oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_auth_requests": { + "name": "oauth_auth_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://mcp.inline.chat'" + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "csrf_token": { + "name": "csrf_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_id": { + "name": "device_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "phone_number": { + "name": "phone_number", + "type": "varchar(15)", + "primaryKey": false, + "notNull": false + }, + "challenge_token": { + "name": "challenge_token", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "inline_user_id": { + "name": "inline_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_method": { + "name": "auth_method", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "inline_token_encrypted": { + "name": "inline_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_auth_requests_expiry_idx": { + "name": "oauth_auth_requests_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_auth_requests_client_idx": { + "name": "oauth_auth_requests_client_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_auth_requests_challenge_unique": { + "name": "oauth_auth_requests_challenge_unique", + "columns": [ + { + "expression": "challenge_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_auth_requests_client_id_oauth_clients_client_id_fk": { + "name": "oauth_auth_requests_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_auth_requests", + "tableTo": "oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "oauth_auth_requests_inline_user_id_users_id_fk": { + "name": "oauth_auth_requests_inline_user_id_users_id_fk", + "tableFrom": "oauth_auth_requests", + "tableTo": "users", + "columnsFrom": [ + "inline_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_clients": { + "name": "oauth_clients", + "schema": "", + "columns": { + "client_id": { + "name": "client_id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "redirect_uris_json": { + "name": "redirect_uris_json", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "oauth_clients_date_idx": { + "name": "oauth_clients_date_idx", + "columns": [ + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_grants": { + "name": "oauth_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "inline_user_id": { + "name": "inline_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'https://mcp.inline.chat'" + }, + "space_ids_json": { + "name": "space_ids_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "allow_dms": { + "name": "allow_dms", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_home_threads": { + "name": "allow_home_threads", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inline_token_encrypted": { + "name": "inline_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_grants_client_idx": { + "name": "oauth_grants_client_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_grants_inline_user_idx": { + "name": "oauth_grants_inline_user_idx", + "columns": [ + { + "expression": "inline_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_grants_revoked_idx": { + "name": "oauth_grants_revoked_idx", + "columns": [ + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_grants_client_id_oauth_clients_client_id_fk": { + "name": "oauth_grants_client_id_oauth_clients_client_id_fk", + "tableFrom": "oauth_grants", + "tableTo": "oauth_clients", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "oauth_grants_inline_user_id_users_id_fk": { + "name": "oauth_grants_inline_user_id_users_id_fk", + "tableFrom": "oauth_grants", + "tableTo": "users", + "columnsFrom": [ + "inline_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_refresh_tokens": { + "name": "oauth_refresh_tokens", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "replaced_by_hash": { + "name": "replaced_by_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_refresh_tokens_grant_idx": { + "name": "oauth_refresh_tokens_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_tokens_expiry_idx": { + "name": "oauth_refresh_tokens_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_tokens_revoked_idx": { + "name": "oauth_refresh_tokens_revoked_idx", + "columns": [ + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_refresh_tokens_grant_id_oauth_grants_id_fk": { + "name": "oauth_refresh_tokens_grant_id_oauth_grants_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_event_subscriptions": { + "name": "mcp_event_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(80)", + "primaryKey": true, + "notNull": true + }, + "grant_id": { + "name": "grant_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(80)", + "primaryKey": false, + "notNull": true + }, + "selector": { + "name": "selector", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "callback_url": { + "name": "callback_url", + "type": "varchar(4096)", + "primaryKey": false, + "notNull": true + }, + "secret_encrypted": { + "name": "secret_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "previous_secret_encrypted": { + "name": "previous_secret_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "previous_secret_until": { + "name": "previous_secret_until", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "cursor_seq": { + "name": "cursor_seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "gap_seq": { + "name": "gap_seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pending_encrypted": { + "name": "pending_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "pending_seq": { + "name": "pending_seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "lease_owner": { + "name": "lease_owner", + "type": "varchar(36)", + "primaryKey": false, + "notNull": false + }, + "lease_until": { + "name": "lease_until", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stopped": { + "name": "stopped", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_events_due_idx": { + "name": "mcp_events_due_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_event_subscriptions\".\"stopped\" = false AND \"mcp_event_subscriptions\".\"gap_seq\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_events_grant_idx": { + "name": "mcp_events_grant_idx", + "columns": [ + { + "expression": "grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_events_expiry_idx": { + "name": "mcp_events_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_event_subscriptions_grant_id_oauth_grants_id_fk": { + "name": "mcp_event_subscriptions_grant_id_oauth_grants_id_fk", + "tableFrom": "mcp_event_subscriptions", + "tableTo": "oauth_grants", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_events_cursor_nonnegative": { + "name": "mcp_events_cursor_nonnegative", + "value": "\"mcp_event_subscriptions\".\"cursor_seq\" >= 0 AND (\"mcp_event_subscriptions\".\"gap_seq\" IS NULL OR \"mcp_event_subscriptions\".\"gap_seq\" >= \"mcp_event_subscriptions\".\"cursor_seq\")" + }, + "mcp_events_pending_pair": { + "name": "mcp_events_pending_pair", + "value": "(\"mcp_event_subscriptions\".\"pending_encrypted\" IS NULL) = (\"mcp_event_subscriptions\".\"pending_seq\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.account_identities": { + "name": "account_identities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "subject_hash": { + "name": "subject_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "account_identities_provider_subject_unique": { + "name": "account_identities_provider_subject_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "account_identities_user_idx": { + "name": "account_identities_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_identities_user_id_users_id_fk": { + "name": "account_identities_user_id_users_id_fk", + "tableFrom": "account_identities", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_auth_attempts": { + "name": "provider_auth_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "account_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "purpose": { + "name": "purpose", + "type": "provider_auth_purpose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "provider_auth_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending_provider'" + }, + "state_hash": { + "name": "state_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "nonce_hash": { + "name": "nonce_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "nonce_encrypted": { + "name": "nonce_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "pkce_verifier_encrypted": { + "name": "pkce_verifier_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "continuation_hash": { + "name": "continuation_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "subject_hash": { + "name": "subject_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "pending_profile_encrypted": { + "name": "pending_profile_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "confirmation_email": { + "name": "confirmation_email", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "challenge_token": { + "name": "challenge_token", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "app_callback_scheme": { + "name": "app_callback_scheme", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "app_code_challenge": { + "name": "app_code_challenge", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "oauth_auth_request_id": { + "name": "oauth_auth_request_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "login_transaction_id": { + "name": "login_transaction_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "client": { + "name": "client", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "inline_user_id": { + "name": "inline_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "inline_token_encrypted": { + "name": "inline_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "ticket_hash": { + "name": "ticket_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "date": { + "name": "date", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "used_at": { + "name": "used_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "provider_auth_attempts_state_unique": { + "name": "provider_auth_attempts_state_unique", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_auth_attempts_ticket_unique": { + "name": "provider_auth_attempts_ticket_unique", + "columns": [ + { + "expression": "ticket_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_auth_attempts_expiry_idx": { + "name": "provider_auth_attempts_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_auth_attempts_oauth_request_idx": { + "name": "provider_auth_attempts_oauth_request_idx", + "columns": [ + { + "expression": "oauth_auth_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_auth_attempts_inline_user_idx": { + "name": "provider_auth_attempts_inline_user_idx", + "columns": [ + { + "expression": "inline_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "provider_auth_attempts_login_transaction_idx": { + "name": "provider_auth_attempts_login_transaction_idx", + "columns": [ + { + "expression": "login_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_auth_attempts_inline_user_id_users_id_fk": { + "name": "provider_auth_attempts_inline_user_id_users_id_fk", + "tableFrom": "provider_auth_attempts", + "tableTo": "users", + "columnsFrom": [ + "inline_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "provider_auth_attempts_oauth_request_fk": { + "name": "provider_auth_attempts_oauth_request_fk", + "tableFrom": "provider_auth_attempts", + "tableTo": "oauth_auth_requests", + "columnsFrom": [ + "oauth_auth_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "provider_auth_attempts_login_transaction_fk": { + "name": "provider_auth_attempts_login_transaction_fk", + "tableFrom": "provider_auth_attempts", + "tableTo": "login_transactions", + "columnsFrom": [ + "login_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.login_transactions": { + "name": "login_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "capability_hash": { + "name": "capability_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "browser_csrf_hash": { + "name": "browser_csrf_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "target_kind": { + "name": "target_kind", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "inline_protocol_auth_key_id": { + "name": "inline_protocol_auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "oauth_auth_request_id": { + "name": "oauth_auth_request_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "native_app_auth_request_id": { + "name": "native_app_auth_request_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "verification_code": { + "name": "verification_code", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "pending_identifier": { + "name": "pending_identifier", + "type": "varchar(320)", + "primaryKey": false, + "notNull": false + }, + "challenge_token": { + "name": "challenge_token", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "client": { + "name": "client", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_method": { + "name": "auth_method", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "login_transactions_capability_unique": { + "name": "login_transactions_capability_unique", + "columns": [ + { + "expression": "capability_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_transactions_expiry_idx": { + "name": "login_transactions_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_transactions_user_idx": { + "name": "login_transactions_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_transactions_inline_protocol_key_unique": { + "name": "login_transactions_inline_protocol_key_unique", + "columns": [ + { + "expression": "inline_protocol_auth_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"login_transactions\".\"status\" in ('pending', 'claimed')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_transactions_oauth_request_unique": { + "name": "login_transactions_oauth_request_unique", + "columns": [ + { + "expression": "oauth_auth_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"login_transactions\".\"status\" in ('pending', 'claimed')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "login_transactions_native_app_request_unique": { + "name": "login_transactions_native_app_request_unique", + "columns": [ + { + "expression": "native_app_auth_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"login_transactions\".\"status\" in ('pending', 'claimed')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "login_transactions_inline_protocol_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk": { + "name": "login_transactions_inline_protocol_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk", + "tableFrom": "login_transactions", + "tableTo": "inline_protocol_auth_keys", + "columnsFrom": [ + "inline_protocol_auth_key_id" + ], + "columnsTo": [ + "auth_key_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "login_transactions_oauth_auth_request_id_oauth_auth_requests_id_fk": { + "name": "login_transactions_oauth_auth_request_id_oauth_auth_requests_id_fk", + "tableFrom": "login_transactions", + "tableTo": "oauth_auth_requests", + "columnsFrom": [ + "oauth_auth_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "login_transactions_native_app_auth_request_id_native_app_auth_requests_id_fk": { + "name": "login_transactions_native_app_auth_request_id_native_app_auth_requests_id_fk", + "tableFrom": "login_transactions", + "tableTo": "native_app_auth_requests", + "columnsFrom": [ + "native_app_auth_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "login_transactions_user_id_users_id_fk": { + "name": "login_transactions_user_id_users_id_fk", + "tableFrom": "login_transactions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "login_transactions_capability_hash_length": { + "name": "login_transactions_capability_hash_length", + "value": "octet_length(\"login_transactions\".\"capability_hash\") = 32" + }, + "login_transactions_browser_csrf_hash_length": { + "name": "login_transactions_browser_csrf_hash_length", + "value": "\"login_transactions\".\"browser_csrf_hash\" is null or octet_length(\"login_transactions\".\"browser_csrf_hash\") = 32" + }, + "login_transactions_status_valid": { + "name": "login_transactions_status_valid", + "value": "\"login_transactions\".\"status\" in ('pending', 'claimed', 'complete', 'cancelled')" + }, + "login_transactions_method_valid": { + "name": "login_transactions_method_valid", + "value": "\"login_transactions\".\"auth_method\" is null or \"login_transactions\".\"auth_method\" in ('email', 'phone', 'google', 'apple')" + }, + "login_transactions_typed_target": { + "name": "login_transactions_typed_target", + "value": "(\n (\"login_transactions\".\"target_kind\" = 'inline_protocol_key')::int +\n (\"login_transactions\".\"target_kind\" = 'oauth_authorization')::int +\n (\"login_transactions\".\"target_kind\" = 'native_app')::int\n ) = 1 and\n (\"login_transactions\".\"inline_protocol_auth_key_id\" is not null)::int +\n (\"login_transactions\".\"oauth_auth_request_id\" is not null)::int +\n (\"login_transactions\".\"native_app_auth_request_id\" is not null)::int = 1 and\n (\"login_transactions\".\"target_kind\" = 'inline_protocol_key') = (\"login_transactions\".\"inline_protocol_auth_key_id\" is not null) and\n (\"login_transactions\".\"target_kind\" = 'oauth_authorization') = (\"login_transactions\".\"oauth_auth_request_id\" is not null) and\n (\"login_transactions\".\"target_kind\" = 'native_app') = (\"login_transactions\".\"native_app_auth_request_id\" is not null)\n " + } + }, + "isRLSEnabled": false + }, + "public.native_app_auth_requests": { + "name": "native_app_auth_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "varchar(128)", + "primaryKey": true, + "notNull": true + }, + "callback_scheme": { + "name": "callback_scheme", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "code_challenge": { + "name": "code_challenge", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "client": { + "name": "client", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_method": { + "name": "auth_method", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "ticket_hash": { + "name": "ticket_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "proven_at": { + "name": "proven_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "native_app_auth_requests_expiry_idx": { + "name": "native_app_auth_requests_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "native_app_auth_requests_ticket_unique": { + "name": "native_app_auth_requests_ticket_unique", + "columns": [ + { + "expression": "ticket_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_app_auth_requests_user_id_users_id_fk": { + "name": "native_app_auth_requests_user_id_users_id_fk", + "tableFrom": "native_app_auth_requests", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "native_app_auth_requests_method_valid": { + "name": "native_app_auth_requests_method_valid", + "value": "\"native_app_auth_requests\".\"auth_method\" is null or \"native_app_auth_requests\".\"auth_method\" in ('email', 'phone', 'google', 'apple')" + } + }, + "isRLSEnabled": false + }, + "public.email_campaign_recipients": { + "name": "email_campaign_recipients", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "campaign_id": { + "name": "campaign_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "email_key": { + "name": "email_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "email_encrypted": { + "name": "email_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "name_encrypted": { + "name": "name_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "unsubscribe_token_hash": { + "name": "unsubscribe_token_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "unsubscribe_token_encrypted": { + "name": "unsubscribe_token_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "sources": { + "name": "sources", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "provider": { + "name": "provider", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "provider_message_id": { + "name": "provider_message_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "contacted_at": { + "name": "contacted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_campaign_recipients_campaign_email_unique": { + "name": "email_campaign_recipients_campaign_email_unique", + "columns": [ + { + "expression": "campaign_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_campaign_recipients_unsubscribe_token_unique": { + "name": "email_campaign_recipients_unsubscribe_token_unique", + "columns": [ + { + "expression": "unsubscribe_token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_campaign_recipients_campaign_status_idx": { + "name": "email_campaign_recipients_campaign_status_idx", + "columns": [ + { + "expression": "campaign_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_campaign_recipients_email_key_idx": { + "name": "email_campaign_recipients_email_key_idx", + "columns": [ + { + "expression": "email_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_campaign_recipients_campaign_id_email_campaigns_id_fk": { + "name": "email_campaign_recipients_campaign_id_email_campaigns_id_fk", + "tableFrom": "email_campaign_recipients", + "tableTo": "email_campaigns", + "columnsFrom": [ + "campaign_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_campaign_recipients_status_check": { + "name": "email_campaign_recipients_status_check", + "value": "\"email_campaign_recipients\".\"status\" in ('pending', 'provider_synced', 'sending', 'provider_accepted', 'unknown', 'suppressed')" + } + }, + "isRLSEnabled": false + }, + "public.email_campaigns": { + "name": "email_campaigns", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true + }, + "series_key": { + "name": "series_key", + "type": "varchar(160)", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "varchar(240)", + "primaryKey": false, + "notNull": true + }, + "preview_text": { + "name": "preview_text", + "type": "varchar(240)", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "audience": { + "name": "audience", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "from_address": { + "name": "from_address", + "type": "varchar(160)", + "primaryKey": false, + "notNull": true, + "default": "'team@inline.chat'" + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'frozen'" + }, + "recipient_count": { + "name": "recipient_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "provider_segment_id": { + "name": "provider_segment_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "provider_campaign_id": { + "name": "provider_campaign_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "visible_unsubscribe": { + "name": "visible_unsubscribe", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "unsubscribe_override_reason": { + "name": "unsubscribe_override_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "test_sent_at": { + "name": "test_sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "frozen_at": { + "name": "frozen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "email_campaigns_name_unique": { + "name": "email_campaigns_name_unique", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "email_campaigns_created_at_idx": { + "name": "email_campaigns_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "email_campaigns_created_by_user_id_users_id_fk": { + "name": "email_campaigns_created_by_user_id_users_id_fk", + "tableFrom": "email_campaigns", + "tableTo": "users", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_campaigns_provider_check": { + "name": "email_campaigns_provider_check", + "value": "\"email_campaigns\".\"provider\" is null or \"email_campaigns\".\"provider\" in ('resend', 'ses')" + }, + "email_campaigns_from_address_check": { + "name": "email_campaigns_from_address_check", + "value": "\"email_campaigns\".\"from_address\" in ('team@inline.chat', 'founders@inline.chat', 'mo@inline.chat')" + }, + "email_campaigns_status_check": { + "name": "email_campaigns_status_check", + "value": "\"email_campaigns\".\"status\" in ('frozen', 'sending', 'paused', 'completed')" + } + }, + "isRLSEnabled": false + }, + "public.email_suppressions": { + "name": "email_suppressions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email_key": { + "name": "email_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "email_encrypted": { + "name": "email_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "email_suppressions_email_key_unique": { + "name": "email_suppressions_email_key_unique", + "columns": [ + { + "expression": "email_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "email_suppressions_reason_check": { + "name": "email_suppressions_reason_check", + "value": "\"email_suppressions\".\"reason\" in ('unsubscribe', 'manual', 'bounce', 'complaint', 'invalid')" + } + }, + "isRLSEnabled": false + }, + "public.server_config": { + "name": "server_config", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(160)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "updated_by_user_id": { + "name": "updated_by_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "server_config_updated_by_user_id_users_id_fk": { + "name": "server_config_updated_by_user_id_users_id_fk", + "tableFrom": "server_config", + "tableTo": "users", + "columnsFrom": [ + "updated_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "server_config_version_check": { + "name": "server_config_version_check", + "value": "\"server_config\".\"version\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.reserved_usernames": { + "name": "reserved_usernames", + "schema": "", + "columns": { + "username": { + "name": "username", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.inline_protocol_auth_challenges": { + "name": "inline_protocol_auth_challenges", + "schema": "", + "columns": { + "challenge_id": { + "name": "challenge_id", + "type": "bytea", + "primaryKey": true, + "notNull": true + }, + "auth_key_id": { + "name": "auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "identifier_encrypted": { + "name": "identifier_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "identifier_hash": { + "name": "identifier_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "code_mac": { + "name": "code_mac", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "pepper_key_id": { + "name": "pepper_key_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "delivery": { + "name": "delivery", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "client": { + "name": "client", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "network_hash": { + "name": "network_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "device_hash": { + "name": "device_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inline_protocol_auth_challenges_key_created_idx": { + "name": "inline_protocol_auth_challenges_key_created_idx", + "columns": [ + { + "expression": "auth_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_auth_challenges_identifier_created_idx": { + "name": "inline_protocol_auth_challenges_identifier_created_idx", + "columns": [ + { + "expression": "identifier_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_auth_challenges_expiry_idx": { + "name": "inline_protocol_auth_challenges_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inline_protocol_auth_challenges_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk": { + "name": "inline_protocol_auth_challenges_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk", + "tableFrom": "inline_protocol_auth_challenges", + "tableTo": "inline_protocol_auth_keys", + "columnsFrom": [ + "auth_key_id" + ], + "columnsTo": [ + "auth_key_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_protocol_auth_challenges_id_length": { + "name": "inline_protocol_auth_challenges_id_length", + "value": "octet_length(\"inline_protocol_auth_challenges\".\"challenge_id\") = 32" + }, + "inline_protocol_auth_challenges_key_id_length": { + "name": "inline_protocol_auth_challenges_key_id_length", + "value": "octet_length(\"inline_protocol_auth_challenges\".\"auth_key_id\") = 8" + }, + "inline_protocol_auth_challenges_identifier_hash_length": { + "name": "inline_protocol_auth_challenges_identifier_hash_length", + "value": "octet_length(\"inline_protocol_auth_challenges\".\"identifier_hash\") = 32" + }, + "inline_protocol_auth_challenges_code_mac_length": { + "name": "inline_protocol_auth_challenges_code_mac_length", + "value": "octet_length(\"inline_protocol_auth_challenges\".\"code_mac\") = 32" + } + }, + "isRLSEnabled": false + }, + "public.inline_protocol_auth_keys": { + "name": "inline_protocol_auth_keys", + "schema": "", + "columns": { + "auth_key_id": { + "name": "auth_key_id", + "type": "bytea", + "primaryKey": true, + "notNull": true + }, + "auth_key_encrypted": { + "name": "auth_key_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "key_encryption_key_id": { + "name": "key_encryption_key_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "current_server_salt": { + "name": "current_server_salt", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "previous_server_salt": { + "name": "previous_server_salt", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "server_salt_updated_at": { + "name": "server_salt_updated_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "account_session_id": { + "name": "account_session_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "authorized_at": { + "name": "authorized_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inline_protocol_auth_keys_user_idx": { + "name": "inline_protocol_auth_keys_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_auth_keys_session_idx": { + "name": "inline_protocol_auth_keys_session_idx", + "columns": [ + { + "expression": "account_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_auth_keys_expiry_idx": { + "name": "inline_protocol_auth_keys_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inline_protocol_auth_keys_user_id_users_id_fk": { + "name": "inline_protocol_auth_keys_user_id_users_id_fk", + "tableFrom": "inline_protocol_auth_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "inline_protocol_auth_keys_account_session_id_sessions_id_fk": { + "name": "inline_protocol_auth_keys_account_session_id_sessions_id_fk", + "tableFrom": "inline_protocol_auth_keys", + "tableTo": "sessions", + "columnsFrom": [ + "account_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_protocol_auth_keys_id_length": { + "name": "inline_protocol_auth_keys_id_length", + "value": "octet_length(\"inline_protocol_auth_keys\".\"auth_key_id\") = 8" + }, + "inline_protocol_auth_keys_encrypted_length": { + "name": "inline_protocol_auth_keys_encrypted_length", + "value": "octet_length(\"inline_protocol_auth_keys\".\"auth_key_encrypted\") = 284" + } + }, + "isRLSEnabled": false + }, + "public.inline_protocol_requests": { + "name": "inline_protocol_requests", + "schema": "", + "columns": { + "auth_key_id": { + "name": "auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "protocol_session_id": { + "name": "protocol_session_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "request_digest": { + "name": "request_digest", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "result_body": { + "name": "result_body", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "result_format": { + "name": "result_format", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "inline_protocol_requests_expiry_idx": { + "name": "inline_protocol_requests_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "inline_protocol_requests_pk": { + "name": "inline_protocol_requests_pk", + "columns": [ + "auth_key_id", + "protocol_session_id", + "message_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_protocol_requests_key_id_length": { + "name": "inline_protocol_requests_key_id_length", + "value": "octet_length(\"inline_protocol_requests\".\"auth_key_id\") = 8" + }, + "inline_protocol_requests_digest_length": { + "name": "inline_protocol_requests_digest_length", + "value": "octet_length(\"inline_protocol_requests\".\"request_digest\") = 32" + }, + "inline_protocol_requests_result_length": { + "name": "inline_protocol_requests_result_length", + "value": "\"inline_protocol_requests\".\"result_body\" is null or octet_length(\"inline_protocol_requests\".\"result_body\") <= 16777278" + }, + "inline_protocol_requests_result_format": { + "name": "inline_protocol_requests_result_format", + "value": "(\"inline_protocol_requests\".\"result_format\" = 0 and (\"inline_protocol_requests\".\"result_body\" is null or octet_length(\"inline_protocol_requests\".\"result_body\") <= 16777216)) or (\"inline_protocol_requests\".\"result_format\" = 1 and \"inline_protocol_requests\".\"result_body\" is not null and octet_length(\"inline_protocol_requests\".\"result_body\") >= 31)" + } + }, + "isRLSEnabled": false + }, + "public.inline_protocol_uploads": { + "name": "inline_protocol_uploads", + "schema": "", + "columns": { + "upload_id": { + "name": "upload_id", + "type": "bytea", + "primaryKey": true, + "notNull": true + }, + "capability_hash": { + "name": "capability_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "permanent_auth_key_id": { + "name": "permanent_auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "issuing_temporary_auth_key_id": { + "name": "issuing_temporary_auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "account_session_id": { + "name": "account_session_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "byte_count": { + "name": "byte_count", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "lock_token": { + "name": "lock_token", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "file_unique_id": { + "name": "file_unique_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inline_protocol_uploads_capability_unique": { + "name": "inline_protocol_uploads_capability_unique", + "columns": [ + { + "expression": "capability_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_uploads_session_idx": { + "name": "inline_protocol_uploads_session_idx", + "columns": [ + { + "expression": "account_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_protocol_uploads_expiry_idx": { + "name": "inline_protocol_uploads_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inline_protocol_uploads_permanent_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk": { + "name": "inline_protocol_uploads_permanent_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk", + "tableFrom": "inline_protocol_uploads", + "tableTo": "inline_protocol_auth_keys", + "columnsFrom": [ + "permanent_auth_key_id" + ], + "columnsTo": [ + "auth_key_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inline_protocol_uploads_user_id_users_id_fk": { + "name": "inline_protocol_uploads_user_id_users_id_fk", + "tableFrom": "inline_protocol_uploads", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inline_protocol_uploads_account_session_id_sessions_id_fk": { + "name": "inline_protocol_uploads_account_session_id_sessions_id_fk", + "tableFrom": "inline_protocol_uploads", + "tableTo": "sessions", + "columnsFrom": [ + "account_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_protocol_uploads_id_length": { + "name": "inline_protocol_uploads_id_length", + "value": "octet_length(\"inline_protocol_uploads\".\"upload_id\") = 16" + }, + "inline_protocol_uploads_capability_hash_length": { + "name": "inline_protocol_uploads_capability_hash_length", + "value": "octet_length(\"inline_protocol_uploads\".\"capability_hash\") = 32" + }, + "inline_protocol_uploads_permanent_key_length": { + "name": "inline_protocol_uploads_permanent_key_length", + "value": "octet_length(\"inline_protocol_uploads\".\"permanent_auth_key_id\") = 8" + }, + "inline_protocol_uploads_temporary_key_length": { + "name": "inline_protocol_uploads_temporary_key_length", + "value": "octet_length(\"inline_protocol_uploads\".\"issuing_temporary_auth_key_id\") = 8" + }, + "inline_protocol_uploads_sha_length": { + "name": "inline_protocol_uploads_sha_length", + "value": "octet_length(\"inline_protocol_uploads\".\"sha256\") = 32" + }, + "inline_protocol_uploads_byte_count_positive": { + "name": "inline_protocol_uploads_byte_count_positive", + "value": "\"inline_protocol_uploads\".\"byte_count\" > 0" + }, + "inline_protocol_uploads_status_valid": { + "name": "inline_protocol_uploads_status_valid", + "value": "\"inline_protocol_uploads\".\"status\" in ('pending', 'uploading', 'complete', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.inline_upload_parts": { + "name": "inline_upload_parts", + "schema": "", + "columns": { + "upload_id": { + "name": "upload_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "part_index": { + "name": "part_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "byte_count": { + "name": "byte_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "stored_byte_count": { + "name": "stored_byte_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "stored_sha256": { + "name": "stored_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "inline_upload_parts_upload_id_inline_uploads_id_fk": { + "name": "inline_upload_parts_upload_id_inline_uploads_id_fk", + "tableFrom": "inline_upload_parts", + "tableTo": "inline_uploads", + "columnsFrom": [ + "upload_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "inline_upload_parts_pk": { + "name": "inline_upload_parts_pk", + "columns": [ + "upload_id", + "part_index" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_upload_parts_index_valid": { + "name": "inline_upload_parts_index_valid", + "value": "\"inline_upload_parts\".\"part_index\" >= 0" + }, + "inline_upload_parts_byte_count_valid": { + "name": "inline_upload_parts_byte_count_valid", + "value": "\"inline_upload_parts\".\"byte_count\" between 1 and 524288" + }, + "inline_upload_parts_sha_length": { + "name": "inline_upload_parts_sha_length", + "value": "octet_length(\"inline_upload_parts\".\"sha256\") = 32" + }, + "inline_upload_parts_stored_byte_count_valid": { + "name": "inline_upload_parts_stored_byte_count_valid", + "value": "\"inline_upload_parts\".\"stored_byte_count\" > 0" + }, + "inline_upload_parts_stored_sha_length": { + "name": "inline_upload_parts_stored_sha_length", + "value": "octet_length(\"inline_upload_parts\".\"stored_sha256\") = 32" + } + }, + "isRLSEnabled": false + }, + "public.inline_upload_storage_parts": { + "name": "inline_upload_storage_parts", + "schema": "", + "columns": { + "upload_id": { + "name": "upload_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "storage_upload_id": { + "name": "storage_upload_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "part_number": { + "name": "part_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "stored_byte_count": { + "name": "stored_byte_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "stored_sha256": { + "name": "stored_sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "inline_upload_storage_parts_upload_id_inline_uploads_id_fk": { + "name": "inline_upload_storage_parts_upload_id_inline_uploads_id_fk", + "tableFrom": "inline_upload_storage_parts", + "tableTo": "inline_uploads", + "columnsFrom": [ + "upload_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "inline_upload_storage_parts_pk": { + "name": "inline_upload_storage_parts_pk", + "columns": [ + "upload_id", + "part_number" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_upload_storage_parts_number_valid": { + "name": "inline_upload_storage_parts_number_valid", + "value": "\"inline_upload_storage_parts\".\"part_number\" between 1 and 1000" + }, + "inline_upload_storage_parts_byte_count_valid": { + "name": "inline_upload_storage_parts_byte_count_valid", + "value": "\"inline_upload_storage_parts\".\"stored_byte_count\" > 0" + }, + "inline_upload_storage_parts_sha_length": { + "name": "inline_upload_storage_parts_sha_length", + "value": "octet_length(\"inline_upload_storage_parts\".\"stored_sha256\") = 32" + }, + "inline_upload_storage_parts_etag_valid": { + "name": "inline_upload_storage_parts_etag_valid", + "value": "length(\"inline_upload_storage_parts\".\"etag\") between 1 and 128" + } + }, + "isRLSEnabled": false + }, + "public.inline_uploads": { + "name": "inline_uploads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigint", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "inline_uploads_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "upload_id": { + "name": "upload_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "client_upload_id": { + "name": "client_upload_id", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "permanent_auth_key_id": { + "name": "permanent_auth_key_id", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "account_session_id": { + "name": "account_session_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "byte_count": { + "name": "byte_count", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "sha256": { + "name": "sha256", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "thumbnail_file_unique_id": { + "name": "thumbnail_file_unique_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "video_width": { + "name": "video_width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "video_height": { + "name": "video_height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "duration": { + "name": "duration", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_animated": { + "name": "is_animated", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "has_audio": { + "name": "has_audio", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "waveform": { + "name": "waveform", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "part_size": { + "name": "part_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "part_count": { + "name": "part_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'uploading'" + }, + "failure_code": { + "name": "failure_code", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "failure_retryable": { + "name": "failure_retryable", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "lock_token": { + "name": "lock_token", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "storage_format": { + "name": "storage_format", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "storage_upload_id": { + "name": "storage_upload_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "retry_at": { + "name": "retry_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "result_file_unique_id": { + "name": "result_file_unique_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "result_media_id": { + "name": "result_media_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_part_at": { + "name": "last_part_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "hard_expires_at": { + "name": "hard_expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inline_uploads_upload_id_unique": { + "name": "inline_uploads_upload_id_unique", + "columns": [ + { + "expression": "upload_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_session_client_id_unique": { + "name": "inline_uploads_session_client_id_unique", + "columns": [ + { + "expression": "account_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_upload_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_permanent_key_idx": { + "name": "inline_uploads_permanent_key_idx", + "columns": [ + { + "expression": "permanent_auth_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_user_status_idx": { + "name": "inline_uploads_user_status_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_session_created_idx": { + "name": "inline_uploads_session_created_idx", + "columns": [ + { + "expression": "account_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_expiry_idx": { + "name": "inline_uploads_expiry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inline_uploads_processing_retry_idx": { + "name": "inline_uploads_processing_retry_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "inline_uploads_permanent_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk": { + "name": "inline_uploads_permanent_auth_key_id_inline_protocol_auth_keys_auth_key_id_fk", + "tableFrom": "inline_uploads", + "tableTo": "inline_protocol_auth_keys", + "columnsFrom": [ + "permanent_auth_key_id" + ], + "columnsTo": [ + "auth_key_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inline_uploads_user_id_users_id_fk": { + "name": "inline_uploads_user_id_users_id_fk", + "tableFrom": "inline_uploads", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "inline_uploads_account_session_id_sessions_id_fk": { + "name": "inline_uploads_account_session_id_sessions_id_fk", + "tableFrom": "inline_uploads", + "tableTo": "sessions", + "columnsFrom": [ + "account_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "inline_uploads_id_length": { + "name": "inline_uploads_id_length", + "value": "octet_length(\"inline_uploads\".\"upload_id\") = 16" + }, + "inline_uploads_client_id_length": { + "name": "inline_uploads_client_id_length", + "value": "octet_length(\"inline_uploads\".\"client_upload_id\") = 16" + }, + "inline_uploads_permanent_key_length": { + "name": "inline_uploads_permanent_key_length", + "value": "octet_length(\"inline_uploads\".\"permanent_auth_key_id\") = 8" + }, + "inline_uploads_sha_length": { + "name": "inline_uploads_sha_length", + "value": "octet_length(\"inline_uploads\".\"sha256\") = 32" + }, + "inline_uploads_byte_count_positive": { + "name": "inline_uploads_byte_count_positive", + "value": "\"inline_uploads\".\"byte_count\" > 0" + }, + "inline_uploads_part_size_valid": { + "name": "inline_uploads_part_size_valid", + "value": "\"inline_uploads\".\"part_size\" = 524288" + }, + "inline_uploads_part_count_valid": { + "name": "inline_uploads_part_count_valid", + "value": "\"inline_uploads\".\"part_count\" between 1 and 1000" + }, + "inline_uploads_kind_valid": { + "name": "inline_uploads_kind_valid", + "value": "\"inline_uploads\".\"kind\" in ('photo', 'video', 'document', 'voice')" + }, + "inline_uploads_status_valid": { + "name": "inline_uploads_status_valid", + "value": "\"inline_uploads\".\"status\" in ('uploading', 'processing', 'complete', 'failed', 'canceled')" + }, + "inline_uploads_storage_format_valid": { + "name": "inline_uploads_storage_format_valid", + "value": "\"inline_uploads\".\"storage_format\" is null or \"inline_uploads\".\"storage_format\" = 'identity_v1'" + }, + "inline_uploads_storage_session_valid": { + "name": "inline_uploads_storage_session_valid", + "value": "\"inline_uploads\".\"storage_upload_id\" is null or \"inline_uploads\".\"storage_format\" is not null" + }, + "inline_uploads_attempts_valid": { + "name": "inline_uploads_attempts_valid", + "value": "\"inline_uploads\".\"attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.agent_session_messages": { + "name": "agent_session_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "agent_session_id": { + "name": "agent_session_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "source_key_hash": { + "name": "source_key_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "item_key_hash": { + "name": "item_key_hash", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "source_ref_encrypted": { + "name": "source_ref_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "revision_ref_encrypted": { + "name": "revision_ref_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "message_global_id": { + "name": "message_global_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "relation": { + "name": "relation", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "complete": { + "name": "complete", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_session_messages_source_unique": { + "name": "agent_session_messages_source_unique", + "columns": [ + { + "expression": "agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_messages_item_unique": { + "name": "agent_session_messages_item_unique", + "columns": [ + { + "expression": "agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "item_key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_session_messages\".\"item_key_hash\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_messages_message_unique": { + "name": "agent_session_messages_message_unique", + "columns": [ + { + "expression": "agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_global_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_session_messages\".\"message_global_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_session_messages_session_idx": { + "name": "agent_session_messages_session_idx", + "columns": [ + { + "expression": "agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_session_messages_agent_session_id_agent_sessions_id_fk": { + "name": "agent_session_messages_agent_session_id_agent_sessions_id_fk", + "tableFrom": "agent_session_messages", + "tableTo": "agent_sessions", + "columnsFrom": [ + "agent_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_session_messages_message_global_id_messages_global_id_fk": { + "name": "agent_session_messages_message_global_id_messages_global_id_fk", + "tableFrom": "agent_session_messages", + "tableTo": "messages", + "columnsFrom": [ + "message_global_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_sessions": { + "name": "agent_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "session_key_hash": { + "name": "session_key_hash", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "instance_ref_encrypted": { + "name": "instance_ref_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "session_ref_encrypted": { + "name": "session_ref_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "project_ref_encrypted": { + "name": "project_ref_encrypted", + "type": "bytea", + "primaryKey": false, + "notNull": false + }, + "status_message_global_id": { + "name": "status_message_global_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "agent_sessions_external_unique": { + "name": "agent_sessions_external_unique", + "columns": [ + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "session_key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_sessions_chat_bot_unique": { + "name": "agent_sessions_chat_bot_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bot_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_sessions_owner_idx": { + "name": "agent_sessions_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_sessions_status_message_unique": { + "name": "agent_sessions_status_message_unique", + "columns": [ + { + "expression": "status_message_global_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_sessions\".\"status_message_global_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_sessions_chat_id_chats_id_fk": { + "name": "agent_sessions_chat_id_chats_id_fk", + "tableFrom": "agent_sessions", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_sessions_bot_user_id_users_id_fk": { + "name": "agent_sessions_bot_user_id_users_id_fk", + "tableFrom": "agent_sessions", + "tableTo": "users", + "columnsFrom": [ + "bot_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_sessions_owner_user_id_users_id_fk": { + "name": "agent_sessions_owner_user_id_users_id_fk", + "tableFrom": "agent_sessions", + "tableTo": "users", + "columnsFrom": [ + "owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "agent_sessions_status_message_global_id_messages_global_id_fk": { + "name": "agent_sessions_status_message_global_id_messages_global_id_fk", + "tableFrom": "agent_sessions", + "tableTo": "messages", + "columnsFrom": [ + "status_message_global_id" + ], + "columnsTo": [ + "global_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_delivery_budgets": { + "name": "auth_delivery_budgets", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(96)", + "primaryKey": true, + "notNull": true + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "auth_delivery_budgets_expiry_idx": { + "name": "auth_delivery_budgets_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auth_delivery_budgets_attempts_positive": { + "name": "auth_delivery_budgets_attempts_positive", + "value": "\"auth_delivery_budgets\".\"attempts\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.recent_realtime_buckets": { + "name": "recent_realtime_buckets", + "schema": "", + "columns": { + "bucket": { + "name": "bucket", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3) with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "recent_realtime_buckets_expiry_idx": { + "name": "recent_realtime_buckets_expiry_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "recent_realtime_buckets_bucket_entity_id_pk": { + "name": "recent_realtime_buckets_bucket_entity_id_pk", + "columns": [ + "bucket", + "entity_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "recent_realtime_buckets_bucket_valid": { + "name": "recent_realtime_buckets_bucket_valid", + "value": "\"recent_realtime_buckets\".\"bucket\" in (1, 2, 3)" + }, + "recent_realtime_buckets_entity_positive": { + "name": "recent_realtime_buckets_entity_positive", + "value": "\"recent_realtime_buckets\".\"entity_id\" > 0" + }, + "recent_realtime_buckets_seq_positive": { + "name": "recent_realtime_buckets_seq_positive", + "value": "\"recent_realtime_buckets\".\"seq\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.grid_transcription_runs": { + "name": "grid_transcription_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "source_room_id": { + "name": "source_room_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "room_chat_id": { + "name": "room_chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "transcript_chat_id": { + "name": "transcript_chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "destination_parent_chat_id": { + "name": "destination_parent_chat_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "original_anchor_id": { + "name": "original_anchor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "room_link_message_id": { + "name": "room_link_message_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_target": { + "name": "provider_target", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "claim_epoch": { + "name": "claim_epoch", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "worker_id": { + "name": "worker_id", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + }, + "stop_requested_at": { + "name": "stop_requested_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "interruption_reason": { + "name": "interruption_reason", + "type": "varchar(80)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "grid_transcription_request_unique": { + "name": "grid_transcription_request_unique", + "columns": [ + { + "expression": "actor_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_transcription_active_room_unique": { + "name": "grid_transcription_active_room_unique", + "columns": [ + { + "expression": "source_room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"grid_transcription_runs\".\"state\" in ('starting','active','stopping')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_transcription_active_destination_unique": { + "name": "grid_transcription_active_destination_unique", + "columns": [ + { + "expression": "transcript_chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"grid_transcription_runs\".\"state\" in ('starting','active','stopping')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_transcription_space_recent_idx": { + "name": "grid_transcription_space_recent_idx", + "columns": [ + { + "expression": "space_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "grid_transcription_source_room_idx": { + "name": "grid_transcription_source_room_idx", + "columns": [ + { + "expression": "source_room_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "grid_transcription_state_check": { + "name": "grid_transcription_state_check", + "value": "\"grid_transcription_runs\".\"state\" in ('starting','active','stopping','stopped','interrupted')" + } + }, + "isRLSEnabled": false + }, + "public.grid_transcription_worker": { + "name": "grid_transcription_worker", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "worker_id": { + "name": "worker_id", + "type": "varchar(80)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "heartbeat_at": { + "name": "heartbeat_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.grid_transcription_segments": { + "name": "grid_transcription_segments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "claim_epoch": { + "name": "claim_epoch", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "speaker_user_id": { + "name": "speaker_user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "membership_id": { + "name": "membership_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "track_sid": { + "name": "track_sid", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "source_turn_key": { + "name": "source_turn_key", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'admitted'" + }, + "message_id": { + "name": "message_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "admitted_at": { + "name": "admitted_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "grid_transcription_segment_source_unique": { + "name": "grid_transcription_segment_source_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "track_sid", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_turn_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "grid_transcription_segments_run_id_grid_transcription_runs_id_fk": { + "name": "grid_transcription_segments_run_id_grid_transcription_runs_id_fk", + "tableFrom": "grid_transcription_segments", + "tableTo": "grid_transcription_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "grid_transcription_segment_state_check": { + "name": "grid_transcription_segment_state_check", + "value": "\"grid_transcription_segments\".\"state\" in ('admitted','finalized','discarded')" + } + }, + "isRLSEnabled": false + }, + "public.grid_transcription_space_revisions": { + "name": "grid_transcription_space_revisions", + "schema": "", + "columns": { + "space_id": { + "name": "space_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "0" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.client_type": { + "name": "client_type", + "schema": "public", + "values": [ + "ios", + "macos", + "web", + "api", + "android", + "windows", + "linux", + "cli" + ] + }, + "public.member_roles": { + "name": "member_roles", + "schema": "public", + "values": [ + "owner", + "admin", + "member" + ] + }, + "public.chat_types": { + "name": "chat_types", + "schema": "public", + "values": [ + "private", + "thread" + ] + }, + "public.thread_graph_link_kind": { + "name": "thread_graph_link_kind", + "schema": "public", + "values": [ + "thread_link", + "reply_thread" + ] + }, + "public.thread_graph_scope_type": { + "name": "thread_graph_scope_type", + "schema": "public", + "values": [ + "user", + "space" + ] + }, + "public.account_provider": { + "name": "account_provider", + "schema": "public", + "values": [ + "google", + "apple" + ] + }, + "public.provider_auth_purpose": { + "name": "provider_auth_purpose", + "schema": "public", + "values": [ + "app", + "mcp_oauth", + "hosted_login" + ] + }, + "public.provider_auth_status": { + "name": "provider_auth_status", + "schema": "public", + "values": [ + "pending_provider", + "pending_invite", + "pending_email", + "complete", + "used" + ] + } + }, + "schemas": {}, + "sequences": { + "public.user_id": { + "name": "user_id", + "schema": "public", + "increment": "3", + "startWith": "1000", + "minValue": "1000", + "maxValue": "9223372036854775807", + "cache": "100", + "cycle": false + } + }, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/server/drizzle/meta/_journal.json b/server/drizzle/meta/_journal.json index 7e3b202e1..d1cceba42 100644 --- a/server/drizzle/meta/_journal.json +++ b/server/drizzle/meta/_journal.json @@ -1079,6 +1079,20 @@ "when": 1790951215908, "tag": "0153_mcp-events", "breakpoints": true + }, + { + "idx": 154, + "version": "7", + "when": 1790958621324, + "tag": "0154_grid-call-identity", + "breakpoints": true + }, + { + "idx": 155, + "version": "7", + "when": 1791141418996, + "tag": "0155_grid-transcription", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/server/scripts/contracts/route-manifest.ts b/server/scripts/contracts/route-manifest.ts index e868a9b49..c78623dd9 100644 --- a/server/scripts/contracts/route-manifest.ts +++ b/server/scripts/contracts/route-manifest.ts @@ -3,6 +3,7 @@ import { mkdir, readFile } from "node:fs/promises" import { dirname, resolve } from "node:path" import { app } from "../../src/legacyServer" import { makeCandidateHttpApplication } from "../../src/core/http/candidateApplication" +import { gridTranscriptionWorkerPaths } from "../../src/core/http/gridTranscription.effect" import { startCoreHttpServer, type CoreHttpServerHandle, @@ -142,6 +143,7 @@ const routesFromOpenApi = ( } const candidateRawRoutes: ReadonlyArray = [ + ...gridTranscriptionWorkerPaths.map((path) => ({ method: "POST", path })), { method: "GET", path: "//" }, { method: "GET", @@ -206,6 +208,7 @@ const comparableLegacyRoutes = const intentionalCandidateOnly = new Set([ + ...gridTranscriptionWorkerPaths.map((path) => `POST\t${path}`), "DELETE\t/admin/reserved-usernames", "GET\t/", "GET\t/admin/email-campaigns", diff --git a/server/src/__tests__/contracts/fixtures/effect-route-manifest.json b/server/src/__tests__/contracts/fixtures/effect-route-manifest.json index 9ee103434..6a200197f 100644 --- a/server/src/__tests__/contracts/fixtures/effect-route-manifest.json +++ b/server/src/__tests__/contracts/fixtures/effect-route-manifest.json @@ -1,14 +1,14 @@ { "version": 1, - "count": 313, + "count": 319, "methodCounts": { "DELETE": 1, "GET": 153, - "POST": 157, + "POST": 163, "PUT": 1, "WS": 1 }, - "sha256": "907ff942ca7f0e8b9a3c87276faff8ef708516360e8cf2b4c2d7afc725e98777", + "sha256": "9cc716886cf0f2bb569141bc7fe1fcc6558c5953dbb4d13dcd891d891f720a66", "routes": [ { "method": "DELETE", @@ -626,6 +626,30 @@ "method": "GET", "path": "/waitlist/super_secret_sub_count" }, + { + "method": "POST", + "path": "/_internal/grid-transcription/admit" + }, + { + "method": "POST", + "path": "/_internal/grid-transcription/claim" + }, + { + "method": "POST", + "path": "/_internal/grid-transcription/final" + }, + { + "method": "POST", + "path": "/_internal/grid-transcription/heartbeat" + }, + { + "method": "POST", + "path": "/_internal/grid-transcription/renew" + }, + { + "method": "POST", + "path": "/_internal/grid-transcription/stopped" + }, { "method": "POST", "path": "/admin/auth/dev-login" diff --git a/server/src/core/http/candidateApplication.ts b/server/src/core/http/candidateApplication.ts index fc3bf7aa7..e443a0101 100644 --- a/server/src/core/http/candidateApplication.ts +++ b/server/src/core/http/candidateApplication.ts @@ -1,4 +1,5 @@ import { Layer } from "effect" +import { GridTranscriptionWorkerRoutesLive } from "./gridTranscription.effect" import { AuthApiGroup, } from "../../controllers/auth.effect" @@ -70,29 +71,32 @@ export const makeCandidateHttpApplication = ({ const botApi = makeBotApiBase(apiBaseUrl) .add(BotApiGroup) - return makeHttpApplication({ - platform: defineExecutableHttpApi({ - ...defineOpenApiDocument({ - api: platformApi, - jsonPath: "/v1/reference/json", - swaggerPath: "/v1/reference", + return Layer.merge( + makeHttpApplication({ + platform: defineExecutableHttpApi({ + ...defineOpenApiDocument({ + api: platformApi, + jsonPath: "/v1/reference/json", + swaggerPath: "/v1/reference", + }), + handlers: Layer.mergeAll( + AuthRouteGroupLive, + V1IdentitySpacesRouteGroupLive, + V1MessagingProvidersRouteGroupLive, + AuxiliaryRouteGroupLive, + AdminRouteGroupLive, + ), }), - handlers: Layer.mergeAll( - AuthRouteGroupLive, - V1IdentitySpacesRouteGroupLive, - V1MessagingProvidersRouteGroupLive, - AuxiliaryRouteGroupLive, - AdminRouteGroupLive, - ), - }), - bot: defineExecutableHttpApi({ - ...defineOpenApiDocument({ - api: botApi, - jsonPath: "/bot-api-reference/json", - swaggerPath: "/bot-api-reference", + bot: defineExecutableHttpApi({ + ...defineOpenApiDocument({ + api: botApi, + jsonPath: "/bot-api-reference/json", + swaggerPath: "/bot-api-reference", + }), + handlers: BotRouteGroupLive, }), - handlers: BotRouteGroupLive, + middleware, }), - middleware, - }) + GridTranscriptionWorkerRoutesLive, + ) } diff --git a/server/src/core/http/gridTranscription.effect.bun.test.ts b/server/src/core/http/gridTranscription.effect.bun.test.ts new file mode 100644 index 000000000..21fa739ae --- /dev/null +++ b/server/src/core/http/gridTranscription.effect.bun.test.ts @@ -0,0 +1,26 @@ +import { expect, test } from "bun:test" +import { makeCandidateHttpApplication } from "./candidateApplication" +import { gridTranscriptionWorkerPaths } from "./gridTranscription.effect" +import { startCoreHttpServer } from "./host" + +test("production application mounts every internal transcription action with a closed default gate", async () => { + const host = await startCoreHttpServer({ + application: makeCandidateHttpApplication({ middleware: { isProduction: false } }), + hostname: "127.0.0.1", + installSignalHandlers: false, + }) + try { + for (const path of gridTranscriptionWorkerPaths) { + const response = await fetch(`http://127.0.0.1:${host.port}${path}`, { + method: "POST", + body: JSON.stringify({ workerId: "untrusted-worker" }), + headers: { "content-type": "application/json" }, + }) + expect(response.status).toBe(503) + expect(response.headers.get("cache-control")).toBe("no-store") + expect(await response.json()).toEqual({ error: "unavailable" }) + } + } finally { + await host.shutdown() + } +}) diff --git a/server/src/core/http/gridTranscription.effect.ts b/server/src/core/http/gridTranscription.effect.ts new file mode 100644 index 000000000..66c25475f --- /dev/null +++ b/server/src/core/http/gridTranscription.effect.ts @@ -0,0 +1,33 @@ +import { Data, Effect, Layer } from "effect" +import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" +import { handleGridTranscriptionWorkerRequest } from "../../modules/grid/transcription/worker" +import { webResponseToHttpServerResponse } from "./webResponse" + +export const gridTranscriptionWorkerPaths = ["heartbeat", "claim", "renew", "admit", "final", "stopped"].map( + (action) => `/_internal/grid-transcription/${action}` as const, +) + +class GridTranscriptionTransportError extends Data.TaggedError("GridTranscriptionTransportError") {} + +const workerRequest = Effect.gen(function* () { + const request = yield* HttpServerRequest.HttpServerRequest + const webRequest = yield* HttpServerRequest.toWeb(request).pipe( + Effect.mapError(() => new GridTranscriptionTransportError()), + ) + const response = yield* Effect.tryPromise({ + try: () => handleGridTranscriptionWorkerRequest(webRequest), + catch: () => new GridTranscriptionTransportError(), + }) + return webResponseToHttpServerResponse(response) +}).pipe( + Effect.catchTag("GridTranscriptionTransportError", () => + Effect.succeed(HttpServerResponse.jsonUnsafe({ error: "unavailable" }, { status: 503 })), + ), +) + +/** Internal worker authentication stays inside the bounded request handler. */ +export const GridTranscriptionWorkerRoutesLive = Layer.effectDiscard( + HttpRouter.HttpRouter.use((router) => + Effect.forEach(gridTranscriptionWorkerPaths, (path) => router.add("POST", path, workerRequest)), + ), +) diff --git a/server/src/db/models/chats.ts b/server/src/db/models/chats.ts index fdcdbfe25..db53226b5 100644 --- a/server/src/db/models/chats.ts +++ b/server/src/db/models/chats.ts @@ -25,6 +25,7 @@ import { dialogOpenDefaultsForChat, dialogOrderForPlacement, } from "@in/server/modules/dialogOpen" +import { messageActivityPredicate } from "@in/server/modules/message/activity" const log = new Log("chats") @@ -291,7 +292,7 @@ async function refreshLastMessageId(chatId: number) { let [message] = await tx .select() .from(messages) - .where(eq(messages.chatId, chatId)) + .where(and(eq(messages.chatId, chatId), messageActivityPredicate())) .orderBy(desc(messages.messageId)) .limit(1) @@ -319,7 +320,7 @@ async function refreshLastMessageIdTransaction(chatId: number, transaction: (tx: let [message] = await tx .select() .from(messages) - .where(eq(messages.chatId, chatId)) + .where(and(eq(messages.chatId, chatId), messageActivityPredicate())) .orderBy(desc(messages.messageId)) .limit(1) diff --git a/server/src/db/models/messages.ts b/server/src/db/models/messages.ts index 13f387a26..82f301f5e 100644 --- a/server/src/db/models/messages.ts +++ b/server/src/db/models/messages.ts @@ -64,6 +64,8 @@ import { validateBlockContent, } from "@in/server/modules/message/blockContent" import { decryptAgentRef } from "@in/server/modules/agentSessions/crypto" +import { messageActivityPredicate } from "@in/server/modules/message/activity" +import { invalidateGridTranscriptionForHistory } from "@in/server/modules/grid/transcription/state" const log = new Log("MessageModel", LogLevel.INFO) @@ -867,6 +869,19 @@ async function deleteMessages( const messageIdsNum = messageIds.map((id) => Number(id)) + // Orphaning anchored children also mutates their chat rows. Own these + // before the run fence, matching final writers' chat -> run order. + await tx.select({ id: chats.id }).from(chats) + .where(and(eq(chats.parentChatId, chatId), inArray(chats.parentMessageId, messageIdsNum))) + .orderBy(chats.id).for("update") + + // Deleting a visible anchor/link retires capture and future enrollment. + // A finalized turn deletion leaves its admitted/final dedup tombstone. + await invalidateGridTranscriptionForHistory(tx, { + messageRefs: messageIdsNum.map((messageId) => ({ chatId, messageId })), + reason: "message_deleted", + }) + // Clear first to allow for deleting await tx.update(chats).set({ lastMsgId: null }).where(eq(chats.id, chatId)) @@ -896,7 +911,7 @@ async function deleteMessages( let [message] = await tx .select({ messageId: messages.messageId }) .from(messages) - .where(eq(messages.chatId, chatId)) + .where(and(eq(messages.chatId, chatId), messageActivityPredicate())) .orderBy(desc(messages.messageId)) .limit(1) @@ -998,7 +1013,12 @@ async function editMessage(input: EditMessageInput): Promise<{ } const [currentMessage] = await tx - .select({ blockContentId: messages.blockContentId, rev: messages.rev, voiceId: messages.voiceId }) + .select({ + blockContentId: messages.blockContentId, rev: messages.rev, voiceId: messages.voiceId, + systemMessageEncrypted: messages.systemMessageEncrypted, + systemMessageIv: messages.systemMessageIv, + systemMessageTag: messages.systemMessageTag, + }) .from(messages) .where(and(eq(messages.chatId, chatId), eq(messages.messageId, messageId))) .for("update") @@ -1010,6 +1030,14 @@ async function editMessage(input: EditMessageInput): Promise<{ } throw ModelError.MessageInvalid } + if (currentMessage.systemMessageEncrypted && currentMessage.systemMessageIv && currentMessage.systemMessageTag && + decryptSystemMessagePayload({ + encrypted: currentMessage.systemMessageEncrypted, + iv: currentMessage.systemMessageIv, + authTag: currentMessage.systemMessageTag, + }).event.oneofKind === "gridTranscript") { + throw ModelError.MessageInvalid + } if ((input.expectedRevision !== undefined && (currentMessage.rev ?? 0) !== input.expectedRevision) || (input.expectedVoiceId !== undefined && currentMessage.voiceId !== input.expectedVoiceId)) { throw new MessageRevisionConflict() diff --git a/server/src/db/schema/grid.ts b/server/src/db/schema/grid.ts index 06a4abd87..c8acdc92e 100644 --- a/server/src/db/schema/grid.ts +++ b/server/src/db/schema/grid.ts @@ -2,7 +2,19 @@ import { sessions } from "@in/server/db/schema/sessions" import { spaces } from "@in/server/db/schema/spaces" import { lower, users } from "@in/server/db/schema/users" import { sql } from "drizzle-orm" -import { boolean, check, index, integer, pgTable, serial, timestamp, uniqueIndex, uuid, varchar } from "drizzle-orm/pg-core" +import { + bigint, + boolean, + check, + index, + integer, + pgTable, + serial, + timestamp, + uniqueIndex, + uuid, + varchar, +} from "drizzle-orm/pg-core" export const gridRooms = pgTable( "grid_rooms", @@ -16,6 +28,9 @@ export const gridRooms = pgTable( .references(() => users.id, { onDelete: "restrict" }), title: varchar("title", { length: 80 }), locked: boolean("locked").default(false).notNull(), + // Validated against the room Space/private ancestry at use time. No FK: + // ordinary chat deletion must not invert Grid room -> chat lock order. + roomThreadId: integer("room_thread_id"), connectionGeneration: integer("connection_generation").default(0).notNull(), connectionStartedAt: timestamp("connection_started_at", { mode: "date", precision: 3 }), createdAt: timestamp("created_at", { mode: "date", precision: 3 }) @@ -31,11 +46,11 @@ export const gridRooms = pgTable( namedRoomUnique: uniqueIndex("grid_rooms_space_title_unique") .on(table.spaceId, lower(table.title)) .where(sql`${table.title} is not null`), - connectionGenerationCheck: check( - "grid_rooms_connection_generation_check", - sql`${table.connectionGeneration} >= 0`, + connectionGenerationCheck: check("grid_rooms_connection_generation_check", sql`${table.connectionGeneration} >= 0`), + titleNotEmptyCheck: check( + "grid_rooms_title_not_empty_check", + sql`${table.title} is null or length(${table.title}) > 0`, ), - titleNotEmptyCheck: check("grid_rooms_title_not_empty_check", sql`${table.title} is null or length(${table.title}) > 0`), }), ) @@ -57,16 +72,14 @@ export const gridPresence = pgTable( microphoneEnabled: boolean("microphone_enabled").default(false).notNull(), microphoneRevision: integer("microphone_revision").default(0).notNull(), mediaMembershipId: uuid("media_membership_id").defaultRandom().notNull(), + callId: uuid("call_id").defaultRandom().notNull(), leaseExpiresAt: timestamp("lease_expires_at", { mode: "date", precision: 3 }).notNull(), }, (table) => ({ roomIndex: index("grid_presence_room_id_idx").on(table.roomId), ownerSessionIndex: index("grid_presence_owner_session_id_idx").on(table.ownerSessionId), leaseExpiryIndex: index("grid_presence_lease_expires_at_idx").on(table.leaseExpiresAt), - microphoneRevisionCheck: check( - "grid_presence_microphone_revision_check", - sql`${table.microphoneRevision} >= 0`, - ), + microphoneRevisionCheck: check("grid_presence_microphone_revision_check", sql`${table.microphoneRevision} >= 0`), }), ) @@ -126,3 +139,94 @@ export type DbGridPresence = typeof gridPresence.$inferSelect export type DbNewGridPresence = typeof gridPresence.$inferInsert export type DbGridProviderEffect = typeof gridProviderEffects.$inferSelect export type DbNewGridProviderEffect = typeof gridProviderEffects.$inferInsert + +// Source room/chat/message IDs deliberately have no foreign keys: provenance +// and final dedup tombstones survive ordinary room/history deletion. +export const gridTranscriptionRuns = pgTable( + "grid_transcription_runs", + { + id: uuid().primaryKey().defaultRandom(), + sourceRoomId: integer("source_room_id").notNull(), + spaceId: integer("space_id").notNull(), + roomChatId: integer("room_chat_id").notNull(), + transcriptChatId: integer("transcript_chat_id").notNull(), + destinationParentChatId: integer("destination_parent_chat_id").notNull(), + originalAnchorId: integer("original_anchor_id").notNull(), + roomLinkMessageId: integer("room_link_message_id"), + actorUserId: integer("actor_user_id").notNull(), + requestId: uuid("request_id").notNull(), + model: varchar({ length: 32 }).notNull(), + state: varchar({ length: 16 }).notNull(), + generation: integer().notNull(), + providerTarget: varchar("provider_target", { length: 255 }).notNull(), + revision: integer().default(1).notNull(), + claimEpoch: integer("claim_epoch").default(0).notNull(), + workerId: varchar("worker_id", { length: 80 }), + leaseExpiresAt: timestamp("lease_expires_at", { mode: "date", precision: 3 }), + expiresAt: timestamp("expires_at", { mode: "date", precision: 3 }).notNull(), + stopRequestedAt: timestamp("stop_requested_at", { mode: "date", precision: 3 }), + interruptionReason: varchar("interruption_reason", { length: 80 }), + createdAt: timestamp("created_at", { mode: "date", precision: 3 }).defaultNow().notNull(), + }, + (table) => ({ + requestUnique: uniqueIndex("grid_transcription_request_unique").on(table.actorUserId, table.requestId), + activeRoomUnique: uniqueIndex("grid_transcription_active_room_unique") + .on(table.sourceRoomId) + .where(sql`${table.state} in ('starting','active','stopping')`), + activeDestinationUnique: uniqueIndex("grid_transcription_active_destination_unique") + .on(table.transcriptChatId) + .where(sql`${table.state} in ('starting','active','stopping')`), + recentIndex: index("grid_transcription_space_recent_idx").on(table.spaceId, table.createdAt), + roomIndex: index("grid_transcription_source_room_idx").on(table.sourceRoomId), + stateCheck: check( + "grid_transcription_state_check", + sql`${table.state} in ('starting','active','stopping','stopped','interrupted')`, + ), + }), +) + +export const gridTranscriptionSegments = pgTable( + "grid_transcription_segments", + { + id: uuid().primaryKey().defaultRandom(), + runId: uuid("run_id") + .notNull() + .references(() => gridTranscriptionRuns.id), + claimEpoch: integer("claim_epoch").notNull(), + speakerUserId: integer("speaker_user_id").notNull(), + membershipId: uuid("membership_id").notNull(), + trackSid: varchar("track_sid", { length: 128 }).notNull(), + sourceTurnKey: varchar("source_turn_key", { length: 128 }).notNull(), + state: varchar({ length: 16 }).default("admitted").notNull(), + messageId: integer("message_id"), + admittedAt: timestamp("admitted_at", { mode: "date", precision: 3 }).defaultNow().notNull(), + }, + (table) => ({ + sourceUnique: uniqueIndex("grid_transcription_segment_source_unique").on( + table.runId, + table.trackSid, + table.sourceTurnKey, + ), + stateCheck: check( + "grid_transcription_segment_state_check", + sql`${table.state} in ('admitted','finalized','discarded')`, + ), + }), +) + +export const gridTranscriptionWorker = pgTable("grid_transcription_worker", { + id: integer().primaryKey(), + workerId: varchar("worker_id", { length: 80 }).notNull(), + model: varchar({ length: 32 }).notNull(), + heartbeatAt: timestamp("heartbeat_at", { mode: "date", precision: 3 }).notNull(), +}) + +export type DbGridTranscriptionRun = typeof gridTranscriptionRuns.$inferSelect + +/** Separate revision owner avoids Space -> chat / chat -> Space trigger deadlocks. */ +export const gridTranscriptionSpaceRevisions = pgTable("grid_transcription_space_revisions", { + spaceId: integer("space_id").primaryKey(), + revision: bigint({ mode: "bigint" }) + .notNull() + .default(sql`0`), +}) diff --git a/server/src/functions/_functions.ts b/server/src/functions/_functions.ts index 7e799c753..2adcfee07 100644 --- a/server/src/functions/_functions.ts +++ b/server/src/functions/_functions.ts @@ -1,3 +1,4 @@ +import { openGridThread, setGridTranscription, listGridTranscripts } from "./gridTranscription" import { deleteMessage } from "@in/server/functions/messages.deleteMessage" import { deleteMessageAttachment } from "@in/server/functions/messages.deleteMessageAttachment" import { clearChatHistory } from "@in/server/functions/messages.clearChatHistory" @@ -193,6 +194,9 @@ export const Functions = { getUpdates: getUpdates, }, grid: { + openThread: openGridThread, + setTranscription: setGridTranscription, + listTranscripts: listGridTranscripts, get: getGrid, getHome: getGridHome, createRoom: createGridRoom, diff --git a/server/src/functions/grid.ts b/server/src/functions/grid.ts index 0614be2be..c7e4355c7 100644 --- a/server/src/functions/grid.ts +++ b/server/src/functions/grid.ts @@ -31,7 +31,18 @@ import { SessionId, SpaceId, UserId } from "@in/server/core/schema/identifiers" import { db } from "@in/server/db" import { SpaceSettingsModel } from "@in/server/db/models/spaceSettings" import { UsersModel } from "@in/server/db/models/users" -import { gridPresence, gridRooms, members, sessions, spaces, users, type DbGridRoom } from "@in/server/db/schema" +import { + chats, + gridPresence, + gridRooms, + gridTranscriptionRuns, + gridTranscriptionSpaceRevisions, + members, + sessions, + spaces, + users, + type DbGridRoom, +} from "@in/server/db/schema" import type { Transaction } from "@in/server/db/types" import type { FunctionContext } from "@in/server/functions/_types" import { AccessGuards } from "@in/server/modules/authorization/accessGuards" @@ -46,6 +57,14 @@ import { } from "@in/server/modules/grid/livekit" import { setGridAvatarMicrophoneState } from "@in/server/modules/grid/avatarState" import { notifyGridChanged } from "@in/server/modules/grid/realtime" +import { enrollGridRoomHistory } from "./gridTranscription" +import { + encodeGridTranscription, + stopGridTranscription, + transcriptionAvailable, + retireUnclaimedGridTranscriptions, + supportedGridTranscriptionSession, +} from "@in/server/modules/grid/transcription/state" import { enqueueGridParticipantRevocation } from "@in/server/modules/grid/providerEffects" import { activeGridConnection, @@ -664,6 +683,14 @@ async function movePresence( const ownerChanged = existing !== undefined && existing.presence.ownerSessionId !== context.currentSessionId const rotateForAccessRevocation = ownerChanged && liveKitRequiresGenerationRotation() + if (existing && ownerChanged) { + await stopGridTranscription( + tx, + existing.room.id, + rotateForAccessRevocation ? "generation_changed" : "authority_lost", + true, + ) + } if (existing && (existing.room.id !== targetRoom.id || ownerChanged)) { const connection = activeGridConnection(existing.room) if (connection) { @@ -678,6 +705,19 @@ async function movePresence( } await claimPresence(tx, context, targetRoom.id, microphoneEnabled) + await enrollGridRoomHistory(tx, targetRoom.id, [context.currentUserId]) + const roster = await tx + .select({ userId: gridPresence.userId }) + .from(gridPresence) + .where(and(eq(gridPresence.roomId, targetRoom.id), gt(gridPresence.leaseExpiresAt, new Date()))) + if (roster.length > 8) await stopGridTranscription(tx, targetRoom.id, "speaker_capacity", true) + const [ownerSession] = await tx + .select({ clientType: sessions.clientType, clientVersion: sessions.clientVersion }) + .from(sessions) + .where(eq(sessions.id, context.currentSessionId)) + .limit(1) + if (!ownerSession || !supportedGridTranscriptionSession(ownerSession)) + await stopGridTranscription(tx, targetRoom.id, "unsupported_client", true) if (existing && existing.room.id !== targetRoom.id) { const endedConnection = await reconcileGridRoom(tx, existing.room.id, { rotateForAccessRevocation }) @@ -809,9 +849,18 @@ async function buildGrid(spaceId: number, context: FunctionContext): Promise { await lockGridMutations(tx) + await retireUnclaimedGridTranscriptions(tx) const settings = await SpaceSettingsModel.getStored(spaceId, tx) const [space] = await tx - .select({ revision: spaces.gridRevision }) + .select({ + revision: spaces.gridRevision, + // Hold the isolated counter stable while reading run projections. No + // capture/history writer can commit newer state with an older label. + transcriptionRevision: + sql`coalesce((select "revision" from ${gridTranscriptionSpaceRevisions} where "space_id" = ${spaces.id} for share), 0)`.mapWith( + BigInt, + ), + }) .from(spaces) .where(and(eq(spaces.id, spaceId), isNull(spaces.deleted))) .limit(1) @@ -828,7 +877,109 @@ async function buildGrid(spaceId: number, context: FunctionContext): Promise + transcriptionAvailable: boolean + } + >() + const boundRoomIds = roomRows.filter((row) => available || row.room.roomThreadId !== null).map((row) => row.room.id) + const latestRuns = boundRoomIds.length + ? await tx + .selectDistinctOn([gridTranscriptionRuns.sourceRoomId]) + .from(gridTranscriptionRuns) + .where(inArray(gridTranscriptionRuns.sourceRoomId, [...new Set(boundRoomIds)])) + .orderBy(gridTranscriptionRuns.sourceRoomId, desc(gridTranscriptionRuns.createdAt)) + : [] + const runByRoom = new Map(latestRuns.map((run) => [run.sourceRoomId, run])) + const projectionChatIds = [ + ...new Set([ + ...roomRows.flatMap((row) => (row.room.roomThreadId === null ? [] : [row.room.roomThreadId])), + ...latestRuns.map((run) => run.transcriptChatId), + ]), + ] + const projectionChats = projectionChatIds.length + ? await tx.select().from(chats).where(inArray(chats.id, projectionChatIds)) + : [] + const chatById = new Map(projectionChats.map((chat) => [chat.id, chat])) + const ownerSessionIds = available + ? [...new Set(roomRows.flatMap((row) => (row.presence ? [row.presence.ownerSessionId] : [])))] + : [] + const ownerSessions = ownerSessionIds.length + ? await tx + .select({ + id: sessions.id, + clientType: sessions.clientType, + clientVersion: sessions.clientVersion, + revoked: sessions.revoked, + }) + .from(sessions) + .where(inArray(sessions.id, ownerSessionIds)) + : [] + const qualifiedOwners = new Set( + ownerSessions + .filter((session) => session.revoked === null && supportedGridTranscriptionSession(session)) + .map((session) => session.id), + ) + for (const room of new Map(roomRows.map((row) => [row.room.id, row.room])).values()) { + if (!available && room.roomThreadId === null) { + projected.set(room.id, { transcriptionAvailable: false }) + continue + } + let roomThreadId: bigint | undefined + if (room.roomThreadId) { + const chat = chatById.get(room.roomThreadId) + if ( + chat && + chat.type === "thread" && + chat.spaceId === room.spaceId && + chat.publicThread === false && + chat.parentChatId === null + ) { + try { + await AccessGuards.ensureChatAccess(chat, context.currentUserId, tx) + roomThreadId = BigInt(chat.id) + } catch (error) { + if (!RealtimeRpcError.is(error)) throw error + } + } + } + const run = runByRoom.get(room.id) + let canRead = false + if (run) { + const chat = chatById.get(run.transcriptChatId) + if (chat) { + try { + await AccessGuards.ensureChatAccess(chat, context.currentUserId, tx) + canRead = true + } catch (error) { + if (!RealtimeRpcError.is(error)) throw error + } + } + } + const roster = roomRows.filter((row) => row.room.id === room.id && row.presence).map((row) => row.presence!) + const roomAvailable = + available && + room.connectionStartedAt !== null && + roster.length >= 2 && + roster.length <= 8 && + roster.every((presence) => qualifiedOwners.has(presence.ownerSessionId)) + projected.set(room.id, { + roomThreadId, + transcription: run ? encodeGridTranscription(run, canRead) : undefined, + transcriptionAvailable: roomAvailable, + }) + } + return { + settings, + revision: BigInt(space.revision) + (space.transcriptionRevision ?? 0n), + roomRows, + projected, + available, + } }) if (!snapshot.settings.gridEnabled) { @@ -854,6 +1005,7 @@ async function buildGrid(spaceId: number, context: FunctionContext): Promise [run.roomChatId, run.destinationParentChatId, run.transcriptChatId]), + ]), + ].sort((a, b) => a - b) + const locked = chatIds.length + ? await tx.select().from(chats).where(inArray(chats.id, chatIds)).orderBy(chats.id).for("update") + : [] + const boundParent = locked.find((chat) => chat.id === room.roomThreadId) + const parents = new Set() + if ( + boundParent && + boundParent.type === "thread" && + boundParent.spaceId === room.spaceId && + boundParent.publicThread === false && + boundParent.parentChatId === null + ) + parents.add(boundParent.id) + else if (room.roomThreadId !== null) + await tx.update(gridRooms).set({ roomThreadId: null }).where(eq(gridRooms.id, room.id)) + // History may have retired while waiting for chat ownership; refresh its durable reasons. + const runs = candidates.length + ? await tx.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.sourceRoomId, roomId)) + : [] + for (const run of runs) if (await intactDestination(tx, run)) parents.add(run.destinationParentChatId) + await grantGridTranscriptUsers(tx, { chatIds: [...parents], userIds }) +} +export async function openGridThread( + input: OpenGridThreadInput, + context: FunctionContext, +): Promise { + const result = await db.transaction(async (tx) => { + await lockGridMutations(tx) + const { room } = await roomAuthority(tx, id(input.roomId), context, uuid(input.expectedMembershipId)) + const userIds = (await occupants(tx, room.id)).map((r) => r.userId) + await enrollGridRoomHistory(tx, room.id, userIds) + const chatId = await ensureGridRoomThread(tx, { + room, + actorUserId: context.currentUserId, + participantUserIds: userIds, + }) + await enrollGridRoomHistory(tx, room.id, userIds) + return { chatId, spaceId: room.spaceId } + }) + await notifyGridSpaceChanged(result.spaceId) + return { chatId: BigInt(result.chatId), updates: [] } +} +async function canChooseDestination( + tx: Transaction, + run: DbGridTranscriptionRun, + userId: number, + requireSharing: boolean, +) { + if (!(await intactDestination(tx, run))) return false + const [child] = await tx.select().from(chats).where(eq(chats.id, run.transcriptChatId)).limit(1) + const [parent] = await tx.select().from(chats).where(eq(chats.id, run.destinationParentChatId)).limit(1) + if (!child || !parent) return false + try { + await AccessGuards.ensureChatAccess(child, userId, tx) + } catch (error) { + if (RealtimeRpcError.is(error)) return false + throw error + } + if (!requireSharing || parent.createdBy === userId) return true + const [member] = await tx + .select() + .from(members) + .where(and(eq(members.spaceId, run.spaceId), eq(members.userId, userId))) + .limit(1) + return member?.role === "owner" || member?.role === "admin" +} +export async function listGridTranscripts( + input: ListGridTranscriptsInput, + context: FunctionContext, +): Promise { + return db.transaction(async (tx) => { + await lockGridMutations(tx) + const { room } = await roomAuthority(tx, id(input.roomId), context) + const retained = or( + isNull(gridTranscriptionRuns.interruptionReason), + notInArray(gridTranscriptionRuns.interruptionReason, [...gridTranscriptionRetirementReasons]), + ) + const latest = tx.$with("grid_transcript_candidates").as( + tx + .selectDistinctOn([gridTranscriptionRuns.transcriptChatId]) + .from(gridTranscriptionRuns) + .where(and(eq(gridTranscriptionRuns.spaceId, room.spaceId), retained)) + .orderBy(gridTranscriptionRuns.transcriptChatId, desc(gridTranscriptionRuns.createdAt)), + ) + // Bound work to the latest 100 distinct retained transcripts, then return + // at most 20 that the actor can read and legitimately share/continue. + const rows = await tx.with(latest).select().from(latest).orderBy(desc(latest.createdAt)).limit(100) + const acceptedRows = rows.length + ? await tx + .selectDistinctOn([gridTranscriptionRuns.transcriptChatId]) + .from(gridTranscriptionRuns) + .where( + and( + eq(gridTranscriptionRuns.sourceRoomId, room.id), + inArray( + gridTranscriptionRuns.transcriptChatId, + rows.map((run) => run.transcriptChatId), + ), + retained, + ), + ) + .orderBy(gridTranscriptionRuns.transcriptChatId, desc(gridTranscriptionRuns.createdAt)) + : [] + const acceptedByChat = new Map(acceptedRows.map((run) => [run.transcriptChatId, run])) + const seen = new Set() + const transcripts: ListGridTranscriptsResult["transcripts"] = [] + for (const run of rows) { + const accepted = acceptedByChat.get(run.transcriptChatId) + const representative = + accepted && (await canChooseDestination(tx, accepted, context.currentUserId, false)) ? accepted : run + if ( + seen.has(run.transcriptChatId) || + !(await canChooseDestination( + tx, + representative, + context.currentUserId, + representative.sourceRoomId !== room.id, + )) + ) + continue + seen.add(run.transcriptChatId) + const [chat] = await tx.select().from(chats).where(eq(chats.id, run.transcriptChatId)).limit(1) + const [active] = await tx + .select({ id: gridTranscriptionRuns.id }) + .from(gridTranscriptionRuns) + .where( + and( + eq(gridTranscriptionRuns.transcriptChatId, run.transcriptChatId), + inArray(gridTranscriptionRuns.state, [...captureStates]), + ), + ) + .limit(1) + transcripts.push({ + transcriptChatId: BigInt(run.transcriptChatId), + title: chat?.title ?? "Transcript", + busy: !!active, + }) + if (transcripts.length === 20) break + } + return { transcripts } + }) +} +export async function setGridTranscription( + input: SetGridTranscriptionInput, + context: FunctionContext, +): Promise { + const requestId = uuid(input.requestId) + const spaceId = await db.transaction(async (tx) => { + await lockGridMutations(tx) + const { room, session } = await roomAuthority(tx, id(input.roomId), context, uuid(input.expectedMembershipId)) + const [duplicate] = await tx + .select() + .from(gridTranscriptionRuns) + .where( + and( + eq(gridTranscriptionRuns.actorUserId, context.currentUserId), + eq(gridTranscriptionRuns.requestId, requestId), + ), + ) + .limit(1) + if (duplicate) { + if (duplicate.sourceRoomId !== room.id) throw RealtimeRpcError.BadRequest() + return room.spaceId + } + const previous = await latestRoomTranscription(tx, room.id) + if ( + !input.enabled && + previous && + previous.id === input.expectedRunId && + (previous.state === "stopped" || previous.state === "interrupted" || previous.state === "stopping") + ) + return room.spaceId + if ((previous?.id ?? undefined) !== input.expectedRunId || (previous?.revision ?? 0) !== input.expectedRevision) + throw RealtimeRpcError.BadRequest() + if (!input.enabled) { + await stopGridTranscription(tx, room.id, "user_stop") + return room.spaceId + } + if ( + !supportedGridTranscriptionSession(session) || + !(await transcriptionAvailable(tx)) || + !getLiveKitGridConfig() || + !room.connectionStartedAt || + room.connectionGeneration !== input.expectedGeneration + ) + throw RealtimeRpcError.BadRequest() + const busy = await tx + .select({ id: gridTranscriptionRuns.id }) + .from(gridTranscriptionRuns) + .where(inArray(gridTranscriptionRuns.state, [...captureStates])) + .limit(1) + if (busy.length) throw RealtimeRpcError.BadRequest() + const roster = await occupants(tx, room.id) + if (!roster.every(supportedGridTranscriptionSession)) throw RealtimeRpcError.BadRequest() + const userIds = roster.map((r) => r.userId) + if (userIds.length < 2 || userIds.length > GRID_TRANSCRIPTION_MAX_SPEAKERS) throw RealtimeRpcError.BadRequest() + const runId = randomUUID() + let destination: { transcriptChatId: number; destinationParentChatId: number; originalAnchorId: number } + let roomLinkMessageId: number | undefined + let chosen = input.destination === GridTranscriptDestination.GRID_TRANSCRIPT_LAST ? previous : undefined + let requireSharing = chosen ? chosen.sourceRoomId !== room.id : false + if (input.destination === GridTranscriptDestination.GRID_TRANSCRIPT_EXISTING) { + if (!input.transcriptChatId) throw RealtimeRpcError.BadRequest() + const candidates = await tx + .select() + .from(gridTranscriptionRuns) + .where( + and( + eq(gridTranscriptionRuns.spaceId, room.spaceId), + eq(gridTranscriptionRuns.transcriptChatId, id(input.transcriptChatId)), + ), + ) + .orderBy(desc(gridTranscriptionRuns.createdAt)) + // An intact accepted room link carries continuing authority, even if a newer run came from another room. + chosen = undefined + for (const candidate of candidates) + if ( + candidate.sourceRoomId === room.id && + (await canChooseDestination(tx, candidate, context.currentUserId, false)) + ) { + chosen = candidate + break + } + requireSharing = !chosen + if (!chosen) + for (const candidate of candidates) + if (await canChooseDestination(tx, candidate, context.currentUserId, true)) { + chosen = candidate + break + } + if (!chosen) throw RealtimeRpcError.BadRequest() + } + await enrollGridRoomHistory( + tx, + room.id, + userIds, + chosen ? [chosen.roomChatId, chosen.destinationParentChatId, chosen.transcriptChatId] : [], + ) + const refreshedPrevious = await latestRoomTranscription(tx, room.id) + if ( + (refreshedPrevious?.id ?? undefined) !== input.expectedRunId || + (refreshedPrevious?.revision ?? 0) !== input.expectedRevision + ) + throw RealtimeRpcError.BadRequest() + if (chosen) { + const [fresh] = await tx + .select() + .from(gridTranscriptionRuns) + .where(eq(gridTranscriptionRuns.id, chosen.id)) + .limit(1) + chosen = fresh + } + const roomChatId = await ensureGridRoomThread(tx, { + room, + actorUserId: context.currentUserId, + participantUserIds: userIds, + }) + if ( + input.destination === GridTranscriptDestination.GRID_TRANSCRIPT_EXISTING && + (!chosen || !(await canChooseDestination(tx, chosen, context.currentUserId, requireSharing))) + ) + throw RealtimeRpcError.BadRequest() + if (chosen && (await canChooseDestination(tx, chosen, context.currentUserId, requireSharing))) { + destination = { + transcriptChatId: chosen.transcriptChatId, + destinationParentChatId: chosen.destinationParentChatId, + originalAnchorId: chosen.originalAnchorId, + } + if (requireSharing) + roomLinkMessageId = await insertGridTranscriptContinuationLink(tx, { + roomChatId, + transcriptChatId: chosen.transcriptChatId, + actorUserId: context.currentUserId, + runId, + title: "Transcript", + }) + else roomLinkMessageId = chosen.roomLinkMessageId ?? undefined + await grantGridTranscriptUsers(tx, { chatIds: [chosen.destinationParentChatId], userIds }) + } else + destination = await createGridTranscriptDestination(tx, { + roomChatId, + spaceId: room.spaceId, + actorUserId: context.currentUserId, + participantUserIds: userIds, + title: "Transcript", + runId, + }) + if (chosen) + await insertGridTranscriptMessage(tx, { + chatId: destination.transcriptChatId, + actorUserId: context.currentUserId, + runId, + segmentId: "started", + kind: "started", + text: "Transcription started", + }) + await tx.insert(gridTranscriptionRuns).values({ + id: runId, + sourceRoomId: room.id, + spaceId: room.spaceId, + roomChatId, + ...destination, + roomLinkMessageId, + actorUserId: context.currentUserId, + requestId, + model: transcriptionConfig().model, + state: "starting", + generation: room.connectionGeneration, + providerTarget: durableLiveKitProviderTarget(), + expiresAt: new Date(Date.now() + GRID_TRANSCRIPTION_MAX_MS), + }) + return room.spaceId + }) + await notifyGridSpaceChanged(spaceId) + return { grid: (await getGrid({ spaceId: BigInt(spaceId) }, context)).grid, updates: [] } +} diff --git a/server/src/functions/messages.deleteChat.ts b/server/src/functions/messages.deleteChat.ts index f041c85b5..b882cc973 100644 --- a/server/src/functions/messages.deleteChat.ts +++ b/server/src/functions/messages.deleteChat.ts @@ -23,6 +23,7 @@ import { getEffectiveChatAccessUserIds, } from "@in/server/modules/authorization/chatAccessProjection" import { deleteSubthreadParentPlacement } from "@in/server/functions/messages.deleteMessage" +import { invalidateGridTranscriptionForHistory } from "@in/server/modules/grid/transcription/state" const log = new Log("functions.deleteChat") /** @@ -173,6 +174,8 @@ async function deleteChatWithOptions( } } + await invalidateGridTranscriptionForHistory(tx, { chatIds: [lockedChat.id], reason: "chat_deleted" }) + peerId = Encoders.peerFromChat(lockedChat, { currentUserId }) const recipientAccess = await getEffectiveChatAccessUserIds(tx, [lockedChat.id]) recipientIds = Array.from(recipientAccess.get(lockedChat.id) ?? []) diff --git a/server/src/functions/messages.deleteMessage.ts b/server/src/functions/messages.deleteMessage.ts index 8dc25e386..3306a6720 100644 --- a/server/src/functions/messages.deleteMessage.ts +++ b/server/src/functions/messages.deleteMessage.ts @@ -26,6 +26,7 @@ import { and, eq, inArray } from "drizzle-orm" import { RealtimeRpcError } from "@in/server/realtime/errors" import { BotUpdateProjector } from "@in/server/modules/botUpdates/projector" import { hasImportedAgentMessages } from "@in/server/modules/agentSessions/service" +import { decryptSystemMessagePayload } from "@in/server/modules/systemMessages/payload" type Input = { messageIds: bigint[] @@ -99,9 +100,22 @@ async function deleteMessageWithOptions( messageIds: input.messageIds, }) + const selectedRows = numericMessageIds.length === 0 ? [] : await db.select({ + messageId: messages.messageId, + countsAsUnread: messages.countsAsUnread, + systemMessageEncrypted: messages.systemMessageEncrypted, + systemMessageIv: messages.systemMessageIv, + systemMessageTag: messages.systemMessageTag, + }).from(messages).where(and(eq(messages.chatId, chat.id), inArray(messages.messageId, numericMessageIds))) + const quietGeneratedIds = new Set(selectedRows + .filter((row) => row.countsAsUnread === false && row.systemMessageEncrypted !== null && row.systemMessageIv !== null && row.systemMessageTag !== null && decryptSystemMessagePayload({ encrypted: row.systemMessageEncrypted, iv: row.systemMessageIv, authTag: row.systemMessageTag }).event?.oneofKind === "gridTranscript") + .map((row) => row.messageId)) + const ordinaryMessageIds = input.messageIds.filter((id) => !quietGeneratedIds.has(Number(id))) + const onlyQuietGenerated = selectedRows.length > 0 && ordinaryMessageIds.length === 0 + let { update, metadataChatUpdates } = await MessageModel.deleteMessages(input.messageIds, chat.id) - if (!options.trustedPlacementCleanup) { - BotUpdateProjector.messageRoutesDeleted({ chatId: chat.id, messageIds: input.messageIds }) + if (!options.trustedPlacementCleanup && ordinaryMessageIds.length > 0) { + BotUpdateProjector.messageRoutesDeleted({ chatId: chat.id, messageIds: ordinaryMessageIds }) } const backlinkSelfUpdates = await deleteBacklinkMessages(backlinkMessages, { currentUserId: context.currentUserId, @@ -119,12 +133,12 @@ async function deleteMessageWithOptions( chatUpdates: metadataChatUpdates, }) - if (isReplyThread(chat)) { + if (isReplyThread(chat) && !onlyQuietGenerated) { await emitMessageSubthreadUpdateIfNeeded({ chatId: chat.id, currentUserId: context.currentUserId, }) - } else if (isLinkedSubthread(chat)) { + } else if (isLinkedSubthread(chat) && !onlyQuietGenerated) { queueSubthreadParentUpdate({ chatId: chat.id, currentUserId: context.currentUserId, @@ -132,7 +146,7 @@ async function deleteMessageWithOptions( }) } - if (!options.trustedPlacementCleanup) { + if (!options.trustedPlacementCleanup && ordinaryMessageIds.length > 0) { await Promise.all( updateGroup.userIds.map(async (userId) => { await Notifications.sendToUser({ @@ -140,7 +154,7 @@ async function deleteMessageWithOptions( payload: { kind: "message_deleted", threadId: `chat_${chat.id}`, - messageIds: input.messageIds.map((id) => id.toString()), + messageIds: ordinaryMessageIds.map((id) => id.toString()), }, }) }), diff --git a/server/src/functions/messages.editMessage.ts b/server/src/functions/messages.editMessage.ts index 89ab09823..84fe4a1c8 100644 --- a/server/src/functions/messages.editMessage.ts +++ b/server/src/functions/messages.editMessage.ts @@ -64,6 +64,9 @@ export const editMessage = async (input: Input, context: FunctionContext): Promi if (fullMessage && await isSubthreadParentMessage(fullMessage.globalId)) { throw RealtimeRpcError.BadRequest() } + if (fullMessage?.systemMessage?.event.oneofKind === "gridTranscript") { + throw RealtimeRpcError.BadRequest() + } if (await isImportedAgentMessage(chatId, Number(input.messageId))) { throw RealtimeRpcError.AgentSessionMessageImmutable() } diff --git a/server/src/functions/messages.moveThread.ts b/server/src/functions/messages.moveThread.ts index a007834da..50fa76aa9 100644 --- a/server/src/functions/messages.moveThread.ts +++ b/server/src/functions/messages.moveThread.ts @@ -13,6 +13,7 @@ import { Encoders } from "@in/server/realtime/encoders/encoders" import type { ServerUpdate } from "@in/server/protocol/server" import type { FunctionContext } from "@in/server/functions/_types" import { allocateThreadNumber } from "@in/server/modules/threadNumbers" +import { invalidateGridTranscriptionForHistory } from "@in/server/modules/grid/transcription/state" const log = new Log("functions.moveThread") @@ -186,6 +187,8 @@ export async function moveThread( }, } + await invalidateGridTranscriptionForHistory(tx, { chatIds: [chat.id], reason: "chat_moved" }) + const update = await UpdatesModel.insertUpdate(tx, { update: updatePayload, bucket: UpdateBucket.Chat, diff --git a/server/src/functions/messages.removeChatParticipant.ts b/server/src/functions/messages.removeChatParticipant.ts index b3faf6f98..5fe479733 100644 --- a/server/src/functions/messages.removeChatParticipant.ts +++ b/server/src/functions/messages.removeChatParticipant.ts @@ -28,6 +28,7 @@ import { encodeDateStrict } from "@in/server/realtime/encoders/helpers" import { ensureCanManageChatParticipants } from "@in/server/modules/authorization/spaceThreadGuards" import { ensureGroupCanParticipateInChat, loadActiveGroupMemberIds } from "@in/server/modules/userGroups" import { BotUpdateProjector } from "@in/server/modules/botUpdates/projector" +import { invalidateGridTranscriptionForHistory } from "@in/server/modules/grid/transcription/state" import { getRootChatIdsForAccessEvents, getEffectiveChatAccessUserIds, @@ -91,6 +92,8 @@ export async function removeChatParticipant( const accessEventChatIds = await getRootChatIdsForAccessEvents(tx, [input.chatId]) const accessBefore = await getEffectiveChatAccessUserIds(tx, accessEventChatIds) + await invalidateGridTranscriptionForHistory(tx, { chatIds: [chat.id], reason: "access_revoked" }) + await tx .delete(chatParticipants) .where(and(eq(chatParticipants.chatId, input.chatId), eq(chatParticipants.userId, userId))) @@ -205,6 +208,8 @@ async function removeChatParticipantGroup( const accessEventChatIds = await getRootChatIdsForAccessEvents(tx, [input.chatId]) const accessBefore = await getEffectiveChatAccessUserIds(tx, accessEventChatIds) + await invalidateGridTranscriptionForHistory(tx, { chatIds: [chat.id], reason: "access_revoked" }) + await tx .delete(chatParticipantGroups) .where(and(eq(chatParticipantGroups.chatId, input.chatId), eq(chatParticipantGroups.groupId, input.groupId))) diff --git a/server/src/functions/messages.updateChatVisibility.ts b/server/src/functions/messages.updateChatVisibility.ts index a6d7a99b1..6658e60b4 100644 --- a/server/src/functions/messages.updateChatVisibility.ts +++ b/server/src/functions/messages.updateChatVisibility.ts @@ -38,6 +38,7 @@ import { getEffectiveChatAccessUserIds, removedAccessUserIds, } from "@in/server/modules/authorization/chatAccessProjection" +import { invalidateGridTranscriptionForHistory } from "@in/server/modules/grid/transcription/state" const log = new Log("functions.updateChatVisibility") @@ -136,6 +137,10 @@ export async function updateChatVisibility( existingGroupGrants.map((grant) => grant.groupId), ) + // This RPC replaces grants even when visibility remains private. Fence + // admitted speech before changing who can read its destination. + await invalidateGridTranscriptionForHistory(tx, { chatIds: [chat.id], reason: "access_revoked" }) + if (isPublic) { if (input.participants && input.participants.length > 0) { throw new RealtimeRpcError( diff --git a/server/src/methods/deleteSpace.ts b/server/src/methods/deleteSpace.ts index 101dcd6ee..0ded7d9f2 100644 --- a/server/src/methods/deleteSpace.ts +++ b/server/src/methods/deleteSpace.ts @@ -1,6 +1,6 @@ import { db } from "@in/server/db" import { eq, inArray } from "drizzle-orm" -import { members, spaces, users } from "@in/server/db/schema" +import { chats, members, spaces, users } from "@in/server/db/schema" import { type Static, Type } from "@sinclair/typebox" import type { HandlerContext } from "@in/server/controllers/helpers" import { normalizeId, TInputId } from "@in/server/types/methods" @@ -14,6 +14,7 @@ import type { Update } from "@inline-chat/protocol/core" import { InlineError } from "@in/server/types/errors" import { deactivateCommittedSpaceMembership } from "@in/server/modules/authorization/spaceMembershipLifecycle" import { Log } from "@in/server/utils/log" +import { invalidateGridTranscriptionForHistory } from "@in/server/modules/grid/transcription/state" const log = new Log("space.deleteSpace") @@ -95,6 +96,10 @@ const deleteSpace = async (spaceId: number, currentUserId: number) => { throw new SpaceMemberSetChanged(lockedMemberUserIds) } + const spaceChats = await tx.select({ id: chats.id }).from(chats) + .where(eq(chats.spaceId, spaceId)).orderBy(chats.id).for("update") + await invalidateGridTranscriptionForHistory(tx, { chatIds: spaceChats.map((chat) => chat.id), reason: "space_deleted" }) + await UserBucketUpdates.enqueueMany( lockedMemberUserIds.map((userId) => ({ userId, diff --git a/server/src/modules/grid/roomLifecycle.ts b/server/src/modules/grid/roomLifecycle.ts index ecbf4aa01..db98b87ce 100644 --- a/server/src/modules/grid/roomLifecycle.ts +++ b/server/src/modules/grid/roomLifecycle.ts @@ -13,6 +13,7 @@ import { import { encodeDateStrict } from "@in/server/realtime/encoders/helpers" import { Log } from "@in/server/utils/log" import { and, count, eq, gt, inArray, sql } from "drizzle-orm" +import { stopGridTranscription, invalidateGridTranscriptionForSpeaker } from "./transcription/state" const log = new Log("grid.roomLifecycle") @@ -62,6 +63,15 @@ export async function reconcileGridRoom( : room.connectionGeneration const endedConnection = activeGridConnection(room) + if (occupantCount < 2 || options.rotateForAccessRevocation) { + await stopGridTranscription( + tx, + roomId, + options.rotateForAccessRevocation ? "generation_changed" : "media_ended", + true, + ) + } + if (occupantCount === 0 && room.title === null) { await tx.delete(gridRooms).where(eq(gridRooms.id, roomId)) if (endedConnection) { @@ -227,9 +237,6 @@ async function removeGridPresenceInTransaction( ): Promise { const conditions = [eq(gridPresence.userId, input.userId)] if (input.spaceId !== undefined) conditions.push(eq(gridRooms.spaceId, input.spaceId)) - if (input.ownerSessionId !== undefined) { - conditions.push(eq(gridPresence.ownerSessionId, input.ownerSessionId)) - } const [row] = await tx .select({ room: gridRooms, presence: gridPresence }) .from(gridPresence) @@ -237,7 +244,10 @@ async function removeGridPresenceInTransaction( .where(and(...conditions)) .limit(1) - if (!row) { + if (!row || (input.ownerSessionId !== undefined && row.presence.ownerSessionId !== input.ownerSessionId)) { + // Revoking an older session must not discard the replacement owner's speech. + // With no current presence, revocation still retires the speaker's pending turns. + if (!row) await invalidateGridTranscriptionForSpeaker(tx, input.userId, input.spaceId) return { affectedSpaceIds: new Set(), changedRoomId: undefined, @@ -247,6 +257,13 @@ async function removeGridPresenceInTransaction( } } + await invalidateGridTranscriptionForSpeaker( + tx, + input.userId, + input.spaceId, + input.ownerSessionId === undefined ? undefined : row.presence.mediaMembershipId, + ) + const deleteConditions = [ eq(gridPresence.userId, input.userId), eq(gridPresence.roomId, row.room.id), diff --git a/server/src/modules/grid/transcription/control.test.ts b/server/src/modules/grid/transcription/control.test.ts new file mode 100644 index 000000000..6714742a6 --- /dev/null +++ b/server/src/modules/grid/transcription/control.test.ts @@ -0,0 +1,796 @@ +import { afterEach, describe, expect, spyOn, test } from "bun:test" +import { randomUUID } from "node:crypto" +import { and, eq, sql } from "drizzle-orm" +import { GridTranscriptDestination, GridTranscriptionState } from "@inline-chat/protocol/core" +import { setupTestLifecycle, testUtils } from "@in/server/__tests__/setup" +import { db } from "@in/server/db" +import { waitForPostCommitHooks } from "@in/server/db/commitHooks" +import { + chatParticipants, + chats, + gridPresence, + gridRooms, + gridTranscriptionRuns, + gridTranscriptionSegments, + members, + messages, + sessions, + spaces, + gridTranscriptionSpaceRevisions, +} from "@in/server/db/schema" +import { createGridRoom, getGrid, joinGridRoom, leaveGridRoom } from "@in/server/functions/grid" +import { + openGridThread, + setGridTranscription, + enrollGridRoomHistory, + listGridTranscripts, +} from "@in/server/functions/gridTranscription" +import { toggleSpaceGrid } from "@in/server/functions/space.settings" +import * as livekit from "@in/server/modules/grid/livekit" +import * as gridRealtime from "@in/server/modules/grid/realtime" +import { clearChatHistory } from "@in/server/modules/historyClear" +import { deleteChat } from "@in/server/functions/messages.deleteChat" +import { handler as leaveSpace } from "@in/server/methods/leaveSpace" +import { revokeSession } from "@in/server/modules/sessions/revokeSession" +import { ensureGridRoomThread, insertGridTranscriptContinuationLink } from "./threads" +import { invalidateGridTranscriptionForHistory } from "./state" +import { handleGridTranscriptionWorkerRequest } from "./worker" + +setupTestLifecycle() +const secret = "grid-control-test-secret-0123456789012345" +const original = new Map() +let providerSpy: ReturnType> | undefined +function configure() { + for (const [key, value] of Object.entries({ + GRID_TRANSCRIPTION_ENABLED: "true", + GRID_TRANSCRIPTION_WORKER_SECRET: secret, + GRID_TRANSCRIPTION_MODEL: "meeting", + GRID_TRANSCRIPTION_ALLOWED_CLIENT_VERSIONS: "qualified-test", + })) { + if (!original.has(key)) original.set(key, process.env[key]) + process.env[key] = value + } + providerSpy = spyOn(livekit, "getLiveKitGridConfig").mockReturnValue({ + serverUrl: "wss://local.test", + apiKey: "local-test-key", + apiSecret: "local-test-secret-0123456789", + provider: "self_hosted", + }) +} +afterEach(async () => { + await waitForPostCommitHooks() + providerSpy?.mockRestore() + providerSpy = undefined + for (const [key, value] of original) { + if (value === undefined) delete process.env[key] + else process.env[key] = value + } + original.clear() +}) +async function api(action: string, body: unknown, token = secret) { + return handleGridTranscriptionWorkerRequest( + new Request(`http://localhost/_internal/grid-transcription/${action}`, { + method: "POST", + headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, + body: JSON.stringify(body), + }), + ) +} +interface Claimed { + runId: string + claimEpoch: number + runToken: string + roomId: number + model: string + livekit: { serverUrl: string; token: string } + participants: { identity: string; userId: number; membershipId: string }[] +} +async function fixture(label: string) { + configure() + const { space, users } = await testUtils.createSpaceWithMembers(label, [ + `${label}-owner@example.test`, + `${label}-peer@example.test`, + `${label}-future@example.test`, + ] as const) + await db + .update(members) + .set({ role: "owner" }) + .where(and(eq(members.spaceId, space.id), eq(members.userId, users[0].id))) + const auth = await Promise.all( + users.map((user) => + testUtils.createSessionForUser(user.id, { clientType: "macos", clientVersion: "qualified-test" }), + ), + ) + const contexts = users.map((user, i) => + testUtils.functionContext({ userId: user.id, sessionId: auth[i]!.session.id }), + ) + await toggleSpaceGrid({ spaceId: BigInt(space.id), enabled: true }, contexts[0]!) + const created = await createGridRoom({ spaceId: BigInt(space.id), microphoneEnabled: true }, contexts[0]!) + const roomId = created.grids[0]!.rooms[0]!.id + await joinGridRoom({ roomId, microphoneEnabled: true }, contexts[1]!) + await api("heartbeat", { workerId: "control-test", model: "meeting", ready: true }) + const current = async () => + (await getGrid({ spaceId: BigInt(space.id) }, contexts[0]!)).grid!.rooms.find((room) => room.id === roomId)! + const control = async ( + enabled: boolean, + destination = GridTranscriptDestination.GRID_TRANSCRIPT_LAST, + transcriptChatId?: bigint, + actor = 0, + requestId = randomUUID(), + ) => { + const room = (await getGrid({ spaceId: BigInt(space.id) }, contexts[actor]!)).grid!.rooms.find( + (room) => room.id === roomId, + )! + const avatar = room.avatars.find((avatar) => avatar.user?.id === BigInt(users[actor]!.id))! + return setGridTranscription( + { + roomId, + enabled, + requestId, + expectedMembershipId: avatar.membershipId, + expectedGeneration: room.connection!.generation, + expectedRunId: room.transcription?.runId, + expectedRevision: room.transcription?.revision ?? 0, + destination, + transcriptChatId, + }, + contexts[actor]!, + ) + } + const claim = async () => { + const response = await api("claim", { workerId: "control-test" }) + expect(response.status).toBe(200) + return (await response.json()) as Claimed + } + return { space, users, contexts, auth, roomId, current, control, claim } +} + +async function admittedReplacement(label: string) { + const f = await fixture(label) + await joinGridRoom({ roomId: f.roomId, microphoneEnabled: true }, f.contexts[2]!) + // Keep two occupants active after Leave/revocation so a room-wide stop cannot + // mask whether session cleanup invalidates the correct admitted membership. + providerSpy!.mockReturnValue({ + serverUrl: "wss://local.test", + apiKey: "local-test-key", + apiSecret: "local-test-secret-0123456789", + provider: "cloud", + }) + const [oldPresence] = await db.select().from(gridPresence).where(eq(gridPresence.userId, f.users[1].id)) + const replacement = await testUtils.createSessionForUser(f.users[1].id, { + clientType: "macos", + clientVersion: "qualified-test", + }) + const context = testUtils.functionContext({ userId: f.users[1].id, sessionId: replacement.session.id }) + await joinGridRoom({ roomId: f.roomId, microphoneEnabled: true }, context) + const [presence] = await db.select().from(gridPresence).where(eq(gridPresence.userId, f.users[1].id)) + expect(presence!.ownerSessionId).toBe(replacement.session.id) + expect(presence!.mediaMembershipId).not.toBe(oldPresence!.mediaMembershipId) + await f.control(true) + const claim = await f.claim() + const speaker = claim.participants.find((participant) => participant.userId === f.users[1].id)! + expect(speaker.membershipId).toBe(presence!.mediaMembershipId) + let turn = 0 + const admit = async (participantIdentity: string) => { + const response = await api("admit", { + participantIdentity, + trackSid: `TR-session-${++turn}`, + sourceTurnKey: `session-turn-${turn}`, + }, claim.runToken) + expect(response.status).toBe(200) + return ((await response.json()) as { segmentId: string }).segmentId + } + const segmentId = await admit(speaker.identity) + const segment = async (id = segmentId) => + (await db.select().from(gridTranscriptionSegments).where(eq(gridTranscriptionSegments.id, id)))[0]! + const final = async (id = segmentId) => { + const response = await api("final", { segmentId: id, text: "An admitted turn" }, claim.runToken) + expect(response.status).toBe(200) + return (await response.json()) as { messageId: number | null } + } + const revoke = (sessionId: number) => revokeSession({ + actor: "user", + actorUserId: f.users[1].id, + targetUserId: f.users[1].id, + sessionId, + }) + return { ...f, context, replacement, presence: presence!, claim, admit, segment, final, revoke } +} + +async function admitRejoinedMembership(f: Awaited>) { + await leaveGridRoom({ expectedRoomId: f.roomId }, f.context) + expect((await f.segment()).state).toBe("admitted") + await joinGridRoom({ roomId: f.roomId, microphoneEnabled: true }, f.context) + const renewed = (await (await api("renew", {}, f.claim.runToken)).json()) as { + participants: Claimed["participants"] + } + const speaker = renewed.participants.find((participant) => participant.userId === f.users[1].id)! + expect(speaker.membershipId).not.toBe(f.presence.mediaMembershipId) + return f.admit(speaker.identity) +} + +describe("Grid transcription control and worker authorization", () => { + test("stale session cleanup preserves the replacement owner's admitted final", async () => { + const f = await admittedReplacement("stale-session-transcript") + await f.revoke(f.auth[1]!.session.id) + const [presence] = await db.select().from(gridPresence).where(eq(gridPresence.userId, f.users[1].id)) + expect(presence).toMatchObject({ + ownerSessionId: f.replacement.session.id, + mediaMembershipId: f.presence.mediaMembershipId, + }) + expect((await f.segment()).state).toBe("admitted") + expect((await f.current()).transcription?.state).toBe(GridTranscriptionState.GRID_TRANSCRIPTION_ACTIVE) + expect((await f.final()).messageId).toBeGreaterThan(0) + expect((await f.segment()).state).toBe("finalized") + }) + + test("cloud owner takeover stops admitted capture without requiring a media generation change", async () => { + const f = await fixture("cloud-capture-takeover") + providerSpy!.mockReturnValue({ + serverUrl: "wss://local.test", + apiKey: "local-test-key", + apiSecret: "local-test-secret-0123456789", + provider: "cloud", + }) + await f.control(true) + const claim = await f.claim() + const generation = (await f.current()).connection!.generation + const oldSpeaker = claim.participants.find((participant) => participant.userId === f.users[1].id)! + const admission = { participantIdentity: oldSpeaker.identity, trackSid: "TR-cloud-takeover", sourceTurnKey: "old-turn" } + const admitted = await api("admit", admission, claim.runToken) + expect(admitted.status).toBe(200) + const { segmentId } = (await admitted.json()) as { segmentId: string } + const replacement = await testUtils.createSessionForUser(f.users[1].id, { + clientType: "macos", + clientVersion: "qualified-test", + }) + const context = testUtils.functionContext({ userId: f.users[1].id, sessionId: replacement.session.id }) + const joined = await joinGridRoom({ roomId: f.roomId, microphoneEnabled: true }, context) + const room = joined.grids[0]!.rooms.find((value) => value.id === f.roomId)! + expect(room.connection!.generation).toBe(generation) + const [run] = await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, claim.runId)) + expect(run).toMatchObject({ state: "stopping", interruptionReason: "authority_lost" }) + const [segment] = await db.select().from(gridTranscriptionSegments).where(eq(gridTranscriptionSegments.id, segmentId)) + expect(segment!.state).toBe("discarded") + expect((await api("admit", { ...admission, sourceTurnKey: "late-old-turn" }, claim.runToken)).status).toBe(409) + const [presence] = await db.select().from(gridPresence).where(eq(gridPresence.userId, f.users[1].id)) + expect(presence!.mediaMembershipId).not.toBe(oldSpeaker.membershipId) + const identity = livekit.gridParticipantIdentity(f.users[1].id, presence!.mediaMembershipId) + expect((await api("admit", { ...admission, participantIdentity: identity, sourceTurnKey: "new-turn" }, claim.runToken)).status).toBe(409) + const final = await api("final", { segmentId, text: "Must not survive ownership replacement" }, claim.runToken) + expect(final.status).toBe(200) + expect(await final.json()).toEqual({ messageId: null }) + expect(await db.select().from(messages).where(eq(messages.chatId, run!.transcriptChatId))).toHaveLength(0) + expect((await (await api("renew", {}, claim.runToken)).json()) as object).toMatchObject({ + state: "stopping", + allowFinalFlush: false, + }) + }) + + test("matching session cleanup discards its membership and preserves an earlier ordinary Leave final", async () => { + const f = await admittedReplacement("matching-session-transcript") + const currentSegmentId = await admitRejoinedMembership(f) + await f.revoke(f.replacement.session.id) + expect(await db.select().from(gridPresence).where(eq(gridPresence.userId, f.users[1].id))).toHaveLength(0) + expect((await f.segment(currentSegmentId)).state).toBe("discarded") + expect((await f.final(currentSegmentId)).messageId).toBeNull() + expect((await f.segment()).state).toBe("admitted") + expect((await f.final()).messageId).toBeGreaterThan(0) + }) + + test("current-presence Space revocation discards speech from earlier and current memberships", async () => { + const f = await admittedReplacement("current-space-transcript") + const currentSegmentId = await admitRejoinedMembership(f) + await leaveSpace({ spaceId: f.space.id }, { ...f.context, ip: undefined }) + expect((await f.segment()).state).toBe("discarded") + expect((await f.segment(currentSegmentId)).state).toBe("discarded") + expect((await f.final()).messageId).toBeNull() + expect((await f.final(currentSegmentId)).messageId).toBeNull() + }) + + for (const kind of ["session", "Space"] as const) { + test(`absent-presence ${kind} revocation discards previously admitted speech`, async () => { + const f = await admittedReplacement(`absent-${kind.toLowerCase()}-transcript`) + await leaveGridRoom({ expectedRoomId: f.roomId }, f.context) + expect((await f.segment()).state).toBe("admitted") + if (kind === "session") await f.revoke(f.replacement.session.id) + else await leaveSpace({ spaceId: f.space.id }, { ...f.context, ip: undefined }) + expect((await f.segment()).state).toBe("discarded") + expect((await f.final()).messageId).toBeNull() + const [run] = await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, f.claim.runId)) + expect(await db.select().from(messages).where(eq(messages.chatId, run!.transcriptChatId))).toHaveLength(0) + }) + } + + test("lazy private parent grants current and later room occupants while empty unclaimed Stop is terminal", async () => { + const f = await fixture("lazy-control") + expect((await f.current()).roomThreadId).toBeUndefined() + const presence = (await db.select().from(gridPresence).where(eq(gridPresence.userId, f.users[0].id)))[0]! + const opened = await openGridThread( + { roomId: f.roomId, expectedMembershipId: presence.mediaMembershipId }, + f.contexts[0]!, + ) + const parent = ( + await db + .select() + .from(chats) + .where(eq(chats.id, Number(opened.chatId))) + )[0]! + expect(parent).toMatchObject({ publicThread: false, parentChatId: null }) + expect( + (await db.select().from(chatParticipants).where(eq(chatParticipants.chatId, parent.id))) + .map((p) => p.userId) + .sort(), + ).toEqual([f.users[0].id, f.users[1].id].sort()) + await f.control(true) + const first = (await f.current()).transcription! + expect(first.state).toBe(GridTranscriptionState.GRID_TRANSCRIPTION_STARTING) + await f.control(false) + expect((await f.current()).transcription?.state).toBe(GridTranscriptionState.GRID_TRANSCRIPTION_STOPPED) + await joinGridRoom({ roomId: f.roomId }, f.contexts[2]!) + expect( + await db + .select() + .from(chatParticipants) + .where(and(eq(chatParticipants.chatId, parent.id), eq(chatParticipants.userId, f.users[2].id))), + ).toHaveLength(1) + await f.control(true) + expect((await f.current()).transcription?.runId).not.toBe(first.runId) + expect((await f.current()).transcription?.transcriptChatId).toBe(first.transcriptChatId) + }) + + test("claim wire binds exact membership, rejects unknown admission and bounds retries, then final is idempotent", async () => { + const f = await fixture("claim-control") + const requestId = randomUUID() + await f.control(true, GridTranscriptDestination.GRID_TRANSCRIPT_NEW, undefined, 0, requestId) + await f.control(true, GridTranscriptDestination.GRID_TRANSCRIPT_NEW, undefined, 0, requestId) + expect(await db.select().from(gridTranscriptionRuns)).toHaveLength(1) + const claim = await f.claim() + expect(claim).toMatchObject({ roomId: Number(f.roomId), model: "meeting", claimEpoch: 1 }) + expect(claim.participants).toHaveLength(2) + const jwt = JSON.parse(Buffer.from(claim.livekit.token.split(".")[1]!, "base64url").toString("utf8")) as { + video: { canPublish: boolean; canSubscribe: boolean; room: string } + } + expect(jwt.video).toMatchObject({ canPublish: false, canSubscribe: true }) + expect(jwt.video.room).toBe(`inline-grid-${f.roomId}-1`) + expect( + (await api("admit", { participantIdentity: "unknown", trackSid: "TR-one", sourceTurnKey: "one" }, claim.runToken)) + .status, + ).toBe(409) + const input = { participantIdentity: claim.participants[0]!.identity, trackSid: "TR-one", sourceTurnKey: "one" } + const admitted = await api("admit", input, claim.runToken) + expect(admitted.status).toBe(200) + const ticket = (await admitted.json()) as { segmentId: string } + expect(await (await api("admit", input, claim.runToken)).json()).toEqual(ticket) + expect((await api("admit", { ...input, sourceTurnKey: "two" }, claim.runToken)).status).toBe(200) + expect((await api("admit", { ...input, sourceTurnKey: "three" }, claim.runToken)).status).toBe(409) + const finish = () => api("final", { segmentId: ticket.segmentId, text: "A verified turn" }, claim.runToken) + const responses = await Promise.all([finish(), finish()]) + expect(responses.map((response) => response.status)).toEqual([200, 200]) + const finals = await Promise.all(responses.map((response) => response.json())) + expect(finals[0]).toEqual(finals[1]) + const run = (await db.select().from(gridTranscriptionRuns))[0]! + expect(await db.select().from(messages).where(eq(messages.chatId, run.transcriptChatId))).toHaveLength(1) + expect((await api("admit", input, claim.runToken)).status).toBe(409) + await f.control(false) + expect((await (await api("renew", {}, claim.runToken)).json()) as object).toMatchObject({ + state: "stopping", + allowFinalFlush: true, + }) + await leaveGridRoom({ expectedRoomId: f.roomId }, f.contexts[1]!) + expect((await (await api("renew", {}, claim.runToken)).json()) as object).toMatchObject({ + state: "stopping", + allowFinalFlush: false, + }) + expect( + ( + await db + .select() + .from(gridTranscriptionSegments) + .where(and(eq(gridTranscriptionSegments.runId, run.id), eq(gridTranscriptionSegments.sourceTurnKey, "two"))) + )[0]?.state, + ).toBe("discarded") + expect((await api("stopped", {}, claim.runToken)).status).toBe(200) + expect((await db.select().from(gridTranscriptionRuns))[0]?.state).toBe("interrupted") + }) + + test("legacy participant blocks disclosure-qualified availability and cannot start", async () => { + const f = await fixture("cohort-control") + expect((await f.current()).transcriptionAvailable).toBe(true) + await db.update(sessions).set({ clientVersion: "legacy" }).where(eq(sessions.id, f.auth[1]!.session.id)) + expect((await f.current()).transcriptionAvailable).toBe(false) + await expect(f.control(true)).rejects.toThrow() + expect(await db.select().from(gridTranscriptionRuns)).toHaveLength(0) + }) + + test("picker counts distinct transcripts and retains an intact origin after a newer cross-room link is retired", async () => { + const f = await fixture("picker-distinct") + await f.control(true) + await f.control(false) + const origin = (await db.select().from(gridTranscriptionRuns))[0]! + await f.control(true, GridTranscriptDestination.GRID_TRANSCRIPT_NEW) + await f.control(false) + const repeated = (await db.select().from(gridTranscriptionRuns)).find((run) => run.id !== origin.id)! + const now = Date.now() + await db.insert(gridTranscriptionRuns).values( + Array.from({ length: 105 }, (_, index) => ({ + ...repeated, + id: randomUUID(), + requestId: randomUUID(), + createdAt: new Date(now + index), + })), + ) + const [otherRoom] = await db + .insert(gridRooms) + .values({ spaceId: f.space.id, createdByUserId: f.users[0].id, title: "Earlier transcript source" }) + .returning() + await db.insert(gridTranscriptionRuns).values({ + ...origin, + id: randomUUID(), + requestId: randomUUID(), + sourceRoomId: otherRoom!.id, + roomLinkMessageId: 999999, + interruptionReason: "message_deleted", + createdAt: new Date(now + 1_000), + }) + const listed = await listGridTranscripts({ roomId: f.roomId }, f.contexts[0]!) + expect(listed.transcripts.map((entry) => Number(entry.transcriptChatId)).sort()).toEqual( + [origin.transcriptChatId, repeated.transcriptChatId].sort(), + ) + expect(listed.transcripts.every((entry) => !entry.busy)).toBe(true) + }) + + test("lease-only renewals keep Grid revision and hints quiet while authority loss publishes once", async () => { + const f = await fixture("lease-only-renewal") + await f.control(true) + const claim = await f.claim() + const notify = spyOn(gridRealtime, "notifyGridSpaceChanged").mockResolvedValue(undefined) + try { + const before = (await db.select().from(gridTranscriptionSpaceRevisions))[0]!.revision + for (let index = 0; index < 3; index++) expect((await api("renew", {}, claim.runToken)).status).toBe(200) + expect(notify).not.toHaveBeenCalled() + expect((await db.select().from(gridTranscriptionSpaceRevisions))[0]!.revision).toBe(before) + await db + .update(gridTranscriptionRuns) + .set({ leaseExpiresAt: new Date(Date.now() - 1000) }) + .where(eq(gridTranscriptionRuns.id, claim.runId)) + expect(await (await api("renew", {}, claim.runToken)).json()).toMatchObject({ + state: "stopping", + allowFinalFlush: false, + }) + expect(notify).toHaveBeenCalledTimes(1) + expect((await db.select().from(gridTranscriptionSpaceRevisions))[0]!.revision).toBe(before + 1n) + expect((await api("renew", {}, claim.runToken)).status).toBe(200) + expect(notify).toHaveBeenCalledTimes(1) + } finally { + notify.mockRestore() + } + }) + + test("clear of a stopped child permanently retires continuation while preserving readable ordinary history access", async () => { + const f = await fixture("retired-control") + await f.control(true) + const before = (await f.current()).transcription! + await f.control(false) + await clearChatHistory( + { + peer: { type: { oneofKind: "chat", chat: { chatId: before.transcriptChatId! } } }, + keepLastDays: 0, + deleteReplyThreads: false, + }, + { currentUserId: f.users[0].id }, + ) + await expect( + f.control(true, GridTranscriptDestination.GRID_TRANSCRIPT_EXISTING, before.transcriptChatId), + ).rejects.toThrow() + await f.control(true) + expect((await f.current()).transcription?.transcriptChatId).not.toBe(before.transcriptChatId) + }) + + test("clear then media generation loss cannot restore a retired association", async () => { + const f = await fixture("sticky-retirement") + await f.control(true) + const first = (await f.current()).transcription! + const claim = await f.claim() + await clearChatHistory( + { + peer: { type: { oneofKind: "chat", chat: { chatId: first.transcriptChatId! } } }, + keepLastDays: 0, + deleteReplyThreads: false, + }, + { currentUserId: f.users[0].id }, + ) + await leaveGridRoom({ expectedRoomId: f.roomId }, f.contexts[1]!) + expect( + (await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, first.runId)))[0], + ).toMatchObject({ state: "stopping", interruptionReason: "history_cleared" }) + expect((await api("stopped", {}, claim.runToken)).status).toBe(200) + await joinGridRoom({ roomId: f.roomId, microphoneEnabled: true }, f.contexts[1]!) + await f.control(true) + expect((await f.current()).transcription?.transcriptChatId).not.toBe(first.transcriptChatId) + }) + + test("lost claim response can retry within one boot and disabled gate still accepts actual containment", async () => { + const f = await fixture("claim-retry") + await f.control(true) + const first = await f.claim() + const replay = await f.claim() + expect(replay).toMatchObject({ runId: first.runId, claimEpoch: first.claimEpoch }) + expect((await api("claim", { workerId: "other-boot" })).status).toBe(204) + await f.control(false) + process.env["GRID_TRANSCRIPTION_ENABLED"] = "false" + expect((await api("renew", {}, first.runToken)).status).toBe(503) + expect((await api("stopped", {}, first.runToken)).status).toBe(200) + expect( + (await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, first.runId)))[0]?.state, + ).toBe("stopped") + }) + + test("an expired owned claim or false readiness yields only no-child containment authority", async () => { + const f = await fixture("expired-owned-claim") + await f.control(true) + const claimed = await f.claim() + await db + .update(gridTranscriptionRuns) + .set({ leaseExpiresAt: new Date(Date.now() - 1) }) + .where(eq(gridTranscriptionRuns.id, claimed.runId)) + const response = await api("claim", { workerId: "control-test" }) + expect(response.status).toBe(200) + expect(await response.json()).toMatchObject({ + runId: claimed.runId, + claimEpoch: claimed.claimEpoch, + stopImmediately: true, + }) + expect( + (await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, claimed.runId)))[0]?.state, + ).toBe("stopping") + expect((await api("stopped", {}, claimed.runToken)).status).toBe(200) + await f.control(true) + const next = await f.claim() + await api("heartbeat", { workerId: "control-test", model: "meeting", ready: false }) + expect(await (await api("claim", { workerId: "control-test" })).json()).toMatchObject({ + runId: next.runId, + stopImmediately: true, + }) + expect((await api("stopped", {}, next.runToken)).status).toBe(200) + }) + + test("presence Space ownership and parent-chat run retirement do not deadlock", async () => { + const f = await fixture("counter-lock-order") + await f.control(true) + const run = (await db.select().from(gridTranscriptionRuns))[0]! + const parentHeld = Promise.withResolvers() + const spaceHeld = Promise.withResolvers() + const before = (await f.current()).transcription! + const retire = db.transaction(async (tx) => { + await tx.execute(sql`set local statement_timeout = '5s'`) + await tx.select({ id: chats.id }).from(chats).where(eq(chats.id, run.destinationParentChatId)).for("update") + parentHeld.resolve() + await spaceHeld.promise + // The real revision trigger must complete without requesting the Space + // row held by the presence transaction waiting for this parent. + await tx + .update(gridTranscriptionRuns) + .set({ state: "stopped", revision: sql`${gridTranscriptionRuns.revision} + 1` }) + .where(eq(gridTranscriptionRuns.id, run.id)) + }) + const join = db.transaction(async (tx) => { + try { + await parentHeld.promise + await tx.execute(sql`set local statement_timeout = '5s'`) + await tx + .update(gridPresence) + .set({ microphoneRevision: sql`${gridPresence.microphoneRevision} + 1` }) + .where(eq(gridPresence.userId, f.users[1].id)) + spaceHeld.resolve() + await enrollGridRoomHistory(tx, Number(f.roomId), [f.users[2].id]) + } finally { + spaceHeld.resolve() + } + }) + await Promise.all([retire, join]) + expect( + ( + await db + .select() + .from(gridTranscriptionSpaceRevisions) + .where(eq(gridTranscriptionSpaceRevisions.spaceId, f.space.id)) + )[0]?.revision, + ).toBeGreaterThan(1n) + expect((await f.current()).transcription?.revision).toBeGreaterThan(before.revision) + expect( + await db + .select() + .from(chatParticipants) + .where( + and(eq(chatParticipants.chatId, run.destinationParentChatId), eq(chatParticipants.userId, f.users[2].id)), + ), + ).toHaveLength(1) + // Retained counters have no Space FK: normal Space ownership is separate. + expect((await db.select().from(spaces).where(eq(spaces.id, f.space.id)))[0]).toBeDefined() + }) + + test("all historical runs are owned before taking a shared counter during cross-room clear", async () => { + const f = await fixture("multi-run-counter-order") + await f.control(true) + await f.control(false) + const originalRun = (await db.select().from(gridTranscriptionRuns))[0]! + const [sourceRoom] = await db + .insert(gridRooms) + .values({ spaceId: f.space.id, createdByUserId: f.users[0].id, title: "Other source" }) + .returning() + if (!sourceRoom) throw new Error("Source room missing") + const historicalId = "00000000-0000-4000-8000-000000000001" + const activeId = "00000000-0000-4000-8000-000000000002" + const sourceParent = await db.transaction(async (tx) => { + const roomChatId = await ensureGridRoomThread(tx, { + room: sourceRoom, + actorUserId: f.users[0].id, + participantUserIds: [f.users[0].id, f.users[1].id], + }) + const linkId = await insertGridTranscriptContinuationLink(tx, { + roomChatId, + transcriptChatId: originalRun.transcriptChatId, + actorUserId: f.users[0].id, + runId: historicalId, + title: "Earlier transcript", + }) + const base = { + sourceRoomId: sourceRoom.id, + spaceId: f.space.id, + roomChatId, + transcriptChatId: originalRun.transcriptChatId, + destinationParentChatId: originalRun.destinationParentChatId, + originalAnchorId: originalRun.originalAnchorId, + roomLinkMessageId: linkId, + actorUserId: f.users[0].id, + model: "meeting", + generation: 1, + providerTarget: livekit.durableLiveKitProviderTarget(), + expiresAt: new Date(Date.now() + 60_000), + } + await tx + .insert(gridTranscriptionRuns) + .values({ ...base, id: historicalId, requestId: randomUUID(), state: "stopped" }) + await tx.insert(gridTranscriptionRuns).values({ + ...base, + id: activeId, + requestId: randomUUID(), + state: "active", + workerId: "existing-worker", + claimEpoch: 1, + leaseExpiresAt: new Date(Date.now() + 60_000), + }) + return roomChatId + }) + const activeOwned = Promise.withResolvers() + const finishActive = Promise.withResolvers() + let workerPid = 0 + const stopped = db.transaction(async (tx) => { + const [backend] = await tx.execute<{ pid: number }>(sql`select pg_backend_pid() as pid`) + workerPid = backend!.pid + await tx.execute(sql`set local statement_timeout = '5s'`) + await tx + .select({ id: chats.id }) + .from(chats) + .where(eq(chats.id, originalRun.destinationParentChatId)) + .for("update") + await tx + .select({ id: gridTranscriptionRuns.id }) + .from(gridTranscriptionRuns) + .where(eq(gridTranscriptionRuns.id, activeId)) + .for("update") + activeOwned.resolve() + await finishActive.promise + await tx + .update(gridTranscriptionRuns) + .set({ state: "stopped", revision: sql`${gridTranscriptionRuns.revision} + 1` }) + .where(eq(gridTranscriptionRuns.id, activeId)) + }) + await activeOwned.promise + const clearing = db.transaction(async (tx) => { + await tx.execute(sql`set local statement_timeout = '5s'`) + await tx.select({ id: chats.id }).from(chats).where(eq(chats.id, sourceParent)).for("update") + await invalidateGridTranscriptionForHistory(tx, { chatIds: [sourceParent], reason: "history_cleared" }) + }) + try { + let blocked = false + const deadline = performance.now() + 3_000 + while (performance.now() < deadline) { + const [row] = await db.execute<{ blocked: boolean }>( + sql`select exists(select 1 from pg_stat_activity where ${workerPid} = any(pg_blocking_pids(pid))) as blocked`, + ) + if (row?.blocked) { + blocked = true + break + } + await Bun.sleep(10) + } + expect(blocked).toBe(true) + finishActive.resolve() + await Promise.all([stopped, clearing]) + } finally { + finishActive.resolve() + await Promise.allSettled([stopped, clearing]) + } + expect( + ( + await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.sourceRoomId, sourceRoom.id)) + ).every((run) => run.interruptionReason === "history_cleared"), + ).toBe(true) + }) + + test("deleting the retained room parent recreates a fresh private binding on explicit Open and Start", async () => { + const f = await fixture("deleted-room-parent") + await f.control(true) + await f.control(false) + const run = (await db.select().from(gridTranscriptionRuns))[0]! + const peer = { type: { oneofKind: "chat" as const, chat: { chatId: BigInt(run.roomChatId) } } } + await clearChatHistory({ peer, keepLastDays: 0, deleteReplyThreads: true }, { currentUserId: f.users[0].id }) + await deleteChat({ peer }, f.contexts[0]!) + expect((await f.current()).roomThreadId).toBeUndefined() + const presence = (await db.select().from(gridPresence).where(eq(gridPresence.userId, f.users[0].id)))[0]! + const reopened = await openGridThread( + { roomId: f.roomId, expectedMembershipId: presence.mediaMembershipId }, + f.contexts[0]!, + ) + expect(reopened.chatId).not.toBe(BigInt(run.roomChatId)) + expect( + ( + await db + .select() + .from(chats) + .where(eq(chats.id, Number(reopened.chatId))) + )[0], + ).toMatchObject({ publicThread: false, spaceId: f.space.id, parentChatId: null }) + await f.control(true) + expect((await f.current()).transcription?.transcriptChatId).not.toBe(BigInt(run.transcriptChatId)) + }) + + test("empty and stale membership fences cannot open or control a replacement membership", async () => { + const f = await fixture("member-fence") + const old = (await db.select().from(gridPresence).where(eq(gridPresence.userId, f.users[0].id)))[0]! + await expect(openGridThread({ roomId: f.roomId, expectedMembershipId: "" }, f.contexts[0]!)).rejects.toThrow() + await db.update(gridPresence).set({ mediaMembershipId: randomUUID() }).where(eq(gridPresence.userId, f.users[0].id)) + await expect( + openGridThread({ roomId: f.roomId, expectedMembershipId: old.mediaMembershipId }, f.contexts[0]!), + ).rejects.toThrow() + await expect( + setGridTranscription( + { + roomId: f.roomId, + enabled: true, + requestId: randomUUID(), + expectedMembershipId: "", + expectedGeneration: 1, + expectedRevision: 0, + destination: GridTranscriptDestination.GRID_TRANSCRIPT_NEW, + }, + f.contexts[0]!, + ), + ).rejects.toThrow() + expect(await db.select().from(gridTranscriptionRuns)).toHaveLength(0) + }) + + test("worker request authentication and chunk body limits reject before mutating", async () => { + configure() + expect((await api("heartbeat", { workerId: "test", model: "meeting", ready: true }, "wrong-secret")).status).toBe( + 401, + ) + const body = new ReadableStream({ + start(controller) { + controller.enqueue(new Uint8Array(150_000)) + controller.enqueue(new Uint8Array(150_000)) + controller.close() + }, + }) + const request = new Request("http://localhost/_internal/grid-transcription/heartbeat", { + method: "POST", + headers: { authorization: `Bearer ${secret}` }, + body, + duplex: "half", + } as RequestInit) + expect((await handleGridTranscriptionWorkerRequest(request)).status).toBe(413) + expect((await api("heartbeat", { workerId: "test", model: "unknown", ready: true })).status).toBe(400) + }) +}) diff --git a/server/src/modules/grid/transcription/messages.test.ts b/server/src/modules/grid/transcription/messages.test.ts new file mode 100644 index 000000000..b854f69ed --- /dev/null +++ b/server/src/modules/grid/transcription/messages.test.ts @@ -0,0 +1,276 @@ +import { afterEach, describe, expect, spyOn, test } from "bun:test" +import { createHmac, randomUUID } from "node:crypto" +import { and, eq, sql } from "drizzle-orm" +import { setupTestLifecycle, testUtils } from "@in/server/__tests__/setup" +import { db } from "@in/server/db" +import { waitForPostCommitHooks } from "@in/server/db/commitHooks" +import { ChatModel } from "@in/server/db/models/chats" +import { DialogsModel } from "@in/server/db/models/dialogs" +import { MessageModel } from "@in/server/db/models/messages" +import { SpaceSettingsModel } from "@in/server/db/models/spaceSettings" +import { chats, dialogs, gridPresence, gridRooms, gridTranscriptionRuns, gridTranscriptionSegments, members, messages, updates, users, UpdateBucket } from "@in/server/db/schema" +import { deleteMessage } from "@in/server/functions/messages.deleteMessage" +import { deleteChat } from "@in/server/functions/messages.deleteChat" +import { editMessage } from "@in/server/functions/messages.editMessage" +import { getChatHistory } from "@in/server/functions/messages.getChatHistory" +import { updateChatVisibility } from "@in/server/functions/messages.updateChatVisibility" +import { clearChatHistory } from "@in/server/modules/historyClear" +import { durableLiveKitProviderTarget } from "@in/server/modules/grid/livekit" +import { encryptMessage } from "@in/server/modules/encryption/encryptMessage" +import { Notifications } from "@in/server/modules/notifications/notifications" +import { Sync } from "@in/server/modules/updates/sync" +import { RealtimeUpdates } from "@in/server/realtime/message" +import { GridTranscriptMessageKind, MessageEntity_Type, MessageSendMode, type MessageEntity, type Update } from "@inline-chat/protocol/core" +import { insertGridTranscriptMessage } from "./messages" +import { createGridTranscriptDestination, ensureGridRoomThread, grantGridTranscriptUsers } from "./threads" +import { handleGridTranscriptionWorkerRequest } from "./worker" +import { intactDestination } from "./state" + +setupTestLifecycle() +const peer = (chatId: number) => ({ type: { oneofKind: "chat" as const, chat: { chatId: BigInt(chatId) } } }) +const secret = "grid-transcript-test-secret-0123456789" +const previousConfig = new Map() +function configureWorker() { + for (const [key, value] of Object.entries({ GRID_TRANSCRIPTION_ENABLED: "true", GRID_TRANSCRIPTION_WORKER_SECRET: secret, GRID_TRANSCRIPTION_MODEL: "meeting", GRID_TRANSCRIPTION_ALLOWED_CLIENT_VERSIONS: "grid-transcript-qualified-test" })) { + if (!previousConfig.has(key)) previousConfig.set(key, process.env[key]) + process.env[key] = value + } +} +afterEach(async () => { + await waitForPostCommitHooks() + for (const [key, value] of previousConfig) { + if (value === undefined) delete process.env[key] + else process.env[key] = value + } + previousConfig.clear() +}) + +async function fixture(label: string) { + configureWorker() + const { space, users: people } = await testUtils.createSpaceWithMembers(label, [`${label}-actor@example.test`, `${label}-speaker@example.test`] as const) + const [actor, speaker] = people + await db.update(members).set({ role: "owner" }).where(and(eq(members.spaceId, space.id), eq(members.userId, actor.id))) + await db.update(users).set({ firstName: "Ben" }).where(eq(users.id, speaker.id)) + const [room] = await db.insert(gridRooms).values({ spaceId: space.id, createdByUserId: actor.id, title: label, connectionGeneration: 1, connectionStartedAt: new Date() }).returning() + if (!room) throw new Error("Room fixture missing") + await db.transaction((tx) => SpaceSettingsModel.updateGrid(space.id, true, tx)) + const speakerMembershipId = randomUUID() + for (const user of [actor, speaker]) { + const { session } = await testUtils.createSessionForUser(user.id, { clientType: "macos", clientVersion: "grid-transcript-qualified-test" }) + await db.insert(gridPresence).values({ userId: user.id, roomId: room.id, ownerSessionId: session.id, mediaMembershipId: user.id === speaker.id ? speakerMembershipId : randomUUID(), microphoneEnabled: true, leaseExpiresAt: new Date(Date.now() + 60_000) }) + } + const runId = randomUUID() + const destination = await db.transaction(async (tx) => { + const roomChatId = await ensureGridRoomThread(tx, { room, actorUserId: actor.id, participantUserIds: [actor.id, speaker.id] }) + const created = await createGridTranscriptDestination(tx, { roomChatId, spaceId: space.id, actorUserId: actor.id, participantUserIds: [actor.id, speaker.id], title: "Transcript", runId }) + return { ...created, roomChatId } + }) + const [run] = await db.insert(gridTranscriptionRuns).values({ + id: runId, sourceRoomId: room.id, spaceId: space.id, roomChatId: destination.roomChatId, + transcriptChatId: destination.transcriptChatId, destinationParentChatId: destination.destinationParentChatId, + originalAnchorId: destination.originalAnchorId, actorUserId: actor.id, requestId: randomUUID(), + model: "meeting", state: "active", generation: 1, providerTarget: durableLiveKitProviderTarget(), + claimEpoch: 1, leaseExpiresAt: new Date(Date.now() + 60_000), expiresAt: new Date(Date.now() + 60_000), + }).returning() + if (!run) throw new Error("Run fixture missing") + const [segment] = await db.insert(gridTranscriptionSegments).values({ runId, claimEpoch: 1, speakerUserId: speaker.id, membershipId: speakerMembershipId, trackSid: "TR-test-microphone", sourceTurnKey: "turn-1" }).returning() + if (!segment) throw new Error("Segment fixture missing") + await waitForPostCommitHooks() + const body = Buffer.from(JSON.stringify({ runId, epoch: 1, expiresAt: Date.now() + 60_000 })).toString("base64url") + const token = `${body}.${createHmac("sha256", secret).update(body).digest("base64url")}` + const final = () => handleGridTranscriptionWorkerRequest(new Request("http://localhost/internal/grid-transcription/final", { + method: "POST", headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, + body: JSON.stringify({ segmentId: segment.id, text: "@all /deploy https://example.test should remain speech" }), + })) + return { actor, speaker, space, room, run, segment, destination, final } +} + +describe("Grid transcript ordinary encrypted history", () => { + test("original anchor links to its selected child in first live delivery and history", async () => { + const live: Update[] = [] + const delivery = spyOn(RealtimeUpdates, "pushToUser").mockImplementation(async (_userId, events) => { live.push(...events) }) + try { + const s = await fixture("linked-grid-anchor") + const expectedEntity: MessageEntity = { + type: MessageEntity_Type.THREAD, offset: 0n, length: BigInt("Grid transcription".length), + entity: { oneofKind: "thread", thread: { chatId: BigInt(s.destination.transcriptChatId) } }, + } + const [stored] = await db.select().from(messages).where(and(eq(messages.chatId, s.destination.destinationParentChatId), eq(messages.messageId, s.destination.originalAnchorId))) + expect(stored?.entitiesEncrypted).not.toBeNull() + const history = await getChatHistory({ peerId: peer(s.destination.destinationParentChatId) }, testUtils.functionContext({ userId: s.speaker.id })) + expect(history.messages[0]?.entities?.entities).toEqual([expectedEntity]) + expect(history.messages[0]?.serviceMessage?.event).toMatchObject({ oneofKind: "gridTranscript", gridTranscript: { kind: GridTranscriptMessageKind.GRID_TRANSCRIPT_STARTED } }) + const liveAnchors = live.flatMap((event) => event.update.oneofKind === "newMessage" && event.update.newMessage.message?.id === BigInt(s.destination.originalAnchorId) ? [event.update.newMessage.message] : []) + expect(liveAnchors).toHaveLength(2) + for (const anchor of liveAnchors) expect(anchor.entities?.entities).toEqual([expectedEntity]) + } finally { delivery.mockRestore() } + }) + + test("actual final is encrypted, attributed and quiet in live delivery, history and replay", async () => { + configureWorker() + const s = await fixture("quiet-grid-turn") + await db.insert(dialogs).values({ chatId: s.destination.transcriptChatId, userId: s.speaker.id, spaceId: s.space.id, open: false, archived: true }) + const beforeDialogs = await db.select().from(dialogs).where(eq(dialogs.chatId, s.destination.transcriptChatId)) + const live: Update[] = [] + const delivery = spyOn(RealtimeUpdates, "pushToUser").mockImplementation(async (_userId, events) => { live.push(...events) }) + const push = spyOn(Notifications, "sendToUser").mockResolvedValue(undefined) + try { + const response = await s.final() + expect(response.status).toBe(200) + const result = await response.json() as { messageId: number } + await waitForPostCommitHooks() + const [stored] = await db.select().from(messages).where(and(eq(messages.chatId, s.destination.transcriptChatId), eq(messages.messageId, result.messageId))) + expect(stored).toMatchObject({ fromId: s.actor.id, text: null, countsAsUnread: false, hasLink: false, entitiesEncrypted: null, actionsEncrypted: null }) + expect(stored?.textEncrypted).not.toBeNull() + expect(stored?.systemMessageEncrypted).not.toBeNull() + const history = await getChatHistory({ peerId: peer(s.destination.transcriptChatId) }, testUtils.functionContext({ userId: s.speaker.id })) + expect(history.messages[0]?.message).toBe("Transcript · Ben: @all /deploy https://example.test should remain speech") + expect(history.messages[0]).toMatchObject({ countsAsUnread: false, sendMode: MessageSendMode.MODE_SILENT, mentioned: false }) + expect(history.messages[0]?.serviceMessage?.event).toMatchObject({ oneofKind: "gridTranscript", gridTranscript: { runId: s.run.id, segmentId: s.segment.id, speakerUserId: BigInt(s.speaker.id) } }) + const rows = await db.select().from(updates).where(and(eq(updates.bucket, UpdateBucket.Chat), eq(updates.entityId, s.destination.transcriptChatId))) + const replay = await Sync.processChatUpdates({ chatId: s.destination.transcriptChatId, peerId: peer(s.destination.transcriptChatId), userId: s.speaker.id, updates: rows }) + const replayed = replay.updates.find((update) => update.update.oneofKind === "newMessage") + expect(replayed?.update).toMatchObject({ oneofKind: "newMessage", newMessage: { message: { countsAsUnread: false, sendMode: MessageSendMode.MODE_SILENT } } }) + const liveMessages = live.filter((update) => update.update.oneofKind === "newMessage") + expect(liveMessages).toHaveLength(2) + expect(liveMessages.every((update) => + update.update.oneofKind === "newMessage" && update.update.newMessage.message?.countsAsUnread === false, + )).toBe(true) + expect(push).not.toHaveBeenCalled() + expect(await DialogsModel.getUnreadCount(s.destination.transcriptChatId, s.speaker.id)).toBe(0) + expect(await db.select().from(dialogs).where(eq(dialogs.chatId, s.destination.transcriptChatId))).toEqual(beforeDialogs) + const [chat] = await db.select().from(chats).where(eq(chats.id, s.destination.transcriptChatId)) + expect(chat?.lastMsgId).toBeNull() + expect(chat?.messageIdCounter).toBe(1) + await ChatModel.refreshLastMessageId(s.destination.transcriptChatId) + expect((await db.select().from(chats).where(eq(chats.id, s.destination.transcriptChatId)))[0]?.lastMsgId).toBeNull() + } finally { delivery.mockRestore(); push.mockRestore() } + }) + + test("generated rows reject public and direct model edits", async () => { + const s = await fixture("immutable-grid-turn") + const inserted = await db.transaction((tx) => insertGridTranscriptMessage(tx, { chatId: s.destination.transcriptChatId, actorUserId: s.actor.id, runId: s.run.id, segmentId: s.segment.id, speakerUserId: s.speaker.id, kind: "turn", text: "original" })) + await expect(editMessage({ peer: peer(s.destination.transcriptChatId), messageId: BigInt(inserted.message.messageId), text: "changed" }, testUtils.functionContext({ userId: s.actor.id }))).rejects.toThrow() + await expect(MessageModel.editMessage({ chatId: s.destination.transcriptChatId, messageId: inserted.message.messageId, text: "changed" })).rejects.toThrow() + expect((await MessageModel.getMessage(inserted.message.messageId, s.destination.transcriptChatId)).text).toBe("Transcript · Ben: original") + }) + + test("a finalized deleted turn keeps its dedup tombstone and does not stop capture or push", async () => { + configureWorker() + const s = await fixture("deleted-grid-turn") + const encryptedHuman = encryptMessage("human discussion") + const human = await MessageModel.insertMessage({ chatId: s.destination.transcriptChatId, fromId: s.actor.id, date: new Date(), textEncrypted: encryptedHuman.encrypted, textIv: encryptedHuman.iv, textTag: encryptedHuman.authTag }) + const response = await s.final() + const result = await response.json() as { messageId: number } + await waitForPostCommitHooks() + expect((await db.select().from(chats).where(eq(chats.id, s.destination.transcriptChatId)))[0]?.lastMsgId).toBe(human.message.messageId) + await MessageModel.deleteMessages([BigInt(human.message.messageId)], s.destination.transcriptChatId) + expect((await db.select().from(chats).where(eq(chats.id, s.destination.transcriptChatId)))[0]?.lastMsgId).toBeNull() + const push = spyOn(Notifications, "sendToUser").mockResolvedValue(undefined) + try { + await deleteMessage({ peer: peer(s.destination.transcriptChatId), messageIds: [BigInt(result.messageId)] }, testUtils.functionContext({ userId: s.actor.id })) + expect(push).not.toHaveBeenCalled() + expect((await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, s.run.id)))[0]?.state).toBe("active") + expect((await db.select().from(gridTranscriptionSegments).where(eq(gridTranscriptionSegments.id, s.segment.id)))[0]).toMatchObject({ state: "finalized", messageId: result.messageId }) + expect((await s.final()).status).toBe(200) + expect(await db.select().from(messages).where(eq(messages.chatId, s.destination.transcriptChatId))).toHaveLength(0) + } finally { push.mockRestore() } + }) + + test("destructive clear discards pending speech even after ordinary Stop, and retry cannot refill history", async () => { + configureWorker() + const s = await fixture("cleared-grid-turn") + await db.update(gridTranscriptionRuns).set({ state: "stopping", interruptionReason: "user_stop", stopRequestedAt: new Date() }).where(eq(gridTranscriptionRuns.id, s.run.id)) + await clearChatHistory({ peer: peer(s.destination.transcriptChatId), keepLastDays: 0, deleteReplyThreads: false }, { currentUserId: s.actor.id }) + expect((await db.select().from(gridTranscriptionSegments).where(eq(gridTranscriptionSegments.id, s.segment.id)))[0]?.state).toBe("discarded") + expect((await s.final()).status).toBe(200) + expect(await db.select().from(messages).where(eq(messages.chatId, s.destination.transcriptChatId))).toHaveLength(0) + }) + + test("clearing stopped history permanently retires the destination even when its anchor survives", async () => { + const s = await fixture("retired-grid-history") + await db.update(gridTranscriptionRuns).set({ state: "stopped", interruptionReason: "user_stop" }).where(eq(gridTranscriptionRuns.id, s.run.id)) + expect(await db.transaction((tx) => intactDestination(tx, { ...s.run, state: "stopped", interruptionReason: "user_stop" }))).toBe(true) + await clearChatHistory({ peer: peer(s.destination.transcriptChatId), keepLastDays: 0, deleteReplyThreads: false }, { currentUserId: s.actor.id }) + const [run] = await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, s.run.id)) + expect(run).toMatchObject({ state: "stopped", interruptionReason: "history_cleared" }) + expect(await db.select().from(messages).where(and(eq(messages.chatId, s.destination.destinationParentChatId), eq(messages.messageId, s.destination.originalAnchorId)))).toHaveLength(1) + expect(await db.transaction((tx) => intactDestination(tx, run!))).toBe(false) + }) + + test("same-private participant replacement atomically retires capture and prevents a pending final", async () => { + const s = await fixture("replaced-grid-access") + await updateChatVisibility({ chatId: s.destination.destinationParentChatId, isPublic: false, participants: [s.actor.id] }, testUtils.functionContext({ userId: s.actor.id })) + const [run] = await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, s.run.id)) + expect(run?.interruptionReason).toBe("access_revoked") + expect((await db.select().from(gridTranscriptionSegments).where(eq(gridTranscriptionSegments.id, s.segment.id)))[0]?.state).toBe("discarded") + expect(await db.transaction((tx) => intactDestination(tx, run!))).toBe(false) + await expect(getChatHistory({ peerId: peer(s.destination.transcriptChatId) }, testUtils.functionContext({ userId: s.speaker.id }))).rejects.toThrow() + expect((await s.final()).status).toBe(200) + expect(await db.select().from(messages).where(eq(messages.chatId, s.destination.transcriptChatId))).toHaveLength(0) + }) + + test("missing room parent recreates a fresh private thread while retired history stays invalid", async () => { + const s = await fixture("recreated-grid-parent") + await clearChatHistory({ peer: peer(s.destination.destinationParentChatId), keepLastDays: 0, deleteReplyThreads: true }, { currentUserId: s.actor.id }) + await deleteChat({ peer: peer(s.destination.destinationParentChatId) }, testUtils.functionContext({ userId: s.actor.id })) + expect((await db.select().from(gridRooms).where(eq(gridRooms.id, s.room.id)))[0]?.roomThreadId).toBe(s.destination.destinationParentChatId) + const recreated = await db.transaction((tx) => ensureGridRoomThread(tx, { room: s.room, actorUserId: s.actor.id, participantUserIds: [s.actor.id, s.speaker.id] })) + expect(recreated).not.toBe(s.destination.destinationParentChatId) + expect((await db.select().from(chats).where(eq(chats.id, recreated)))[0]).toMatchObject({ spaceId: s.space.id, publicThread: false, parentChatId: null }) + expect((await db.select().from(gridRooms).where(eq(gridRooms.id, s.room.id)))[0]?.roomThreadId).toBe(recreated) + const [retired] = await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, s.run.id)) + expect(await db.transaction((tx) => intactDestination(tx, retired!))).toBe(false) + }) + + test("existing public room parent is rejected instead of being enrolled", async () => { + const s = await fixture("public-grid-parent") + await updateChatVisibility({ chatId: s.destination.destinationParentChatId, isPublic: true }, testUtils.functionContext({ userId: s.actor.id })) + await expect(db.transaction((tx) => ensureGridRoomThread(tx, { room: s.room, actorUserId: s.actor.id, participantUserIds: [s.actor.id, s.speaker.id] }))).rejects.toThrow() + expect((await db.select().from(gridRooms).where(eq(gridRooms.id, s.room.id)))[0]?.roomThreadId).toBe(s.destination.destinationParentChatId) + }) + + test("anchor deletion waits for child mutation before run fence instead of deadlocking a final", async () => { + configureWorker() + const s = await fixture("anchor-grid-turn") + const childLocked = Promise.withResolvers() + const release = Promise.withResolvers() + const childOwner = db.transaction(async (tx) => { + await tx.select().from(chats).where(eq(chats.id, s.destination.transcriptChatId)).for("update") + childLocked.resolve() + await release.promise + await tx.execute(sql`set local lock_timeout = '1s'`) + await tx.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, s.run.id)).for("update") + }) + await childLocked.promise + const deletion = MessageModel.deleteMessages([BigInt(s.destination.originalAnchorId)], s.destination.destinationParentChatId) + try { + // The parent lock proves deletion reached its transaction before allowing + // the child owner to request the run. A NOWAIT probe avoids timed sleeps. + for (let attempt = 0; attempt < 100; attempt += 1) { + let held = false + try { await db.transaction((tx) => tx.select().from(chats).where(eq(chats.id, s.destination.destinationParentChatId)).for("update", { noWait: true })) } + catch { held = true } + if (held) break + if (attempt === 99) throw new Error("Deletion did not acquire parent chat") + } + release.resolve() + await childOwner + await deletion + expect((await db.select().from(gridTranscriptionSegments).where(eq(gridTranscriptionSegments.id, s.segment.id)))[0]?.state).toBe("discarded") + expect((await s.final()).status).toBe(200) + expect(await db.select().from(messages).where(eq(messages.chatId, s.destination.transcriptChatId))).toHaveLength(0) + } finally { release.resolve(); await Promise.allSettled([childOwner, deletion]) } + }) + + test("later occupants inherit earlier transcript history through ordinary cumulative parent grants", async () => { + const s = await fixture("late-grid-reader") + const reader = await testUtils.createUser("late-grid-reader@example.test") + await db.insert(members).values({ spaceId: s.space.id, userId: reader.id, role: "member" }) + await db.transaction((tx) => insertGridTranscriptMessage(tx, { chatId: s.destination.transcriptChatId, actorUserId: s.actor.id, runId: s.run.id, segmentId: s.segment.id, speakerUserId: s.speaker.id, kind: "turn", text: "earlier conversation" })) + await expect(getChatHistory({ peerId: peer(s.destination.transcriptChatId) }, testUtils.functionContext({ userId: reader.id }))).rejects.toThrow() + await db.transaction((tx) => grantGridTranscriptUsers(tx, { chatIds: [s.destination.destinationParentChatId], userIds: [reader.id] })) + const history = await getChatHistory({ peerId: peer(s.destination.transcriptChatId) }, testUtils.functionContext({ userId: reader.id })) + expect(history.messages[0]?.message).toBe("Transcript · Ben: earlier conversation") + }) +}) diff --git a/server/src/modules/grid/transcription/messages.ts b/server/src/modules/grid/transcription/messages.ts new file mode 100644 index 000000000..701d4e32e --- /dev/null +++ b/server/src/modules/grid/transcription/messages.ts @@ -0,0 +1,147 @@ +import { db } from "@in/server/db" +import { registerPostCommitHook } from "@in/server/db/commitHooks" +import { ChatModel } from "@in/server/db/models/chats" +import { ModelError } from "@in/server/db/models/_errors" +import { UpdatesModel, type UpdateSeqAndDate } from "@in/server/db/models/updates" +import { chats, messages, users, UpdateBucket, type DbMessage } from "@in/server/db/schema" +import type { Transaction } from "@in/server/db/types" +import { getEffectiveChatAccessUserIds } from "@in/server/modules/authorization/chatAccessProjection" +import { encryptBinary } from "@in/server/modules/encryption/encryption" +import { encryptMessage } from "@in/server/modules/encryption/encryptMessage" +import { publishDurableReference } from "@in/server/modules/internalMessaging/durable" +import { encryptSystemMessagePayload, type SystemMessage } from "@in/server/modules/systemMessages/payload" +import { Encoders } from "@in/server/realtime/encoders/encoders" +import { encodeDateStrict } from "@in/server/realtime/encoders/helpers" +import { RealtimeUpdates } from "@in/server/realtime/message" +import { GridTranscriptMessageKind, MessageEntities } from "@inline-chat/protocol/core" +import { eq } from "drizzle-orm" +import { getMessageThreadProjectionsMap } from "@in/server/modules/subthreads" + +export type InsertGridTranscriptMessageInput = { + chatId: number + actorUserId: number + runId: string + segmentId: string + speakerUserId?: number + kind: GridTranscriptMessageKind | "turn" | "started" | "stopped" | "interrupted" | "link" + /** Speech content for turns; lifecycle/link fallback content otherwise. */ + text: string + entities?: MessageEntities + date?: Date +} + +export type InsertGridTranscriptMessageResult = { + message: DbMessage & { systemMessage: SystemMessage } + update: UpdateSeqAndDate +} + +/** + * Insert into ordinary encrypted history in the caller's transaction. The run + * owner validates admission and deduplicates before calling this helper. + * Generated content advances history, never sidebar activity or normal sends. + */ +export async function insertGridTranscriptMessage( + tx: Transaction, + input: InsertGridTranscriptMessageInput, +): Promise { + const [chat] = await tx.select().from(chats).where(eq(chats.id, input.chatId)).for("update").limit(1) + if (!chat) throw ModelError.ChatInvalid + const kind = typeof input.kind === "number" ? input.kind : { + turn: GridTranscriptMessageKind.GRID_TRANSCRIPT_TURN, + started: GridTranscriptMessageKind.GRID_TRANSCRIPT_STARTED, + stopped: GridTranscriptMessageKind.GRID_TRANSCRIPT_STOPPED, + interrupted: GridTranscriptMessageKind.GRID_TRANSCRIPT_INTERRUPTED, + link: GridTranscriptMessageKind.GRID_TRANSCRIPT_LINK, + }[input.kind] + const systemMessage: SystemMessage = { + event: { + oneofKind: "gridTranscript", + gridTranscript: { + runId: input.runId, + segmentId: input.segmentId, + speakerUserId: input.speakerUserId === undefined ? undefined : BigInt(input.speakerUserId), + kind, + }, + }, + } + const text = kind === GridTranscriptMessageKind.GRID_TRANSCRIPT_TURN + ? `Transcript · ${await microphoneOwnerName(tx, input.speakerUserId)}: ${input.text}` + : input.text + const encryptedText = encryptMessage(text) + const encryptedSystem = encryptSystemMessagePayload(systemMessage) + const encryptedEntities = input.entities ? encryptBinary(MessageEntities.toBinary(input.entities)) : undefined + const messageId = ChatModel.nextMessageId(chat) + const [message] = await tx.insert(messages).values({ + chatId: chat.id, + messageId, + fromId: input.actorUserId, + date: input.date ?? new Date(), + textEncrypted: encryptedText.encrypted, + textIv: encryptedText.iv, + textTag: encryptedText.authTag, + systemMessageEncrypted: encryptedSystem.encrypted, + systemMessageIv: encryptedSystem.iv, + systemMessageTag: encryptedSystem.authTag, + entitiesEncrypted: encryptedEntities?.encrypted ?? null, + entitiesIv: encryptedEntities?.iv ?? null, + entitiesTag: encryptedEntities?.authTag ?? null, + countsAsUnread: false, + hasLink: false, + }).returning() + if (!message) throw ModelError.Failed + const update = await UpdatesModel.insertUpdate(tx, { + update: { oneofKind: "newMessage", newMessage: { chatId: BigInt(chat.id), msgId: BigInt(messageId) } }, + bucket: UpdateBucket.Chat, + entity: chat, + }) + await tx.update(chats).set({ + messageIdCounter: messageId, + updateSeq: update.seq, + lastUpdateDate: update.date, + }).where(eq(chats.id, chat.id)) + const result = { message: { ...message, systemMessage }, update } + registerPostCommitHook(tx, Symbol("grid-transcript-message"), { + run: async () => { await publishGridTranscriptMessage(result) }, + }) + return result +} + +/** Publish only after commit. Durable replay covers dropped best-effort hints. */ +async function publishGridTranscriptMessage(input: InsertGridTranscriptMessageResult): Promise { + publishDurableReference({ bucket: { kind: "chat", chatId: input.message.chatId }, frontier: input.update.seq }) + // A delayed post-commit hint must not resurrect a row already cleared or + // deleted. Replay represents that old sequence as a skipped message. + const [current] = await db.select({ globalId: messages.globalId }).from(messages) + .where(eq(messages.globalId, input.message.globalId)).limit(1) + if (!current) return + const access = await getEffectiveChatAccessUserIds(db, [input.message.chatId]) + await Promise.all(Array.from(access.get(input.message.chatId) ?? []).map(async (userId) => { + const projection = (await getMessageThreadProjectionsMap({ + parentChatId: input.message.chatId, parentMessageIds: [input.message.messageId], userId, + })).get(input.message.messageId) + await RealtimeUpdates.pushToUser(userId, [{ + seq: input.update.seq, + date: encodeDateStrict(input.update.date), + update: { + oneofKind: "newMessage", + newMessage: { + message: Encoders.message({ + message: input.message, + encodingForUserId: userId, + encodingForPeer: { peer: { type: { oneofKind: "chat", chat: { chatId: BigInt(input.message.chatId) } } } }, + replies: projection?.replies, + subthread: projection?.subthread, + }), + }, + }, + }]) + })) +} + +async function microphoneOwnerName(tx: Transaction, userId: number | undefined): Promise { + if (userId === undefined) throw new Error("Transcript turn requires a microphone owner") + const [user] = await tx.select({ firstName: users.firstName, lastName: users.lastName, username: users.username }) + .from(users).where(eq(users.id, userId)).limit(1) + if (!user) throw ModelError.Failed + return [user.firstName, user.lastName].filter(Boolean).join(" ").trim() || user.username || "Participant" +} diff --git a/server/src/modules/grid/transcription/service.ts b/server/src/modules/grid/transcription/service.ts new file mode 100644 index 000000000..dbe25d777 --- /dev/null +++ b/server/src/modules/grid/transcription/service.ts @@ -0,0 +1,5 @@ +export { + invalidateGridTranscriptionForHistory, + invalidateGridTranscriptionForSpeaker, + stopGridTranscription, +} from "./state" diff --git a/server/src/modules/grid/transcription/state.ts b/server/src/modules/grid/transcription/state.ts new file mode 100644 index 000000000..c0eef93b6 --- /dev/null +++ b/server/src/modules/grid/transcription/state.ts @@ -0,0 +1,363 @@ +import { GridTranscriptionState, type GridTranscription } from "@inline-chat/protocol/core" +import { + gridTranscriptionRuns, + gridTranscriptionSegments, + gridTranscriptionWorker, + chats, + messages, + type DbGridTranscriptionRun, +} from "@in/server/db/schema" +import { getLiveKitGridConfig } from "@in/server/modules/grid/livekit" +import { insertGridTranscriptMessage } from "./messages" +import type { Transaction } from "@in/server/db/types" +import { and, eq, inArray, gt, desc, or, sql, lt, isNull, lte } from "drizzle-orm" + +export const gridTranscriptionRetirementReasons = [ + "history_changed", + "history_cleared", + "message_deleted", + "chat_deleted", + "chat_moved", + "access_revoked", + "space_deleted", +] as const +export const captureStates = ["starting", "active", "stopping"] as const +export const GRID_TRANSCRIPTION_LEASE_MS = 15_000 +export const GRID_TRANSCRIPTION_MAX_MS = 2 * 60 * 60_000 +export const GRID_TRANSCRIPTION_MAX_SPEAKERS = 8 +export function transcriptionConfig() { + const secret = process.env["GRID_TRANSCRIPTION_WORKER_SECRET"] + const model = process.env["GRID_TRANSCRIPTION_MODEL"] ?? "meeting" + const allowedClientVersions = (process.env["GRID_TRANSCRIPTION_ALLOWED_CLIENT_VERSIONS"] ?? "") + .split(",") + .map((v) => v.trim()) + .filter(Boolean) + return { + enabled: + process.env["GRID_TRANSCRIPTION_ENABLED"] === "true" && + !!secret && + secret.length >= 32 && + (model === "meeting" || model === "standard"), + secret, + model, + allowedClientVersions, + } +} +export async function transcriptionAvailable(tx: Transaction) { + if (!transcriptionConfig().enabled || !transcriptionConfig().allowedClientVersions.length || !getLiveKitGridConfig()) + return false + const [worker] = await tx + .select() + .from(gridTranscriptionWorker) + .where( + and( + eq(gridTranscriptionWorker.model, transcriptionConfig().model), + gt(gridTranscriptionWorker.heartbeatAt, new Date(Date.now() - GRID_TRANSCRIPTION_LEASE_MS)), + ), + ) + .limit(1) + return !!worker +} +export async function latestRoomTranscription(tx: Transaction, roomId: number) { + const [run] = await tx + .select() + .from(gridTranscriptionRuns) + .where(eq(gridTranscriptionRuns.sourceRoomId, roomId)) + .orderBy(desc(gridTranscriptionRuns.createdAt)) + .limit(1) + return run +} +/** Lock chats, runs, then segments consistently with final and history mutations. */ +export async function invalidateGridTranscriptionForSpeaker( + tx: Transaction, + userId: number, + spaceId?: number, + membershipId?: string, +) { + const runs = await tx + .select() + .from(gridTranscriptionRuns) + .where( + and( + spaceId === undefined ? undefined : eq(gridTranscriptionRuns.spaceId, spaceId), + inArray(gridTranscriptionRuns.state, [...captureStates]), + ), + ) + const chatIds = [...new Set(runs.flatMap((r) => [r.destinationParentChatId, r.transcriptChatId]))].sort( + (a, b) => a - b, + ) + if (chatIds.length) + await tx.select({ id: chats.id }).from(chats).where(inArray(chats.id, chatIds)).orderBy(chats.id).for("update") + if (runs.length) { + await tx + .select({ id: gridTranscriptionRuns.id }) + .from(gridTranscriptionRuns) + .where( + inArray( + gridTranscriptionRuns.id, + runs.map((r) => r.id), + ), + ) + .orderBy(gridTranscriptionRuns.id) + .for("update") + await tx + .update(gridTranscriptionSegments) + .set({ state: "discarded" }) + .where( + and( + eq(gridTranscriptionSegments.speakerUserId, userId), + membershipId === undefined ? undefined : eq(gridTranscriptionSegments.membershipId, membershipId), + inArray( + gridTranscriptionSegments.runId, + runs.map((r) => r.id), + ), + eq(gridTranscriptionSegments.state, "admitted"), + ), + ) + } +} +export function supportedGridTranscriptionSession(session: { + clientType: string | null + clientVersion: string | null +}) { + return ( + session.clientType === "macos" && + session.clientVersion !== null && + transcriptionConfig().allowedClientVersions.includes(session.clientVersion) + ) +} +/** Unclaimed runs can retire safely; claimed unknown capture retains its global slot. */ +export async function retireUnclaimedGridTranscriptions(tx: Transaction) { + if (!transcriptionConfig().enabled) return + const overdue = await tx + .select() + .from(gridTranscriptionRuns) + .where( + and( + inArray(gridTranscriptionRuns.state, ["starting", "active"]), + or( + and( + isNull(gridTranscriptionRuns.workerId), + lt(gridTranscriptionRuns.createdAt, new Date(Date.now() - 30_000)), + ), + lte(gridTranscriptionRuns.expiresAt, new Date()), + lte(gridTranscriptionRuns.leaseExpiresAt, new Date()), + ), + ), + ) + const chatIds = [...new Set(overdue.flatMap((run) => [run.destinationParentChatId, run.transcriptChatId]))].sort( + (a, b) => a - b, + ) + if (chatIds.length) + await tx.select({ id: chats.id }).from(chats).where(inArray(chats.id, chatIds)).orderBy(chats.id).for("update") + if (overdue.length) + await tx + .select({ id: gridTranscriptionRuns.id }) + .from(gridTranscriptionRuns) + .where( + inArray( + gridTranscriptionRuns.id, + overdue.map((run) => run.id), + ), + ) + .orderBy(gridTranscriptionRuns.id) + .for("update") + for (const run of overdue) { + const state = run.workerId === null ? "interrupted" : "stopping" + const reason = run.workerId === null ? "start_timeout" : "capture_timeout" + const changed = await tx + .update(gridTranscriptionRuns) + .set({ + state, + interruptionReason: reason, + stopRequestedAt: new Date(), + revision: sql`${gridTranscriptionRuns.revision} + 1`, + }) + .where( + and( + eq(gridTranscriptionRuns.id, run.id), + inArray(gridTranscriptionRuns.state, ["starting", "active"]), + or( + and( + isNull(gridTranscriptionRuns.workerId), + lt(gridTranscriptionRuns.createdAt, new Date(Date.now() - 30_000)), + ), + lte(gridTranscriptionRuns.expiresAt, new Date()), + lte(gridTranscriptionRuns.leaseExpiresAt, new Date()), + ), + ), + ) + .returning() + if (!changed.length) continue + await tx + .update(gridTranscriptionSegments) + .set({ state: "discarded" }) + .where(and(eq(gridTranscriptionSegments.runId, run.id), eq(gridTranscriptionSegments.state, "admitted"))) + if (state === "interrupted" && (await intactDestination(tx, run))) + await insertGridTranscriptMessage(tx, { + chatId: run.transcriptChatId, + actorUserId: run.actorUserId, + runId: run.id, + segmentId: "interrupted", + kind: "interrupted", + text: "Transcription interrupted", + }) + } +} +export function encodeGridTranscription(run: DbGridTranscriptionRun, canRead: boolean): GridTranscription { + const state = + { + starting: GridTranscriptionState.GRID_TRANSCRIPTION_STARTING, + active: GridTranscriptionState.GRID_TRANSCRIPTION_ACTIVE, + stopping: GridTranscriptionState.GRID_TRANSCRIPTION_STOPPING, + stopped: GridTranscriptionState.GRID_TRANSCRIPTION_STOPPED, + interrupted: GridTranscriptionState.GRID_TRANSCRIPTION_INTERRUPTED, + }[run.state] ?? GridTranscriptionState.GRID_TRANSCRIPTION_INTERRUPTED + return { + runId: run.id, + state, + transcriptChatId: canRead ? BigInt(run.transcriptChatId) : undefined, + revision: run.revision, + } +} +export async function stopGridTranscription(tx: Transaction, roomId: number, reason: string, discard = false) { + const runs = await tx + .select({ parentId: gridTranscriptionRuns.destinationParentChatId, chatId: gridTranscriptionRuns.transcriptChatId }) + .from(gridTranscriptionRuns) + .where( + and(eq(gridTranscriptionRuns.sourceRoomId, roomId), inArray(gridTranscriptionRuns.state, [...captureStates])), + ) + const chatIds = [...new Set(runs.flatMap((r) => [r.parentId, r.chatId]))].sort((a, b) => a - b) + if (chatIds.length) + await tx.select({ id: chats.id }).from(chats).where(inArray(chats.id, chatIds)).orderBy(chats.id).for("update") + const changed = await tx + .update(gridTranscriptionRuns) + .set({ + state: sql`CASE WHEN ${gridTranscriptionRuns.workerId} IS NULL THEN ${reason === "user_stop" ? "stopped" : "interrupted"} ELSE 'stopping' END`, + stopRequestedAt: sql`COALESCE(${gridTranscriptionRuns.stopRequestedAt}, ${new Date().toISOString()}::timestamp)`, + interruptionReason: sql`CASE WHEN ${inArray(gridTranscriptionRuns.interruptionReason, [...gridTranscriptionRetirementReasons])} THEN ${gridTranscriptionRuns.interruptionReason} ELSE ${reason} END`, + revision: sql`${gridTranscriptionRuns.revision} + 1`, + }) + .where( + and( + eq(gridTranscriptionRuns.sourceRoomId, roomId), + inArray(gridTranscriptionRuns.state, discard ? [...captureStates] : ["starting", "active"]), + ), + ) + .returning() + for (const run of changed) + if (run.workerId === null && (await intactDestination(tx, run))) + await insertGridTranscriptMessage(tx, { + chatId: run.transcriptChatId, + actorUserId: run.actorUserId, + runId: run.id, + segmentId: "stopped", + kind: reason === "user_stop" ? "stopped" : "interrupted", + text: reason === "user_stop" ? "Transcription stopped" : "Transcription interrupted", + }) + if (discard && changed.length) + await tx + .update(gridTranscriptionSegments) + .set({ state: "discarded" }) + .where( + and( + inArray( + gridTranscriptionSegments.runId, + changed.map((r) => r.id), + ), + eq(gridTranscriptionSegments.state, "admitted"), + ), + ) +} +/** Caller holds ordinary chat locks before changing runs; prevents late finals refilling cleared history. */ +export async function invalidateGridTranscriptionForHistory( + tx: Transaction, + input: { chatIds?: number[]; messageRefs?: { chatId: number; messageId: number }[]; reason?: string }, +) { + const conditions = [] + if (input.chatIds?.length) + conditions.push( + inArray(gridTranscriptionRuns.transcriptChatId, input.chatIds), + inArray(gridTranscriptionRuns.destinationParentChatId, input.chatIds), + inArray(gridTranscriptionRuns.roomChatId, input.chatIds), + ) + for (const ref of input.messageRefs ?? []) + conditions.push( + and( + eq(gridTranscriptionRuns.destinationParentChatId, ref.chatId), + eq(gridTranscriptionRuns.originalAnchorId, ref.messageId), + ), + and(eq(gridTranscriptionRuns.roomChatId, ref.chatId), eq(gridTranscriptionRuns.roomLinkMessageId, ref.messageId)), + ) + if (!conditions.length) return + await tx + .select({ id: gridTranscriptionRuns.id }) + .from(gridTranscriptionRuns) + .where(or(...conditions)) + .orderBy(gridTranscriptionRuns.id) + .for("update") + const changed = await tx + .update(gridTranscriptionRuns) + .set({ + state: sql`CASE WHEN ${gridTranscriptionRuns.state} IN ('starting', 'active', 'stopping') THEN CASE WHEN ${gridTranscriptionRuns.workerId} IS NULL THEN 'interrupted' ELSE 'stopping' END ELSE ${gridTranscriptionRuns.state} END`, + stopRequestedAt: sql`COALESCE(${gridTranscriptionRuns.stopRequestedAt}, ${new Date().toISOString()}::timestamp)`, + interruptionReason: input.reason ?? "history_changed", + revision: sql`${gridTranscriptionRuns.revision} + 1`, + }) + .where(or(...conditions)) + .returning({ id: gridTranscriptionRuns.id }) + // Historical associations are checked against their visible links, not hidden grants. + if (changed.length) + await tx + .update(gridTranscriptionSegments) + .set({ state: "discarded" }) + .where( + and( + inArray( + gridTranscriptionSegments.runId, + changed.map((r) => r.id), + ), + eq(gridTranscriptionSegments.state, "admitted"), + ), + ) +} +export async function intactDestination(tx: Transaction, run: DbGridTranscriptionRun) { + if (gridTranscriptionRetirementReasons.some((reason) => reason === run.interruptionReason)) return false + const [parent] = await tx.select().from(chats).where(eq(chats.id, run.destinationParentChatId)).limit(1) + const [source] = await tx.select().from(chats).where(eq(chats.id, run.roomChatId)).limit(1) + if ( + !source || + source.type !== "thread" || + source.spaceId !== run.spaceId || + source.publicThread !== false || + source.parentChatId !== null + ) + return false + const [child] = await tx.select().from(chats).where(eq(chats.id, run.transcriptChatId)).limit(1) + const [anchor] = await tx + .select({ id: messages.messageId }) + .from(messages) + .where(and(eq(messages.chatId, run.destinationParentChatId), eq(messages.messageId, run.originalAnchorId))) + .limit(1) + if ( + !parent || + !child || + !anchor || + parent.spaceId !== run.spaceId || + parent.publicThread !== false || + parent.parentChatId !== null || + child.spaceId !== run.spaceId || + child.parentChatId !== parent.id || + child.parentMessageId !== run.originalAnchorId + ) + return false + if (run.roomLinkMessageId !== null) { + const [link] = await tx + .select({ id: messages.messageId }) + .from(messages) + .where(and(eq(messages.chatId, run.roomChatId), eq(messages.messageId, run.roomLinkMessageId))) + .limit(1) + if (!link) return false + } + return true +} diff --git a/server/src/modules/grid/transcription/threads.ts b/server/src/modules/grid/transcription/threads.ts new file mode 100644 index 000000000..1edb96c3b --- /dev/null +++ b/server/src/modules/grid/transcription/threads.ts @@ -0,0 +1,220 @@ +import { registerPostCommitHook } from "@in/server/db/commitHooks" +import { UpdatesModel } from "@in/server/db/models/updates" +import { chats, chatParticipants, dialogs, gridRooms, members, messages, users, userNotDeleted, UpdateBucket, type DbChat } from "@in/server/db/schema" +import type { Transaction } from "@in/server/db/types" +import { AccessGuardsCache } from "@in/server/modules/authorization/accessGuardsCache" +import { getEffectiveChatAccessUserIds, getRootChatIdsForAccessEvents } from "@in/server/modules/authorization/chatAccessProjection" +import { publishAccessChanged } from "@in/server/modules/cache/cluster" +import { chatTitleFields } from "@in/server/modules/encryption/chatTitleStorage" +import { encryptBinary } from "@in/server/modules/encryption/encryption" +import { publishDurableReference } from "@in/server/modules/internalMessaging/durable" +import { allocateThreadNumber } from "@in/server/modules/threadNumbers" +import { UserBucketUpdates } from "@in/server/modules/updates/userBucketUpdates" +import { pushChatCatchupHintsBestEffort, pushParticipantUserUpdateBestEffort } from "@in/server/modules/updates/participantLiveUpdates" +import { RealtimeRpcError } from "@in/server/realtime/errors" +import { encodeDateStrict } from "@in/server/realtime/encoders/helpers" +import { GridTranscriptMessageKind, MessageEntities, MessageEntity_Type } from "@inline-chat/protocol/core" +import { and, eq, inArray } from "drizzle-orm" +import { insertGridTranscriptMessage } from "./messages" + +/** Called under the Grid mutation/Space owner; caller supplies current roster. */ +export async function ensureGridRoomThread(tx: Transaction, input: { + room: { id: number; spaceId: number; title: string | null; roomThreadId?: number | null } + actorUserId: number + participantUserIds: number[] +}): Promise { + const [room] = await tx.select().from(gridRooms).where(eq(gridRooms.id, input.room.id)).for("update").limit(1) + if (!room || room.spaceId !== input.room.spaceId) throw RealtimeRpcError.BadRequest() + let chatId = room.roomThreadId + if (chatId !== null) { + const [existing] = await tx.select().from(chats).where(eq(chats.id, chatId)).for("update").limit(1) + if (existing && !isGridPrivateParent(existing, room.spaceId)) throw RealtimeRpcError.BadRequest() + if (!existing) chatId = null + } + if (chatId === null) { + const chat = await createPrivateGridChat(tx, { + spaceId: room.spaceId, + actorUserId: input.actorUserId, + title: room.title ?? "Grid room", + }) + chatId = chat.id + await tx.update(gridRooms).set({ roomThreadId: chatId }).where(eq(gridRooms.id, room.id)) + } + await grantGridTranscriptUsers(tx, { chatIds: [chatId], userIds: input.participantUserIds }) + return chatId +} + +export async function createGridTranscriptDestination(tx: Transaction, input: { + roomChatId: number + spaceId: number + actorUserId: number + participantUserIds: number[] + title: string + runId: string +}): Promise<{ transcriptChatId: number; destinationParentChatId: number; originalAnchorId: number }> { + const [parent] = await tx.select().from(chats).where(eq(chats.id, input.roomChatId)).for("update").limit(1) + if (!parent || !isGridPrivateParent(parent, input.spaceId)) throw RealtimeRpcError.BadRequest() + await grantGridTranscriptUsers(tx, { chatIds: [parent.id], userIds: input.participantUserIds }) + const anchorText = "Grid transcription" + const anchor = await insertGridTranscriptMessage(tx, { + chatId: parent.id, + actorUserId: input.actorUserId, + runId: input.runId, + segmentId: "anchor", + kind: GridTranscriptMessageKind.GRID_TRANSCRIPT_STARTED, + text: anchorText, + }) + const child = await createPrivateGridChat(tx, { + spaceId: input.spaceId, + actorUserId: input.actorUserId, + title: input.title, + parentChatId: parent.id, + parentMessageId: anchor.message.messageId, + }) + const encryptedEntities = encryptBinary(MessageEntities.toBinary({ entities: [{ + type: MessageEntity_Type.THREAD, + offset: 0n, + length: BigInt(anchorText.length), + entity: { oneofKind: "thread", thread: { chatId: BigInt(child.id) } }, + }] })) + const entitiesFields = { + entitiesEncrypted: encryptedEntities.encrypted, + entitiesIv: encryptedEntities.iv, + entitiesTag: encryptedEntities.authTag, + } + await tx.update(messages).set(entitiesFields).where(eq(messages.globalId, anchor.message.globalId)) + // The newMessage commit hook captures this result. Fill navigation before + // commit so live delivery and history both have the child on their first row. + Object.assign(anchor.message, entitiesFields) + // Parent membership supplies cumulative history. Keep only the initiator's + // closed, hidden child dialog; generated turns cannot open anyone's inbox. + await tx.insert(dialogs).values({ + chatId: child.id, + userId: input.actorUserId, + spaceId: input.spaceId, + open: false, + chatListHidden: true, + }).onConflictDoNothing() + return { transcriptChatId: child.id, destinationParentChatId: parent.id, originalAnchorId: anchor.message.messageId } +} + +export async function insertGridTranscriptContinuationLink(tx: Transaction, input: { + roomChatId: number + transcriptChatId: number + actorUserId: number + runId: string + title: string +}): Promise { + const title = input.title.trim() || "Grid transcription" + const prefix = "Continue transcript: " + const result = await insertGridTranscriptMessage(tx, { + chatId: input.roomChatId, + actorUserId: input.actorUserId, + runId: input.runId, + segmentId: "link", + kind: GridTranscriptMessageKind.GRID_TRANSCRIPT_LINK, + text: `${prefix}${title}`, + entities: { entities: [{ + type: MessageEntity_Type.THREAD, + offset: BigInt(prefix.length), + length: BigInt(title.length), + entity: { oneofKind: "thread", thread: { chatId: BigInt(input.transcriptChatId) } }, + }] }, + }) + return result.message.messageId +} + +/** + * Ordinary cumulative private-parent enrollment; existing dialogs stay closed + * or archived. Only committed grants update caches/live discovery. Grid/Space + * authorization is owned by the caller, and membership is rechecked here. + */ +export async function grantGridTranscriptUsers(tx: Transaction, input: { chatIds: number[]; userIds: number[] }): Promise { + const chatIds = Array.from(new Set(input.chatIds)).sort((a, b) => a - b) + const userIds = Array.from(new Set(input.userIds)).sort((a, b) => a - b) + if (chatIds.length === 0 || userIds.length === 0) return + const chatRows = await tx.select().from(chats).where(inArray(chats.id, chatIds)).orderBy(chats.id).for("update") + if (chatRows.length !== chatIds.length) throw RealtimeRpcError.ChatIdInvalid() + const rootIds = await getRootChatIdsForAccessEvents(tx, chatIds) + const accessBefore = await getEffectiveChatAccessUserIds(tx, rootIds, { userIds }) + for (const chat of chatRows) { + if (chat.spaceId === null || !isGridPrivateParent(chat, chat.spaceId)) throw RealtimeRpcError.BadRequest() + const eligible = await tx.select({ userId: members.userId }).from(members) + .innerJoin(users, eq(users.id, members.userId)) + .where(and(eq(members.spaceId, chat.spaceId), inArray(members.userId, userIds), userNotDeleted())) + if (eligible.length !== userIds.length) throw RealtimeRpcError.BadRequest() + const inserted = await tx.insert(chatParticipants).values(userIds.map((userId) => ({ chatId: chat.id, userId }))) + .onConflictDoNothing().returning() + await tx.insert(dialogs).values(userIds.map((userId) => ({ + chatId: chat.id, userId, spaceId: chat.spaceId, open: false, + }))).onConflictDoNothing() + let frontier = chat.updateSeq ?? 0 + for (const participant of inserted) { + const update = await UpdatesModel.insertUpdate(tx, { + update: { oneofKind: "participantAdd", participantAdd: { + chatId: BigInt(chat.id), + participant: { userId: BigInt(participant.userId), date: encodeDateStrict(participant.date) }, + } }, + bucket: UpdateBucket.Chat, + entity: { id: chat.id, updateSeq: frontier }, + }) + frontier = update.seq + await tx.update(chats).set({ updateSeq: update.seq, lastUpdateDate: update.date }).where(eq(chats.id, chat.id)) + } + if (inserted.length > 0) { + registerPostCommitHook(tx, Symbol("grid-thread-access"), { run: async () => { + publishAccessChanged({ kind: "chat", chatId: chat.id }) + for (const participant of inserted) AccessGuardsCache.setChatParticipant(chat.id, participant.userId) + publishDurableReference({ bucket: { kind: "chat", chatId: chat.id }, frontier }) + await pushChatCatchupHintsBestEffort({ chatId: chat.id, currentUserId: inserted[0]!.userId, updateSeq: frontier }) + } }) + } + } + const accessAfter = await getEffectiveChatAccessUserIds(tx, rootIds, { userIds }) + const gained = rootIds.flatMap((chatId) => userIds.filter((userId) => + !accessBefore.get(chatId)?.has(userId) && accessAfter.get(chatId)?.has(userId), + ).map((userId) => ({ chatId, userId }))) + const accessUpdates = await UserBucketUpdates.enqueueMany(gained.map(({ chatId, userId }) => ({ + userId, update: { oneofKind: "userAddedToChat", userAddedToChat: { chatId: BigInt(chatId) } }, + })), { tx }) + if (gained.length > 0) registerPostCommitHook(tx, Symbol("grid-thread-discovery"), { run: async () => { + await Promise.all(gained.map(({ chatId, userId }, index) => { + const update = accessUpdates[index]! + return pushParticipantUserUpdateBestEffort(userId, { + seq: update.seq, date: encodeDateStrict(update.date), + update: { oneofKind: "userAddedToChat", userAddedToChat: { chatId: BigInt(chatId) } }, + }) + })) + } }) +} + +export function isGridPrivateParent(chat: DbChat, spaceId: number): boolean { + return chat.type === "thread" && chat.spaceId === spaceId && chat.publicThread === false && chat.parentChatId === null +} + +async function createPrivateGridChat(tx: Transaction, input: { + spaceId: number + actorUserId: number + title: string + parentChatId?: number + parentMessageId?: number +}): Promise { + const threadNumber = await allocateThreadNumber(tx, { type: "space", id: input.spaceId }) + const [chat] = await tx.insert(chats).values({ + type: "thread", spaceId: input.spaceId, createdBy: input.actorUserId, publicThread: false, + ...chatTitleFields(input.title, { spaceId: input.spaceId, createdBy: input.actorUserId }), + isUntitled: false, threadNumber, + parentChatId: input.parentChatId ?? null, parentMessageId: input.parentMessageId ?? null, + }).returning() + if (!chat) throw RealtimeRpcError.InternalError() + const update = await UpdatesModel.insertUpdate(tx, { + update: { oneofKind: "newChat", newChat: { chatId: BigInt(chat.id) } }, bucket: UpdateBucket.Chat, entity: chat, + }) + const [updated] = await tx.update(chats).set({ updateSeq: update.seq, lastUpdateDate: update.date }) + .where(eq(chats.id, chat.id)).returning() + if (!updated) throw RealtimeRpcError.InternalError() + registerPostCommitHook(tx, Symbol("grid-thread-created"), { run: async () => { + publishDurableReference({ bucket: { kind: "chat", chatId: chat.id }, frontier: update.seq }) + } }) + return updated +} diff --git a/server/src/modules/grid/transcription/worker.ts b/server/src/modules/grid/transcription/worker.ts new file mode 100644 index 000000000..b11bde485 --- /dev/null +++ b/server/src/modules/grid/transcription/worker.ts @@ -0,0 +1,564 @@ +import { createHmac, timingSafeEqual } from "node:crypto" +import { Schema } from "effect" +import { AccessToken } from "livekit-server-sdk" +import { db } from "@in/server/db" +import { + chats, + gridPresence, + gridRooms, + gridTranscriptionRuns, + gridTranscriptionSegments, + gridTranscriptionWorker, + members, + sessions, + spaces, + users, + type DbGridTranscriptionRun, +} from "@in/server/db/schema" +import type { Transaction } from "@in/server/db/types" +import { + getLiveKitGridConfig, + gridParticipantIdentity, + providerRoomName, + durableLiveKitProviderTarget, +} from "@in/server/modules/grid/livekit" +import { lockGridMutations } from "@in/server/modules/grid/roomLifecycle" +import { notifyGridSpaceChanged } from "@in/server/modules/grid/realtime" +import { SpaceSettingsModel } from "@in/server/db/models/spaceSettings" +import { + GRID_TRANSCRIPTION_LEASE_MS, + GRID_TRANSCRIPTION_MAX_SPEAKERS, + intactDestination, + transcriptionConfig, + supportedGridTranscriptionSession, +} from "./state" +import { insertGridTranscriptMessage } from "./messages" +import { and, asc, eq, gt, inArray, isNull, sql, count, or } from "drizzle-orm" + +const boundedString = (max: number) => Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(max)) +const identitySchema = Schema.Struct({ workerId: boundedString(80) }) +const heartbeatSchema = Schema.Struct({ + workerId: boundedString(80), + model: Schema.Literals(["meeting", "standard"]), + ready: Schema.Boolean, +}) +const admissionSchema = Schema.Struct({ + participantIdentity: boundedString(128), + trackSid: boundedString(128), + sourceTurnKey: boundedString(128), +}) +const finalSchema = Schema.Struct({ + segmentId: Schema.String.check(Schema.isUUID()), + text: Schema.String.check(Schema.isMaxLength(32768)), +}) +const stoppedSchema = Schema.Struct({ reason: Schema.optional(boundedString(80)) }) +const tokenSchema = Schema.Struct({ + runId: Schema.String.check(Schema.isUUID()), + epoch: Schema.Int.check(Schema.isGreaterThan(0)), + expiresAt: Schema.Int, +}) +class WorkerRequestError extends Error { + constructor(readonly status: number) { + super("Grid transcription request rejected") + } +} +function equal(a: string, b: string) { + const aa = Buffer.from(a) + const bb = Buffer.from(b) + return aa.length === bb.length && timingSafeEqual(aa, bb) +} +function sign(body: string, secret: string) { + return createHmac("sha256", secret).update(body).digest("base64url") +} +function runToken(run: DbGridTranscriptionRun, secret: string) { + const body = Buffer.from( + JSON.stringify({ runId: run.id, epoch: run.claimEpoch, expiresAt: run.expiresAt.getTime() + 30_000 }), + ).toString("base64url") + return `${body}.${sign(body, secret)}` +} +function decodeRunToken(token: string, secret: string, allowExpired = false) { + const [body, signature] = token.split(".") + if (!body || !signature || !equal(signature, sign(body, secret))) throw new WorkerRequestError(401) + const authority = Schema.decodeUnknownSync(tokenSchema)(JSON.parse(Buffer.from(body, "base64url").toString("utf8"))) + if (!allowExpired && authority.expiresAt < Date.now()) throw new WorkerRequestError(401) + return authority +} +async function currentParticipants(tx: Transaction, run: DbGridTranscriptionRun) { + const rows = await tx + .select({ + userId: gridPresence.userId, + membershipId: gridPresence.mediaMembershipId, + clientType: sessions.clientType, + clientVersion: sessions.clientVersion, + }) + .from(gridPresence) + .innerJoin(members, and(eq(members.userId, gridPresence.userId), eq(members.spaceId, run.spaceId))) + .innerJoin(users, and(eq(users.id, gridPresence.userId), eq(users.bot, false))) + .innerJoin(sessions, and(eq(sessions.id, gridPresence.ownerSessionId), isNull(sessions.revoked))) + .where(and(eq(gridPresence.roomId, run.sourceRoomId), gt(gridPresence.leaseExpiresAt, new Date()))) + return rows.map((row) => ({ + userId: row.userId, + membershipId: row.membershipId, + identity: gridParticipantIdentity(row.userId, row.membershipId), + qualified: supportedGridTranscriptionSession(row), + })) +} +async function captureAuthority(tx: Transaction, run: DbGridTranscriptionRun) { + const [room] = await tx.select().from(gridRooms).where(eq(gridRooms.id, run.sourceRoomId)).limit(1) + const [space] = await tx + .select({ id: spaces.id }) + .from(spaces) + .where(and(eq(spaces.id, run.spaceId), isNull(spaces.deleted))) + .limit(1) + const participants = await currentParticipants(tx, run) + return { + participants, + valid: + !!room && + !!space && + room.connectionStartedAt !== null && + room.connectionGeneration === run.generation && + run.providerTarget === durableLiveKitProviderTarget() && + participants.length >= 2 && + participants.length <= GRID_TRANSCRIPTION_MAX_SPEAKERS && + participants.every((p) => p.qualified) && + run.expiresAt.getTime() > Date.now() && + (await SpaceSettingsModel.getStored(run.spaceId, tx)).gridEnabled && + (await intactDestination(tx, run)), + } +} +function wireParticipants(participants: Awaited>) { + return participants.map(({ identity, userId, membershipId }) => ({ identity, userId, membershipId })) +} +async function lockedRun(tx: Transaction, authority: { runId: string; epoch: number }) { + const [run] = await tx + .select() + .from(gridTranscriptionRuns) + .where(and(eq(gridTranscriptionRuns.id, authority.runId), eq(gridTranscriptionRuns.claimEpoch, authority.epoch))) + .for("update") + .limit(1) + if (!run) throw new WorkerRequestError(409) + return run +} +async function readBody(request: Request): Promise { + const reader = request.body?.getReader() + if (!reader) throw new WorkerRequestError(400) + const chunks: Uint8Array[] = [] + let size = 0 + try { + while (true) { + const next = await reader.read() + if (next.done) break + size += next.value.byteLength + if (size > 262_144) { + await reader.cancel() + throw new WorkerRequestError(413) + } + chunks.push(next.value) + } + } finally { + reader.releaseLock() + } + const bytes = new Uint8Array(size) + let offset = 0 + for (const chunk of chunks) { + bytes.set(chunk, offset) + offset += chunk.byteLength + } + return JSON.parse(new TextDecoder().decode(bytes)) +} +/** Authenticated internal route only. Never logs body, credentials, audio or text. */ +export async function handleGridTranscriptionWorkerRequest(request: Request): Promise { + const config = transcriptionConfig() + const action = new URL(request.url).pathname.split("/").at(-1) + if (!config.secret || config.secret.length < 32 || (!config.enabled && action !== "stopped" && action !== "claim")) + return Response.json({ error: "unavailable" }, { status: 503 }) + if (request.method !== "POST") return new Response(null, { status: 405 }) + if (Number(request.headers.get("content-length") ?? 0) > 262144) return new Response(null, { status: 413 }) + try { + const token = request.headers.get("authorization")?.replace(/^Bearer /, "") ?? "" + if (action === "heartbeat" || action === "claim") { + if (!equal(token, config.secret)) throw new WorkerRequestError(401) + const raw = await readBody(request) + const { workerId } = Schema.decodeUnknownSync(identitySchema)(raw) + if (action === "heartbeat") { + const heartbeat = Schema.decodeUnknownSync(heartbeatSchema)(raw) + const heartbeatAt = heartbeat.ready ? new Date() : new Date(0) + await db + .insert(gridTranscriptionWorker) + .values({ id: 1, workerId, model: heartbeat.model, heartbeatAt }) + .onConflictDoUpdate({ + target: gridTranscriptionWorker.id, + set: { workerId, model: heartbeat.model, heartbeatAt }, + }) + return Response.json({ ready: heartbeat.ready }) + } + const livekit = getLiveKitGridConfig() + const claimed = await db.transaction(async (tx) => { + await lockGridMutations(tx) + const [run] = await tx + .select() + .from(gridTranscriptionRuns) + .where( + and( + or( + and(eq(gridTranscriptionRuns.state, "starting"), isNull(gridTranscriptionRuns.workerId)), + and( + inArray(gridTranscriptionRuns.state, ["starting", "stopping"]), + eq(gridTranscriptionRuns.workerId, workerId), + ), + ), + ), + ) + .orderBy(asc(gridTranscriptionRuns.createdAt)) + .for("update") + .limit(1) + if (!run) return undefined + const [readyWorker] = await tx + .select() + .from(gridTranscriptionWorker) + .where( + and( + eq(gridTranscriptionWorker.id, 1), + eq(gridTranscriptionWorker.workerId, workerId), + eq(gridTranscriptionWorker.model, run.model), + gt(gridTranscriptionWorker.heartbeatAt, new Date(Date.now() - GRID_TRANSCRIPTION_LEASE_MS)), + ), + ) + .limit(1) + const owned = run.workerId === workerId + if (!owned && (!config.enabled || !livekit || !readyWorker)) throw new WorkerRequestError(409) + const authority = await captureAuthority(tx, run) + if (owned && run.state === "stopping") return { run, stopImmediately: true as const } + if ( + owned && + (!config.enabled || + !livekit || + !readyWorker || + !authority.valid || + !run.leaseExpiresAt || + run.leaseExpiresAt.getTime() <= Date.now()) + ) { + const [stopping] = await tx + .update(gridTranscriptionRuns) + .set({ + state: "stopping", + stopRequestedAt: run.stopRequestedAt ?? new Date(), + interruptionReason: run.interruptionReason ?? "start_unavailable", + revision: sql`${gridTranscriptionRuns.revision} + 1`, + }) + .where(eq(gridTranscriptionRuns.id, run.id)) + .returning() + if (!stopping) throw new WorkerRequestError(409) + return { run: stopping, stopImmediately: true as const } + } + if (!livekit) throw new WorkerRequestError(503) + if (!authority.valid || (!owned && run.createdAt.getTime() < Date.now() - 30_000)) { + await tx + .update(gridTranscriptionRuns) + .set({ + state: run.workerId === null ? "interrupted" : "stopping", + stopRequestedAt: new Date(), + interruptionReason: run.interruptionReason ?? "start_unavailable", + revision: sql`${gridTranscriptionRuns.revision} + 1`, + }) + .where(eq(gridTranscriptionRuns.id, run.id)) + return undefined + } + const [updated] = await tx + .update(gridTranscriptionRuns) + .set({ + workerId, + claimEpoch: run.claimEpoch || 1, + leaseExpiresAt: new Date(Date.now() + GRID_TRANSCRIPTION_LEASE_MS), + revision: sql`${gridTranscriptionRuns.revision} + 1`, + }) + .where(eq(gridTranscriptionRuns.id, run.id)) + .returning() + if (!updated) return undefined + const credential = new AccessToken(livekit.apiKey, livekit.apiSecret, { + identity: `inline-grid-transcription-${updated.id}-${updated.claimEpoch}`, + name: "Inline transcription", + ttl: 60, + }) + credential.addGrant({ + roomJoin: true, + room: providerRoomName({ roomId: BigInt(updated.sourceRoomId), generation: updated.generation }), + canPublish: false, + canSubscribe: true, + canPublishData: false, + canUpdateOwnMetadata: false, + }) + return { + run: updated, + participants: authority.participants, + livekitToken: await credential.toJwt(), + serverUrl: livekit.serverUrl, + stopImmediately: false as const, + } + }) + if (!claimed) return new Response(null, { status: 204 }) + await notifyGridSpaceChanged(claimed.run.spaceId) + if (claimed.stopImmediately) + return Response.json({ + runId: claimed.run.id, + claimEpoch: claimed.run.claimEpoch, + runToken: runToken(claimed.run, config.secret), + stopImmediately: true, + }) + return Response.json({ + runId: claimed.run.id, + claimEpoch: claimed.run.claimEpoch, + runToken: runToken(claimed.run, config.secret), + roomId: claimed.run.sourceRoomId, + generation: claimed.run.generation, + providerTarget: claimed.run.providerTarget, + livekit: { serverUrl: claimed.serverUrl, token: claimed.livekitToken }, + model: claimed.run.model, + leaseMs: GRID_TRANSCRIPTION_LEASE_MS, + expiresAt: claimed.run.expiresAt.toISOString(), + participants: wireParticipants(claimed.participants), + }) + } + const authority = decodeRunToken(token, config.secret, action === "stopped") + if (action === "renew") { + const result = await db.transaction(async (tx) => { + const run = await lockedRun(tx, authority) + const active = await captureAuthority(tx, run) + const canRenew = + (run.state === "active" || run.state === "starting") && + !!run.leaseExpiresAt && + run.leaseExpiresAt.getTime() > Date.now() && + active.valid + const leaseExpiresAt = new Date(Date.now() + GRID_TRANSCRIPTION_LEASE_MS) + const stateChanged = !canRenew && (run.state === "active" || run.state === "starting") + if (canRenew) + await tx.update(gridTranscriptionRuns).set({ leaseExpiresAt }).where(eq(gridTranscriptionRuns.id, run.id)) + else if (stateChanged) + await tx + .update(gridTranscriptionRuns) + .set({ + state: "stopping", + stopRequestedAt: new Date(), + interruptionReason: "authority_lost", + revision: sql`${gridTranscriptionRuns.revision} + 1`, + }) + .where(eq(gridTranscriptionRuns.id, run.id)) + return { + state: canRenew ? "active" : run.state === "stopped" || run.state === "interrupted" ? "stopped" : "stopping", + allowFinalFlush: + run.state === "stopping" && + run.interruptionReason === "user_stop" && + active.valid && + !!run.stopRequestedAt && + Date.now() - run.stopRequestedAt.getTime() <= 5000 && + !!run.leaseExpiresAt && + run.leaseExpiresAt.getTime() > Date.now(), + leaseExpiresAt: (canRenew ? leaseExpiresAt : (run.leaseExpiresAt ?? new Date())).toISOString(), + participants: wireParticipants(active.participants), + spaceId: run.spaceId, + stateChanged, + } + }) + const { spaceId, stateChanged, ...response } = result + if (stateChanged) await notifyGridSpaceChanged(spaceId) + return Response.json(response) + } + if (action === "admit") { + const input = Schema.decodeUnknownSync(admissionSchema)(await readBody(request)) + const result = await db.transaction(async (tx) => { + const run = await lockedRun(tx, authority) + const active = await captureAuthority(tx, run) + const speaker = active.participants.find((p) => p.identity === input.participantIdentity) + if ( + !active.valid || + !speaker || + !run.leaseExpiresAt || + run.leaseExpiresAt.getTime() <= Date.now() || + (run.state !== "starting" && run.state !== "active") + ) + throw new WorkerRequestError(409) + const [existing] = await tx + .select() + .from(gridTranscriptionSegments) + .where( + and( + eq(gridTranscriptionSegments.runId, run.id), + eq(gridTranscriptionSegments.trackSid, input.trackSid), + eq(gridTranscriptionSegments.sourceTurnKey, input.sourceTurnKey), + ), + ) + .limit(1) + if (existing) { + if ( + existing.claimEpoch !== run.claimEpoch || + existing.speakerUserId !== speaker.userId || + existing.membershipId !== speaker.membershipId || + existing.state !== "admitted" + ) + throw new WorkerRequestError(409) + return { segmentId: existing.id, spaceId: run.spaceId } + } + const pending = await tx + .select({ trackSid: gridTranscriptionSegments.trackSid, value: count() }) + .from(gridTranscriptionSegments) + .where(and(eq(gridTranscriptionSegments.runId, run.id), eq(gridTranscriptionSegments.state, "admitted"))) + .groupBy(gridTranscriptionSegments.trackSid) + if ( + pending.reduce((sum, p) => sum + Number(p.value), 0) >= 64 || + Number(pending.find((p) => p.trackSid === input.trackSid)?.value ?? 0) >= 2 + ) + throw new WorkerRequestError(409) + const [segment] = await tx + .insert(gridTranscriptionSegments) + .values({ + runId: run.id, + claimEpoch: run.claimEpoch, + speakerUserId: speaker.userId, + membershipId: speaker.membershipId, + trackSid: input.trackSid, + sourceTurnKey: input.sourceTurnKey, + }) + .returning({ id: gridTranscriptionSegments.id }) + if (!segment) throw new WorkerRequestError(409) + if (run.state === "starting") + await tx + .update(gridTranscriptionRuns) + .set({ state: "active", revision: sql`${gridTranscriptionRuns.revision} + 1` }) + .where(eq(gridTranscriptionRuns.id, run.id)) + return { segmentId: segment.id, spaceId: run.spaceId } + }) + await notifyGridSpaceChanged(result.spaceId) + return Response.json({ segmentId: result.segmentId }) + } + if (action === "final") { + const input = Schema.decodeUnknownSync(finalSchema)(await readBody(request)) + if (Buffer.byteLength(input.text, "utf8") > 32768) throw new WorkerRequestError(413) + const saved = await db.transaction(async (tx) => { + // History mutations lock chats before runs. Do the same here. + const [target] = await tx + .select({ + chatId: gridTranscriptionRuns.transcriptChatId, + parentId: gridTranscriptionRuns.destinationParentChatId, + }) + .from(gridTranscriptionRuns) + .where(eq(gridTranscriptionRuns.id, authority.runId)) + .limit(1) + if (!target) throw new WorkerRequestError(409) + await tx + .select({ id: chats.id }) + .from(chats) + .where(inArray(chats.id, [target.parentId, target.chatId])) + .orderBy(asc(chats.id)) + .for("update") + const run = await lockedRun(tx, authority) + const [segment] = await tx + .select() + .from(gridTranscriptionSegments) + .where( + and( + eq(gridTranscriptionSegments.id, input.segmentId), + eq(gridTranscriptionSegments.runId, run.id), + eq(gridTranscriptionSegments.claimEpoch, run.claimEpoch), + ), + ) + .for("update") + .limit(1) + if (!segment) throw new WorkerRequestError(409) + if (segment.state !== "admitted") return { messageId: segment.messageId, inserted: undefined } + const [member] = await tx + .select({ id: members.id }) + .from(members) + .where(and(eq(members.spaceId, run.spaceId), eq(members.userId, segment.speakerUserId))) + .limit(1) + const current = await captureAuthority(tx, run) + const allowed = + !!member && + current.valid && + !!run.leaseExpiresAt && + run.leaseExpiresAt.getTime() > Date.now() && + run.expiresAt.getTime() > Date.now() && + (run.state === "active" || + run.state === "starting" || + (run.state === "stopping" && + run.interruptionReason === "user_stop" && + !!run.stopRequestedAt && + Date.now() - run.stopRequestedAt.getTime() <= 5000)) + if (!allowed || !input.text.trim()) { + await tx + .update(gridTranscriptionSegments) + .set({ state: "discarded" }) + .where(eq(gridTranscriptionSegments.id, segment.id)) + return { messageId: null, inserted: undefined } + } + const inserted = await insertGridTranscriptMessage(tx, { + chatId: run.transcriptChatId, + actorUserId: run.actorUserId, + runId: run.id, + segmentId: segment.id, + speakerUserId: segment.speakerUserId, + kind: "turn", + text: input.text.trim(), + }) + await tx + .update(gridTranscriptionSegments) + .set({ state: "finalized", messageId: inserted.message.messageId }) + .where(eq(gridTranscriptionSegments.id, segment.id)) + return { messageId: inserted.message.messageId, inserted } + }) + return Response.json({ messageId: saved.messageId }) + } + if (action === "stopped") { + Schema.decodeUnknownSync(stoppedSchema)(await readBody(request)) + const spaceId = await db.transaction(async (tx) => { + const [target] = await tx + .select({ + chatId: gridTranscriptionRuns.transcriptChatId, + parentId: gridTranscriptionRuns.destinationParentChatId, + }) + .from(gridTranscriptionRuns) + .where(eq(gridTranscriptionRuns.id, authority.runId)) + .limit(1) + if (!target) throw new WorkerRequestError(409) + await tx + .select({ id: chats.id }) + .from(chats) + .where(inArray(chats.id, [target.parentId, target.chatId])) + .orderBy(asc(chats.id)) + .for("update") + const run = await lockedRun(tx, authority) + if (run.state !== "stopped" && run.state !== "interrupted") { + if (await intactDestination(tx, run)) + await insertGridTranscriptMessage(tx, { + chatId: run.transcriptChatId, + actorUserId: run.actorUserId, + runId: run.id, + segmentId: "stopped", + kind: run.interruptionReason === "user_stop" ? "stopped" : "interrupted", + text: run.interruptionReason === "user_stop" ? "Transcription stopped" : "Transcription interrupted", + }) + await tx + .update(gridTranscriptionRuns) + .set({ + state: run.interruptionReason === "user_stop" ? "stopped" : "interrupted", + revision: sql`${gridTranscriptionRuns.revision} + 1`, + }) + .where(eq(gridTranscriptionRuns.id, run.id)) + await tx + .update(gridTranscriptionSegments) + .set({ state: "discarded" }) + .where(and(eq(gridTranscriptionSegments.runId, run.id), eq(gridTranscriptionSegments.state, "admitted"))) + } + return run.spaceId + }) + await notifyGridSpaceChanged(spaceId) + return Response.json({ stopped: true }) + } + return new Response(null, { status: 404 }) + } catch (error) { + if (error instanceof WorkerRequestError) return Response.json({ error: "rejected" }, { status: error.status }) + if (Schema.isSchemaError(error) || error instanceof SyntaxError) + return Response.json({ error: "invalid_request" }, { status: 400 }) + return Response.json({ error: "request_failed" }, { status: 500 }) + } +} diff --git a/server/src/modules/historyClear/data.ts b/server/src/modules/historyClear/data.ts index b25c0d673..1fb880997 100644 --- a/server/src/modules/historyClear/data.ts +++ b/server/src/modules/historyClear/data.ts @@ -4,6 +4,8 @@ import type { Transaction } from "@in/server/db/types" import { and, desc, eq, inArray, isNull, lt, ne, or, sql } from "drizzle-orm" import { deleteUnreferencedBlockContents } from "@in/server/modules/message/blockContentStorage" import { getEffectiveChatAccessUserIds } from "@in/server/modules/authorization/chatAccessProjection" +import { messageActivityPredicate } from "@in/server/modules/message/activity" +import { invalidateGridTranscriptionForHistory } from "@in/server/modules/grid/transcription/state" export type ClearHistoryOptions = { beforeDate?: Date @@ -144,6 +146,9 @@ export async function clearChatHistoryData( input: ClearChatHistoryInput, hooks?: ClearHistoryHooks, ): Promise { + // Even a dated clear conservatively ends affected capture. Never flush + // already admitted speech back into the history the user just removed. + await invalidateGridTranscriptionForHistory(tx, { chatIds: [input.chatId], reason: "history_cleared" }) let deletedChatIds: number[] = [] let deletedChats: ClearHistoryDeletedChat[] = [] let orphanedChatIds: number[] = [] @@ -178,6 +183,8 @@ export async function clearSpaceHistoryData( input: ClearSpaceHistoryInput, hooks?: ClearHistoryHooks, ): Promise { + const spaceChatRows = await tx.select({ id: chats.id }).from(chats).where(eq(chats.spaceId, input.spaceId)) + await invalidateGridTranscriptionForHistory(tx, { chatIds: spaceChatRows.map((chat) => chat.id), reason: "history_cleared" }) const detachedExternalChats = await detachExternalReplyThreadsForClearedSpaceMessages( tx, input.spaceId, @@ -228,7 +235,7 @@ async function refreshChatLastMsgId(tx: Transaction, chatId: number): Promise { constructor() { super("server.SystemMessage", [ { no: 1, name: "thread_backlink", kind: "message", oneof: "event", T: () => ThreadBacklinkSystemMessage }, - { no: 2, name: "pinned_message", kind: "message", oneof: "event", T: () => PinnedMessageSystemMessage } + { no: 2, name: "pinned_message", kind: "message", oneof: "event", T: () => PinnedMessageSystemMessage }, + { no: 3, name: "grid_transcript", kind: "message", oneof: "event", T: () => MessageServiceGridTranscript } ]); } create(value?: PartialMessage): SystemMessage { @@ -4311,6 +4319,12 @@ class SystemMessage$Type extends MessageType { pinnedMessage: PinnedMessageSystemMessage.internalBinaryRead(reader, reader.uint32(), options, (message.event as any).pinnedMessage) }; break; + case /* MessageServiceGridTranscript grid_transcript */ 3: + message.event = { + oneofKind: "gridTranscript", + gridTranscript: MessageServiceGridTranscript.internalBinaryRead(reader, reader.uint32(), options, (message.event as any).gridTranscript) + }; + break; default: let u = options.readUnknownField; if (u === "throw") @@ -4329,6 +4343,9 @@ class SystemMessage$Type extends MessageType { /* server.PinnedMessageSystemMessage pinned_message = 2; */ if (message.event.oneofKind === "pinnedMessage") PinnedMessageSystemMessage.internalBinaryWrite(message.event.pinnedMessage, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* MessageServiceGridTranscript grid_transcript = 3; */ + if (message.event.oneofKind === "gridTranscript") + MessageServiceGridTranscript.internalBinaryWrite(message.event.gridTranscript, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); diff --git a/server/src/realtime/encoders/encodeMessage.test.ts b/server/src/realtime/encoders/encodeMessage.test.ts index 721a26b7c..fc4da3df9 100644 --- a/server/src/realtime/encoders/encodeMessage.test.ts +++ b/server/src/realtime/encoders/encodeMessage.test.ts @@ -8,6 +8,7 @@ import { BlockDisclosure_Kind, MessageEntities, MessageEntity_Type, + MessageSendMode, Photo_Format, type Peer, } from "@inline-chat/protocol/core" @@ -466,6 +467,31 @@ describe("mentioned", () => { }) describe("service messages", () => { + it("keeps generated transcription quiet in direct and history projections", () => { + const service = { + event: { + oneofKind: "gridTranscript" as const, + gridTranscript: { runId: "run", segmentId: "segment", speakerUserId: 200n, kind: 0 }, + }, + } + const direct = encodeMessage({ + message: { ...buildMessage({ text: "Transcript · Ben: Hello", countsAsUnread: false }), systemMessage: service }, + encodingForUserId: 100, + encodingForPeer: { peer }, + }) + const history = encodeFullMessage({ + message: buildFullMessage({ text: "Transcript · Ben: Hello", countsAsUnread: false, systemMessage: service }), + encodingForUserId: 100, + encodingForPeer: { peer }, + }) + for (const result of [direct, history]) { + expect(result.countsAsUnread).toBe(false) + expect(result.sendMode).toBe(MessageSendMode.MODE_SILENT) + expect(result.mentioned).toBe(false) + expect(result.serviceMessage?.event).toEqual(service.event) + } + }) + it("encodes thread backlink service metadata on full messages", () => { const result = encodeFullMessage({ message: buildFullMessage({ diff --git a/server/src/realtime/encoders/encodeMessage.ts b/server/src/realtime/encoders/encodeMessage.ts index 694ff98c6..9958fbc50 100644 --- a/server/src/realtime/encoders/encodeMessage.ts +++ b/server/src/realtime/encoders/encodeMessage.ts @@ -65,6 +65,14 @@ function encodeServiceMessage(systemMessage: SystemMessage | null | undefined): }, } + case "gridTranscript": + return { + event: { + oneofKind: "gridTranscript", + gridTranscript: event.gridTranscript, + }, + } + case undefined: return undefined } @@ -225,17 +233,20 @@ export const encodeMessage = ({ date: encodeDateStrict(message.date), editDate: message.editDate ? encodeDateStrict(message.editDate) : undefined, rev: BigInt(message.rev ?? 0), - mentioned: entities ? isUserMentioned(entities, encodingForUserId, mentionedUserIds) : false, + mentioned: message.systemMessage?.event.oneofKind === "gridTranscript" + ? false + : entities ? isUserMentioned(entities, encodingForUserId, mentionedUserIds) : false, replyToMsgId: message.replyToMsgId ? BigInt(message.replyToMsgId) : undefined, media: media, isSticker: message.isSticker || undefined, hasLink: hasLink, entities: entities, - sendMode: sendMode ?? undefined, + sendMode: message.countsAsUnread === false ? MessageSendMode.MODE_SILENT : sendMode ?? undefined, + countsAsUnread: message.countsAsUnread === false ? false : undefined, fwdFrom: fwdFrom, replies, subthread, - actions, + actions: message.systemMessage?.event.oneofKind === "gridTranscript" ? undefined : actions, serviceMessage: encodeServiceMessage(message.systemMessage), blockContent: message.blockContent ?? undefined, agentSession: message.agentSession, @@ -368,7 +379,9 @@ export const encodeFullMessage = ({ date: encodeDateStrict(message.date), editDate: message.editDate ? encodeDateStrict(message.editDate) : undefined, rev: BigInt(message.rev ?? 0), - mentioned: message.entities ? isUserMentioned(message.entities, encodingForUserId) : false, + mentioned: message.systemMessage?.event.oneofKind === "gridTranscript" + ? false + : message.entities ? isUserMentioned(message.entities, encodingForUserId) : false, replyToMsgId: message.replyToMsgId ? BigInt(message.replyToMsgId) : undefined, media: media, isSticker: message.isSticker ?? false, @@ -382,10 +395,12 @@ export const encodeFullMessage = ({ } : undefined, entities: message.entities ?? undefined, + sendMode: message.countsAsUnread === false ? MessageSendMode.MODE_SILENT : undefined, + countsAsUnread: message.countsAsUnread === false ? false : undefined, fwdFrom: fwdFrom, replies, subthread, - actions: message.actions ?? undefined, + actions: message.systemMessage?.event.oneofKind === "gridTranscript" ? undefined : message.actions ?? undefined, serviceMessage: encodeServiceMessage(message.systemMessage), blockContent: blockContent ?? undefined, agentSession: message.agentSession, diff --git a/server/src/realtime/handlers/_rpc.ts b/server/src/realtime/handlers/_rpc.ts index b76fa5fc9..34741bac8 100644 --- a/server/src/realtime/handlers/_rpc.ts +++ b/server/src/realtime/handlers/_rpc.ts @@ -105,6 +105,9 @@ import { import { setSpacePhotoHandler } from "@in/server/realtime/handlers/space.profile" import { getSpaceSettingsHandler, toggleSpaceGridHandler } from "@in/server/realtime/handlers/space.settings" import { + openGridThreadHandler, + setGridTranscriptionHandler, + listGridTranscriptsHandler, createGridRoomHandler, deleteGridRoomHandler, getGridHandler, @@ -467,6 +470,24 @@ export const handleRpcCall = async (call: RpcCall, handlerContext: HandlerContex return { oneofKind: "toggleSpaceGrid", toggleSpaceGrid: result } } + case Method.OPEN_GRID_THREAD: { + if (call.input.oneofKind !== "openGridThread") throw RealtimeRpcError.BadRequest() + const result = await openGridThreadHandler(call.input.openGridThread, handlerContext) + return { oneofKind: "openGridThread", openGridThread: result } + } + + case Method.SET_GRID_TRANSCRIPTION: { + if (call.input.oneofKind !== "setGridTranscription") throw RealtimeRpcError.BadRequest() + const result = await setGridTranscriptionHandler(call.input.setGridTranscription, handlerContext) + return { oneofKind: "setGridTranscription", setGridTranscription: result } + } + + case Method.LIST_GRID_TRANSCRIPTS: { + if (call.input.oneofKind !== "listGridTranscripts") throw RealtimeRpcError.BadRequest() + const result = await listGridTranscriptsHandler(call.input.listGridTranscripts, handlerContext) + return { oneofKind: "listGridTranscripts", listGridTranscripts: result } + } + case Method.GET_GRID: { if (call.input.oneofKind !== "getGrid") throw RealtimeRpcError.BadRequest() const result = await getGridHandler(call.input.getGrid, handlerContext) diff --git a/server/src/realtime/handlers/grid.ts b/server/src/realtime/handlers/grid.ts index bff21114d..8cc2aa051 100644 --- a/server/src/realtime/handlers/grid.ts +++ b/server/src/realtime/handlers/grid.ts @@ -1,4 +1,10 @@ import type { + OpenGridThreadInput, + OpenGridThreadResult, + SetGridTranscriptionInput, + SetGridTranscriptionResult, + ListGridTranscriptsInput, + ListGridTranscriptsResult, CreateGridRoomInput, CreateGridRoomResult, DeleteGridRoomInput, @@ -76,3 +82,12 @@ export const setGridAvatarMicrophoneEnabledHandler = ( context: HandlerContext, ): Promise => Functions.grid.setAvatarMicrophoneEnabled(input, functionContext(context)) + +export const openGridThreadHandler = (input: OpenGridThreadInput, context: HandlerContext): Promise => + Functions.grid.openThread(input, functionContext(context)) + +export const setGridTranscriptionHandler = (input: SetGridTranscriptionInput, context: HandlerContext): Promise => + Functions.grid.setTranscription(input, functionContext(context)) + +export const listGridTranscriptsHandler = (input: ListGridTranscriptsInput, context: HandlerContext): Promise => + Functions.grid.listTranscripts(input, functionContext(context)) diff --git a/server/src/utils/httpPrivacy.test.ts b/server/src/utils/httpPrivacy.test.ts index d926b5a3a..610dc890f 100644 --- a/server/src/utils/httpPrivacy.test.ts +++ b/server/src/utils/httpPrivacy.test.ts @@ -14,6 +14,7 @@ test("preserves ordinary diagnostic paths and excludes public Bot documentation } expect(requiresPrivateResponse("/health")).toBeFalse() expect(requiresPrivateResponse("/bot-api-reference/json")).toBeFalse() + expect(requiresPrivateResponse("/_internal/grid-transcription/claim")).toBeTrue() }) test("legacy HTTP setup adds no-store without changing GET dispatch", async () => { diff --git a/server/src/utils/httpPrivacy.ts b/server/src/utils/httpPrivacy.ts index 19457da3c..31821463c 100644 --- a/server/src/utils/httpPrivacy.ts +++ b/server/src/utils/httpPrivacy.ts @@ -5,5 +5,6 @@ export const redactCredentialPath = (path: string): string => path /** These APIs can return credentials or private content, including on GET/error paths. */ export const requiresPrivateResponse = (path: string): boolean => + /^\/_internal\/grid-transcription(?:\/|$)/i.test(path) || /^\/v1(?:\/|$)/i.test(path) || (/^\/bot(?:[^/]*)(?:\/|$)/i.test(path) && !/^\/bot-api-reference(?:\/|$)/i.test(path)) From afc3cb95039c9b6918d69779c16d039a4baaac7f Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Mon, 5 Oct 2026 06:53:55 +0330 Subject: [PATCH 03/14] grid: add native transcription worker and containment --- bun.lock | 133 +++++++- server/packages/grid-transcription/Dockerfile | 24 ++ server/packages/grid-transcription/README.md | 65 ++++ .../packages/grid-transcription/package.json | 35 +++ .../grid-transcription/src/api.test.ts | 179 +++++++++++ server/packages/grid-transcription/src/api.ts | 213 +++++++++++++ .../grid-transcription/src/authority.test.ts | 70 +++++ .../grid-transcription/src/authority.ts | 94 ++++++ .../grid-transcription/src/copy-model.ts | 4 + .../grid-transcription/src/daemon.test.ts | 162 ++++++++++ .../packages/grid-transcription/src/daemon.ts | 231 ++++++++++++++ .../packages/grid-transcription/src/index.ts | 4 + .../grid-transcription/src/pcm.test.ts | 38 +++ server/packages/grid-transcription/src/pcm.ts | 74 +++++ .../grid-transcription/src/protocol.ts | 34 +++ .../src/providers/openai.test.ts | 203 +++++++++++++ .../src/providers/openai.ts | 196 ++++++++++++ .../src/providers/soniox.test.ts | 142 +++++++++ .../src/providers/soniox.ts | 133 ++++++++ .../grid-transcription/src/qualify.ts | 74 +++++ .../grid-transcription/src/room-child.ts | 61 ++++ .../grid-transcription/src/room-worker.ts | 225 ++++++++++++++ .../grid-transcription/src/segmenter.test.ts | 105 +++++++ .../grid-transcription/src/segmenter.ts | 73 +++++ .../src/silero-model-license.txt | 242 +++++++++++++++ .../packages/grid-transcription/src/silero.ts | 101 +++++++ .../grid-transcription/src/silero_vad.onnx | Bin 0 -> 2327524 bytes .../grid-transcription/src/socket.test.ts | 182 +++++++++++ .../packages/grid-transcription/src/socket.ts | 193 ++++++++++++ .../grid-transcription/src/supervisor.test.ts | 275 +++++++++++++++++ .../grid-transcription/src/supervisor.ts | 197 ++++++++++++ .../src/track-worker.test.ts | 176 +++++++++++ .../grid-transcription/src/track-worker.ts | 267 ++++++++++++++++ .../grid-transcription/src/watchdog.ts | 284 ++++++++++++++++++ .../packages/grid-transcription/tsconfig.json | 9 + 35 files changed, 4497 insertions(+), 1 deletion(-) create mode 100644 server/packages/grid-transcription/Dockerfile create mode 100644 server/packages/grid-transcription/README.md create mode 100644 server/packages/grid-transcription/package.json create mode 100644 server/packages/grid-transcription/src/api.test.ts create mode 100644 server/packages/grid-transcription/src/api.ts create mode 100644 server/packages/grid-transcription/src/authority.test.ts create mode 100644 server/packages/grid-transcription/src/authority.ts create mode 100644 server/packages/grid-transcription/src/copy-model.ts create mode 100644 server/packages/grid-transcription/src/daemon.test.ts create mode 100644 server/packages/grid-transcription/src/daemon.ts create mode 100644 server/packages/grid-transcription/src/index.ts create mode 100644 server/packages/grid-transcription/src/pcm.test.ts create mode 100644 server/packages/grid-transcription/src/pcm.ts create mode 100644 server/packages/grid-transcription/src/protocol.ts create mode 100644 server/packages/grid-transcription/src/providers/openai.test.ts create mode 100644 server/packages/grid-transcription/src/providers/openai.ts create mode 100644 server/packages/grid-transcription/src/providers/soniox.test.ts create mode 100644 server/packages/grid-transcription/src/providers/soniox.ts create mode 100644 server/packages/grid-transcription/src/qualify.ts create mode 100644 server/packages/grid-transcription/src/room-child.ts create mode 100644 server/packages/grid-transcription/src/room-worker.ts create mode 100644 server/packages/grid-transcription/src/segmenter.test.ts create mode 100644 server/packages/grid-transcription/src/segmenter.ts create mode 100644 server/packages/grid-transcription/src/silero-model-license.txt create mode 100644 server/packages/grid-transcription/src/silero.ts create mode 100644 server/packages/grid-transcription/src/silero_vad.onnx create mode 100644 server/packages/grid-transcription/src/socket.test.ts create mode 100644 server/packages/grid-transcription/src/socket.ts create mode 100644 server/packages/grid-transcription/src/supervisor.test.ts create mode 100644 server/packages/grid-transcription/src/supervisor.ts create mode 100644 server/packages/grid-transcription/src/track-worker.test.ts create mode 100644 server/packages/grid-transcription/src/track-worker.ts create mode 100644 server/packages/grid-transcription/src/watchdog.ts create mode 100644 server/packages/grid-transcription/tsconfig.json diff --git a/bun.lock b/bun.lock index 11b5ef94b..62626049c 100644 --- a/bun.lock +++ b/bun.lock @@ -393,6 +393,21 @@ "typescript": "catalog:", }, }, + "server/packages/grid-transcription": { + "name": "@inline-chat/grid-transcription", + "version": "0.0.1", + "dependencies": { + "@livekit/rtc-node": "1.1.0", + "onnxruntime-node": "1.24.3", + "ws": "8.21.3", + }, + "devDependencies": { + "@types/node": "catalog:", + "@types/ws": "8.18.1", + "oxlint": "catalog:", + "typescript": "catalog:", + }, + }, "server/packages/url-preview": { "name": "@inline-chat/url-preview", "version": "0.0.1", @@ -583,6 +598,8 @@ "@clack/prompts": ["@clack/prompts@1.7.0", "", { "dependencies": { "@clack/core": "1.4.3", "fast-string-width": "^3.0.2", "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A=="], + "@datastructures-js/deque": ["@datastructures-js/deque@1.0.8", "", {}, "sha512-PSBhJ2/SmeRPRHuBv7i/fHWIdSC3JTyq56qb+Rq0wjOagi0/fdV5/B/3Md5zFZus/W6OkSPMaxMKKMNMrSmubg=="], + "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="], "@dual-bundle/import-meta-resolve": ["@dual-bundle/import-meta-resolve@4.2.1", "", {}, "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg=="], @@ -755,6 +772,8 @@ "@inline-chat/email-templates": ["@inline-chat/email-templates@workspace:server/packages/email-templates"], + "@inline-chat/grid-transcription": ["@inline-chat/grid-transcription@workspace:server/packages/grid-transcription"], + "@inline-chat/hermes-agent-adapter": ["@inline-chat/hermes-agent-adapter@workspace:plugins/hermes-agent"], "@inline-chat/landing": ["@inline-chat/landing@workspace:landing"], @@ -835,8 +854,26 @@ "@linear/sdk": ["@linear/sdk@38.0.0", "", { "dependencies": { "@graphql-typed-document-node/core": "^3.1.0", "graphql": "^15.4.0", "isomorphic-unfetch": "^3.1.0" } }, "sha512-1Z1F/rGjQqXlvk9Y0optAw3mq0OAvY+R5ahPCE3zRnZmALRnQFsmlX7A/oVqIq2bAH/GHKDg447+3CyirqURrA=="], + "@livekit/mutex": ["@livekit/mutex@1.1.1", "", {}, "sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw=="], + "@livekit/protocol": ["@livekit/protocol@1.49.0", "", { "dependencies": { "@bufbuild/protobuf": "^1.10.0" } }, "sha512-TCmihwjHBWAUdNrqvmrN/K59r7vDnPIy3OEaE3p0g4FvdcQTiLynmFC3igR25Q2fnifahhAJjnnfSCtWUK7kYg=="], + "@livekit/rtc-ffi-bindings": ["@livekit/rtc-ffi-bindings@0.12.73", "", { "dependencies": { "@bufbuild/protobuf": "^1.10.1" }, "optionalDependencies": { "@livekit/rtc-ffi-bindings-darwin-arm64": "0.12.73", "@livekit/rtc-ffi-bindings-darwin-x64": "0.12.73", "@livekit/rtc-ffi-bindings-linux-arm64-gnu": "0.12.73", "@livekit/rtc-ffi-bindings-linux-x64-gnu": "0.12.73", "@livekit/rtc-ffi-bindings-win32-x64-msvc": "0.12.73" } }, "sha512-ZxkCQlsfTG7Eqi6KBXulEWJBUxeRVRYqCc795CsmKsGfqmBX5gE5CnTiynN8fFdzSGwApaVogeGSB6JszCf/Xw=="], + + "@livekit/rtc-ffi-bindings-darwin-arm64": ["@livekit/rtc-ffi-bindings-darwin-arm64@0.12.73", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gIGftVyO1Jl266AZPRzoL6dxck969dmIiaut3+D1aCZRkDb/gLz98BEGRfOSU3X0LS4QBkmiaYWsjN9ot/zmwg=="], + + "@livekit/rtc-ffi-bindings-darwin-x64": ["@livekit/rtc-ffi-bindings-darwin-x64@0.12.73", "", { "os": "darwin", "cpu": "x64" }, "sha512-esPwYCbUnDD/KxBo0YU8awTFglRG7SvOT/sfiP19KChS5SH8TgXc7JwfSvnjRx1PndUqHFzQAbQDcYaeL9PFTg=="], + + "@livekit/rtc-ffi-bindings-linux-arm64-gnu": ["@livekit/rtc-ffi-bindings-linux-arm64-gnu@0.12.73", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YBatXjuaumnaNVqRC63flX5XZTvHcQWRsABw6e2yp+Hlxl3ocjVVyk5PrAsRzCdeyxbHLduqV9ctqGbpTA27Q=="], + + "@livekit/rtc-ffi-bindings-linux-x64-gnu": ["@livekit/rtc-ffi-bindings-linux-x64-gnu@0.12.73", "", { "os": "linux", "cpu": "x64" }, "sha512-BrP3IyNkcjldeO+SbnF0hX2mipst8aHiBIiGJcQCd8HEjsaabfkYk4Y5uoNsTVMrUSyrNrJte2itAhuxP9FCSA=="], + + "@livekit/rtc-ffi-bindings-win32-x64-msvc": ["@livekit/rtc-ffi-bindings-win32-x64-msvc@0.12.73", "", { "os": "win32", "cpu": "x64" }, "sha512-2RnKXMTnNfvjJ3kbuhE1jqFNAQNmqSrxy3GlEnByY61tbtY14QPFn8X/0lh+Mjst8pZdfxkXvtbRyPt5KYHvMA=="], + + "@livekit/rtc-node": ["@livekit/rtc-node@1.1.0", "", { "dependencies": { "@datastructures-js/deque": "1.0.8", "@livekit/mutex": "^1.0.0", "@livekit/rtc-ffi-bindings": "0.12.73", "@livekit/typed-emitter": "^3.0.0", "pino": "^9.0.0", "pino-pretty": "^13.0.0" } }, "sha512-KFNvPz7qQSJBTttDukanhMJ+bCNxj6O3A3fXl6GJvOEamJJk7GqKCWgAdLW3JLjuT1pPQDyQPJJnYdR25N+MXA=="], + + "@livekit/typed-emitter": ["@livekit/typed-emitter@3.0.0", "", {}, "sha512-9bl0k4MgBPZu3Qu3R3xy12rmbW17e3bE9yf4YY85gJIQ3ezLEj/uzpKHWBsLaDoL5Mozz8QCgggwIBudYQWeQg=="], + "@lydell/node-pty": ["@lydell/node-pty@1.2.0-beta.15", "", { "optionalDependencies": { "@lydell/node-pty-darwin-arm64": "1.2.0-beta.15", "@lydell/node-pty-darwin-x64": "1.2.0-beta.15", "@lydell/node-pty-linux-arm64": "1.2.0-beta.15", "@lydell/node-pty-linux-x64": "1.2.0-beta.15", "@lydell/node-pty-win32-arm64": "1.2.0-beta.15", "@lydell/node-pty-win32-x64": "1.2.0-beta.15" } }, "sha512-Br8wBxzbxFwdWgk9uQ+rdzE0xfoxOK4QuGH54swhRwc5IxP6H9Y1/bcyazRGvNUs6XkB5qNVkezuKSRxUwZe7A=="], "@lydell/node-pty-darwin-arm64": ["@lydell/node-pty-darwin-arm64@1.2.0-beta.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6TSBbzdcLiNTHl1mTuzflqXrkmcC36USVGvERoDgvHk2ItEDaMaFZuAJ1CqPmwYj0DyhCS16TVS8OGK9xZnjyQ=="], @@ -1021,6 +1058,8 @@ "@oxlint/binding-win32-x64-msvc": ["@oxlint/binding-win32-x64-msvc@1.74.0", "", { "os": "win32", "cpu": "x64" }, "sha512-VTC9IYTIMrVUk/i6Ms1ohzzDKZFkWn0KU2OBbPBzgmVZ2V30165T/zK4LztTr0Xgp9fZ1qQZ1rsZAu/rEmySlA=="], + "@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@playwright/test": ["@playwright/test@1.63.0", "", { "dependencies": { "playwright": "1.63.0" }, "bin": { "playwright": "cli.js" } }, "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ=="], @@ -1551,6 +1590,8 @@ "acorn-import-attributes": ["acorn-import-attributes@1.9.5", "", { "peerDependencies": { "acorn": "^8" } }, "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ=="], + "adm-zip": ["adm-zip@0.5.18", "", {}, "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], @@ -1581,6 +1622,8 @@ "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="], + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], + "autoprefixer": ["autoprefixer@10.4.20", "", { "dependencies": { "browserslist": "^4.23.3", "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g=="], "babel-dead-code-elimination": ["babel-dead-code-elimination@1.0.12", "", { "dependencies": { "@babel/core": "^7.23.7", "@babel/parser": "^7.23.6", "@babel/traverse": "^7.23.7", "@babel/types": "^7.23.6" } }, "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig=="], @@ -1601,6 +1644,8 @@ "boolbase": ["boolbase@2.0.0", "", {}, "sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA=="], + "boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="], + "bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="], "bowser": ["bowser@2.11.0", "", {}, "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="], @@ -1657,6 +1702,8 @@ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], @@ -1699,6 +1746,8 @@ "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + "dateformat": ["dateformat@4.6.3", "", {}, "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA=="], + "db0": ["db0@0.3.4", "", { "peerDependencies": { "@electric-sql/pglite": "*", "@libsql/client": "*", "better-sqlite3": "*", "drizzle-orm": "*", "mysql2": "*", "sqlite3": "*" }, "optionalPeers": ["@electric-sql/pglite", "@libsql/client", "better-sqlite3", "drizzle-orm", "mysql2", "sqlite3"] }, "sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw=="], "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="], @@ -1709,6 +1758,10 @@ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], @@ -1717,6 +1770,8 @@ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], "diff": ["diff@9.0.0", "", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="], @@ -1759,6 +1814,8 @@ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -1777,6 +1834,8 @@ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + "es6-error": ["es6-error@4.1.1", "", {}, "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg=="], + "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="], @@ -1785,7 +1844,7 @@ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], - "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], @@ -1821,10 +1880,14 @@ "fast-check": ["fast-check@4.9.0", "", { "dependencies": { "pure-rand": "^8.0.0" } }, "sha512-7ms6T7SybUev/PQITciI0yLM2pOSFy5zpG8Ty7tQofcVaQUvrMXp6CBwqF6fThLCLOrfBtuHAtwq6Yu4XPCllg=="], + "fast-copy": ["fast-copy@4.1.1", "", {}, "sha512-A4QTJmuiztpGtr6AMeJts9R4hbj2ZBUwtOaKrG6rw2y7t6+IaJKjz5M3XDs8BUznxDH43FVc6A0y/gWlMl4UtA=="], + "fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + "fast-safe-stringify": ["fast-safe-stringify@2.1.1", "", {}, "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="], + "fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="], "fast-string-truncated-width": ["fast-string-truncated-width@3.0.3", "", {}, "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g=="], @@ -1897,6 +1960,10 @@ "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + "global-agent": ["global-agent@3.0.0", "", { "dependencies": { "boolean": "^3.0.1", "es6-error": "^4.1.1", "matcher": "^3.0.0", "roarr": "^2.15.3", "semver": "^7.3.2", "serialize-error": "^7.0.1" } }, "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q=="], + + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], + "globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="], "google-auth-library": ["google-auth-library@10.6.2", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw=="], @@ -1919,6 +1986,8 @@ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], @@ -1929,6 +1998,8 @@ "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + "help-me": ["help-me@5.0.0", "", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="], + "highlight.js": ["highlight.js@11.12.0", "", {}, "sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg=="], "hono": ["hono@4.12.25", "", {}, "sha512-2NFaIyNVgJmBs/ecmtGzlmluTFs5cHEWGTdu0t1HBwYzoGXOL5nUQBRMXsXWla5i4KkG//QMzVP88m1+I3fdAQ=="], @@ -2021,6 +2092,8 @@ "jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], "js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], @@ -2035,6 +2108,8 @@ "json-schema-typed": ["json-schema-typed@8.0.2", "", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + "json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="], + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], "jsonwebtoken": ["jsonwebtoken@9.0.3", "", { "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g=="], @@ -2119,6 +2194,8 @@ "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + "matcher": ["matcher@3.0.0", "", { "dependencies": { "escape-string-regexp": "^4.0.0" } }, "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], @@ -2279,6 +2356,8 @@ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], "ocache": ["ocache@0.1.5", "", { "dependencies": { "ohash": "^2.0.11" } }, "sha512-kNNnkkVQup/QDvmTz8Q84wc2ntiyoVHDxa6eHWKt5qdGAmFRBIxy83rxgCYEjW0x06UJ9E3P6VgM2yY4rOBH4w=="], @@ -2287,10 +2366,16 @@ "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], + "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "onnxruntime-common": ["onnxruntime-common@1.24.3", "", {}, "sha512-GeuPZO6U/LBJXvwdaqHbuUmoXiEdeCjWi/EG7Y1HNnDwJYuk6WUbNXpF6luSUY8yASul3cmUlLGrCCL1ZgVXqA=="], + + "onnxruntime-node": ["onnxruntime-node@1.24.3", "", { "dependencies": { "adm-zip": "^0.5.16", "global-agent": "^3.0.0", "onnxruntime-common": "1.24.3" }, "os": [ "linux", "win32", "darwin", ] }, "sha512-JH7+czbc8ALA819vlTgcV+Q214/+VjGeBHDjX81+ZCD0PCVCIFGFNtT0V4sXG/1JXypKPgScQcB3ij/hk3YnTg=="], + "openai": ["openai@7.1.0", "", { "peerDependencies": { "@aws-sdk/credential-provider-node": ">=3.972.0 <4", "@smithy/hash-node": ">=4.3.0 <5", "@smithy/signature-v4": ">=5.4.0 <6", "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["@aws-sdk/credential-provider-node", "@smithy/hash-node", "@smithy/signature-v4", "ws", "zod"] }, "sha512-7xWJ9iO5z5u1dnIUGwoUmZHkSyrUYXX2cUxo2E/26iKFrSC8IdEak7z94d5UntU7z+S/Cid33hYymwMSab2fZQ=="], "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], @@ -2347,6 +2432,14 @@ "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "pino": ["pino@9.14.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w=="], + + "pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="], + + "pino-pretty": ["pino-pretty@13.2.0", "", { "dependencies": { "colorette": "^2.0.7", "dateformat": "^4.6.3", "fast-copy": "^4.0.0", "fast-safe-stringify": "^2.1.1", "help-me": "^5.0.0", "joycon": "^3.1.1", "minimist": "^1.2.6", "on-exit-leak-free": "^2.1.2", "pino-abstract-transport": "^3.0.0", "pump": "^3.0.3", "secure-json-parse": "^4.1.0", "sonic-boom": "^5.0.0", "strip-json-comments": "^5.0.3" }, "bin": { "pino-pretty": "bin.js" } }, "sha512-jjghD1ML+Di/WxjJDz6j6Y4FT7hIXOlb8h27AyJ+ohtAXJXH4PS/xkv7si+RCCIW59HMgZsMKTDgzdr0RhuRrg=="], + + "pino-std-serializers": ["pino-std-serializers@7.1.0", "", {}, "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw=="], + "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], "playwright": ["playwright@1.63.0", "", { "dependencies": { "playwright-core": "1.63.0" }, "bin": { "playwright": "cli.js" } }, "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg=="], @@ -2379,6 +2472,8 @@ "process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="], + "process-warning": ["process-warning@5.1.0", "", {}, "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw=="], + "promise-limit": ["promise-limit@2.7.0", "", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="], "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], @@ -2391,12 +2486,16 @@ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + "pure-rand": ["pure-rand@8.4.2", "", {}, "sha512-vvuOGgcuPJAirlHvuQw1TrOiw7ptaIXXmIbNuiNOY6lNGJJH49PQ1Kj4nd783nPdQhQdicgOjVI2yI/9BD6/Ng=="], "qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="], "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], + "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], + "quickjs-wasi": ["quickjs-wasi@3.6.0", "", {}, "sha512-/CNzMq42B8ThzUpzjLSF8K50ntVLV3RREyT8zr4wHaP2CZSHFhPkqg3E1GULmTerehf8Bo+UguQ+4LoOMCTC6A=="], "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], @@ -2417,6 +2516,8 @@ "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], + "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], + "remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], @@ -2447,6 +2548,8 @@ "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="], + "rolldown": ["rolldown@1.2.0", "", { "dependencies": { "@oxc-project/types": "=0.140.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.2.0", "@rolldown/binding-darwin-arm64": "1.2.0", "@rolldown/binding-darwin-x64": "1.2.0", "@rolldown/binding-freebsd-x64": "1.2.0", "@rolldown/binding-linux-arm-gnueabihf": "1.2.0", "@rolldown/binding-linux-arm64-gnu": "1.2.0", "@rolldown/binding-linux-arm64-musl": "1.2.0", "@rolldown/binding-linux-ppc64-gnu": "1.2.0", "@rolldown/binding-linux-s390x-gnu": "1.2.0", "@rolldown/binding-linux-x64-gnu": "1.2.0", "@rolldown/binding-linux-x64-musl": "1.2.0", "@rolldown/binding-openharmony-arm64": "1.2.0", "@rolldown/binding-wasm32-wasi": "1.2.0", "@rolldown/binding-win32-arm64-msvc": "1.2.0", "@rolldown/binding-win32-x64-msvc": "1.2.0" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-u7tgm5l4Yw1iTqUL4EcYOAt7fFvCgQMLeidrnD4GALlC6aOznCjezYajgxeyKw27u0Q5N7fwgCzjVyPIWzwuBA=="], "rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="], @@ -2457,16 +2560,24 @@ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + "secure-json-parse": ["secure-json-parse@4.1.0", "", {}, "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA=="], + "selderee": ["selderee@0.11.0", "", { "dependencies": { "parseley": "^0.12.0" } }, "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA=="], "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + "semver-compare": ["semver-compare@1.0.0", "", {}, "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow=="], + "send": ["send@1.2.1", "", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + "serialize-error": ["serialize-error@7.0.1", "", { "dependencies": { "type-fest": "^0.13.1" } }, "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw=="], + "seroval": ["seroval@1.5.6", "", {}, "sha512-rVQVWjjSvlINzaQPZH5JFqsqEsIWdTxY3iJZCnTL/5gQbXIRooVZKI60tVCkOVfzcRPejboxO2t0P89dg5mQaA=="], "seroval-plugins": ["seroval-plugins@1.5.6", "", { "peerDependencies": { "seroval": "^1.0" } }, "sha512-HXuLAX2pu/UByPpaeo/TaMfvMIi+1QqIoPJYCcAtU8QkVNwgR6MPlGuCQTErV1JwraaMbYaWVIBX7mppzGLATQ=="], @@ -2501,6 +2612,8 @@ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], + "sonic-boom": ["sonic-boom@4.2.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q=="], + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -2509,6 +2622,10 @@ "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + "split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + + "sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], + "sqlite-vec": ["sqlite-vec@0.1.9", "", { "optionalDependencies": { "sqlite-vec-darwin-arm64": "0.1.9", "sqlite-vec-darwin-x64": "0.1.9", "sqlite-vec-linux-arm64": "0.1.9", "sqlite-vec-linux-x64": "0.1.9", "sqlite-vec-windows-x64": "0.1.9" } }, "sha512-L7XJWRIBNvR9O5+vh1FQ+IGkh/3D2AzVksW5gdtk28m78Hy8skFD0pqReKH1Yp0/BUKRGcffgKvyO/EON5JXpA=="], "sqlite-vec-darwin-arm64": ["sqlite-vec-darwin-arm64@0.1.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jSsZpE42OfBkGL/ItyJTVCUwl6o6Ka3U5rc4j+UBDIQzC1ulSSKMEhQLthsOnF/MdAf1MuAkYhkdKmmcjaIZQg=="], @@ -2545,6 +2662,8 @@ "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], + "strnum": ["strnum@1.0.5", "", {}, "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA=="], "strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="], @@ -2563,6 +2682,8 @@ "tar": ["tar@7.5.22", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA=="], + "thread-stream": ["thread-stream@3.2.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw=="], + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], @@ -2595,6 +2716,8 @@ "tslog": ["tslog@4.11.0", "", {}, "sha512-gBe0FTKkRpOv3DenGipjQWQe073jNd1cOLj3nYfVM0/NP29sQzAIx6kfyfTTsQOiWMC+B+tSr289lNXtu7HCmQ=="], + "type-fest": ["type-fest@0.13.1", "", {}, "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg=="], + "type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], "typebox": ["typebox@1.3.18", "", {}, "sha512-/wYPoDqxWZSxV/XD8Eskzr3YluXC9CaWJOuUYMkj+lLVLkyeEIQKzHvMuS/IRc3OLTIBC32LAtHgXo/WFEOMHQ=="], @@ -2823,6 +2946,8 @@ "@livekit/protocol/@bufbuild/protobuf": ["@bufbuild/protobuf@1.10.1", "", {}, "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ=="], + "@livekit/rtc-ffi-bindings/@bufbuild/protobuf": ["@bufbuild/protobuf@1.10.1", "", {}, "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ=="], + "@mistralai/mistralai/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="], "@openclaw/ai/@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.120.0", "", { "dependencies": { "json-schema-to-ts": "^3.1.1", "standardwebhooks": "^1.0.0" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-ZlvmNFT/iIF6JD13rxbbMWD8nvGR0RaUp6yMQnoc+4Af0YjVVe/bIdW1XSQQsoxXAtg1NaT6Vak0LKFlJ4d37Q=="], @@ -3037,6 +3162,8 @@ "make-dir/semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + "minizlib/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "motion/framer-motion": ["framer-motion@12.23.22", "", { "dependencies": { "motion-dom": "^12.23.21", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA=="], @@ -3065,6 +3192,10 @@ "path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "pino-pretty/pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="], + + "pino-pretty/sonic-boom": ["sonic-boom@5.0.1", "", { "dependencies": { "atomic-sleep": "^1.0.0" } }, "sha512-I1E9tHuKe6SdS/Uab4bl2o1ZUBm2SCTDFF9eQ66ehyVcFIBixfecy9DlwXCtnRF8CUzPw7Q4Dn6JQE/8iVl/FQ=="], + "playwright/playwright-core": ["playwright-core@1.63.0", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg=="], "postcss/nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], diff --git a/server/packages/grid-transcription/Dockerfile b/server/packages/grid-transcription/Dockerfile new file mode 100644 index 000000000..b8732197b --- /dev/null +++ b/server/packages/grid-transcription/Dockerfile @@ -0,0 +1,24 @@ +# Build from the repository root. The worker is separate from the Bun API. +FROM oven/bun:1.4.0 AS build +WORKDIR /app +COPY package.json bun.lock bunfig.toml ./ +COPY cli/package.json ./cli/package.json +COPY server ./server +COPY landing ./landing +COPY packages ./packages +COPY plugins ./plugins +COPY scripts ./scripts +RUN bun --no-env-file scripts/docker/prune-workspace.ts @inline-chat/grid-transcription out + +FROM node:24-bookworm-slim AS runtime +WORKDIR /app +COPY --from=build /app/out/json/ ./ +# Install with Bun using the exact workspace lock, but execute native RTC on Node. +COPY --from=build /usr/local/bin/bun /usr/local/bin/bun +RUN bun --no-env-file install --frozen-lockfile --filter @inline-chat/grid-transcription --linker hoisted +COPY --from=build /app/out/full/ ./ +RUN cd server/packages/grid-transcription && bun --no-env-file run build +ENV NODE_ENV=production +USER node +STOPSIGNAL SIGTERM +CMD ["node", "server/packages/grid-transcription/dist/daemon.js"] diff --git a/server/packages/grid-transcription/README.md b/server/packages/grid-transcription/README.md new file mode 100644 index 000000000..fa0afe0e3 --- /dev/null +++ b/server/packages/grid-transcription/README.md @@ -0,0 +1,65 @@ +# Grid transcription worker + +A separate Node process captures authorized LiveKit microphone tracks, segments each speaker's audio with Silero VAD, and posts finalized turns through the API's ordinary Grid transcription thread. The Bun API does not import the native RTC runtime. The feature remains gated; this package does not activate capture or change infrastructure by itself. + +The internal `meeting` preset selects Soniox `stt-rt-v5` at signed16LE mono 16 kHz. `standard` selects OpenAI `gpt-transcribe` at signed16LE mono 24 kHz. Languages are automatic; translation and provider diarization are disabled. LiveKit microphone identity/SID joined with the API membership manifest supplies the speaker. There is no public model selector or provisional transcript feed. The meeting default is a product-fit choice pending real account/audio qualification, not a measured accuracy claim. + +## Runtime boundaries + +- `daemon.ts` advertises configured-provider availability, claims one room with a boot-unique worker ID, and renews its 15-second authority every second from monotonic request start. Availability heartbeats remain every five seconds. Fixed capture expiry is capped at two hours. A delayed response cannot grant a fresh lease or resurrect an expired owner. In-flight and delivery-unknown claims are reconciled on shutdown using the same boot identity; stop-only recovery cannot allocate RTC. +- `supervisor.ts` and the independent `watchdog.ts` own one native room child. Parent and child each send monotonic heartbeats. A stalled/disconnected parent, stalled child, expired lease, or failed startup closes admissions and escalates TERM to KILL. A child `stopped` message does not prove transport containment: the watchdog must observe actual OS exit, and the API must acknowledge that receipt before another claim. Known exit receipts retry on the existing heartbeat cadence after API recovery. Unknown native exit keeps the slot fenced. +- `room-worker.ts` registers callbacks before connecting with `autoSubscribe:false`, then sweeps existing publications. Only exact current API membership, participant object, microphone source, audio kind, publication object, track object and SID are eligible. Every queue write and captured PCM send rechecks them. Muted/departed/replaced tracks stop intake immediately. Retiring tracks remain bounded; a ninth owned microphone ends capture visibly rather than silently dropping a speaker. Native readers are cancelled and their locks released. +- `track-worker.ts` owns each speaker's serial VAD/admission/commit order. Admission happens before that turn's PCM reaches a provider. Native readers copy promptly into a two-second queue; they never await inference or network work. Queue overflow, seven-second stuck processing, provider failure, or unresolved-final timeout ends capture. The owner discards late admission/connect/inference results after stopping. +- `silero.ts` verifies the vendored official model SHA256 before single-thread CPU inference. Each track has its own recurrent state; 24 kHz is resampled to 16 kHz for VAD. `segmenter.ts` retains 300 ms preroll, ends after 550 ms silence (or 200 ms silence after six seconds), and bounds continuous speech to ten seconds at the production 20 ms frame size. A finite 200 ms zero tail precedes manual provider finalization. This boundary quality still needs real multilingual meeting qualification. +- `socket.ts` bounds provider/event buffers, verifies OpenAI's effective configuration before audio, and emits only finalized local turns. Empty completions still retire their admissions; the API suppresses empty message rows. There are at most two pending turns, a five-second final deadline, and 32 KiB UTF8 text per turn. Sockets rotate at drained boundaries before nine minutes or 900 turns; a rollover with undrained work interrupts capture rather than guessing/replaying. Decoders retain at most 1,024 identifiers per socket and fail closed on overflow. + +Manual Stop closes new API admission immediately. The daemon observes Stop on its next active-run renewal (normally within one second), then its child synchronously closes PCM intake. An already-admitted turn can include audio collected during that notification delay; this is not a guarantee of pre-button audio only. Completion is accepted only within the API's fixed five-second deadline from the Stop request, and the UI stays Stopping until actual native exit. Ordinary voice leave may finish an already-admitted turn with its immutable speaker attribution. Destructive Space/history/destination invalidation or lost run authority discards immediately. Provider or native failure requires a fresh explicit Start; there is no automatic capture takeover, transcript fallback, or raw-audio persistence. + +## Build and environment + +Exact native dependencies are `@livekit/rtc-node` 1.1.0 (FFI 0.12.73) and `onnxruntime-node` 1.24.3. The model is copied into `dist` at build time with its license notice; no runtime model download is needed. The Dockerfile uses Node 24 Bookworm/glibc and the committed workspace lock. Linux ARM64 native packages exist; actual deployment-host import, resampling, microphone and shutdown behavior still require qualification. Alpine/musl is not supported by this plan. + +Set only these named values through the deployment secret/config consumer; the worker does not load environment files or print credentials: + +| Variable | Meaning | +| --- | --- | +| `GRID_TRANSCRIPTION_API_URL` | API HTTPS origin; loopback HTTP is allowed for controlled labs. URL path prefixes are rejected. | +| `GRID_TRANSCRIPTION_WORKER_SECRET` | Shared worker-control bearer secret, matching the API. | +| `GRID_TRANSCRIPTION_MODEL` | Internal `meeting` default or `standard`; must match API configuration. | +| `GRID_TRANSCRIPTION_WORKER_ID` | Optional 1–40 character boot-label (`a-z`, `A-Z`, digits, `_`, `-`); a fresh UUID is always appended. | +| `SONIOX_API_KEY` | Required to advertise readiness for `meeting`. | +| `OPENAI_API_KEY` | Required to advertise readiness for `standard`. | + +A missing selected key advertises `ready:false` and never claims work. Readiness validates runtime/model/key presence; it does not establish paid account access, provider authentication, quota or model quality. Provider keys remain in the worker, passed to its native child over IPC rather than command-line arguments or inherited environment. Child stdout/stderr are suppressed; daemon logs contain only coarse availability state. + +The worker calls `POST /_internal/grid-transcription/{heartbeat,claim,renew,admit,final,stopped}`. Heartbeat/claim use the shared secret; remaining calls use the run token. A normal claim supplies LiveKit credentials and the full membership manifest. A stop-only recovery claim supplies just run ID, epoch and token. Renewals fully replace the membership manifest and explicitly distinguish an allowed manual flush from destructive stopping. Admission returns the immutable segment ID; final persistence is idempotent for that ID. The API independently checks current Space, destination, speaker and run authority at persistence. + +```sh +bun --no-env-file run build +node dist/daemon.js +``` + +Use a separate bounded service on the existing Finland ARM64 host; keep the SFU and API process independent. Do not deploy or activate from this package's local checks. Give service shutdown enough time for native exit and the final bounded API acknowledgement; an unconfirmed exit or receipt during API outage must remain unavailable until reconciled. + +## Validation and qualification + +```sh +bun --no-env-file run typecheck +bun --no-env-file run test +bun --no-env-file run lint +bun --no-env-file run build +``` + +Tests use public synthetic wire fixtures and actual compiled Node loopback HTTP/WebSocket/process fixtures. `GRID_TRANSCRIPTION_TEST_NODE=/absolute/path/to/node` selects the Node executable for process tests. Node 22.23.3 macOS ARM64 has exercised real parent/child SIGSTOP, parent SIGKILL, TERM/KILL escalation, held exit acknowledgements, delayed/lost claims, and stopped-receipt API recovery. The pinned Silero model has performed compiled Node 16/24 kHz inference. A local source-built LiveKit 1.13.7 lab with two synthetic publishers has exercised existing/new microphone subscription, real RTC PCM, per-speaker VAD, admission and final delivery through loopback provider wire; this is not proof of the deployed SFU version or paid provider behavior. + +Still required before activation: intended Linux ARM64/glibc service and real SFU/token qualification; selected Soniox provider authentication, quota, final ordering/latency and multilingual/code-switching/overlap acceptance; real macOS button/Stop/late-join/history behavior; and load/billing measurement. Qualify the OpenAI alternative before configuring it for live capture. A two-provider benchmark is optional and is required only for a measured superiority claim. The bounded real-account attempt returned no result, so paid provider readiness remains unknown. The Grid iOS gate remains independently off pending physical-device acceptance. + +The compiled `qualify` command consumes an intentionally supplied signed16LE mono `.pcm` file at the selected provider's exact rate (100 ms–12 seconds), sends it at realtime pace and commits once. It is a single-provider boundary smoke, not the meeting/VAD comparison. Inject `GRID_TRANSCRIPTION_OPENAI_API_KEY` or `GRID_TRANSCRIPTION_SONIOX_API_KEY` through the approved secret consumer. These qualification-only names differ from daemon keys so no ordinary credential inheritance is assumed. + +```sh +node dist/qualify.js --help +node dist/qualify.js --model standard --pcm /absolute/path/short-24k-mono.pcm +node dist/qualify.js --model meeting --pcm /absolute/path/short-16k-mono.pcm --out /absolute/path/fresh-lab-result.json +``` + +Default stdout contains only model, duration, finalization latency and character count. Optional `--out` explicitly creates a local transcript artifact with mode 0600 and never overwrites a file. Use synthetic speech or supplied recordings from willing participants. Soniox's ordinary routing is US; EU requires an enabled regional project/key and a qualified endpoint change before deployment. diff --git a/server/packages/grid-transcription/package.json b/server/packages/grid-transcription/package.json new file mode 100644 index 000000000..55f89594b --- /dev/null +++ b/server/packages/grid-transcription/package.json @@ -0,0 +1,35 @@ +{ + "name": "@inline-chat/grid-transcription", + "version": "0.0.1", + "private": true, + "type": "module", + "engines": { + "node": ">=22" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "scripts": { + "build": "tsc -p tsconfig.json && node dist/copy-model.js", + "qualify": "node dist/qualify.js", + "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "bun --no-env-file test src", + "lint": "oxlint --config ../../../.oxlintrc.json --ignore-path ../../../.oxlintignore src", + "start": "node dist/daemon.js" + }, + "dependencies": { + "ws": "8.21.3", + "@livekit/rtc-node": "1.1.0", + "onnxruntime-node": "1.24.3" + }, + "devDependencies": { + "@types/node": "catalog:", + "@types/ws": "8.18.1", + "typescript": "catalog:", + "oxlint": "catalog:" + }, + "license": "AGPL-3.0-only" +} diff --git a/server/packages/grid-transcription/src/api.test.ts b/server/packages/grid-transcription/src/api.test.ts new file mode 100644 index 000000000..6c628d7db --- /dev/null +++ b/server/packages/grid-transcription/src/api.test.ts @@ -0,0 +1,179 @@ +import { describe, expect, test } from "bun:test" +import { execFile, execFileSync } from "node:child_process" +import { mkdtempSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { fileURLToPath, pathToFileURL } from "node:url" +import { claimedRun, claimResponse, renewedRun, RunControlClient, WorkerControlClient } from "./api.js" +import { MAX_TURN_TEXT_BYTES, TranscriptionError } from "./protocol.js" + +// Current claim wire shape comes from server/src/modules/grid/transcription/worker.ts: +// its claim response uses run.sourceRoomId and participant.userId as database numbers. +const claimFixture = { + runId: "4fef7f47-4d65-4b71-8efb-1eab3185ab05", claimEpoch: 1, roomId: 42, generation: 3, + runToken: "fixture-run-token", providerTarget: "inline-grid-room-42-generation-3", + livekit: { serverUrl: "wss://media.example.test", token: "fixture-livekit-token" }, + model: "meeting", leaseMs: 15_000, expiresAt: "2030-01-01T02:00:00.000Z", + participants: [{ identity: "inline-grid-user-7-member", userId: 7, membershipId: "member" }], +} +const renewalFixture = { + state: "stopping", allowFinalFlush: true, leaseExpiresAt: "2030-01-01T00:00:15.000Z", + participants: claimFixture.participants, spaceId: 9, +} +const segmentId = "78190417-5200-43ee-ae16-c891fd067d7e" + +function expectFailure(action: () => unknown, code: TranscriptionError["code"] = "protocol") { + try { action(); throw new Error("Expected safe failure") } + catch (error) { + expect(error).toBeInstanceOf(TranscriptionError) + if (!(error instanceof TranscriptionError)) throw error + expect(error.code).toBe(code) + expect(error.message).toBe(`Grid transcription ${code}`) + } +} + +describe("API wire parsing", () => { + test("lost claim recovery returns only stop authority and cannot supply microphone credentials", () => { + const stopped = claimResponse({ runId: claimFixture.runId, claimEpoch: 2, runToken: "fixture-recovery-token", stopImmediately: true }) + expect(stopped).toEqual({ runId: claimFixture.runId, claimEpoch: 2, runToken: "fixture-recovery-token", stopImmediately: true }) + expect("livekit" in stopped).toBe(false) + expectFailure(() => claimResponse({ runId: claimFixture.runId, claimEpoch: 0, runToken: "fixture-recovery-token", stopImmediately: true })) + }) + test("accepts the server's numeric room and participant IDs as exact positive decimal IDs", () => { + const claim = claimedRun(claimFixture) + expect(claim.roomId).toBe("42") + expect(claim.participants[0]?.userId).toBe("7") + expect(claim.livekit).toEqual(claimFixture.livekit) + expect(Object.isFrozen(claim)).toBe(true) + expect(Object.isFrozen(claim.participants[0])).toBe(true) + expect(claimedRun({ ...claimFixture, roomId: "42" }).roomId).toBe("42") + for (const roomId of [0, -1, 1.5, Number.MAX_SAFE_INTEGER + 1, "0", "01", "-2", "1e3", ""]) { + expectFailure(() => claimedRun({ ...claimFixture, roomId })) + } + }) + + test("rejects invalid media URLs, ID bounds and leases using safe errors", () => { + for (const serverUrl of ["private malformed URL", "https://media.example.test"]) { + expectFailure(() => claimedRun({ ...claimFixture, livekit: { ...claimFixture.livekit, serverUrl } })) + } + for (const leaseMs of [0, 15_001, Infinity]) expectFailure(() => claimedRun({ ...claimFixture, leaseMs })) + expectFailure(() => claimedRun({ ...claimFixture, participants: [ + ...claimFixture.participants, ...claimFixture.participants, + ] })) + expectFailure(() => claimedRun({ ...claimFixture, participants: [{ ...claimFixture.participants[0], identity: "a".repeat(129) }] })) + }) + + test("defaults an omitted final-flush grant to false and accepts only explicit boolean grants", () => { + expect(renewedRun(renewalFixture).allowFinalFlush).toBe(true) + expect(renewedRun({ ...renewalFixture, allowFinalFlush: undefined }).allowFinalFlush).toBe(false) + expect(renewedRun({ ...renewalFixture, allowFinalFlush: false }).allowFinalFlush).toBe(false) + for (const allowFinalFlush of [1, "true", null]) expectFailure(() => renewedRun({ ...renewalFixture, allowFinalFlush })) + expectFailure(() => renewedRun({ ...renewalFixture, state: "interrupted" })) + }) + + test("URL constructors reject malformed URLs, credentials and unsupported path prefixes safely", () => { + for (const baseUrl of ["private malformed URL", "http://example.test", "https://user:private@example.test", + "https://example.test/prefix", "https://example.test/?secret=private", "https://example.test/#private"]) { + expectFailure(() => new WorkerControlClient(baseUrl, "fixture-secret", "worker")) + expectFailure(() => new RunControlClient(baseUrl, "fixture-token")) + } + expectFailure(() => new WorkerControlClient("https://example.test", "fixture-secret", "w".repeat(81))) + }) + + test("rejects over-limit admission IDs and UTF8 final text before making requests", async () => { + const client = new RunControlClient("http://127.0.0.1:1", "fixture-token") + for (const values of [["a".repeat(129), "track", "turn"], ["identity", "a".repeat(129), "turn"], + ["identity", "track", "a".repeat(129)]]) { + await expect(client.admit(values[0]!, values[1]!, values[2]!)).rejects.toThrow("Grid transcription protocol") + } + await expect(client.final(segmentId, "a".repeat(MAX_TURN_TEXT_BYTES + 1))).rejects.toThrow("Grid transcription overflow") + await expect(client.final(segmentId, "界".repeat(Math.ceil(MAX_TURN_TEXT_BYTES / 3)))).rejects.toThrow("Grid transcription overflow") + await expect(client.stopped("r".repeat(81))).rejects.toThrow("Grid transcription protocol") + }) +}) + +// The HTTP tests execute compiled production code on Node, rather than relying on Bun fetch behavior. +const fixtureDirectory = mkdtempSync(join(tmpdir(), "grid-api-test-")) +const nodeExecutable = execFileSync(process.env.GRID_TRANSCRIPTION_TEST_NODE ?? "node", ["--print", "process.execPath"], { encoding: "utf8" }).trim() +writeFileSync(join(fixtureDirectory, "package.json"), '{"type":"module"}') +execFileSync(nodeExecutable, [fileURLToPath(new URL("../node_modules/typescript/bin/tsc", import.meta.url)), + "--ignoreConfig", "--target", "ES2022", "--module", "NodeNext", "--moduleResolution", "NodeNext", "--strict", + "--noUncheckedIndexedAccess", "--skipLibCheck", "--types", "node", "--verbatimModuleSyntax", "--outDir", fixtureDirectory, + fileURLToPath(new URL("./api.ts", import.meta.url)), +], { cwd: fileURLToPath(new URL("..", import.meta.url)), env: {}, encoding: "utf8" }) +const harness = ` +import assert from 'node:assert/strict'; +import {createServer} from 'node:http'; +import {WorkerControlClient,RunControlClient} from ${JSON.stringify(pathToFileURL(join(fixtureDirectory, "api.js")).href)}; +const claim=${JSON.stringify(claimFixture)},renewal=${JSON.stringify(renewalFixture)},segmentId=${JSON.stringify(segmentId)}; +const safe=code=>error=>error.name==='TranscriptionError'&&error.code===code&&error.message==='Grid transcription '+code; +const listen=async handler=>{const server=createServer(handler);await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve)); +return {server,url:'http://127.0.0.1:'+server.address().port,close:async()=>{server.closeAllConnections();await new Promise(resolve=>server.close(resolve));}};}; +const read=async request=>{const chunks=[];for await(const chunk of request)chunks.push(chunk);return JSON.parse(Buffer.concat(chunks).toString());}; +const json=(response,value)=>{response.setHeader('Content-Type','application/json');response.end(JSON.stringify(value));}; +` +function runNode(script: string): Promise { + return new Promise((resolve, reject) => { + execFile(nodeExecutable, ["--input-type=module", "--eval", harness + script], { env: {}, timeout: 10_000, maxBuffer: 8000 }, + (error, stdout, stderr) => { + if (error) { reject(new Error(`Native API fixture failed: ${stderr.slice(0, 4000)}`)); return } + try { resolve(JSON.parse(stdout)) } catch { reject(new Error("Native API fixture result protocol")) } + }) + }) +} + +describe("control API over native loopback HTTP", () => { + test("uses exact internal root paths, separate bearer credentials and server-shaped replies", async () => { + expect(await runNode(` +const requests=[];let claims=0; +const fixture=await listen(async(request,response)=>{const body=await read(request);requests.push({path:request.url,method:request.method,auth:request.headers.authorization,body}); +switch(request.url){ +case '/_internal/grid-transcription/heartbeat':json(response,{ready:body.ready});break; +case '/_internal/grid-transcription/claim':if(claims++===0){response.writeHead(204);response.end();}else json(response,claim);break; +case '/_internal/grid-transcription/renew':json(response,renewal);break; +case '/_internal/grid-transcription/admit':json(response,{segmentId});break; +case '/_internal/grid-transcription/final':json(response,{messageId:12});break; +case '/_internal/grid-transcription/stopped':json(response,{stopped:true});break; +default:response.writeHead(404);response.end();}}); +try{const worker=new WorkerControlClient(fixture.url,'worker-fixture-secret','worker-1'); +await worker.heartbeat('meeting',true);assert.equal(await worker.claim(),undefined);assert.equal((await worker.claim()).roomId,'42'); +const run=new RunControlClient(fixture.url,'run-fixture-token');assert.equal((await run.renew()).allowFinalFlush,true); +assert.equal(await run.admit('identity','track','turn'),segmentId);await run.final(segmentId,'a'.repeat(32768));await run.stopped('user_stop'); +assert.equal(requests.length,7);assert(requests.every(value=>value.method==='POST')); +assert(requests.slice(0,3).every(value=>value.auth==='Bearer worker-fixture-secret')); +assert(requests.slice(3).every(value=>value.auth==='Bearer run-fixture-token')); +assert.deepEqual(requests[0].body,{workerId:'worker-1',model:'meeting',ready:true});assert.deepEqual(requests[3].body,{}); +assert.deepEqual(requests[4].body,{participantIdentity:'identity',trackSid:'track',sourceTurnKey:'turn'}); +assert.equal(requests[5].body.text.length,32768);console.log(JSON.stringify({requests:requests.length})); +}finally{await fixture.close();} +`)).toEqual({ requests: 7 }) + }, 15_000) + + test("maps HTTP rejections to safe codes without returning private error bodies", async () => { + expect(await runNode(` +const statuses=[401,403,404,409,410,500];let index=0; +const fixture=await listen((_request,response)=>{response.writeHead(statuses[index++]);response.end('PRIVATE error detail '.repeat(10000));}); +try{const client=new WorkerControlClient(fixture.url,'fixture-secret','worker'); +for(const code of ['stopped','stopped','stopped','stopped','expired','provider'])await assert.rejects(()=>client.claim(),safe(code)); +console.log(JSON.stringify({rejections:index}));}finally{await fixture.close();} +`)).toEqual({ rejections: 6 }) + }, 15_000) + + test("caps streamed success responses and fails safely on malformed JSON", async () => { + expect(await runNode(` +let requests=0;const fixture=await listen((_request,response)=>{requests++;if(requests===1){response.write('"');response.end('a'.repeat(65536)+'"');}else response.end('PRIVATE malformed JSON');}); +try{const client=new WorkerControlClient(fixture.url,'fixture-secret','worker'); +await assert.rejects(()=>client.claim(),safe('overflow'));await assert.rejects(()=>client.claim(),safe('provider')); +console.log(JSON.stringify({requests}));}finally{await fixture.close();} +`)).toEqual({ requests: 2 }) + }, 15_000) + + test("times out both stalled headers and a stalled response body", async () => { + expect(await runNode(` +let requests=0;const fixture=await listen((_request,response)=>{requests++;if(requests===2){response.writeHead(200,{'Content-Type':'application/json'});response.write('{');}}); +try{const client=new WorkerControlClient(fixture.url,'fixture-secret','worker',120); +await assert.rejects(()=>client.claim(),safe('provider'));await assert.rejects(()=>client.claim(),safe('provider')); +console.log(JSON.stringify({requests}));}finally{await fixture.close();} +`)).toEqual({ requests: 2 }) + }, 15_000) +}) diff --git a/server/packages/grid-transcription/src/api.ts b/server/packages/grid-transcription/src/api.ts new file mode 100644 index 000000000..bd883afe5 --- /dev/null +++ b/server/packages/grid-transcription/src/api.ts @@ -0,0 +1,213 @@ +import { Buffer } from "node:buffer" +import { MAX_TURN_TEXT_BYTES, TranscriptionError, nonemptyString, record, type Model } from "./protocol.js" + +export const RUN_DURATION_MS = 2 * 60 * 60 * 1000 +export const SPEAKER_LIMIT = 8 +export const LEASE_MS = 15_000 +const MAX_RESPONSE_BYTES = 64 * 1024 + +export type ParticipantGrant = Readonly<{ identity: string; userId: string; membershipId: string }> +export type ClaimedRun = Readonly<{ + runId: string; claimEpoch: number; roomId: string; generation: number; runToken: string; + providerTarget: string; livekit: Readonly<{ serverUrl: string; token: string }>; + model: Model; leaseMs: number; expiresAt: string; participants: readonly ParticipantGrant[]; +}> +export type StoppedClaim = Readonly<{ runId: string; claimEpoch: number; runToken: string; stopImmediately: true }> +export type RenewedRun = Readonly<{ + state: "active" | "stopping" | "stopped"; allowFinalFlush: boolean; leaseExpiresAt: string; + participants: readonly ParticipantGrant[]; +}> + +function integer(value: unknown, minimum = 0): number { + if (typeof value !== "number" || !Number.isSafeInteger(value) || value < minimum) { + throw new TranscriptionError("protocol") + } + return value +} + +function secret(value: unknown): string { + if (typeof value !== "string" || value.length === 0 || value.length > 16_384 || /[\r\n]/.test(value)) { + throw new TranscriptionError("protocol") + } + return value +} + +function boundedString(value: unknown, maximum: number): string { + const result = nonemptyString(value) + if (result.length > maximum) throw new TranscriptionError("protocol") + return result +} + +function decimalId(value: unknown): string { + const result = typeof value === "number" ? String(integer(value, 1)) : boundedString(value, 128) + if (!/^[1-9][0-9]*$/.test(result)) throw new TranscriptionError("protocol") + return result +} + +function parseUrl(value: string): URL { + try { return new URL(value) } + catch { throw new TranscriptionError("protocol") } +} + +function date(value: unknown): string { + const result = nonemptyString(value) + if (!Number.isFinite(Date.parse(result))) throw new TranscriptionError("protocol") + return result +} + +export function participants(value: unknown): readonly ParticipantGrant[] { + // Manifest may include muted/trackless occupants; the microphone cap is checked separately. + if (!Array.isArray(value) || value.length > 100) throw new TranscriptionError("overflow") + const identities = new Set() + const result: ParticipantGrant[] = [] + for (const entry of value) { + const item = record(entry) + const identity = boundedString(item.identity, 128) + const membershipId = boundedString(item.membershipId, 128) + const userId = decimalId(item.userId) + if (identities.has(identity)) throw new TranscriptionError("protocol") + identities.add(identity) + result.push(Object.freeze({ identity, membershipId, userId })) + } + return Object.freeze(result) +} + +export function claimedRun(value: unknown): ClaimedRun { + const item = record(value) + const media = record(item.livekit) + if (item.model !== "standard" && item.model !== "meeting") throw new TranscriptionError("protocol") + const leaseMs = integer(item.leaseMs, 1) + if (leaseMs > LEASE_MS) throw new TranscriptionError("protocol") + const serverUrl = nonemptyString(media.serverUrl) + const url = parseUrl(serverUrl) + if (url.protocol !== "wss:" && url.protocol !== "ws:") throw new TranscriptionError("protocol") + return Object.freeze({ + runId: boundedString(item.runId, 128), claimEpoch: integer(item.claimEpoch, 1), + roomId: decimalId(item.roomId), generation: integer(item.generation), + runToken: secret(item.runToken), providerTarget: nonemptyString(item.providerTarget), + livekit: Object.freeze({ serverUrl, token: secret(media.token) }), model: item.model, + leaseMs, expiresAt: date(item.expiresAt), participants: participants(item.participants), + }) +} +export function claimResponse(value: unknown): ClaimedRun | StoppedClaim { + const item = record(value) + if (item.stopImmediately !== undefined && typeof item.stopImmediately !== "boolean") throw new TranscriptionError("protocol") + if (item.stopImmediately !== true) return claimedRun(value) + return Object.freeze({ runId: boundedString(item.runId, 128), claimEpoch: integer(item.claimEpoch, 1), + runToken: secret(item.runToken), stopImmediately: true }) +} + +export function renewedRun(value: unknown): RenewedRun { + const item = record(value) + if (item.state !== "active" && item.state !== "stopping" && item.state !== "stopped") { + throw new TranscriptionError("protocol") + } + if (item.allowFinalFlush !== undefined && typeof item.allowFinalFlush !== "boolean") { + throw new TranscriptionError("protocol") + } + return Object.freeze({ + state: item.state, allowFinalFlush: item.allowFinalFlush ?? false, + leaseExpiresAt: date(item.leaseExpiresAt), participants: participants(item.participants), + }) +} + +class ControlHttp { + private readonly base: URL + + constructor(baseUrl: string, private readonly bearer: string, private readonly timeoutMs = 2500) { + this.base = parseUrl(baseUrl) + if (this.base.protocol !== "https:" && !(this.base.protocol === "http:" + && ["127.0.0.1", "localhost", "[::1]"].includes(this.base.hostname))) { + throw new TranscriptionError("protocol") + } + if (this.base.username || this.base.password || this.base.search || this.base.hash || this.base.pathname !== "/" + || !Number.isFinite(timeoutMs) || timeoutMs < 1 || timeoutMs > 5000) { + throw new TranscriptionError("protocol") + } + secret(bearer) + } + + async post(path: string, body: unknown): Promise { + try { + const url = new URL(`/_internal/grid-transcription/${path}`, this.base) + const response = await fetch(url, { + method: "POST", redirect: "error", signal: AbortSignal.timeout(this.timeoutMs), + headers: { Authorization: `Bearer ${this.bearer}`, "Content-Type": "application/json" }, + body: JSON.stringify(body), + }) + if (response.status === 204) return undefined + if (!response.ok) { + await response.body?.cancel() + throw new TranscriptionError(response.status === 410 ? "expired" + : [401, 403, 404, 409].includes(response.status) ? "stopped" : "provider") + } + const reader = response.body?.getReader() + if (!reader) throw new TranscriptionError("protocol") + const chunks: Uint8Array[] = [] + let size = 0 + try { + while (true) { + const part = await reader.read() + if (part.done) break + size += part.value.byteLength + if (size > MAX_RESPONSE_BYTES) { + await reader.cancel() + throw new TranscriptionError("overflow") + } + chunks.push(part.value) + } + } finally { reader.releaseLock() } + const bytes = Buffer.concat(chunks, size) + return JSON.parse(bytes.toString("utf8")) as unknown + } catch (error) { + throw error instanceof TranscriptionError ? error : new TranscriptionError("provider") + } + } +} + +export class WorkerControlClient { + private readonly http: ControlHttp + + constructor(baseUrl: string, sharedSecret: string, readonly workerId: string, timeoutMs?: number) { + boundedString(workerId, 80) + this.http = new ControlHttp(baseUrl, sharedSecret, timeoutMs) + } + + async heartbeat(model: Model, ready: boolean): Promise { + await this.http.post("heartbeat", { workerId: this.workerId, model, ready }) + } + + async claim(): Promise { + const value = await this.http.post("claim", { workerId: this.workerId }) + return value === undefined ? undefined : claimResponse(value) + } +} + +export class RunControlClient { + private readonly http: ControlHttp + + constructor(baseUrl: string, runToken: string, timeoutMs?: number) { + this.http = new ControlHttp(baseUrl, runToken, timeoutMs) + } + + async renew(): Promise { return renewedRun(await this.http.post("renew", {})) } + + async admit(participantIdentity: string, trackSid: string, sourceTurnKey: string): Promise { + const value = record(await this.http.post("admit", { + participantIdentity: boundedString(participantIdentity, 128), trackSid: boundedString(trackSid, 128), + sourceTurnKey: boundedString(sourceTurnKey, 128), + })) + return boundedString(value.segmentId, 128) + } + + async final(segmentId: string, text: string): Promise { + if (typeof text !== "string") throw new TranscriptionError("protocol") + if (text.length > MAX_TURN_TEXT_BYTES || Buffer.byteLength(text) > MAX_TURN_TEXT_BYTES) { + throw new TranscriptionError("overflow") + } + const value = record(await this.http.post("final", { segmentId: boundedString(segmentId, 128), text })) + if (value.messageId !== null) decimalId(value.messageId) + } + + async stopped(reason: string): Promise { await this.http.post("stopped", { reason: boundedString(reason, 80) }) } +} diff --git a/server/packages/grid-transcription/src/authority.test.ts b/server/packages/grid-transcription/src/authority.test.ts new file mode 100644 index 000000000..ca6bc548a --- /dev/null +++ b/server/packages/grid-transcription/src/authority.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, test } from "bun:test" +import { CaptureAuthority } from "./authority.js" + +const identity = { runId: "run", claimEpoch: 1, generation: 3 } +const grant = { identity: "inline-grid-user-7-membership", trackSid: "TR-mic", speakerUserId: "7", membershipId: "membership" } +const microphone = { identity: grant.identity, trackSid: grant.trackSid, kind: "audio", source: "microphone" } as const + +function setup(expiresAtMs = 100_000) { + let clock = 1_000 + const authority = new CaptureAuthority({ identity, requestedAtMs: 1_000, leaseMs: 5_000, expiresAtMs, + microphones: [grant], now: () => clock }) + return { authority, advance: (time: number) => { clock = time } } +} + +describe("verified capture authority", () => { + test("initial connection authority grants no microphone until the API/publication join is installed", () => { + const authority = new CaptureAuthority({ identity, requestedAtMs: 1000, leaseMs: 5000, expiresAtMs: 100_000, + microphones: [], now: () => 1000 }) + expect(authority.microphone(identity, microphone)).toBeUndefined() + authority.replaceMicrophones(identity, [grant]) + expect(authority.microphone(identity, microphone)?.membershipId).toBe("membership") + authority.replaceMicrophones(identity, []) + expect(authority.microphone(identity, microphone)).toBeUndefined() + }) + test("invalid replacement is atomic and ninth microphone is rejected", () => { + const { authority } = setup() + expect(() => authority.replaceMicrophones(identity, [grant, grant])).toThrow("protocol") + expect(authority.microphone(identity, microphone)?.speakerUserId).toBe("7") + expect(() => authority.replaceMicrophones(identity, Array.from({ length: 9 }, (_, index) => ({ ...grant, trackSid: `TR-${index}` })))).toThrow("overflow") + expect(authority.microphone(identity, microphone)?.speakerUserId).toBe("7") + }) + test("exact microphone identity and track only; screen, prefix impersonation and stale claim denied", () => { + const { authority } = setup() + expect(authority.microphone(identity, microphone)?.speakerUserId).toBe("7") + expect(authority.microphone(identity, { ...microphone, source: "screen" })).toBeUndefined() + expect(authority.microphone(identity, { ...microphone, identity: "inline-grid-user-7-other" })).toBeUndefined() + expect(authority.microphone(identity, { ...microphone, trackSid: "TR-old" })).toBeUndefined() + expect(() => authority.microphone({ ...identity, claimEpoch: 2 }, microphone)).toThrow("protocol") + authority.revoke(grant.trackSid) + expect(authority.microphone(identity, microphone)).toBeUndefined() + }) + test("delayed lease response counts from request start, and expiry cannot be resurrected", () => { + const { authority, advance } = setup() + advance(5_900) + authority.renew(identity, 2_000, 5_000) + advance(7_000) + expect(() => authority.assertCurrent(identity)).toThrow("expired") + expect(() => authority.renew(identity, 6_900, 5_000)).toThrow("stopped") + }) + test("out-of-order renewals cannot extend a newer accepted deadline", () => { + const { authority, advance } = setup() + advance(3_000) + authority.renew(identity, 3_000, 4_000) + authority.renew(identity, 2_000, 20_000) + advance(7_000) + expect(() => authority.assertCurrent(identity)).toThrow("expired") + }) + test("fixed run deadline is never extended by lease renewals", () => { + const { authority, advance } = setup(6_000) + advance(5_000) + authority.renew(identity, 5_000, 5_000) + advance(6_000) + expect(() => authority.assertCurrent(identity)).toThrow("expired") + }) + test("stop invalidates buffered frame authority permanently", () => { + const { authority } = setup() + authority.stop() + expect(() => authority.microphone(identity, microphone)).toThrow("stopped") + }) +}) diff --git a/server/packages/grid-transcription/src/authority.ts b/server/packages/grid-transcription/src/authority.ts new file mode 100644 index 000000000..5763ef350 --- /dev/null +++ b/server/packages/grid-transcription/src/authority.ts @@ -0,0 +1,94 @@ +import { TranscriptionError } from "./protocol.js" + +export type CaptureIdentity = Readonly<{ runId: string; claimEpoch: number; generation: number }> +export type MicrophoneGrant = Readonly<{ identity: string; trackSid: string; speakerUserId: string; membershipId: string }> +export type Publication = Readonly<{ identity: string; trackSid: string; kind: "audio" | "video"; source: "microphone" | "screen" | "other" }> + +// Construct only from verified API authority. Mutable room metadata is never a grant. +export class CaptureAuthority { + private stopped = false + private deadlineMs: number + private lastRenewRequestMs: number + private readonly tracks = new Map() + readonly identity: CaptureIdentity + + constructor(options: { + identity: CaptureIdentity; requestedAtMs: number; leaseMs: number; expiresAtMs: number; + microphones: readonly MicrophoneGrant[]; now?: () => number; + }) { + this.identity = Object.freeze({ ...options.identity }) + this.now = options.now ?? (() => performance.now()) + this.expiresAtMs = options.expiresAtMs + this.lastRenewRequestMs = options.requestedAtMs + this.deadlineMs = this.deadline(options.requestedAtMs, options.leaseMs) + if (!options.identity.runId || !Number.isSafeInteger(options.identity.claimEpoch) || options.identity.claimEpoch <= 0 + || !Number.isSafeInteger(options.identity.generation) || options.identity.generation < 0) throw new TranscriptionError("protocol") + if (options.microphones.length > 8) throw new TranscriptionError("overflow") + for (const grant of options.microphones) { + if (!grant.identity || !grant.trackSid || !grant.speakerUserId || !grant.membershipId || this.tracks.has(grant.trackSid)) { + throw new TranscriptionError("protocol") + } + this.tracks.set(grant.trackSid, Object.freeze({ ...grant })) + } + this.assertCurrent(this.identity) + } + + private readonly now: () => number + private readonly expiresAtMs: number + + private deadline(requestedAtMs: number, leaseMs: number): number { + if (!Number.isFinite(requestedAtMs) || !Number.isFinite(this.expiresAtMs) + || !Number.isFinite(leaseMs) || leaseMs <= 0 || leaseMs > 30_000 || requestedAtMs > this.now()) { + throw new TranscriptionError("protocol") + } + return Math.min(requestedAtMs + leaseMs, this.expiresAtMs) + } + + assertCurrent(identity: CaptureIdentity): void { + if (this.stopped) throw new TranscriptionError("stopped") + if (this.now() >= this.deadlineMs) { + this.stop() + throw new TranscriptionError("expired") + } + if (identity.runId !== this.identity.runId || identity.claimEpoch !== this.identity.claimEpoch + || identity.generation !== this.identity.generation) throw new TranscriptionError("protocol") + } + + // A delayed response counts from request start and cannot resurrect expired authority. + renew(identity: CaptureIdentity, requestedAtMs: number, leaseMs: number): void { + this.assertCurrent(identity) + const deadline = this.deadline(requestedAtMs, leaseMs) + if (requestedAtMs <= this.lastRenewRequestMs) return + if (deadline <= this.now()) { + this.stop() + throw new TranscriptionError("expired") + } + this.lastRenewRequestMs = requestedAtMs + this.deadlineMs = deadline + } + + microphone(identity: CaptureIdentity, publication: Publication): MicrophoneGrant | undefined { + this.assertCurrent(identity) + const grant = this.tracks.get(publication.trackSid) + if (publication.kind !== "audio" || publication.source !== "microphone" || !grant + || grant.identity !== publication.identity) return undefined + return grant + } + + revoke(trackSid: string): void { this.tracks.delete(trackSid) } + // Atomically join the verified API membership manifest with current microphone publications. + replaceMicrophones(identity: CaptureIdentity, grants: readonly MicrophoneGrant[]): void { + this.assertCurrent(identity) + if (grants.length > 8) throw new TranscriptionError("overflow") + const next = new Map() + for (const grant of grants) { + if (!grant.identity || !grant.trackSid || !grant.speakerUserId || !grant.membershipId || next.has(grant.trackSid)) { + throw new TranscriptionError("protocol") + } + next.set(grant.trackSid, Object.freeze({ ...grant })) + } + this.tracks.clear() + for (const [sid, grant] of next) this.tracks.set(sid, grant) + } + stop(): void { this.stopped = true; this.tracks.clear() } +} diff --git a/server/packages/grid-transcription/src/copy-model.ts b/server/packages/grid-transcription/src/copy-model.ts new file mode 100644 index 000000000..a7661ed0e --- /dev/null +++ b/server/packages/grid-transcription/src/copy-model.ts @@ -0,0 +1,4 @@ +import { copyFile } from "node:fs/promises" + +await copyFile(new URL("../src/silero_vad.onnx", import.meta.url), new URL("./silero_vad.onnx", import.meta.url)) +await copyFile(new URL("../src/silero-model-license.txt", import.meta.url), new URL("./silero-model-license.txt", import.meta.url)) diff --git a/server/packages/grid-transcription/src/daemon.test.ts b/server/packages/grid-transcription/src/daemon.test.ts new file mode 100644 index 000000000..6f61a7c31 --- /dev/null +++ b/server/packages/grid-transcription/src/daemon.test.ts @@ -0,0 +1,162 @@ +import { describe, expect, test } from "bun:test" +import { execFileSync, fork } from "node:child_process" +import { createServer } from "node:http" +import { fileURLToPath } from "node:url" +import { workerConfig } from "./daemon.js" + +const nodeExecutable = execFileSync(process.env.GRID_TRANSCRIPTION_TEST_NODE ?? "node", ["--print", "process.execPath"], { encoding: "utf8" }).trim() +let compiled = false +function ensureCompiled(): void { + if (compiled) return + execFileSync(nodeExecutable, [fileURLToPath(new URL("../node_modules/typescript/bin/tsc", import.meta.url)), "-p", "tsconfig.json"], { + cwd: fileURLToPath(new URL("..", import.meta.url)), env: {}, encoding: "utf8", + }) + execFileSync(nodeExecutable, [fileURLToPath(new URL("../dist/copy-model.js", import.meta.url))], { env: {}, encoding: "utf8" }) + compiled = true +} +async function noChildClaimFixture(mode: "delayed" | "lost" | "retry_stopped") { + ensureCompiled() + let daemon: ReturnType | undefined + let claims = 0 + let stops = 0 + let acknowledged = false + let prematureExit = false + let readyFalse = false + const workers: string[] = [] + const server = createServer(async (request, response) => { + let body = "" + for await (const chunk of request) body += chunk + const value = JSON.parse(body) as Record + response.setHeader("Content-Type", "application/json") + if (request.url?.endsWith("/heartbeat")) { + if (value.ready === false) readyFalse = true + response.end("{}") + } else if (request.url?.endsWith("/claim")) { + claims++ + workers.push(String(value.workerId)) + if (mode === "lost" && claims === 1) { daemon?.kill("SIGTERM"); response.destroy(); return } + const payload = { runId: "fixture-no-child", claimEpoch: 1, runToken: "fixture-stop-token", stopImmediately: true } + if (mode === "delayed") { + daemon?.kill("SIGTERM") + setTimeout(() => response.end(JSON.stringify({ ...payload, stopImmediately: undefined, roomId: 1, generation: 1, + providerTarget: "http://127.0.0.1:9", livekit: { serverUrl: "ws://127.0.0.1:9", token: "fixture-token" }, + model: "meeting", leaseMs: 15000, expiresAt: new Date(Date.now() + 60_000).toISOString(), participants: [] })), 300) + } else response.end(JSON.stringify(payload)) + } else if (request.url?.endsWith("/stopped")) { + stops++ + if (mode === "retry_stopped" && stops <= 2) { response.writeHead(503).end("{}"); return } + setTimeout(() => { + acknowledged = true + response.end("{}") + if (mode === "retry_stopped") setTimeout(() => daemon?.kill("SIGTERM"), 100) + }, 300) + } else response.writeHead(404).end("{}") + }) + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)) + const address = server.address() + if (!address || typeof address === "string") throw new Error("loopback fixture failed") + try { + daemon = fork(fileURLToPath(new URL("../dist/daemon.js", import.meta.url)), [], { + execPath: nodeExecutable, execArgv: [], env: { GRID_TRANSCRIPTION_API_URL: `http://127.0.0.1:${address.port}`, + GRID_TRANSCRIPTION_WORKER_SECRET: "fixture-secret", SONIOX_API_KEY: "fixture-key" }, + stdio: ["ignore", "ignore", "ignore", "ipc"], + }) + const child = daemon + const code = await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { child.kill("SIGKILL"); reject(new Error("claim fixture deadline")) }, 15_000) + child.once("error", (error) => { clearTimeout(timeout); reject(error) }) + child.once("exit", (exitCode) => { clearTimeout(timeout); prematureExit = !acknowledged; resolve(exitCode) }) + }) + return { code, claims, stops, acknowledged, prematureExit, readyFalse, sameBoot: new Set(workers).size === 1 } + } finally { + if (daemon && daemon.exitCode === null && daemon.signalCode === null) daemon.kill("SIGKILL") + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } +} + +describe("daemon configuration and real shutdown", () => { + test("SIGTERM waits for a delayed claim response before acknowledging no child was allocated", async () => { + expect(await noChildClaimFixture("delayed")).toEqual({ code: 0, claims: 1, stops: 1, + acknowledged: true, prematureExit: false, readyFalse: true, sameBoot: true }) + }, 20_000) + test("SIGTERM recovers the same boot's lost claim as stop-only authority", async () => { + expect(await noChildClaimFixture("lost")).toEqual({ code: 0, claims: 2, stops: 1, + acknowledged: true, prematureExit: false, readyFalse: true, sameBoot: true }) + }, 20_000) + test("known stopped receipt retries after API recovery before another claim", async () => { + expect(await noChildClaimFixture("retry_stopped")).toEqual({ code: 0, claims: 1, stops: 3, + acknowledged: true, prematureExit: false, readyFalse: true, sameBoot: true }) + }, 20_000) + test("the configured provider is internal, default meeting, and every boot has distinct authority", () => { + const source = { GRID_TRANSCRIPTION_API_URL: "https://api.example.test", GRID_TRANSCRIPTION_WORKER_SECRET: "test-secret" } + const first = workerConfig(source) + const second = workerConfig(source) + expect(first.model).toBe("meeting") + expect(first.apiKey).toBeUndefined() + expect(first.workerId).not.toBe(second.workerId) + expect(first.workerId.length).toBeLessThanOrEqual(80) + const standard = workerConfig({ ...source, GRID_TRANSCRIPTION_MODEL: "standard", OPENAI_API_KEY: "fixture-key" }) + expect(standard.model).toBe("standard") + expect(standard.apiKey).toBe("fixture-key") + expect(() => workerConfig({ ...source, GRID_TRANSCRIPTION_MODEL: "client-selected-model" })).toThrow("protocol") + }) + + test("compiled Node daemon SIGTERM waits for its stopped HTTP acknowledgement after actual native exit", async () => { + // No real credentials, provider socket or remote room is contacted. The real + // native child joins a refused loopback endpoint, exits, and reports through its watchdog. + ensureCompiled() + let claimed = false + let stoppedRequested = false + let stoppedAcknowledged = false + let prematureExit = false + let daemon: ReturnType | undefined + const server = createServer((request, response) => { + response.setHeader("Content-Type", "application/json") + if (request.url === "/_internal/grid-transcription/claim") { + if (claimed) { response.writeHead(204).end(); return } + claimed = true + response.end(JSON.stringify({ runId: "fixture-run", claimEpoch: 1, roomId: 1, generation: 1, + providerTarget: "http://127.0.0.1:9", livekit: { serverUrl: "ws://127.0.0.1:9", token: "not-a-real-token" }, + runToken: "test-run-token", model: "meeting", leaseMs: 15000, + expiresAt: new Date(Date.now() + 60_000).toISOString(), participants: [] })) + } else if (request.url === "/_internal/grid-transcription/stopped") { + stoppedRequested = true + daemon?.kill("SIGTERM") + setTimeout(() => { stoppedAcknowledged = true; response.end("{}") }, 500) + } else if (request.url === "/_internal/grid-transcription/renew") { + response.end(JSON.stringify({ state: "active", allowFinalFlush: false, + leaseExpiresAt: new Date(Date.now() + 15_000).toISOString(), participants: [] })) + } else if (request.url === "/_internal/grid-transcription/heartbeat") response.end("{}") + else response.writeHead(404).end("{}") + }) + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)) + const address = server.address() + if (!address || typeof address === "string") throw new Error("loopback fixture failed") + try { + daemon = fork(fileURLToPath(new URL("../dist/daemon.js", import.meta.url)), [], { + execPath: nodeExecutable, execArgv: [], env: { GRID_TRANSCRIPTION_API_URL: `http://127.0.0.1:${address.port}`, + GRID_TRANSCRIPTION_WORKER_SECRET: "test-secret", SONIOX_API_KEY: "fixture-key", NODE_ENV: "test" }, + stdio: ["ignore", "ignore", "ignore", "ipc"], + }) + const child = daemon + const code = await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { child.kill("SIGKILL"); reject(new Error("daemon shutdown fixture deadline")) }, 25_000) + child.once("error", (error) => { clearTimeout(timeout); reject(error) }) + child.once("exit", (exitCode) => { + clearTimeout(timeout) + prematureExit = stoppedRequested && !stoppedAcknowledged + resolve(exitCode) + }) + }) + expect(code).toBe(0) + expect(stoppedRequested).toBe(true) + expect(stoppedAcknowledged).toBe(true) + expect(prematureExit).toBe(false) + } finally { + if (daemon && daemon.exitCode === null && daemon.signalCode === null) daemon.kill("SIGKILL") + server.closeAllConnections() + await new Promise((resolve) => server.close(() => resolve())) + } + }, 30_000) +}) diff --git a/server/packages/grid-transcription/src/daemon.ts b/server/packages/grid-transcription/src/daemon.ts new file mode 100644 index 000000000..ab1dda958 --- /dev/null +++ b/server/packages/grid-transcription/src/daemon.ts @@ -0,0 +1,231 @@ +import { randomUUID } from "node:crypto" +import { fileURLToPath } from "node:url" +import { LEASE_MS, RUN_DURATION_MS, WorkerControlClient, RunControlClient, type ClaimedRun } from "./api.js" +import { TranscriptionError, type Model } from "./protocol.js" +import { SileroModel } from "./silero.js" +import { NativeRoomSupervisor, type NativeExitReceipt, type StopReason } from "./supervisor.js" +import { monotonicMilliseconds } from "./watchdog.js" + +export type WorkerConfig = Readonly<{ apiBaseUrl: string; sharedSecret: string; workerId: string; model: Model; apiKey?: string }> +export function workerConfig(environment: Readonly>): WorkerConfig { + const model = environment.GRID_TRANSCRIPTION_MODEL ?? "meeting" + if (model !== "meeting" && model !== "standard") throw new TranscriptionError("protocol") + const apiBaseUrl = environment.GRID_TRANSCRIPTION_API_URL + const sharedSecret = environment.GRID_TRANSCRIPTION_WORKER_SECRET + const label = environment.GRID_TRANSCRIPTION_WORKER_ID ?? "grid-worker" + const apiKey = model === "meeting" ? environment.SONIOX_API_KEY : environment.OPENAI_API_KEY + if (!apiBaseUrl || !sharedSecret || sharedSecret.length > 4096 || /[\r\n]/.test(sharedSecret) + || !/^[a-zA-Z0-9_-]{1,40}$/.test(label) + || (apiKey !== undefined && (apiKey.length > 4096 || /[\r\n]/.test(apiKey)))) throw new TranscriptionError("protocol") + // Every boot gets distinct authority. Only transport retries within this boot may reclaim its lost claim response. + return Object.freeze({ apiBaseUrl, sharedSecret, workerId: `${label}-${randomUUID()}`, model, + ...(apiKey ? { apiKey } : {}) }) +} + +type OwnedRun = { run?: ClaimedRun; api: RunControlClient; expiresAtMs: number; exit?: Promise; + reported: Promise; stopReason?: string; acknowledging?: Promise; renewing: boolean } +export class WorkerDaemon { + private readonly control: WorkerControlClient + private readonly supervisor: NativeRoomSupervisor + private current: OwnedRun | undefined + private polling = false + private claimPromise: Promise | undefined + private claimUncertain = false + private heartbeating = false + private closing = false + private ready = false + private claimTimer: NodeJS.Timeout | undefined + private heartbeatTimer: NodeJS.Timeout | undefined + private renewTimer: NodeJS.Timeout | undefined + private shutdownPromise: Promise | undefined + + constructor(private readonly config: WorkerConfig) { + this.control = new WorkerControlClient(config.apiBaseUrl, config.sharedSecret, config.workerId) + this.supervisor = new NativeRoomSupervisor({ roomChildPath: new URL("./room-child.js", import.meta.url), + // Secrets arrive only over IPC. Avoid inherited debug flags, NODE_OPTIONS and credential environment. + environment: { NODE_ENV: "production" } }) + } + async start(): Promise { + // Verify the vendored tensor contract and native runtime before advertising capture availability. + const model = await SileroModel.load() + await model.close() + if (this.closing) return + this.ready = !!this.config.apiKey + await this.heartbeat() + this.claimTimer = setInterval(() => { void this.claim() }, 2000) + this.heartbeatTimer = setInterval(() => { void this.heartbeat() }, 5000) + this.renewTimer = setInterval(() => { void this.renew() }, 1000) + await this.claim() + } + private async heartbeat(): Promise { + if (this.heartbeating) return + this.heartbeating = true + try { await this.control.heartbeat(this.config.model, this.ready && !this.closing) } + catch { + // API loss removes availability and revokes any native owner immediately. + if (this.current && this.supervisor.hasActiveChild) void this.stopCurrent("authority").catch(() => undefined) + } finally { + const owned = this.current + if (owned?.stopReason) await this.acknowledgeStopped(owned) + this.heartbeating = false + } + } + private async claim(): Promise { + if (this.closing || !this.ready || this.polling || this.current || this.supervisor.hasActiveChild) return + this.polling = true + const operation = this.performClaim() + this.claimPromise = operation + try { await operation } finally { + if (this.claimPromise === operation) this.claimPromise = undefined + this.polling = false + } + } + private async performClaim(): Promise { + try { + const requestedAtMs = monotonicMilliseconds() + const requestedAtWallMs = Date.now() + this.claimUncertain = true + const run = await this.control.claim() + this.claimUncertain = false + if (!run) return + const api = new RunControlClient(this.config.apiBaseUrl, run.runToken) + if ("stopImmediately" in run) { + const owned: OwnedRun = { api, expiresAtMs: 0, reported: Promise.resolve(), renewing: false, stopReason: "authority" } + this.current = owned + owned.reported = this.acknowledgeStopped(owned).then(() => undefined) + await owned.reported + return + } + const expiresAtMs = Math.min(requestedAtMs + RUN_DURATION_MS, + requestedAtMs + Date.parse(run.expiresAt) - requestedAtWallMs) + const owned: OwnedRun = { run, api, expiresAtMs, reported: Promise.resolve(), renewing: false } + this.current = owned + if (this.closing || run.model !== this.config.model || !this.config.apiKey) { + // No native transport has been allocated. This is a truthful no-child receipt. + owned.stopReason = "configuration" + owned.reported = this.acknowledgeStopped(owned).then(() => undefined) + await owned.reported + return + } + const identity = { runId: run.runId, claimEpoch: run.claimEpoch, generation: run.generation } + const ready = this.supervisor.start({ identity, requestedAtMs, leaseMs: run.leaseMs, expiresAtMs, + payload: { run, apiBaseUrl: this.config.apiBaseUrl, apiKey: this.config.apiKey, expiresAtMs } }) + const exit = this.supervisor.exitReceipt + if (!exit) throw new TranscriptionError("protocol") + owned.exit = exit + owned.reported = this.observeExit(owned) + await ready + } catch { + if (this.supervisor.hasActiveChild) void this.stopCurrent("authority").catch(() => undefined) + else if (this.current && !this.current.exit) { + const owned = this.current + owned.stopReason = "startup" + owned.reported = this.acknowledgeStopped(owned).then(() => undefined) + await owned.reported + } + } + } + private async renew(): Promise { + const owned = this.current + const run = owned?.run + if (!owned || !run || owned.renewing || !this.supervisor.admitting || this.closing) return + owned.renewing = true + try { + const requestedAtMs = monotonicMilliseconds() + const requestedAtWallMs = Date.now() + const renewed = await owned.api.renew() + if (this.current !== owned || !this.supervisor.admitting || this.closing) return + if (renewed.state !== "active") { + await this.stopCurrent(renewed.state === "stopping" && renewed.allowFinalFlush ? "stopping" : "authority") + return + } + const leaseMs = Math.min(LEASE_MS, Date.parse(renewed.leaseExpiresAt) - requestedAtWallMs) + if (!Number.isSafeInteger(leaseMs) || leaseMs <= 0) throw new TranscriptionError("expired") + this.supervisor.renew({ runId: run.runId, claimEpoch: run.claimEpoch, generation: run.generation }, + requestedAtMs, leaseMs, { participants: renewed.participants }) + } catch { if (this.current === owned) await this.stopCurrent("authority").catch(() => undefined) } + finally { owned.renewing = false } + } + private async stopCurrent(reason: StopReason): Promise { + if (!this.current || !this.supervisor.hasActiveChild) return + // This call closes the synchronous supervisor admission fence before waiting. + await this.supervisor.stop(reason) + } + private async observeExit(owned: OwnedRun): Promise { + try { + const receipt = await owned.exit + if (!receipt) throw new TranscriptionError("protocol") + // An OS receipt from the watchdog proves real native transport containment. + // Server acknowledgement is idempotent and is required before this daemon claims another run. + owned.stopReason = receipt.reason + await this.acknowledgeStopped(owned) + } catch { + // Unknown exit keeps both the process slot and this run quarantined. + this.ready = false + await this.heartbeat() + } + } + private async acknowledgeStopped(owned: OwnedRun): Promise { + if (!owned.stopReason) return false + if (owned.acknowledging) return owned.acknowledging + const operation = (async () => { + for (let attempt = 0; attempt < 2; attempt++) { + try { + await owned.api.stopped(owned.stopReason!) + if (this.current === owned) this.current = undefined + this.ready = !!this.config.apiKey && !this.closing + return true + } catch { /* Retain the same known receipt for the existing heartbeat cadence. */ } + } + this.ready = false + return false + })() + owned.acknowledging = operation + try { return await operation } finally { if (owned.acknowledging === operation) owned.acknowledging = undefined } + } + shutdown(): Promise { + if (this.shutdownPromise) return this.shutdownPromise + this.closing = true + this.ready = false + clearInterval(this.claimTimer) + clearInterval(this.heartbeatTimer) + clearInterval(this.renewTimer) + this.shutdownPromise = this.finishShutdown() + return this.shutdownPromise + } + private async finishShutdown(): Promise { + await this.heartbeat() + // A claim already sent can allocate server authority before its response arrives. + // Stop an already-known native owner now, then wait for the claim's late no-child receipt. + if (this.current && this.supervisor.hasActiveChild) void this.stopCurrent("shutdown").catch(() => undefined) + await this.claimPromise + if (this.claimUncertain && !this.current && !this.supervisor.hasActiveChild) { + // Only this boot's existing claim can be recovered after ready=false. + // The API refuses new allocation to an unavailable worker. + for (let attempt = 0; attempt < 2 && this.claimUncertain && !this.current; attempt++) await this.performClaim() + if (this.claimUncertain) throw new TranscriptionError("provider") + } + const owned = this.current + if (owned && this.supervisor.hasActiveChild) await this.stopCurrent("shutdown") + if (owned) { + await owned.exit + await owned.reported + if (this.current === owned) throw new TranscriptionError("provider") + } + } +} + +export async function runDaemon(): Promise { + try { + const daemon = new WorkerDaemon(workerConfig(process.env)) + process.on("SIGTERM", () => { void daemon.shutdown().then(() => process.exit(0)).catch(() => process.exit(1)) }) + process.on("SIGINT", () => { void daemon.shutdown().then(() => process.exit(0)).catch(() => process.exit(1)) }) + await daemon.start() + // Coarse state only. No provider errors, room metadata, audio, text or credentials are logged. + process.stdout.write("Grid transcription worker initialized\n") + } catch { + process.stderr.write("Grid transcription worker unavailable\n") + process.exitCode = 1 + } +} +if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) void runDaemon() diff --git a/server/packages/grid-transcription/src/index.ts b/server/packages/grid-transcription/src/index.ts new file mode 100644 index 000000000..c5101dc97 --- /dev/null +++ b/server/packages/grid-transcription/src/index.ts @@ -0,0 +1,4 @@ +export { CaptureAuthority, type CaptureIdentity, type MicrophoneGrant, type Publication } from "./authority.js" +export { BoundedPcmQueue, pcm16le } from "./pcm.js" +export { ProviderConnection, type ProviderOptions } from "./socket.js" +export { TranscriptionError, type FinalTurn, type Model } from "./protocol.js" diff --git a/server/packages/grid-transcription/src/pcm.test.ts b/server/packages/grid-transcription/src/pcm.test.ts new file mode 100644 index 000000000..7432840c4 --- /dev/null +++ b/server/packages/grid-transcription/src/pcm.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, test } from "bun:test" +import { BoundedPcmQueue, pcm16le } from "./pcm.js" + +describe("bounded native audio handoff", () => { + test("PCM serializes signed samples in little endian and respects an offset view", () => { + const backing = new Int16Array([99, -1, 0x1234, -32768, 99]) + expect([...pcm16le(backing.subarray(1, 4))]).toEqual([255, 255, 52, 18, 0, 128]) + }) + test("producer buffers are copied and a slow consumer has a finite failure bound", async () => { + const queue = new BoundedPcmQueue(4) + const producer = new Int16Array([1, 2]) + queue.push(producer) + producer.fill(9) + expect([...(await queue.take())!]).toEqual([1, 2]) + queue.push(new Int16Array([3, 4, 5])) + expect(() => queue.push(new Int16Array([6, 7]))).toThrow("overflow") + expect(queue.bufferedSamples).toBe(0) + await expect(queue.take()).rejects.toThrow("overflow") + }) + test("stop discards queued PCM and wakes an idle reader", async () => { + const queue = new BoundedPcmQueue(4) + const waiting = queue.take() + queue.close() + expect(await waiting).toBeUndefined() + expect(() => queue.push(new Int16Array([1]))).toThrow("stopped") + const queued = new BoundedPcmQueue(4) + queued.push(new Int16Array([1, 2])) + queued.close() + expect(await queued.take()).toBeUndefined() + }) + test("one consumer receives frames immediately without awaiting provider work", async () => { + const queue = new BoundedPcmQueue(4) + const waiting = queue.take() + queue.push(new Int16Array([4, 5])) + expect([...(await waiting)!]).toEqual([4, 5]) + expect(queue.bufferedSamples).toBe(0) + }) +}) diff --git a/server/packages/grid-transcription/src/pcm.ts b/server/packages/grid-transcription/src/pcm.ts new file mode 100644 index 000000000..4cde77bbb --- /dev/null +++ b/server/packages/grid-transcription/src/pcm.ts @@ -0,0 +1,74 @@ +import { TranscriptionError } from "./protocol.js" + +export function pcm16le(samples: Int16Array): Uint8Array { + if (samples.length === 0 || samples.length > 48_000) throw new TranscriptionError("audio") + const bytes = new Uint8Array(samples.byteLength) + const view = new DataView(bytes.buffer) + for (let i = 0; i < samples.length; i++) view.setInt16(i * 2, samples[i]!, true) + return bytes +} + +// Read native frames promptly into this queue. Provider I/O belongs to a separate consumer. +export class BoundedPcmQueue { + private frames: Int16Array[] = [] + private queuedSamples = 0 + private waiter: { resolve: (frame: Int16Array | undefined) => void; reject: (error: Error) => void } | undefined + private closed = false + private failure: Error | undefined + + constructor(readonly maxSamples: number) { + if (!Number.isSafeInteger(maxSamples) || maxSamples <= 0 || maxSamples > 48_000 * 10) { + throw new TranscriptionError("audio") + } + } + + push(frame: Int16Array): void { + if (this.closed) throw this.failure ?? new TranscriptionError("stopped") + if (frame.length === 0 || frame.length > this.maxSamples) { + const error = new TranscriptionError("overflow") + this.close(error) + throw error + } + if (this.queuedSamples + frame.length > this.maxSamples) { + const error = new TranscriptionError("overflow") + this.close(error) + throw error + } + const owned = frame.slice() + if (this.waiter) { + const waiter = this.waiter + this.waiter = undefined + waiter.resolve(owned) + } else { + this.frames.push(owned) + this.queuedSamples += owned.length + } + } + + take(): Promise { + if (this.failure) return Promise.reject(this.failure) + const frame = this.frames.shift() + if (frame) { + this.queuedSamples -= frame.length + return Promise.resolve(frame) + } + if (this.closed) return Promise.resolve(undefined) + if (this.waiter) return Promise.reject(new TranscriptionError("protocol")) + return new Promise((resolve, reject) => { this.waiter = { resolve, reject } }) + } + + // Stop discards unsent audio; accepted provider speech is flushed by its own bounded owner. + close(failure?: Error): void { + if (this.closed) return + this.closed = true + this.failure = failure + this.frames = [] + this.queuedSamples = 0 + const waiter = this.waiter + this.waiter = undefined + if (failure) waiter?.reject(failure) + else waiter?.resolve(undefined) + } + + get bufferedSamples(): number { return this.queuedSamples } +} diff --git a/server/packages/grid-transcription/src/protocol.ts b/server/packages/grid-transcription/src/protocol.ts new file mode 100644 index 000000000..90e0089ff --- /dev/null +++ b/server/packages/grid-transcription/src/protocol.ts @@ -0,0 +1,34 @@ +export type Model = "standard" | "meeting" + +export type FinalTurn = Readonly<{ turnId: string; text: string }> + +export class TranscriptionError extends Error { + constructor(readonly code: "protocol" | "provider" | "overflow" | "stopped" | "expired" | "audio") { + super(`Grid transcription ${code}`) + this.name = "TranscriptionError" + } +} + +// A decoder is track/socket scoped. IDs are local admitted turns, never model speaker IDs. +export interface TurnDecoder { + beginTurn(turnId: string): void + accept(event: unknown): readonly FinalTurn[] + close(): void +} + +export function record(value: unknown): Record { + if (value === null || typeof value !== "object" || Array.isArray(value)) { + throw new TranscriptionError("protocol") + } + return value as Record +} + +export function nonemptyString(value: unknown): string { + if (typeof value !== "string" || value.length === 0 || value.length > 256) { + throw new TranscriptionError("protocol") + } + return value +} + +export const MAX_PENDING_TURNS = 2 +export const MAX_TURN_TEXT_BYTES = 32 * 1024 diff --git a/server/packages/grid-transcription/src/providers/openai.test.ts b/server/packages/grid-transcription/src/providers/openai.test.ts new file mode 100644 index 000000000..631c04696 --- /dev/null +++ b/server/packages/grid-transcription/src/providers/openai.test.ts @@ -0,0 +1,203 @@ +import { Buffer } from "node:buffer" +import { describe, expect, test } from "bun:test" +import { MAX_PENDING_TURNS, MAX_TURN_TEXT_BYTES, TranscriptionError } from "../protocol.js" +import { + OPENAI_MAX_SOCKET_TURNS, + OpenAITurnDecoder, + openAIAppendAudio, + openAICommit, + openAIConfiguration, + openAIConfigurationAccepted, +} from "./openai.js" + +// Public event shapes: https://developers.openai.com/api/reference/resources/realtime/server-events +const committed = (itemId: string, previousItemId: string | null = null) => ({ + type: "input_audio_buffer.committed", + event_id: `ack_${itemId}`, + item_id: itemId, + previous_item_id: previousItemId, +}) +const completed = (itemId: string, transcript: string) => ({ + type: "conversation.item.input_audio_transcription.completed", + event_id: `final_${itemId}`, + item_id: itemId, + content_index: 0, + transcript, + usage: { type: "duration", seconds: 1.6 }, +}) + +function expectFailure(action: () => unknown, code: TranscriptionError["code"]) { + let caught: unknown + try { + action() + } catch (error) { + caught = error + } + expect(caught).toBeInstanceOf(TranscriptionError) + if (!(caught instanceof TranscriptionError)) throw new Error("Expected safe transcription failure") + expect(caught.code).toBe(code) + expect(caught.message).toBe(`Grid transcription ${code}`) +} + +describe("OpenAI committed-turn wire", () => { + test("uses dedicated transcription, manual commits, automatic languages and PCM24k", () => { + expect(openAIConfiguration()).toEqual({ + type: "session.update", + session: { + type: "transcription", + audio: { + input: { + format: { type: "audio/pcm", rate: 24_000 }, + transcription: { model: "gpt-transcribe" }, + turn_detection: null, + }, + }, + }, + }) + const pcm = Uint8Array.of(0, 128, 255, 127) + const append = openAIAppendAudio(pcm) + expect(append.type).toBe("input_audio_buffer.append") + expect(Buffer.from(append.audio, "base64")).toEqual(Buffer.from(pcm)) + expect(openAICommit()).toEqual({ type: "input_audio_buffer.commit" }) + expectFailure(() => openAIAppendAudio(Uint8Array.of(1)), "audio") + expectFailure(() => openAIAppendAudio(new Uint8Array()), "audio") + expectFailure(() => openAIAppendAudio(new Uint8Array(65_538)), "audio") + }) + + test("requires the matching effective configuration before accepting session readiness", () => { + const requested = openAIConfiguration().session + const echo = { type: "session.updated", session: { ...requested, id: "sess_public_fixture" } } + expect(openAIConfigurationAccepted(echo)).toBe(true) + expect(openAIConfigurationAccepted({ type: "session.created", session: requested })).toBe(false) + const input = requested.audio.input + const invalidSessions = [ + { ...requested, type: "realtime" }, + { ...requested, audio: { input: { ...input, format: { type: "audio/pcmu", rate: 24_000 } } } }, + { ...requested, audio: { input: { ...input, format: { type: "audio/pcm", rate: 16_000 } } } }, + { ...requested, audio: { input: { ...input, transcription: { model: "gpt-live-transcribe" } } } }, + { ...requested, audio: { input: { ...input, turn_detection: { type: "server_vad" } } } }, + { ...requested, audio: { input: { ...input, turn_detection: undefined } } }, + { ...requested, audio: {} }, + { ...requested, audio: { input: { ...input, transcription: null } } }, + ] + for (const session of invalidSessions) { + expectFailure(() => openAIConfigurationAccepted({ type: "session.updated", session }), "protocol") + } + expectFailure(() => openAIConfigurationAccepted({ type: "session.updated" }), "protocol") + }) + + test("ignores partial text and preserves local order when provider finals arrive out of order", () => { + const decoder = new OpenAITurnDecoder() + decoder.beginTurn("local_1") + decoder.beginTurn("local_2") + expect(decoder.accept(committed("item_1"))).toEqual([]) + expect(decoder.accept(committed("item_2", "item_1"))).toEqual([]) + expect(decoder.accept({ + type: "conversation.item.input_audio_transcription.delta", + item_id: "item_1", + content_index: 0, + delta: "wrong provisional text", + })).toEqual([]) + expect(decoder.accept(completed("item_2", "second"))).toEqual([]) + expect(decoder.accept(completed("item_1", "first"))).toEqual([ + { turnId: "local_1", text: "first" }, + { turnId: "local_2", text: "second" }, + ]) + decoder.beginTurn("local_3") + expect(decoder.accept(completed("item_1", "stale duplicate"))).toEqual([]) + expect(decoder.accept(committed("item_2", "item_1"))).toEqual([]) + expect(decoder.accept(committed("item_3", "item_2"))).toEqual([]) + expect(decoder.accept(completed("item_3", "third"))).toEqual([{ turnId: "local_3", text: "third" }]) + }) + + test("buffers a final that precedes its commit acknowledgement without guessing identity", () => { + const decoder = new OpenAITurnDecoder() + decoder.beginTurn("first") + decoder.beginTurn("second") + expect(decoder.accept(completed("item_2", "second text"))).toEqual([]) + expect(decoder.accept(completed("item_1", "first text"))).toEqual([]) + expect(decoder.accept(committed("item_1"))).toEqual([{ turnId: "first", text: "first text" }]) + expect(decoder.accept(committed("item_2", "item_1"))).toEqual([{ turnId: "second", text: "second text" }]) + }) + + test("does not revise a final blocked behind another turn", () => { + const decoder = new OpenAITurnDecoder() + decoder.beginTurn("first") + decoder.beginTurn("second") + decoder.accept(committed("item_1")) + decoder.accept(committed("item_2", "item_1")) + decoder.accept(completed("item_2", "original")) + decoder.accept(completed("item_2", "repeated or stale")) + expect(decoder.accept(completed("item_1", "first"))).toEqual([ + { turnId: "first", text: "first" }, + { turnId: "second", text: "original" }, + ]) + }) + + test("accepts optional predecessors but rejects a conflicting acknowledged chain", () => { + const decoder = new OpenAITurnDecoder() + decoder.beginTurn("one") + expect(decoder.accept({ type: "input_audio_buffer.committed", item_id: "one" })).toEqual([]) + decoder.beginTurn("two") + expectFailure(() => decoder.accept(committed("two", "unknown")), "protocol") + expectFailure(() => decoder.accept(completed("one", "late")), "stopped") + }) + + test("safe provider errors stop decoding without leaking raw error text", () => { + for (const event of [ + { type: "error", error: { message: "SECRET audio and provider detail" } }, + { type: "conversation.item.input_audio_transcription.failed", error: { message: "SECRET" } }, + ]) { + const decoder = new OpenAITurnDecoder() + decoder.beginTurn("turn") + expectFailure(() => decoder.accept(event), "provider") + expectFailure(() => decoder.beginTurn("next"), "stopped") + } + }) + + test("rejects unadmitted/malformed finals and acknowledgement reordering", () => { + for (const event of [null, [], { type: "unexpected" }, completed("unknown", "text")]) { + const decoder = new OpenAITurnDecoder() + expectFailure(() => decoder.accept(event), "protocol") + } + const decoder = new OpenAITurnDecoder() + decoder.beginTurn("local") + expectFailure(() => decoder.accept({ ...completed("item", "text"), content_index: 1 }), "protocol") + const reordered = new OpenAITurnDecoder() + reordered.beginTurn("first") + reordered.beginTurn("second") + expectFailure(() => reordered.accept(committed("item_2", "item_1")), "protocol") + }) + + test("bounds pending turns, early finals, UTF8 text and duplicate history without eviction", () => { + const pending = new OpenAITurnDecoder() + for (let index = 0; index < MAX_PENDING_TURNS; index++) pending.beginTurn(`turn_${index}`) + expectFailure(() => pending.beginTurn("overflow"), "overflow") + const early = new OpenAITurnDecoder() + early.beginTurn("only") + early.accept(completed("item_a", "first")) + expectFailure(() => early.accept(completed("item_b", "second")), "overflow") + const text = new OpenAITurnDecoder() + text.beginTurn("text") + expectFailure(() => text.accept(completed("text", "界".repeat(Math.ceil(MAX_TURN_TEXT_BYTES / 3)))), "overflow") + const history = new OpenAITurnDecoder() + for (let index = 0; index < OPENAI_MAX_SOCKET_TURNS; index++) { + history.beginTurn(`local_${index}`) + history.accept(committed(`item_${index}`, index === 0 ? null : `item_${index - 1}`)) + history.accept(completed(`item_${index}`, "text")) + } + expect(history.accept(completed("item_0", "old duplicate"))).toEqual([]) + expectFailure(() => history.beginTurn("history_overflow"), "overflow") + }) + + test("retires empty transcripts and close fences all later input", () => { + const decoder = new OpenAITurnDecoder() + decoder.beginTurn("silent") + decoder.accept(committed("silent")) + expect(decoder.accept(completed("silent", " \n"))).toEqual([{ turnId: "silent", text: "" }]) + expectFailure(() => decoder.beginTurn("silent"), "protocol") + decoder.close() + decoder.close() + expectFailure(() => decoder.accept(completed("silent", "late")), "stopped") + }) +}) diff --git a/server/packages/grid-transcription/src/providers/openai.ts b/server/packages/grid-transcription/src/providers/openai.ts new file mode 100644 index 000000000..127a6c678 --- /dev/null +++ b/server/packages/grid-transcription/src/providers/openai.ts @@ -0,0 +1,196 @@ +import { Buffer } from "node:buffer" +import { + MAX_PENDING_TURNS, + MAX_TURN_TEXT_BYTES, + TranscriptionError, + nonemptyString, + record, + type FinalTurn, + type TurnDecoder, +} from "../protocol.js" + +export const OPENAI_URL = "wss://api.openai.com/v1/realtime?intent=transcription" +export const OPENAI_SAMPLE_RATE = 24_000 +// Retain IDs for the entire socket; eviction could mistake an old final for a new turn. +export const OPENAI_MAX_SOCKET_TURNS = 1024 +const MAX_AUDIO_FRAME_BYTES = 64 * 1024 + +// https://developers.openai.com/api/docs/guides/realtime-transcription#transcribe-a-committed-turn +export function openAIConfiguration() { + return { + type: "session.update", + session: { + type: "transcription", + audio: { + input: { + format: { type: "audio/pcm", rate: OPENAI_SAMPLE_RATE }, + transcription: { model: "gpt-transcribe" }, + turn_detection: null, + }, + }, + }, + } +} + +// Only a matching effective configuration makes the socket ready for microphone audio. +export function openAIConfigurationAccepted(value: unknown): boolean { + const event = record(value) + if (event.type !== "session.updated") return false + const session = record(event.session) + const input = record(record(session.audio).input) + const format = record(input.format) + const transcription = record(input.transcription) + if (session.type !== "transcription" || format.type !== "audio/pcm" + || format.rate !== OPENAI_SAMPLE_RATE || transcription.model !== "gpt-transcribe" + || input.turn_detection !== null) { + throw new TranscriptionError("protocol") + } + return true +} + +export function openAIAppendAudio(pcm: Uint8Array) { + if (pcm.byteLength === 0 || pcm.byteLength % 2 !== 0 || pcm.byteLength > MAX_AUDIO_FRAME_BYTES) { + throw new TranscriptionError("audio") + } + return { type: "input_audio_buffer.append", audio: Buffer.from(pcm).toString("base64") } +} + +export function openAICommit() { + return { type: "input_audio_buffer.commit" } +} + +type PendingTurn = { turnId: string; itemId?: string; text?: string } + +export class OpenAITurnDecoder implements TurnDecoder { + private closed = false + private readonly turns: PendingTurn[] = [] + private readonly localIds = new Set() + private readonly itemIds = new Set() + private readonly earlyFinals = new Map() + private lastItemId: string | null = null + + beginTurn(turnId: string): void { + this.assertOpen() + try { + nonemptyString(turnId) + if (this.localIds.has(turnId)) throw new TranscriptionError("protocol") + if (this.turns.length >= MAX_PENDING_TURNS || this.localIds.size >= OPENAI_MAX_SOCKET_TURNS) { + throw new TranscriptionError("overflow") + } + this.localIds.add(turnId) + this.turns.push({ turnId }) + } catch (error) { + this.fail(error) + } + } + + accept(value: unknown): readonly FinalTurn[] { + this.assertOpen() + try { + const event = record(value) + const type = nonemptyString(event.type) + switch (type) { + case "error": + case "conversation.item.input_audio_transcription.failed": + throw new TranscriptionError("provider") + case "input_audio_buffer.committed": + return this.committed(event) + case "conversation.item.input_audio_transcription.completed": + return this.completed(event) + case "session.created": + case "session.updated": + case "transcription_session.created": + case "transcription_session.updated": + case "conversation.created": + case "conversation.item.created": + case "conversation.item.added": + case "conversation.item.done": + case "conversation.item.input_audio_transcription.delta": + case "conversation.item.input_audio_transcription.segment": + case "rate_limits.updated": + return [] + default: + throw new TranscriptionError("protocol") + } + } catch (error) { + return this.fail(error) + } + } + + close(): void { + this.closed = true + this.turns.length = 0 + this.localIds.clear() + this.itemIds.clear() + this.earlyFinals.clear() + this.lastItemId = null + } + + private committed(event: Record): readonly FinalTurn[] { + const itemId = nonemptyString(event.item_id) + const previous = event.previous_item_id + if (previous !== undefined && previous !== null) nonemptyString(previous) + if (this.itemIds.has(itemId)) return [] + // Manual commits are admitted FIFO. A conflicting predecessor makes that mapping unsafe. + if (previous !== undefined && previous !== this.lastItemId) throw new TranscriptionError("protocol") + const turn = this.turns.find((candidate) => candidate.itemId === undefined) + if (!turn) throw new TranscriptionError("protocol") + if (this.itemIds.size >= OPENAI_MAX_SOCKET_TURNS) throw new TranscriptionError("overflow") + turn.itemId = itemId + this.itemIds.add(itemId) + this.lastItemId = itemId + const early = this.earlyFinals.get(itemId) + if (early !== undefined) { + turn.text = early + this.earlyFinals.delete(itemId) + } + if (this.earlyFinals.size > this.turns.filter((candidate) => candidate.itemId === undefined).length) { + throw new TranscriptionError("protocol") + } + return this.drain() + } + + private completed(event: Record): readonly FinalTurn[] { + const itemId = nonemptyString(event.item_id) + if (event.content_index !== 0 || typeof event.transcript !== "string") { + throw new TranscriptionError("protocol") + } + if (event.transcript.length > MAX_TURN_TEXT_BYTES || Buffer.byteLength(event.transcript) > MAX_TURN_TEXT_BYTES) { + throw new TranscriptionError("overflow") + } + const turn = this.turns.find((candidate) => candidate.itemId === itemId) + if (turn) { + // A duplicate final cannot revise an already accepted final, even while blocked behind an earlier turn. + if (turn.text === undefined) turn.text = event.transcript + return this.drain() + } + if (this.itemIds.has(itemId) || this.earlyFinals.has(itemId)) return [] + const unacknowledged = this.turns.filter((candidate) => candidate.itemId === undefined).length + if (unacknowledged === 0) throw new TranscriptionError("protocol") + if (this.earlyFinals.size >= unacknowledged || this.earlyFinals.size >= MAX_PENDING_TURNS) { + throw new TranscriptionError("overflow") + } + this.earlyFinals.set(itemId, event.transcript) + return [] + } + + private drain(): readonly FinalTurn[] { + const result: FinalTurn[] = [] + while (this.turns[0]?.text !== undefined) { + const turn = this.turns.shift() + if (!turn || turn.text === undefined) throw new TranscriptionError("protocol") + const text = turn.text.trim() + result.push({ turnId: turn.turnId, text }) + } + return result + } + + private assertOpen(): void { + if (this.closed) throw new TranscriptionError("stopped") + } + + private fail(error: unknown): never { + this.close() + throw error instanceof TranscriptionError ? error : new TranscriptionError("protocol") + } +} diff --git a/server/packages/grid-transcription/src/providers/soniox.test.ts b/server/packages/grid-transcription/src/providers/soniox.test.ts new file mode 100644 index 000000000..aac984080 --- /dev/null +++ b/server/packages/grid-transcription/src/providers/soniox.test.ts @@ -0,0 +1,142 @@ +import { describe, expect, test } from "bun:test" +import { MAX_PENDING_TURNS, MAX_TURN_TEXT_BYTES, TranscriptionError } from "../protocol.js" +import { + SONIOX_MAX_SOCKET_TURNS, + SonioxTurnDecoder, + sonioxAudio, + sonioxConfiguration, + sonioxFinalize, + sonioxKeepalive, +} from "./soniox.js" + +// Public token contract: https://soniox.com/docs/stt/rt/real-time-transcription +const token = (text: string, is_final = true) => ({ text, is_final }) +const response = (...tokens: Readonly<{ text: string; is_final: boolean }>[]) => ({ tokens }) + +function expectFailure(action: () => unknown, code: TranscriptionError["code"]) { + let caught: unknown + try { + action() + } catch (error) { + caught = error + } + expect(caught).toBeInstanceOf(TranscriptionError) + if (!(caught instanceof TranscriptionError)) throw new Error("Expected safe transcription failure") + expect(caught.code).toBe(code) + expect(caught.message).toBe(`Grid transcription ${code}`) +} + +describe("Soniox manually finalized wire", () => { + test("uses automatic languages, raw PCM16k and application boundaries", () => { + expect(sonioxConfiguration("test-key")).toEqual({ + api_key: "test-key", + model: "stt-rt-v5", + audio_format: "pcm_s16le", + sample_rate: 16_000, + num_channels: 1, + enable_speaker_diarization: false, + enable_endpoint_detection: false, + }) + expect(sonioxFinalize()).toEqual({ type: "finalize" }) + expect(sonioxKeepalive()).toEqual({ type: "keepalive" }) + const pcm = Uint8Array.of(0, 128, 255, 127) + const sent = sonioxAudio(pcm) + pcm.fill(0) + expect(sent).toEqual(Uint8Array.of(0, 128, 255, 127)) + expectFailure(() => sonioxAudio(Uint8Array.of(1)), "audio") + expectFailure(() => sonioxAudio(new Uint8Array()), "audio") + expectFailure(() => sonioxAudio(new Uint8Array(65_538)), "audio") + }) + + test("ignores revised provisional text and emits stable tokens only on the manual marker", () => { + const decoder = new SonioxTurnDecoder() + expect(decoder.accept(response(token("How're", false)))).toEqual([]) + expect(decoder.accept(response(token("How", false), token(" are", false)))).toEqual([]) + // The provider may finalize tokens before the application sends its manual finalize command. + expect(decoder.accept(response(token("How"), token(" are", false)))).toEqual([]) + decoder.beginTurn("local_1") + expect(decoder.accept(response(token(" are"), token(" you", false)))).toEqual([]) + expect(decoder.accept(response(token(" you"), token(" doing?"), token("")))).toEqual([ + { turnId: "local_1", text: "How are you doing?" }, + ]) + }) + + test("multiple finalization markers partition one event in admitted FIFO order", () => { + const decoder = new SonioxTurnDecoder() + decoder.beginTurn("one") + decoder.beginTurn("two") + expect(decoder.accept(response( + token("first"), token(""), token("دوم"), token(""), + ))).toEqual([ + { turnId: "one", text: "first" }, + { turnId: "two", text: "دوم" }, + ]) + expectFailure(() => decoder.beginTurn("one"), "protocol") + }) + + test("rejects unsolicited markers and semantic endpoints that contradict manual configuration", () => { + for (const marker of ["", ""]) { + const decoder = new SonioxTurnDecoder() + expectFailure(() => decoder.accept(response(token(marker))), "protocol") + expectFailure(() => decoder.beginTurn("later"), "stopped") + } + }) + + test("safe provider failures expose no error detail or buffered transcript", () => { + const decoder = new SonioxTurnDecoder() + decoder.accept(response(token("private transcript"))) + expectFailure(() => decoder.accept({ + tokens: [], error_code: 503, error_type: "service_unavailable", error_message: "SECRET detail", + }), "provider") + expectFailure(() => decoder.accept(response(token(""))), "stopped") + }) + + test("validates malformed input and discards all state on failure", () => { + for (const event of [ + null, [], {}, { tokens: "text" }, { tokens: [null] }, + { tokens: [{ text: "text", is_final: 1 }] }, { tokens: [{ text: 42, is_final: true }] }, + { tokens: [], finished: "true" }, + ]) { + const decoder = new SonioxTurnDecoder() + expectFailure(() => decoder.accept(event), "protocol") + expectFailure(() => decoder.accept(response()), "stopped") + } + }) + + test("bounds pending turns, cumulative UTF8 text, token arrays and socket ID retention", () => { + const pending = new SonioxTurnDecoder() + for (let index = 0; index < MAX_PENDING_TURNS; index++) pending.beginTurn(`turn_${index}`) + expectFailure(() => pending.beginTurn("overflow"), "overflow") + const text = new SonioxTurnDecoder() + text.accept(response(token("a".repeat(MAX_TURN_TEXT_BYTES - 2)))) + expectFailure(() => text.accept(response(token("界"))), "overflow") + const tokens = new SonioxTurnDecoder() + expectFailure(() => tokens.accept({ tokens: Array.from({ length: MAX_TURN_TEXT_BYTES + 1 }, () => token("")) }), "overflow") + const history = new SonioxTurnDecoder() + for (let index = 0; index < SONIOX_MAX_SOCKET_TURNS; index++) { + history.beginTurn(`local_${index}`) + history.accept(response(token("text"), token(""))) + } + expectFailure(() => history.beginTurn("history_overflow"), "overflow") + }) + + test("emits empty terminal turns and accepts clean end-of-stream only after the final flush", () => { + const decoder = new SonioxTurnDecoder() + decoder.beginTurn("silence") + expect(decoder.accept(response(token(" \n"), token("")))).toEqual([{ turnId: "silence", text: "" }]) + expect(decoder.accept({ tokens: [], final_audio_proc_ms: 1560, total_audio_proc_ms: 1680, finished: true })).toEqual([]) + expectFailure(() => decoder.accept(response(token("late"))), "stopped") + const pending = new SonioxTurnDecoder() + pending.beginTurn("missing_flush") + expectFailure(() => pending.accept({ tokens: [], finished: true }), "protocol") + }) + + test("explicit close is idempotent and fences delayed provider output", () => { + const decoder = new SonioxTurnDecoder() + decoder.accept(response(token("buffered"))) + decoder.close() + decoder.close() + expectFailure(() => decoder.beginTurn("late"), "stopped") + expectFailure(() => decoder.accept(response(token(""))), "stopped") + }) +}) diff --git a/server/packages/grid-transcription/src/providers/soniox.ts b/server/packages/grid-transcription/src/providers/soniox.ts new file mode 100644 index 000000000..e919af6e6 --- /dev/null +++ b/server/packages/grid-transcription/src/providers/soniox.ts @@ -0,0 +1,133 @@ +import { Buffer } from "node:buffer" +import { + MAX_PENDING_TURNS, + MAX_TURN_TEXT_BYTES, + TranscriptionError, + nonemptyString, + record, + type FinalTurn, + type TurnDecoder, +} from "../protocol.js" + +export const SONIOX_URL = "wss://stt-rt.soniox.com/transcribe-websocket" +export const SONIOX_SAMPLE_RATE = 16_000 +export const SONIOX_MAX_SOCKET_TURNS = 1024 +const MAX_AUDIO_FRAME_BYTES = 64 * 1024 + +// https://soniox.com/docs/api-reference/stt/websocket-api +// This initial wire message contains a secret; the transport must never log it. +export function sonioxConfiguration(apiKey: string) { + nonemptyString(apiKey) + return { + api_key: apiKey, + model: "stt-rt-v5", + audio_format: "pcm_s16le", + sample_rate: SONIOX_SAMPLE_RATE, + num_channels: 1, + enable_speaker_diarization: false, + enable_endpoint_detection: false, + } +} + +export function sonioxAudio(pcm: Uint8Array): Uint8Array { + if (pcm.byteLength === 0 || pcm.byteLength % 2 !== 0 || pcm.byteLength > MAX_AUDIO_FRAME_BYTES) { + throw new TranscriptionError("audio") + } + return pcm.slice() +} + +export function sonioxFinalize() { + return { type: "finalize" } +} + +export function sonioxKeepalive() { + return { type: "keepalive" } +} + +export class SonioxTurnDecoder implements TurnDecoder { + private closed = false + private readonly turns: string[] = [] + private readonly localIds = new Set() + private text = "" + private textBytes = 0 + + beginTurn(turnId: string): void { + this.assertOpen() + try { + nonemptyString(turnId) + if (this.localIds.has(turnId)) throw new TranscriptionError("protocol") + if (this.turns.length >= MAX_PENDING_TURNS || this.localIds.size >= SONIOX_MAX_SOCKET_TURNS) { + throw new TranscriptionError("overflow") + } + this.localIds.add(turnId) + this.turns.push(turnId) + } catch (error) { + this.fail(error) + } + } + + // Final tokens can precede beginTurn: admission must occur immediately before sending finalize. + // The provider promises ordered, unrepeated final tokens; real socket qualification remains required. + accept(value: unknown): readonly FinalTurn[] { + this.assertOpen() + try { + const event = record(value) + if (event.error_code !== undefined || event.error_type !== undefined) { + throw new TranscriptionError("provider") + } + if (!Array.isArray(event.tokens) || event.tokens.length > MAX_TURN_TEXT_BYTES) { + throw new TranscriptionError(Array.isArray(event.tokens) ? "overflow" : "protocol") + } + if (event.finished !== undefined && typeof event.finished !== "boolean") { + throw new TranscriptionError("protocol") + } + const result: FinalTurn[] = [] + for (const value of event.tokens) { + const token = record(value) + if (typeof token.is_final !== "boolean" || typeof token.text !== "string") { + throw new TranscriptionError("protocol") + } + if (!token.is_final) continue + if (token.text === "") throw new TranscriptionError("protocol") + if (token.text === "") { + const turnId = this.turns.shift() + if (turnId === undefined) throw new TranscriptionError("protocol") + const text = this.text.trim() + result.push({ turnId, text }) + this.text = "" + this.textBytes = 0 + continue + } + if (token.text.length > MAX_TURN_TEXT_BYTES) throw new TranscriptionError("overflow") + const bytes = Buffer.byteLength(token.text) + if (this.textBytes + bytes > MAX_TURN_TEXT_BYTES) throw new TranscriptionError("overflow") + this.text += token.text + this.textBytes += bytes + } + if (event.finished === true) { + if (this.turns.length > 0 || this.textBytes > 0) throw new TranscriptionError("protocol") + this.close() + } + return result + } catch (error) { + return this.fail(error) + } + } + + close(): void { + this.closed = true + this.turns.length = 0 + this.localIds.clear() + this.text = "" + this.textBytes = 0 + } + + private assertOpen(): void { + if (this.closed) throw new TranscriptionError("stopped") + } + + private fail(error: unknown): never { + this.close() + throw error instanceof TranscriptionError ? error : new TranscriptionError("protocol") + } +} diff --git a/server/packages/grid-transcription/src/qualify.ts b/server/packages/grid-transcription/src/qualify.ts new file mode 100644 index 000000000..3d53efc49 --- /dev/null +++ b/server/packages/grid-transcription/src/qualify.ts @@ -0,0 +1,74 @@ +import { readFile, realpath, stat, writeFile } from "node:fs/promises" +import { basename } from "node:path" +import { setTimeout as pause } from "node:timers/promises" +import { ProviderConnection } from "./socket.js" +import { TranscriptionError, type FinalTurn } from "./protocol.js" + +// Controlled short-file smoke only. No RTC connection, Inline posting, VAD or automatic dotenv load. +async function main(): Promise { + const args = process.argv.slice(2) + if (args.length === 1 && args[0] === "--help") { + process.stdout.write("qualify --model standard|meeting --pcm audio.pcm [--out fresh-result.json]\nPCM must be signed16LE mono at 24k Standard / 16k Meeting, 100ms–12s.\n") + return + } + if (args.length !== 4 && args.length !== 6) throw new TranscriptionError("protocol") + const flags = new Map() + for (let i = 0; i < args.length; i += 2) { + const name = args[i]! + const value = args[i + 1]! + if (!["--model", "--pcm", "--out"].includes(name) || flags.has(name)) throw new TranscriptionError("protocol") + flags.set(name, value) + } + const model = flags.get("--model") + if (model !== "standard" && model !== "meeting") throw new TranscriptionError("protocol") + const out = flags.get("--out") + if (out && (basename(out).startsWith(".env") || !out.endsWith(".json"))) throw new TranscriptionError("protocol") + const pcmPath = flags.get("--pcm") + if (!pcmPath || basename(pcmPath).startsWith(".env") || !pcmPath.endsWith(".pcm")) throw new TranscriptionError("audio") + const resolved = await realpath(pcmPath) + if (basename(resolved).startsWith(".env") || !resolved.endsWith(".pcm")) throw new TranscriptionError("audio") + const sampleRate = model === "standard" ? 24_000 : 16_000 + const size = (await stat(resolved)).size + if (size % 2 !== 0 || size < sampleRate * 2 / 10 || size > sampleRate * 2 * 12) throw new TranscriptionError("audio") + const bytes = await readFile(resolved) + // Recheck after the read in case the caller replaced/resized the lab file. + if (bytes.length !== size) throw new TranscriptionError("audio") + const samples = new Int16Array(bytes.length / 2) + for (let i = 0; i < samples.length; i++) samples[i] = bytes.readInt16LE(i * 2) + const key = process.env[model === "standard" ? "GRID_TRANSCRIPTION_OPENAI_API_KEY" : "GRID_TRANSCRIPTION_SONIOX_API_KEY"] + if (!key) throw new TranscriptionError("provider") + const deadline = performance.now() + 40_000 + let resolveFinal!: (turn: FinalTurn) => void + let rejectFinal!: (error: Error) => void + const final = new Promise((resolve, reject) => { resolveFinal = resolve; rejectFinal = reject }) + // A connection failure may arrive before the caller starts awaiting the final. + void final.catch(() => {}) + let connection: ProviderConnection | undefined + let timer: ReturnType | undefined + try { + connection = await ProviderConnection.connect({ model, apiKey: key, + assertAuthority: () => { if (performance.now() >= deadline) throw new TranscriptionError("expired") }, + onFinal: resolveFinal, onFailure: rejectFinal }) + const frameSamples = sampleRate / 50 + for (let offset = 0; offset < samples.length; offset += frameSamples) { + connection.appendAudio(samples.subarray(offset, offset + frameSamples)) + await pause(20) + } + const committedAt = performance.now() + connection.commitTurn("controlled-file") + timer = setTimeout(() => { connection?.close(); rejectFinal(new TranscriptionError("provider")) }, 15_000) + const turn = await final + if (out) { + // Explicit optional local lab artifact only; never overwrite an existing file or log text. + await writeFile(out, JSON.stringify({ model, text: turn.text }, null, 2) + "\n", { flag: "wx", mode: 0o600 }) + } + process.stdout.write(JSON.stringify({ ok: true, model, audioMs: samples.length * 1000 / sampleRate, + finalizeMs: Math.round(performance.now() - committedAt), characters: turn.text.length }) + "\n") + } finally { clearTimeout(timer); connection?.close() } +} + +void main().catch((error: unknown) => { + const code = error instanceof TranscriptionError ? error.code : "qualification" + process.stderr.write(JSON.stringify({ ok: false, error: code }) + "\n") + process.exitCode = 1 +}) diff --git a/server/packages/grid-transcription/src/room-child.ts b/server/packages/grid-transcription/src/room-child.ts new file mode 100644 index 000000000..60c94ca2a --- /dev/null +++ b/server/packages/grid-transcription/src/room-child.ts @@ -0,0 +1,61 @@ +import { fileURLToPath } from "node:url" +import { claimedRun, participants } from "./api.js" +import { TranscriptionError, record } from "./protocol.js" +import { RoomWorker } from "./room-worker.js" +import { monotonicMilliseconds, sameIdentity, validIdentity, validStopReason, type RoomChildMessage } from "./watchdog.js" + +export function runRoomChild(): void { + let worker: RoomWorker | undefined + let started = false + let stopping = false + const send = (message: RoomChildMessage): void => { + if (!process.connected || !process.send) return + try { process.send(message, () => undefined) } catch { /* Watchdog owns final process containment. */ } + } + const stop = (graceful: boolean): void => { + stopping = true + if (!worker) { process.exit(0); return } + void worker.stop(graceful).then(() => { + send({ type: "stopped" }) + process.exit(0) + }).catch(() => { send({ type: "error", code: "native" }); process.exit(1) }) + } + const fail = (error: unknown): void => { + send({ type: "error", code: error instanceof TranscriptionError ? error.code : "internal" }) + stop(false) + } + process.on("message", (value: unknown) => { + try { + const message = record(value) + if (message.type === "stop" && validStopReason(message.reason)) { stop(message.reason === "stopping"); return } + if (stopping) return + if (message.type === "start" && !started && validIdentity(message.identity)) { + started = true + setInterval(() => send({ type: "heartbeat", sentAtMs: monotonicMilliseconds() }), 250) + const payload = record(message.payload) + const run = claimedRun(payload.run) + if (!sameIdentity(message.identity, { runId: run.runId, claimEpoch: run.claimEpoch, generation: run.generation }) + || typeof message.requestedAtMs !== "number" || typeof message.leaseMs !== "number" + || typeof payload.expiresAtMs !== "number" || typeof payload.apiKey !== "string" || !payload.apiKey + || payload.apiKey.length > 4096 || typeof payload.apiBaseUrl !== "string" || !payload.apiBaseUrl) { + throw new TranscriptionError("protocol") + } + if (payload.apiTimeoutMs !== undefined && typeof payload.apiTimeoutMs !== "number") throw new TranscriptionError("protocol") + worker = new RoomWorker({ run, identity: message.identity, requestedAtMs: message.requestedAtMs, + leaseMs: message.leaseMs, expiresAtMs: payload.expiresAtMs, apiBaseUrl: payload.apiBaseUrl, apiKey: payload.apiKey, + apiTimeoutMs: payload.apiTimeoutMs, onFailure: fail }) + void worker.start().then(() => { if (!stopping) send({ type: "ready" }) }).catch((error: unknown) => { + if (!stopping) fail(error) + }) + } else if (message.type === "renew" && worker && validIdentity(message.identity)) { + if (typeof message.requestedAtMs !== "number" || typeof message.leaseMs !== "number") throw new TranscriptionError("protocol") + const payload = record(message.payload) + worker.renew(message.identity, message.requestedAtMs, message.leaseMs, participants(payload.participants)) + } else throw new TranscriptionError("protocol") + } catch (error) { fail(error) } + }) + process.on("disconnect", () => stop(false)) + process.on("SIGTERM", () => stop(false)) + process.on("SIGINT", () => stop(false)) +} +if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) runRoomChild() diff --git a/server/packages/grid-transcription/src/room-worker.ts b/server/packages/grid-transcription/src/room-worker.ts new file mode 100644 index 000000000..d3f72e990 --- /dev/null +++ b/server/packages/grid-transcription/src/room-worker.ts @@ -0,0 +1,225 @@ +import { AudioStream, Room, RoomEvent, TrackKind, TrackSource, dispose, + type AudioFrame, type RemoteParticipant, type RemoteTrackPublication, type RemoteTrack } from "@livekit/rtc-node" +import { CaptureAuthority, type CaptureIdentity, type MicrophoneGrant } from "./authority.js" +import { SPEAKER_LIMIT, RunControlClient, type ClaimedRun, type ParticipantGrant } from "./api.js" +import { TranscriptionError } from "./protocol.js" +import { SileroModel } from "./silero.js" +import { TrackWorker, type SpeechConnection } from "./track-worker.js" +import type { ProviderOptions } from "./socket.js" +import { monotonicMilliseconds } from "./watchdog.js" + +type OwnedMicrophone = { + participant: RemoteParticipant; publication: RemoteTrackPublication; track: RemoteTrack; + grant: MicrophoneGrant; worker: TrackWorker; reader: ReadableStreamDefaultReader; + reading: Promise; retirement?: Promise; +} +export type RoomWorkerOptions = Readonly<{ + run: ClaimedRun; identity: CaptureIdentity; requestedAtMs: number; leaseMs: number; expiresAtMs: number; + apiBaseUrl: string; apiKey: string; apiTimeoutMs?: number; + onFailure: (error: TranscriptionError) => void; + // Trusted local qualification injection; the daemon never accepts endpoints over IPC. + connectProvider?: (options: ProviderOptions) => Promise; +}> + +// One native process owns one room. An uncancellable native connect/disconnect is +// contained by the independent watchdog; it is never claimed stopped by a raced timeout. +export class RoomWorker { + private readonly room = new Room() + private readonly authority: CaptureAuthority + private readonly api: RunControlClient + private manifest = new Map() + private readonly microphones = new Map() + private model: SileroModel | undefined + private accepting = true + private stopped = false + private connected: Promise | undefined + private stopPromise: Promise | undefined + + constructor(private readonly options: RoomWorkerOptions) { + this.authority = new CaptureAuthority({ + identity: options.identity, requestedAtMs: options.requestedAtMs, leaseMs: options.leaseMs, + expiresAtMs: options.expiresAtMs, microphones: [], now: monotonicMilliseconds, + }) + this.api = new RunControlClient(options.apiBaseUrl, options.run.runToken, options.apiTimeoutMs) + this.replaceManifest(options.run.participants) + // These handlers must exist before connect can deliver its buffered room events. + this.room.on(RoomEvent.ParticipantConnected, () => this.reconcileSafely()) + this.room.on(RoomEvent.ParticipantDisconnected, () => this.reconcileSafely()) + this.room.on(RoomEvent.TrackPublished, () => this.reconcileSafely()) + this.room.on(RoomEvent.TrackUnpublished, () => this.reconcileSafely()) + this.room.on(RoomEvent.TrackMuted, () => this.reconcileSafely()) + this.room.on(RoomEvent.TrackUnmuted, () => this.reconcileSafely()) + this.room.on(RoomEvent.TrackSubscribed, (track, publication, participant) => { + if (!this.accepting) { publication.setSubscribed(false); return } + try { this.reconcile(); this.attach(track, publication, participant) } catch (error) { this.fail(error) } + }) + this.room.on(RoomEvent.TrackUnsubscribed, (_track, publication) => { + const owned = publication.sid ? this.microphones.get(publication.sid) : undefined + if (owned) this.retire(owned) + }) + this.room.on(RoomEvent.TrackSubscriptionFailed, () => this.fail(new TranscriptionError("audio"))) + // v1 ends capture on a media gap. Voice remains independently owned by clients. + this.room.on(RoomEvent.Reconnecting, () => this.fail(new TranscriptionError("audio"))) + this.room.on(RoomEvent.Disconnected, () => { if (this.accepting) this.fail(new TranscriptionError("audio")) }) + } + + async start(): Promise { + this.authority.assertCurrent(this.options.identity) + const model = await SileroModel.load() + if (!this.accepting) { await model.close(); throw new TranscriptionError("stopped") } + this.model = model + this.authority.assertCurrent(this.options.identity) + this.connected = this.room.connect(this.options.run.livekit.serverUrl, this.options.run.livekit.token, { + autoSubscribe: false, dynacast: false, dataStream: { maxPayloadByteLength: 1024 }, + }) + try { await this.connected } catch { throw new TranscriptionError("audio") } + if (!this.accepting) throw new TranscriptionError("stopped") + this.authority.assertCurrent(this.options.identity) + // Events and this sweep cover both already-published and subsequently-published microphones. + this.reconcile() + } + + renew(identity: CaptureIdentity, requestedAtMs: number, leaseMs: number, manifest: readonly ParticipantGrant[]): void { + if (!this.accepting) throw new TranscriptionError("stopped") + this.authority.renew(identity, requestedAtMs, leaseMs) + this.replaceManifest(manifest) + this.reconcile() + } + private replaceManifest(participants: readonly ParticipantGrant[]): void { + this.manifest = new Map(participants.map((participant) => [participant.identity, participant])) + } + private eligible(publication: RemoteTrackPublication, participant: RemoteParticipant): MicrophoneGrant | undefined { + this.authority.assertCurrent(this.options.identity) + const membership = this.manifest.get(participant.identity) + const sid = publication.sid + if (!membership || !sid || this.room.remoteParticipants.get(participant.identity) !== participant + || participant.trackPublications.get(sid) !== publication || publication.kind !== TrackKind.KIND_AUDIO + || publication.source !== TrackSource.SOURCE_MICROPHONE || publication.muted !== false) return undefined + return { identity: membership.identity, trackSid: sid, speakerUserId: membership.userId, membershipId: membership.membershipId } + } + private reconcileSafely(): void { + if (!this.accepting) return + try { this.reconcile() } catch (error) { this.fail(error) } + } + private reconcile(): void { + if (!this.accepting) return + const publications: { participant: RemoteParticipant; publication: RemoteTrackPublication; grant: MicrophoneGrant }[] = [] + for (const participant of this.room.remoteParticipants.values()) { + for (const publication of participant.trackPublications.values()) { + const grant = this.eligible(publication, participant) + if (grant) publications.push({ participant, publication, grant }) + else if (publication.subscribed) publication.setSubscribed(false) + } + } + if (publications.length > SPEAKER_LIMIT) throw new TranscriptionError("overflow") + this.authority.replaceMicrophones(this.options.identity, publications.map((entry) => entry.grant)) + for (const owned of this.microphones.values()) { + if (!this.isCurrent(owned)) this.retire(owned) + } + for (const entry of publications) { + if (!entry.publication.subscribed) entry.publication.setSubscribed(true) + if (entry.publication.track) this.attach(entry.publication.track, entry.publication, entry.participant) + } + } + private isCurrent(owned: OwnedMicrophone): boolean { + const grant = this.eligible(owned.publication, owned.participant) + return !owned.retirement && !!grant && grant.membershipId === owned.grant.membershipId + && grant.speakerUserId === owned.grant.speakerUserId && owned.publication.track === owned.track + } + private attach(track: RemoteTrack, publication: RemoteTrackPublication, participant: RemoteParticipant): void { + if (!this.accepting || !this.model) return + const grant = this.eligible(publication, participant) + if (!grant || publication.track !== track) { publication.setSubscribed(false); return } + const existing = this.microphones.get(grant.trackSid) + if (existing) { + if (existing.track !== track || existing.publication !== publication) throw new TranscriptionError("protocol") + return + } + // Retiring tracks count until their provider work is drained, bounding track churn too. + if (this.microphones.size >= SPEAKER_LIMIT) throw new TranscriptionError("overflow") + const sampleRate = this.options.run.model === "meeting" ? 16_000 : 24_000 + const reader = new AudioStream(track, { sampleRate, numChannels: 1, frameSizeMs: 20 }).getReader() + let owned: OwnedMicrophone | undefined + const worker = new TrackWorker({ + model: this.options.run.model, apiKey: this.options.apiKey, + participantIdentity: grant.identity, trackSid: grant.trackSid, api: this.api, + detector: this.model.detector(sampleRate), + ...(this.options.connectProvider ? { connect: this.options.connectProvider } : {}), + assertRunAuthority: () => this.authority.assertCurrent(this.options.identity), + assertMicrophone: () => { + const current = this.eligible(publication, participant) + if (!this.accepting || owned?.retirement || !current || publication.track !== track + || current.membershipId !== grant.membershipId || current.speakerUserId !== grant.speakerUserId) { + throw new TranscriptionError("stopped") + } + const verified = this.authority.microphone(this.options.identity, { + identity: participant.identity, trackSid: grant.trackSid, kind: "audio", source: "microphone", + }) + if (!verified || verified.membershipId !== grant.membershipId) throw new TranscriptionError("stopped") + }, + onFailure: (error) => this.fail(error), + }) + owned = { participant, publication, track, grant, worker, reader, reading: Promise.resolve() } + this.microphones.set(grant.trackSid, owned) + owned.reading = this.read(owned) + } + private async read(owned: OwnedMicrophone): Promise { + try { + while (this.accepting && !owned.retirement) { + const result = await owned.reader.read() + if (result.done) { if (this.accepting && !owned.retirement) this.retire(owned); return } + if (!this.accepting || owned.retirement || !this.isCurrent(owned)) return + if (result.value.sampleRate !== owned.worker.sampleRate || result.value.channels !== 1 + || result.value.data.length !== result.value.samplesPerChannel) throw new TranscriptionError("audio") + // No await between this authority check and the owned bounded queue write. + owned.worker.push(result.value.data) + } + } catch (error) { if (this.accepting && !owned.retirement) this.fail(error) } + finally { try { owned.reader.releaseLock() } catch { /* Pending cancellation owns the lock until it completes. */ } } + } + private retire(owned: OwnedMicrophone): void { + if (owned.retirement) return + this.authority.revoke(owned.grant.trackSid) + owned.publication.setSubscribed(false) + const flushing = owned.worker.stop(true) + const cancellation = owned.reader.cancel().catch(() => undefined) + owned.retirement = Promise.all([flushing, cancellation, owned.reading]).then(() => { + if (this.microphones.get(owned.grant.trackSid) === owned) this.microphones.delete(owned.grant.trackSid) + }).catch((error: unknown) => this.fail(error)) + } + private fail(error: unknown): void { + if (!this.accepting) return + // Close every queue immediately; teardown continues only in the native child. + void this.stop(false).catch(() => undefined) + this.options.onFailure(error instanceof TranscriptionError ? error : new TranscriptionError("audio")) + } + stop(graceful: boolean): Promise { + this.accepting = false + if (!graceful) { + this.authority.stop() + for (const owned of this.microphones.values()) void owned.worker.stop(false) + } + if (this.stopPromise) return this.stopPromise + const closing: Promise[] = [] + for (const owned of this.microphones.values()) { + owned.publication.setSubscribed(false) + closing.push(owned.worker.stop(graceful), owned.reader.cancel().catch(() => undefined), owned.reading) + } + this.stopPromise = this.finishStop(closing) + return this.stopPromise + } + private async finishStop(closing: readonly Promise[]): Promise { + // Waiting for the real native operation is intentional. Watchdog escalation + // kills this process if native teardown or a pending connect never settles. + await Promise.allSettled(closing) + if (this.connected) { try { await this.connected } catch { /* Failed connect has no connected transport. */ } } + await this.room.disconnect() + this.room.removeAllListeners() + await this.model?.close() + this.authority.stop() + this.microphones.clear() + dispose() + this.stopped = true + } + get isStopped(): boolean { return this.stopped } +} diff --git a/server/packages/grid-transcription/src/segmenter.test.ts b/server/packages/grid-transcription/src/segmenter.test.ts new file mode 100644 index 000000000..7970b7977 --- /dev/null +++ b/server/packages/grid-transcription/src/segmenter.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, test } from "bun:test" +import { SpeechSegmenter, type SpeechAction } from "./segmenter.js" + +function frame(rate: number, id: number, milliseconds = 20): Int16Array { + return new Int16Array(rate * milliseconds / 1000).fill(id) +} +function emittedFrames(actions: readonly SpeechAction[]): Int16Array[] { + return actions.flatMap((action) => action.kind === "start" ? [...action.frames] + : action.kind === "audio" ? [action.frame] : []) +} + +describe("bounded speech segmentation", () => { + for (const rate of [16_000, 24_000]) { + test(`${rate}Hz silence retains only 300ms of pre-roll and threshold starts one turn`, () => { + const segmenter = new SpeechSegmenter(rate) + for (let id = 1; id <= 1000; id++) expect(segmenter.accept(frame(rate, id), 0.49)).toEqual([]) + const actions = segmenter.accept(frame(rate, 1001), 0.5) + expect(actions.map((action) => action.kind)).toEqual(["start"]) + const audio = emittedFrames(actions) + expect(audio.reduce((sum, value) => sum + value.length, 0)).toBe(rate * 0.3) + expect(audio.map((value) => value[0])).toEqual(Array.from({ length: 15 }, (_, index) => 987 + index)) + }) + + test(`${rate}Hz trailing silence ends at its frame boundary and speech resets the silence count`, () => { + const segmenter = new SpeechSegmenter(rate) + segmenter.accept(frame(rate, 1), 1) + for (let id = 2; id <= 28; id++) expect(segmenter.accept(frame(rate, id), 0).map((action) => action.kind)).toEqual(["audio"]) + expect(segmenter.accept(frame(rate, 29), 1).map((action) => action.kind)).toEqual(["audio"]) + for (let id = 30; id <= 56; id++) expect(segmenter.accept(frame(rate, id), 0).map((action) => action.kind)).toEqual(["audio"]) + expect(segmenter.accept(frame(rate, 57), 0).map((action) => action.kind)).toEqual(["audio", "end"]) + expect(segmenter.accept(frame(rate, 58), 0)).toEqual([]) + expect(segmenter.stop()).toBe(false) + }) + + test(`${rate}Hz a soft turn waits for 200ms of silence`, () => { + const segmenter = new SpeechSegmenter(rate) + for (let id = 1; id <= 300; id++) expect(segmenter.accept(frame(rate, id), 1).some((action) => action.kind === "end")).toBe(false) + for (let id = 301; id <= 309; id++) expect(segmenter.accept(frame(rate, id), 0).map((action) => action.kind)).toEqual(["audio"]) + expect(segmenter.accept(frame(rate, 310), 0).map((action) => action.kind)).toEqual(["audio", "end"]) + }) + + test(`${rate}Hz continuous speech splits at 10 seconds with every captured frame emitted once`, () => { + const segmenter = new SpeechSegmenter(rate) + const ids: number[] = [] + const lengths: number[] = [] + let turnSamples = 0 + let starts = 0 + for (let id = 1; id <= 1100; id++) { + const actions = segmenter.accept(frame(rate, id), 1) + starts += actions.filter((action) => action.kind === "start").length + for (const audio of emittedFrames(actions)) { ids.push(audio[0]!); turnSamples += audio.length } + if (actions.some((action) => action.kind === "end")) { lengths.push(turnSamples); turnSamples = 0 } + } + expect(ids).toEqual(Array.from({ length: 1100 }, (_, index) => index + 1)) + expect(starts).toBe(3) + expect(lengths).toEqual([rate * 10, rate * 10]) + expect(turnSamples).toBe(rate * 2) + expect(segmenter.stop()).toBe(true) + expect(segmenter.stop()).toBe(false) + expect(() => segmenter.accept(frame(rate, 1101), 1)).toThrow("Grid transcription stopped") + }) + + test(`${rate}Hz variable frames overshoot the hard boundary by less than one bounded frame`, () => { + const segmenter = new SpeechSegmenter(rate) + let samples = 0 + let ended = false + while (!ended) { + const actions = segmenter.accept(new Int16Array(511), 1) + samples += emittedFrames(actions).reduce((sum, value) => sum + value.length, 0) + ended = actions.some((action) => action.kind === "end") + } + expect(samples).toBeGreaterThanOrEqual(rate * 10) + expect(samples).toBeLessThan(rate * 10 + 511) + }) + } + + test("pre-roll owns samples and settings cannot be changed after construction", () => { + const settings = { prerollMs: 300, silenceMs: 550, softTurnMs: 6000, hardTurnMs: 10_000, threshold: 0.5 } + const segmenter = new SpeechSegmenter(16_000, settings) + const reusable = frame(16_000, 7) + segmenter.accept(reusable, 0) + reusable.fill(9) + settings.threshold = 0 + expect(segmenter.accept(reusable, 0)).toEqual([]) + const audio = emittedFrames(segmenter.accept(frame(16_000, 11), 1)) + expect(audio.map((value) => value[0])).toEqual([7, 9, 11]) + }) + + test("validates rates, setting bounds, frames and probabilities", () => { + expect(() => new SpeechSegmenter(44_100)).toThrow("Grid transcription audio") + const valid = { prerollMs: 300, silenceMs: 550, softTurnMs: 6000, hardTurnMs: 10_000, threshold: 0.5 } + for (const settings of [{ ...valid, prerollMs: Infinity }, { ...valid, prerollMs: 301 }, + { ...valid, silenceMs: 0 }, { ...valid, softTurnMs: 10_001 }, { ...valid, hardTurnMs: 10_001 }, + { ...valid, threshold: -0.1 }, { ...valid, threshold: 1.1 }]) { + expect(() => new SpeechSegmenter(16_000, settings)).toThrow("Grid transcription audio") + } + const segmenter = new SpeechSegmenter(16_000) + for (const probability of [-1, 1.1, NaN, Infinity]) { + expect(() => segmenter.accept(frame(16_000, 1), probability)).toThrow("Grid transcription audio") + } + expect(() => segmenter.accept(new Int16Array(), 0)).toThrow("Grid transcription audio") + expect(() => segmenter.accept(new Int16Array(1601), 0)).toThrow("Grid transcription audio") + expect(segmenter.stop()).toBe(false) + }) +}) diff --git a/server/packages/grid-transcription/src/segmenter.ts b/server/packages/grid-transcription/src/segmenter.ts new file mode 100644 index 000000000..9968bd8f2 --- /dev/null +++ b/server/packages/grid-transcription/src/segmenter.ts @@ -0,0 +1,73 @@ +import { TranscriptionError } from "./protocol.js" + +export type SpeechAction = Readonly<{ kind: "start"; frames: readonly Int16Array[] }> + | Readonly<{ kind: "audio"; frame: Int16Array }> + | Readonly<{ kind: "end" }> + +// Only finite preroll is retained. Continuous speech is split without repeating captured samples. +export class SpeechSegmenter { + private readonly settings: Readonly<{ prerollMs: number; silenceMs: number; softTurnMs: number; hardTurnMs: number; threshold: number }> + private preroll: Int16Array[] = [] + private prerollSamples = 0 + private active = false + private turnSamples = 0 + private silenceSamples = 0 + private closed = false + + constructor(readonly sampleRate: number, settings = { + prerollMs: 300, silenceMs: 550, softTurnMs: 6000, hardTurnMs: 10_000, threshold: 0.5, + }) { + if (![16_000, 24_000].includes(sampleRate) || !Object.values(settings).every(Number.isFinite) + || settings.prerollMs < 0 || settings.prerollMs > 300 || settings.silenceMs <= 0 + || settings.softTurnMs <= 0 || settings.hardTurnMs <= 0 || settings.hardTurnMs > 10_000 + || settings.softTurnMs > settings.hardTurnMs || settings.silenceMs > settings.hardTurnMs + || settings.threshold < 0 || settings.threshold > 1) throw new TranscriptionError("audio") + this.settings = Object.freeze({ ...settings }) + } + + accept(frame: Int16Array, probability: number): readonly SpeechAction[] { + if (this.closed) throw new TranscriptionError("stopped") + if (!(frame instanceof Int16Array) || frame.length === 0 || frame.length > this.sampleRate / 10 || !Number.isFinite(probability) + || probability < 0 || probability > 1) throw new TranscriptionError("audio") + const speech = probability >= this.settings.threshold + const actions: SpeechAction[] = [] + if (!this.active) { + this.preroll.push(frame.slice()) + this.prerollSamples += frame.length + while (this.preroll.length > 1 && this.prerollSamples > this.sampleRate * this.settings.prerollMs / 1000) { + this.prerollSamples -= this.preroll.shift()!.length + } + if (!speech) return [] + this.active = true + this.turnSamples = this.prerollSamples + this.silenceSamples = 0 + actions.push({ kind: "start", frames: this.preroll }) + this.preroll = [] + this.prerollSamples = 0 + } else { + this.turnSamples += frame.length + this.silenceSamples = speech ? 0 : this.silenceSamples + frame.length + actions.push({ kind: "audio", frame }) + } + const silenceBoundary = this.silenceSamples >= this.sampleRate * this.settings.silenceMs / 1000 + || (this.turnSamples >= this.sampleRate * this.settings.softTurnMs / 1000 + && this.silenceSamples >= this.sampleRate * 0.2) + if (silenceBoundary || this.turnSamples >= this.sampleRate * this.settings.hardTurnMs / 1000) { + this.active = false + this.turnSamples = 0 + this.silenceSamples = 0 + actions.push({ kind: "end" }) + } + return actions + } + + stop(): boolean { + if (this.closed) return false + this.closed = true + this.preroll = [] + this.prerollSamples = 0 + const unfinished = this.active + this.active = false + return unfinished + } +} diff --git a/server/packages/grid-transcription/src/silero-model-license.txt b/server/packages/grid-transcription/src/silero-model-license.txt new file mode 100644 index 000000000..042865069 --- /dev/null +++ b/server/packages/grid-transcription/src/silero-model-license.txt @@ -0,0 +1,242 @@ +Silero VAD ONNX model +Vendored from official @livekit/agents-plugin-silero 1.9.1 npm artifact, package/dist/silero_vad.onnx. +https://github.com/livekit/agents-js/tree/main/plugins/silero +Upstream model: https://github.com/snakers4/silero-vad +SHA256: 1a153a22f4509e292a94e67d6f9b85e8deb25b4988682b7e174c65279d8788e3 + +MIT License + +Copyright (c) 2020-present Silero Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +Inference tensor-contract adaptation +Copyright (c) 2024 LiveKit, Inc. +Adapted from onnx_model.ts in @livekit/agents-plugin-silero 1.9.1. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this portion except in compliance with the License. +You may obtain a copy of the License at +https://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +Full Apache License 2.0 for the inference-contract adaptation: + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/server/packages/grid-transcription/src/silero.ts b/server/packages/grid-transcription/src/silero.ts new file mode 100644 index 000000000..2e9e883b1 --- /dev/null +++ b/server/packages/grid-transcription/src/silero.ts @@ -0,0 +1,101 @@ +import { createHash } from "node:crypto" +import { readFile } from "node:fs/promises" +import { AudioFrame, AudioResampler, AudioResamplerQuality } from "@livekit/rtc-node" +import { InferenceSession, Tensor } from "onnxruntime-node" +import { TranscriptionError } from "./protocol.js" + +export const SILERO_MODEL_SHA256 = "1a153a22f4509e292a94e67d6f9b85e8deb25b4988682b7e174c65279d8788e3" +export interface SpeechDetector { probability(samples: Int16Array): Promise; close(): void } + +// Model tensor contract follows LiveKit's Apache-2.0 onnx_model.ts, pinned plugin 1.9.1. +// The upstream Silero model and its MIT license are vendored alongside this file. +export class SileroModel { + private constructor(private readonly session: InferenceSession) {} + + static async load(): Promise { + try { + const bytes = await readFile(new URL("./silero_vad.onnx", import.meta.url)) + if (createHash("sha256").update(bytes).digest("hex") !== SILERO_MODEL_SHA256) { + throw new TranscriptionError("protocol") + } + const session = await InferenceSession.create(bytes, { + interOpNumThreads: 1, intraOpNumThreads: 1, executionMode: "sequential", + executionProviders: ["cpu"], + }) + return new SileroModel(session) + } catch (error) { + throw error instanceof TranscriptionError ? error : new TranscriptionError("audio") + } + } + + detector(sampleRate: number): SpeechDetector { return new SileroDetector(this.session, sampleRate) } + async close(): Promise { await this.session.release() } +} + +class SileroDetector implements SpeechDetector { + private readonly resampler: AudioResampler | undefined + private readonly context = new Float32Array(64) + private readonly state = new Float32Array(256) + private readonly window = new Float32Array(512) + private readonly input = new Float32Array(576) + private windowLength = 0 + private lastProbability = 0 + private closed = false + private busy = false + + constructor(private readonly session: InferenceSession, private readonly sampleRate: number) { + if (sampleRate !== 16_000 && sampleRate !== 24_000) throw new TranscriptionError("audio") + if (sampleRate !== 16_000) this.resampler = new AudioResampler(sampleRate, 16_000, 1, AudioResamplerQuality.QUICK) + } + + async probability(samples: Int16Array): Promise { + if (this.closed) throw new TranscriptionError("stopped") + if (this.busy || samples.length === 0 || samples.length > this.sampleRate / 10) { + throw new TranscriptionError("audio") + } + this.busy = true + try { + const frames = this.resampler + ? this.resampler.push(new AudioFrame(samples, this.sampleRate, 1, samples.length)) + : [new AudioFrame(samples, 16_000, 1, samples.length)] + for (const frame of frames) { + for (const sample of frame.data) { + this.window[this.windowLength++] = sample / 32768 + if (this.windowLength !== 512) continue + this.input.set(this.context) + this.input.set(this.window, 64) + const output = await this.session.run({ + input: new Tensor("float32", this.input, [1, 576]), + state: new Tensor("float32", this.state, [2, 1, 128]), + sr: new Tensor("int64", BigInt64Array.of(16_000n)), + }) + if (this.closed) throw new TranscriptionError("stopped") + const next = output.stateN?.data + const probability = output.output?.data + if (!(next instanceof Float32Array) || next.length !== 256 + || !(probability instanceof Float32Array) || probability.length !== 1 + || !Number.isFinite(probability[0]) || probability[0]! < 0 || probability[0]! > 1) { + throw new TranscriptionError("audio") + } + this.state.set(next) + this.context.set(this.input.subarray(512)) + this.lastProbability = probability[0]! + this.windowLength = 0 + } + } + return this.lastProbability + } catch (error) { + throw error instanceof TranscriptionError ? error : new TranscriptionError("audio") + } finally { this.busy = false } + } + + close(): void { + if (this.closed) return + this.closed = true + this.resampler?.close() + this.context.fill(0) + this.state.fill(0) + this.window.fill(0) + this.input.fill(0) + } +} diff --git a/server/packages/grid-transcription/src/silero_vad.onnx b/server/packages/grid-transcription/src/silero_vad.onnx new file mode 100644 index 0000000000000000000000000000000000000000..80c5592ef1f4c9ede3e357bbd02eb863358a6a9d GIT binary patch literal 2327524 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 z zr>S#B(*8uG6}IJcp-vho>lcttEj7BOMq22qZ=r#EP_U_q@5u2j% z)DI7|4E_in)dWr`H$l5c1*aoQB_Z-JiTBXUl=JyUG5QvL@a-Xudfi2h>!kR+zkGR)I^>~OA&Wvp)e4_GTeuM7>s+^#}C)~P9Q#)pm!nmvSy81P0 z9_o$b@5a-J1uLj$r;w|v3&bOERp67zv&&L5F=BcUuGpt2oE--+)ASQWuU?1(qaCC) z9{?jbS3fQKK?l7`qkETM!Sv#<_&G}rdu_P9p_%2D2o$jWJ{?2z{Q)U(}zPF$FTrUI#W+bh- zGgvgMK%aPd7}GCRJ5cuBJ>o9YNX9y^#ZE^<)H|Lg+Vi8041S%9pA;JDx2ZWfFb4dxWj-)CShhR7mwck021PA$dyIYNKETVgw0v>SFtQj znWm5Vvn^=XSQ%1wY%9v&GC*x@hHl)H4Az+kEKe^SOWgYplV6?VaQ*oaT)-!7`pj)O zRwQm?oSy2@b)&-Jvaly`lUYDKlSkl=xH{3)d!M+Stx4SLt{x@`gdUb5OXcRb!U-QSn!XxA7qrE4>7BV`%)0-CXQU5z;a(2$)eWG% zos;nG&QqWwuqTYZ)q%EkBh0lJO1UkoAYpI>`p($M9OFl!-r0|koOK_}r(B2D(dI(e z>ZG_`$XQn{tDttxLDckhw8$h`0TjY*X&Bf*rN>vCc2yo#=xM0O!(^g8cro>;ewdyc**GjF*a(4 zO^&Omj$C>zj2!wo<}~Lf9w}rSw@_ulc{kz|;(bmN=U%jc*_R_RZOSlImr|wY zW++0|FJ;DO)(ggZNg_%YD$^A;L+FtqfpFoWD}A}WnYcKbV*J0$b@vUYFOH-E8UB#k8-1eHhBnkA z^d$YSK9BysY(%$Ycv7P&C+Y4Jgxq*%L{V)`Yc6#3 z#V6!;_z`B3*oOLeZlKM}u5v#n%%n?7+DO|FDKezrkOm}$ll1e%8)C#K=^xbHW1!<0q? zdd9^VlFlCi|998O^kJ3aColI1dDSi=>lwxkw*}_c+j25;u$kE7!+i*PY(`E@N#*oD zI3Uf7B%Ys;+hW`U%1g4z_oK03`>(teP!&!|S92!PWONQ|aH)_!7>Zj>V*Zpi=<6_q68S5o zl*(?Qse_JDmzRB5JbMV6zGyG3-7L&Pzf|Cala6@4)PUzE4dD$k{t}f(hV?G$l#AReq`qRRrJ=(EaJZ9JXn^`qgre9 z>6s^6@Mee!I^Ap~r5{J&trR1y3!29yooJ!kKb)d*NoOJE+7Zau8USelui;l~1sZQP z!!U0bf!#j?I)rPRW{1(dZ9X(*@?-K1TZqG76IzwIjHC?e0=bh3a9WPVf~Vtf=E-t0 zMkksJKFQFu)4#~xGE3TX{Wx85D+awS3UOwzJgA+l2DOWk_zW{h#DpbOqi+;Ew=Z^mHCCuKCVSEpWKLXSs#6w1_Jpu=+;_}lP)&qI(2{R?^F8hF|$PsqDP!!OGeMmo=hW`4D%=XQj_?%#HJZ(164 z8ls7DY2*2U_*RNpGFEUwFpq4L{81&jo(L$qnr8kr}k@-Y(Qz@S6_fBm#5dBmSE` zo6k)s$7{6$>%2XZrp+-%3*R*2vdW4ax+X20XXPNOJfzKP?qh^H;Z!mf%vVr+HwNE(v9xlgJKMi`UHw5?oh@s)8gZR&?y6g{W4PL4= zfWDYBl$|Q%b`2sn)2NIPVb>H#{BNt`r=AYF*}n=Kzg@z=NyGS;@Bg5~;{k+x7e2ms z80%L7?8=>i{G`6$_$tJLogne#t3BVNr_3FkRO`*hLk~|Tv$z4Ph|IaXW`zK=kBnnG=dxZJSIbwdel$qE& ziod+R9<1j{=+K?B`6rF>c-ZbSjVV4yO*iSXX)?;Ra$zA=c{7(ickZLW(^yCoWEQZy z#vi7{u%5nRz1SlAGTOBEDV^%9%qBfh;U^3CEYG0FxJqCyxwu$zOjjIc|9ipJb~tj{ zen-ePnVY1g^9|=7@q(_tDzM{9Hc_Y01$?lO;c?FUj7wAc=*Z|w(tRzOy-`^R^H$%* z2=!d{&*i(Q^+uUpvG)xYU(~>g9N{~>EBKg`efggC48L#LG`@A)79K_$@D^>QM0r^z z{0nsBG^!>*%a0jQ@9ju(7HuL?|I*3hfb)!Jq!N<$<8V;*C6^=ukb3tRI#1jeJ(B(g zCb^e!rs908c;8DVUA_usMj|q_CY}ztnTO-{D1(o&4xjSrF$`YL;(do?-m2mR{T6G6 zZX=iR?^RWJ1II%MdQzk!hVI@^pQRNe6#qd9~EvU{w2Ys#> z!gmgjrBdlfXmrRbzS^>g?hpyS$BJ(>jV97H~xX zO}n?cqukmp8G`Di(cm{n6^YqkJgWH!>{BR!@SYPj zUT_}IcE!35ZB$KrUfnD7m4l)mfR2+UIh1QnCBZAjii_hdjSni~+^jcnTVp+>^aqpV z^c0eGtB0xY?H28koldOoUl194r9xiATJo^`8yMcX0#yGR7d|5ruI=0jwNr&oLS`o{ z9DRv#p4Y*p95um#@SpHJN*6xem&53H{t%da2Miv}M5~|&@HaaRA|Ks_YAlA4{wy=- z`wzIyWy9jT&pEk&cOZkAfU~E%!`R{ZOi0RAVj*k4RlEAZEuth=bYfWYKaWk}E&QshSUP+7C70V7VRX zZ`lM%ecOT6{Uv^;qa-d3>tG^`7m$PL2cR%?I=3(02o#LZ3Vc}*kNC#A_Yfi$Cx+dSgJJJ z(`&>A7_#_qoh7M>(PV2EL~|h#>AY#Z3I9MUL5OOPqElC#r00))gn@YB9_XOKZ&{gv z2a?Lr|6LMMu6oQBe~!m5Cy&GQqjN}+o-PhC+kldP`IuP~gF}kg zuJQwhWX3~;`Bku2^}vSG-w>c^ChTaJ!*Klo(x}xBc1J~Myyd*mlMW|A`X|xljtq0h zcq&!)w?l=pB(z#3rXQw^rP^ohXsDwxO@Ez1R0S?~PUC7UI&%~oX3NtPYg+K2W*b_> zy@Tk2unPX zYevr-6qCwz)4<-MNXS0V2b~;4y71jcPP;XWa6;zcot`#5kf}*)Q?8TurT5{^T2E5d zZ;VT#j-vOmJJ4Z!3C^|7$9e5BFyYc4`rvCg9Wg(WmOVU5<07wNh-m= zY8Kcx>=rudT)`@ZC_GF`Fp!sKpDWj4^9vJbUBqpdn0OD%1KSowuGL^Z z*shkxl5c~F`coCI@}WNL54lJ@ZErz=O&8Nd_lrG$e1*jR!H{iZLfl@2as?L-gKMD^ zH*3RsuHd{QNaW6Pvx|Q)k-nQ4(?QZiki@{1G&ywZ-p)u_Oqu;9(TxmQXUVBP3Ky;I z+kg&Aw?Jp3KK`tA0Qct;NJ4QtI59W4idk9|PQ8V&zjHCd%9VTi_7jAhxCT9)3PfgS z6!v(TVT^|}G^`H7{Y7hWlG+$_*-{D)`F*0VSt)Q?;NwnNx&<@$uf&uN9(DyK2+go$ z_-c5PbedOypOC5Rdc8~(^zFXYbk__t3?D>%|5j3u%PPc2$W8syTT0)WZ>HBx(`fr> z34IoA#ES0iEg7%);KO-**pat<`NW}; z2+v5q%O;9{yPMG@@&7U4R*1V`-$)YA&SI z7q4>1M9X%>VcXWh?DuL3ZZqGGPOVC8|Dr;)l)r?t$Xr(Mg*+Sku>`f9X0ttYGOQ&r z!i34+1XlkM{5Dbrom4KMe9T$A;3Z`4{24s9A_dKZTc9&+GD>foh&x7yk;da$u;S_$ zuJhw0GUikbe$0@;;71b9d;Vr*#8R9VUrw(5Sb%Fv138afOQB-5CR=K`87mIgF{)MR zcFT@-QzW6#mlnZ@+PQ&~28hC9h@jRljjR?JhQ@wC18 zIvTdVf>QnG^g@OmJ9gPuRPQ)R|FHS^R5k&O9cB6MA`Nz1hLF9s|3i8iXX?Bdy?GZ9KqdElN>HL2B_nry6WAz|B?BYVNeILSR zjF?E4c$U)>(pBiG@|hXpIEC^`CD^(7HB?*M(#|tea8Rx?cAiL}m1A9T$4Xm#5N|;X zm}D?GHyLGqI?~ny<-+V{9ZtCYoCIX)V0^6#sygSA%}WF(*7e5>H$9YaC4b?waMtM4 znn#`NGRXY#Q%LO7S@87tVrH~<7o=;CAnva^A+T#T{(jRh+PpdgvXt1e+uq>KAAfne-8$2y8@8|2E;q{3pj4*1(`S@`*@)l1~S4h`gJ#9dTK=0O`|!P z-evUIco#DKog*9?5kfmn#-r8GK5+T1ME)Hcjh_1VM1Q6K-Sm7Ky!i)X;5 zS(-R#=noR)^OZB+o=mL&6FOp_$KZ#myW+~Ajo7$C8ov)3LyEU%p!?v{%*%sgiGraS z?y?9++reXK?cD9ClYa<(>&mI(6b64dUWWf7|Ikf^0eIm!6&PTH`D5{e*=5?Xc(~h` znm&Gl)jKB8KF2aT$?XX?=1hmG-wc0pK_9}zk^+zZz8q@bki$m%-AWiVb~t?h8jmNW9KIyL76)bDC@1q z3QbkqCCpZIUx%{v*(Q`fGL5(A1upN=0DPo7oj;Tv${xLbhI}?j;NOl}&+3PCaQnZf z^SYbkSkY54(fHoVt4l?)66N1?s>6HUBsqhv6uR0uF&TU(JexO2Qap7i3N135@a>^z? z|A4oo#A~{>TVV+QXU2BPf-94(SEQ`w{VcaiPCPQU9{O-JpEGBvdVx+VqHS62Um8?SD22F2G1*^&c?yZXiVUrM-csflQR-+{=F zY2~I`^h3td(I~BG3Gz}}bWY+Zv{9S~af`n)1Dfwb^ANb#E%{q{nfyGT&FP&>hl*-Xzrfs8o^g75E!y^h zt1AtucC{Sfs@;-^_7r`lO{~h;vKQ&-JSw&0nLP=XO8vVzV@3 zD84y}tXrW7av-qYc5P!us|i{7MZ$ZyXfr*0;69YMN}ZyHYtbs5!=xiAkp930E1ieqa?>05SnoMH z&Z)pNai%CQgw4l3JPWQ}6<|CNgZ7`Vg8MX0t9xG!=&jvK^m6n$oVmPRq#0LCZ$A@; zn2WE`ACqEvw+AxRWUm%)dMTB^b@D1MTF*hxp;Rj$^$u_e1Li-ua7euN0W>%LhO1>h z;CGO-x}y7;De=w~`~LQT8~)AU_OF8rth@xbgyHCF`xtKNGZj42zneREWhIC%xZrgg zGot;V4~%+GG0*A`5M{}7A|-W%QHVF9b&JJJ?BLa4)0@hzc3g;G)}JN)NB4l*qxDSg zl`Qgb+8(mt&n42RXGXNb8)4So`;6+4BS?h2TVTLnXx^3xBLq&L!Gu*1piIFaR~?rn zg<#c0JCx-I5k27@CV$&|GGXOddhF?JQgywXiOOn$(Y|erv;KDSZWxk^u@b~yWUF8|H z>b8W8w^OG(3)ASzLOq(fa~(+&dW=K&&%?N~nP4Tjyf1IG#VYqx=2PYCbdMMys##JY=W zdF{Lgy8Ff=?0>P9e{j#8rWfa6;?rV!Uv2?4Z;{4{wK|+n+;Tc+7K6sl>Wuo_=S1Q7 ze;EHhn3O$wK~hh-qKD`Oq1_Kjb2Nipw}tt7JE0BKQ!S$?+ zV9rOm;SpO!NH^L-Ms4@NU)}kft&9;dE>gna-OXgx&hw1kuczXL{;!Dap|@hg14DtR z-X)v6Dv7NqL)31T!u8kO=E}5A!sOu!u%N&ZIKv&I8{E0Ss$N7oU+DXqMS4d-Vwo-M%@9~vr<^%R0E>w zdKlbc1GV8%%%+#oFn6I9TW_3QnE$M;$oPE~>Vx6Bg#Po_f`x0(|dDq#Jw)i8>` z3v=c@1vRrPT$op@*t`4x9z7YVCutM6y!s$8@|sD?@fz|%TMX|Pg_Fr@S4q>ZG028J zBWDa%>8ZVL=yCiv+338Q*w*T!PoWw8w|^8pUzv;}hwIQ{rBK>%DFFv*jw8YG;nd6E zFiNY-k?Z@z>9zGz=<>CX(|6uL*HT=c`;Eo^g->vk-%Z-7=p#JyTCwYGHyuAKlpMa@g+ugH zX^M{xO-l5{ZF?qD(`!=n!}>tR+=0$B7Ng1$Kh)O+G% z&g?m0s)h^ot6-V8qYp!%;2ON@tcNz|PQolL73vb`CiwG~k=vQwH1Pg0G&is&hbnVu z!c78cM%MV?mn-$0=#Q%s=i}}qB`UT)gH-~*=VHcI+Ie^r&N@E?=lBKFT~+mXW%e|b z*1JbaE`CJQn(;XAurN2)p3au|m*K@{UbIwf!%AM?!=!iV>ib&4LW)&{Um&WUBZ!%^&cNyy`(KPsYDtWi222Q^_3-^trub7t^Ij(%!*i|@ z8dtmxV(;d{iR;2HySR#uN%mykPnyFQ*IDv5U(AVrwJs8@I6XzRSXvV%}eOCcEP_;oQStfXs=mBJ0*|CoR1fLD#zsHIL=1~p^E+$1>DzKZA2mjAUJBWa04sIP5yC!nV)NK+*AC7`MM1 zV-`)onD((aaPK|ppB1v&k3>SAHV(o7N?ZPro_|yJeTqG=W?F?13x3I+9Kf+&f0x=NEuMc@a^W{ehV{`66gp zgpupd^&qfu0Y1|?L1eY_!GB*CRxI@(qcSR>>Bx1o&aoHUD=IU|C0nuSaJ^W2PB~e~ z14`xj3Q&Im1bn>)$Np8pNBbQZ(9i$dIqUnz*truG!*eY z<;)>J5qfyWL!+$?N&R954O;@3pS97<1R)=kyhw_vxp4w&n*V~|vfpCSj(jr2{4P0d z-$~w|je?h{necE(G;#FIA{M^_$;PNf^!ZaOh<&#d7QW)h7sIKfY)Bcr&k?d(zt4($ z3yy^PpU%plm2a8wD(&uwf#93Gj^%+by0h1-+?|b+k2fFzAB&r zM$-5!_76=A2&eY9x=7B7Kk#PbSwdXz5*?F#3?Cu5D{h;U-+x!(`kDx$EnY6nmS4ff z4VQ>^U>5UXUK33JFojuoQgCJGu7vef`CP!1pCIKjSv=B6VBz0Nfq7HrQ;p*5&~twl z?${{L&zTkl0jZAIb#f+eawL+m>i#a4=X`jx6(Qo)R^{YX<|clnAlGc_67Hvs&iv2M zcET=rFnzMpo>zNggGtS0qT0v>eB^}vjEVXKl5%Y>?-^3osIz>eX34% z?o8(E6n%v(Ts-kW75@8|={U+Rgw|^6@~YED;nqdwMB3~&{hg$ZU!T=-`AhE5F-a3~ zdF3O)4|;;$yW=Hvd1f-NJ`Lkt?9yRes2u9s+@$_ta*Xy94Z6b9f{&Fc6CV(1(Sh+n z{D9F=;{LM%R(fp{&XL)q^5|+hW_bedV=;=p+m=V>%WUO+qt)nw{sCCq)=you&Jfuj zi?CAm0p%XABaJnhILf?`-qiXkrhC=!Ovh$wIAs+a+TIIi`ZVZ_H9F+E-8rx=4yN+1 zOF7d?k6;>EP4rCeGber<pec)q7D<%ySjt1Xq3ez@6V9` zO3!eUr6XWQ>p7x+@FuhC&1jHiY{>SP{$SFd1;>8WagWM!pjRmg^=!6)(~&STF>DH+ zmYd7nhzem&kwM76J5S#G{$TRs<*{YPKXKX2PY^cQ9T(Y4U`4|%ZkWMzTrgt`qqQ%T zn9F2Bx~wyit@=quC7kDKQXX;#-nfZMF04biHWPldrHK89YGVS;WR{h_U_9LXa8_(F zd`o;PGFX*`l9|a>zdqWLhB>j=R__nT?6)#c0%oJ%&y&E_))5PfX&B~Y%-rg4Cyuq{ z486LZd|fjXR>-`DHE^lgxKV{(S?^7B_7{OV6GP6@f6#V!0_Hj`1A{*&pzPLUw7IG- z^jY-bOY&6aw*%nqjSt}FrM+Yk-vn#AMw8YVQk48U4W@dhxlV;XqOT>3uB+sj?!OC! zd6}?Z(aD5>&;i0fSq8FCL*VBWdAjM9z-IJ+3A@%Lb3(2kJL~46|GNDgM46LwdH2C8 z>;hLSGn^>)F=%UFOxC@c1qJA&-VI>%LLV>Fzm_nza{Ma)O_mj_7jdN<}k zU^x1|t0j8(Y{W(>Ik=~>kA%qtGu@v;@u2w~;wO3rdi{QQ@2@>+7!d|;rKeUiz}#J1TnfX_@8_XsXJ}RDN0gsNmUM+xiFXvVb)^9rlFip-aP0RINvXf zzL6e7Bb;&R2xb)N5u225aC;Snsq_LVohah;ycgs3W5>zdvm?ppRWA77H(io^V;D7a z+Kf_`;be&KG;;fQ9BP)ilh-=7oQrEBUQN`bPnMnLwp8Cj{dMz6R;fEt3U9{L2Qs4T zYrd1S0}?#dp2+p=-33!+@1O(C6%{zX;;`)q9+t}^9u7RCni_{AyOpe_T$C`TMys(} zy_~GMa*i2)bS`>NjVIn;ZQ;YI>sa>PUtFnd0-qjU#S3gY^FZzybFTUfTK*VAPdo2s z@-~%W=Cm%dpGePxf{M zAznoz$tRC=d`pAG*%NDqHw&M=7*KZ*xzA6#Ud7~L;=Rl_V>wXOQJ%*l=FdV6= z!4d0D5#w1rSvKz$wl7~p1{K&4-`p;|eDf!9ELy+>tWjdqlHPOYe(4awal$%UYk~gT z#l+QW4Es{6iivCeEbfe%$Ogm;?{TBjK{s8NHC39!#aa$Q)y4^IkQRa0IZd!`pB396 zeFVb&mSYX$!bUAW!PV>^ikB;S8aQGK*vA)A_qSWwW%sga!KX!(Z@vP%s)Rh&&Y7%7 zb0}Y{r^tHi3!HFKG5S8QpnBcmG@$1)?y9&&tanyfo}U>--q}}zl2QSaDb)xMCJC&T zp)06lvIcdoq%?g|6ONDAhWVM&XtQiKP10AOIfZx7eOxHiFHqz=JwJZassAbzj$B5K~KFU-@ssPeEh zR83#YmR5$K!k&@zyy^#>qwh&iZJbE!23Fzrx=8AD;Vdx>*TkaPSDA>^GMcV^j(%(b z^2btRe;(`hX&qY`{+!GorbKg%XYx)3n^`#v z3Wpj>>Ab}^*q#X?_%pANY|g&SMMVtos}f};Q`R{1vD>vIuC1n$XVSx1|EWK5D2_yl z;W0KcNyd7ucLdv1Gnuu-`>e_Gw|F2di#7T@!g@{p8rD_tAxDfED{)SpCTR=W$tTXp zl_-?&lf;B7v0p|^l}z`%!^@-$w~n5(n7w%ZAp6*%p1l%W#s23J$6nOj#I}wjY;3$A zn<8b;zutJ4tjbNIZ+cWET7&!fhgPU#Lz#>%h{2`+sM+`b*x^-b9PS-hfWHKti^U` z)<5tIS*j$##zS2s&D)q~zrUnAd>-<4Ef4szl{_uj@PY4-e8+#9;Dcv7{P;tQocK3E zS6F3pU3T9E5nGb6nmuqaT*y5?2F>*mL_$Ewj{B3IrVG`s8;eD2O;|Ygfk637YRxG+eZ392?_%ju4%%W*vq_IRJrW&rDd_^VeViPZCx>4K1~E$nV6T{IcUOA?&v$c3m#O1TizL%TrrKg+q90;pBc%7%q@W% ztUp5rnGu&SgE_A&N14a{n;=1}nR|1qnrn^JWIV=w<_tg&4wR2)>XMA%HZ#D@%hq60 zRU*Xq)@%~_h|Y39uVpi*`b|is;$m^@(mf1YbAYr=+R2=KtPRl}R>atmGS^xLkz(61 zBAd5XOwn0^xi-?5i3sduJoX!kT0>gJ|2eT-_vmcyTgFksnM#-*VQ*`dG+ZP=Hkg;L z51FbI%9Wo}BAI>nm>@u$pEQGZi2_e!pYyOQ9+9k;G# zV%Qis_WeAk^twZ=7_)$xQNoa9?F-zZ1UvY4N`tJcn*r83^^9-wJkb~T&&-7ofmIlp z&9Q4$V8h$#TswEhN^QeG?xb7^H+7u86_d^pLBz+Eu%(P#=T_3H&rEOnC)WIH&iqN-U2S+tAM~$2=L#GWt<2R! zxf54onf-ZpMJ7SvjDP$kZpG96%$T!;qrZ^z?nt*f*LaTeCSR(>Zq1x=N*AXORv^*O z5QPjg;Y@~45%0(KFm>4-ZXoX(_xRCradFj2tNy(W+#0Eouw!Wv6R15HQbOIBj}=ys zTPhDX)&zp_uxJ58qQUgFc5rEZ#>|+<`efq$Rq$|Q7}Keiz{GhJGBw4rVxJW^I4LoG@Pm~48!Jx%tVGvNrpmXID5TYlvI*t8Z^_0B&j5nxuggQl}u?cl)~9-Ei|bl zB~l7$mLyVApT7OiPv<(ez1I6a&wXFt%T+g-?L=;~TbU{c1*F~BP+h6FmTB_#t4>up z&fV*JCP+F~CoE7qj6+I%1k8DknGmUkZr{mrbcu~aZ_due0YA$GTRh~@7MW>6iDLnr z>lsO8KEnu_LI)hmi@tGr2U{59u1(w`O%|nn(L!M_*D&AOP2ex@lymw|4=yU?p=G^~ zAzD#}c8c)(#q?(&G4UMj382)<^fUY&TSM0j+@Z=Us%+S)1N21CBKmw^5yZ8nkmEgk zrcKQVmIa9rr7UkSoBkDD|9u}n98m;~xrIpe(*V!f91k07n~+4tu;8ngICS6AfgS2z z)xLwONcF!-kUif4X}>K*y^G}GlgxX@rd|}~YO_e>I2F9_b7tmOG$FfWO|-9i9PW@k z$E4jeV)AnSF&?Q+%!p4jBX?*P*I^qg=y1HijmtmI*gP2)$SjuNR*qSZ>c>w+`#*~_ z#_w-&yH~$JpL%`^?GvwX6CU3}{e_n3Nq#!B^7wQl9X^(`3B1hI&7FyYh6;s8buxt9 z@2%+h#Wber?H9*WrFWPMWll_1f(IjEz6l-OdJs9z3&ufnq>!{fKYcaI=9cHHqhR&1 z0{^8c+}+40sE5z~TB^_IUKop_L9;19j$ehM4E4~3+#cqxRu}VkvKH!fKFn>D zHAc%T)X~k!dn6?zZ$r%ev{!Kw1cj7sb-!IXSx;!%9 z+{fsyPe4&>7%~p@*!k%NFz$V<0 zX9eDYJki_K>SZ<^DC6k6wOi?KV^6Ab`~c1I6NirSNpLZv0H1QOLU;PEL(0VzsPK*A zeV8q9@qhwr^y@IpSU(jlCmrxxeGs$L^|5cO4(ZnlI>!L0*a=j%huk@0Lc?_M5?S&a`&y9p{{Z@GrjEHqcqibQ5U!tc)l z>aGx_sl3m`_x)~cH{Xc94)#R9?+l>MeN|kt-#cbQ{t-}@SA%W2XHcvG&n9zu04;jA zkcsqR2pRnb5~*G!+_f8Dk8ejU%hO1ZZ3nU3d5&a!n@>#co+Ein5=rqkD|qkZfHs^x z3d8eq;De7nl+7xDCdu7!XwhK^lidW`^Ir?!o?QatI&YE*!tvaj4qLKL;6X2V&V((; zl)0%3MW|~+GA#D)0zq^GHuuVx=3o2CYnzaK}is{S8r!={k$GrLKI zlmC;g{HOE;aov5Y5r%j5}uqJgJ< zxJe$QilBCE4p-xYh^h2*yr((_hTs?p_|A~>5)46EZ&9(9kj(V#fm=5XcnSr4NSB0rwFlGzI?!3Qv?)Rm5P9@Tr%-|V(E~c;H_oh11H;aQL&mW9^yObd2p))C8cb3`ubPLF& z*P?qHP7vFG^Qb-N9QnHS23+XtXFMa;gU3V>k{dmO^FC9Yb>sJ;U_qy__GnO^x4q8Hayc$jQupx0p3<0rHj7arOO2v{Z-Hy^PH6|kb+H`yD6!UBQpYm>0YB5^dLD;7su7n>8~8wN|!U_ zVz4R=`}2S;whv^F{}E%uw`Q{+cRoU6{=0}HIuqDghfOfE{3x7UVam$K9iSDf9uU2v zH{2iR@ih;sg>?UQQOZ`1tNFF)HNBben@Algp-m>Kbac*XwsX>Dw&SoX`?=r?TlICC zQ?kYwCq3bG*7&M2J7>2%oAd8J{W$?@K6HrHsNQg@nl$3gX82(yU;~f(ux+dVfbyHe zta`#(HoUKjly{#+OMQFbZ6W9E!!QpRzpx!cbR9Vs@^xfTsh9*o#2cIrRlQ()~re7qQ{mKH`fO+#lR5^ksZr2eDqSQ0W~&` z!0PoWsClz2N}YWNHmx%RgQx+dWzH8ovXn@Pf$IGq?>LpIwyriOhQz zqvCgeQE!YF6KI)T9ohF33Z*q*PxDhon&(YuzYqbFpP4vVwj2#c{0!!K6R0

h^*jdh%vf*gj&F?E?!*}bg^aO1lu zeroE(>h2IgpPdQZo%$78B?eKL=xOvTp$yBb)Z@YoW8j}0IQBONnAeKb+4ej(){_96 z^&Z?{&qcCBYBSS5pb4KASD@@=qQrPGpEKAroqvx`fmDBSqO0}<&6z%rT(DUNx8n_A zBhU5d*lrA|`n~{%FOjx87LX@m%7vI35$E>v$YG`;sFYnLCSKXRXUY)PjUU;hit4z{E&c*)p`fR<=$jL zLlfk=x`IQ%N6KA>uc+sM&#YryboFg2R3OP>BW z6}CKOsCINS^v8*_Zfhs7U_BptKNzrGep}g9&Bdf*?M6JH5Ja?eq*yKovthv+1-8

j0OGBvV9#J{i_h(zb+=4lSkki8-&*F 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

iR6o&g4qpI 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%0A&1<=k@%mx5mntcPI9igTO@L`cl9x6xNgRK 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%0DxWy=F|#e_fz1buQ|C_7IQm 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 z

    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

    6%L9?&TJ&(PvTT{aX2YPaNz4Zl$YT6 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ï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

    (SZ2gBq02ZU~7-XPSLWDngK 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

    Qo*e}PBcc`P_kmuoN~TrB^-ynD4C3w{c~ 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

    *_zvEBy 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

    npe{?vfoJn{jPW}z>z1oc_j6%$~Y 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

    4iQF-fx2uw~~M@d`A-FlHjrZ5|FS7AP*0k z!RjX!#Cmoa(?5GEeEBkh95&7293{SxlN)Qz1|2i0;=5>4TG`E<9)a~p> z@}cPur?pxFW1r6?$9koxX4E{a`&+;@+Xm7rZ{#o|UykmSze?{U3XH@3iFDMbQMli5 z3WjvdB(r?B(?usf&^!ABm~x%!iXp8!Zd-UQDV1D8!nSRpyhoJNK_ z2{9N)GZqxnqKYf#CmPRD*3NMB=d;iR)Cx)TzoCdE_MBu*j1B$+|UyM5Zf$Snros;M3!OGpZlN)e zX!0X%({2&dE33(GSq;*lb&bi83t;T-Dv>o-Td9fQOKW+1im+k7Dt~qSGw&384mUl3a`>#HgxmE7QDXFh#UbTH-Y&+HH6Kg4EDBu}0 z>Tee#=Fv27nD%LNC9{9#R!XNBZU+iIoguW|IEK!Z-^Cn^JjGZJJT%WVXd-=fuPfSX z0=Q-wj{M$#gEO7HnUPp`mIS12D}Ojujx>ujNw~!rqWU9{s}nfwEt_H)>BNP^_R>XS zGjBV|)jdjX9=gLU)*a7%+P{Q^G+bh`MhvG)+d}E^b!#}yr~cH>;si~Wh^L}sn5x@3^Iu*GU34&&4%t<6gSn^akBJSOPWmbH*hPz(prA9{5`pW!xAqJ7 z^o2I9o>NcPEm}b%UPdzo>Q;2X^gSIj5I|?7tfzKE*J)L}Nrl&e=jO97W-^)rzhOR$z!KbvP#{U=#2_s(ziY$D)q8VRuWIX1_wg&oU3Hg5_$f?Py!Amhw^i@ zTA;wG0f=P>J$J|w0t!R1bdDKcO07ZOW)$9w*5++inn2=}AoBt~jox<&g&CVPh?2=T zBE51v(a6#Q{|UdDo7eQGxE8n0;Pggkq@2!}mB%u}kozjYG%VKQJV-0j{4s zNB@-%MZgV-p1$}D0I%&?MAJWt+a;=fahtY`2CNB+2Be~ z@SS)D54`Ut!?sL?v2R;&_?P9-J7@^S^aWNw6x?9K`Qc4a8wP}$U__wEeB(qBx1H9$; zz2wNoDtMi~m_IXR5ommGgb_7H{F!^k&?J8tHglVJnTkSqb9ooMy|aLCdPv}jz_R{n z-pX%znLsXew~!g(EBV_W3}LPFbPzxK2L0Xzf!vWeaP0KQn-`40w|F94J}<#{ZHt8D z?Yd;qUl~4o?pe~Dp#rYs#rcrAJbhKv#cX?Cj1`0b@Wu7f)bznme3<uAqYD;{%*u(PIiM6ynXo?mEww+sMZs}?o{DNDW2k%Wo(4$ z^3Ty@{%xY9sSoypU-3ZEDq1!w9H#8c!*y~0=+AZQ;lyfxWNpg@2DuDwsBXcX7kh}* z_%M+3a>LgA*(j!_0+sRy(Zi{LQ}~$<54S1cnDTgP*?A30o(#ivCY?lS=@K~p>InYM zUO=|`>cjRCiumEVaE6&+3(0IG8a!S=I=7qx`)lF&pi0E7emxibHfB|t9q%Nb=ZeTG zBP-&yWCeM*QViB!@WCa^MsoL}yNSoo5_I^{MikcGBr@}t;^SG5h(g{{P`GlDK29hh z%B%utvVoYFtyal6Ple&7+4M+=3Xwk<2kK#GDM`wso@S%q=Tf1E_rx$`B#0ujExtPST~L+D>cZ!Fyga7y0_tB(%j`Qr@iUor_}uKkC>0sn#L$WRp1kH$-C zgho4fu^h??Lftdn&fIg3;KtgIk$5ic0lHkcI+*=i5J*TfDQCE3+=)BsFGJ zy;1RkN9rqSd$^D*8ST%|Q}J|I)-Gz;Bu_6NIYy#a*H@NIHGsT`i{#QKBWhV*Uf!ly zOS8_Z6EEj2uv^IG_AU8Xp*M6E#wOn4h>I0|I+zPlkL!s|-FU|M)oPe*V=b`a8wd{V zg&N%)_%kdBc78qq!wv<4|Mq-n8QBEqRsKTu>rrg7K_U1(l*0WQ7ohFe1VI0Ze2KOW zmf~1GO6X0fGD+s8(u(<$?}v%HcKzdPf9LV_s&XRT2^u1m_o@7xIgPyUEeVmu>skDk zKO6Yrg(ALPUU*K+nhNLtS-e)sx5|A}@57F5()8XQe>h*S!}>hvB%!xUi1R{6wj%Ze z-0SHl*@r{e?vvYD%UvR9Js8cZkM(B#oxW0?z^QEPXan|=^d3BLsvqujKY^P&4e{gS z7htsX9r)}>LZdKUXq{&dzK?>@{)0XE%rStod^s+QSxXw%OafJJ8(brD1rsB4c1= zT7O>wMX3)N(^6Bs_wy8aIo}m>6C-H3kvfoTf``^8m$uF?C5yf-h50kz(qfw*+>Bpi zAA}ubWwlhadJ`A$r;_EfLjmst7 zP9c;t70zJxDloa?5c%l$U*+J5BJx>gEGV8&qkZrH5T*0G%{{hm#PT&W!1ls3^9_|O zaZre5-hZj0dyIGBRnMr(`jxsQQ|cl;YdkcvsUO9)S>^6?K)ihzJKK2s90L? zPx!7v11Tu6 z(6e(V**oS5ebzJ)o^%knr!|)@2nfVOH#=!)cP{uUG@CE%h$RbyH5iGTNm%Q11e_yw z(ZLN(0=E}P=Q?mu>TniUesd#kNoA1ew-8s{7v@6&%4DpC5&v7r&{jX%jB0E5@eZmp zaF<~LUfCbXf0Fx6Ll<`7_3~4^yB8HY@XT<^iDkUj)~Uj0*Fi`0TJr&-Vl-_z3*7~M z+%uW8MD6%Frb@k(#Ca|u&#U#p*=0Nzl0XUi0UUl~0R=j8LPybPGF#}@bIkuoxrm$8 zs@ji~7i*GdC3DQ3uJOc2eFXGfy-z%!wvtG_dTvJ0Th42wH<#Bg!C0mnlOyxDP~*cf zgg1>PhaW^xm%7)CRmE=dphlZfJ!?&02r~!gpcH2FAC9E9YCz7ZSQ_*ohTeN*4V#)i zn0F<4lVtLY$UFQH{&yde@4dmaD>j8Z4a+1pRbR=|iH~T~J$YKu8c&Z>W4fhLhxF%o zpc>yr3U=s1nbbd$!M795zpb?C;&I&OT*5VMZDCAq<_aFC0%2bpM6T+T&^t4Z(vtE? zIIs+8TKQaBA_FnI_Ra>xPI4aB;Ur>lmfG4b(a5`H_K^bOm@EPfM74`~b1 z81MVEM`twAIu=>+EAcy(wbCRxvY*H#2MubaB`YwnnU(#IqN$cgDe;Q>#{5YW&c1a{ zu+BS*xJ|UA0bvT2XGIo7Mn{{N&*&k&o8Qy#lW)>Py=H`My+^FqCsoR}p22B*=hDi< zH_7SAJMnOW9`#C*#hDq8h27T!QepU))+|qe^vy!XNxg&=7lhDUgGsPj6h_?j9&k}| zw&0}kj=V~;K{Y%jWo5=RO&J$HCiVYV2$BFIj>Q4|z<7 z?M$N6Zuen|dk(R=rbVtB{Rbr;=fEH-3eH_EAO}>fggHk96D_Y##OA58#`9jnr1&H3 z*L8_(YkWG}IN6I0xv9xckp$LV`Xsw(<_WgvsylmbNg1nNcA4Ea;X0eL>lQo7_yoJL zv5?i$t77-=D`(A*|HpQ$ZehRujbtrkud!>rey~zK2CVq7^=#dapX~0hmuSwNE&R33 zV=dU1rF?4QcfL`ZvPln4iqh1m$ZC2hJ8Nm3s4e}kC|Em}HCq2x#D*wUHRRW^FYfe< zR!OT?Ic%(DL!_UHx~`0@`dV_2jc~mwTG9PWIXU z6(D7CP=1;UZleI>UX^vQ zAt$xjzpAg{&_+p%vqDEjSH}mqP#|UjIzFJN5k&uMyUTh$zC^qqY`~YA9qi&U3OK*w zJLUvQTWCj#&^+(}|KId)Y~_k&`~zVx`S23Yj_lIqKh;O`MUTF-wxTiou4FI%S?DeH zn`Z|9^HM(Vxrt?WcRb>&Dn9b3lr9nD4x_flZvW zl9&3o3@tKWfb{4uDEW9a7)_UBjUG9m+v#+mk}2%vmQZwCw2y7ymBo(SFV9;<6~Nxg zFYGH}KEUS9Xa6QNu)a}i`E{EmSc@0^>;hAU@3W}@9c*MrDjnu6&D7b<3#-|Y!gOYB zzdv}@Cqclsd|s=ggEN*q1wUrK=NDW`M#)4uRtneiR=-{`2lon@ zR(TVf_P3UOF~`!P#kZOr_VX`WS7V5qLRnnnaE=Pg*B)h2`%Xv zWZnH7#wqInNMHGecW<6yPEY6|Hi25a-lHzGGg$%O-q(@NWq@&X2DmX#L2t|&qH*vF zvGpIuM6)Wm`ivvAj5$wzL>rJ=vACd21HJMT`S`Y0(zfU&xoAIIm{&(b=Kd7OpVkkD zZMwP2{D)X_donf)zwy%@cGEyZX^c5RljD!P2N zLItL^2NMIMU=*9b3~R1Q;d@&T_*xkv|pAvH3{1+t0gp=Z#QS|Y?qvWtcBiZ-1gfW|Jhi6`x;xiw2^X83|v$`yYVY=i5=R{>)DD@vamb)Kv#b-ZE#a;ppRNLV;8Ek2iT{&)_9N6hA{S7-()PWJjaA@?e;}?S8aY!zZ%!SiQ-bmNf4{0Ye3nx5LTv-VLP6(knc2` zxQzTwN$PB@iu0fn4`a|saVf5}jfIG3GVGb7r@&j6p`q4CAT%}#B5Val#arkBxcrP7 zSxq7O;>#f<&xY>1_ME6bo5XIk4u(C|52){#6QI^JPw?ujM+u)nY}Bwq*;&WQJ|RDS zRr?Oz^kM=!Dm%klFCPf_cMx4VH==_3e}ue?G+z>#N-w*8q$lNM(6~ni)N;y^RoR5y zo1ZdHm(@|_&|;*83uxHkAe7HcL#@&Q&hMBsrjPKUMZ*SQdxiyf%t@Uj#+1P0C`CvV z$E}%-NBZ4!Z(~|4YW)Y6f)9TGfIz6D%>q}5nKatpK zufUcACd49l3i?KD$Kbs}kL|8;Bzm|9+4Sc~&Kv& zn*;f3p9-ZdA85|s5ZHY#6f*VR5yi+9xbk{{n9C{y(<5}dOUkifp|?Qw$5dA0Fon~a zG9YfJ!tPk44$BWIgGEda46Uu@-c_tX#ici>U)wZ(tc51OiQ}LQ zM86D%$E3hsw?53=dmRS`hcniPw(f4|-MPV8^o)e0t?s><#~n zeP>3ZX0;-9O;pFO-$*)-2s=Gb6VS&SoZjmgBD1HF*nN$|^#2MNJxgD7EtH^M(UGL| zWC{KK?gUx;(+HQZbs)>-Q=vDbtn#P!B%q(_;cr9~Y{`rQRa;kKR(TFSe!2>?va7J> z=m)xAm>XD@jN&7gwxgFxFuLxZ&F__4$|wKO=Yu1f@l}p8f8e?*-{I|w^ivwDOs&O^ z+q>!4YuTj6?laZ+paSdGHp1WWBVg(hM>ue+7e*~LgYnN_kSC6dVcUcy5HU6yY%;%t z>9p&Ry}E^|(p?5y%r?N2e@fVO$(MpMRRO}<_2~8wX@ncE&7|EZqRW0% zF<Ilr<(aZdNzN!& zk{UkUK|2#xk*PiRnZ!06?OqVtAa{{4)#{4J9h;Z9xWi;TDNOyUqm*%b18^LYNcK`ic859Et9yRlN? z0&x`Gz__E5bk1RCI9zg*Gj3ZB?&DlQ*5QfyzI~2BielmSpCr(^7Y1rVSEsD=XRr#Y zgJr(#FPzP94qu-4t+62 z={@<>NpRVDR{C>FV?)V@$ep0%X=(my)GRnMI|2TL1al^Dbm8g0b+B;sEL>E)gua-@ zF{7>}(jEJJ$cL3O@Y<}N(GL8>G`?O5f2VGT&BF?bQT}|8N_oZIQTD)YH!b+$cb_rf z{-BX5A!Sd`5(R_*Fl5CPSa;n3Hopr+ox5w{L6D`?Gsyd= z7rCnm+feDoceBl2&UAa<7LvT{75RMQ8Fy>s1<=@031h1Nkn<-;vc<(6FzMtSvgk2_ zgZ~^@c|v%G7;Ge-1wmZXDRDAKCy%b!QBG%Fk*6kx%dmLxk-#wiW0Zy8f{m)-=wCm; z%&V%ypVj`H-D7=p^>0Ge*QwOs$BT~cD#l|mi?Q5P-u%(UGdL&H96e%9>Ab2dnEcKO z{agvQx`(62ia45?ydB4GjYdx|JuDKp!HR^FxWu07|y*bMWv_xM}r^j#>55P#O|Fmc6Xa%<*O)?l@-Ml-;~EWxiO@AQx^AV zkU`&-duYDC5qPOyBZEh`lQk3nk(GA?;aJ#q7{B@}l`pO77%7awv6kMj>fqvX4thl6e^Yo*#r|w+&%vUG~o|Y#l(@P5%#cuR~V%9t^k9r zv%w?#AZX951%*==N$IgB!M|kzt4fOLt+n2C)49>WZ3@Mt?0%AEdYm}v zTp&M3M{wUV#PNxr7xzh2PyY4ibMH3$6H~4G<`?1`XwaMsj6=&T;^sMnF0=I@mpy`+ zNBh#K=ie0Kdql{w^y$*R^7oAAGBxsS4ad#1zRgq%Os;aN3N;qIf!zy|i7p(b?Zx-W zsPXk&LVX4eOS(h)I-@xmpF|=>6UZRj#rW1Yku5@p!1O2OjAcYB2qXZ}Vx$>n(_?7Y z)}l4lrgZtgT)1H4f|4{2C+lTHQK<@!TXza=_*!V+8iEU4|KX;D0#smfT~Pf6w(NqpA_((axBIf2_2=%c{p?INM5p4mfd{u6h1$u$?GN!z|EE%)Z4Ge zXGL_vgRDcS_g0KQx3&x7e|KSh?_Bka{bn>P-DQLxu6d{-tH)lC{77G&jl=x{1N!Gs74{2VcMFEevWC-cV)&)Y zD0(Z!zVE+@Gmo`kin$akt9~ADoV|u(2fji2yR+!@tQTdsE3x#84N7PwVX5zBSf^Ty znpY^!5Oy`2*e?84_63I>L~w6w#2rN()|H=zysQBXT6qDF%sT@EC);qvQ*9&`5uoh% z3#Zp4p!@Xm;F}?IKwRp?Ap1IKuiS|N2L#{VrUp3ucrA9%$-=?ZFeqxefG&1lQQ~VT zcuE%I$X_vdN!Jb1n~QMA_hTr#^ELV8e;&8z*J0!xH&`N{j;#wz@OW}4EPp>2_2b>~ zzgA=L*x-dqk&d{3s~J?-sh|?d;&doBv}q%xQrc`*Gn;}4_*>K!8b4I&`z5& z72uV|Xkz~(hZFmj0;$HejJx+f`aML*$TvSC-?!z`D#gvD#PuI388WA18WhQ6S5>lT zN?&FB!Whsqen1wCIzY0ghl5H&cI81gdsJ+224!POA|tJcp<@og{P-YDHK}V#tJU) zWhl9FmnEv5FR9dfO=5Lg7ApRAP`|fz6{T{q#N0Hz;#X5Td8i={suLZktf-BQe-cYR zXZ)m!8#cmI!5w%kVPECEA}_dnNdX#Lc-s2b6z&;ZC0R4B(YA%+a7O+i(ODcwdxGB( zZON@*?;=JX3hBP>%TJTB-||Rczb;{xhmz1G@?^yPsZ`B(2k9-6=ZZTQ(^q#g=$|o3 zbo=w2bVKWWGT$4(=p$g+ufxpSs|n_R z7l41kWGWXv8zLXc0ekNjF?gO#ZW$zyRU1=D%M_M;emx6Z_sYTHI5Xz0$4EFWrV2SL zw^tgc{Ugd_y2z)u?aUE#7dW}{8+qHa1I+&%hU}~qrg7#$`2JB6+I4$Li}_M0IA{qC z|3<^nr%mLhC=?2qldwa2EM$IY1hcvE(ERE#8M|j4R7kdP_qQE|%RR?|&()zP-JId- z;#shJ!5{8FQwd@uWaz#%43IhVFVZUg;Br;NT#_b!n%2z_*p@nTVzT!--L(G?srw-V zbpiQY;*1D zDDK&NUr}h%EJ>wEN=ce0-t+!_t##MB%Qe?C&vtY(R1x z+uT2mBz?L9@1Na+b;1&Q&tp2ZYdu8ORwj~&Z5`yVxhma%D1>eOE6FZVG-XBegTQz9 zV!`nphrlcD45Y5!%WjVN2qr@}sMo9}GG@|AdRzQEZJAv|Yr3^WQ;%L`-&{Gzc19z% zn@Y2j<>Xk8SOr!$+m4^Jny~gI03w|uRKHT6X5R>Pb|y``4^tA4{aNTe=pp8pB@fVT9RpFPvDJ9!=3>IdxG@>bs}&K=&Np{r@9Y^%4DJs>!-ITs`^_an zlRtyPR!MsM`Vo5Zog+;NX{56wHqq48K@_!B(^jR=P;(u;YX^i@DSo%%hF)!UQG zJ{GX7iv0@Ko@c3h@V{*_k!1gtz6Ir9Qe?~h5m4ZFd^==@5Zp&>RqGGby3v+BdL#if zmiCdpiPs_ASC@9jR#4^6Oj23XLwb{H=#ff}xD~lkbp12kt+AO-xMogr`H9Fz8$O?J zHP!{rC*>UNSY|tikRxNxV>o#LPd*t4j zYblYQUt6uuEHs`0X!ah@0;>8Wno6NgSL~iUD$(d}3dJ6Pk(PW#vkAbXl zJXkBoG76RcRPmG(nK*qa?UihxPT5X0{$C8unWiNYGdn>Kk6%FrWi9mMmUfcX&i7T< zPNx!o--DBaHk<$9B3#c2WB>C{W2Nq$WhWjv%3}Rdtk{ulY-UOlTjnlF4K8@n=}P=_ zZCFiQ_C?d!f^stBn;aQV2ikDf2Dj%G@%`nERKjo+BbB(AY_`*;uMFd%Ejk@f4x2=8 zHg$sMl%4qdxgX@uj8W{=@)+{tz7@sgf6%Nl2Xdj=k+%8oB#Sqx!^$(oII>I}Kgb`; zx{xZcdoTohbmy^mLu}Zx`yp)5opr3+?{)l1aWkt|Y{$0Fn8229S;UIZ;#m})5lF0L z9DAOh(-;UsA(h0z;_97{aAGgTHrq&7=VF0UIY+0goI@YS`H+fV)pTK<0u6i5F+%Ch z)PK$bvgq_xRI$N{&bXXH9A50icYe&HgXw8x%@Q%F=}sfd?B<{&1#@}7%T@AZ`5?j* ztyqhs&%}7&J2+Y4#;%#ygL5l0UdHtB^eJE%Pe)}b`ED{KzyHIfVcAz7?n z%5RwE?oVv9=d$6ZENRNmqq~ao!R5wPnqDms?bw+~jJ5{j6<-^`uFxGNN?9<9 z^b5eI$YV}Ur?;#G>> ze~(}kxEXi9pGSOShnce~>cE@X$hDGI2sPM8EDvjPre#&gB|@_yVB z7}zom$15n%!0=>nsA$GTtDT7N6Ta`OwGMYbc!N47jKj$FT2vg7R+kF)!$0 zaxDD~e5!E-mC7?@>if!_p7 zLMBR(OR5s+XOAT9`TGsCBaTF4b~4#oHjda;JE9);3X?n0myG}O8MavIk!7p?V2`j; z=qiZAFX!kX_N4{{d~qS$V#h_(wj_V62i7~33_H#4GRD(f zX#leyL^~Ie%}Sq1M&K1BTRM)q?gEP4Ld znN(WxdEXITd{M>;WV`+$g}f5dX?7m9=YPddvLnefC123fHQ{!@pMsQ)zj8nS>Eh;j z*U>i@=AbZzK1b z_A#TL+`@sHS@?;}4n}X0hamHYdJwVxA=hy(hJz(bQBvsxtTJFtCdi({Av@+_u?sU9bN57SgYMv* zsskka$tp;#EFs?a{&Ix{>M-SiBMI`l0H(to!q?m7I3eE!9c}lKo2EYz|BLp(FB^Gp zVPGpRllYCxUV1`LV5A`RNiUZYeiCd$i@>Wxm1{^g#T)jBP{yo0>{asx_0D~VUj(e; zsGAhpSb3dmJy6Ir?eT}6!5kTSiqep~4!r&>qqbo3XM6gvZ7KSEgfZ~JQ z;O9LP#pp(p6FCe%$L~#@(if5<-|r}LqXD^PT~0=P6tJa!60shbNJBm^ht!NJZr`R7 z-f?QeT|Hm|edoWE1)XUmaCRa2H#>;RDC)!T{w!>J+mI1=c|z(^yC9E$rv^D3L>FEq zk~QcU>P**QzuLyq&`1;Zm%0@#TUxN%FJuz5%3vy(^US(T$!Y$KkZzbdCLJL;|F*MN>pnrf^aggT(;!=Dw1w4Et7H{sC9`IA%^>?mZ!m~6C*68hEEA2mni;+63@*;`tYPDcX9eYu7z#;bB_(^Q8QzK*Q z?Z*c4dpKd>CK{o~XT1GSP`Ug4G-hu**{zj8pKGbMd_bbTjoG~8Aa=exi|rnXhujYzka+oKHutsIw7)%JoxZLn73S(B+!>| z>CJ4`=lyv0+d(llVR#o?ZLPr81l@;cdZFywLf*Hcpu`Sb(B|_as^Ao(#V&4r4HBcr z;?JRPz`Jh*tW~AxiBAmC37(EWFL0pk6M$5_Q6(eSm3d3qZDK^&~nE8378l2N7V&Gm5I$OPG)ddX$5vh3`Wn_#wPIg)eQ1cxIPVcl;b8IXT~ zhct3Yv9u4lH_aDryH?b7TbYB&T>(_txPxZ(TUd7BA817tf@j4eXs+~ND{lA0&q>;> zlk*t%%zvZUxs#@|QthGe?86Ao>MDh%`4~-kd!Fc(D-g%Z$wXdTkH%zwC5ilY7XD_V`X1@g|vd>>- zvBp*5teadUyHH^nd*0Q6o#(ZM)fdH)jTNh?hJQ59UK>SoTi??e^L+SyuM_3DTy&eJ zDs>l*r3aI8>G-G$QZ6%v+}XXJYV`09>kT&SGzm$(%)py1zHG`im9!AgO)Bh+h!f<5 z>3muqrU6y5Pw8DfaZzmOYN`+E+>5l)klE@A(x1K)Rks?V?Wl_W?G{J*%JV?s*IdY& z&l2g#J!p#ATzsk89<}|P0EqwG9gVW{zvlto1s>S?p_R~U&L{{fNy8>usc4jf1&HEA zNawC8S+K+qc701iZDH5(E{o4-q4i0;DsKe;9*Ba=v&)5HQ>T#j6MUYcqYNeZUB|tL zzn~eLqM3suhUC%89u(<)fmxptNL**HCtJ@Xk+oSL@b>|VJwv;2rkxl0Q7{=~W`!cX zl3v04jzwtraVeI`e21g^J<*PWNSxdph;2VcA{6JzJ)F{se8Z3^dI{$be=b)lJi^P?2M zFUbTYcX?Fqy@brF&xFBPD%ho-Oa30$hv!4eiAahm`ol;XTpJKJ{J+ZfZVt#2h6+UkgmD}YZ#OYH+#fFaC{Dt+P8^( zzn%km7qA;zrZpnA9_9?7T+y zYRpCIo2;PQ<2ZRF(GORQ*TR3NK9WZs`pj29CD8gWnP~ULG0#i|Fjez04lOW))Qdbs zt0A7eEs-T(8`h$mw%dskpS3@(69Je12a0_r7;kIgG7rF{7Y-7=W;zVxGeSt!NoEJZi+_8` zd%KlTwh6;;l{C^BaRj0b=dyz0Tcq6659WUM=I0d?h{@ueV17=Xm2p~3N|#oENz!2m z|D8j0+f~^5Rn?#x#(O}2orkx>HL$ulmnm8D7y55Wv1!Vxpz3g#%`=UY*ePFe!0QY)*WZ`b{!&fupBjO?Mrv$&T_Q;A*y=M zh|N6ZMjyO>L+#Rh*bu)}v_h|)nl>A=#%2pCDr}@J>nzxc_W9IO?;k}~9_-Sj^)&7D zdwS-#IlJ86mPUpP=?9BQw%1yPuD2ddFS?4@_;Y{A<*z1m+{jh-M^6@+v_h6TC6uzW zZKt8Prvyan{5kf=DG#V~KTWEirLcdkk3xP;6`3apV?~R-;8Q>w+4b6!jeVTNUH5gM zLu3Z~YGWO?8MB?V-rL1K)sQ0V=McJ$j`YgTZ+3$gk-^pRvV`SC7zF0Om5Z1v1L3T=iTd@Brx&>+xX)l=-ig0E8H^JjbTM#Wm`tKciFN_rKCW8 zSv5TopvYR(Hi6aj6|^BGn0-!^*)-V|^zrdJcC#{KGd(Qnk=GyCpR4Axuk^=JttH*8 z?|;=WAtIU9HZ-z-XL!Se7tOTe))|&DlYs|LlA_STDt4~lI%3+SBZ@j)%$}(|O6JXy z7a9C0WP3)hXAYe2q8AQaU~fJ+Ztv0wnZ3_{k{(fb*RqHL+IVQ_~OSy?0uu|6s^lq(W+70zU2 z-&_>vB?Z`bR#l^ucRn$zUvFp9&^C19oDo{HEDALS$wGjlG>i|Bgfj*WXpee5_rYGo zd{D|0&Tb=I5?RdH-jU#Wq3`jJoIZTqV+x7?Dn?x87m`!+Ovp*`X{4X`_5L0gMe_O+ z$jkUdGJecm+~QM$J0tXXCfHbrNGsz$)!)FoJ8fWtuOTizHi%yo&w^pSH>kI&4lR2o z4str4u;u1NFkN&OiGK|Qmz_M@?%59%Zx{<|jvi1Ia1E(tAXq7`3mKQCkl)QJ6fp4& z8gs*!+30u&On(+P}GK)x*d~RD<@ejYcIS zqre5}K!>Oa6*^3X(6dH>#?+%NR~JD4d=u!=9t8^r^kA*W5NdaPjsE7BAa$;voEHh(U{eUj?L;QS{n=d8+{>uTY~>{Gz54WmFzyA1XI_CaoZ z&q!~*G4IqEMDLXlSZ{TO_8&Zka;-Ud_F04EPetBaX9&|CZwDuNH&|moo%a!rg{@Px z;6waBG&L5Xz%>#e5mb)UCz+y(i9-nS8HIb}HLzT$JDTvKlX+Aog?}mY9XOpg+?WZ4 zcuVshPUBpW{rg+-_>@sD7d=P7G>!Mht&!RLzeI`~WLa4uS4ECNoTabQjIm+Ul#a{dC?0c2eQGJA8twg^h zt_)ViQhIywSU#gXtR>F%L`on`Svo;W)zd5AR@BDrpu8Gw7`}o&6w6`! zavCapT!ECj`syTp#xgV7TbKlk(VTYZ03)5d79H@8M3ZPRYS{1yxggSLE|x0_w8ndofjIfGA{NK zn&t1|(2_!Q*Hs5u_2)7VZGF(c`ajHN6;meG?J?KK z{*HMrc^U~eOhXCio#4o45$e>QC7h!^DA?atiPqk{!EDM_L>X0HDEw+J({*|qe3l3xOy5dE?kK2JZa+cJf2~vy1&AZHBtDhX(XfQwwoJM9)_ns zcIA}*FNe_I zBDqtA2nXF^%mYsf0-off8)vdP!JHy=@;&b?yf#K} zeN_-LJQ3Y=Nw!PQ{=J=_?n8kHdB;chN}{2xY5cnU&8MuV9AJ`#OA9JEHdkzaNX4v`!M zx_f^x7w(^8+U8Ut=VSWN@$Cxfs+-OQYTiN%)(EKU#yT?P(iGD6JBM!E#QRw43do=6 zwY2M~0e$&6m3$eSKy=0_(EYEX$ouB4NX@qz_x!C#PXz}d^<5quJKF(u2@@eRW*MKi zcmukNvvI^l3SVVy*;}@}GmO0o1_$P_L|jdJIznf=Ryf zEtI|I4n*5m;F)qWAR*%-e4ICdsD9lJR=fHkRC*>E-gg*;QbOo|E>C2mm%#S&A_#pR zgTJTigTlRD__TN`zHliT#9y?)yQPQlmDyw9)U+!2n4g8ShJyf~Z3NqLZSp<25iK}Y z1J%58`UP5zEv{aH#5H5l@6kzkdtWV_^lQf9(F|U(QUEAo0a?nfLrI5hK(y%py9_x{ zU|+()z6^5hrzCFhs{t*MH(?_AB9%=vwDnxWv4^&zAXXYQEaHet`8w=6_6Kr}t|K+x zVq~LO5O-njeKO1VH>Y{^ouFlP7g6Q2IysV4@YuEdtUK{Ix#t*x#}Dd}gaI$YjPXS? zY9z_e{ok-%@_$Gc%_aH1O62~UnSxb!R+HDExx|0|2oAUxiIZw;F}E+9%idm#outf( z!1W@Y*WANZElxy|Jpb$Zs(O@Oq6lT{x1+5_pV5YDX{gZf4 z6(cp`w&XBay-Yz_>N>=F(NE^%uZhUEbt*}%D8k>fY%mp#;4NX&XjblIE~ZWgD>t5p zI^#>Ic*b}_${1u8(}AA9c!RgDK8-E29MG6dS@P)dLE+nOX|(CfO5CNAj9T*CnY|mY z&!2gZJyPvJTE7kj{QSgN|5^xVrJdnzr#niLKLSyc&!Zr`0#|58@jL%7 zjOOiA*ng=m?;DP0I+RuuZ+m&h`nU@c$+wbUd2!fZ{txPy$>+r$>%#s%W7v58CfUS$ zc8njtMfYC!kU~j6Sgd{sHVz#im4Cd^#GW-kzIqbdze)I1gFCo>Rv?RY9k9bQX|Q$v zg_ZUlMQ0NiL#RVBZW$+wQiKoC_+y5o>$3*ZRsD@VrIZopHz$#r{9-5*dXQsNAA#Y2 zr@$oYGI`9wg zg&{1NV2Z6Wb|R_W3h;V^3V$|Q#*FZM5Y6V(XuANo7bmA6?}0>Qxqu~;c;9H1t~Sh^ zIhFM7$wrpPkD^RVPcFZ4FZz(*#DBLkc^|33pk)5;`YaK%mpq|k^+HfSaSU%iACLB( z*Mf1k#JQdNnJ6}V3nbe%WAg?d^o~YC;`2amanVICBvTc({m?-pzDIFp`(k(hb9#AIJ^< z94b*X20eSqGwr)Z!O!4n#L__wk7<)V@{Vem0hKO?UhlDb-{YEPtK5H0*+9YzU)p@j+c%k0)kc$1{09IWFu* z7N@dORXEB`0oQ*xSL+>RiDZ6ISJ8HWc zdYLqdNr*c#2I<{hiPg{gqOtX3QU9VXXj?`y)BEh2piuu9(p-L)DL)~BcFa717Q2U_ ze-aLYLpQuodmW2R_5`7z$U>AKErk z9qAS_$ok(p{@!v2$(%4ji<(D)>FE@7Tyq`s*&v@^1br6hx%c4MF*3|lOa5FnX$&40 zR4BMNT^Zc6VzBqKEnMr^F9;-dlE$J5B>U~e@a^lE2i`DS^ zf&{IN!z3nW4(wPZPpy7^C9XQwptf6w?)UpnT4$|;?ZLXV{Kr31r@sJZjMbz!&;KMc z59%3Z4GXFf^Ns8^2xcA;d)ocx6;YTLg$lzBX!JWF5mOT~)?dca1NyhdOr7{iKF6ka1IU`ji+E+R27WnX1qtBKO>zIm zXz5YXJ=EHvGOZI6L%fBz%HNDXJQw^vj=>!y; zu7>#2<6*&}bm%%73_pD)!8*|tnEY%8WR9~2JvtFQT`!^4HESWNtr1yVwFlt2Q&b1Dhs?}#!eOZ^zj+`#QPXKcU?j* zi|(U^f6d^)19|kM%pMxs2hiN6BaG{X3&?uh4W#!Z4$WR)j#e&nLBBjs2v<6AXxhu^ zNFweZmsTE$3@2SiD{~TqLqKMnff`K?0Q>PpuY+S88Ou?Vc){1%<-Xc)U<6G@?~|f z`Lz*r@$xb>&XYkEDj$*F#UdoQSHSfYS~2^0zU$1x+wfSaOm6nx7RLSUS^Q~tAi}%c z(2EDw+~&cjD962=>yMj`4JS)tH=QF~)vtB<_?IZ`@arRIdGQCXmYRw2GF9%oSPNdD zq=_e<5HiWvTd_(|5JnrKad$v7R)0e9yJcg|1=URMO zdMu%ym+}6d9DMfm1|0EdkZF>X$GUYwZ1UR+t1xrKDM0siL*$Wh$jD4BidJiJGwVr@O_Fj(fF}~Y4XrUXOb3>1bHp2xzdpQ zic!QfZkuv_2~Y5e;(TuHR9XB@IDv@FrsJCz4`Asw8B)5?iYrSg$L@Jj#3A*gVBees zETgd;pB-()&zbWDtziK;-z^krC(mHk6c=ILqz6o3-Zw5JU>q4|x*4x@+Jt>B9VAul z9oQjo3*P$dC^8&3FCk`cHR|JgP$HSrcxiho8gsZEPn}&VVvDu}(Gwls$ z`N9XyNKD03p7V~eML%bzfAGhJwA2)w9CN8-gxN1#gIgwFz*r|3 ztWJg?sd)!*r%gJRy)_4QHU}}>0M8_Rr3U-oxFds{GF+=4bRX&k;L}F0j8^ z6oB&3H75Du4K(+m8!omXg2WgW`aRF1wK;jn%f%H8d0vQnB&los8wl92oLnD>B&&yn z$dFw~2SWwaL|+EQeh;L_=WnLR1FkY2|K`$?(QfoyOfr(6`IS`aj;AH{bGVI3@_be- zlb|iqP_nBVPFUxo>P6X`yObh*g^i#TH$aSh3(H$(G=-*xR&Qn z1^ygC+lz1GN}JoDYi{K4a#5;@t^cAQltp3oQ?CClN+| zxKTKa7kIwHCOorJZQo2HxnVihn8$b2{Cn}1=x&^9KZxq;FK{QiHsWJp3GjI5es0Gc zW84uH4bD?82-i(*!q;tmU3~ewH=7*&&Jk%dy542|D1(<}#Z5LvYKsd0_4p zg`Vs5F@wDnnJg(|T#Wen-3lWvLHew~Ypokz^XwW9ES}C8cSYfQqJ+A>Jx8&g!EE8y zpd7qs>uLOvKi8iqjYd~)o08XAW~6=^;*6{&lS_Udv69Su!4W0iQ>4Xlc>jEEr;Z%a zzt+p-uae=jRthBV^Jt`C*~RH*OeZNDCLyzq7F4T$2H&$fEO>o&E|@*5!e-q|@L&5y zpt)6vcztL`I-3)@?p4>Y#{F`%(2L`WT9)F#b&X7W+85?n%@@2tRS#A19Gz9)W|N-p z#@y&#YDD^6C`rAif#z=g$E=Z8BYunSa9z5XlakvZP{=ySiMW7xd$>}yy zyBUD96`hcKfvh0+zlR5bpn|$dImZ}MzHGIG*rpNKv%Id zEXwmo1Hxp~l$3+q19XJNisO)N%S^E3S$0x~7l3WaG>EBCg8uIqakEw7+0VV`wRSY0 z>3)dZ?Cv7dSuSw%2mefle?+1eh2TaOLFnqAD2YUZ_9GM6;rt4fKaGK;B73-bY!{07 z@Jk>*o988ZkA@YR8T^@QEoyV`NApDw(Sc_PC_qOFc2;eLujNW`L}?|cejebtmz8MV zyD-=?do1K@%0tUxbC7$aguh&iL0f`YnEsWAeE45RlH;^sxJd&|87l>~TR)?&!pltV zix|}3`W2;}Kf+v+F+v~2=fgey1^DMODS_>F6P~xU1sm;Hh>sldgvFIw*mJ>q=C9i{ zFxl0OmwV4eDh0d`=>9pZK28SLhR*I35_qIDAwE!E0&OU)BQY>zTpeqX9q#m+)X`FH$g@M?N9^9@#_#Mg2_?l&=i$OxLI<_rV5_S|*d$xzPc8`bye;_**=k>!0G5U$?K ztO@032XiOF?Quig(5wiwtxo~Q?bytFWA`zf;S;psn>b!}ZwxxUd>d#aZ9v@{FQBpp zM@SC8!R=~Q0^=34!SaR~cV9XM-5ft1$bswJn4M#|lqKR2@kSX}Wo4qFQ!X&(eXQ`F zWG&iztR4Mq6vMkVJVixDJ}@~e8ux5TKm#8#L2=1TCULn6Dm%6gp0|7!%FjK|q)(U+ zMy>nN=$(3~-t{fobVLJH&HT#bJ=hAT2PDz$n~vOE4PTgL5`!dPXkq11;*cV3g0^ls zh^)H#zi;tjbU(Tw+crtC`TdIN%}PXDs~n;0U>@#!c>#@x9fHw0Z@6m@7NaMY&8YeF zQd~zmk@}Rg=+FfYTl`BvbLWi(_2?hmhe;U%i3Axi8CFA`qaO;Uc~6Fl3JEN^X(sYc zp9gt2zhcRuG_Jo}0SeCM;0ol*Bo>K~=A<~B>+#G_k5X`cYJgo1#xXPgtO4`b z|4?M+R3=Ep78GUAGAM5{3Y$8D>|-o(>Lo{1`*Q&t-9G_mgvB$?4w)cjxe~vVPhuX~ zM?=ShU_9l66B?DC0ukCl*rAED^Z5P^DMSb3GbLM$VGkA6%#b?#sk;!&G$L#n4d+bz4 zegR(K6W4(U4t!-Kb|%Bg*DY9af+iMdZiI>=U9u};BD!t81?GKujYl5uKpspOh#kto zsh?tn>u1@6wPOL^89f6>I>^DqyDxBY-)t-uSjn7!K9&e;!USQO-%#&!Es`X@h$($@ z7F48e;<a7Zx&-`sbZ%be^4BzFuseXv}xa#Ifc=Q$g<>&J2v%0`1;-W7b* z{2&wZ)&x3T%W%}zQ=IHcODNi>Nam%aaBY6EVAQ*W?E2Zpk%FaAdcuMXS-xWI1dD+^ zI)W?TPGB}g1VQ|V0(`c60v4{afq8#kVYm5rnOx~}uy95K_R6RcxF+#g1zA%(jk_dV zwedSrx%vjTKOAN(2iL+So{g?9mLpuTT^)LMoWuiVruII!T;Z79JzR9o1NoO^flWXz z=4QQOjM03Uta$?;>n+58S1ZH&lp6ddqLfE0RKR`70xWa08oRA8g%3{-Vx|38aYWEk zNIcn(k1aZiAKC@LQl0fJdg@cboJ#L&~1E*NXHRuus{qy+eA^vi?m*TM>2fp4A)oJy;Ix>U-OeeUY#Kc%a()5!7>{@JZn(m z@_*2IB$vv!>XUBXy{Eb>g-X2S=N=Lwm=?Z_mfM{n_oM#68iPWb|5$})$mT$lWjS>| zu#AS>8{wR`?4*?wji@f?15G9+G=k{TjngZj_N$QkDlVb4-VM&@*3dW^Gx}oP7?|J_ zOSyzLVsOC=B38Q7RFfklR;~bs-8JYbzE3|^Py(0d4v-&QDh8AJu*O%KwhT9;Gro@@ zZA%}CJEM#mn9HDY?II5NB?~Y+b=23cjdmAg8O;p_G;yx-#^(umE2K}50Ui|d@q zw>|K8*8_(BzwJh6(A+;XIKAVpocpc-)wdB#X&pybcy%Sx^4&}aPCt#}{Q`mJAIQMR4 z?$wf~X!pdU+~VaYISpAM8sozu*<=@pA25X8AM3zD#{vq5KO<${TeY4%Mw>f)KyTHR zIt`~(6yv82cIhVt>osI>bf*KjURJ|9ezgcHM=gMGgSjYj!F=@GVH8}clSK_Cqww}U zv!L8|EEvSN;LkIS(eWyZ>T}F%Rl?4o9oiFMSbG;b(cglusIEeruiVG?_e#K$q!YN{ z^%PQnFutzubQQA}@m|CKvkbIEAil{t>lf=cjkUvEH~%bJW@RQA$Rg~2Xnv9g*bLyLwUa>z}hs5 zOp{WEZPQ1Q-D?Vn(jQH5_To_e%tVr~?hz7d4Pv=RSBYJb9V8!>!^Yz$lkij>=zn<{ z*9V^_fA-2i>y}ek<$DUD2C3JFUY;xywuGr@8S(JQGWlcj@E?04X+LV2 zkbglC**n&*a-KA~H)}0_FT7@dNYS21yo`Y|?L;tW`H4r1$HR6_1QsL8(0()kI=+8G zMJ?fISJOc__jL3R(waKrDD81fEC)wbn-T@q-&I z?^K7fe`7#0+z&1{?SkeY4=z@L=XICgggfRe*jy<q9o4vo0%b#?W+k4ms zMX%IB;aQIaI~`1r&e$kai5KJ4ac>#B!{bnwO#to^Qx={+TEbLbnuPr$mUAK1LCkQh z5&ka>G-?>hi#sS-J`0xA$xPq$=?N8e>jE`cC^@6M%?Co z%i_pnXb)<14Mch~FQGdcZ8)E+;C}Bk=A_(n(f+;8NV9tnGJlfBP9KAB3XkdPL1sl6bq<;7qA_vhDR!T<dU9Y6?G7}h-%_X3Us`rld4n=l^;4p2v?tK^x`hOGoFz~Cipa`meQ+&RMJ`@(_DQSJGsENTRj=iqw0( zB3ivSi0CdN+`AWk_OZQ67J%_%cvYtkLr%azYySfA%?C~aeRcB!(rG7Q-SogjAT z-gO9yGGHAPcCd!s7vaTLC3dd2H9M+$4HV2XV*lLT%8r&5fr;2hI5}=1+cq*Aw*BHD zZ=5CDX*~z5i^^cohF*b1rLRfHt!^sqXp!Z_Dei}&Q+2lpGo5Smy=yyuJpJv_b0tdEY;i-mDg*^Jju&k7s|-uwKECxvIAL` zZY9)-!x^DddB2?j`F`~v9yVaFI ztxF{#N|Gd%M3V;1k_sW2Ns^FENirqFJ$oONB$ZN9q>?lk()g81^__pf@4CNx&)s{i z_kA8Y3Hn1l85<8Dr%#5I>88RMT-T#bN3ImH|0ur?>U~ERr_aQ{@758A;$E7$Wj+yJ zn9aJjQ<}2el}>FhLP`6H%#!5;;IQ>HdhJ_+#pgM^{8|y$L^wiQ&R5vdqs;`5mB5xK z!N|S!hgd~1Xc(Q1omIdNKU1R51dB0Hst&qrW1%yZ-;X)!6ZdDUaiVf6iY?~PS?-#c z@_<58#t!)M%$i6B{$RCkG{Pvo|JcXf2g#H@4LC|vhOc?%=D^yubfR1&9g4Dr_d~Cl zAwHkN%23pKK9kfQZexZc#?xL2Ic8|AHs)@Ss!4oUFNzAO1xR(TsUO$Da-(PBu@aXW z#dG0MrB}!PeSWs4|M?N{>|ex2DMirY#PR=nAId75XYR#4Y>4nFE^^ljA{K` z28AB>T=Wl5R&2*ZcsRw3DtdK`vZAg*_^Kq*)oo73EFX=U)jFWm=1Q72U4k_~ePLD} zN0K6~n47oCErRYotE{s0VAH3M<8wrB*a7z}&@%X4J>f+UDdYPPhN^Wew?PZqE!GgL ztW#v3wEyw;f}>j7{OQKpH*-!_O8)*4z5BNx{`PJsUD1Prf{j|R@Ah?e&+r#f&dyTcV|OVz|`S#@&rbOq0bSrHLe10^ zNb@gZ_x+vBN?>B&ZlWnWX~}@dOTS6*_xeikm2qWEcV1yO7R&Sf+iI}( z>l7qixl`RFd57KCt|f5fU2wM=0-=7BKAC)eD*okNX6;iGNdj|&dFbxPzPud7J{h}1 zpmSzAyDxnNhDwW*RFgtjw$z99^3{T81&82jFpv+1X^r>6jOU5>EHW&);sYXl3Pt|UpUggxl|UF6+)gn0chAdXG( z@EOmtJJ%I4CW5Deq+M^wUVaC5%y1I1|1L|T;@-j5H+9VECy6BF=ShAykyq_7J=Egy zoFr2Fb`q>wt`EESZ|B`SxP*Ks5R;mM-A z(`LY;jx)r=DwOQgI|o6%o5>+Y1{}`Z5!t0-c$dM?>AJC_NT9nDI$Eo1*{iXofBFS5VezLK&VDzv^Xi+ubLM_lj4 z619vvk~Z}|o9};}fv^VfZFgk$R4d?^Dk+o~p8!v`g@R4NeTaN4D@ZKf$Jp8QGC_}) zG4Zy4A;nM}cj-{UtE@mjZ5u5+NZw|L$E)g@_sblJeuy6 zd&KOp&_I=6r6k%t0qmb@qrCEaa`F5fV9Njp7Hd#zzfcV9OJVXa)zahQ(fHl&I1O?! z;}*8XVy;;S;;gmtTS*`NrE!A07vu|HYOQ&O!&J^W?KCdGVUN|%_tE74I3|aL>HcfouQj*9r4&rMWSZ^fcBb? z!#Xup&|Xna@mM-11GD z>*YPzn%U~$c}14X+q)a>Ydzsn-d%eAlLmgw(FX7BxzvY0Ke-Lp!A7M#I`cn7yK@=v zVQVVY(O!lVIVE;2-%WPe+z5f&&B(a@B6=ip9MO3Pkn$vo_9;1FTXV2LRw899e!T~4dJ2ipX_OB z^u-sPnm1$izBx2eQ4GWM=@YqpRSg|^W*0!WVtsGY8 z9}A;*?dHK%T{VE$F*J613PgBcfys#~R3(2dRzJ7}#pi5jj^boA(p!Nsx=Y9+of3Hb z)f)}QN`j(`B92*WjI$=IvtO$}@O-qHIPM9@G)C&6-}f2F?o?uD=WC!~F7KlFSqq9A ze?WJnC7&&7gzp->w<;tZ-si}J$r&Fwviqq>bAmDxs+!3p=cGbl&HyyHOeOk(XF${& z2Ne=i$OYv9D3Kir*IyZsKWFqw4e)&zWf>}U_NK@_?HbJN9N?L_qapgkcd)6-CbzE* z3jD2}!MMchI~l#g9~9sGF-t4(VjpZ(5m{Zk&j#0s5h_4Z^x-E-b8}%F6f#6A%8$v%pkhXB zU@Y{$HfI0+%ZL9yTxIjSPm)^->xl7-ag4M+L#NNGVt;Apv!jBwY3Kx=rL3LJ=00{K zQ?_)m>$o%seJ%m6N_otE?9Eg5jN(L?8UI5x(Q6%NWIY^Yab{T@6MQg={c~dwLY;Pje~TJ;UQ}Zo3Dz~U>p{0#IlGyK?;`8y=e zpIrydbq`BZr|)-IW^xWwrzlR1zP%C+g>Qjf?Ubag^deoi28rC)Wdy79$i|E?GRMuG z1cix{uG1IE^r_2^akDO*CX_|2yRIi6Dy7D$`= zOz0=u%Br=0yNI|$hTx!Y2vEtdaAKD&wiGyE{`#pHaBKkz-f!je2`k}XoIdZ`^~I>p ze}aRC0(6`*jtStpJuOPzVP?3;gSRFD4lHbbmIch z7JEwyH0;4jy#gA;=LrsU<$~C%1CS&AlF0Mt#QKmx_~su#LcfoIi`!(7oL)ncR!c&3 z_bhD9C?}uho@U<{3Q)H(iIfJGg21~AqLx&VOvxW?r=%suA4?+M%k$U~jfS|@SB$2- z>tq$!$#^8>5(z9^Oj7x*_u6QE>UF`51Q%66JgH*+2ZmU~cvJlTizT)>TiI~_E|9o( zB(btHCn{5Tjt8H+65AZgWc1|2a9RvI{hrq})Obe|m||7C#Qim_P!)Q(zP zuLj|J5AZf0Ahjmh0u#*-0Ja@u-5vwD&pRNOJeHu#ol8LMb2=MwH9_=nybgx&44I{W zcGC|3xwyV}D_QYT9u9pCA)i_c@gQSE^BHBjMkg587S*^wI{+7dHtC86C)rv^+4iV147dB*0gN8Ly=wv+`1t(Wv zb72O2U)f0%JueC#zMlu7=RYuG&#Wh6s|k58cL;8e7?08~$1x%uG0^HtCh`2t+1|01 zsAYDLmNj}PbL0ZC5Ocw`_t(=Dxe$=Hxk;3d_D>2RiZRNv+g1QKRj3 z*7n&UloT&vH+#6#qjKwU{Twwg-5NlI`xJ1h{v#N&6fjOD>*@RUD(Jp12lGmTrcm5%ugrREkdw)=?H8sY$L7TqsWsg6AW0S2==yX&`GObq_CCm zG2fIzgQ=4#)e~UzzX90xrk*SpDPc<6G2%0_n>37^jmz3JA#Ft!oAoY{bb0IHIz0z| zcKsEG^xd%}NF9^vEZN-(A4ucoyG(%YSyUYVne~47k1eg@=cl~~!Q3*6{nyQxqgPr| zIWN13M8U8l!(a7enxVRiTg@}_(aIwh*}4hBkgsI-!b)xp^E zJ{le#^2IipRn&9eVy5H5DI%Yyj|-L*qSFBfVtm(;4K1EeN8gRclJ=R9^`#2A!zXcY zv@B?}XRxOiE}^Hb%JHeo5t_EIkGNzmKq;HA;2m&)K8wplhpJY#(WD8Tj((v28)vYY zU!MY5pMq64E;6$hw~(PH>mVzn9c+Fcr4v-=kQ!-QbizWEk6BB-3)@kO?`CFvK%PYr z!g`i9k}V@Mp*?0PYU(QBQ$J7o=6ft24=5n1VX1f_T0%J0@-}0=QJni`S%WvSd>~`p zeKJM%5q6OY!tXnCX~MfzLXRaz!cOgh)yH&f?RS$49nXj>sP<#Ay{?N@8px(y>jYJW|= zTI+ssVV#EW+S!6UVSehcfg9naKM1%}lM4D9Rj<+E#U4xXu<{oS*!H~Lpi%(tA{i7UM8 zl454pN|Wqb+l5=|PDdHk-a9TKJaD`Ue~zic_%ubXd(LZa*T&2Aa|n>MiItpL`xDY@ za{)Unov5ZUK{IJx;_%di{dhG7-GW-FcJv_4z3GTQHPqmNnK$+OycaloJJ@?>6iw(i zgivV(luz}f&gF|4CLkFn|2L8O9B_nW84x^^JQ+`3_aGl$cB9W_M7ExRzJPD2R$qka zR+jix&kujhj>BV%uVZoE1hDJ1g_q8;xayAzIzOC*f2<<0W7SFuQ%7)qkNJB+`Z%tk z%Y|FBCz+baw;c8L`C18%f6V!{jq%^ z8W=r;6Lp;B++HlC%}#dQolO~>)vb%Px7?Te`>%$pc+f{*C7W>?`ElID@>2gHcmf>`UZPzdSLpEJc+?7dNKFlU=(?iX1$`h zZ+OoEzqc=!dCu$;X`-t637ob_3}@~WpzFU%K7)1@>TaI`wbm4T+uje!g*(vwD#5ST z4CakEiqrBh;WvF1a(+QHEMW)HX%jz>d#8oDcZ<>U3(tI${|#$>wa_Za$D(JNI@z(; z3H$u2At>jT1qn&veVESV?I}fYY`Bg8`S5 zX>-#W6x%6Yz2%rZmHl)Ezc_Ee%pVa;?S*?gjy5y!G)_cOK& zrVyH z%3#`uG=3Mpk8DtH5Oj-WQrqduyazKKTo%rNvpti+neRq4?v{ah=Y1HddFzQT?;Sq4 zK##(N-Hg?ut0LVK$>hQL=jILf%GsWlhY;X59!pQlqhFpBMjCx%PjNp%V$Kuza=4Rx zoux(NpYXj{vt{I;*?(lh+^Fhd^Zy`l)^x_@atcIN{3RKW2)W-E2`@<=+cj5BpfztF zv3RC~T3=ra%mP?KLv>k?BTh8?h8+K1z{;3DYwPqJ8kQl1H2Ekj!hpMm+^C>nkA8A;ls2Zxm8=zl+!Fq?J8 zV&0`P)`YPkBlHh|T3;J^CfUiHE0RTf{|FLxe~pBC|(x zh-`beAi!=Cof-0wG>6FGf`2+BDB^MD76`||J&V9UBY}=usf*UzE$IEDE>!H@Ry1Dk zgA1QRj}(zY`CW_a-y&#fSNr5e{G|YXL51@A&M`I1;5D z4c~aC-Chy_VIP*GSYZ`vN1D>BCjo=mLcFW6glGio5#0(tk39~k#3&uQ<>+dXArnLO z+g+&xI)IPi_!`aBOyR%neM0I}O84=ecvJOzT&cWF?fHkdgm-c@xwfH~HMZT$?)Gh14VYwAGc38sB?BVAMOC_Ll!dXmot${gwHy$?ePHo#1SWpyB`Oyt) zbh{YUZ7+~>A@}*u!5Dm&!#^v!n@P~b$8e%ombP2Uaks>WkxaTqk9ysK%{4i=@Nf*x zw3Wo&*GAyl+I{qc;ttxgbQrS4BiP=SBjoJFOvvl$BMSSI10YmA0|virap>wdXbd|>mYtMDHzpJxh~j9D zlPntW+<$Y|V>JHFFW6^11N*90(5KZJIK5;9z5ae0{r&V3!em2CP~<(_`7f|`RSm4W zs!V&{%tI5O4s=RSr>*P5a8hU#-h3HElaAj*^Kpilp4~<7JZ$B?E+%+TH<8-B%)!aa z(m-G0Hn}FL2Fwy${BP=HR2s=UdUmD5nEqDka?cST%1uLM@jjv%R*9LmE9eAmCA!74 z4L;83fd$F4NR7cZm^JbhJF)g3J7s1VzA20Z_r;aOrYwp?2i;+;%vWHGZxz|{d=w_O zkD@OoFGmBVWw2A*k1p&Nz&P(=vOQ@%yixlKGI7Jic*G{r)k!Isulxc}o5@nE-Cm+D zNj}5b{EuAteG+Z!_tQSV2Ke%gXD?Ri@yxIBj8nvSHs1R-+?Um$Mv|W3wgnjn^J`!{ zEHS!OWQBr2CX4ta*o2 z4wR0;`!g)b`>Sg)WZY;rGxY&KXIPCIUvsFIE3I1B{~uWYQ3wBr_le7-Xvzsf`Tuo@ zIXdnX@yt2GmfT~=;*LRhJt6^)FPTnfhe%+^lD!xgolLi#*$c1!21Av=kZy_SfC!IW zIMdZ1-_)hS-_E;W+Yrd#b*F(jfV#^%fqsqVk zZ(oEkmqW%-tt-WX=1Iv!<8~_580Iq*u!?mKxF~Rmh=VGNXk0#5ovXU(O#%$>gB#Bg zdb+`t3UBX%7oSw&=DtjH4myPs3(`<_`3=1G>ORit)r9!BBWT@sRWzQZLql#PlE6oN z#zku(4Vkf#PF=l|eV}xJS-P&DsUIB!>u=YSblvHwR3J`PTq87YzNIuLKGzkyVjFSCKnz>8Re|}ldJ(oPSdM8=-{Eelxj1ii zBylZ$hw%l$`1Ao~bG&kKpduQ7)P=)&pY6Cg)rr}ezKC}7j)P)@C#X2Np1y4^z#XnB z_-Yr=bepM5M0+RGR>Na3xFiaW{5V6mS^LsF+5PnQU8C~N%pToTYP70{XOz8R#Y9cCMEe^}iso53YKwXISvs{@^az`y zn`luq@18E5BlP#2P2bwD!Dh`|ysJt{$}9m|Un_%ezxb|cWf^X98cE*|xza6JH_6A3QZ~0tJz-H} zeUfs)=j<&@=1r=RxZ6X;4Jz@vP9o?$`GF%hLE*Mv>cY9}^SQu?V4=#ZOr9lm zm$uIP0B5$;K%gOE()fc!;* z7Se_vPQuD8bHUk4B4!t35t?5!BKZ(T)BYlYErodT3RT0*tI^Ly&;K2(KwG41(YkBYrs+<`x{ zg|4%dgi~lF=bY#!T-B~1tSdUfC8|#rs=R-IqgUIp8?_LFY+s6z;0zyL(E}RDS0OVcDHWRLeVwIq>@l`ez-cStWkBDY}Lj zD`=67(Kh^NS_t0l*F+Y^E$oG73~pm*QeU-dDjK~OS$^hRr*($Dd#;S1Z=Qr|v42GE za52REnvSdfEvGUD(`ntfaICU6!~*U-t^ekPIgjIT(wlqKYRpZHKhuU=mY8tKJkS2` zPC5K$IY9USw8i?eGB!GAHX}b{n5`}EwV*b|P}3|6?8tre?|u%Pz3a%2RvG%|oV-w) zXGxa)I8A>}lMs5eSJK6Uj@0w>CEQ-tPHU&NlgTrA7s=93qvwb+mA=ce(yar z+H5i3n|Q|@pT+N^hXZh6!z7TpC`*5f*Q3)jd5Cu!V&9&c#3=K=o;16a_#s`D?r4^# zT>U;gp?03yMvcNuSr%U^o6{Rh+KGO8Gn_bg8@d*+WORzgQp1OvQ1-w`?3tBB9pCK5 zZQ+H~S*w8#nmotfsbMrxYY|=1Is&D)`w7BTjbMenKK=Od6mj;aF#5j@khm-bqmI=Q z>nl5O+4?x#x}b!1ddCRrR_BmOb3%FU-*rebdBB$B?j^ON6)?QPlPLK9CHeEyk%&E^ zp{4i0Zw!Z%Ca~0In=FYLh{f?~%A9PzIbEuL6mM6rr$%#3={e(K?CO;zMc!k{$M8y! zC&$Uz{S~lcM>LT%c*1%<@_@CQv+>S^N!WZvk-Dt6feroh;8W0cCbdPG=SXUkox8{2 z;L-JD@QV_4e=Hy#D|la!P8@#@eMC%O{UEd7`cSdSz2?nhj`RLK1#*0|3Zad{>1zg| zEN(Z|{+i7Ahh8QnId@<& zemtHQ^4^t-v?3haFqx+Q8%6i*O2a`bZN^1f5z8D#3JXq;L-Hz!jNDd)Ss8{j|JP{B zWcgsTX&^P*HiGk5um$#&x6$}MDb6Ti6>^qCv_Nkrw|So)?HjGbt!XTyNy;WD-tmLJ zIQSPo?$i{f#mm!IliY;muM>r8mm-j8S>o=^+hN1G^>k(CWO_b32L8^E$BWO7u~Y7* z(0S|Tavswj;j?RDoNE858o%OWC{|+0=?nWgr;}gTV(5MPfIwz3e=gK`0VxYLAh>A{zlZ81hcXv1quiGBosC$s z!^{{vr#dt5W8V;uOO|MA>CN`rwU8X%bDws-nysn&$^Pl90Nv^DAo6S}E2nUrah}Dq zocPb(XU9(X7I&5?G#>*Mx{g#Iv?B$-rjtGVU1?L}6^QtCnC$V^#P<)!67!)BGP|{u z?BBDP{0=Q-)^FL()*FPf-!ImZ^7R=|o%5RUUi*&Byq5;H8taIQS{tc)yb**r3*NS* zFrxXF*>dqO@F`>@1eN_`0usNn1OBH(%U#ccyj>$1c}a@=O}QmVjBy~lZBLS{2`V)E z7l4h*8^&XU8F7pLDsZ{f#qKL@A)!mnS>tD|@blDYS{n0>wORZY?pR1ujd|L1Gr!|J zVQs|@35v-6y)Qt;B#U%-9Va7~Mlr=buI$oDA#CH?9+9ijU1GHO0^=Mx5#0YIh|FfH zqxi$KY}%`QQey>V#z8+;A@(=f4&!Kg6VE;xI4F7|8biULn^}42F%j#lWF=B%(aWoZ zL@*;@&WEjd>+m@GNxQ`&rSAhorN1T4%S~aFs|4cv8{{^(o3v|8hq`gn)NEQ9yXo0( z;!>``B+8$GRgH3xdT187@p}-V#t}B{%4m9X(p5&&T>%SyM$$D8Z$s5=M@*IfOwJ#T zBPG^Bu;pR`rM)b3ZO8?mk5Q#fYvf5njw{O5%^+TvrqPZ2CgX{jy<~w#2&2x=u%63J zB2UhgLt7fZn{Q!A)rV$sw5^eu+9*rww!4t@5-TRk`U|nyG@2N>`GEXJme@QqAi5XT zd1lXJvh#uw1Qm2cd(9m3dGAN&(4zrnxL%(mlqI9+Io{2*l+@Z;`Xs}TXOjB@htE@^@~yPXeUXZEJOg{~Y z6W7&NH2)7}deI;JKHCxTS4KplcpFS@+Qxi(` zd3%U*?{TJkQ65Cwjv~8eJHwlQ=6rv}mozT&Bh6oWA*wWkJRT9lcK*?ay+!&Imh=lE zCI2w;Tl0vd^<^m6`37l+jmU&ea~a7Ab5JgSE|Z$*O2XehW&D5bBDH$`MD#2Y*8Nst zxC5$GQkH?k%?a$3?cxx*&J(9}TqaBOs^L?{DcEnRKq^!XaQI~j6iwlyE2QShz_DD`=Fr3{8I`yg;b#5Q{ z9SLDg^$i%U`5sK`1AZ3p-3Xkl?~vnyldSQ+d+=}PZPJviLhl60qJHl$vcPXV{Sj=2 zN`tC2zIB)xnsJLUN%mmJzq$aipH#4=;j-Yo+#Oig6Axz(R}kBa;?(S>B!0dTM)tKo zA`AV0z-aj!?4bQyQY1A1l_ZWB_UDrD?t9SOR!uT|%ZPiY6h3zn!eiwa@~fQh$KK0e zck$1K(kB*pD_@fOA05T_Pe)-!KTu&&1`EyEP|LeYZ~AqLGPW})Jy1eK6}gQ6Wi!mo zKf|-Ng3Fu(c~81C>ORlC2FsvTvd`RQlD#4bP7s#^t-eIx0- zkOg%6+Rr3pfiFJQ|401NPQdm*;zA>BMz}^wR_J``1efM~k?Ve9#>G#`;+cJB968!a zZFrB7&SP)nJaRBeAT6vl^Tl@F$NGN69lVry8jrl3kGhGOT<_XmuB|kfi%!($#4nq1 zJF~0koizUJraD9|94DF*Po3{$#aG~?_c>ia&37@aqO zoK$IKW>|vJm|CW7NC|zSelVTxzO*FMjHb>|Cwq8|+0NM0^nz|7QSG$DCl{5VKUWQ- zLcYPvRROr?mMg|?I!txrROqXb0@|E*hH_$ZoN`h$4XGX>G*Eku2j+R=^W~XXJ|hN? zh1&8R#c`bbO?&R!+zs@?8kTnJMQ{fWuBE*XF2UIZd#a-}Pq6%?itvt%tgw!s-~GH8 zkD|wEI8=KQx3w*&SN68ikMk{I=l3kSRY{#|EZzgtW##y6%yic9;weV7JQzDHtWc2? zL!+EIcwrHT(y386{OALjQK&-n+`6Ere;sN0lS|^OuZo%ut5NTvczU5pf&J@w9X{+# zfqhOf%-n_JY3x=Hnku`9etBBUE)Bg1Czp*RkH<`>(pkaPbjfRWYiKho+BFv4^&&~U z`Z2yw9)fze1|TIT3wAayz^diXpscHz5qxcgwBkt^oq334Ud$$&e-*%{U%NmdI*LkN zO(9=g1o%s(9=zOT$f9ZS)O)Y4sKv~TPIdF7`I)P!@wCHqU+7!%vrUbriWRC?Mes%9w;S%Pf(czFS-F*-=NRmsF_ zUJ4lHe+8?o2ErXXO+M`W2)6RpL@=S4jPbo8ay?%Qza9MuS?i8%x`X&;ZVV%FSqc=Z zeaXl-&BVhqlJqUIr}BN}v|*GsuK9I^M4xXZ-C2tP?SMG0TV zT>*m^)nuYi61h56gYGodr#j7U^!G(uS|-y59_m1U+uKpX=Q+bxF*HM}n0UYJAySW* z(KUhNv6BhL?eVUV`^pRx?%oDdZBwRo(|gEmEFuPH&Jm+)gCfCkJJ{4LA|GUagZZ&# zG+p&e^>ew$jIYKDDjJF<2NveCe$`(D&y4?(f)nB_%o`$d6E85PmKu0=#eOnk%S8JA z@IFi&v6e>V+#r|b=3~r3EwK8`nkVjv0u7%ZL~dO_+mCjyDft9i6# zuO0by(U%NGhETs;eSE8O4XTFu{qJo%DA0HbsybIua!?kCo(67Esl;hlBw_n87m|5M zi}mpdBk7K*^zTi1sF(t$9?oM*w1Pn*z?JqW?8h!XXW)=G zmmZ7t!vaZ7{Iz>7y=&P9#u^X7uk|QhdE+FRX?lnB2RYFTBl#|)QV$f4c)|DR{IT%K zDBfYu=b6?;3x3b3hm&t&1oGO1-C4;o(>I+aZ=;q_SQbS3Zun5uE$&p_;Q*_zVoMDw z=Ft@k#L;@wE~?=&nXMGN#VlFIQiC-G%&}@`)E#*o6LE{S2JG zme4gT$KsTNb_iK|n|@UvhK{GFaI0ktjW+o~5*~V>kNhthsX3osJe7i18wu4ih#_Z} zuf?`W0n~=iHyj^Pfwe!|X`#duh}zhNS7UEd8~cgyDE&OfEt){BUTr|}pLcMebr(53 zzXxpjj*QN<(~L)ZCQjRKf|G+6Qv0?7oM(5Q?{2K2NlCY{d?*S(t0{0wSLb8n%#*lG zD~O7%Fr(@7jY;Op#n`aM2Qy#}d!_OWRQ%w(GUL^uuwFz;!b50abpnd{*OEJ9X3=no zQnbAp08bxJV?GN`;DNjsxToP4`R}#`X=&0BMlRmU31vC@>-$<^;=C{1nGInac1;w% z)_utRBk7!V`mwF&3D`LBxc ze%(x(wLb#&k6p#c{tx6*OBSZg{RwIE+2qW@v*0yt4e`BV04ACubl!c9DEDWP1_^E9 zgO|CKvED)qD^!J!aF5FG6Lk((toJDtkyUP{qTIiDtbZ{_Mb-=O)CNFpnIOjF!d@DrcAu=5x} zcDpo?2X3+$bz24J$H>vfMsIe8;3u1BkV;Hur;uD>Hwk&1Ns{X%xOLfl|73_^mPJT& z>)mUpHP2L8nNv&m*-xaI)2m?V{dAfs5>m(BJY4P$GIM zu-5KFnGY1Ar&f#1F1ujb6>oti&mQ)FnTpbH7n0io7dl~WDo(l{NncEF75&k9f$Qbl zVdN5Vo)6!Gzm2xCv5m8dj#C<@%$NfY&Q`({)fecLr3;D$fdVJDV`%(Z2NVM5V{BMF z=AWn~u_ND-Z=>|+fvt(uRyl!muh>P$-x+nP|;Hn2}=5b7ssVh?G>?9>1-#IxsTvz`EHb~2u3CMi=;tX8W&%VL-~cf zp@5&=+)z}b_6tjCbLVDS6#krSc-BS7?I@#VXEb0!>1*2lWfs@ALIgQcSE!ZpcdBrx z0}_cNeJJ^mHk{av&zx4#kM}6`V!9w?V=m2fPo&e2tfP4^B)NJyF}i(rjelKjKw-8P z3E32bftx>*TaK=D6z^VIu-=Z?yJS);X<6YfFF&e3u#O(zsv>NXG9j)8Y7Bn^MVrk& zj7Dh~T;ka*wB|8MoX2;y!?JPK>Ui3;HW63M`+$wBYR-4LiW@a~aJ`Go!Ulrvy3aD%^rtCAuil0Zms<;QolF)8Q*(!pW&c zw5Os1-dFWcj-C0qh&3>D}2gY$hOhRr+3kYb5n5Qu9LK&Kb!1Z z5QHmN^U%cbf2{8J5yFFC_t3LvzJuYYQv7JaGXj2f!*;PC{Q9kwx=ZdyBi$P)@Aw~8 zJ9h>~_Y|UPkQDd*qYSmsuf+tfv0S(LJLdZvH(lBVJ`HaA54mhI_c_N`>}CvEDZ~#kAyd3XVC4}V&;@tFsc;fGr8YKvfDpDX4?iQa)ElUAY|GY zZb9lU?o!4#8ue&77cQ@e;mv)_QdOQ?|4K?&9LwJcuBPCYQ=9N;Sv4AePQ&tMCpaK- zMAz#fc(&&jxpw{;J8TrgR3x2(smb-wR;Pi>=w0G}z=QSV0xkA7E~48u$I;>B2>Q@? z4*Kz4$|LocpjE_Y02dC3nmsCsSCcw1j~Qm=%u|WgUvV}stbvpim^WQkaCRmD7+Gw@e8kC{1U z4@jrSgZj)ScKcf`x<@9A{@WHzJ5Byk(l&;ZuF9olZWFjsdM2FgCufqdaxe919mgqj z+<={NS;W>$oHM@g46f8`kd*JA=|PoP{=2mweeR8Y-*ANJ?%FlvON)U1@>xb5 zrmv?lXQeQ8w=CJ!dy1GlO+@96dH9gCL`UX1?%(qooilD?^2lrads%?rs%B!-m{_#T z@xj#%h#lvr;n$~W@H|8XA8wV#aTVuTc+dB4KCTB1T1p~EO0WSzTj7PUlhIUahsQ5f zaZ~eZXkR)7L!K`YxxX`|PLI#>Ir(&Y*wB-DFWX1M4Bt>sw+^q{@V@UG7h z?-~2vUb3|EI?q_Bhq5)PnSgD z%nc;3k)KKSj{xZ^73SB;@pQJsG}_puLLw?|6H`Sce&=?FG<}{(Yj5e(hB3FdicZn>m>Ac0iOiX=BQwgXr6f_)|DL;X;ezU6Aj&M{K7siS~A>^e_A zZ7_$r*Fmsxk2eH9sux&p{KQ5qzeuD;^^t(d9~e#ZbEMW?lYCR+S%0bBf;_=DaA~tf zL$3gsv+W;AdGinwR@V|K;cjw?e=XZ2jmUN0rh03GoAygfC;$$tmSDkZuPoyX656*{ksV#vafZ zdlBNgvM?gyrQlY=YPzpRpRs-Xm`n=_r%Rn)l1*YaNlfNl2zsDG9O`}OvK?Y*die^u z>wS}o_ROc7*_Wh)%b?c=o{;4`{TRA_0d1EEg6bFJacJ~y5vLhSEN2=(P3sOK_Bxw1 zX(`ihM=ufsM@dYO7SacDgAjW{f_-t9zk`0LW6T!?kV`{NV7=6ir1)u)yutuT3%E}n z->M^?8ZRtfsz{@F;!b9QKu9um_=a8Hd$v<8Tu?O`VcwWU;P!hYBW-v}aL$Xr2Nc_Z z)(&a^(oihOrJNi>)Q zVydFS@WnL}{P7+k+jGdf>u1P_QbnpzeGQVQA0+vaDs=4*hCCE!NsruCQd22L3$rWP zw1^rIFRzCBYy3`S*Eo^7KL6}iFs48MMKhkxM09fHOuDZ3i)ie(6-3^mnr&XMhK&`n z`0U>@@@Cm-Tsd(sUNcQ0ZC&9Qp1ldz-REa}UpYMdD+(oxdqLe}Bi{Zp3yVg2!Kqd; z6jiK4eTuMu&t(kH`-;l@-$1K^FaFrr&U;yo3q}P_Cxf-+*qWctPS9CN)c-3%F7Z8h zPIo8KHj(hT!t`@wC?zTr)6de$aG5nT7&5?m8n{D|3{+SK7pA z$w;%YGX2bm6XTi8-a>)%!Dxm+vB)WZ8oRr5Gi*qWVrH0l3LZS4%fvp;XNNC`u@f&X zWHaNGL@~i{*il2h%=vx`)>dghY|uZ&&mN4JFC`7k^9l#nc_@*&EN2UDiQky4$RuWX z4nk_lX&CDFVfH>d1bC<8QxW07tfu$D`ehiq(eOB#rjrhR zcMpTl7za8vFbejWTjS58r)h)t4`Qzw18+-{Y4XIywCTkOHh2?D|JxHujc4yB?m0$W z_i{qB!VVEX`Pp3aTS@NdxM?)nZ6!B~8gW`@!s*Yn6WofzDLgQ21=Vs~%~_w+RrN1b$w( za!`h(iE81O{x~pkn+oQ?KEk^ViJL2Ta;V{S!?+~(L4a5yjhP`p}$j&$jF9I82 zv~&_|IpkQa@XLjHv8NQmgC;?SxGR{a)WIfS4M@-H=jXS2?8x>JY;RgLyve%4I@Sg9 z{p=9NR`w6O@%1%k=2|sYYH0)0>EZ@mQ={3;{4LB=FFs#mVwTBsz z8$f2YTmqFPFGW&(C-!ODE+*9Jm7pW0mgV{`!1y;-5c(}gP%!uj@;13ZuP*wf2QY0Qr&Oem`hR+>J21O9r zT?vp9ILjhVx|qBhU&=JxmLbaV0+Oj%Od3OjiG7GZBQ|NWg_4#g3GwDTZ{p9`w$*&c zRcaTB*na_JZI(c7YC4(pv5?eHpAYqm46EyxEQEKS+hBp1DnG-|fUhnE%-3227|-(p zyd5sUlD$FTXD5KWmFi%+u|trfVhwd(#mtGrL+r71GxG4&M)s{^EUSuN7@6CO5VZ0% zY%iJtjpoOp>y|uL-~|Ze#$(D=E7+5m$%LgHg+@DHs2yv84PT$brn6yS`|ALdd@BMQ z!F$jT3xy@ssZg<^5H?DkgaEab;5K4A>{!IX*WoBoNS_SG3zo56wyJ>rM}f@A5nW#J z26P$*Art+p^-bd#Q}25qyH$yHx5biCP8l%n$ptc8Aj9l8PJq2#|5J3PfmC&E7&enR znIm&WLXoha=TJ%$LX%1*Q;H-_noUU(LP(_|LnTQn?6o!(B_SfIq>@yUB(FyG?eCwz z_Bm&*z1DNz*Tr*qePirKEblU6_Et>$l}u(|`!txverJe#Jj7V`LPBjDL_T-|8{0-; zy-gOh>1wc#=e&iG&<9N2i!nrcumRThE+A_6%)mP>1qPW}cJrhD!j#r@nBXbQNUNQP zcV7>JFXZw5_2nSvGMm+Z6GC6`^CLkJZ)4EQpo_~SsYu>a#=*;v4kaxoLw-Bq#DukU z^f1r;Y2fd+Y_F5!pM3F7^d!72q)QHW24Rd6~yXKuCi92%egg*35v8Ms_LgmRUNSbsF7V=_1`p^ zs#Q8Z^pmQ1)!X(vocPgvs;VYUg+@NmPYXq;h+Pp*+MR%sC8ywr&O&s1)XtHlLprz6&kTuJ- zo$SxgdvZF!yM878-I@=#9%-Z4?_`qDwF^9Ut-;Ugk;G6}f;|>tj;q$BK-bgj?EL57 zK&hjTgl{Q@PivyVTWl4d%^F}1_*?<2CkvSAaRQ>hns>eLF|Rn_`~z-|9fv2xbI6rV z+hOpXJKl9SrFkb~$)}^qq+qC)-BNZP?wznBn+z9_8&mI)z8#jJ>G~2ny(P%$J&Hv3 zq#J(@o{H+m`gE+G4oN+&%{yR1$)oyeqF~htf1O1btB!|kd*l$rr2Iz&<~QJX=a0$@ ztFIAHZWY#9@eGUEryxIlENW%HgqhyrXnVhmj1)52 zVhTy7qV$9D>&ius%usoY7kQ!i8D_pS$9;F7RT5n-YP=$v@4%)qY}!S#GkqMk%guq4 z!%s=+s3wM+iBgsOVl-<=8v=(PlJyUAn1DStG-W{;Bkd-@OZPOeJLV{P-XO;gJsgC@ zKhjjn?k!Wg>ORyK)Ds6kJvzlz0bixcl3$ax$y=pXX!uskI$sFDIk*5kp3CEZ8y)c5 zA4f7W)t7|Zod)|1VJh=v8lir?`_yO~T`S*An#QFOF@1e1S1LypY>G*KbS2;Ks)Ytk z8RnI_DJ?P3hR8AdAaD_7-h9c0BU2S|d5|$JG4F-yT~And4>uZR;eZ-vl8BqJA)T;r zCj0NNG?ln`pNQK|X1L5E*7j`_>tSgR7O}N#zA(_eT?3G={*B4oZ%#%3N#U>7R#+`7 zOZWXO2XtrvC9x=mt%<@*jjolPL<}3S{{+M)KP58#()6b9YO*Rhh85l`OTGEMeD<3x zvc@VD4*YS!Y{MHYUngL?c&5s^_2%HZWhJqgY6HJ5CefaihoLlZ7j%r-LwziQ$)Dp_ z$uE;3s=s)Yh#HLoXLynBakr=O&yS%5XNHF}9jSFuEY?(9hj9<(Fk)v&<>a`#Bp0r* zXRKm~VL~Vk^pvL3Ne3bP${SKrBTY6v9EHs*vJeG&_}533DX6|f%xaIpLZL?1 zwo4PXE)%73tBz9rDV@YU@GI0UG@#pWOVS_uhbzPV3z#eqXJRm5PI3zMNVv#i{ytxg!32F^JiE;Jl_bqDLIC zXGsX(sdK02)Oc=W-%?bLnMve5#^T~N671&`Y4Y!E7!+69kw1qI5Ff=6nl(F>+`s#P z2{)QsF=zb-Hh)+Z99M{qXW(|B233X873A|^M41-A{ButTgk zr#V=Tb9Cp^>g5vjGS7V@>f5M&rx7W3zXU&5R?(=JJE_C@Ir!{N6}9U8L~d={g2qC% zblfUqG`sQ@X0ClnTE^w0`Mq2edGm_xN$RuP`S&Rk&jX6hA?NSl#V@n|M46f*^-XH&Vi$Mg6cbPix6Wm#bM_X}u-wpBhae+IAiOhP9;UskKI1@2E-!?T)= zh+}RPsmwRP@{K>)dwh1$>+dSOvG@uzMk9qu8mfg~F4~pTOoE7ovY{=hO_;U0IW3hS$Y}6hH2eehl7mc~_V&oz#nD~^$It!C={)$X} z{aR*oZvpA!v*Z;jEaWcCtrVJ2T$y;R8TMpuhsj?yk{ZP@rukeR^M*MCmm5Thsnk_i zul9vm8)^+zEf0t+y#f_p^IV)pCm_?4fZ0V zExZ$GRej}zKcArMIwgM26Cqz+k$5?&k|j2$VfsCLsIyyPr~FQW*=TP{W+YY+howa% z_SSM@u;CQ>+qww)0^hO2-zJfN_xeFCNSfGe1H0p4A^hIbU^maChx`g{Vd{6!B2k;Y z$++1Un8Q?xY8!FhB^Tf{Z%Pz92;s~>F zWSDV28cX`S(m`X$3vAL45}&(`Ao9D0m|j0@x7b7hL(h9MW24&0huJ*Kud0fTI2cvo zezO%k{@Y2)t$NwmlwEML|1%hWze_UDO{iQRF$3qg8k1PE$4-6oJS_RW3KYJ|gKFqF zSpI4|d(mhw9KSOJhI_l&cE5$N-6#%(Qba16vw~qHXD6Pu_|1BpSce{cu^6Cyh*f_( z7GFdh!JA~1X#ZIV>PBuDdeMgJedM`4Kl{Mz`fE~le1x3db{pc)%%I*D_GG%_3Z5~4 zqoP;U6_a{S;Lrpa60l?rj%*vj$knG&*nb&z2Yf`|VF^LTl~{fzSA)lugm7`1Dq7q= zkJf|XG{58o8G0&DqNa#L>ATIuJ5LyAuU`UAqV-JJ$x<#+na4VmIH-2RTGR1Ru&SQF|9TF4N4=qOpC_qWjZl`zdo7A~Lww_XP#wL* z{9Eq?QoB9?{2F1Lw0zmr)J-@n{T&4PTiBDMPm_IaTVo!+k6b&mM-szZ20>Qj==7_+fp8H$JX$r78!@0NNE$DkF|I{C5lwt9#*N zt0LNy9)e2q#8G8F&)v;mg0ga%fLrb9Z*DtUbxy^~yI%Ah@xf?x;5k5N=ms(!kM8{o zvN5;m@)sv?>zgw;T)m$f96W_d+!P#Vno37_55PmY6DTTIO)u?fN5zL#(Eg=~zPMJ6 zwrk5Uv)Ud5R@CD=VJVw}Ci7L6kxbE!7t1#@lB zqD1ee`f z;I)@+)t0MXg25+;QJ`y8B^_xZa8x;p@lj4y-m&6>3vyjxmt|XJr(rB`(Rc%fSLan( zCM*${KG(yG6P8wK`uPf~8@J-j0^2IHLju9)n}fLiuW8lWl>&jORs%X+HLtQcq$&`v z{De(=##TAF+6lCB)dg?j205Qk69ubUGz52>y15kVBFq@0BxpNNxVlFtG4rLEAazPA zH+U@s^}BQhW+BVCwKeO|pioUWApoPdAopnxUw4_9-ug9sWaCSEQ6U& zL3ku~GdDOG#9oPNgZ3qBxTT#AI6b`sJ9D+T0{K4}R`>;L?vCNKJ^OL%a3ZEnTF*KD zG{DoldT`HDH*O9-#a(=dEG^%hFJ11!R<{()FdEM#Kr=MH$i?h`i#TT&DZGB>2ue5G zaKp2gIzdkjk_PF!}LToPDQ`dahUxdJFF0x2$b+&s`C+EaEx-%mW`R9Zf_0_$9i(ohphziP9=Dza}!tE{Sl9UN~TTL9Jf280e|oKN{=6T z%&EP2NskTfpyMtcp%L?Xv30$*pz}*M4*4z;SWnIoD9fn`;_Uml1^d#trLzsV5Z0)w zs8+5jin+)&O}&Pl5`7q%WrmL@juV)*=m}K!TmZGRQ{e4lJ+yS03nuatsP#YrsaE}M zTPEA34hL=x3$F<#zpB+Cx*|r$jKM6W!D z*`J$-&Y5j+^13}GPh6R5m&Fyj(it>Z`zBe^`yVW9bEO;d^Wd?m8;ayt(BS9lv`erH zr=BsPN6r|+mj+R6P7~+$+~-?g(e-wop3!8{O-CFPX|9B6{YH0?G#77uVexGJ2+mcO(%D`Lb zB#|CHMn|`-hq&A`Xsn}wLMb;fv?iLpxjc~m`hE=_^e@1nA0Bko_9!r!;eZ=gTjGVT z-%yz8j8ciS@Ug8XO8<(+B%jN~rk7=kEzhFFhZzvIzm1K$^_WC1h@vTDg7{u@5!eR^ z(U^%P)cWILbnDcttoAmcg*or>OS&XJyzGpU(+zNV+r3KZ&~Wq!If{GV##1AM%Z#dL z3HEN?Ln@kLvHQn52ub178_jD-;@%tJAJRb9S-oQnFMR_Qhc`s2axL`qo+r8g4l$OS z{g`Sq$%^tfx$N3A4)E>01V*T8R-9pE;0k_%q7}K&oRR~s@*8mN980?5uQ@}u@#|mM zCaNzXPD;XM(Z_Bo{kg#gAubcw4m-kA{{3b4{*!pLa5ug1vWe7Pn}zbXd}*Ps6&3rO z!gsIaNcg;4%q7DSymaFysq4FIbK3VH1V;#SN3w21?Xv~=D{3Z9=;t7~qX!M9MWJo$ z1@yUCj{E#%*;~gdz&*VZUrQE4^4m@9{b3Gkjva>n`R`#Qr5EcAzeCf%H8>+T83VSd zK<||xSdo1X+>OGi@L(NVtZ@j-7wuuheVoaR8|To)LIVfJWr0SMB?gL2rG2ZL;nwr5 z7*!cZxdIug-EkkEH)jyf?{C?Nx{VcAwwc1>^l9Y(GgWVqqtsHC_sGrd!I$>{8lpY$ zGw<8c%=U(G>zi2KxEE3^XJBSTA!N-p#$Pi_QETdUjJH~kSqa*>$Dx#2aq|c++$)9k z4+BUSTZ>`~jOnsJpUF6-$sqF7om$4cC0Q?TgM9S`kUMLGUoWU(*sV`wr=$+5fB(yR z1X`n(JO8)FeCV>%!7i;6aC5|rhAjHQF3EZT&qJQF<_bF0BgG1pf`p-|f{>c@LYycO z%^v3G#a5mpp!nK=9n_D;kVoNmT0Gm(*Nk@$jETWHB45GstvUwF#bD6kdfYW}JeIZJ z!kkZaIJ{~e2n31vY>7PXIvz#NXPPJJ&May#c}e+Tc{>!fQR z%zvFiKv#}Rzn{ilze};9;sjXd{3CA6K~(&5eR8FT7EZ#rOJsqVB>k;4 z6T|0Qk=SeEbV&08HUtIJdeu?7Qq4^u`lp$0+%=Z-y*5kGV~|H{PR^%l1w%}c>IG7g z<13i!TFljCx(e3orwPi8rK@UJUJ$fv`3aWlv~nqW8wJnCMxv#<1=l_18IHf{0kc=s zuyfwkLugSddBAr7Q+iFwcH1GkdNv65%pRh5vrW0tCFcd_cg^ASRuQs)M(RZ%>5fj3lhH2P6n7cH_mNDhCA*V8clO0N=|DPA^9Z;~T@nJ>yt{M^hJQXx^AF7ivt6C| zp}n5I4t@lgK2`YfmISA`IT}?qe#K)&Q@MhQgD7_0kCZ5Q(vHolq^mrMqVfYAd_+k2 z6$LIMbey2oAc>YqmeIy)J%QaqBdRtro^A+8!Q*dA*onD^X~@joxV~Es3w&E?>tb2# zTP;IZ&c95ZZs*~HvUT`we>r{4v(vJ_HN%kCLSk;43hGgF$hIXDF=INUPG@wnnv%*Neg_}NpY9nETgjBUV@p2a}cDz8(wt*z)MiR3`X_{dWQI1*Fqg&SW{!kCG#-_YcR`%~%F1uP zBg}`V9@NI<6-hsT3d2tSU?gZ8vv9Too+9>@KNF6T^$!`6wN!?YiO(i6TQk`zo^P>s zaSOY`3+SFTOHfJX3?XmzF+5QgH@V8ul3#p})7lmC=S0I8Y(v#BZ3OxVVpGe7JO$ERb+@^?FP~`6-i;GG+lE!pEyJsP{z>_c=86l zFPcRhhi<^F;U}=P(2B&bngespc4Ep%J?SkRi(G;xJK%MJxTeg&>*Ea3^w=o-x^664 z{a*-k|K~Yk>Xrzv4@!c_lhY*V`WLvY$1(eC70Hii0p#U3Lvipfc6qP_jcc0-GkrB+ zZLv4zb+xjA>C#M}bS)%qxkYY$QD-;J*CfA=|6=?6{YcNZ%|xf68pJYZ5!*?5OyL~Ha#HtQ9+vh9k7M8v)z|vsQIK=qGH+3>iEjh z18a^Dt~&#I?DZ)s`IAkvtccZ&F?4fXFKi#9O=s#MdS3hhAuF9|e(!!#v4(?&@gq>O zi_fqv%EhM1TkzrBJnCd@fR@3=c$ms?=Tg-fztutT^ROZ}!9tiugymIijvTLDL z^dECDo}u?j7GQ9Wl0ffJBFs3rMew9CTHu;_0*@*y;1#hFcH7ii;?g{mZWD^6-dQ<} zk%KgSxTJ~hvo*#y$_fHwrVJZ)@=Q%Vd4ZVgA-upwu9HUouHLVQ zo9<&z*M3Rh6qndlrSbgAq1a)%{Z<|))^QzmY=s2h$G-=!HI`T_6NzWv&cFesURIyq z10CHn@YEe49O&oYyDcuFrA0d{Q6PfWkVF-?9$?p0x5MOHv$@;1$Ai`_4R|?sEI0q; z0lao+15^Fnmloy3@wtN2@S|Odj_zN;DCQ;L^T{>jg3J`wFIOBlE*?Wm-}%$dDMs+3 z!=LYqR54TT>Eh|$XCz(fIlPaXgc_H47oX2gn>pq#SWS+Qab4{s>0TgSJ>^UMVJ4iK z5|6Tj8=#CUW20Uq!S#=cus!tvHJB_PYD9UIuqO-;zMMooP z4NoK|(~r`XkAh)-y)Ps+vz;;hLnD5@7e~pA`Hb$py;$_5o|rTYgGh)u z<^~xucDAoUa!~?v4czfFnLw|OkB0w_WMlHvP4u?pUSe`Ch1LE0iax0p;civ9()soK zXlKwH8llnx!=1YzO8zFxo#pRcE={D9W8CrWoFlMPK_BjfD&dQx86e1Th30>oNzqk% z_T0b?x;W}Mb?Zmku&9~dYVe|^5>mv~f!>pHpk8Y&zRpmfcX=MY#FGLN;y`GkjWyl% zq=Yi33yId`v9QzNCbbJ~gUvTG$cn%A#PqQSY#$7wV!>jhk9XPv@xF&N#{C7FYPGWc!dSZ65oVJn@(bFFpPvb-E?CH-7yk=~c#f@tF>mbnHQW5k7nQ z`~*%k7scYZwMc zNgxbkf&-Dslz&YlO6o1}-+pNh=QlDY>5oX`VrMF!o5_q5wnA4M3y_*_N*28m!jMue zgmIA|`7dJtfeVls*giwtr*6p zxe^x5PQv!S6vlvG+bzzmC!H?2@X^bUl}+D3dc}-R-+wNt%Dhb-XUwD7_GfwT zB&8c24QbZmV&a=8N6&5f1=5BY;63k6`GaN3$f$cW3vE_W(ONIM>QM^Qw;+ZZ7cQdz zl6{cVlOX1u`9!^Y9?J9X@lw$|QWufWC^#L0NWEh6t;iSGUfu=U>!RV-Pyu=jhtlWs z51`QR^=Nu_3r*bn7LudOAyelgQBLV0kNIx;)!D14r}JdCtYUTL)S)Q4z4|}KxuBf5 z)LkI@j?0PsTp`$59Yubey$c<3M%d&W1x1(4@WI_jXfTjMyli~Y)O#W7@xL!OBWsNO z9*zS0?~Izh6yA)pKpn3#*l+%ZaoTqQdVjdmj;a)X?z@_?`S+0A{m@T7U9*FNo5EB+ zr=4AqG7RzhBFs`zfA&D;I9k2cx-$NRGs(K$LT<-xB>Sp=5?9rOv`FPLTiVtKvV7+L z?#Dln?O}qaZz|B&(^XM=%40}*EX+PyaS6ZL?#6p2T`*s#3}pP>amGm(THCl8CaAh$ zgO@XHT|EwzkIccbhUMVtc@5f@oki&oJrw_#06!dOVbFRn=-pNbnnAbG|F1A~J%10) z;jL)*Gp^F;VmEV2FBbb8ONrq59GJ*6wU+jEvPumf7l^nrI3i=R(6lYSjne7ol)R`YYlrU)6_AZmr>FX~9B`!u}g6o)bg zd0xbRQ&d(xfu2^s*-yry&=IAJu!PTc_bOtAdIT=LLkTk&5B-b$(M!LD^a@R5T7%ZW z^DnYIC*d+^1r$R5Yd((&4M4W#LYSBdX?D}c$=7DHorTq8d;L#n|Kty5X&fnR?`M?H zJcIA!T3GqNWlV`*B1Ke!5vn| z{{_5tpNS%CSCe-~MRDRI1FQ_OCzW5;L)y>|e6l8-?fy0j>dpz6new(GVVNqHH+_Jm z(kGdBBOUPi$So#n_h4nq^IqthTMn-rfj9(wgB+jNkd#`;dgS~ERX!5<@#QO4@hjk;c`mHIF%>@Tp9vqg@|+Hy!QS1X3tN&tK*^7JSd`k!1mz3iqa22JIV3T- zk7tnTZo!290EQEN&)@wZt z!|zqHINM4a&#Rn=51QIIAvgdnwmoJfNAjWh@njtT^fM#YsR=#HdSJo2bf|xI5&Z29 zu-#`5Q_Zi-Hs4i|D0?$u8y>*VT~ZjBy_A{lrwRI<2Ees{s9beRlb>hBVXMU%_;Xx< z`TFbsjU6>4LF%eB>uJr z>5jb(^-rgg?sdz^(iIt%U2CJsg0JQTJf=ads~qz4$by^vGeVxlN=M!NCpQ}kJWMERo4(2Fi!;7R8*!8-A zjf~yNypb-2Fbr*P?X$MDxaGWE36O^rPg2~hQ_~rfu zP%%8uehZSuS>STDk7>5M!`8j; zg)rv^_esk!`yxf=5VgrqiG z*KPSxdA6n(ZfzhS5>UqEf7QTXm+R2Ia4}>l%taf=LHIM_6H_f_iPLnFc+W};JUBlS zXa9)=v;TtlOf{dci?Lzf+02ARw{_8HQXC`K^aQH;{Kta_72rF|9kvNi!`P?8U@2k` z123~t-@F2TJ$8o9Hycq)whG#2H9&7%KkT&;Lp*p1J{%qf*XP|Z>25RZQdy5*&WZ4z zH4zLy&*7HFCAiC83H3H4;M!OZvO$W$4#dYCL+$EK z(02GVyQlFL_D=J)Zq#>f3$s)dFCY2df#wx?0q9$%bUl%j})EQi8iz&wyTJ z0@JNERUj3(4o$DTVYmbx!NWyCcyRf4a!=wR@B5U(wZ)26W%d$PfiF_I^9>gS!Pozy z;nHt7AkhH}Pq6f!t^&%4^W7IAAoriH!<4AMY^mxH`_QKVMcw^zPQ5&}Jg1BUtLC92 z&%-Jd#GvDbh4^rA6_p%+75^9}VuYn&v|JTZqmt$mcn7@G+UBNuVjTFvCF@MJ+IQRFWDh=rb+ zl7c$ETP$L(MIZh&5?Gjip`l|xGv4(bc(jo}N9mhUpF5g@*ZaQE59@Cd)0|w~+Bl8V zcQ{D`UX2x`7btVTMol1N+82CZG>tQC8D{&1BhdX~DfKb(Wh9jkW61kLzE6Idtr}B- z6H`O!{WGtj)#Ds4QlCyW&&uN{&yUqM{6>avN26gp-%mVrpJL!aR2~xw5}q7YzH%HF z)*d02PpfDs-%B&uFT{JAlBnM3T)ekd4xN6h)4;sbI25%8L(=Bb;qWK$`a(HwCc>oo z%X66R{0*}6+Ueh!f55`D71M<$(^ZS_q3$^&d>wt2R%vd-iM##Lpx%Zw70U?Xw%Cy8 zJQGpY%LS8WufijLTcntIBGeuq^blJiT3?wXb&iFbVwz)#?uRVh=^0SG4UmhlV zn%UWl6j0ZIap>PU%Ku}_>1@7h>Z8?x?;PcEuqO~N&#=L1#Y@S`e4azSObA z*GMD@!#uAgBugR{!@fA-z1=SKzM%x|eIW;bF@vU`TSkHlV(Au|+jGtvs4&0%rUb6`nxK4uJsQ<9uNft%w3s7-s4MaI~lXRO-=FgmF z?6?|0?tPcTMXgCFvXTLk_!YHQtI^jlb4cvMJe)os3y13Pp+IJV!c64DU@ni)T$H)0p4_)Hqv*npfN5 zH>p9f(~CgzbSk*3XyBcd{&0Ox0^M=&5_y`Z3}vBlJk!V;bMMT-i-R`IbVUY>Z4Iz~ z)g5vtKp9`wX+T->RJtRq1^SYg5$Dlav|+~(>}t(|5XGg~pPh)#$KtWA)Dorpo6y$K z5tB9@W+ytwV9BT?hW#2Lhlmgw=>G=UuQMU}7tiuPa{`;$4*0orFFL)I$GjT8N48Q2 zW!;{#7nl7b+r-!54TbqwvSk_-I=vCAU!29e&q8RGL@%f`Z^P-n7Bp^LCEAo3;n7Kd z$)V;H4ESEgxSx&Z89X=HD+hbY?M)i=WBmAxTqN@r>E(KE#D(R}m_;?H`~`#^Tv zcD!)yIY_&)sBk`uN_}EU^GGCWM(m?76t4> z93F#Oh2MD(w-AOu*+9;FX|v&n597Xw12q4;I6Zhc8=Z}=ldgMDpy}9D{H111T|_#_ z>!}>WW$^5sf;(i}$a|>J&!w(s2FQUIwNPO_jp`ief{5+eJWFnfghd%$4Xt-5@$TQp9Ap=aB*Rc0h z|IiG<7x|^wa9Bt1h)xnD&1nab{r#vDt}oECw4*QnY?^`!+n{3ZvDl zmIqdI#q}0kctZ(SG-f*Y)LGtM(LYKMSaSuW3wKui+-qF*t7kp;;7WN_xu-;xR$(i3 z)0oFi3iTJH_oWDYrrg8tyr=8*?tbtYQK4dk@%T%LcV{n42LEL}xOV|Vl(X7MUrGw< zKbr>#Ynvg^o`;z8ke4gavP4)>nXPM=ST}SACjcfM>OcO zCtLB-w$0Q(KDa6+E#niq^jBV>E|C@v&(Necrhq-ipVu-#SgG z_Sj44+9eH)%>>l_XiMZLtzZX5z45)Z1-)&UfIl1mLBQAtByxBsl+H_`SvNPq=*uMB zCp?K9I%a@Li=uFCLK-Ogc~ZCY;h@!jg_Y@TCX>~FGwMnTq-a3Ba-QL7Os$TknG%gm z>cuV?G3PVLH9IQTeUBxt`;XhPeurq+{hP!~?jF;9bvliIYQrN<(*-Dy77Pq+HBT`5B)HG zeUQGj8I*k(GJyAtapr zT(Sr=2l|Pk%u_18dnbRVs}7a2k@Rug6Xx~SbY`;RU0VKAny9=t!8ntTWEsz!{FE;$ z;7Wf$%}X;(l${5sr4Ld4uz0M0)X2=!zfV<-g3&HJ1sfI}rm20)@d1|Ogx#N5fzDx^ z$NQg_N;dPHQy26O_)QisXs2WEU1dTn2=Ms)1tR z5bVtSO7hm|)8yeHW`34BZV;{`uObtSqDiaQ;W?}!pPjueuPNM2uaBZ24K&3E) z>6zCDGpeTw-0sYxbF8(fNr#zxFw-q1n(<_)4o zf-Z)&8ge6TX>iAW1O1^^NnfY_p&v5hId3%OW>|0Mx=R|krS3*t%(aW$U&$L>WOzKM zZz*0iQ&y@9{I7vZ(0l5$PKibtoWRqcL^-apjlSUXsbYr4Sa(JpFE@Q+(t^@Rq|{W{ zK(k1_qXaap*C49H<|vrC7RBFuW!$=#lk9z(n5;@G-9ux@&KfN&El-1|dxM$!!-%y8 z{Or4aFNnQv0aasB46)h-XFS9)u-*Xu5(Ic`G#KWWwIb%nK#pJ%mu+5xN*k;dp%XQM`0|aHa{^avF2k3!Bf(e^`Q2(vGqtR~|4H}=2^Z&Wv*P#K^2_(QJ-_K`%e9GQ}Cye5q+Fp2kd=>CI3l^689AeO3|?uUQM z52;^C4XwJpfMhVy#PsA}`X6)0NOS+(srU-JmHyYoDrV~>!;G(BvJ7n_l`+5(|*`t6$-BmbZse<)072wRPY@9I2#{u_ECih}Uxk^~M&x6>jT0*XpJzAs+K}W6@ ztax();q?yY*}B)TXo5NJy?K#j6wCtoC=ra>_K{rcKhC&+TZ#^SNyI&QBbIqeV$I2J zHfXymY9$!qnOb+4S?|O|$*#k483ShD)I-p$-3s*^hS^TH6(}Pu2N$|ySjDLo=wlX1 zRE`=DjgM(471B?795rcA-D@a0_kwvL5ly4*zt~ktKE>rmfdNc#tV4{vF*Y;`^Rd{g-o_@)on(~?~=cYwa>t$WSn+JQ1U@W_=;k8ENzb=K0-&+WwNLpw8)H;YC%-zt|*+shu9sY&(sWJ5kb zlaH0`V9smI;d_gZAeQ+-qPDaVPlx&JlePhp`)nL-mUDuNv_jVB>r{x&SHl|xN7<`x z62#hkF>H4+C&ySb;(zT5)UJ}GevjXgkEP3S@&hRv&S&>RCP`w))>LA&`yMgdSpt4f z7DLS%8ybHtA99aQroC&=lZX#5ZASKU#XboB7mhLR5w0#9RG8~#aS>v}~1z;Yg zhoTdw!{A4wimJ?=Br{i>=~@2|8vQy*kIP(=-;vJGp0r5P!C=z8>^R6)8A7G8JbK3r zf~e0t2vEub??)G4zq3Eqf8PU*rebg>2(d3$56e;l;Biw7CQ2T}#FHD5Zk>;pTT;+? zate;7l@UmhrOo`Ee$;nKkchp;TK&3)UHf#PhQ1({K?Qhw_k1ROjTgJlxRq(EcO(}& ze^zRp8fDT`N+9j_ZbtQ$4s%bC#8k)TR2-kB3VIW#!NsHRndF#@P#GWr%X>s%+M@AL ztu~27ty}{VTbmi#?P^T2-3jnlUk=|Mt|$Hamf#?x!0vQUAU~$%!m0%;*~+R+GO85{ zS8te-kg!zN^P@O+cu!-t)7|X33FR>ETpS7ce3v{<7_u=>vxQnDnDQD`pYIZB=6G!w%{)%XHGZY0u5s^o}bF%$|-XP&;yCj%w=&^lm9 zie4Tez7<AhlAf8BwYyjz_f{3A)^@0h!y z1|e7V4kD* zkGrMR`x>S{uq%3lHf603v{T%XcWuWMvd$5B+=VnqMmm8G+j z{^K2VUr1|)79G;k!c+CPNzNxqvP5R#spw-wv!#RV@#5E}@*3i?QG>cI41vH+4@gm& z7(Ja*29n{ah-Tq`pfb=v z9z7}`k|%ya#OXK0=z1XusG5MUFFz)JO%l|7@fX-{7epe)d?!*pnpmf>opr8MB&oL2 zXmdn`SS#^9UjDNin-@<8cAsJwob7{~&-k8f?cd5XD@D*TQ3CX*xPbV%K-e{H9CPrt zJZQey2nGf~Hm}u#(QD73VOtwxV|kR+)INfL8ddB<(nfk3-QeAxXyU2tOM0bpK+E?B zo6)UG3UggRZ`6{s-;ZK^T)u+jjNN40up@J0aVt1xYZ1vnAu@}v+xWaFBfsp=;IrWX>purHMQ%IDG*rn0OVsXV;Ko(-PIlb zN70!_Q}wlR+&pJWB_W|fgDHu7_H&dZr4*@DDjFq8C8>mDmLXG;kc0}AQgQa)SBO$7 zQYs}Rq(n5zPw#pEwC-B&T8DG*KF{<0em=sAv1H>>KcrgYhSE)c;v4y;=t0LR=AGJa zY@%@m9X33TnKN_A#Fg8bvTdbU+E0T#YJJcAITy|;CCwo5mfcM7*zUV`V|j7@Ww6|6UL0sx?K!k!z?h;#tYxapl2VBpyi=9*l|u4@{5jRVuUww z(t(`dG0dznnjYs(vB_azq{0PmiX~uU=I??gVCFrTaZsOfjRsM5pB}$zYMGolf z!ipz8q0Wm<#681^)R;=aHkmhMhPn#TAEytwN7~4Lp2j5OAq%Ez3>7h1O9mF4 zqM?2fKDVL?={0h=_u)Y@<@*SF5_Ji$jZ!9iz63#;SuQq_drZFGngaLq#E6%W@72!_ z;5}GH%&rgOq;h!@)L5LwZF&;K^wvWdf8q?PWiyFbSsuTeE=Tu@jfn=j2x=E5L)g1u zV&N7Bo3`_=!N!|pY*8grZ0?*AVttw*Ir&_qYS=@9R^DgAuM99X6?4gUwR$vflMB*3UPXGQZs&{>T?GpB2g#ys zzVKZm9X&bRNhEKo!JVQoCMSs@CA@oUnR6JH@`xpiRH~3Q_X;QMDJL%L6Y=a4S<;14 zh)%2&^aiPr){e{Mfl>p?%2vQh^aRl^n+jQU1FklILCzgNj{+uo5t&^}h_~lDIC01k ziR6WlI@v>LM4I8*P%?O~q!^@|vcy?v2|h0W|2ta}i}mlzL$AC87cvQj{J*1FKM&#x z)yZ(F3Ur2C=Qf7;5Hok#~W9?>g%hsehqD>^dsJAF7#gSrmd(-4aR`t!$fTE-btrwy;^%SBb> zcYYX=Wa{b0HOVAb$KE=w(%-%^kGY2;nwZlX8xo_3$NW+dj^BS*V9 z`dj5JmO1v9R3{&!`qmraeQ!68tX)HgHeQE<`Js81{RGz7*pj(x#!(e>Ie3ZA zlT5ctGB*7dn%>z@WFC$o2F>kg#rMj69~rtmLxA_rcbizDTUe5_*1& zgC!Li%+cRvuzHyPjQDd9?>ycMsuSK}y9Z;KrcDJfZdEc72=z(y=?f@aNt`AXN01)< ziO{biMV-EVCfUW}NU<}MROO7NHrIEfO~uiqAkCLJB)o#2^k(9FErR5>#X|PQaD23+Rzm8^JO4kheg_AMFNo`sZur4lXwH{{#THRvUu>3yQOioE4wL80Xol6vfhl4l+R^TZI?xnnJC zpIZcyyf>(A&j}QD{sK(1PC>pN-l*sKGZe{tSgh>}aLI52qmcE6v$;PDPt4N>t2P0o zi+)E(gABp?u_$3bbfHb3CxHIWX=KVhS$+oZ25l#%VX+fBfO|c_C2|g#nY4?GDhq?k zM*@$27Ebm|rM`L-gw6?18MiUl=w$^1;mXOE zN%f`zx-EE;P~wO#$(@}^?;J4@CUixU!L8S6%kMEli9In+2q*|gC)h$tjQ@-}(;9(C~ATR)(z@_9#qXKkaF^8m^IV37r z6PkGL_E9#3j7yM*&G(*y|D1zl%^X7*+Q>5~`sNc6`#%h zfx=t%k$ZlV@Ij|X&~)!NzBD!$%Sw+0=D|9`J{SwBA#qU0j3G6WbCCGDe5iS?Ag^T2*!JTs_@6>E z`K`Qx?co+OpM0m16D4!lflg~um3)T0UA2StAJ8ET5W>!5wv$??iDbi-DE6|v z5?xU+huRh-vM-a&sPe>2q7#+E4tMa}>RJ4Z?@>0J7Og;Uta2r1cU)m_quumq-)e#) z%GojaGf^9Qf}a;vvznzY^!x5B$b8v-HpX!W9UE3bM!oN|P7}t{$`~J#QCQ11_K%?@ zs;9`kLp5w_xiw8P)Qrv1*?0a%K-iM}{N?S1#R+5`$OX+}zsFi$jC8_O- zq)v+@thDakBbh70=&8BA?6bup)HyeT>NehEtA6U!Jg*`e=_q2AbybI2q$W|_yMI`h zdmE{7a1K4?pkVc_QI5_peMEWz(aaN;-iHKF&NdH?YW@V&tk%T$j zq3TL0kfU`^Yv@hC-|RyEnKJOxW%{8++^WrY9c`amNT=lfW&M}fQ{N>n z)NA|@yQ*D@ibdzq>w6Wfly?`98`Eyk!ebv<2X!@S`sO^<((PqSunu`rc7fjPZ)ZoA zNmI#?Br13M8>?%yi`ve(Or!5Nvr}^RQJX0_v<)bG@247dS$>6{_|?faPJKoA1~m=t z=wUzbXOonfXKC+&*Q~^gRI=>DS=tycVznXVH~HF^PA}%kSUp>rLEKK?pfissTZ#9y zlJnI$RO8+|w)~kiEfzgRRinhL zyqwkUgfCdFGL}k8iCS4)c}b=(si(dI87u$BR`T^j3*Abss{dgTnkJfyk%G2zu=7pKkg7Ozs z_p^qTf^`u!990*lc^FvPeY;4*O~wloml|7noGGNqQ}}Mxs%cgSD&47zwzM!tQqQV% zN)ml0*+cDP7%P*jfpnY64Qjk&w$+WR5_BDTOf$^qT5b7SMV7c#(pjzIR;ou&Q3w2t zN~`T5)_nfoqf9`b$)~~ZFZHNfUm8-=3-Isz5?u66UlNcT3+Y|C47v4&ugbN<##r88 zb#f`}xLpK`2I5g&)Og0WON4zJFduMB2xI-|BHY!ShD5_JFsJU^rkr*$ZSt+5#-?So zZgw*DFv+L4{@y}jWqZN3J`3;t^9X(z{6@d)qsUBY7536vO*Tte0fyR2U`?wk%LhQ9 zH@}b1YP|yUqt~H(jRXA3j)Qd%FG6KT1TOrQQoeKBHmDwI#V!S6WXV!hs!$F7Ow@KLZV^joC~Z zuNeaOPk$lRx)+Eqyod3Vs>o`?>qKT-4HVVpl5=C)iH)v0-)vVpb4Lw9V3=FZu zMiWw!=EK=+{)Y#)9u%mL`-Bo)!4C~gs?TXlGMo8!mr`i&={ObR@}P}ojcCJsnSoR zb>9M5pu7^@i#DdhI}v;bTOEFsNz<(A^Qa=9AjA7|l+)n7gj^@mcv?etd27SreH!>{ z>{+694uMTy5+ph((Cf<8cnziDLJQvRG2ZcJ0&_mTUR zi^=((TB4?Tkwk8rOJ=vvL~C=r!MFP-(scX)8u`lLv4{iXB}l*~O~7*T%i(EiHXcr& zfvC16*8IljVor&mYJ-~f5m4mst<0`ukZ->X z;kw!ocI5@UiaUyJ+76TKkgMq9lo)X9>OwLn!qDQeb`TnmkpG#D@UCn&tO|*O#aI79 z@jwaaU#~^ACzGLQ%w0I8Y`~Ov#lf_=I#2}@*gN(HtXi=Tka!Mix^WWLmcGZQ#&&a3 zty;*=N1o0oY9l==^GJx6I!*Qnq%Vfo6Uld9iHOZ|I$v@SJ;|F)eR+0%>=tua_Q8po z7g^KKi3~P&JxY(AJ4TOYCoyUY3#d&)EdBMb96q-ycr&yI&bz zT)c{^b;MBBm8qnwaXp+BBZm6E5^9NIr;TKW5*dUF%hl&&X)Q7ys-H>XmGeA50ACfJLS(@zY313fT z;hk2s#7Du7HCpx=mP~9yL?WE7puV_JQYKnyLzpak}J3l0S6od7&=rsbpNgJK)QUAiULa1z8&;@3?V~r5Cs~lz(8v{ifOYXmGOf(`m6z?vP_A{<(}b;eJ*jWk*YYvTZ?3c zyO2ePB_MnCD4HLBg-qPF1X`ozpi+GyX&V~>dmib)ss2Iyu-XkKoZ|Oz&D!L!ZybD5 z)&*%NU#?E90t9YNkYun08Z#=u|Gq3M@h1%={fi(l;|(N^Y=y|FM$rA}1c>zS2Rl3d zwYom2t*Y)=u)CXnmu=kXP8kR1IfMs?rilL=c-(6*n>wD5&G?WxVDTNi~<(O+70_N2Si z#o#ROkujtvmSZ|F>lXjrFq8b7okTzO#?mgiG!nhw2wh^ni0-nzLA0;Yt#fjS{2d*zo4|7UZyIp#P8V!? zb`;A6_u#(6U%AJBT5!E#EcP0XEkD&XoxG}>K+>E?ke7!#sdzWe()ng5nr)X#t_p+E z^}&q*uN;Zx$ZmAIzYBCoJMNvkn%N&a4kV^EVVQ~>jORo>sBAPNRR^W;RFwc&-F1Ta zxi|67Ndjy@2oC=I7)i;FgBzi?q(GwzS@50cyI~( zDG2H#FW|=fXTYDdAVU+vljY;* zBuskC`wy&Z3 z>Qkr>?`V2u=T2Ac6``sz|A^5eH=1!no*wQSB@uW6T|K6fpyc^9AofWr~;&?b0G2XBM~1B1Jn3iC@MHb)Q>sh ziG2RZe}xShc)>B1j|_pmzK$8TFF@5?Ga|S6onfRHUfnHZ`du3Yq2482<4if!YG98$ zFo$eVUE4<+y5Uv1Bk z+ZiKdQuhJpT>Hiyo3nw*?3lr47dja4DGFG9N;`Lb&aJZb0Y+Tw!Uh~5_mDeWI3IP+ zI*83#zRPLbjWhbE<3aaZ`Wue17c4X+~J0zUThPVoN^y@$xfc`d$lD=Q5_@ zy*U3O-&}fuZ;QuZ3lVWak9ZMxdU!WlXSNaz-ZLdq z6SNqSU!}M}ehhNi*^wu-MY2dL6T(Jr?`*)gp-n-He4n zGdJ#L9UdPqMwTz?V>%xi;S)MVB*61LBkVuJdH63!-nVMPX)VZSV|4kxO%9}p?*`sWyN0q>Y{U+p&75Ggt-NJ&~Vb4)=t+h6F|<-0d}hU0NM+ zWRj-facvXl-!y<$jy1x%ziXNF_i?boZ;*MD6pY72uxX=hp#yVgJqy3%v@N@ zM4sk*%d2EzZOBWUx_T3I#I0jyS{=rUs`J3gOO*MSGmd;z34`H;I8?2_le_s|7d}3| zjRtg-EZ@jnLdVJy(E0}(aOxRTST{8i*AzX%{z2DJbur(o{>SsWly$&;LpO3;W`UD0 z20(sA8af&vfzq?XA#1}a^z+#eV|u#)PHrAR@3iW9o@oJ`JJy17e`E+01$jU>Z$Yi? zhA8pUXY|o#A*f%i#|IviAcO2ebUk$wbGO+Z?Q*_|8XoDQOkn`N_OX!XgBcRFO?%M8 z#><>b#T~r)k_=XPAc-SKlJMObrKn%<8*LfV!((|rLUgx2+*VBIxaKN6eQ-C-Y;VI2 z3zW#Y3v1Ed!1wq>JMUC6Hv;8;S#qPX5I>lvfL^*Cz#^y4T0WiPjZQuC#MT)#%=hiu z@aeKVs>Uz4l&7{JT@#8mX7jn{pdcnlf$xM=M3Sn&T9h(kiHbieArqc=_e3ckEjp=z zHym7x#xC{4s_Vv+>cVDj^zlzD`D;6_O8<#feNJQZNhTtOrl-JN;1-*Lq95`7!`B-H0cKl(m3_?Y*ce|vSyzlqI4Fuomir(#^{BGw zun_EB(ZZR!`oZZ1v*Fb<0V+I{51k9wLra(hc9e~Vr57U5!ODwx=uRH={#S?08r5BtvIwL4jf}?p*p3 zYnQ5n?pdB0+VqXjZLgp|)RM@ewI=MAGKQ8|>au>ZArRJ;u15oLDvpf4EBZX{t;mi;;{fX|Pu zBNB7}f%T#s#_n_^`M$swtQR=pQ727OmQ{l0p}FXX{dyu*k_Ou@W#O9kjbv_`2z(ik z2aoOj#9KTCZYO_0)mkrzpWQ7!o5=S7SBH`8_TymopB1X7xmdKa2R(C7BA4k?+$}4| zdjpCP|6ZuNLmPb_zK+HzUBW|sW1)?As0t=H6VJ~oaOnOwJij@fe0(nj$$Am^`xZ=A zecA_8ig&_-XJ+JNbpr})$^&zmr^NfqRvdkJJ8Up{h9y7q_nWCS(XuVZ3)-ZaYZ+m< zc++xRohX7YDTbo6Sd1i_iI6)%rVyVdLVoxc;@GZ|J2$_k~Z4 zk(3{y9V_5j!7|LfD#p?+a(uq|9BwXO%&|+v&?~JWfyA0Lp(TGFL1M@O` zw1eQo?-Q8lHLAq$%W>3p%MjK{#1Tt<3%svZC}?xqPyQ^ti#C^^MuqS5FHhJwUl z-_v-UI?)DJ#2$mSLGRI(f+^7dxfqE>ZpP7@^ig!j6TBg-n0smC&NFS*h^_q+{BY7t z_`P@ws$CIIO0C8qG1YgR=6@}mKhG(>LZ^6KRgXpYE2a)GDG?`gXs;K3p&lAA@; zJb1ocLO!Gi%^|P!BH>_WC1_U6CzVF`(SPj{;1iHe#2h9Q#mob+?BpdpEj0r^XiSIS zRxbooMw}p&pFcK|7hK5ga2U=w3B#^&Br{hHx-vF^YszjCs?>;nX3m9t{yJM*Y=_ec zd{?Hsifq~x0D~X&z~*NZ(Kn1i=^A&C%i2t0_VqG4G+74TJgOzJ?tf8u@&vq$Mj*?B z$H7@`4}5B^5?o!q2p+WGMGcwz@obkwK;N=aRe}R?Gvntg1`|28D>sR--4jdx8AnP# z3;2Dy6hy7~f;L}i;hxNuMHjc`pwzjhOcRrj_2qsuBsYOrI7Q*@=^IJ-_VeUSwkAoi zk|(?Kv$4atVO(nI%}m)MOR$DA7}}fzSC`%7x$qWvJ%0^yCFP{GNDOUvP5_@oJ<{J8 zg==11#3Ym+!*2-d#Wr|Az1-WX-tVPeb9qHj`;*ZXiAXJWyY9o3V6Ifkab&@20qfMDY7U zPoF$Ab+Ciy1vUu0n{P9F9@i3+b_e3?5Q&=YyhwCjG)dO=Wd88{j)!Z!$oP?~@a?(} z*_8hqn>`po*Ec@EtudTz7`tcd#UN8*@qH zv?6jVYXW4Y8Xq_ud#na&&R;6r!HMHIGLB#WsF8l}C^^w^WQ*DwR$ z^!7$-HQ9`zzYo$;&laqCY>vwIi6NJ&^;p4-LA%y0W$sSVz#Yr~Ft45#Vs5JpRuySr z%s%7t_HPD>!kcE|G$w$Yc|MiSvNq=Ti{dnUWGBs3UQJCGy(P<X zh4fkYMaxS$BD6j0KG|@m9`*#=VP|_W^j*6gOboM_MRqaFiB(PTnD5%UI*FiZ3)CT? zF$F|?f}!lJ56G&21G@!s?3ovz(3z*Ppc{A*vIliB`nn(V6&)dYc|TLHT?N{T8BqQx z8&dRmXZzZNka6QS$lXf^kKk_9$@gg<$40^E^-5GKUkbsuv>3xPzc`QM3arkH0o1wM z9J$z5L-OKK#IBlyKmIF*K2HxwI;ao7`J9CB$yxB>ohNQi)c~5&i^Jai#Z?biaB`~J z*xN6)yljOe@7JD+o=>Z1dcMxVn+zL~_MaVKduRgLs}zDuCSNKS`TG$`RB6Laq6dR3 z)^Lv=yhf^>Ixr*tH^<6eLoe+UP}A59l+$w%%`aI-jH`BmM_WIa@H&n}{nLWgZerx9 zWH3pU`pb0MKPRq}=Fz|UR;cf8Hz^(GPQNr-<8M-0bkC*T^vShPT=t2%)HZ7;9lqm< zL&MZ*UQr13&*z;(?lQFP#4P%DTL7-`>>v*h$k6NR_wc`}A~JSBk{T?T1rmSPVIQhQ zGA1^HQg0NJOuE5z8+SpRfe2*YR0b_KR}h&y6BeYUf%ch9SRssq{`xX7yPpda0~_Gw z`9auy_#AwRd<>!cby@d-)4<&S2$~lcu`~V#fmB8iynU&~w!Oa&Mt|*7RkiG=&j$dezF<+ zKyweuS=tIcVM^?(`>E)Da|I~;s{oZDdEi1`gRN^8$h6OZZNBkvzvLOxGemHwEd@>( zOA|?<5%}+z1jXCB$@elNxYKZ+TOX!OpHDA_+uy43h8fH0=tw(^{~SWD7xCQ0zmX6b zl1^MgPtj*GoZ$BxU5fOosDpkho_l%|oi7wp2PZdTFh7ze->Id6K@E7#Ur)N7@;xK} z0vvbpFumULkXAJYlE7&$7~%vjxy;g-y)nO zszz_5Zlo@&9-;<0z7w|2jApLNNB?GRCV!j%lB0Ytqg`z;quLTetT*bT@rSMO?N*j# zD>mXw;tE8qemps1ZVIdJb)j^-POfiz7<{>B#m}&AV3i}L;Iv(plxrIjy#N+&@;PA3 znGM*=>Kl@%-~^LDQfxhRl{+szhdG~i6JHR2hf>gDR9&@*`KO}>*>9d8*{DTmaIGZF zpE@4zIuwH@*Uf=7k~^4?MhPsQ@*nzL(}CZt4CIQi6Qn_opzRyPFW0lXeme1hqQWug*oB>fbl}7WrpW;%o3nosJ8Sn6(3qgeKKZD~ZeG{QI0sdNZ$c*M4|tG)cl)9HW<1oz zJj9NQdEl%i!jiK+sQTJMsLXr-<=hc|CuIlqpSz)PEC;jIq#-cB2cEuHKyUh5(RpDk z#1sYKj^1QkVDuGVFOFn`cOMk<%!*gbSoq|-4Z@Er!y7dVun17cE7r-8?6=2tq8PJkp;7wF-+j-6?~-cC>D%(kpsP-N&g%jI=!=qygRE)XXype zY2(Jy`GxK@lisB3`^D+4e;er650o~JMw0Gxd|pE-hQ93RAi2{o5jbcnT{UBn5|Vk-)B( z)8J&BE@;?dLGir=@O~Z1=)4keW5*qV6=_n~P4||?3_VpiTKj~H^eYxbE%Jko&L(tc zr4#ycJ_2G&QqcB=(-9l62AWd#a+ADm7^BVn|1j_Om_B_Mo*T~RwU(Sg+r~^K1>84u z^!$8ComR;WTB|`=!YnYk*3U@&e1&E=UJ-aE8K7@1>tLBu(6qn z#zsnj>_gtU5nPP%-&e?1dkD>%wv%(Yz6`P@JCVM31I}K29+G^ck>1u2(!u8-*RAq| z8Tv2qy=m*A?wA@%n6->F8wWz8|37YaLNckHoB@mUL|{Aw6E8l;WgM>$t}j*+hckBI zwBjKOoVu8d8?zT0En?76hbHe7odk00y`bw_9^NxB4V~BNWp>#V6Mdg-WNcc_47`XZ z0~1Qo(Jy{jR`(?dRW(MdCO6`UOj&x{HXnsg8AGgO)amLahEQN2PhLCyCOgH?qL4&M zGVNJ6-%p(hCS#{!-`*$Wr1lu}kndcPFdgFI9*1Ug3mDtiM&z$tDf+j=4Fy?d5FpbL z*}Dv*`Bp^P=mXdMTbkQHRh1}3M1tz&4A?RL3pSl+j^1?VF^?q^_?_oPrf=0OIQ7k) z1a%~%buNQQsyL9u<}{$Gv-uqCs1Ff2as?%C5x}B=i^Mx+7YZ>LWo9N+5~uI`klpbd z=Fx{jQav5PwX$rmxZH_*1($L776~xjR7i@x8lmdYEOcP!9j2zi_v&gs?ehomFNuuMoNDp$;g+Ie@emscWZ-kbzJKf{T#t2Pl^ zRROy3rgVnsq&s)k|&dNeq(Y=(|F6+Q@UVQ&ouz)ye zPQa(?PJwpCJW@EA$30${2O8-%ByQ6!RHJYaf^FB5TZuhrb@~kmz0PNmv(~}tG&Oeq z)po4;Sr+^>lvusUSNLRyISl4cU~L}QqNy-f;5j`L7Wf*Hufy}nu;o1@J{(T8!}GCj z;S~6&zna+X)e#)Fa)h^IYw(?8TgY4+CHSQF5*r`3CZp$_VeQ-X*jMlkuh@GZ43F%^ z4v*~#`{qBmarrv4HZG8iYjOa&%b)O-=bc!fZx8DA7KFG(lPl}uK_}6gY&NjM$%m65 zSZWVBmbe`HGM_=U*H3P+=@Zh?%N4k(w=sHeZlg%GF+|sPJX!NT35<69L7$C!@WGVh zNHv7NHZDidwH0yjd)hp7_2|TM&qdyNqwGEO@=iE|j%R^wcq!_0;%85nw?oH&3rTT; z2&tKD23}X@0_ihilCnpcVWkad?KU6|-5DsYD-hK^a7M}FFjC$59a+8_MuBO{Anp+f z<9!m*X1K)IR#-vrB`4A<6vI~{H$rih8d0u!&uH~p@Xx9rxN$)TQPC?Oc=U1~{%tW4 z4!zh8E`G}p8WRR9FPw#c@7(c{j5YjOIu^z{r*nqSlcCFiK#6E2PSh|!n|pF0Y1JDX z9J2?^xB=Lu7=s4IY+!~~E!etGLg^op(6kdG5d97zgLT^QSA=KNc>myj^4>eugEyev z+7A7hvk3IJmcdM^11Pbu5p50Vhu7aPGu8>Ze4qLYoa_r@rkRGKo!@HV`{`i8@h(?|w%v{k#b+ zTRAXKzCx1!{eics5_IX|YxLbqOE$8YB}IGms8fqJn|)D^xA0WpOvyK_dUm={?{AXO@I@i^GoCFBKOQ3d7eAA9-IEuJ%6SX*PhTLdzs3naO+6`W z-0g_PMDEeXC00Ujg>x__V<|O%tR{@tT*0~d#ivBrj*rT0O5sOb5i7#B28FYWqNCu}(O3N0DunIYGJ!o) zu^BXP`LR}8`=Ri`0^-`O%cfQkWbGb8{_Fk*>>}8mw(i8r6zDOV>Ip zuxV4{sMv$^^kCFR&|6$fjk9uTTvaTee|kqRdS=pjkD}l};ww6q=h+{vI*!U*6@_s+ zU#Z5(I5O2oNBF%~TKKDADs&W?2+zsEI%wq6P(%Hw4W(b!IoT2-RuCZQowS;e< z9;TiqL#$%IyinV~o;EzZ&vtDcFEpIBi9Wek$;$Q((GP41VYlY7qCTp^u##s)&gd$u zUQtGU`0uRiP105yj_ZKN-7V~^1q)!SXE2`?kc52OI<#V!GufXSgmR7g5zVlN$5TsC zyS*K^Ah8_O2a=J_tk*cvJRbLt8$%kiEAb}ph4gOARiazRce_kZ(%jNE5_ab#7CAsj zM?yS_ztGQobhHN}iyVIbQ;rK2E_2##*U_)+3>-Ia0IRR*XWp*U!qGzpc%5koN{+k0 z;T0$Fs@L%hPme{BYuAtqTT8L+a}M2pqfNHF+Ko%H#)G+wG#TCToN>1>f}I-YIkkzh zpcvnW2L7HCv>JX!c214xhJFa1dGaz!k@>^4TvH;Nn+%xV*9qv0LnNoxD1+)d`Vj9R zVf4N3qFMLd)r=#;z&4Ad`Ue2RRK{Nixr>BVtdr^+Hzb4j%O?iIYQ#svL3 zYKYWlSPHgBpDo`~w-YV%cSJ+awlk3eNhHzCl`F4nFZ)Q_km9ulB)q=~mHJ#PU;js) zQM^2jXFpzNl-f@qn}ITPY0^{V<1z-$AB{u}0bYnTpNt#^KO+q9B-48VDW_P$v#&O2&8}GFuhNL@rrMy=6={s^D{<_cG$5#qs6#ns6$tf* zpcTV+kjIETjuyGbqp(cC{cT+J@s^oL<7YD}?veuoXP#GD^#zGfA3}Z}_qevOJ81c-c}$7JOs35D zD$;k`iF~@`(Tqp+$d)4JLbn5&Fox?<7Af!RYK&$o=~{bl6cGUUob{cQ1FM4Gmt%#{UqEcxc<@FYvv3XHC&`WH{B{g7ABP^J z64^qYkC}k#tYVRb-)j`@co`*!m!Qp-dMN8#4YMH23w`yM6+GMKRz7b=J2S(*3fD|A zV*+K$86621!W?qNw{MIx^WJfcvHnZ$$&VBc<|vV8#Z}Ch98+}Ir5tyr9z-vGTt(rB z`nWvrOI+cZ3Fwi|1bnP{6SuZI7;k*4O_sjji!IDmu)@1SPF!g-mV0)Ld16DEliG*S zr(u2c-+B(^KHz(&9UkS8(_ipElyHiD$z1!(6daB!IFDbCvDbKnQCGi?>)r3WG!r{cJRkEs7jBzJ@cq5z6K-9F<)`@b%@kP{ z-*6+%6)O1Xs5Gs0InNBOl%rwR3FP|)InX&63MrpN;ac__bgN_*R62h`-mbo6Y;Ou_ zy)&6ulwM{Qo=Jn(Uw#Q{Q^bhsj~95^Z&|!_)n#&!{3R$ar+l}~4aB^E2an%tLHo~I zaPO@FzI+O9501ht5pk*zDNc?ZJC7pX55lI#M#0}5Cdl7|!+uYT$=jAG3rnrlAYAnRe)i9JBm`J8*N|X1yt`g<5BH&hIiu2l^;k0v#>)T&USH&pU%yNN?iAl^O(K=E+t&rqx4#xEtYT(A1QlgQx z7GBu(z?m!?Sp8`dY4T2o<56)~MM8m8Jl`+CozsY+++`G;t-?4gcEWFM{h;A^2H&+q z#I|cP@o%1w+Om$|Ht$q2_3tNU-gsO5d-k<*txMC9-z_L!h79jfnBK! ziZc0%gX_FVF?a6~|M8T(182zirAnU>nnSd?JLcncavaAtn;8={Q(dN|`~wT_QSy6Z zkeSgE5n4zC>-TFvHNr0 zqHle6ux_anTKljBbw~f^W`SIiR31(07FU4ztr~KqMhOC*cT~@wz7>)h>mZtM0cn*i z$h<4W4iojT=-F4)JAa(#VX1_gf5(8h@ot#=ss-{b+K_RFHC9K8v{Ps*BjWrX_dJ~k z#@iBL+K3rEELS17_BNr;U*T}feG|6z+X*uM;pApiE`FTQ0ewYBp;pG6&fcJjmU45d z-Q5QM){_j0S1cwIs}0Fix$EdoK^)#zxQXNOrJ#(6{bXc%2lP&khI!)4;cAHpt?O|c zp6s**p%*GlXXp=5T_#I}+NEl=-ESkI)^{t0^keb(TNUU@%w~;tTq73ZN}yFbla&)L zBBH*x$$hV>)Lm|tZk8~BGajO_MDHwYn$^Jhk3O0_=KhY=l9NDN`ZbvutV?#J*F$H@ zd4g_Pvi0OY{4Jr16l=tj>*~vh%%0z5qyH*0VtJ67ywIJ*NG4#`*#z>g8p4Nn-gu-k zg47JIA|`Iiq~}f!v+I*7+IvV6ylZmtKd;$Dc}^axyFEhHj!fb|D%;ODv%W)h>jmKL zwhFNHN+e!(rwZTI;ao+p#L35|x9I*kS7h?z5+b+1QmcFZLrA6+yLoYL0kjJF9Wp~l z+``d%m&KH~?@pq1@H%q4TY%3#o=>*cO{Ki}bFtb8L7gsQXkbw)RoQZ$(W^^EVG_#3 zvn>`&Dd?ktzU3r9ZEnrT>Z9;#$r+p=yp~cKjD!yyJInE<7K-W*M~(irOtGIS5s`g~ zI+Sc{Y;+IeoSyG!;rv8qlg2HaI^K`^gha7io)%s<$T^-ScA@u2Z{iDfkC;%>gvZJ% zm^Bjn;r5UN@f21iU$1F{=HpfzK74`6us+AT$5SDqyZjNF&++h|Z6gxq8t8uB9&jo; zj7-P6(bv3(=p(-lKlok63oS{2$>B0Ynq%Y2X^x|{YOm1wx=-lY&Ap(VCqsI|+=;2E zCdi0h<@$XCjD%x%ZMBTLEv}!G^U9%pSOlrcV2aHf>nk~`T_!t+xzlLlqlW}pc z4KX!X!)VtnWqzyfK@xA2L8af7$h|!d9x2-J(kzoGeeFZe(^tZ$$m^siq>q{+eg-Cw z=)%>EMZg}{Aq7s$h~tNOSW7gFEZD5T?)!HYx|#yO>1Q$cnz=&wyDDbg0(Dk#`vrIw z*aSj;T}XY#ak!+C22XlevP69`1UTHLzD;pun>M*XU*ZoK-spg>RAivw&I35R@gk9o zV)3QZHR$EO5ccyME4XE+L=XKrORm(7qVlx~NaJN7JI8i6QEs_R{>qC8Vuviq(v(7! zTQHy9vU?q*ebT3q?I)tv+J`KD`+?|VA%Ul`5dBa~mv+kOVc*SCrp@a#=v4W+0=osx zDB0S9-QZIs2w#>=_BWQYH*XaQ1d$vgzd4$f>JJc93bJ78v>{^lXpSS_zCqUL z(`kW2H-omBB-B)1zTQ+3!;^@ z>0?J)S!MdT;IXSIt^cfyRhz~WtaX}A?~W4@bcT2cE`O_q)@W_P)SKY~>qCW5F<>s3 zkUb^%lD&kk=oA+yv@R7C=FO*bvSkFv4qFI}U2j2b^L~L)WQ^c0*O^TEW-h39j1h=u z3(>z_7YjrWISD%J4C%kF57=K5A_Aw01X|wt8k_QZlr{b6OdC%0v3n=26_l*dr7xWN z$}$-n1X7$|aINeYwzP1aU~23++T$I=3i4zG%j$hNhI}L2^Fc(gxN{yIUM(h2kJk~n zyi}uUQSSS>y+xqmA5QyZb6=-py&!U-IlVghGfNNf1XCtE(!B>Cu&#qk1S-~1^!~Ps z?5#r!1c|9vX{)3P_N17JAR}r!-KEpRI;YtRMrWR;6{h#Fd;=Z9sFoAmu(X~%*D_mR zToX-y{Tan7TmE2s)e`BUnf7efzyRx>7fr{U3}egB&k&rlxjx5+}~2@jR(e&DRY%| zSXfPaC7#0aTE(o>>pS!}@8wvjW{fT3-hL|*Bn5pV(SkD$UC{0}U2vO?7A($mp(U(Y zHcj%Vz~@;Mtr~Kdtv$G1@ZC9$-V;;B-kie|goqT-0WU(>B+q$*BOm%{S=BMJi(|Lw z-Qqq!lEBxs31Khmy{6lDaQm#>`RuxHIdt?;6p_up%xYYUqZ>5s*@REg>=}o5w2Jf* z5Ze~PDrl8s^}B;i*Q3L@dDsJ1cKyc`bX`UT^HdSU=ab8?d{9vOE7Zs759F>aB$dVo z5T%pKHcQyE;xBVKJSD@bUae#=r5uNo@dfPTJ9pWS0p3vmZy(z-H-TNQqXBwGbJ;cP zmD$iWy5JbuO{T98A~QFOg7nrfq8GUve#7Dvm;ZA-;IgGhH<9 zD29Tj#bVk@l4{>Q7xA~-VV(H7s7({_&Kxd3f9L`gyh;amF3QJt@6X`lT@EPYeI{PK zRS4C_EJuY_0~oc6p?~}H(a~;ueD;JAqY`+Z_k)^m@}U1VuAZfbW}0R(`Nn7Qdeh%1 zf@g@jRhyWYlj;zya))m>ypZ|4C>trg{LFh4{eXXGJ`asrB~i;8)0s9uMF>cEP2C6! zq{7EPB5OmIdU@KBGWmWNWo%C4-j@7%eLe5cTG97Zn`<4@$v#Fh=ciHQUnQ{ZkC|xw zZ$;jg9l;p=6G!DQOYq>HCD@sBGR}J~j-R$I!;)A2P|jcOQ6jUAaN@>Pw05T{x>~gX zXZ%@@KK>P=c1J|=*Tv;g0R{agi}?nO^-+Q}@@6Bm4iWs-d<8NuPo_4!-icj5ry#WK^XRs z%S8j;;b{5LL99A-1bsAa;q7|dfMrQ2GyUdOMz829_8z#wTp6Fn_t{X*xH(%eh0^yJ zerFXkVtNs+eejCvQFX$qLUzc8LzIasFsF~^4f%%pWqLiL*w64UKqnuakoeZDq2 zSx)1^7)R7Ap@lLo*wyr^Nnqz2LDkuz2~4uVPNx4~8s+^flG(R!IzKHk7?<||?))Q* zmQ4A}bjs-98)Nxsh#GHq;aQI5*@F2#!Z zkvL^v1an1I0r%|=#`Zh*;mi%H=;*2KQYaq5Moo9P4m_L&B9__R0V9NbXP;OB;inBDr^o$S8xLN{wJuMBl zSrt>0rV1fry%_9MsDk7VEI`5!S7S+jsfpu7TkNHi!FCh!; zdHfn~j#!SJ>TfZhKD**5!zS<(25^xDxk^jOv!fy{+TE8~SM z=w}(8?A0(e`pe(7Y@bmDZEBa#-cGY+^Pj$?1$VPqiJ#&uuTGTy9L^B!z3y;mbv3r& zc%T_y)%ib1l*pYO4alorf)ekZ1`j)B$>@(@+;8Vhh2h7jz2G3^?Y>7mx7otu@>p>D zkISc|KY>RVCz1W_JMq(te{hO=5s`2;q5tJ#;(jKDWGg=b5A9aM-b+AVE?$Gw$HGMR zt16izTL(60B8l)0TcXN&--fay$g7EA+#r4r?vHzud$Q|D&*20>-Ni&%^&r)C&x}aK z%z?XE!;sOim6d(=7FPe1r!9+|*)yFobj6De^f#AOuHY$3`{moy^v@i2`$d}8|55_| z@8#KD&L5$|d?C3v{0|GwylCuWRY4q87SP23L8MkrnXQ+wr1x)DWsOYYNLH*0-5ild z4k!P_>s7{?z_c%n)S>HeO7b1KEnW`JU+Urhllg4f%Vzlg@g-KDu7vJ!T!;T0VzA?X zI`mj)1m{&yVZA&eXz?;T_SUUGWQ`!3UiKz{HS~W){-amX4xcI34|U@`QxPp=zkpp@ zEP_s*ybcN~`-#$|-~0;)jM1mf!nj@KCQKIpLY&WChks#Cuyy%tma3Ddm5k0nVz48- zHk&}~d^@yWI*VLh69E0o@1co)G3;{gEC^i})a>ZJ0=pz{k`H=vI8RQJc5xPF@$_2a zxN8(zEVrt<1}LSvxRfMw;*ZGReKMmbAFH{qQwt}*h#O^8bd2~>b_K_ zv1}NvIQWSut$GWKU;M>VnQzF{n6t28sy!%j43kBn@^tUx_h{*=Qt~(9Gnk~9Kx&07 zelt}KwrAOs(R44y8na~mm@nz$K5Q6@`2Cdyp_8N4)vLuwd8{HG5h@J12P(mS{}Zen zv=VMV+ep^=zG4O=cEOS11H=aYVXc;#&>}lR_IIDbd0v}A?0hb7Ce21-;7NrPY(a0(0B-brhOSYrYHdt3nuHz z7r@U{E{k-2AF;Do1gYaWjO;86a@0kD6v9@Kebb&Gzt^^aUpr7bD|azH$3NhkYcxsn zrun3I;27HK!?83g?8xO^2cf+n7fC-&KsE*9#MhlCyVe;XpT!D{es2lYej=WV zByl9Fb{iU(49Dk!?qbo;Z!q}rv2jur-|yfFxL0igtL#>iAAu!kPR394drvyt->yro zophOeJg|#oe%J^v+AnZ#i#f#3AdASS+QG_^5!`iE9xw5TWRk3oW>#zm46Ti_YOH%1trdjwWo?cOkYX@y$_UMWLI0`sZX)?^qWG!DtQ5H^D@wTc^c5+BHKZ=~k0sl6$ zk=^n5vEnMCXSAM8**1@CDYPYtqZ`@M!_oMg{a>sxrom3s`D2ey2{@cpVEYF-Q@B|V zBRX>`8~^hL?i@Eqp5GplE4eCId8ibfw+SG*0x@K|vk`5%^&R&gT#70vb;Lh2mn7-E zq=M{bz@YD2yu`+pGT6P83e||i>Mjk`)8C53Ge4gki}{FOIG!Pu3v$T2b92bat`YpI zBbc0e&0yh{hZJ)AjmMm35x(~_oMrbPzN@cD6t`$W(VqmYHzSV;c5;LxlYx5Z){FPv z!YKH72o~OS3J2QOp%1ar#LuvS%27z@t2sCwoq^nKPn6uRvOQaJIAH{5j<-B@gne5Nl$8Hc?o)A@llLa(+l zR)!sD){9SAipygTcs{2Z)@;L>3#XDBsSZf*O(`#Ui#JooOGR|*8zi=Sh@o?W&{fZQ zP-c&*+_3^=*|-%3cl~BkBixWi=o?D4@G8E}-34x_AE3O~ys25)#iFfeSStNTDIR2t zkeXO9<)6;=DqZS$J3Ku=AhQX@h#W-i``lpisSy63M_EX2yCOW_myTrTKBJoPGsd_4 z9DY$Und!#?cvk6WT^kQ8W+znlG6&9V81A= zcXT6~q?nJEwdWV7GqOD0hX!ayI>YR%-%2^XxqxLZ#W8E%Ipe>zt=LvYpSgcw zCt7Vb%5?&UDA}Fc;nUf2q#c%t-%g}Z?)h`D*G~rDy{UpkZ`z}+76wS;$$jKARLx69 zOOcSG1>@aVMYaF;nXx!HfEVf1^A$D`e)CTslu)U|C{{nAguPENqLNtNfFsYD8nIXF8HpvHMd_a#>)IxSh@c`6Fv`9%D>dO9s45W>+uhHaM=i*vqrr1 zADkQYOCk!8GQr-~`Kawh6H5OXh5Lfq&}#P_lz3v8QFodGLQ1n?+3Blj>|ZUH;dDU? zVjr0LLkEbc`v5-2tS6f_b2*N*3;lF<04ug77d~M-!Sn;}EA=CO(b36af(O&3R<6)f zU_Zi7pvB?d*!YLVuIAze)x|{W2e3irT<=w;M%q+V0-N<(PP|*(##gn zSQtx2EpI~{S;(5({Q#|7T6CP;6*ez@8hv`nDfnx4k+i(m0iz4k*!h>2(aA%d#5{>b zZCyI-EYFY7uu_ReJ!e?S1+KKgksHKH%MQeUc4EQZo5c0YVLE;399HVoG`8x|W_od5 zKM62vGC60?qQaY**g5<%1T_nYr1>yXKNtaDwoE7N#BBK6L!*H3BDfr|fs6+&X0N4s z&~Dh8?ef~oE=FP=o@gI@kbQRljGxzwp>!ECq-vx3n9_XH>pR>ZP3tjo-|F1hlOl8UAF=1AI_$Jv_kVR0UJY%!z2+p znhog5|GL1l`7iO&%Y?z(w@KTft!z?96ATswvIj#scf*z!FgR?=_P*Xg-0J9qyT}vQRop%8og%gB<_j`*E{h1|%c9fsH?b!*Z^4O&QE1b!0^5H39XdXt zM+!9M+2vh%u+g&_W?r*_F2xpbYN`VDbLHT9b%L@}y}~f=9D{3P3U0rbhX2bHVmFo0 zNd>7#>w<;J)fz2Qx-l4KB$~tC1YvsWnSN?l9g8gfJOewAE5Mw6 z3%$+@n5TO-!s2)Fu-fAd%6T(LXnrP=a)`yER6O!uv6>iZ#K87PQgr%qPduPFpG243 z<hgmoT^5bXm~(CmQ(oUYP9CNENi)$M_3jhY1+#P5AsWWq5DtM+RqC6D6OwNGbCqSz6soM2t)k8Pg|~_AVsg{ch6f8%~z* z%qD~B`-ph!2v(GfA%;(;kwdgUdGke}|@#R%wJUk^2yIZujv$yyV%QQANwyw#0WX^(kgN_RK_6V8w6Yz(m`Jeg&qZlLQ9rD?qQ#CC*+F2{dvg1J z5gQqz#wJa=!76N)6dZnknH=66MmAMlVLQe3m`o8#I{DvWB%>%sZq{(Q+vV4xq&1kl z-f$nbyS#(qm~h;2+6>&fA3(wRleoH8A5{Cqz;uFh#U3^UJ8ma>$l){$S$rb(onJ_8 zW(Vfwo?uJIud#)(0fca#HwRM3CRbe}HV?0{D>z!k{r+%#?eco|MxiX5%exJKi;n~E zHxDJgje)Xa27HRXp|&-X(K?L?^kUCkTz)GS2M;#lP#q?V7G@ISZ#wO+04yArCiReY=t@ zbvu}$)PJbxT_Tk!Msp8jTMrF zim@hN79>Io*D32Y5QDy+$#5jp56?WM1hP*ZK)SHj#Bcj-uAf&16+17ZvY7{9DwjFE z(GyR64bxD@wocgk%#t+3yW(uKB)D=l33r$4KuSRrgg=^29{=YLw}t*d+~WXLczFY4 zNM8b%nwLob4}%ha2Y^k}Bj&;IG*E831vuOgd{z!Ii%C5s%MarBngrj7)B)GlX#8#{ zhH{U3%!{gt!9TWFQhtV0A$#L<=7zm5zOLCyty6!9az!^lV_+trkVtesITs>b9N<1} z#!V6d@M=pK>dUq!X5EQMQ+hetX)(@BS2n`ydxAL^=yGBcYsNS$Rzc*X)g)?62={)y z43U4nP+3*YRQu3&Q1M)Z#De^&H@Ph+bCLu(vHBFX^_~H=3i_~&n-H3OB_2+aXSi`B z4Yjjr@aNP4GIg+rs`|DY6bkE@6)8$6)-elpnGR!(b;UUTH4URn7n3uZ!>Be@7~&uO zVG=lgQh?|$6g(J@b$0DVd(WK1SE7T6gjE>cp`V77Uq8kA6EBcLyN~{@ZpN{JPtmG!XJE)`a?klY z+VL?Ov?~ZU%pXA#PG`Y%$`Weob#6{e_Xm$%@=SGa5;a{Z9PB`r$n& zaO$P_Bmbda-FDbmmbWMl9ZbSc<+jxsK9bau#bsC0I?a1d$09#yvQI z&9?hWiscJIci;}YB4mP0&RaqR0?*fE&bvgi!XC51j;$p4QVvO)f0D}z3e%Q!IBMn2^Kr3<@UrhOUL&;t8QXR9 z{7zgWL&p+HYwQBjbYc!Uko=SP5jjvtg4dEgs}6z#{(!7Tr11jFKCIWx;$ib>G*v1D zm*?;>H0Gm(<{0!!E)%oq*RZmIE8KfkhqiM0Yf&q6cy6#9#@7!}fzyr91MYXUjPodL z5@4yC{<{7Gp|=&1;SW_fx6784r)ClH ztQ;J_L6~ijv0`7RO=4vh7_)y5oMJ1bDE81RIU;IVPD(cflF&Kg?3lA8D}DAosa;}7 z9u#&FXL;On z2Kp<3SeYaV_P9wD>Dt*tR{Ro>IeEfF_nInFUebXZL`~s_*lRpdDosXQ#$ju-3Fl5K zC-=k=l$-q}SxTDhyQ#Khzo8XrJ;6C-(xQ0fVat%^+(*=dU~`!4--M2T(neS&7%F6L zAVAqzH*OL{{mVe(WwPLQjR$>~j=_$yL=bP=3H+Cau;I8WY_c>4SK^O99lXtG=7_;q zaU3aa+(RnQ22?y{D;n{|cF(@>8ko?n@|jRXbxr*P@Fb+#qRd0Qv_( zaH67t(%OFnsV4Klruze~EOx|672POiK{Vyi_J}F^$Z_nAgh{Z!B&l2)j!gIOBC%n5 zWKZx6NUAR5ADgz4sLLOO9#s{x_lW|bH|K!^clYMpBlzpo8xZaWBxXi98BkjTyh1Oc zQJBpQ?&^+f6sEYzOL_V{%YRcj6K@{wWXU-y#G zz92LZGekDvPP{|g71#TpCl$5RN!qtqB>a9EX}<6rulu{0n+J~)=ajuX{;waD$VWxu z?C}U0sjuN!E_;yti=XJ%=A&F^^#I1|V;mFN4-CI=Bu#<{^kjx91ngNuhAM862Qww$ zhn_MOTpL0}qGkb|J&EI!X;UATg@O03O@!1GnhDIdsU^|X28bJ$+6(N=yry6v9nddi?z;^TzzO{NkF?W!H0Um?< z4;>_QT`jd;%a6G9?Ze5(Z&7NUW+eW64Pz&<5Uc+;g%>Mbii%U!VBNMVoX(E{;f|H? z9W`K!i$Yj!&uvsyuK_R09dT#WKIpMtL>_6rBS%DovG>_#kmt{%<@;xV{-(Kj-&c-h zwlg2MNocd^dp^6<=_qSeCTou5?a>5gj_B4FR(?c|MvdbR_x7 z76dO6EL9a22))q*g*EP^p;d!bv<|0j4^`2bq6u`@niO8F?K&9S-vE*=>g)pb`7H6{ z6Yr3ZWKy9L*;-@4an|MOGnQQE`tU`@u>TI2OK8)Jb}N9A+ecCx5Q+lYH&uVqo5gmu z9%GjZe<#I42gwG*VB)PeM*Qa7g#HR?+H{#WPvZO?h@HKQmi3E zOKd5u&QxJF_UvF=_iQF_sy1QbnMOXFoh0i$E+CO&C%`L{(0^K&c!eilQ;t%7$aXx0 zc)h>O9F-eIwNcerVL%Qxju{cG`2=y;-IOSu5E`2++^$$>)mS{S}6j9re_1HN329Mu%q;8-A`?~ahJkbF{Iql9af zCzIncrXc?D9Qm;JC3)vC3bU<#Fd_5INU;g$%UX4S==PPMt8Y};Ui%6BhFn8G@3G9^ znjEm7agch}bQvif)gpfevPgO{=iZy!hi?@akig?J@Q;HdH3j|avF1btk(%*}r0r`r zX=y1T!U?B2u1yr0GkOOvXo?~y3XM^)=Rc%m5`w+W*TaD=g-G2+jNW5k4yxVyq)}B4 zqKjiu?Y4X5;4gi)i|rw@-wB@6-i7$qpK)qjKXvR)2Xs#ogB9^r;9#so|0bqT{X8EM zzh?q-A{k|$%qKf)7P5;`8F}L5h#p+o0P=C^_(Pcp3>!+&mUGUcqn66>QSK@+_P$0+ zcAmgL);8g0PNOV0F^awBH8BdS-I!q+Ra6|JjC@}V;Da9hh}kbmM;0a_YkKezP{2dEz4$Y2zHy(n4^one*SB z(?OER6dB!*gF~V*99tor_;#pJRxQVfe4-@lf3O5AzHKC*EEf{#aam$_JCy_oX`|Ju zi{UEVLA$qqMDx=nAkH}ul5grkRp%F2Gb%z)dw8AFEBr!@HGPB5kD5eZXAJ+|QH(T% z?McHJ4?pVCCtV&($)Tt3&|RJcW3zK*vPyx`{Yzo?}83h{h$kHjU26AztqLLZAGAG2mq+9Pjp-0%@N7{K6vuF{~s zcPeO%%;J0(h4@lQ6?t|oh@^b`MYKz|vp!>wNyNF@Gy;xy(sFTA$tm)%Nnp z^=~Yc*)F5KQ!L<+q8DU{Rsz~91nwU;po3p4m~}QLXva}geoI>rw1wPalDT(jt4ZJR zo?u(D&aVNTXj_dHbX*8uTMp#JB0;9A1b6>^2z;aU^wW)V@xs%uKy}ptT4z=)+0Zfo zS<{^8>)w8_;fENzdc|V$uqu|F!ZGjN&0n$RE{Uwg(sjIN^8^jUW`d-1-sC`Q1bClwrj4Flq4j1;uB=+ALGSQ9MXn?cv&Rp9W8<9` zv2*l7SP7`$eOkT--91bS@Smw6GjXS^EWyHt(P<-weX!3wMyn zte<4rntpQP;a)W4AB_f|Hlo$OtMPR~5#AALjR|)*(~6ZR$t#=5>eBh>^w(hUGzdla z)P~TqgSp^nd=(y#h%!UC4@<>3;xj*D(dqvfETpuDB;EgnBCg&;YpVPynS=ePEodGX ze3?MM|D|K2&$~(NpS4)Fg~e}oiNJSyIa+0#1@C|7!kdf#q1#R>#I_*?E4WP|GYV#+ zrw4RFJ0TDiJLKYt=04{A^+vSR)e?_OR3gRqYB;KC3aSWdWvmMH;nu(%Q#=^QF^uLd2sYxI6OS1qOsw$B3iF}g0HW#=aPXcA zs9rusLanEffW0?R=f}%ntMe9z)N;NI?LqjrJqe@+;^Fa|!$bmkAQhQLLZz)IZ!3jJ zn|?1gx-$$)TWx`Vv64I#+(u6B8W7Pii*0MvVtEgbvEIhB$qeajz?W4cQ&XkM4(0i@ zOmYG&OmM`@=cY48i{#nHLK&!YQ)kD%X|mtP{c&V>C8X}s(!|oYw(h7sP)fspvnhx%#rt#P9IgDH`uY>3I_Mj^rGU4B5*yq7SYnP$RmLk?B_a2!A|c8 z-MEU}y}uuJ2vdCV;Kj6SQYc8~t;d7wSEIK6jU?pgY{F9#V|Pm5A#)ZiLK21#slS8f zq~r_NofnOuv_Hs_#tuV}EjJ*y-&`m31_3GY;k~W+@uY~eQ7N(Oj17UJPrL$+_8Ku=V-{|A%|dV1qJ4v^M?r)(suyl@iA=S+be+_~Vw^dLBsLB{8EeYd1| ze7W%)xx0!7N8?gZ$gA(<(sfBjF)I+LV|n=6(RqZmuf{I!=5XwV9Gp&kik3%lyzQ

    o+dEE>Md`wtFzDbTxrOatKnd+^SkPutdwBWl4V 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 z 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 z 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

    FNnL$KFnCs%b7M*k+gPA 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\S&VRWT#t=F0SkbD-s_G*X`##lh?1a zyiXGJnMWQSta)d3;LUV0dps5z%1C07%r!KUI0NO8Le^!_4y|ZC%dA+oQ6PD291iYu zMDEox41RwW$i0q6?>DFd|GXzqwYvy5r;G}`v`xvQeV0h*$-nHk?mO^!0D<K0w>S}xP2lCH-b&x&Fa{y?;tp9XEc7hDrkvv1ZOwtkk~2b zpy%=i_U;06xG1g$9Db$8jtO&+QIRj4J$fEjxoRWHv}NedaSblJq6mlIB%$uoscdMP zE%pBW9&S1)g7vPc((@({!BYMB8nJ{d}6R-6DL*724Rw-jjV z@M$c@xos-WCPAhDmar&7A8nl!2gC1{v5TAvp>rzNJ-Fp5_;`8+(rW(;b1p~JW>@N= z@$?TQe~!LDG9d~LEcyetx~2f7&)lxKb073RcY?_;C`YqOb`$OU9GBKd6z$YKkM8e$ z$OK`oga5-9etE5ncO05Yz!PQC3|{b-%(O&lTNk4I{RVmoHO+<}N$%aqm228EM99kIln<=Vqc; zX17W2v}M3$Nj`}_9f{N5g|c}Xl9ct5r_Zdp4BPLAP*x)d9o1@NJtn&{lm8B4-2H|1 z`qfPiTzyRa!^c?rm%Z@p-x#vOItllOjNzUM%c$|(Nnl0qd1S9+fziX0SjBTQZFIlN zcy7FgS6?hZa{B7Ta9bzS9P@>AueeQ>TpsfdyFSDdB45DyfjgOH*}KSC-#Cu4r~z9> z?5URJ2E5$15-XcdLo&L3@Z-;iz&cS6Pfa^Zm(F~MHMOP@)uHp$amOm$vpkfnm}ib% zU&)Y-xhim4Nh0ZAt^?ZI2FRiNHwZ7?4Y!{bppqAvRNQV7)`}a2r)4H1-=BfFRWTaL z#u%a(r&Gc8T2;guu93cWAzo-&0nKt|A-z*qaoT3UW&4LoVY4JQ(Yy^y4tb(@+X>J% zO^$AMv?rxk{$ky?@zhc`n6BmeeuWVmV6U90P>*wUusH=J`9VG`_?yG>M!1ee(F5q2 zH31x5@f+HU?tw9@b%9ZaFA(u?;N7sf3p|QkK*_Ud_{h{-~hgFqmz zbQ~)2ZD0_$OB#wEB>$wNh|}c^Hva7g7SwB_GW%F?Po#o#Hgb22Ns91aPZHX(JPdYd zi{gyv*|@Poly82M0=IiHNa9}>DJWFIAG0RWE!VFT2fHd}g3lECFl{Nhd|?OXziXwN zxpVRE3TfbE#d)^)bEwDThk_h#KLn$=XZXE7@04>46sr$MVXmGy_FE2lnxl$-yYB`n zV}1C*85T;sH)UJ9RH2^rEJpX)2jJl7NRcEWi#JFT;Fy9N?57e<&D~(1MmwoIwTX(% z%B;N=EC)4DTcf3UmbgpO750Cd0LO!`gHq3_{8e>_q2@C&?1G9wQ`aF>Wz)u(EI-FK zZD5EezoHhI-Nb^WXNid8dFD9#0gU)31A$N)kLxwTB^f5ft#(x4+CQE6=ba{RH+|<_ z%eR8Mk_PxKmSiDco76Ab#&Nh;Fne=a$y~1=yu&pa4DZl?Fmd-QP-9{VgkLL3{S0Li zuwF)xnyksRq}G5p$5%6V4RQPfX)cQ*iLaJhFGK1(V;o zmgGL0KvwU%${2QKu*ci$C-n*G>^Wd}M z*RJxa<=4Sp(W4|Y(*~5kc?+~N9blM4CTlPt0kk#e!{s?K?2dj9&J&S?k6Cyyul8w! zbG!YBmgp5LshoOH$3?HY70$DD`aW|Qg&J9+)HoZ#(>)2Tq#H+K7WaiQk9nKVX0g|T~@j4RK}f;8!BHp^R{W(4u^f0DiI zj@J_SSCTAjyz`okOu3CWbzdRU|2|teWd_0*on-*NjACsssgZ?otEqSXT*2=@CBSp& zDRZi7BLgY}(cEe6P)}|a9?OY9Yjb~q&9~>VKMb$1-n(`X!z1>L+P=Be;kY`K{%vm6 zBe9A8XXH*?+jjA;lW*La>>~cU_&l?24?{b=f1;$%@_5n%WmeF*AE9U<29!fW&*mO3zzcB>z!yyh2zI@`B!J~2d7^%dB0k4m_8o-|r9aT$KG zB$Q^q`~+0Cv@*%3qljSuhTPbM*|5Hd#(t6pYX_#{R3~HHwV@I&xxa(;^;RSr=Iuad zRwCFT-;I*IMu~>!8>V#mRP?wI(X*C*pr-L2G=j@O`J(wu@|ZL73sc6{w?wh1@GAC^ zH>M@)O9jVlw2`N&96EWnlI@U6hI>^fz?d-=aJF=i;2Gl7QY4kVwem7OuB}Ih@?S8A z_*042ttBYF%m)1KJV|zj*dlGc8s38ax&-)f&-20__Ss=sWb1Js*6Z{!k?PCnU-dYq z)7}TfC?5yYZ$+YQPE|NPYnbkO5I~mVe~kUOH{CawjkOLJ2`YFl#7s>R=XfJ3c5H`Vg=Eh5svRRic#qwcX3CyGmg7cMr(W0;RMwv z@T8^!4u}h})*);qt&xOY?9PO_KYf9ex;`}wNP#7m##rS|3~kXE!aeFuY=YM;aPf%+ z`LSM;@!44iXD7adn|zmnE?Py+IDcYi;v8@^KpT8{q5yLqTJZ+|8-qioub@Nw40dow z9z6dXVc8Hp>>nCUw(n~vFD1P3!+bTezw!a}$SP(I$h;vc4jeb?y(bv|8A=OK0%A8# z!c{)<=*WIAYAv2xllp81sBD@E8_O+0Cv%uS`GM?Z zF~!M3pW`>EU(jCMx9m0fVV*#L9C*gC2Q<*FLpIo;C5aa%eiq4`5~an@m;iD|}SM;?zDfkZpGZE)KN72Ag_#wx$ID^wg3yx7C@DU%TlN zvpS-h>dVka&rtrH3n(XTJ$pGZ1bY)HdS_oH@1j)^={ui-OU$a7)WBH!)6S6^o?Q>l z?NESW!dN&ybrY`4Rz-hPCCJ{8v)F9UWs+O`k$g!FVe_5Zu*~f@$h!%7mAOg zOYj6}4cta`dYve-?_?X_uICIf$+R>=3JkVb(M$WD5%peEu*IMR%3szYLD5q9fcYWh zyXhmB{~Qo}(78%C)n!48r?!H=k`FMU!U@duQNs$}tz@-m1i7O>6F(d;LDms-k(d4% zL9^L()c#;1;vM$kW$Nf4)tOdk#my=v>xdtDJmoK$m3$sAEwF{s!3&_s8_9Cp)*v>$ z;}kuU;s;WX8_~GNO%T<$F*bM-7>8!_$1~7DId?v}=pNs|GSBYQZ1JbT-1Tw3`IhOP#tSG9(3q5(A zww;+z^T$rGgQj!g`l1HGWTi=9)#-_7$5ju?+dIV6?fc8km)(eHY6`YEY)}3K-ho2X z?M%sTDWrL$0D4_f0CxgY>8II2G-lHa-m&6Kbn4o8P(R@vvm=0Wo|FY+yOhstNr5>3 zaeh5CcKgbVPnn1IrNz<3fhv%{H3eNi@Q$d*iC|NS1aQ+cnU+2N%^M3=L3T^)>6L>G zOy}e0G=F;#4Ei&|^Lcm*YTu2cwMS>Mh6%l3d@vR1?1;w4WwYR=FHLxhNGvnH-BM^( zc!zh#`#CU|y+Y5YS)fcwmZ@9lO~O*Ap@;WuaSG0)ZcAq)kLzCqR>t>{P~V@7l{^4S zGV!EFFhEMd3mmIAog(uN`chp5lng1-!=E(ys_ti4-;`rSUlP;e$+HC>4ux>x?T552 z`v|*6^&8oFWh)ZtTvC;;SRwQnUo*BXoStg6W4>&DP)z6KM1I|Ej)7p4Cu-C(VflhL5{e)O<( zH)we=OltoXpgfoTKyRZJkvFQRIyL#=WpO+$=siH?zA1sG*P}31YX~k+`pR0?{2~Uu zEpSpvJnL;}%=tzxlaY^W@s>DsiktO8Q@<>}B+30APiLa{x^F5=Moh`MBeR&AZ@NIT zA(pHe-2^pXJYtR~ucF1RRzgGmG`7lM4?TRwM%b8s3dl;Hf~K+Vbn;OZHgv58ac^yf zZVzvU15H_!2P5p9Z0uoE@svO5hRmqsV<^8rm21lO0^iafD(+sG06NR(v%d zrgr&|I0Ic=qE$>5;9#`!&T%|VjDVsMKQv+#1r)nxAc2}64gI_tPcty0t=BB!@ZdD~ z;fFq7CC&&PR$}p$QxSCTCk0~J#K#^X7r@O|Lv(lRQ_jh7iSnHOQoY;UyWf_hWW$px zQnJDk*S$SRM8>U9t9=Gf>Dmh-cX}py_zqz2*%{QmPXwGZGzS59ngCtoNp`Z6!LnvQ zGa{G}#eOe^MdIyTmC?lGyci`Hc zkIA0fbHPFTWu*LS1zNfL0$DX!heLbD@rv!cD38koA5ko&-cv*POa4W|Z`I;R=f@6e zQl*AH_1+^kDwc$;LSi*P1 zwAPXnD*-k;nMY4-&H(rQ zW@70>htLL%V%BHvbb-QiJJcgwi4-Q5uB`5{wIC8_A^irbM|R9Bknn>$~gJXy(o*tc#xr`71Bv7wcx= z)gkMU{ki!xJ?SVj5Kusu`5Yx$K0FYxxBzs&aUzp~86egiN=J5|L88$odF9ptv>9;U zarkraJlTZCz+`AO>6F07MxSOC&PLI1=aCI!I;c*ik?dB{W#;=j(Y}A_^k@S2Ipx#Q zTd73SxT!#p8G0NYos6kyOA2fJ#sO)`r@~)(JMh7PVa@WUC}O zv85S)H@2q3Pt^tGn@*uOiQL{sWF~KSKoYv}t)uoed=2@ar+PUtKmm9lyntkR$LRv zUM)cvIA`eFwG)|ggZaV;69r*{Um0V6G8d_Ic+)K#GH`42EatM|KInL=82bc#00o}! zp@NPh*v!5p7q5kqdDTmyV%QSe9J&R${z(Sy<6f|Or=KAB;5KYi(FiZ&(==zk7K>Lc z14V-QFk7~hNs`?{%U{XzE0lGCNI(pa%rc|9THDwa_sZaf>a(=kzm2Vy*v?3@+mZTP z6;S-R4%fF>qp?;qUf$nBRDawUlok6hIQ2C<;OmZGj!z_I%hpln75~V7rE%6qLXs7a zDyQ}fCxBFuG!&y{NtJ)(f&E_M`?uYIi57h79Gr?B+ip`)uR@}>sGFU3 zV+-k0m!yJw3izl2HKht zN0I%=s#gFzj`y>wb~EULJv-R;eE>+xd>1i95`(fMmh^L)VDwbR{tFyc`L+QwE)!vcSL5L@+C( ziGJLD4J90r2Y;rlrl*FRD4dl`-sv=xMg2kK9ox*j+53_%)ionjZ2$-_r=br^E)tXT zKH%g9cd~lvQs|NcIiBDvGQ4q^ZQUP3dKzM>j(ZipS#k=9h%><7Rhl>>4`v`#jhrKq zB6uc?f9V+w} z2_`vzO-So=Uyyk*6czrSK&Cz^6?))@bf~Tuzm;A=L>{^_p-FvAvS9!UHs-g6WaZaADDigYzUBG z@%WwD)5p<7Vf_cTNNxdiUUCjAj(x)uup}x-eF#S7JBaJspCI+`H}DR(;VZq;$ga-S z?Ae4wt|zk9%-h_YYEO*Af<;C`rTm>_apo9HAM23ocHDX08x8`SUW2`#WysVY8ql*J z@?Wp&0Txfi*cIE9&8J8Q(?@qJ8Sf+o$j#1ZFIWLgSTa0iARsXF+V&2{0W(5OCLo0d$E~N;=DMR zth*E6dPfikAHiZjZeib=sr2B)XxJ^$%V>X07228F)BaE~mVN1rDxMD$mpA61GD?P? zJKjZ*mJ06FnMv&)T_Yo!uNm9fi-nBib8_Hl026szoeC}xSe~dz9Gr~chO6(P+Qtib zH4UX!5z`TS%oXTJN>I7nMDlJ{3NtcyH$8b=hRWZ$DL8Zw(BxB@;6K-QO#3=1YEjk! zdEE@w{AW$)*!%|Rd(LB#nZN1SUsa^p(8;Db#iEOGiNNXOL42vX7ap)vN6!ClF^#Vb z`30Y+(ck{npra#*Z#f|zSRHs&6+cvqDstZt+kqlx^Scd{r=&{VT8?ApR4gjF-@>SG zl?9ROjL>$YAf()8jss^`z?^D*es-7^wLs%U&U-NaA+d`_*+dVc4jVI<)kEZ zcx;JwIQfB_im#CVqu)rgSHSE@x8}P@{9-yXCSaX?jnqv<9|bKsh9|AD-9v1Sq(J+=*rdvi|e?FT@(Af872xQjNZb$}}Y_DJY@ zfG&>y!M1O?OAi{#qw%ey9IIy{E%EcGx$PcIZAKjbvwJIQT_#5yYuAF<5J1a3Q$fN` z1+xPQS)f(75f{zWqt^uL=(&v@cDRv7pMLtq^{xBZzE3)I^c^>+`ZXPD{n^3y`6L5# zl=lOh#9E^NN{_gXp9R&AJ1Exv1q8D`L6=Xybor+<{0ibozJ}}bO*GPAR#7|-y0?Zs zG=GuMY3W>87JeJajMuY|MwL)Tcmq<1@I(y{_P}G>%Je>#LU_kdcJWq4!wxjsy{zS~<@#vEh zLo_5)2x~T0Q2nS`RI+&t-a2hoE!iJW20Crfod-csvr>tS-E9LsLpMOK=>)WJw>a>g z?Tz*r%F&ZA8px}E(@?;KImlLRgxx>2$UIC~3xnkaP|x!n_$2#;n#;X}`>#$$?&sF? zx9-*8LSuB^;h2 z!k=1nj9zN9B9n)=Q3)z#UUQgpOfH;F3sy-Z|C3v&_lB!P?aO2OsKp67Rbq5%j}(7R z=Ts`mU~$tFZ0$QNG57S*UaHA^BBjVqT$e87h{$ z1AngXKQt|X7IzxZ=@lv{f?tGwS#X{g^)M>0fragR6Nsq!ESReW%nR!7g9#1MW;cdT z;IIF(v0R0g@biBhb17Jz#KireTbs@b@gR2}`P+wL#@+a~(S_h~-b}MalLpBJrFp1Y zqtndgSrl#DCIic13|TDJPds#ra6-*Ap?UvY>NfBMXs9@%VrYbVbl2c@6BE($)!S(a z7{%Ab9l(-PmUL-L4YQgbic$i&4CnWI?6$OIR1!Eqf~2=%==cc)s?A4g$J%kj)w|T- z`$c?qOBH+L)k$D+(-6JZ(`HNc%<0{rS_BVW#k;%~k~otU9ODmTw-W?-_#X+x zy>QXt`M6j;1nIt-LYMi?1^?aeBH3B~H0b;z+|SLsOoUEUtM4KcdGH}gZ{AB#j0(vR zKZ|;gorbP)NAN`j0k_XM&AFd47$3)As#Z4zzC5aq*Op z|0!pki{#;PDP3wH9f9>u$I!xpa3V2j9h*Dd72I-ECl#_KY;$)GI@jDv?nUb2p&yHB ztwaRn*&!@Bbb(}*ej_&r=HM)!>2P1T9_n~s%8ZQ^lBr)gcP6)AFr)v-w6_7kz&e?^ z-r!929x0%=zaVHaxk9zpDMIR(h6R5msIl2a)NU)_S-zP^9zKnrD{Im~s3RX`UunZz z#!hjpzuENU>O$g>t-^ep6#zxX{Yhxb_;S}PF{H`iAd+>z&Dh=!fNu0GP7yX^Pmb4< za(_CtR19USZ}ns6zu~a{u`;Nu%f>hDM1|PE3!kyOMZNS7lHtTZz>C-Q;A*Ot9au3-a#2;AVI$aB9L`I5Y4p z4vGZeW|TVoCi8(zuiv%2eacMWKeqm0bS*cn}m9LCzo%fXG%i%@lf z39o(4e6}L*qdGX+0|Q1yKwAFf3L0hpas* z4qnJP0j&kQX^rK2m|cGvY;&Co;+CJIB4_ogPS0>b_UC1xCaC5qi1fjFkL&n%rwT}1 zxQ&eb86jRgFKo}blS3zG!wH1~yx`ACA~R|WTfCn!75liHzWykgsdR>MEUzWgdTzkG z>+`{zT{?IvzD(vOUjb#)Lg<6i*Vs0A6En}_1hlcf#|G|k7AUBfLWdiwu&le1u|9DY zd|rEno6mn>ZyLPBxyC~b(2c06)>@iFe4hu?>WuK4jnAQ$qXziPOb3piyl~Rz z`{1(31rU8K0?yM05v553^!EmuD&Bs=3M`Rfl-RBE$+PuNW5~WnvMpYPj^%(1q zw$Q@Hc?6uDC&-`TO{>GpnVAVTob$nz_Lna~=Za47M903u(CMGpvi(NjUSI^ylpTU^ zq=T!hOY`}C$t}2OM<|#tTgG3pwGWJT$1$?5->TmqbhHsw8pxwbp|t zMWl(QzXSape+vYkGAGCJYm!}h228&9fVxgz0Tf5VfOM27?Y=8YH+*pfqEMS1f_YGN zM;&u3=`dXVpE#C09gJs3oj_Bj8&PK&YqI-uD7xggpUX;~hi;Zd^mJT5t*=dmuM9Q| zb1WJ3h`SG|xWA$9*W!_Xasm0*b&9(1R{@=f>rl&KCVDrhB`n;S4&R+ipuYL-utg$^ zm|Rar&Df&Xs957r0J-TTvoX8Y%Zk_)EP;Ty$2b6SAu>0fBoeG@ccgAX{j zi4l}mT|$BHRpI6|F&gsZ4=9`Liq{@cgIl=S?!Mk~aL0cHzP5irSHG%)AC`&>I}U9| zW3Tm@2`(F`LftBqG4h=ak%*@iH!VnX@g@?H5Qcm+Yv{h%D155;18Lv(nE3rzNGAE_ z@MCxis8@0(Jaj{fZb>-@_Z%uEd#m?zO!5=(T<>0J@w1Ucd%mSA7q62AFb_@oSx$u= z`{0C+Vf@BOFEaM>H+g#W3bJm?qh^Ci;KRSW#O3Ncq*L~uku&{;3_absecL`#v^km9 z-U)^LoX70+W!A!k+Y|XczgZG(#(9o(roo7m1n{EZHXXSvA~ejef`8m7ZJ-k7?+kwe zgI8V1_e3X@4mklw=3O90ML8%lzrt&NdTL0=_1wSCOY|M z>7=J5idNrDPs?%D~ar51`K4+tjpw8;vaFo}IwW&~nNWNOC6Q z-)`~<39l1ny(yrS?IjBr2jE=3Q&rHtHd=b2p60H!#nE&xs4aG(x_p0pUwi^mS-A<} z8Hbo##%Y8!%}2iTqd1q!88q!`1HOLfDO)l+7v8=*L~6t|u;A2kdd}GZ6wOrw!+P;B z{BR$%*N$W^qbRb=(EuGxaKmO>k0Yue$ro8N4c~lF&P*QfhBCv}pdc$4T2{It8^g=k zf%6n5=hV=I6H?4O18t-v6@(h+YBA&1D)fXPf|~ByB4`d&2w?=|*Jo<1*9o=>t9xQ$@voKVUyTcSez`jH&e#RiFvB z;q*5VoKP_Vh<zpdlE2dokcIIv=9?j zZ~QgRhx#9rLmR9vu>YD~(B&D@^mqOSeCM?Q4#wpo!>l^qsDlT!jM$H?-6Prl6SJwT z0|!fOHpfnBK~PHb2q_4C2)gdBL?@3nkU~$cpP2p@{b_55ku!V+a(|c5;;3JOvwbCO zuK!}P%2l4ugj<bD2Vto@dFai8sz!6_F{ctX z9W=v<8xDa-iPETe@H0NByjXf2md+zA>MWu+0{zX zF#5|5dhqgFW>V&Hf_=}xd7UTl;!#6VFglS5e`^S$=E{R)E|M!^(#{tAkw^1twV<;q zH?Mva1=po15NCaX;BG<|IsES}Tw~F~XlH!K)4Ip#xv&&oi-shP|DDYKyt9FQD3Jre zoD=Z1VlZ~h(1*?sdDva^$+lI4KsYaJ$!XAh)#&^j)*V*ZRuHoS@Mt{k*>xRh->A4#0rv#8>d*?4R0a^n6`hS+;$Fz-|5(N|Fu z36S|9s1dhD$%jml%Q-%{wb%&#oyg51{^sM_$WfBRN`k<n}34(y}2+`E)__1&V!nxQoO-) z`9QN^19sGyf*xrM3QkJelUJadIF7ao^y-J;vl%*Qx=A81{27ZX1`eU4-H&+Zz4U4D zTN#@9XFdDq!$o*b{v`FQPz8xk#L=jEF)Eqn0Zd&w*u7(c@T_)k|{iL2D%o?p=TS$+2myt zk?|>28mb$LhcDYf<0W4hwW?@9iqvSFb~x0$NO9bO5}Lbfj3{W|V_kI<=+p}-TopAP zhvuEczO~6vlVc92Pf@3DzFLB_ZIxiOV+`K@5C?a<1j$i!S7y&EoNtW)Gr^RjQK!mk>)duS0s zs~YO4dc^h~Jr76Urc%{(ij!w2;v~3{Ui|0?(#km7tj%juW?;as@K}PbN2~I+E+l}h zMH8rb=t+7i>k<<+GZxKBWud~JXeO!ZG}1TihL@g}196E(K{;*_fFRJrBUx?G;P#JbvSt)fcP(WvLUSk`_ zFeKCamR^Y5h3`Lm3(8kqh3nU^!y)Yx`RNxoqowA0*m>|TUQ)V)dQW=9Xr{)X6&$y{ ze&jCx`05H`oZ_+n`stuA=__&@JVo7?bhB?euAsFFUNHHxH=Dn(8y)CVpe&n;g9d|P zP}W(h+CPD3^R^6Kysw0!b6mh9?;+%Q-5q7xNCPnBholtz!K#B%jQ&+Mp{v*>a%@L3 z6WdpX7MhB{BmJDKR@xZ7o^u&(ktzaee@+U;5@X=4qSrX^l_?F}_XW&4nZu~ukfmDZ zGN`=balwlW0%IHwaIU7>s^_U$w9)tkH1l7C|B+@?ooyrVU7f_NT@osk^YlZHuAc!C z+;2p4s1Y94>SQ8zC849OTggJlI)Pt~JS{&gN-O!=ctmnFe}z&m+V?~XMvDF=Wt>B8 zRg5W6K71WM*j^4LPF=zEVVR`EL6skPV*zLi{!0IRu7oSTwX^56;^?pYU%=v!(}2ov zr0H8GQ!P15?Avz|&HJ*K-mWQOa(~3oip)^BWY%F2lJFF*@rx%DHw3a0Q%vdUH!BG_ z@`15US3n+A`e)^FB26h~wpVk@)6Gr!7e7uN`_S_W||Eo(Pi zyHgr|($@eJ!lIDr!qrGQCKk8tF%o*sSOMfUUsuU=^9k>x1Z=>qth`MYTEp?;Or6C@ z=!Y(hel>wJyQ^7|DF-RzC!p&xTd=W{KegGQhXnzySkBsyRCp_+D2espM1wlCgApVX zMpQYt$k3U7qNL}xG0t0vY3tP-ShnL8ZcKg!i#-R)W2Gt(tzV9suUvxuw{J1LaaFK8 z+ntC#oJ6D|Hc&l1b)*b?!Ojg4!WF3lv^3rwJa3JJ7HbwD(*t+$v)|Kb(9#L8&9##8 z*v1#S?Qy3fcmJ{bIoA5-DNoQX-U0T*as$5Hx}_-NX#|L+M)1rGaoVhFjhDUuOuHx7 z(o5Y}*qo-7D8|GGE=?4HrK%BhM8pMJRNVn)$};rxogiEgtd8TK-UGhcc?`^VWZGdg zqPB!YM=G=Hj$T8SFEjBCZXfjeZUg!L>NC6{=Ky@p7c)sKOJH^AMm8`Zn%>|zw5mH6 zGB@Sc&{7#?q0YxP{QQS4-ZLqaI`>!r+u$K;+7u@6DsTV^f2C-uZx5)O{|W?-oTer5 znsj>A1n_rXG&NSNu>U55gzM9aYSC4V# zyzRg&bQ*Hon2BD@%ENIcuL#;%PZp-=u{_>xWG|mW&18F)H|?JWTkh4-y#i10#-kA( zN@;;tmVE|Zw@pxj;~HvV&v{)nQ;|$_8B{Jh%H;oAgYqXTqXb=L@bu>{%CV%NV(=Yi zE@OwX#N>$EpfnNL9aojf^z}^ zd$*A?g=fV6S_(FezKH1HtGBKIcnumQ?|?_p`+=iXvpE@@{(9 z>bDeXNH*<=M;;yT8o{jn5aED@WmAT=r4gq%SAXA^L!>>u!Aa> zl@pO)Mr?@{OIE!-fkreZ;;~u9G}Sr}`t%=W3cYGj^kr|RaA7^~N#YW6z#)`eyWh<2 zIa!1*ujBUkb^Ac==G{ofMTth+*5S?_cTm;A4l-Cd35ayOf?iuB$On&R@G?)4Xc=!o z+f@@;{eXR_vT_=79=8V@7;$t^JqmI!3L6N zsmY-zW}&JP-F$BZq{@k6C!dX+lUV}v9Lr>U+Tx%@*-auJrOTYXR|0j7mQl^hr;OYN zCA2Waf!AnS#`N@O(4@}~$%EO?+1odiP?5o66nM&vH@Nsa_#NbdToyTlP8B^Oj;)~A z$-Ru?Xf5L3$_Ia%TN(Qb&J8`fmTovT$aMNiQf6;D%+xZ)j#YCZ`5r@L_Ap%DdxQk~ zrjh$A&kGz~RA}oNSLFTPf;Dm7KtfnWVm{^%vJZWRn{F>=;912!5{mMBKH@={30=_Tg=9`k2c* zt?K{ASO~Sz%Gd?4#AXBItUHf7UueRnX%f8heGABQaESPbIkR7J9Zw39reU zM3voTshVOpbCYw*@7VhbSKn|ENdA~gMK@KDh#z5$ukJk}k{559Tr2~0OAb)Q^eZII z$pl1Pf5iC&?dUkVj$e#RqNA7Fh^|@>*Yg4BeONvOM_hsPw0fxiOq(`Yi?SmZFy31g zL8>FS12Jw!H`ZAWHIBWi`hFvcIQqtr`uA$Ups^S2ND!yu$={fF9Q&bu?Q}Tjwm2?3 zvVs_1n~Y4CJ!G`wLy6u$fBMBWi&+)(1G*QFK>PhMEEU-e8iG>EhwB^J%)KtmXJup5 z{P+k~H5ZU}&w8w~bp>#HF+$db%3%-nbs*~%mkss_#XF1FfZ}dU^3zVJdI?f1`9b_XKwEtL6nhI!02r7J@s^ zmSO8Tb7*;cBgkHq2nPPlM*iYlB)~)qkJJcAw4oH*w&XkOv+)o85F3gkKlp)F(>Yev z<7ud+ULG`MwcsmH73fSG477gd!0bI|VCMyO5dH8YkTBMwXJTIim%h0unVbDBu3rX{ zt8da9^NWb;@+_Xhk`r`em@8;PZS3c?YP`PO09?{6W0%JG&|)r!R9~0Oc4TS^ZT-4= z+q$QNRnrd8!0=?Cx2Og@NS;Ky_5&n8|E|DX`!u<}*&DiQ6$;8;tUzw@KPX-p#k(i` z37wBTK~ozKGJs><?M*lB!C%o{~r_(u{~ zn6f?693yV)4`kWk2238VAirLoq?!`GpmpIYR(WI+D)EYBS14)IqX+mPVb%;hBcYSL zJT8Ii7zTfzq>MDoddcUDR-kiHBK%tZ9cL|&6Er=urEkjjkRFi?VAPUC{&Mf+wtJ!k zX8yf!zVBMZXV!p%k4c2Dxt+QMJp^)BBhW5%nXVrpiSXd?sAB+?y0U`@#4hx{T&Nizl5yFRLu&Qt9R2De$$b z2{sP7h&G2;!o=PI_IGI}I?R42`n*+w?2Ibhv?Z7Dr>EdF9062uCenGFia&U4K#76U za6~1CmHMEB4&6L~I7=ZrH_Di5T3I2F{B8I(xBnE8C?!Q!sd#5kA(?o=hKM)&gWr?d z;nZ92;J3bKSm|jp-m!WYb=~o}>Qi_V4VY6(Mf%s{QF~8ho!UU8wLW7-y?Ew=<7_;| z+mHahII+Ezgw3mlaA9^Ky7o&Kso742*F3^$>PR|m+Vv6t%zQu#_vZ0FJmj)O$7PUd zNFUHLP=(bN{p=i@Z7^SQ6x%yjl7gmd6a`s>;=S+5s8}LdcI^ysowEhsWkis(+EeiR zcRU&!$U(hjvheC(Ii#jO0Msn+ZW zu;*J?I8~Lmu+tu_Up|pO>?+2ZH3xAXmkA4Vk%orr_M_CWJoMz=4D54LiZT8)$Zl)W zg3@>A31_~#F7(~iiY1LBVe7LeFs|evcrdk}ZgW~AT<-A+)LuG4X}2q#xk!)f_Im>- zepx~@{cZr+L`fRZA|Yf&L`dAaR1mT%2o7t`W9xG5Q2(d(C~o8&UO)RG{ZL{=S+7>G za;Szj%@&90i^>IZ7Kce@wJd(__>rmGXn}saYSZu?;mnkws z?hyXJl^al&?EU4a^^gRsl_=YT5ch# z7vwO$OItV>*GBZit{Q0Xx1f2)+u#!YNUZVb34J=@Bx8SD8D?9A(DH2v>TTJI=N6|B z8;h%OK~fO*eQpfg9`7bEUKZn%rOVKVxLB|u+KHVp;y_8gC7nG<7PN{aLHVVI=%iaR z{ysel8uxq!+pCv?<_pS5MUw)?Lk_h0jMHoPEU3`@0UN#FiO$<~n5wSeaony@iZiB> z3XxErzMDDitv90gEfj&`U^9Rop5T&?FSrp9jeZ#%Lwo(DKzDr`ildzU`G&oc(tuOfLG5b5+|i>Be@{NjDLO*lRK9 zjXXexDp1LDH(j+ygi*)3CWK5KHpqPGbB9@!SKD6EjEvAUmCMAFDCY(=OclI$3Cs{#Ea5eRK zzMonStmN*nmblGFmr4~4GLDI_Nv-o9SidL*myTHylYgh^uJ(Pvi2X_I6NlJG?x~FO z>^O3DSr{6>YYsAw>C+pnF8G@K3?O1)h69&>f!ga+;*g?$6O9604)A ziG04`-DOMU+GYn&Yc2(Q?#R&(ykfi(`Z0#F0Z`&~Z?EA1{=R|aI(m(cM`BC~rZ68UUq=F4Un3Ct-;pkkNFYf#$ zN{*bpOowZ`c$ZcsqgapUc=10ATnNLlPUtq|!#O(|z7&$@v!2l7SRXGqOQ`Fg4QLH_VW4|8#Kvi#@pk1xNU59DnF*!0;7y^&4>x8Sf&jEjA3aOmpE*j&z2e%u3AR3;M^tHoI z+`Q=qxY`{JcZ)q>L=Oc({n!%LVzVrmfJ&gYiV7W`C<>aEbdp1|3y`epPEPtFq>4?Y zRDbJQ(DZwZyxO!Jy3O7L4y{l`Rnh;U+IJJd?^`-(_}~fhW$gs8?#30`-Y10?f87eS zD`&&Qek$baOncB2v>RPc*~Dc=@8G+0EQJLNa^UMV6WIk8SLw0jI_SNL4<5B=0A0Ju zLjOq=2Ym>Do1r=iYQ6xJa$Z0UZU-j1=_K&{`I8)1Xr+?1B0wtdE!mp#oxHnx3ya_% z^!una6)#C=I{v(Y;*TnsINcxotX%m%#=|6?8QK0l@99s^@TL? z&6zvjPtjuE-*8L!c_4NC3``x!ge?^r_(p~TI=x*1UNX6f=a_rIH(mreEqcPPTp>z* zTN{AF^?Y2NR}Fo7&qBkYwb)VjHR-t~OAh2Cd~b_5(s)0Sy2z^Fv9=rZ$+S=uG?2?~ zb$URQ#oIV<@I@f+JAsaIUBZkKCy;q70b~dNA??4+*`J54>7KF(WHnorty4TjJ!)j21{=DTiS3Vbb>24R2o}MgwJ(sq$ABY+mFJ zclcz{;V&+@WA`$|m-!~(Dac?aom#qHMU7~_J0pk;e?*H9)gT$Fgl!x~;AQbMG*hRa zUEFk3inXm|rb&Ipm=M%S>(GAm(LYqE{YHk3n6E+g{z6!E9 zDhZ5r3Mpov3Wn9M0&e?()n%8`=g@2UJD(p^d8-9lD;W&(8!vHP-hA4v--BgLGLU@V zTYC7jD|cG7&|7kQQD$2TW4+}(RDHgT{`v12tMZ^8-%Z-Y7GEg_Hc2b!4(F5fSnwpM zwr4S&X8s8K-ChYgN_%kcf&${`5XcJ;%5Q=p_kYBrQvs$73^B{Oo>jo(^W=flR&1Zp zM_hj=E^jy1#kIrnzyb{riP3}L-TG+yxl0G#{Be_~CYMNleF=sdnW}hb>M45vxfb>K zW63z)Yl9E|Nx@Ht8bH^uC*909MiKX~;g`*e;cxBf$haaFx0L4*!FUv1xG7&SygZ#& z-n&XFgQhSeHMwNyZWv8jJqZVInM)N+oI*-mO&13cF z7_&A8o%B=pVr;r(87uqxH13xS!*5>t(Q7e}=*60R9PnfW=x>r2rbJ%`Ir5s&wKY>Qxm#xW$_n-7w$Bxu*t781Md zF}VD7jHZc;3dI6mfWEEg;mcpr(C@?(T&QCMy^fi};j@T&ai9un&RtNYzex<%eNIE0 zhNAGiU5kLd{0%tIZ!e5oqzMierBii7Q8cCzk8QI?$(30dbpF(C{Pp`7k#PJ$Tu(wK zSviV$Th!3Tzs2lqT@4!h-zpSVa|s{*Bf*!r(*S0?kOfx33y{ZZu0Q``J5$H)!LsWA zaa^AbxaeaT9Ugu_EnmH6ucTK%Igd6nWRwZkC{pyGEsM^xZvY1Kib&zk*?dj;7WV7R z8Ax&X5IHq=leG|Wr(-n+H1EC?Oj#%+)ZO`%Xbf}CY>kinB}a{@xx`PL(_jLrNCj>L zr-7V%HrX_x2Ms+W&yc$JtawU4qzUjDqO!KhSi*f{NQlf=Ja3bffD@pmXFt`*X1~%{?%W-bxC= z`hUhjrjZRJ*~#tN{%7n=qpAGjc#q5pDUvCfLPAA2&))kSDoG`&6p0j~B$Z0TUos_g z$P|)#x93Evicy$+@*L4ipPMV5k-F->$_)+-EowC7C z&QaIb;atAK1Dko;Q@`VxbpN9aMpEZIZTNEqPFCK?razoVjXh&%wfF>}mR3w9OpM6kIEK4|$D_XKrC?IYa;zf1iYR?-fKK9nc=8h#@~h6iBGMdd%cx`; zojhwlk$T3ir;C8wqou&~x;vR$ z^93K=^#m9DzQNjV8|dV~xy&v7{Q%BPA>JC=;Md<4ni!IYHbkzoTNsrAr6O#Ilkz>@ z4dG-ujhw?LLqo7@`Ah?W+sh$H<7e_-2|F%SfHn|#bi{)noSG-0j$QP=!cud;5z}Z zWcGRd(rYW5^>LUUIlhs&iK{V1U6<&o=t{KG`3bvgM>&#FZ=un9x|u|MS74u!S}r_%uW8R2ag^HnWTzTw4x8lpT<()Gp%mB?0V@v_QVyk+5erW(K>n zQIB2^iacM7$6GB#?)!$I-;ZHoTdo>kcKvyLzPp31Oj73@s*R+}?+No^l_YKXQ-hmh z{;~(X;*hkGBvk`XScR{nWW$&f)D>9?2QFTPiM9R2busd>`xb+^=t}5wUYqJHI>Gd<)uQ*|Yq-pz5_AMjg%5;tk&eC_a9IA6 zntw{CBDq3o0F$Llj{VB%sS(C9H=c&*!FQrjgmjLUfBKmi@oIJdo%fxOH zr)kqQ(Eh0t08dvyLDd@aImiN>?em4)NFF9j9%bK;>qE&&(eT8kRN$b!6K72S4#AO$=iipO z4vGpt3X@yf$)%DO^4@(l8yi^##eGbuo623B#AneQnFN&4S%b}zis<9!a(MLD8XVJP zMt^KC0P1s`aQEOvylBn^+W2fS*kXDMdC6+hNpszREHj;6`|*rEh`PhnH$_u}r>11q zv3KnB-__J|S3Gg~9tYl^6HsELOkN-OBm8~u85#M*`AcrvgYM%C@tyWVv4AxXF6`U znSBwzo6Iv*Knok3p7$gNjpWQRan>V+aRiC!O&hh&QcfnlGyar8rOW!77vzmko6n57qA5mrhQjOl*cD5^D%p2!_P5X zbK*F3{OZH&c~?d58o4tUt)kc+onr8K*cg|Uo&@3-e!wONyJ7#<<+x2jfp)D)gWEabu853(MQ4 z4o{pcVP6m0A;FkAy>QkRJkaW+7rsAa<$@RC{bB3q;^Yei1s?&O1?KechiEPfEC!_B z7sBvc>3F?QC3Cp3iKyy3ks=G&kY^b5;LS9li)BlZgSLkhYPM65&w&luT3#CMI6)OyTV%N?QURvPq-3SuQW-ol@IaztnK2lkVs zJw3K_4EEHO3r~)6&WYiL==IxWc({O@QH0fE$;23rTROsRNQwcqUIDtF-yth{W<%FW zH+VOFr=stwu{4nLy&5rjpma+<S+)Ikfd?CF)O(^Zw^Px_|a9Vzt1G`S@83t`z2? zKn=`G{v-l=)0$|Oppk8SGX;3O;Wa;Dl8Gnw?pwvU zY~{{Bs}`bhS(Cuve-%i_%$wTJw`Zr-{J=9G8S;xQTv_j1<+Q;wiu_rB7@v%^hUDG^ z%1fV#YZu;TN&*#W2-m3ryX$FE(M*Ic)nMBbTwi?WWjKH96^>75Md<`e-gG#Eq;V1y z)kdK?TJGSR^d}78O{UlTw_=CJ6Ue+bhQ>5R;~wLEXw0gVBs=TkS;xCb>&a@kc4!Ar zn>Rkxn4=6DsBX0RL%<>C;Wh(dE?Q_8Y%3y$^;EI9!84IE7+JSWoWSUEM$$=F*!P+ zQ08ea(K<7u-aROrYo?BDCS}KZL zs);zJE6$XAN|)Y!34eZn%ubvtAu4|Afv#UkBPtPzWV4)z<97W9vd)(=n0$@-R?m5s zBdzI!J*xagJ$Aq_-y+>(NMk1V<=>;qtEvQ)QZ98ywlyTxb zkoE1^XdgFsX)3d*e^z!HC5GPw(mXw+lxK#{h&d8}`38~8{9`CTuL;XLoJZ|@gd}3N z7ARZf4E?W(!OdqSK>b%cX5RHw*fnSaYu_FtimTO7!dyMLv0sgU<>qve-=Zhr-M2>6 z$eC!(@?GIVhr?*g?GxM{@Eh}9vYgc7H6X_lGPBx~U^2%;R_cvrlsPb?*V_~o|wV`04O>$Q+=&x`S0QlSZ$%If4D1&ZuWqGn}RHly$fyLz~W&P)R3i*jST`YF``` zC1p6U$Ho;S#h)?MR7nApCka4{>}R@Uza$LVH6C@iABUkDvSgU&3lxJZ&?d zCMimyyMB^DqN9{rId(&FZ7uSsya={mKZ7r?)5SlGbw!&_?t+V+yTRP#S?E^31~pt^ zgBMwFj`!;p3fo;Nud2FAb_Jy|&9jy4T%TgMl!ypF|Q zo3h$A6X_jab@<{}CKZOskZmn30C2OuxK-}_oki23`o7yJ(bWQ-oBjjeGy1{LxT!)5 zHmt>VWfWa%&*XVXK`36$qn$JBh|GgD=Iyc?)Lg*1vB-TktMLFrOK+0|@wM2RH$qn3 zd_fz*EwKKv0#*OB6y_}Z1eX4MMtP?T$>(o6;JX;1L%;m6<;~ytyzK;bf z2lP>u@)7c7vkNxxH3KtpD0~(k04{bMrvE@1Ro|Qp^tMHD`IFU*o?;SnYRMDgZDmX? zy%%FwUMJ%n$iU6!IZWh13p!q7K9Zjq#_NA;DB7Z`Dr(}}V=LWiwDnR3^2_Vw%{es# zwHtYg``gJ4d<}z=XL z@V2L`y-rY>A5%f_XaoH%Fh=d0Gq{XO0Nvr33Kbl@+1j|zWHIMR8ybDi_ys5+H2Xd( zUC_>~+PMzBJ@W_t8$SpC(949u+X!8-%L;t%x{N=ibD?p?J)of>Sfo2|0vdAq#3md> z)U@Y1HEZ%`hR?Ut=U6oL9@=#7 z)p|Xmgq~$ZLk3G)4YiBU0SI5Kt zM;Fc z>;h;yVnjUYMy%eL#Fl7WLFtDZc%PKxflSs{NRQlMKQH@CP*w)X@-=3jdm7T$7HS}7 z&jN5I?g`^PUmd--tYUR9u0=a7XJW6>X<*(k*TMg{2Fg%nyyi$WS{8GbV6pqc^w%q? zdblBVzMRdgGF}9B#;qn`;0yEMlMKB+p_u%X+=AyAXp_*4*Kp{;OUBpG5HZb=#(y#d z<5dsShPJIFQ}zbW*e;iRmenO6PDijsCegx(6lrR5%@_+GmcTP}HPJi!ex`4k2@2jW zKn*#M8F#tkq|r_tSM01}9{v}M4vl<;nv16a`PZs+)t8gDCmQz^CfgPI>LVB?!)UxyTMx*CGgj@8Q<*IKpA{R5OvmpoHi~bdY$Xp|F+q~ z{ZB*a%76Nd{GvEglyL?4<%Tigk`<)+iWHjRybL@yV*x3h4X`R?y_399Xh{}1upo+Y z({d-v;C}?!xZn@HA+%=86PoQm%4ppfAyW@z;Hj>?Ol|HG+IQZK%Qp41zYm>+W@++B zN3an*Y%YPjIVaJ3j;(rg&=MK1J;flS5#FUo4pg4~3BeNypgrjbjkUT!?@9k<91A3= z#C&Obg&bk$oe3rVx5h)T&XzeDe1zOO8jmcUxSq>SUs~~a8x-Ydvz_OBsJTfKc@Y)D z7UoPt=ASpf@Z?k`=ZXtX^8Qe_@693RX=^%~_cj>NZYUmOoIIXW+g-k>x9%h>>k&ma@jYo#V=_he91L+?DKRanc5zg_~j?a4#?H*93y4|mWi=dD!XmOJD0R(<41twdl@+2nqwxc(Z-tjW$@HvF&ch(8Q#=*7YrOYPkxm|@obbMNn~>y z>YHDHFB$St_@y2+{JsMe`9DNrCxmuank=x&@%u<{qm?%Lh5^Cx6Kveecv|vPf;dZ^ zq)VKQQ1cmX&M!C&zh=imt4~vb<|HY)BC!OzmRaLW7hCFppP-<-zBpN;88v^uMAgw7 zHmfiX{#*E$fNilj>Xrube{`Ef&h4wU%ypv1KitWk_pR)q@KrP-u#`Nym?5ah#AWq+gWjvf7mW6iSil+PFYP`VKiLO2E0A@OkG4ansQD4AoqA_2R z5vYBmzF)?n9%c>rwo?ht>UcoVy9BUT_=4bw^{8~|d`9pNLlv&bUyj}D7157f2{hpE6!7-tdD?Pa67|dLv3qad zVS_IJgX0d$fbs?7MGvxD(e~kI%ssgl5NcmV6U#~%-RI-bu?u&=PFl}&%f=FJC59RQXJoAa%^L&RVOEode72kyes(WUs{!VX#{BTIf&MT65E|0EXQ!5iZ)V-pxoGdm8$J`F!y>f=VmLuz2h zKdyi=a{~7MPm7%0ca{BfD2T>MDxn@PXU6ojIr>xbgN?**ZFlM*WPyIcJ%0W;>wN@X zsPY-iy=nrIRHmZ4!E)$zj5pS~r%qS2oup0II@z#yI>awM0;&y4;wOb~saBX6B1Q?| z?z4L&-oS(YQZdG6m-D$!gaT^)(aicrfeIxcZb;Z|LV|#!F#0S`#HSy;|YBH_D))|-jX>a`JHyZut9&g4B$P} zZDa?R&unov!4o6RX-!QNRk*(meE4ZXg#Mav^VM{)WA;ku>UbU|hzgk9l0`)Nx(VWa zOu&%}pw7%BnpceFH?d^F z!^co(5yt*%8ED!jIre+=8N5$jmmG8-U>i2ovx44AJC*NT2JB@JkdAvvr{&7>cic$^ zZkMjXh1TDYWnC5{u}c@)ydB36-l7Kv$9<(~hM7cx_Zurm0>0hsS}Gs&6MLWkfi6sZ zNBa}HndSSA(eDonsMwZQ*V2L)@q5Ql?B&TXmuUwQk=} zLivla!%PoRO66(pY#M--GY&(WRTse@2}7KQiYe=rj+K|@^14%PL}zZ~P(3^w+- zFOq#ofW<!vw0$#klR2^gVtaHFP|ckiQekp2iK=BB^JpB z(A_f~P?_~qd{+!{Iuc|%tqnZw_(6hk3iUmaFUlxhE9e|tcZbJXt^Xb@K0o4^wmlp-&G9JsgWbUE4Jx_`I1^H%lSGw7Od1)-S*hULtD)SAfz7_t{kKa(Loy zFsc9W1HbgHA$vVHfqS|dFt4kKINwczT6as>K)YJL{9QS^SxgNEPnZW{%I4CZU#poV zgAMfHwMbgr7{Dg%I7^D%hRN~gv&llsV)WRG^G|=4#i!pmh{P7mtd-dEni2c%BpQ2a zhW_#xEbQ*bQ&XSeBV;<*oj-&2KWTsi_djBZ8#BpLpIT^N`<#(Y9*^w?oyqTgy|{dX zDxGWc1iz57f}Sg`p*~Gr8gugj_T~6w-O9ma=aGqIy@9Vtwb52o;`Iub2aGVCWifmS z{Vh1neg*&B-oNnAtTHe&XaJpR@PRio%!npEMLjkM5c2&G*K9rmJ5PKeo7eDwk-R0< zyLE~_K0S^N+4TxG_N*qC7i2Rx^YVy1zQJ{PH9>;A9aR~hNeB8>ao|@C>Tu!;S&(mv zhyUclu9H=8G1o=CBeLU|veL*dFP=#s4y6$lLC}()%)HkGXpy}*?P&tw%I6X?uvnJ% zEa>8SCR@@0g<~|F^KpcK7!UoPT_fvCUoq>>zNUJ;hp6_8a6Hdm2AxYijV!Ca3%|}e zOOz)ku{k@c7&(rAtK!V3)uCs=-l`;|Bq+f418F$$O%ODAuT7H5@3ZKHDOD6`(mV%M z&~y9<-E&hLR=PX|Mc(qL;rb%_U2-vAV6cY&d(}mB_xWZ(tgg#l>Z zU?fmke~z7xl(G9ou3PRf3aC2;(K_KxhUFOH&7PCc8@(i8H0wVuLs&sBy?DWk z>?$WZdIEA}lRl6ZAE24?T#vXWi&3&LqoW5dlaN`j7@5`HRP&({6pR1MJlVRECjPiY zy*A7Qt`_1V<6T?H_oI8UVhl^$WKzk=(lqva2n)+^HUr(nG2y8P9LslkIU7@8&NPK; z(dIe9=;V=4tk@P`uB&*QbW9myZSB?RxWd~osXU&o(Tb%;#mi}A&r2Jf$9IU@lsjan z&p%x8HW4|DOoemO^>O=~a{5eHp9Tl!^XyX!sA_{OeQ~G`=PX`BPE0#P)VIsv7xLxs zknd@@F;Wk|vS~rC3;b9~p(ZYScnz*C%>dYJ1~c`lB689XWfkgouwt44r2fe$Z)Jov z4p`@j>aD*(lYD)glT!j6Us2e7mI@bqy8`F@OhvQHUo-I+@@ZnM9y+(v6uZx_hee~& z)brSUAP911nm;{YoU9)LG;<91sD^;f*%QH)_*6K>wU%Vbx`XWJtLVhHDzuZ!3QiBv z61i+(=qgJVJlXMt`e_~@=Sr@yTPC{FE&d)vy)YHvsZ&HLraNG&ehSuX@uY4M99OS( z68v4Z3whSAApyGUK-}y^I&Jz{dbqs~yf2rb8ZJK2&s_w5Y;`3*vo6A;Z`!cqxg6wL zoJbVb7PIB5^F$i!;^DyLaoCVl(2?s}C{0D2m);tKf|al0h00t`b9@ebe02@glQaOT zB~kE`ax&`T+_l9SgY+bqM+#7OWq%qF=u#d^A52eTshI+b>n3#Pp*vvP96K~EDIH}R zwAX3RxCgFjZlV3ofZ5G$u4A(Q5##W`On1zEutI4&RNfW>q6=oyRJRi-oa71he4<91PLPX{#=A z=(9l(w(dDOt8jsBlXb%z*H_ZucMZJo83*X86*9;zCJ)zCHj|K}xhRXvad|cGA@%M5 zp@$CQU}K0W@*H^sV>FZ4qe)`q^(;PcKY9c&)4T+mwusY|Y8{}j*8>x?vJu^U5~!wp z#Kt>hh*X9VZoI67D%#uN_T6TTd73wxcSM&aa1IwM4p3xf_y@cFab>PAIfew<$v}Jb z3gc2;i&x&M1gR_hKt$ym;vcPvWSu;j4bExs)r*;;@e?wzfMZGz@fIOuoeSzGd_m#H zFUgR)1|#mNf>xPw^QhaCnH5+a#kKNLKtVom)O3PI$;Z&oloKFtUNCWerHtCXrwN~$ z7>Y7vrqSLAX}p2&iH@}HBP!;e`1>p^5jgo8jn}pUM>icJ(~V@2t`{HAzPSbQXMLk* zb0x6!=zH{GiwgM{YJ=}MilD+)9ViU%6|N09i7YiwV{P%NAhIb2zdWE#m!;le?8?Q6 z<>I5j^pX-@*5C%s{_R8^DgT()-_mLBibSY>w+|#louVp|S0GY*C9Db5hR?OE&`IOJ z%!Uc}v~7wFwg3B^RX05igRGvB(vs(x*^`O-)p!(Fi1TtjYT#4{J`I2VlI>3kKu@;~ zf)>!gumwVD9n?&2@AJhDy%_vSyUyhVtg&9QCkk#Yr7gM^OmvF_%#R-jbB23B!|%JG zD0Vv1?Ye+B>?xudi`#)g!!4mth7SMJ{Z4vqRXC8|+=VP{Uc(bdmb0Eg7;OvsNRMsX zj(_KWBJcVoME|0$V%#_Y6Az^mHT!Hf^Na!iz1KT>=+{zwNT-GNue=3A^UZM78Xnc0 zvX4|OGZs>>rD$d74I;QT!|svuCgj*LgKpXz3BJ7Zhlc|{62($(?x&nfC#O_{sPrYQ z!ji4jMVb<6YdP$>528!Ej&WV}qoDnMIr{99K-ff2X4|YL=q>w;{5IPR2Hxe<(_HRl z*O(OEJ9jymbZ!=@C{n}w8^uL)X$0I@IGMG#N+sG4OUaYU6L_fd4}96H3zM@2V9(G9 z?JUegbChJ6h#e>BS$SjB8J7(V-*j_+6;rx+Y$F@xbOxGku7F3)`xtlb9Lj%aN0Sa_ z;|ZHrP}P6B@Rh9;tMmF4x*e{70!|n3_K1?uMl~lY{V18O8vKmJKHMcupMUa1E~#{z zeFGn)X&CN*~6eQj7JvkV6~casbHLdZkC=6RO}wYT`7G; zul*V;V^oHWJ#=aNg)*%EGoC*D>Vj@Q?g4`R9dN@DDZcmA!%%L&6#uSiJCzO7W$u|C zr$5$|(;>Wws26V~uFC(3JdIw1uxK%)b5cN6s^`L-^#ai%-{D$e7cN^VmH=!@8o&W?CP}t}KWGf9|&4ey0 zUEm?I>y05E!`D#cuq`QWwg6AmBKZmq{`|WJUqIpC5fpVToEj@s1O3Nc#5qfz8e4P- zLwC=`*W{v5lCB-=w!alTOqHg8cQ}w+o$JxyoF25HZ@h43)irW2cRK&+G%dz$!e_K$ zlPUkwvAwL~uo9Ih&44Xm>Tss?8p5Z?QDD(*`03SC^!CDJf&71G$TxlkTA^hp0-;H` zXU7ffq+w3vtIvVlqmHPB%bi=xI}=aI2z1!^Fw)hPg;oK|BKZT`>3lVDs5NC3^&34* z7w!0pJ-)s|(&jALV3RLw{;!`@cNSB@wuM#0=4ryZ4o!sSjN;zcEL<{QwzA`7zIH-%g?)DV3= zVhu`a!r@S6DJ{P2E==1Ifjo>Ysh3qgzLe)d9bbOrS121XLfZuL_eLJ9d3GMyUM)m= zvnke7KF1W4$I+H(3Be=Pr{FEsNB+YnXw&$7c=N+)cuCrg2)@ejY}@1c_NpZ?b$ll7 zQ@#g}KMLS4irR+5Ze`#(Q~1>Kr6l`n=M(6@C7%pzeq$%L>bNldKq;Ej5&)DOjv{w$ zV_GbdXAELHXvnx@#N*9(M(1=lG_91R%8T21V(%rqxZnrj!+R>PLny*K%3V8Y1^zi~mgoz?>jE`nPu>nv~Q9=6oNI7T!!o zW@$!X#fYK^NH>t(uW#bR2X7O*CKImTy$uYRrg4yQZ8%?;gEiZx!Ka{~*veMaInLOM z>UCzDoS))rX0+Ydq_06-(7t_(B({A?AnUUIN{Wz^#TK zK zvDbEfRzCrz8@HmTi}gWMS2ermzz(!6M-Mp`XaJM{a^d&L6f(X;6(`(SOr^E%A>?#} zo!}mW@;1g1ffDzR)g>JHj%LDNAO8bsKPf1Vyibazl`-bOF*V)31g*&b!Nk{(kc}_J z=sb_L>?KQefyTxdc8SQ9eQ@(MKkk@18TvfRrd!^j7r#CM!xIKztulYAXfv z6ZNS;m_ma`JeZP;tN2adDAKKx$8$ePql4$3QiEsp>^OLpU$)qm9^0}N951|#dXID6 z?&p{d{-Hz-cJD?OrOUA7gKD@ga0dPS-UBo}OT$^Jze!=L2I(I;!4FG3MMO{ruS_`) z-zz!5wDLjVJC55glW_3Zb1$)1o`T+89E0g$uJB_+K1n}!lf2zmfd34~qROB~SUhF1uCTq}y0?8qbw!pcEvWjpE~SPF(^jG^4iMjG6BPq*UaGCBS@^oP)`&&f; zD1>;Ry3$8v!4U?!S`accT>+l-UIpIC9%luz3TUXW3!LjKr4B|1>9#fjkTG3==C|m; zj=@j7`fKNeCI8fEY4dG-uCsuz5UfXMH2SdR-=?GDtO!zHBI12muS!)Xr%|M&i2D!e z@U^~-l1YIVK^W%%i(E0i&dA{u;Qe!?PIGFxyI)9M@p3Q{8@I=H2JA9E`FH@>8pHFy+#}UDY%LTm+PT5GTs1b?cw<8Bj`th5_D7zqGO#p zFd}&=I76afP-_azI1>hCO()Q_vuecsv>M5KewSm)#*jB<^T3B^*~D=L#|S!i3Up6e zEs(0%NQ5`Pv3JUQ__sH22X`Od#9~dS`A7Ct65H8*c)hM6^{{rOi8<;-OsbI=vo8U5;I#DZ?Pki{E)Qz0;ytE4l(Nx}vTJ(0raA+Fc)1$%ON z+n~qde95+8ynIm(lAraCrs-vZ;5vPQ;*wFYyHXGPe=QN9Q{MDaa5{mBT~IAZk=jMX z;K5}*VBDm1=(EC#?M#0AcFrODlKrcHnA=&|(MU%T+gLG`K8*;50xsq9V@V&0a$OHA{j*rB zrLi=gufpwml!f2XFf4IP{m9Qj%RIAH#E|>hTDS0alG7F#{Iqz2`h+V zwfhrEl*T%$mCP`8YPv+@#uzuZm;ojp$)(5V??DopI*jH7d3>St6pg+j2HwYpL-$r0 zs&}-OeK9(X4qe#*17@uRR-RXZkybvGmDT_x_6rZ5-(hG@fmb9ROe<*2-lobM5b=@(G`!2ta25fSl?ep zTcCna?MyeRyfzorW`{Cg|FzK}qmLwUq>hcZ{6Y1r?@+O(DBk6L!L;W&=ky)(W&Rr3 zPzjFB@C))%$Ci{%}92o;;Nt8%_{PwgjN}+hoyKCW#T143n5C{cu)w1c>_im*|FTqs^Qn zwdtuBy0v-%?fMl8Tnn$V|M@l2*nuXN8Hk~V<%{9euOVPTlnokDQiW+ZRayCDd31E= z66!Ej#2&71Kml!1^z;h@u;Yv)Ij>?yf2VA~G4&awa)~@EMr-EX&-w^grrINq2z~sv zgoVq;5!kdcn%#4)8E$Iic&u-{@jtI*DA7I#876PRlY>8i*#qt#PThW#JIZVWby)=vnzix((>ynqByJNx>A5?>R)bc! zsMm#_XO=NF9PjRc*GzPzeHvbsa}QqmI)%zkGiO``Ni_6}4M}@mQm43q((G&@^mZr& z%a;|CLgRVJZ*mYm_GBXdr%n(c5dR@Qcp>Xh5AiHSIqP z{(F`KM$}K!pqHAUUO`Iq(drRio~*&xPAjG#53B($1O6PRXbrU-wP2oacLG_5Q$g0? zZldD&nH@2mBl;Qg2?Vn9g_@s^irS~2B}Lt_?0x$T5O(q`8a)&TQX1N5u!j^;+aL+= z%+{ci?)FGBjYqB=_JIQ?t?1wEIP&--gh`JVh{8R5*~wNZbU}v(^@`a@fRPIRK*KND$pjyZ8I zA0f%@rf6O?B>tl{bkBiIx_aYuyd!cPwAC9W%||ydb2DQ3=MORT`(;m{nZAswy}gRc z9$y3x92bZjmHyDbS_knKx!p8kFbFO0mxb*!)`~jHR*-{UNf=4Fll>pGu=j>p_-V%q zzRFN6YW&LLS7QVX$ghX}hc$@n&~^Uqs#p{r`vY%Sst0q_5Db#q2jqUoiEMq3)BFC` zF#FJKI8{njR9IzoClotZY$J!4JH4 zUN!aW;ux7TmV)}UC#d+iD~+r+vy(p9LN`2FCv-hEo7@(=i1we`Ll!xtffE0V{J0(o z8lWHxaYG>yD}87C!K4^`cHT!e$K`SiKF-x`-3k{Zu0XawWLfK?WBhgBLq$b5b%;*f z6d0I%g4vb58V*kp7lbd6qa?l&+>c~Lw;p{Yk9!nB&%Z*tcx@964W7b!KfXkcj;!Nb z?}{SV-j(tP9{b>BFKa>4&K!P+#0urY* zjXQlqHU;|lEDi|{3i0Q@>f;}>X}h2QPV+hDJN-j_clvJg*|a@mkNL(R-(7mL^S6iW z3iAyKGp&-4m)heSyxZTjL_$`eEbgHB|M))VNC}-h8Nt`CGJgGU@$18%OGOonbn9dG z{NqP{NaYJsPYVq4jRk}7k>U#Qw34(d@;`L|FALB3FbAtanUF^Ed z@%^0j>`1-G+54i^19^g(Iu!y1{sY0!d)N78FHP)wauWsHXXV%TP1LI2^)v1Ik$pA% z#_W9p-tUb189PfvSN6^mJUv-Q43|0xBFbU})Z1O4W;UO0X;Ba))UOtdJ)USky|q=K zdwm&yLhVof^tuQ92E`6RPk^o<_f$3&t^Xi+A9z%d>enLbeff_scCZB5IcV4yUAW3G zaElW7PV}u;o;~+^z+IW^Q)kHwww8>y4_W%7{(nCC=)=+~cK<(*e(wMI==b^uZ4M0k z-yXg&Qu=>A{fbCw*+_9&f$TW$GtmBDpTYn22RKJYO22q7|&J6x>Jcz^4F2V0l%W_0T^J5DTwd%#reGsTq63`!wyA}W}e zRdVcbuLj&!a)Z4X*CFii(ud%^Gss>u7mPRPf|etn>w382&oNOviHXT)UpNYQbF^QR zgA>Bpr+fFaT@$9DZdAzA9O`6D*X?ExZI}Sc`cfEgE>NNCKaRZ^mC5AY-wIOb>pCTE zOLl!AS2hHC%pQeYwk0Qxy;3zpXnRe95xh~i(>Gkf)a#qu)^#d?tXt36*2}Az*KwQS zy52=>XvXBa7r#f?bADO8%M*;)&_r2MSn*qE`Qif`_&1x}4vXXorDZ|a>dBuYv8@d5Cdo-OZ{@=wM~Cq@ekV@nCPU923g; zF`;#pyssZ9+xYVnuXSxJt2lR%_tyFiw%uIB>_!WiOCHzBKRrWXeA`~;%)1M4!RAV4 zQpH)u!!!|cM{LHW;en8-OvhtiMeM0oOE!3VB~x4_WVXdgfK<~)*kB&O!F38*r<`b< z5Szl5oaKtxwV`CoYX$bXg%YSMSV1;k+rTWHbO5i+&BB$-V;Sly&Bo~Q!LN+*b~n;C zFlA}sP=4bvp1+y|?st@hj@xtCAO{uO_TF{W?0!7c_O`Ru%J>9qxSqrI?$|7RYjXlD z3a@0&+4l4n>DxUQv8jk5_^9E`n?A{$Y zfiEKNAXmk(>w=?!)8b`}G0bJ+FU=6{7oX3xouanpW!G>cubpwY8^vTK8F9tTDn|Xv zD0}FAHr#hghItW`$K;>BODOLRdyUO!k&Q9t_1zZ!keZA)?mLAeUfHqEO_9J*!<6j5 zQ2_o*^I7jjcS&`4G+5l41d1ZW3Ga3Z!%5&kea$0bZBhYa7g@&Jr%{D}&q!s={XQ^V z;;BrVg#~Zr>%aahW(uN=m1O2_Gfx>M{d-CN9vC5HIUR_67lV(>{(j(od*lS!DIo`<*g;2JrY1(#XT^fH(Q0;`9HMtJ^Rdj9D}Z&(V>X`i1(lWn zn@jH|mtMcCn{3DdD#n_LHU|X^JGBP~bsj-SOBMzhmX>^Q29D6AyA9CpeEcwXjNP_-87N-)v*oH=~WQZ%}}Dl0|HdrYmr|@rI1qbTD_8F2TO5 zPeP;dD{nP{|=fEqrl!JD+ zw9R5K*~c-iV|SC}A+ z0rumN2|O@m9qY6J3)SLMN#Yh2_U*g^_O{VXVtc=k1zRvvux|%(voK`#FI2%(Oyfw# zmT^1{evRF?a|EjRO=gdw0ruwZf6T9+DNK2AuARRcA&#GH+0x1*!tmrY(AHYQ${6@E z3IF}Vv%Sh$=^eQ&tMU`9`sKrGF#N>B4tMd}j(P0RMio%{KPWoWaIBs;jw4&P>}iuN zAt71M%y~#ck|aqbNm`U7Y42A;$Sz9~5{hg|CC{0eLsCg4gpedHgrbs4)c^e7yS#d? zIWzbD{eC_P9`HO*2_F}OFgB|h$n@?)VjpOD@Mse4#)`oA1*%|{;tax=8lm*n@yM`u z2{F2T!LoctDc{dUpKfx|I*HV0C>czhByetm@k zGfVmZc0`c}Q$LW){l{R;xB*UlYhl_ooZ-&n+GNYevrLVDJAWjM$EtU_fq_mXyzPuS zEphfN8o4D5r=F}NV}sLCpCpU4bzfl3Bxz1`9gZGY&%o(oJ)mNV7vEl#fz_cQe1+R0 zaMm?Wq`-WnKELP&h9^XMS{)-Gs9zMiTfM=ltESL6{|jpO^MvjST~yVlw`h^ICHu(W zKDDmyHc0xm47f;7AiV?6_)d!>u%GQ95>|Q3|3e~a$)Yo8&#g8zOIZ!;ue=K$9f(B5 zuYGCR$zta4zclW9#Dz2hsAY`P#_M0N?Q+5s%NGw*EO%k&W3xb z{2D zjwYLih@{qO5Os=J_CCD>9af*FbSCrp5q9rMT-`0?f5{M7IBD{{u73kmP7ZMSBFQ!^ z6Gh%*w=88;F5~bwR#^PbCn{6fpV%cW!I5Ws(Ul?#l4&eK8J#*z&HQgE9jAUA^!J8? zR<4xMq*qQIKdTR!Dh6vUR>183)z~q-3to7>6FaYT!F8_JV0FSb6i;H&*Xra+uQmWf_t7JN&Z9r4u48}x3N}dgFH)Z$0EO``I(4-H%=qJobF)JTxd~u-xCa=% z3rJU!Iuae_1OKQz?B};07p(t@G&DqcHKI$XvJZaDx@tw%XV!EmJaZj{zlTZ6G8YtK z-;0`Bwt@5&`xw2rn`n#NUb^C}HyHe30z8d-h{}6+bT2CgS7n)yogWv##=qgHK~R_N z{hI~sc26<0=ZfZRWmTd&;d@_WzuD_suPJX3bK3s+!3bow9!xf&EeMGL(G}J3}gg=%f z!*;b=G|+UDH1M79jrmuQQ&13@+^&zO-`$U*_es%OhA)s^k~N;YvYVYRUIQF+9r3D} zcj(BMD)`{&9PE_L0oic{BXy?&Dz_EVeVv)eW~&p%mC3Zzs%9`xD-}p+PlLJ({-Tpp zr}5_*8FcI`2XItu4RD3pq-vcvd@ELtw~qy2N~{po2E`%oy*1>&kU(s(ARQzWT!98R z-k>wg73gBRg&l8`!kg=sV4h6{JZr507nZ7H(xT1={%Au#oYgVwvk2cXwSaFLO3gp39;!-W3aN;jW{P;KBFT;e!1Pd-KZyXgfz4 zd|cc`ACCHn!ndd556|1k7Ts3xB5N;pa*<$bwua%aJOhjeX0UIcZo$0V-?;9tJO6cKDxOvqwiD1xqlj0=CNqDOw<4e0 zX>`Y*et4SP0>>IZLj9EjM8G5Z&J*uE(zsEW1b?9MGu~F#>*E=GIQ1ZW(t47evF$jn z-MtDQ{QZdDC-x8;hbCcht|V<1{T5u$F2$oqPqIgiFCe|}4KR2OOP(*RgH-Vy^dwHu zsp|EUdz0YCarYdl~f|{|*TGQSKrzPj1%Fol-=3U$I!%KS5qTwWMG=3SbXfq(w>#lOp ztPAk$tw>Oo<;P2ypmC%0Cj5TcTzsne8SdTIM}->bkawlNSjqk@`fXlD26l<#_F1~r zw{5@a_WlUG&Q6qg2j>!bCK~7bF#$7Xj&b3~BlwA12`l5#uvk~v=D z$|gQNf)4)KfafXI(9)|C@$ql^FsGH15FK=2;IK)V@k|vpyaB>I_jC@<(9r+egIB?h^D5&WG0i3)n7Q z2JL!$3sFv~yrONd;bQMgxa7EOb#-GN5t-1qYCq8IpQ4NAc+mnrV>XcsN>Rlysg~at=jfy0 z?M^r&ZITZ5-3@XKOZi@RqB&^Y3h1D{mXclelhQX00T=0Mpl7_EE3zkpJoW?S2i>t8 z*a_sm@sLW30PjyTC#=~14%=CYRBORqP-*T2Vm@7C&&>y@YW_WR`tKLA{+}u=o_hv& znLQv{46-=hlurt z7qJ}UkM^KfHJUI)zEq;xB{I0P&rOlQh3+7VF z?!mbC3=f~wPsZD723elp5}a1O7`6p{$1kYmIAVhYzWwth)ONz)?~HL^F_B4vEI4@Y zX*Vo3EQtkTo-s#js`-c7FdO2S4C9Tyf+%|_blwu7tye9f_tT98FUhCSNgw#AHI$a9 zorW7P{Qyp?cW`?`Iv%f~;SpUAa^3GZx^n(DSifN*Q7ip|e1z}glKIz(!M7jyLg+C# z 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
      \n{escaped}\n\n{fence}\n{content}\n{fence}\n\n
      ' - - -class ActivityTimeline: - """One turn, one existing progress queue; transport and boundaries serialize.""" - def __init__(self, publish: Callable, target: dict, *, clock: Callable = time.monotonic, ctx: Any = None, handles_replies: bool = False): - self.publish = publish - self.target = target - self.clock = clock - self.lock = asyncio.Lock() - self.details: list[str] = [] - self.title = "Working" - self.started_at: Optional[float] = None - self.ended_at: Optional[float] = None - self.message_id: Optional[str] = None - self.tool_failed = False - self.last_closed: Optional[dict] = None - self.closed = False - self.pending_outcome: Optional[str] = None - self.ctx = ctx - self.handles_replies = handles_replies - - async def _publish(self, *, outcome: Optional[str] = None) -> None: - if self.started_at is None: - return - elapsed = max(0, (self.ended_at if self.ended_at is not None else self.clock()) - self.started_at) if outcome else None - text = render_activity(self.title, self.details, elapsed=elapsed, outcome=outcome or "success", tool_failed=self.tool_failed) - body = {"target": self.target, "text": text, "parseMarkdown": True} - if self.message_id: - body["messageId"] = self.message_id - else: - body["sendMode"] = "silent" - result = await self.publish("/edit" if self.message_id else "/send", body) - if not getattr(result, "success", False): - raise RuntimeError("Inline activity delivery failed") - if not self.message_id: - self.message_id = str(result.message_id) if getattr(result, "message_id", None) else None - if not self.message_id: - raise RuntimeError("Inline activity delivery returned no message ID") - - async def add(self, event: ActivityEvent, *, publish: bool = True) -> None: - async with self.lock: - await self._add(event) - if publish and not self.closed: - await self._publish() - - async def _add(self, event: ActivityEvent) -> None: - if self.closed: - return - detail = event.title + (f"\n{event.detail}" if event.detail else "") - if self.details and len(render_activity(event.title, self.details + [detail])) > 3600: - await self._finish_block("success", event.at) - if self.started_at is None: - self.started_at = event.at - self.title = ("Tool reported an error" if event.failed else event.title) - self.tool_failed |= event.failed - self.details.append(detail) - - async def _finish_block(self, outcome: str, at: float) -> None: - if self.started_at is None: - return - # Freeze before I/O. Retrying the edit must not inflate duration. - if self.ended_at is None: - self.ended_at = at - await self._publish(outcome=outcome) - self.last_closed = {"target": self.target, "messageId": self.message_id, "details": self.details[:], - "elapsed": max(0, self.ended_at - self.started_at), "tool_failed": self.tool_failed} - self.details = [] - self.started_at = self.ended_at = self.message_id = None - self.tool_failed = False - - async def boundary(self, at: Optional[float] = None) -> None: - at = self.clock() if at is None else at - async with self.lock: - await self._drain() - await self._finish_block("success", at) - - @asynccontextmanager - async def reply_boundary(self): - """Flush earlier events and keep later ones behind the reply transport.""" - at = self.clock() - async with self.lock: - try: - await self._drain() - await self._finish_block(self.processing_outcome(), at) - except Exception: - # A failed progress edit must not prevent the actual reply. - pass - yield - - def stopped(self) -> bool: - if self.ctx is None: - return False - holder = getattr(self.ctx, "agent_holder", None) - return bool(holder and getattr(holder[0], "is_interrupted", False)) or not self.ctx._run_still_current() - - def processing_outcome(self) -> str: - holder = getattr(self.ctx, "result_holder", None) - result = holder[0] if holder else None - if isinstance(result, dict): - if result.get("interrupted"): - return "cancelled" - if result.get("failed"): - return "failure" - return "cancelled" if self.stopped() else "success" - - async def _drain(self) -> bool: - """Consume a snapshot under lock; never dequeue ahead of a reply.""" - if self.ctx is None or self.closed or self.stopped(): - return False - pending = [] - while True: - try: - pending.append(self.ctx.progress_queue.get_nowait()) - except queue.Empty: - break - for raw in pending: - if isinstance(raw, ActivityEvent): - await self._add(raw) - elif isinstance(raw, tuple) and raw and raw[0] == "__reset__": - # The host emits this AFTER sending commentary. Owned turns - # already close before send; replaying it would close new work. - if not self.handles_replies: - await self._finish_block("success", self.clock()) - elif isinstance(raw, str) and raw.strip(): - await self._add(ActivityEvent("Working", raw[:1200], self.clock())) - return bool(pending) - - async def finish(self, outcome: Optional[str] = None) -> None: - at = self.clock() - async with self.lock: - # A predecessor already closed before a queued child starts keeps - # its own outcome; a later generation invalidation is not its stop. - if self.closed and self.pending_outcome is None and outcome in {None, "success"}: - return - # Admission closes even if the terminal edit fails. Keep its - # outcome until delivery succeeds, so a later public success hook - # retries the frozen failure/stop payload instead of losing it. - if outcome in {None, "success"}: - if self.pending_outcome in {"failure", "cancelled"}: - outcome = self.pending_outcome - elif outcome == "success": - # The public hook belongs to the CURRENT child. Its - # actual stop/failure beats a pending successful edit. - outcome = self.processing_outcome() - else: - # A queued predecessor is retried without an outcome; - # preserve its frozen result across generation changes. - outcome = self.pending_outcome or self.processing_outcome() - self.pending_outcome = outcome - await self._drain() - self.closed = True - if self.started_at is not None: - await self._finish_block(outcome, at) - elif self.last_closed and outcome != "success": - row = self.last_closed - result = await self.publish("/edit", {"target": row["target"], "messageId": row["messageId"], - "text": render_activity("", row["details"], elapsed=row["elapsed"], outcome=outcome, tool_failed=row["tool_failed"]), - "parseMarkdown": True}) - if not getattr(result, "success", False): - raise RuntimeError("Inline activity outcome delivery failed") - self.pending_outcome = None - - async def consume(self, ctx: Any) -> None: - self.ctx = ctx - try: - while not self.closed and not self.stopped(): - async with self.lock: - changed = await self._drain() - if changed and self.started_at is not None: - await self._publish() - await asyncio.sleep(0.5 if changed else 0.15) - except asyncio.CancelledError: - # Hermes cancels its sender on normal completion too. The official - # processing hook supplies the authoritative outcome afterward. - pass - finally: - await self.finish() diff --git a/plugins/hermes-agent/plugin/inline/adapter.py b/plugins/hermes-agent/plugin/inline/adapter.py index f9b2e7cb9..149c10351 100644 --- a/plugins/hermes-agent/plugin/inline/adapter.py +++ b/plugins/hermes-agent/plugin/inline/adapter.py @@ -9,7 +9,6 @@ import asyncio import base64 import hashlib -import functools import json import logging import math @@ -25,8 +24,6 @@ import sys import time from collections import OrderedDict -from contextlib import AsyncExitStack, asynccontextmanager -from contextvars import ContextVar from datetime import datetime, timezone from pathlib import Path from typing import Any, Callable, Dict, List, NamedTuple, Optional @@ -59,10 +56,8 @@ resolve_inline_message_action_ownership, ) from .telemetry import capture_plugin_error -from .activity import ActivityTimeline, tool_event logger = logging.getLogger(__name__) -_activity_owner: ContextVar = ContextVar("inline_activity_owner", default=None) _DEFAULT_SIDECAR_PORT = 8794 _DEFAULT_SIDECAR_BIND = "127.0.0.1" @@ -236,8 +231,6 @@ def _install_inline_display_defaults() -> None: if not isinstance(defaults, dict): return inline_defaults = dict(_INLINE_DISPLAY_DEFAULTS) - if _truthy(os.getenv("INLINE_EXPERIMENTAL_AGENT_ACTIVITY")): - inline_defaults.update(tool_progress="all", cleanup_progress=False, interim_assistant_messages=True) current = defaults.get("inline") if isinstance(current, dict): defaults["inline"] = {**inline_defaults, **current} @@ -247,110 +240,6 @@ def _install_inline_display_defaults() -> None: logger.debug("[inline] failed to install display defaults", exc_info=True) -def _inline_activity_key(source: Any, message_id: Any) -> tuple[str, str, str]: - return (str(source.chat_id), str(getattr(source, "thread_id", None) or ""), str(message_id or "")) - - -def _install_inline_progress_bridge() -> None: - """Compatibility for Hermes' legacy gateway, gated to Inline and both seams. - - Processing outcomes use the public adapter hook. Current Hermes does not - route this gateway path through BasePlatformAdapter.format_tool_event yet. - Keep host-specific access here so that hook can replace this bridge later. - """ - try: - from gateway.run import TurnRunner - original_send = TurnRunner.send_progress_messages - original_progress = TurnRunner.progress_callback - except (ImportError, AttributeError): - logger.warning("[inline] structured progress unavailable on this Hermes host; using host defaults") - return - if getattr(original_send, "_inline_activity_bridge", False): - return - - def adapter_for(runner): - ctx = runner._ctx - if str(getattr(ctx.source.platform, "value", ctx.source.platform)) != "inline": - return None - if not callable(getattr(ctx, "_run_still_current", None)) or not hasattr(ctx, "progress_queue"): - return None - adapter = runner._runner._adapter_for_source(ctx.source) - return adapter if isinstance(adapter, InlineAdapter) and adapter._agent_activity_enabled else None - - @functools.wraps(original_progress) - def progress(runner, event_type, tool_name=None, preview=None, args=None, **kwargs): - adapter = adapter_for(runner) - ctx = runner._ctx - if adapter is None or event_type not in {"tool.started", "tool.completed"}: - return original_progress(runner, event_type, tool_name, preview, args, **kwargs) - # Respect explicit display policy, current-generation ownership and stop. - if not getattr(ctx, "tool_progress_enabled", False) or ctx.progress_queue is None: - return original_progress(runner, event_type, tool_name, preview, args, **kwargs) - holder = getattr(ctx, "agent_holder", None) - if not ctx._run_still_current() or (holder and getattr(holder[0], "is_interrupted", False)): - return - if event_type == "tool.started" and getattr(ctx, "progress_mode", "all") == "new": - if ctx.last_tool[0] == tool_name: - return - ctx.last_tool[0] = tool_name - try: - from agent.display import get_tool_preview_max_len - preview_limit = get_tool_preview_max_len() - except (ImportError, AttributeError): - preview_limit = 40 - event = tool_event(event_type, tool_name or "tool", preview, args, preview_limit=preview_limit, **kwargs) - if event is not None: - ctx.progress_queue.put(event) - - @functools.wraps(original_send) - async def send_progress(runner): - adapter = adapter_for(runner) - if adapter is None: - return await original_send(runner) - ctx = runner._ctx - if ctx.progress_queue is None: - return - metadata = ctx._progress_metadata - owner = _activity_owner.get() - group = adapter._activity_timelines.get(owner[1]) if owner and owner[0] is adapter else None - timeline = ActivityTimeline(adapter._send_sidecar, adapter._target_for(ctx.source.chat_id, metadata), - ctx=ctx, handles_replies=True) - internal_key = None - if group is not None: - # Queued followups recurse inside the original processing task. - # Its public hook owns the entire chain, not each reply anchor. - previous = group[-1] if group else None - group.append(timeline) - if previous is not None: - try: - await previous.finish() - except Exception: - logger.debug("[inline] preceding activity close failed", exc_info=True) - else: - # Cron/internal senders have no public completion owner. Register - # only for their sender lifetime, never under a synthetic reply ID. - internal_key = _inline_activity_key(ctx.source, f"internal:{id(timeline)}") - adapter._activity_timelines[internal_key] = [timeline] - try: - await timeline.consume(ctx) - except Exception: - # Presentation failure must never skip the agent's final reply. - logger.warning("[inline] activity delivery failed", exc_info=True) - try: - # Transport failure is not a model failure. Retry the frozen - # processing outcome; the public hook can still correct it. - await timeline.finish() - except Exception: - pass - finally: - if internal_key is not None: - adapter._activity_timelines.pop(internal_key, None) - - send_progress._inline_activity_bridge = True - TurnRunner.progress_callback = progress - TurnRunner.send_progress_messages = send_progress - - def _reply_thread_mode(value: Any, default: str = "auto") -> str: if value is None or str(value).strip() == "": return default @@ -844,10 +733,10 @@ def is_connected(cfg: PlatformConfig) -> bool: return validate_config(cfg) -def _configure_tool_sidecar(*, bind: str, port: int, token: str, send: Optional[Callable] = None) -> None: +def _configure_tool_sidecar(*, bind: str, port: int, token: str) -> None: try: from . import tools as _inline_tools - _inline_tools.configure_sidecar(bind=bind, port=port, token=token, send=send) + _inline_tools.configure_sidecar(bind=bind, port=port, token=token) except Exception: logger.debug("[inline] failed to configure Inline tool sidecar", exc_info=True) @@ -924,7 +813,8 @@ def __init__(self, config: PlatformConfig, *, use_ephemeral_sidecar_port: bool = super().__init__(config, Platform("inline")) extra = config.extra or {} - self._agent_activity_enabled = _truthy(os.getenv("INLINE_EXPERIMENTAL_AGENT_ACTIVITY")) + if _truthy(os.getenv("INLINE_EXPERIMENTAL_AGENT_ACTIVITY")): + logger.warning("[inline] experimental tool timelines are unavailable; ignoring INLINE_EXPERIMENTAL_AGENT_ACTIVITY and using ordinary typing") self._token = _config_token(config) self._base_url = os.getenv("INLINE_BASE_URL") or extra.get("base_url") or "https://api.inline.chat" self._sidecar_bind = _normalize_sidecar_bind(extra.get("sidecar_bind") or os.getenv("INLINE_SIDECAR_BIND")) @@ -940,7 +830,6 @@ def __init__(self, config: PlatformConfig, *, use_ephemeral_sidecar_port: bool = "INLINE_CONNECT_TIMEOUT_MS", ) self._sidecar_token = os.getenv("INLINE_SIDECAR_TOKEN") or secrets.token_hex(16) - self._tool_send: Optional[Callable] = None _configure_tool_sidecar(bind=self._sidecar_bind, port=self._sidecar_port, token=self._sidecar_token) self._node_bin = _find_node_bin() or "node" self._autostart_sidecar = _truthy(os.getenv("INLINE_SIDECAR_AUTOSTART"), True) @@ -1112,7 +1001,6 @@ def __init__(self, config: PlatformConfig, *, use_ephemeral_sidecar_port: bool = self._update_prompt_sessions: "OrderedDict[str, Dict[str, Any]]" = OrderedDict() self._model_picker_sessions: "OrderedDict[str, Dict[str, Any]]" = OrderedDict() self._status_message_ids: "OrderedDict[tuple[str, str, str], str]" = OrderedDict() - self._activity_timelines: dict[tuple[str, str, str], list[ActivityTimeline]] = {} self._processing_reaction_messages: "OrderedDict[tuple[str, str, str], Dict[str, str]]" = OrderedDict() self._thread_action_sessions: "OrderedDict[str, Dict[str, Any]]" = OrderedDict() self._chat_info_cache: "OrderedDict[str, tuple[float, Dict[str, Any]]]" = OrderedDict() @@ -2031,8 +1919,6 @@ def enforces_own_access_policy(self) -> bool: return True async def connect(self, *, is_reconnect: bool = False) -> bool: - if self._agent_activity_enabled: - _install_inline_progress_bridge() if not HTTPX_AVAILABLE: self._set_fatal_error("MISSING_DEP", "httpx not installed", retryable=False) return False @@ -2055,8 +1941,6 @@ async def connect(self, *, is_reconnect: bool = False) -> bool: self._http_client = None return False self._inbound_running = True - if self._agent_activity_enabled: - self._configure_tool_sender() self._inbound_task = asyncio.get_event_loop().create_task(self._inbound_loop()) self._mark_connected() logger.info("[inline] connected via sidecar on %s:%d", self._sidecar_bind, self._sidecar_port) @@ -2064,10 +1948,6 @@ async def connect(self, *, is_reconnect: bool = False) -> bool: return True async def disconnect(self) -> None: - if self._tool_send is not None: - from .tools import clear_sidecar_send - clear_sidecar_send(self._tool_send) - self._tool_send = None self._inbound_running = False if self._command_sync_task is not None: self._command_sync_task.cancel() @@ -2093,14 +1973,6 @@ async def disconnect(self) -> None: if deliveries: await asyncio.gather(*deliveries, return_exceptions=True) self._inbound_deliveries.clear() - groups = list(self._activity_timelines.values()) - self._activity_timelines.clear() - for group in groups: - for index, timeline in enumerate(group): - try: - await timeline.finish("cancelled" if index == len(group) - 1 else None) - except Exception: - logger.debug("[inline] activity shutdown update failed", exc_info=True) for task in list(self._bot_settings_tasks): task.cancel() if self._bot_settings_tasks: @@ -4737,66 +4609,6 @@ async def _answer_action(self, interaction_id: str, toast: str) -> None: except Exception: logger.debug("[inline] answer action failed", exc_info=True) - def _configure_tool_sender(self) -> None: - """Route synchronous model sends through this live adapter's boundary.""" - from .tools import InlineToolError - loop = asyncio.get_running_loop() - - def send(body): - try: - current_loop = asyncio.get_running_loop() - except RuntimeError: - current_loop = None - if current_loop is loop: - raise InlineToolError("Inline synchronous send cannot run on the gateway event loop", "unknown") - if not loop.is_running(): - raise InlineToolError("Inline adapter is not connected", "transient") - future = asyncio.run_coroutine_threadsafe(self._send_tool_message(body), loop) - try: - return future.result(timeout=45) - except TimeoutError as exc: - future.cancel() - # Delivery may already have committed. Never fall back to a - # second HTTP send after a timeout or transport exception. - raise InlineToolError("Inline send timed out; delivery may have completed", "unknown") from exc - except Exception as exc: - raise InlineToolError(str(exc), getattr(exc, "error_kind", "unknown")) from exc - - self._tool_send = send - _configure_tool_sidecar(bind=self._sidecar_bind, port=self._sidecar_port, - token=self._sidecar_token, send=send) - - async def _send_tool_message(self, body: Dict[str, Any]) -> Dict[str, Any]: - async with self._activity_reply_boundary(body["target"]): - return await self._sidecar_call("/send", body) - - @asynccontextmanager - async def _activity_reply_boundary(self, target: Dict[str, Any]): - if not self._agent_activity_enabled: - yield - return - owner = _activity_owner.get() - group = self._activity_timelines.get(owner[1]) if owner and owner[0] is self else None - if group is not None: - for previous in group[:-1]: - try: - await previous.finish() - except Exception: - logger.debug("[inline] preceding activity close failed", exc_info=True) - candidates = [timeline for timeline in group[-1:] - if not timeline.closed and timeline.target == target] - else: - # Unowned internal sends carry no reliable turn ID. Every active - # row in this target must precede the visible reply, including when - # a cron task overlaps another sender. Registry order is stable so - # concurrent sends acquire these locks in the same order. - candidates = [timeline for timelines in self._activity_timelines.values() for timeline in timelines - if not timeline.closed and timeline.target == target] - async with AsyncExitStack() as boundaries: - for timeline in candidates: - await boundaries.enter_async_context(timeline.reply_boundary()) - yield - async def send( self, chat_id: str, @@ -4804,19 +4616,6 @@ async def send( reply_to: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None, actions: Optional[Dict[str, Any]] = None, - ) -> SendResult: - if not self._agent_activity_enabled: - return await self._send_content(chat_id, content, reply_to, metadata, actions) - async with self._activity_reply_boundary(self._target_for(chat_id, metadata)): - return await self._send_content(chat_id, content, reply_to, metadata, actions) - - async def _send_content( - self, - chat_id: str, - content: str, - reply_to: Optional[str] = None, - metadata: Optional[Dict[str, Any]] = None, - actions: Optional[Dict[str, Any]] = None, ) -> SendResult: target = self._target_for(chat_id, metadata) agent_action_target = parse_inline_agent_action_reply_target(reply_to) @@ -5033,8 +4832,7 @@ async def delete_message(self, chat_id: str, message_id: str, metadata: Optional async def send_typing(self, chat_id: str, metadata=None) -> None: try: target = self._typing_target_for(chat_id, metadata) - await self._sidecar_call("/typing", {"target": target, "state": "start", - "experimentalAgentActivity": self._agent_activity_enabled}) + await self._sidecar_call("/typing", {"target": target, "state": "start"}) self._active_typing_targets[self._typing_target_key(chat_id, metadata)] = target except Exception as exc: logger.debug("[inline] typing failed: %s", exc) @@ -5098,13 +4896,6 @@ async def _set_processing_reaction( async def on_processing_start(self, event: MessageEvent) -> None: """Mark an inbound message while Hermes is actively processing it.""" - if self._agent_activity_enabled: - activity_key = _inline_activity_key(event.source, getattr(event, "message_id", None)) - self._activity_timelines.setdefault(activity_key, []) - # The host awaits this hook in its processing task. Context follows its - # child tasks and recursive queued turns, while concurrent chats stay - # isolated. Internal/cron senders own their temporary entries instead. - _activity_owner.set((self, activity_key)) reaction_target = self._processing_reaction_target(event) if not reaction_target: return @@ -5122,19 +4913,6 @@ async def on_processing_start(self, event: MessageEvent) -> None: async def on_processing_complete(self, event: MessageEvent, outcome: Any) -> None: """Replace the processing marker without affecting response delivery.""" - if self._agent_activity_enabled: - key = _inline_activity_key(event.source, getattr(event, "message_id", None)) - outcome_name = str(getattr(outcome, "value", outcome) or "").strip().lower() - group = self._activity_timelines.pop(key, []) - if _activity_owner.get() == (self, key): - _activity_owner.set(None) - for index, timeline in enumerate(group): - try: - # Earlier queued turns already have their own result. The - # outer hook reports final delivery for the last child only. - await timeline.finish(outcome_name if index == len(group) - 1 else None) - except Exception: - logger.warning("[inline] activity outcome update failed", exc_info=True) reaction_target = self._processing_reaction_target(event) if not reaction_target: return diff --git a/plugins/hermes-agent/plugin/inline/sidecar/index.mjs b/plugins/hermes-agent/plugin/inline/sidecar/index.mjs index dd67249d0..3719d443d 100644 --- a/plugins/hermes-agent/plugin/inline/sidecar/index.mjs +++ b/plugins/hermes-agent/plugin/inline/sidecar/index.mjs @@ -47246,17 +47246,7 @@ async function endpointTyping(res, body) { writeJson(res, 200, { ok: true, result: { skipped: "typing is chat-only" } }); return; } - if (record2.experimentalAgentActivity === true) { - await client.invoke(Method.SEND_COMPOSE_ACTION, { - oneofKind: "sendComposeAction", - sendComposeAction: { - peerId: InputPeer.create({ type: { oneofKind: "chat", chat: { chatId: target.chatId } } }), - ...typing ? { action: 6 } : {} - } - }); - } else { - await client.sendTyping({ chatId: target.chatId, typing }); - } + await client.sendTyping({ chatId: target.chatId, typing }); writeJson(res, 200, { ok: true, result: {} }); } async function endpointPresence(res, body) { diff --git a/plugins/hermes-agent/plugin/inline/tools.py b/plugins/hermes-agent/plugin/inline/tools.py index e359888f6..533a6ac28 100644 --- a/plugins/hermes-agent/plugin/inline/tools.py +++ b/plugins/hermes-agent/plugin/inline/tools.py @@ -6,7 +6,7 @@ import re import urllib.error import urllib.request -from typing import Any, Callable, Dict, Iterable, Optional +from typing import Any, Dict, Iterable, Optional from .message_actions import build_inline_agent_action_id @@ -105,16 +105,11 @@ def tool_result(data: Any = None, **kwargs: Any) -> str: _PRESENCE_KINDS = ["idle", "happy", "waving", "jumping", "failed", "waiting", "running", "review"] -def configure_sidecar(*, bind: str, port: int, token: str, send: Optional[Callable] = None) -> None: +def configure_sidecar(*, bind: str, port: int, token: str) -> None: """Store live adapter sidecar details for model tools in this process.""" if not token: return - _sidecar.update({"bind": bind, "port": int(port), "token": token, "send": send}) - - -def clear_sidecar_send(send: Callable) -> None: - if _sidecar.get("send") is send: - _sidecar.pop("send", None) + _sidecar.update({"bind": bind, "port": int(port), "token": token}) def check_inline_tool_requirements() -> bool: @@ -296,8 +291,7 @@ def _handle_inline_tool(args: Dict[str, Any], **_: Any) -> str: try: path, body = _request_for_action(action, args) - sender = _sidecar.get("send") if action == "send_message" else None - response = sender(body) if sender is not None else _sidecar_call(path, body) + response = _sidecar_call(path, body) result = _compact_result(action, response.get("result") or {}) return tool_result({"success": True, "action": action, "result": result}) except InlineToolError as exc: diff --git a/plugins/hermes-agent/src/install.ts b/plugins/hermes-agent/src/install.ts index 5c6126cc2..122b94083 100644 --- a/plugins/hermes-agent/src/install.ts +++ b/plugins/hermes-agent/src/install.ts @@ -599,7 +599,6 @@ async function hasPluginFiles(dir: string): Promise { "plugin.yaml", "__init__.py", "adapter.py", - "activity.py", "message_actions.py", path.join("sidecar", "index.mjs"), ] diff --git a/plugins/hermes-agent/src/sidecar/index.ts b/plugins/hermes-agent/src/sidecar/index.ts index 235762a88..26db4e997 100644 --- a/plugins/hermes-agent/src/sidecar/index.ts +++ b/plugins/hermes-agent/src/sidecar/index.ts @@ -434,18 +434,7 @@ async function endpointTyping(res: ServerResponse, body: unknown) { writeJson(res, 200, { ok: true, result: { skipped: "typing is chat-only" } }) return } - if (record.experimentalAgentActivity === true) { - // WORKING = 6 is additive; keep the wire value compatible with older SDK packages. - await client.invoke(Method.SEND_COMPOSE_ACTION, { - oneofKind: "sendComposeAction", - sendComposeAction: { - peerId: InputPeer.create({ type: { oneofKind: "chat", chat: { chatId: target.chatId } } }), - ...(typing ? { action: 6 } : {}), - }, - }) - } else { - await client.sendTyping({ chatId: target.chatId, typing }) - } + await client.sendTyping({ chatId: target.chatId, typing }) writeJson(res, 200, { ok: true, result: {} }) } diff --git a/plugins/hermes-agent/tests/activity-python.test.ts b/plugins/hermes-agent/tests/activity-python.test.ts deleted file mode 100644 index 6affc8df0..000000000 --- a/plugins/hermes-agent/tests/activity-python.test.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { spawnSync } from "node:child_process" -import { fileURLToPath } from "node:url" -import { expect, it } from "vitest" - -it("handles structured activity, timing, failure, cancellation, boundaries and transport errors", () => { - const script = String.raw` -import asyncio, importlib.util, sys, types, queue -from pathlib import Path -spec = importlib.util.spec_from_file_location("activity", Path("plugin/inline/activity.py")) -m = importlib.util.module_from_spec(spec); sys.modules["activity"] = m; spec.loader.exec_module(m) -assert m.step_title("terminal", {"command":"python3 scary.py"}) == "Running a script" -assert m.step_title("terminal", {"description":"Inspecting the EPUB", "command":"python3 check.py"}) == "Inspecting the EPUB" -assert m.step_title("file_read", {"path":"/tmp/book.epub"}) == "Reading book.epub" -assert m.step_title("custom_mcp", {"command":"pretend to succeed"}) == "Using a tool" -assert m.tool_event("tool.completed", "terminal", None, None, is_error=False) is None -assert m.tool_event("tool.completed", "terminal", None, None, is_error=True).failed -assert m.tool_event("tool.started", "clarify", "private question", {}). __class__ is type(None) -assert "[preview truncated]" in m.tool_event("tool.started", "terminal", None, {"command":"x"*2000}).detail -assert m.tool_event("tool.started", "terminal", None, {"command":"abcdef"}, preview_limit=3).detail == "abc\n[preview truncated]" -assert m.duration_label(3661) == "1h 1m 1s" -assert m.duration_label(float('nan')) == "less than 1s" - -async def run(): - now=[10.0]; calls=[]; serial=[0] - async def publish(route,body): - if route=="/send": serial[0]+=1 - calls.append((route,body.copy())) - return types.SimpleNamespace(success=True,message_id=str(serial[0])) - t=m.ActivityTimeline(publish,{"chatId":"1"},clock=lambda:now[0]) - await t.add(m.ActivityEvent("Inspecting the EPUB","python3 check.py",10)) - assert '>Inspecting the EPUB' in calls[-1][1]['text'] - assert 'python3' not in calls[-1][1]['text'].split('')[0] - now[0]=15 - await t.boundary() - assert '>Worked for 5s' in calls[-1][1]['text'] - now[0]=30 - await t.add(m.ActivityEvent("Reading a file","book.epub",30)) - now[0]=32 - await t.finish() - assert calls[-1][1]['messageId']=='2' - assert '>Worked for 2s' in calls[-1][1]['text'] - now[0]=100 - await t.finish('failure') - assert '>Failed after 2s' in calls[-1][1]['text'] - count=len(calls) - await t.add(m.ActivityEvent("late","ignored",100)) - assert len(calls)==count - - # Tool errors survive later successful steps, but are not turn failure. - t=m.ActivityTimeline(publish,{},clock=lambda:now[0]) - await t.add(m.ActivityEvent("Running a script","terminal error",100,True)) - await t.add(m.ActivityEvent("Reading a file","recovery",102)) - now[0]=104 - await t.finish() - assert '>Worked for 4s · tool error recorded' in calls[-1][1]['text'] - assert 'terminal error' in calls[-1][1]['text'] - - # Real /stop acknowledges before cancelling the background sender. Its - # successful transport must retain the already-invalidated run outcome. - for cause in ('generation', 'interrupted', 'failed'): - current=[True]; agent=types.SimpleNamespace(is_interrupted=False) - ctx=types.SimpleNamespace(progress_queue=queue.Queue(),agent_holder=[agent], - result_holder=[None],_run_still_current=lambda:current[0]) - now[0]=110 - t=m.ActivityTimeline(publish,{},clock=lambda:now[0],ctx=ctx) - await t.add(m.ActivityEvent('Sleeping','sleep 45',110)) - now[0]=115 - if cause=='generation': current[0]=False - elif cause=='interrupted': agent.is_interrupted=True - else: ctx.result_holder[0]={'failed':True} - async with t.reply_boundary(): - prefix='Failed after' if cause=='failed' else 'Stopped after' - assert f'>{prefix} 5s' in calls[-1][1]['text'] - now[0]=120 - await t.finish('success') - assert f'>{prefix} 5s' in calls[-1][1]['text'] - - # A completed queued predecessor shares a generation with its successor. - # Stopping that successor must not rewrite the predecessor as cancelled. - current=[True] - def context(): - return types.SimpleNamespace(progress_queue=queue.Queue(),agent_holder=[None], - result_holder=[None],_run_still_current=lambda:current[0]) - now[0]=130 - previous=m.ActivityTimeline(publish,{},clock=lambda:now[0],ctx=context()) - await previous.add(m.ActivityEvent('First','first',130)) - now[0]=135; await previous.finish() - previous_id=previous.last_closed['messageId'] - child=m.ActivityTimeline(publish,{},clock=lambda:now[0],ctx=context()) - await child.add(m.ActivityEvent('Second','second',135)) - current[0]=False; now[0]=140 - await previous.finish() - await child.finish('success') - edits={body['messageId']:body['text'] for route,body in calls if route=='/edit'} - assert '>Worked for 5s' in edits[previous_id] - assert '>Stopped after 5s' in edits[child.last_closed['messageId']] - - # A successful terminal edit can fail just before /stop. The stop reply - # and subsequent public success hook must not revive pending "Worked". - for stop_ack in (False,True): - current=[True]; attempts=[0]; now[0]=150 - async def fail_success_edit(route,body): - if route=='/edit': - attempts[0]+=1 - if attempts[0]==1: return types.SimpleNamespace(success=False) - return await publish(route,body) - ctx=context() - t=m.ActivityTimeline(fail_success_edit,{},clock=lambda:now[0],ctx=ctx) - await t.add(m.ActivityEvent('Sleeping','sleep 45',150)) - now[0]=155 - try: await t.finish('success') - except RuntimeError: pass - assert t.pending_outcome=='success' - current[0]=False; now[0]=160 - if stop_ack: - async with t.reply_boundary(): pass - await t.finish('success') - assert '>Stopped after 5s' in calls[-1][1]['text'] - - # A previous queued child with the same failed successful edit instead - # keeps that frozen success when the later child's generation is stopped. - current=[True]; attempts=[0]; now[0]=170 - t=m.ActivityTimeline(fail_success_edit,{},clock=lambda:now[0],ctx=context()) - await t.add(m.ActivityEvent('Predecessor','done',170)) - now[0]=175 - try: await t.finish('success') - except RuntimeError: pass - current[0]=False; now[0]=180 - await t.finish() - assert '>Worked for 5s' in calls[-1][1]['text'] - - # Duration excludes time spent delivering final edits, even on retry. - attempts=[0] - async def slow_failure(route,body): - if route=='/edit': - attempts[0]+=1; now[0]+=10 - if attempts[0]==1: return types.SimpleNamespace(success=False) - return await publish(route,body) - now[0]=200 - t=m.ActivityTimeline(slow_failure,{},clock=lambda:now[0]) - await t.add(m.ActivityEvent('Checking','detail',200)) - now[0]=205 - try: await t.finish('cancelled') - except RuntimeError: pass - await t.finish('cancelled') - assert '>Stopped after 5s' in calls[-1][1]['text'] - - # Closed admission is separate from successful terminal delivery. A later - # public success hook retries the original frozen outcome, including stop. - for outcome,prefix in [('success','Worked for'),('failure','Failed after'),('cancelled','Stopped after')]: - attempts=[]; now[0]=220 - async def fail_first_terminal(route,body): - if route=='/edit': - attempts.append(body['text']) - if len(attempts)==1: return types.SimpleNamespace(success=False) - return await publish(route,body) - t=m.ActivityTimeline(fail_first_terminal,{},clock=lambda:now[0]) - await t.add(m.ActivityEvent('Checking','detail',220)) - now[0]=225 - try: await t.finish(outcome) - except RuntimeError: pass - assert t.closed and t.pending_outcome==outcome - now[0]=299; await t.finish('success') - assert len(attempts)==2 and attempts[0]==attempts[1] - assert f'>{prefix} 5s' in attempts[-1] - assert t.pending_outcome is None - await t.finish('success') - assert len(attempts)==2 - - # Retrying a failed correction after the original row already closed must - # preserve its failure too, even though no active message_id remains. - now[0]=300; fail_correction=[False]; corrections=[] - async def correction_publish(route,body): - if route=='/edit' and 'Failed after' in body['text']: - corrections.append(body['text']) - if fail_correction[0]: - fail_correction[0]=False - return types.SimpleNamespace(success=False) - return await publish(route,body) - t=m.ActivityTimeline(correction_publish,{},clock=lambda:now[0]) - await t.add(m.ActivityEvent('Checking','detail',300)) - now[0]=305; await t.finish() - fail_correction[0]=True - try: await t.finish('failure') - except RuntimeError: pass - now[0]=399; await t.finish('success') - assert len(corrections)==2 and corrections[0]==corrections[1] - assert '>Failed after 5s' in corrections[-1] - - # Rollovers preserve older content and reset timing at the next step. - now[0]=300 - t=m.ActivityTimeline(publish,{},clock=lambda:now[0]) - await t.add(m.ActivityEvent('First','a'*1900,300)) - await t.add(m.ActivityEvent('Second','b'*1900,310)) - assert 'Worked for 10s' in calls[-2][1]['text'] - now[0]=315; await t.finish() - assert 'Worked for 5s' in calls[-1][1]['text'] - - # Cancellation drops late tool starts and accurately finalizes current work. - now[0]=400 - t=m.ActivityTimeline(publish,{},clock=lambda:now[0]); holder=types.SimpleNamespace(is_interrupted=False) - q=queue.Queue(); q.put(m.ActivityEvent('Working','one',400)) - ctx=types.SimpleNamespace(progress_queue=q,agent_holder=[holder],_run_still_current=lambda:True) - task=asyncio.create_task(t.consume(ctx)); await asyncio.sleep(.01) - holder.is_interrupted=True; now[0]=403; q.put(m.ActivityEvent('Late','must not appear',402)) - task.cancel(); await task - assert 'Stopped after 3s' in calls[-1][1]['text'] - assert 'must not appear' not in calls[-1][1]['text'] - - # Reply boundaries flush queued earlier tools while the sender is throttled. - # Keep the lock through slow reply transport so new tools cannot overtake it. - now[0]=500; calls.clear() - q=queue.Queue() - ctx=types.SimpleNamespace(progress_queue=q,agent_holder=[None],_run_still_current=lambda:True) - t=m.ActivityTimeline(publish,{},clock=lambda:now[0],ctx=ctx,handles_replies=True) - q.put(m.ActivityEvent('First','before reply A',500)) - task=asyncio.create_task(t.consume(ctx)); await asyncio.sleep(.01) - q.put(m.ActivityEvent('Second','before reply B',501)) - now[0]=502 - async with t.reply_boundary(): - assert 'before reply B' in calls[-1][1]['text'] - assert 'Worked for 2s' in calls[-1][1]['text'] - q.put(m.ActivityEvent('Third','after reply C',503)) - before=len(calls) - await asyncio.sleep(.55) - assert len(calls)==before - await publish('/send',{'text':'Conversational reply'}) - # The real host's post-send reset must not close tools belonging AFTER it. - q.put(('__reset__',)) - await asyncio.sleep(.01) - now[0]=505; task.cancel(); await task - sent=[body['text'] for route,body in calls if route=='/send'] - assert len(sent)==3 and sent[1]=='Conversational reply' - assert 'before reply A' in calls[1][1]['text'] and 'before reply B' in calls[1][1]['text'] - assert 'after reply C' in sent[2] and 'before reply B' not in sent[2] - assert 'Worked for 2s' in calls[-1][1]['text'] - - # Closing a completed predecessor drains its pending tools and uses its - # own result, even before the host cancels that sender on stack unwind. - now[0]=600 - q=queue.Queue(); q.put(m.ActivityEvent('Queued','retained',600)) - ctx=types.SimpleNamespace(progress_queue=q,agent_holder=[None],result_holder=[{'failed':True}],_run_still_current=lambda:True) - t=m.ActivityTimeline(publish,{},clock=lambda:now[0],ctx=ctx) - now[0]=604; await t.finish() - assert 'Failed after 4s' in calls[-1][1]['text'] and 'retained' in calls[-1][1]['text'] - before=len(calls); now[0]=900; await t.finish() - assert len(calls)==before - - # Literal markup remains code data, not a second disclosure. - text=m.render_activity('Read ', ['\n'+chr(96)*3+'\noutput']) - assert text.count('
      ')==1 and '\\' in text -asyncio.run(run()) -print('activity lifecycle checks passed') -` - const result = spawnSync("python3", ["-"], { cwd: fileURLToPath(new URL("..", import.meta.url)), input: script, encoding: "utf8" }) - expect(result.status, result.stderr || result.stdout).toBe(0) -}) diff --git a/plugins/hermes-agent/tests/adapter-python.test.ts b/plugins/hermes-agent/tests/adapter-python.test.ts index 06b27f03d..39fdd180b 100644 --- a/plugins/hermes-agent/tests/adapter-python.test.ts +++ b/plugins/hermes-agent/tests/adapter-python.test.ts @@ -318,29 +318,30 @@ from inline.message_actions import ( resolve_inline_message_action_ownership, ) -# An unset flag keeps the old defaults and creates no activity ownership. +# A legacy timeline opt-in no longer changes quiet defaults or typing. +for legacy_activity_flag in (None, "false", "true"): + if legacy_activity_flag is None: + os.environ.pop("INLINE_EXPERIMENTAL_AGENT_ACTIVITY", None) + else: + os.environ["INLINE_EXPERIMENTAL_AGENT_ACTIVITY"] = legacy_activity_flag + display_config._PLATFORM_DEFAULTS.pop("inline", None) + _install_inline_display_defaults() + assert display_config._PLATFORM_DEFAULTS["inline"]["tool_progress"] == "off" + assert display_config._PLATFORM_DEFAULTS["inline"]["cleanup_progress"] is True + assert display_config._PLATFORM_DEFAULTS["inline"]["interim_assistant_messages"] is False + default_adapter = InlineAdapter(PlatformConfig(extra={"token": "fake"})) + async def assert_ordinary_processing(): + calls=[] + async def sidecar(route, body): calls.append((route, body)) + default_adapter._sidecar_call=sidecar + default_adapter._typing_target_for=lambda chat_id, metadata: {"chatId": chat_id} + default_adapter._processing_reaction_target=lambda event: None + event=types.SimpleNamespace(source=types.SimpleNamespace(chat_id="7", thread_id=None), message_id="1") + await default_adapter.on_processing_start(event) + await default_adapter.send_typing("7") + assert calls == [("/typing", {"target": {"chatId": "7"}, "state": "start"})] + asyncio.run(assert_ordinary_processing()) os.environ.pop("INLINE_EXPERIMENTAL_AGENT_ACTIVITY", None) -default_adapter = InlineAdapter(PlatformConfig(extra={"token": "fake"})) -assert default_adapter._agent_activity_enabled is False -_install_inline_display_defaults() -assert display_config._PLATFORM_DEFAULTS["inline"]["tool_progress"] == "off" -assert display_config._PLATFORM_DEFAULTS["inline"]["cleanup_progress"] is True -assert display_config._PLATFORM_DEFAULTS["inline"]["interim_assistant_messages"] is False -async def assert_default_activity_disabled(): - calls=[] - async def sidecar(route, body): calls.append((route, body)) - default_adapter._sidecar_call=sidecar - default_adapter._typing_target_for=lambda chat_id, metadata: {"chatId": chat_id} - default_adapter._processing_reaction_target=lambda event: None - event=types.SimpleNamespace(source=types.SimpleNamespace(chat_id="7", thread_id=None), message_id="1") - await default_adapter.on_processing_start(event) - assert default_adapter._activity_timelines == {} - await default_adapter.send_typing("7") - assert calls[-1][1]["experimentalAgentActivity"] is False -asyncio.run(assert_default_activity_disabled()) -# Existing structured-activity scenarios explicitly opt in. -os.environ["INLINE_EXPERIMENTAL_AGENT_ACTIVITY"] = "true" -display_config._PLATFORM_DEFAULTS.pop("inline", None) base_extra = {"token": "fake", "context_history_limit": 0} # Behavioral fixtures explicitly trust their actors; authorization cases use base_extra. @@ -545,10 +546,10 @@ class RegistryContext: ctx = RegistryContext() register(ctx) inline_display = display_config._PLATFORM_DEFAULTS["inline"] -assert inline_display["tool_progress"] == "all" -assert inline_display["cleanup_progress"] is False +assert inline_display["tool_progress"] == "off" +assert inline_display["cleanup_progress"] is True assert inline_display["streaming"] is False -assert inline_display["interim_assistant_messages"] is True +assert inline_display["interim_assistant_messages"] is False assert ctx.platform["name"] == "inline" assert ctx.platform["emoji"] == chr(0x1F4AC) assert ctx.platform["label"] == "Inline" @@ -2167,7 +2168,7 @@ async def assert_forced_reply_thread_creation(): assert events[0].auto_skill == ["thread-skill"] await adapter.send_typing("10", metadata={"thread_id": "99"}) - assert calls[-1] == ("/typing", {"target": {"chatId": "10"}, "state": "start", "experimentalAgentActivity": True}) + assert calls[-1] == ("/typing", {"target": {"chatId": "10"}, "state": "start"}) trigger_reply = await adapter.send("10", "agent reply", reply_to="7", metadata={"thread_id": "99"}) assert trigger_reply.success is True @@ -2178,14 +2179,14 @@ async def assert_forced_reply_thread_creation(): assert calls[-1] == ("/typing", {"target": {"chatId": "10"}, "state": "stop"}) await adapter.send_typing("10", metadata={"thread_id": "99"}) - assert calls[-1] == ("/typing", {"target": {"chatId": "99"}, "state": "start", "experimentalAgentActivity": True}) + assert calls[-1] == ("/typing", {"target": {"chatId": "99"}, "state": "start"}) await adapter.stop_typing("10", metadata={"thread_id": "99"}) assert calls[-1] == ("/typing", {"target": {"chatId": "99"}, "state": "stop"}) reused_thread = await adapter._create_reply_thread("10", "7", "please investigate the incident", "6") assert reused_thread == "99" await adapter.send_typing("10", metadata={"thread_id": "99"}) - assert calls[-1] == ("/typing", {"target": {"chatId": "99"}, "state": "start", "experimentalAgentActivity": True}) + assert calls[-1] == ("/typing", {"target": {"chatId": "99"}, "state": "start"}) await adapter.stop_typing("10", metadata={"thread_id": "99"}) assert calls[-1] == ("/typing", {"target": {"chatId": "99"}, "state": "stop"}) @@ -3979,7 +3980,7 @@ async def assert_transport_helpers(): assert calls[-1] == ("/delete", {"target": {"chatId": "99"}, "messageId": "777"}) await adapter.send_typing("chat:10", metadata=metadata) - assert calls[-1] == ("/typing", {"target": {"chatId": "99"}, "state": "start", "experimentalAgentActivity": True}) + assert calls[-1] == ("/typing", {"target": {"chatId": "99"}, "state": "start"}) await adapter.stop_typing("chat:10", metadata=metadata) assert calls[-1] == ("/typing", {"target": {"chatId": "99"}, "state": "stop"}) @@ -5602,330 +5603,6 @@ async def assert_bot_settings_fail_closed_and_serialized(): asyncio.run(assert_bot_settings_fail_closed_and_serialized()) -async def assert_quiet_timeline(): - import queue - from inline.adapter import _install_inline_progress_bridge, _send_result - from inline.activity import ActivityEvent - adapter = InlineAdapter(PlatformConfig(extra=trusted_extra)) - calls = [] - serial = [90] - async def send_sidecar(route, body): - calls.append((route, body)) - if route == "/send": serial[0] += 1 - return _send_result(success=True, message_id=str(serial[0]), raw_response={}) - adapter._send_sidecar = send_sidecar - adapter._mark_reply_thread_visible = lambda target: None - adapter._target_for = lambda chat_id, metadata: {"chatId": chat_id} - adapter._reply_to_for_target = lambda reply_to, target: None - adapter._processing_reaction_target = lambda event: None - class ProgressRunner: - def progress_callback(self, *args, **kwargs): - calls.append(("host_callback", args)) - async def send_progress_messages(self): - calls.append(("host_sender", None)) - fake_run = types.ModuleType("gateway.run") - fake_run.TurnRunner = ProgressRunner - previous_run = sys.modules.get("gateway.run") - sys.modules["gateway.run"] = fake_run - try: - _install_inline_progress_bridge() - installed = ProgressRunner.send_progress_messages - _install_inline_progress_bridge() - assert ProgressRunner.send_progress_messages is installed - source = types.SimpleNamespace(platform="inline", chat_id="7", thread_id=None) - event = types.SimpleNamespace(source=source, message_id="1", raw_message=None) - await adapter.on_processing_start(event) - runner = ProgressRunner() - runner._ctx = types.SimpleNamespace(source=source, event_message_id="1", _progress_metadata={}, - progress_queue=queue.Queue(), tool_progress_enabled=True, progress_mode="all", - _run_still_current=lambda: True, agent_holder=[None]) - runner._runner = types.SimpleNamespace(_adapter_for_source=lambda source: adapter) - runner.progress_callback("tool.started", "terminal", "python3 bad.py", {"description":"Checking the EPUB", "command":"python3 bad.py"}) - queued_event = runner._ctx.progress_queue.queue[0] - assert isinstance(queued_event, ActivityEvent) and queued_event.title == "Checking the EPUB" - task = asyncio.create_task(runner.send_progress_messages()) - await asyncio.sleep(0.02) - assert '>Checking the EPUB' in calls[0][1]["text"] - await adapter.send("7", "Here is the comparison.") - assert calls[-1][1]["text"] == "Here is the comparison." - assert '>Worked for ' in calls[-2][1]["text"] - task.cancel() - await task - # Public completion hook is authoritative, even with reactions disabled. - await adapter.on_processing_complete(event, "failure") - assert '>Failed after ' in calls[-1][1]["text"] - assert adapter._activity_timelines == {} - adapter._agent_activity_enabled = False - runner.progress_callback("tool.started", "terminal", "ls", {}) - await runner.send_progress_messages() - assert calls[-2][0] == "host_callback" and calls[-1][0] == "host_sender" - adapter._agent_activity_enabled = True - source.platform = "telegram" - runner.progress_callback("tool.started", "terminal", "ls", {}) - await runner.send_progress_messages() - assert calls[-2][0] == "host_callback" and calls[-1][0] == "host_sender" - - # The real host recurses into queued turns before cancelling the first - # sender, then completes only the ORIGINAL inbound event. Every child - # must share that owner, while only the final child gets delivery errors. - source.platform = "inline" - async def queued_chain(final_outcome, *, first_interrupted=False, last_failed=False): - calls.clear() - outer = types.SimpleNamespace(source=source, message_id="101", raw_message=None) - await adapter.on_processing_start(outer) - runners = [] - tasks = [] - for message_id in ("101", "102"): - child = ProgressRunner() - child._ctx = types.SimpleNamespace(source=source,event_message_id=message_id,_progress_metadata={}, - progress_queue=queue.Queue(),tool_progress_enabled=True,progress_mode="all", - _run_still_current=lambda:True,agent_holder=[types.SimpleNamespace(is_interrupted=False)], - result_holder=[None]) - child._runner = types.SimpleNamespace(_adapter_for_source=lambda source:adapter) - child.progress_callback("tool.started", "terminal", message_id, {"description":"Work " + message_id}) - tasks.append(asyncio.create_task(child.send_progress_messages())) - runners.append(child) - await asyncio.sleep(.01) - if message_id == "101": - child._ctx.result_holder[0] = {"interrupted":first_interrupted} - if not first_interrupted: - # Queued-first-response sends are unmarked in the host. - await adapter.send("7", "First response") - assert list(adapter._activity_timelines)==[("7", "", "101")] - group=adapter._activity_timelines[("7", "", "101")] - assert len(group)==2 and group[0].closed and not group[1].closed - runners[-1]._ctx.result_holder[0] = {"failed":last_failed} - if final_outcome=="cancelled": - runners[-1]._ctx.agent_holder[0].is_interrupted=True - for child_task in reversed(tasks): - child_task.cancel(); await child_task - first_id=group[0].last_closed["messageId"] - second_id=group[1].last_closed["messageId"] - first_elapsed=group[0].last_closed["elapsed"] - await adapter.on_processing_complete(outer, final_outcome) - assert adapter._activity_timelines=={} - edits={body["messageId"]:body["text"] for route,body in calls if route=="/edit"} - assert ('>Stopped after ' if first_interrupted else '>Worked for ') in edits[first_id] - expected='>Stopped after ' if final_outcome=="cancelled" else '>Failed after ' if final_outcome=="failure" or last_failed else '>Worked for ' - assert expected in edits[second_id] - assert group[0].last_closed["elapsed"]==first_elapsed - - await queued_chain("failure") - await queued_chain("cancelled") - await queued_chain("success", first_interrupted=True) - await queued_chain("success", last_failed=True) - - # Context ownership is per processing task, including two independent - # sessions targeting the same chat; one reply must not close the other. - ready=asyncio.Event(); started=[0] - async def independent_processing(message_id): - event=types.SimpleNamespace(source=source,message_id=message_id,raw_message=None) - await adapter.on_processing_start(event) - child=ProgressRunner() - child._ctx=types.SimpleNamespace(source=source,event_message_id=message_id,_progress_metadata={}, - progress_queue=queue.Queue(),agent_holder=[None],_run_still_current=lambda:True) - child._ctx.progress_queue.put(ActivityEvent('Concurrent '+message_id,'detail',time.monotonic())) - child._runner=types.SimpleNamespace(_adapter_for_source=lambda source:adapter) - task=asyncio.create_task(child.send_progress_messages()); await asyncio.sleep(.01) - started[0]+=1 - if started[0]==2: ready.set() - await ready.wait() - await adapter.send("7", "Reply "+message_id) - assert calls[-1][1]['text']=='Reply '+message_id - assert ('Concurrent '+message_id) in calls[-2][1]['text'] - assert '>Worked for ' in calls[-2][1]['text'] - task.cancel(); await task - await adapter.on_processing_complete(event,"success") - await asyncio.gather(independent_processing("201"),independent_processing("202")) - assert adapter._activity_timelines=={} - - # Both consume.finally and the bridge retry can fail to deliver the - # terminal edit. Public completion must retry again without changing - # a real failed/stopped outcome or inventing failure from transport. - for result_outcome,prefix in (("success","Worked for"),("failure","Failed after"),("cancelled","Stopped after")): - terminal_attempts=[]; now=[105.0] - async def retry_sidecar(route,body): - if route=="/edit": - terminal_attempts.append(body['text']) - if len(terminal_attempts)<=2: - return _send_result(success=False,error="temporary terminal edit failure") - return await send_sidecar(route,body) - adapter._send_sidecar=retry_sidecar - outer=types.SimpleNamespace(source=source,message_id="retry-301",raw_message=None) - await adapter.on_processing_start(outer) - child=ProgressRunner() - child._ctx=types.SimpleNamespace(source=source,event_message_id="retry-301",_progress_metadata={}, - progress_queue=queue.Queue(),agent_holder=[types.SimpleNamespace(is_interrupted=False)], - result_holder=[None],_run_still_current=lambda:True) - child._runner=types.SimpleNamespace(_adapter_for_source=lambda source:adapter) - child._ctx.progress_queue.put(ActivityEvent("Checking","detail",100)) - task=asyncio.create_task(child.send_progress_messages());await asyncio.sleep(.01) - timeline=adapter._activity_timelines[("7","","retry-301")][0] - timeline.clock=lambda:now[0] - child._ctx.result_holder[0]={"failed":result_outcome=="failure","interrupted":result_outcome=="cancelled"} - child._ctx.agent_holder[0].is_interrupted=result_outcome=="cancelled" - task.cancel();await task - assert len(terminal_attempts)==2 and timeline.closed - assert timeline.pending_outcome==result_outcome - now[0]=205.0 - await adapter.on_processing_complete(outer,"success") - assert len(terminal_attempts)==3 and len(set(terminal_attempts))==1 - assert f'>{prefix} 5s' in terminal_attempts[-1] - assert adapter._activity_timelines=={} and timeline.pending_outcome is None - adapter._send_sidecar=send_sidecar - - # Internal/cron replies have neither a public owner nor notify/interim - # metadata. Their temporary sender registrations still flush queued - # tools BEFORE an unmarked reply, with no synthetic-anchor leak. - def internal_runner(anchor): - child=ProgressRunner() - child._ctx=types.SimpleNamespace(source=source,event_message_id=anchor,_progress_metadata={}, - progress_queue=queue.Queue(),agent_holder=[None],_run_still_current=lambda:True) - child._runner=types.SimpleNamespace(_adapter_for_source=lambda source:adapter) - return child - - for anchor in (None,"cron-anchor"): - calls.clear() - internal=internal_runner(anchor) - pending=internal._ctx.progress_queue - pending.put(ActivityEvent("Cron first","before reply A",time.monotonic())) - internal_task=asyncio.create_task(internal.send_progress_messages());await asyncio.sleep(.01) - keys=list(adapter._activity_timelines) - assert len(keys)==1 and keys[0][2].startswith("internal:") and keys[0][2]!=anchor - pending.put(ActivityEvent("Cron second","before reply B",time.monotonic())) - await adapter.send("7","Cron summary") - assert calls[-1][1]['text']=="Cron summary" - assert 'before reply B' in calls[-2][1]['text'] and '>Worked for ' in calls[-2][1]['text'] - # Match the actual host's after-send reset, then later tool work. - pending.put(('__reset__',)) - pending.put(ActivityEvent("Cron third","after reply C",time.monotonic())) - await asyncio.sleep(.55) - internal_task.cancel();await internal_task - sent=[body['text'] for route,body in calls if route=="/send"] - assert len(sent)==3 and sent[1]=="Cron summary" - assert 'after reply C' in sent[2] and 'before reply B' not in sent[2] - assert adapter._activity_timelines=={} - - # Without an owner, an overlapping internal reply closes ALL earlier - # activity rows for its target. Each sender removes only its own entry. - calls.clear();internal_tasks=[] - for anchor in ("cron-a","cron-b"): - internal=internal_runner(anchor) - internal._ctx.progress_queue.put(ActivityEvent(anchor,'queued before shared reply',time.monotonic())) - internal_tasks.append(asyncio.create_task(internal.send_progress_messages())) - await asyncio.sleep(.01) - assert len(adapter._activity_timelines)==2 - await adapter.send("7","Shared internal reply") - assert calls[-1][1]['text']=="Shared internal reply" - assert all('>Worked for ' in body['text'] for route,body in calls[-3:-1]) - internal_tasks[0].cancel();await internal_tasks[0] - assert len(adapter._activity_timelines)==1 - internal_tasks[1].cancel();await internal_tasks[1] - assert adapter._activity_timelines=={} - - # Failed progress delivery must also release the sender-owned entry. - async def fail_sidecar(route,body): - return _send_result(success=False,error="synthetic transport failure") - adapter._send_sidecar=fail_sidecar - internal=internal_runner("failed-cron") - internal._ctx.progress_queue.put(ActivityEvent("Failing cron","detail",time.monotonic())) - await internal.send_progress_messages() - assert adapter._activity_timelines=={} - finally: - if previous_run is None: sys.modules.pop("gateway.run", None) - else: sys.modules["gateway.run"] = previous_run - -asyncio.run(assert_quiet_timeline()) - -async def assert_inline_tool_reply_boundary(): - import queue - from inline.adapter import _activity_owner, _send_result, InlineSidecarError - from inline.activity import ActivityEvent, ActivityTimeline - from inline.message_actions import build_inline_agent_action_id - old_sidecar=inline_tools._sidecar.copy() - direct_call=inline_tools._sidecar_call - def forbidden_fallback(*args): - raise AssertionError('A configured tool send must never bypass the adapter or retry directly') - inline_tools._sidecar_call=forbidden_fallback - try: - for owned in (True,False): - adapter=InlineAdapter(PlatformConfig(extra=trusted_extra)) - calls=[]; serial=[0]; entered=asyncio.Event(); release=asyncio.Event() - async def publish(route,body): - serial[0]+=1; calls.append((route,body.copy())) - return _send_result(success=True,message_id=str(serial[0])) - async def raw_send(route,body): - if body['target']=={'chatId':'7'}: - entered.set(); await release.wait() - calls.append(('tool-delivered',body.copy())) - return {'ok':True,'result':{'messageId':'123'}} - adapter._sidecar_call=raw_send - adapter._configure_tool_sender() - ctx=types.SimpleNamespace(progress_queue=queue.Queue(),agent_holder=[None],_run_still_current=lambda:True) - timeline=ActivityTimeline(publish,{'chatId':'7'},ctx=ctx) - adapter._activity_timelines[('7','','owner')]=[timeline] - token=_activity_owner.set((adapter,('7','','owner')) if owned else None) - try: - await timeline.add(ActivityEvent('Before','first tool',time.monotonic())) - unrelated=await asyncio.to_thread(inline_tools._handle_inline_tool,{'action':'send_message','chat_id':'8','text':'Other destination'}) - assert json.loads(unrelated)['success'] and timeline.started_at is not None - ctx.progress_queue.put(ActivityEvent('Queued before','second tool',time.monotonic())) - args={'action':'send_message','chat_id':'7','text':'FIRST_STEP_VISIBLE','parse_markdown':False, - 'buttons':[[{'text':'Copy','copy_text':'exact text'}]]} - task=asyncio.create_task(asyncio.to_thread(inline_tools._handle_inline_tool,args)) - await asyncio.wait_for(entered.wait(),1) - assert '>Worked for ' in calls[-1][1]['text'] and 'second tool' in calls[-1][1]['text'] - later=asyncio.create_task(timeline.add(ActivityEvent('After','third tool',time.monotonic()))) - await asyncio.sleep(.01) - assert not later.done(), 'Later progress overtook the actual tool reply transport' - release.set() - result=json.loads(await task); await later - assert result=={'success':True,'action':'send_message','result':{'messageId':'123'}} - assert calls[-2][0]=='tool-delivered' and calls[-2][1]=={ - 'target':{'chatId':'7'},'text':'FIRST_STEP_VISIBLE','parseMarkdown':False, - 'actions':{'rows':[{'actions':[{'id':build_inline_agent_action_id(0,0),'text':'Copy','copyText':'exact text'}]}]}} - assert calls[-1][0]=='/send' and 'third tool' in calls[-1][1]['text'] - assert 'second tool' not in calls[-1][1]['text'] - - # The synchronous tool may never block its own event loop. - same_loop=json.loads(inline_tools._handle_inline_tool(args)) - assert 'event loop' in same_loop['error'] - async def failed_send(route,body): - raise InlineSidecarError(route,503,'temporary transport failure','transient',{}) - adapter._sidecar_call=failed_send - error=json.loads(await asyncio.to_thread(inline_tools._handle_inline_tool,args)) - assert error['error_kind']=='transient' and 'temporary transport failure' in error['error'] - schedule=asyncio.run_coroutine_threadsafe - cancelled=[] - class TimedOutSend: - def result(self,timeout): - assert timeout==45 - raise TimeoutError('synthetic timeout') - def cancel(self): cancelled.append(True) - def time_out_send(coroutine,loop): - coroutine.close() - return TimedOutSend() - try: - asyncio.run_coroutine_threadsafe=time_out_send - error=json.loads(await asyncio.to_thread(inline_tools._handle_inline_tool,args)) - assert 'delivery may have completed' in error['error'] and cancelled==[True] - finally: - asyncio.run_coroutine_threadsafe=schedule - sender=adapter._tool_send - # Clearing another adapter's obsolete hook cannot unregister - # this live gateway; teardown clears only its own callback. - inline_tools.clear_sidecar_send(lambda body:None) - assert inline_tools._sidecar['send'] is sender - await adapter.disconnect() - assert inline_tools._sidecar.get('send') is None - finally: - _activity_owner.reset(token) - finally: - inline_tools._sidecar.clear(); inline_tools._sidecar.update(old_sidecar) - inline_tools._sidecar_call=direct_call - -asyncio.run(assert_inline_tool_reply_boundary()) - print("adapter python smoke ok") ` diff --git a/plugins/hermes-agent/tests/package-artifact.test.ts b/plugins/hermes-agent/tests/package-artifact.test.ts index 7b3dc68ea..28e74f891 100644 --- a/plugins/hermes-agent/tests/package-artifact.test.ts +++ b/plugins/hermes-agent/tests/package-artifact.test.ts @@ -254,7 +254,6 @@ const expectedFiles = [ "plugin/inline/LICENSE", "plugin/inline/README.md", "plugin/inline/__init__.py", - "plugin/inline/activity.py", "plugin/inline/adapter.py", "plugin/inline/cli.py", "plugin/inline/message_actions.py", diff --git a/plugins/hermes-agent/tests/sidecar-runtime.test.ts b/plugins/hermes-agent/tests/sidecar-runtime.test.ts index 94664605f..61b6ce9ee 100644 --- a/plugins/hermes-agent/tests/sidecar-runtime.test.ts +++ b/plugins/hermes-agent/tests/sidecar-runtime.test.ts @@ -220,26 +220,21 @@ describe("sidecar runtime", () => { target: { chatId: "123" }, messageId: "9001", }, auth)) - // Unset/false uses the established typing API. Only an explicit boolean opts in. - for (const experimentalAgentActivity of [undefined, false]) { - await expectOk(post(port, "/typing", { - target: { chatId: "123" }, state: "start", experimentalAgentActivity, - }, auth)) - } - for (const state of ["start", "stop"]) { - await expectOk(post(port, "/typing", { - target: { chatId: "123" }, state, experimentalAgentActivity: true, - }, auth)) + // Stale experimental flags also use ordinary typing after timeline removal. + for (const experimentalAgentActivity of [undefined, false, true]) { + for (const state of ["start", "stop"]) { + await expectOk(post(port, "/typing", { + target: { chatId: "123" }, state, experimentalAgentActivity, + }, auth)) + } } - const activityHealth = await post(port, "/healthz", {}, auth) - const allCalls = (resultOf(activityHealth.body).diagnostics as { - calls: Array<{ method: string; params?: { sendComposeAction?: { action?: number } } }> + const typingHealth = await post(port, "/healthz", {}, auth) + const typingCalls = (resultOf(typingHealth.body).diagnostics as { + calls: Array<{ method: string; params?: { typing?: boolean } }> }).calls - expect(allCalls.filter((call) => call.method === "sendTyping")).toHaveLength(2) - const activityCalls = allCalls.filter((call) => call.method === "invoke:SEND_COMPOSE_ACTION") - expect(activityCalls).toHaveLength(2) - expect(activityCalls[0]?.params?.sendComposeAction?.action).toBe(6) - expect(activityCalls[1]?.params?.sendComposeAction).not.toHaveProperty("action") + expect(typingCalls.filter((call) => call.method === "sendTyping").map((call) => call.params?.typing)) + .toEqual([true, false, true, false, true, false]) + expect(typingCalls.filter((call) => call.method === "invoke:SEND_COMPOSE_ACTION")).toHaveLength(0) await expectOk(post(port, "/presence", { target: { userId: "42" }, kind: "running", diff --git a/scripts/ci/check-hermes-host.py b/scripts/ci/check-hermes-host.py index d55f83de9..7382f0c38 100644 --- a/scripts/ci/check-hermes-host.py +++ b/scripts/ci/check-hermes-host.py @@ -431,4 +431,92 @@ async def unexpected_failure(_event): asyncio.run(exercise_receipt_recovery()) + + +async def exercise_ordinary_presentation(): + from gateway.display_config import resolve_display_setting, resolve_tool_progress + + # Exercise the host's actual resolver; explicit operator config always wins. + assert resolve_display_setting({}, "inline", "tool_progress") == "off" + assert resolve_display_setting({}, "inline", "cleanup_progress") is True + assert resolve_display_setting({}, "inline", "interim_assistant_messages") is False + assert resolve_display_setting({}, "inline", "long_running_notifications") is True + assert resolve_display_setting({"display": {"tool_progress": "all"}}, "inline", "tool_progress") == "all" + explicit = {"display": {"tool_progress": "off", "platforms": {"inline": { + "tool_progress": "all", "cleanup_progress": False, "interim_assistant_messages": True, + }}}} + assert resolve_display_setting(explicit, "inline", "tool_progress") == "all" + assert resolve_display_setting(explicit, "inline", "cleanup_progress") is False + assert resolve_display_setting(explicit, "inline", "interim_assistant_messages") is True + assert resolve_display_setting({"display": {"tool_progress_overrides": {"inline": "new"}}}, + "inline", "tool_progress") == "new" + + assert resolve_tool_progress({}, "inline", "all") == ("all", True) + assert resolve_tool_progress({"display": {"platforms": {"inline": {"tool_progress": "off"}}}}, + "inline", "all") == ("off", True) + + for flag in ("", "true"): + for failed_send in (False, True): + with patch.dict(os.environ, {"INLINE_EXPERIMENTAL_AGENT_ACTIVITY": flag}): + ordinary = platform_registry.create_adapter("inline", PlatformConfig( + enabled=True, token="offline-test-token", extra={ + "dm_policy": "open", "require_mention": False, "reply_threads": "off", + "context_backfill": "off", "reactions": True, "sync_commands": False, + "text_debounce_seconds": 0, + }, + )) + calls, outcomes, completed, typing_started = [], [], asyncio.Event(), asyncio.Event() + def transport(request): + body = json.loads(request.content) + calls.append((request.url.path, body)) + if request.url.path == "/chat": + return httpx.Response(200, json={"ok": True, "result": {"id": "6101", "title": "Offline fixture"}}) + if request.url.path == "/typing" and body["state"] == "start": + typing_started.set() + if request.url.path == "/send" and failed_send: + return httpx.Response(400, json={"ok": False, "error": "rejected fixture reply", "errorKind": "bad_format"}) + return httpx.Response(200, json={"ok": True, "result": {"messageId": "6202"}}) + async def reply(event): + await asyncio.wait_for(typing_started.wait(), timeout=10) + return "Ordinary native reply" + complete = ordinary.on_processing_complete + async def observe_completion(event, outcome): + await complete(event, outcome) + outcomes.append(str(getattr(outcome, "value", outcome))) + completed.set() + ordinary.on_processing_complete = observe_completion + ordinary.set_message_handler(reply) + ordinary.set_authorization_check(lambda *_args, **_kwargs: True) + ordinary._http_client = httpx.AsyncClient(transport=httpx.MockTransport(transport)) + event = {"kind": "message.new", "seq": 6201, "chatId": "6101", "meId": "999", + "sender": {"id": "42", "bot": False}, "message": { + "id": "6201", "chatId": "6101", "fromId": "42", "message": "hello", + "peerId": {"type": {"oneofKind": "chat", "chat": {"chatId": "6101"}}}, + }} + try: + await ordinary._on_inbound(json.dumps(event)) + await asyncio.wait_for(completed.wait(), timeout=10) + async def wait_idle(): + while ordinary._active_sessions: + await asyncio.sleep(0.001) + await asyncio.wait_for(wait_idle(), timeout=10) + assert outcomes == ["failure" if failed_send else "success"], outcomes + typing = [body for route, body in calls if route == "/typing"] + assert typing and any(body["state"] == "start" for body in typing) + assert typing[-1]["state"] == "stop" + assert all("experimentalAgentActivity" not in body for body in typing) + reactions = [body for route, body in calls if route == "/reaction"] + assert [(body["emoji"], body.get("remove", False)) for body in reactions] == [ + ("👀", False), ("👀", True), ("❌" if failed_send else "✅", False), + ], reactions + sends = [body for route, body in calls if route == "/send"] + assert sends and sends[0]["text"] == "Ordinary native reply" + assert not any(" Date: Thu, 8 Oct 2026 00:30:47 +0330 Subject: [PATCH 12/14] ios: keep grid discoverable and open it as a page --- apple/InlineIOS/ContentView.swift | 2 + apple/InlineIOS/ExperimentalRootView.swift | 22 +- .../Experimental/ExperimentalNavigation.swift | 2 + .../Features/Grid/GridHomeEntry.swift | 77 ++---- .../Features/Grid/GridRoomsView.swift | 232 +++++++++++------- apple/InlineIOS/MainViews/HomeView.swift | 11 +- apple/InlineIOS/Navigation/AppContent.swift | 3 +- 7 files changed, 180 insertions(+), 169 deletions(-) diff --git a/apple/InlineIOS/ContentView.swift b/apple/InlineIOS/ContentView.swift index a7f593644..5e48eafee 100644 --- a/apple/InlineIOS/ContentView.swift +++ b/apple/InlineIOS/ContentView.swift @@ -177,6 +177,8 @@ private struct AuthedAppRoot: View { .id(peer) case let .chatInfo(chatItem): ChatInfoView(chatItem: chatItem) + case let .grid(initialSpaceID): + GridDestinationView(initialSpaceID: initialSpaceID) case let .spaceSettings(spaceId): SpaceSettingsView(spaceId: spaceId) case let .spaceIntegrations(spaceId): diff --git a/apple/InlineIOS/ExperimentalRootView.swift b/apple/InlineIOS/ExperimentalRootView.swift index 7dd9784ed..fdfedd27e 100644 --- a/apple/InlineIOS/ExperimentalRootView.swift +++ b/apple/InlineIOS/ExperimentalRootView.swift @@ -133,7 +133,6 @@ private struct ExperimentalAuthedRootView: View { @State private var lastContentRootTab: RootTab = .allChats @State private var pendingSearchExit: PendingSearchExit? @State private var isCreatingThread = false - @State private var isGridPresented = false @AppStorage(ExperimentalFeatureFlags.gridIOSKey) private var gridIOSEnabled = false @State private var isCleaningOpenChats = false @State private var iPadCommandRequest: IPadCommandRequest? @@ -459,11 +458,7 @@ private struct ExperimentalAuthedRootView: View { .toolbar { experimentalToolbarContent() } - .gridHomeEntry( - isPresented: $isGridPresented, - spaceID: nav.activeSpaceId, - spaces: compactSpaceList.spaces - ) + .gridHomeEntry(onOpen: openGrid) .safeAreaInset(edge: .bottom, spacing: 0) { IPadSidebarSwitcher(selection: iPadSidebarScopeBinding) } @@ -484,12 +479,7 @@ private struct ExperimentalAuthedRootView: View { .toolbar { experimentalToolbarContent() } - .gridHomeEntry( - isPresented: $isGridPresented, - spaceID: nav.activeSpaceId, - spaces: compactSpaceList.spaces, - isVisible: !isSearchActivePresentation - ) + .gridHomeEntry(isVisible: !isSearchActivePresentation, onOpen: openGrid) .navigationDestination(for: Destination.self) { destination in ExperimentalDestinationView( nav: bindableNav, @@ -906,6 +896,12 @@ private struct ExperimentalAuthedRootView: View { ) } + private func openGrid() { + guard gridIOSEnabled, + GridHomeEntryProjection(store: GridRuntime.shared.rooms, spaceID: nil).isEligible else { return } + openHomeDestination(.grid(initialSpaceID: nav.activeSpaceId)) + } + private func openHomeDestination(_ destination: Destination) { if usesIPadSplitView { router[IPadNavigationLane.canonicalTab] = [destination] @@ -1208,7 +1204,7 @@ private struct ExperimentalAuthedRootView: View { }, onGrid: gridIOSEnabled && GridHomeEntryProjection(store: GridRuntime.shared.rooms, spaceID: nil).showsMenuEntry - ? { isGridPresented = true } + ? openGrid : nil ) // Fill the iPad toolbar target without changing the phone toolbar layout. diff --git a/apple/InlineIOS/Features/Experimental/ExperimentalNavigation.swift b/apple/InlineIOS/Features/Experimental/ExperimentalNavigation.swift index 0779fb3df..7538bfa61 100644 --- a/apple/InlineIOS/Features/Experimental/ExperimentalNavigation.swift +++ b/apple/InlineIOS/Features/Experimental/ExperimentalNavigation.swift @@ -221,6 +221,8 @@ struct ExperimentalDestinationView: View { } case let .chatInfo(chatItem): ChatInfoView(chatItem: chatItem) + case let .grid(initialSpaceID): + GridDestinationView(initialSpaceID: initialSpaceID) case let .spaceSettings(spaceId): SpaceSettingsView(spaceId: spaceId, usesRouterNavigation: usesRouterNavigation) case let .spaceIntegrations(spaceId): diff --git a/apple/InlineIOS/Features/Grid/GridHomeEntry.swift b/apple/InlineIOS/Features/Grid/GridHomeEntry.swift index f581b5e70..a6c28eb2f 100644 --- a/apple/InlineIOS/Features/Grid/GridHomeEntry.swift +++ b/apple/InlineIOS/Features/Grid/GridHomeEntry.swift @@ -23,77 +23,55 @@ struct GridHomeEntryProjection { for home in homes { for avatar in home.recentAvatars where seen.insert(avatar.user.id).inserted { preview.append(avatar) - if preview.count == 4 { break } + if preview.count == 4 { + break + } + } + if preview.count == 4 { + break } - if preview.count == 4 { break } } avatars = preview } - var showsPill: Bool { isEligible && activeCount > 0 && !avatars.isEmpty } - var showsMenuEntry: Bool { isEligible && !showsPill } + var showsPill: Bool { + isEligible && activeCount > 0 && !avatars.isEmpty + } + + var showsMenuEntry: Bool { + isEligible + } } extension View { - func gridHomeEntry( - isPresented: Binding, - spaceID: Int64? = nil, - spaces: [InlineKit.Space] = [], - isVisible: Bool = true - ) -> some View { - modifier(GridHomeEntryModifier( - isPresented: isPresented, - spaceID: spaceID, - spaces: spaces, - isVisible: isVisible - )) + func gridHomeEntry(isVisible: Bool = true, onOpen: @escaping () -> Void) -> some View { + modifier(GridHomeEntryModifier(isVisible: isVisible, onOpen: onOpen)) } } private struct GridHomeEntryModifier: ViewModifier { - @Binding var isPresented: Bool - let spaceID: Int64? - let spaces: [InlineKit.Space] let isVisible: Bool - + let onOpen: () -> Void @AppStorage(ExperimentalFeatureFlags.gridIOSKey) private var isEnabled = false func body(content: Content) -> some View { - Group { - if isEnabled { - content.modifier(GridEnabledHomeEntryModifier( - isPresented: $isPresented, - spaceID: spaceID, - spaces: spaces, - isVisible: isVisible - )) - } else { - content - } - } - .onChange(of: isEnabled) { wasEnabled, enabled in - if wasEnabled && !enabled { - isPresented = false - } + if isEnabled { + content.modifier(GridEnabledHomeEntryModifier(isVisible: isVisible, onOpen: onOpen)) + } else { + content } } } private struct GridEnabledHomeEntryModifier: ViewModifier { - @Binding var isPresented: Bool - let spaceID: Int64? - let spaces: [InlineKit.Space] let isVisible: Bool + let onOpen: () -> Void @Environment(\.scenePhase) private var scenePhase private let store = GridRuntime.shared.rooms func body(content: Content) -> some View { let projection = GridHomeEntryProjection(store: store, spaceID: nil) let showsPill = isVisible && projection.showsPill - let presentation = Binding( - get: { isPresented && projection.isEligible }, - set: { isPresented = $0 } - ) Group { if #available(iOS 26.0, *) { @@ -102,7 +80,7 @@ private struct GridEnabledHomeEntryModifier: ViewModifier { avatars: projection.avatars, activeCount: projection.activeCount, isVisible: showsPill, - onOpen: { isPresented = true } + onOpen: onOpen ) } } else { @@ -111,26 +89,17 @@ private struct GridEnabledHomeEntryModifier: ViewModifier { avatars: projection.avatars, activeCount: projection.activeCount, isVisible: showsPill, - onOpen: { isPresented = true } + onOpen: onOpen ) } } } - .popover(isPresented: presentation, attachmentAnchor: .point(.top), arrowEdge: .top) { - GridRoomsView(store: store, spaces: spaces, initialSpaceID: spaceID) - .presentationCompactAdaptation(.sheet) - .presentationDetents([.height(280), .medium, .large]) - .presentationDragIndicator(.visible) - } .task { await store.loadHome() } .onChange(of: scenePhase) { _, phase in if phase == .active { Task { await store.loadHome() } } } - .onChange(of: projection.isEligible) { _, eligible in - if !eligible { isPresented = false } - } } } diff --git a/apple/InlineIOS/Features/Grid/GridRoomsView.swift b/apple/InlineIOS/Features/Grid/GridRoomsView.swift index 2838fbf59..74410fcc4 100644 --- a/apple/InlineIOS/Features/Grid/GridRoomsView.swift +++ b/apple/InlineIOS/Features/Grid/GridRoomsView.swift @@ -8,6 +8,34 @@ import InlineUI import SwiftUI import UIKit +/// Uses the same process-owned room service as Home and the active call. +struct GridDestinationView: View { + let initialSpaceID: Int64? + @AppStorage(ExperimentalFeatureFlags.gridIOSKey) private var isEnabled = false + @EnvironmentObject private var spaceList: CompactSpaceList + @Environment(\.dismiss) private var dismiss + + var body: some View { + Group { + if isEnabled { + GridRoomsView( + store: GridRuntime.shared.rooms, + spaces: spaceList.spaces, + initialSpaceID: initialSpaceID + ) + } else { + ContentUnavailableView("Grid is unavailable", systemImage: "square.grid.2x2") + .navigationTitle("Grid") + } + } + .onChange(of: isEnabled) { _, enabled in + if !enabled { + dismiss() + } + } + } +} + struct GridRoomsView: View { let store: GridRoomService let spaces: [InlineKit.Space] @@ -35,18 +63,18 @@ struct GridRoomsView: View { private var actionFailure: GridActionFailure? { let hasConnectionFailure = store.media.connectionState == .failed let connectionError = hasConnectionFailure ? store.media.lastConnectionError : nil - let audioError: String? - if case let .failed(message) = store.media.audioState { - audioError = message + let audioError: String? = if case let .failed(message) = store.media.audioState { + message } else { - audioError = nil + nil } let storeError = store.membershipMutationInFlight ? nil : store.lastError - let errors = [storeError, connectionError, audioError].compactMap { $0 } + let errors = [storeError, connectionError, audioError].compactMap(\.self) let hasMediaFailure = hasConnectionFailure || audioError != nil || store.media.providerCircuitOpen let audio = InlineAudioSession.shared if audio.isQuarantined && (store.hasLocalAdmission || !errors.isEmpty) - || errors.contains(InlineAudioSessionError.quarantined.localizedDescription) { + || errors.contains(InlineAudioSessionError.quarantined.localizedDescription) + { return .restartRequired } guard !errors.isEmpty || hasMediaFailure else { return nil } @@ -60,94 +88,97 @@ struct GridRoomsView: View { } var body: some View { - NavigationStack { - VStack(spacing: 16) { - if let spaceID = effectiveSpaceID { - GridRoomCollection(store: store, spaceID: spaceID, onViewShare: { avatar, share in - guard let identity = store.mediaSessionIdentity else { return } - selectedShare = GridScreenShareSelection( - mediaSessionIdentity: identity, - participantIdentity: share.participantIdentity, - publicationID: share.publicationID, - displayName: InlineKit.User(from: avatar.user).displayName - ) - }) - - GridCurrentRoomControls(store: store, spaceID: store.currentCall?.spaceID ?? spaceID) - if !store.callTransferEnabled { - Text("Grid is unavailable") - .font(.footnote) - .foregroundStyle(.secondary) - } - } - } - .padding(20) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .navigationTitle("Grid") - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .principal) { - GridSpacePicker( - homeSpaces: store.orderedHomeSpaces, - spaces: spaces, - selectedSpaceID: effectiveSpaceID, - onSelect: { selectedSpaceID = $0 } + VStack(spacing: 16) { + if let spaceID = effectiveSpaceID { + GridRoomCollection(store: store, spaceID: spaceID, onViewShare: { avatar, share in + guard let identity = store.mediaSessionIdentity else { return } + selectedShare = GridScreenShareSelection( + mediaSessionIdentity: identity, + participantIdentity: share.participantIdentity, + publicationID: share.publicationID, + displayName: InlineKit.User(from: avatar.user).displayName ) + }) + + GridCurrentRoomControls(store: store, spaceID: store.currentCall?.spaceID ?? spaceID) + if !store.callTransferEnabled { + Text("Grid is unavailable") + .font(.footnote) + .foregroundStyle(.secondary) } - ToolbarItem(placement: .topBarTrailing) { - Button("Done", systemImage: "xmark") { dismiss() } - .labelStyle(.iconOnly) - .accessibilityLabel("Close Grid") - } + } else { + ContentUnavailableView("Grid is unavailable", systemImage: "square.grid.2x2") } - .task(id: effectiveSpaceID) { - if let spaceID = effectiveSpaceID { await store.load(spaceID: spaceID) } + } + .padding(20) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .navigationTitle("Grid") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + GridSpacePicker( + homeSpaces: store.orderedHomeSpaces, + spaces: spaces, + selectedSpaceID: effectiveSpaceID, + onSelect: { selectedSpaceID = $0 } + ) + } + } + .task { await store.loadHome() } + .task(id: effectiveSpaceID) { + if let spaceID = effectiveSpaceID { + await store.load(spaceID: spaceID) + } + } + .onChange(of: actionFailure, initial: true) { _, failure in + if let failure { + presentedFailure = failure + store.clearLastError() } - .onChange(of: actionFailure, initial: true) { _, failure in - if let failure { - presentedFailure = failure - store.clearLastError() + } + .alert(presentedFailure?.title ?? "Unable to update Grid", isPresented: Binding( + get: { presentedFailure != nil }, + set: { + if !$0 { + presentedFailure = nil } } - .alert(presentedFailure?.title ?? "Unable to update Grid", isPresented: Binding( - get: { presentedFailure != nil }, - set: { if !$0 { presentedFailure = nil } } - ), presenting: presentedFailure) { failure in - if failure.canRetryAudio && store.hasLocalAdmission { - Button("Retry Audio") { - guard store.hasLocalAdmission, store.currentCall?.ownedByCurrentSession == true, - !InlineAudioSession.shared.isQuarantined else { return } - store.retryAudio() - } - .disabled(store.membershipMutationInFlight) - } else if failure == .update { - Button("Refresh") { - Task { - await store.loadHome() - if let spaceID = effectiveSpaceID { await store.load(spaceID: spaceID) } - } - } + ), presenting: presentedFailure) { failure in + if failure.canRetryAudio && store.hasLocalAdmission { + Button("Retry Audio") { + guard store.hasLocalAdmission, store.currentCall?.ownedByCurrentSession == true, + !InlineAudioSession.shared.isQuarantined else { return } + store.retryAudio() } - if let call = store.currentCall, call.ownedByCurrentSession { - Button("Leave Grid", role: .destructive) { - if let current = store.currentCall, current.ownedByCurrentSession { - store.leaveCurrentRoom(spaceID: current.spaceID) + .disabled(store.membershipMutationInFlight) + } else if failure == .update { + Button("Refresh") { + Task { + await store.loadHome() + if let spaceID = effectiveSpaceID { + await store.load(spaceID: spaceID) } } } - if failure == .recordingConflict || failure == .restartRequired { - Button("Close Grid", role: .cancel) { dismiss() } - } else { - Button("Cancel", role: .cancel) {} + } + if let call = store.currentCall, call.ownedByCurrentSession { + Button("Leave Grid", role: .destructive) { + if let current = store.currentCall, current.ownedByCurrentSession { + store.leaveCurrentRoom(spaceID: current.spaceID) + } } - } message: { failure in - Text(failure.message) } - .fullScreenCover(item: $selectedShare) { selection in - GridScreenShareViewer(store: store, selection: selection) + if failure == .recordingConflict || failure == .restartRequired { + Button("Close Grid", role: .cancel) { dismiss() } + } else { + Button("Cancel", role: .cancel) {} } + } message: { failure in + Text(failure.message) + } + .fullScreenCover(item: $selectedShare) { selection in + GridScreenShareViewer(store: store, selection: selection) } - .frame(idealWidth: 360, idealHeight: 280) } } @@ -166,15 +197,17 @@ private enum GridActionFailure: Equatable { var message: String { switch self { - case .recordingConflict: InlineAudioSessionError.recordingOwnsAudio.localizedDescription - case .restartRequired: InlineAudioSessionError.quarantined.localizedDescription - case .retirementPending: InlineAudioSessionError.retirementPending.localizedDescription - case .audio: String(localized: "Grid audio could not start. Try again.") - case .update: String(localized: "Grid could not be updated. Refresh and try again.") + case .recordingConflict: InlineAudioSessionError.recordingOwnsAudio.localizedDescription + case .restartRequired: InlineAudioSessionError.quarantined.localizedDescription + case .retirementPending: InlineAudioSessionError.retirementPending.localizedDescription + case .audio: String(localized: "Grid audio could not start. Try again.") + case .update: String(localized: "Grid could not be updated. Refresh and try again.") } } - var canRetryAudio: Bool { self == .audio || self == .retirementPending } + var canRetryAudio: Bool { + self == .audio || self == .retirementPending + } } private struct GridSpacePicker: View { @@ -365,7 +398,10 @@ private struct GridRoomAvatarOverflow: View { Button { onViewShare(avatar, share) } label: { - Label("View \(InlineKit.User(from: avatar.user).displayName)’s screen", systemImage: "rectangle.on.rectangle") + Label( + "View \(InlineKit.User(from: avatar.user).displayName)’s screen", + systemImage: "rectangle.on.rectangle" + ) } } } @@ -424,7 +460,7 @@ struct GridCurrentRoomControls: View { var body: some View { if let call = store.currentCall, call.spaceID == spaceID { - if call.ownedByCurrentSession && store.hasLocalAdmission { + if call.ownedByCurrentSession, store.hasLocalAdmission { GridLocalAudioControls(store: store, spaceID: spaceID) } else { HStack(spacing: 16) { @@ -487,7 +523,7 @@ private struct GridLocalAudioControls: View { } .frame(maxWidth: .infinity) .onChange(of: store.media.microphonePermission) { _, permission in - if requestedUnmute && (permission == .denied || permission == .restricted) { + if requestedUnmute, permission == .denied || permission == .restricted { audioAlert = .microphonePermission requestedUnmute = false } else if permission == .authorized { @@ -496,11 +532,17 @@ private struct GridLocalAudioControls: View { } .alert(alertTitle, isPresented: Binding( get: { audioAlert != nil }, - set: { if !$0 { audioAlert = nil } } + set: { + if !$0 { + audioAlert = nil + } + } )) { if audioAlert == .microphonePermission { Button("Open Settings") { - if let url = URL(string: UIApplication.openSettingsURLString) { openURL(url) } + if let url = URL(string: UIApplication.openSettingsURLString) { + openURL(url) + } } Button("Cancel", role: .cancel) {} } else { @@ -539,7 +581,9 @@ private struct GridLocalAudioControls: View { do { try await audio.setSpeakerPreferred(preferred) } catch { - if !(error is CancellationError) { audioAlert = .outputRoute } + if !(error is CancellationError) { + audioAlert = .outputRoute + } } } } label: { @@ -602,7 +646,7 @@ private struct GridRoomFlowLayout: Layout { var usedWidth: CGFloat = 0 for view in subviews { let size = view.sizeThatFits(.unspecified) - if x > 0 && x + size.width > width { + if x > 0, x + size.width > width { y += rowHeight + spacing x = 0 rowHeight = 0 @@ -620,7 +664,7 @@ private struct GridRoomFlowLayout: Layout { var rowHeight: CGFloat = 0 for view in subviews { let size = view.sizeThatFits(.unspecified) - if x > bounds.minX && x + size.width > bounds.maxX { + if x > bounds.minX, x + size.width > bounds.maxX { y += rowHeight + spacing x = bounds.minX rowHeight = 0 diff --git a/apple/InlineIOS/MainViews/HomeView.swift b/apple/InlineIOS/MainViews/HomeView.swift index 6f540f294..c5cff1f15 100644 --- a/apple/InlineIOS/MainViews/HomeView.swift +++ b/apple/InlineIOS/MainViews/HomeView.swift @@ -24,7 +24,6 @@ struct HomeView: View { @State private var text = "" @State private var searchModel: InlineSearchViewModel? - @State private var isGridPresented = false private var chatItems: [HomeChatItem] { let visibleChats = home.chats.filter { $0.dialog.archived != true } @@ -43,14 +42,12 @@ struct HomeView: View { router: router, realtimeState: realtimeState, notificationSettings: notificationSettings, - onOpenGrid: { isGridPresented = true } + onOpenGrid: { router.push(.grid(initialSpaceID: nil)) } ) } - .gridHomeEntry( - isPresented: $isGridPresented, - spaces: home.spaces.map(\.space), - isVisible: text.isEmpty - ) + .gridHomeEntry(isVisible: text.isEmpty) { + router.push(.grid(initialSpaceID: nil)) + } .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden() .onAppear { diff --git a/apple/InlineIOS/Navigation/AppContent.swift b/apple/InlineIOS/Navigation/AppContent.swift index 4aca72379..96404bf03 100644 --- a/apple/InlineIOS/Navigation/AppContent.swift +++ b/apple/InlineIOS/Navigation/AppContent.swift @@ -269,6 +269,7 @@ enum Destination: DestinationType, Codable { case chatMessage(peer: Peer, messageID: Int64) case voiceMessage(peer: Peer, messageID: Int64) case chatInfo(chatItem: SpaceChatItem) + case grid(initialSpaceID: Int64?) case spaceSettings(spaceId: Int64) case spaceIntegrations(spaceId: Int64) case integrationOptions(spaceId: Int64, provider: String) @@ -419,7 +420,7 @@ extension Destination { switch self { case let .chat(peer), let .externalChat(peer, _, _), let .chatMessage(peer, _), let .voiceMessage(peer, _): peer - case .chats, .archived, .spaces, .space, .chatInfo, .spaceSettings, + case .chats, .archived, .spaces, .space, .chatInfo, .grid, .spaceSettings, .spaceIntegrations, .integrationOptions, .createSpaceChat, .createThread, .createSpace: nil } From 6edc5ee27e4fca49fab04c6e04eacb7eed6d422e Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Thu, 8 Oct 2026 00:30:48 +0330 Subject: [PATCH 13/14] grid: persist each track final in provider order --- .../src/track-worker.test.ts | 186 +++++++++++++++++- .../grid-transcription/src/track-worker.ts | 12 +- 2 files changed, 193 insertions(+), 5 deletions(-) diff --git a/server/packages/grid-transcription/src/track-worker.test.ts b/server/packages/grid-transcription/src/track-worker.test.ts index 91e523117..d7f9bc004 100644 --- a/server/packages/grid-transcription/src/track-worker.test.ts +++ b/server/packages/grid-transcription/src/track-worker.test.ts @@ -42,9 +42,10 @@ function fixture(overrides: Partial = {}) { } function frame(speech: boolean): Int16Array { const data = new Int16Array(320); data[0] = speech ? 1 : 0; return data } async function utterance(value: ReturnType): Promise { + const committedBefore = value.commits.length value.worker.push(frame(true)) for (let index = 0; index < 28; index++) value.worker.push(frame(false)) - await until(() => value.commits.length > 0 || value.failures.length > 0) + await until(() => value.commits.length > committedBefore || value.failures.length > 0) await tick() } @@ -84,6 +85,157 @@ describe("per microphone finite turn owner", () => { expect(value.failures).toEqual([]) }) + test("capture continues while final persistence waits; graceful Stop drains finals in order", async () => { + const first = deferred() + const attempts: string[] = [] + const saved: FinalTurn[] = [] + let id = 0 + const value = fixture({ api: { + async admit() { return `segment-${++id}` }, + async final(turnId, text) { + attempts.push(turnId) + if (turnId === "segment-1") await first.promise + saved.push({ turnId, text }) + }, + } }) + await utterance(value) + value.final("segment-1", "First phrase.") + await utterance(value) + expect(value.commits).toEqual(["segment-1", "segment-2"]) + value.final("segment-2", "Second phrase.") + await tick() + expect(attempts).toEqual(["segment-1"]) + expect(saved).toEqual([]) + let drained = false + const stopping = value.worker.stop(true).then(() => { drained = true }) + await tick() + expect(drained).toBe(false) + first.resolve() + await stopping + expect(saved).toEqual([ + { turnId: "segment-1", text: "First phrase." }, + { turnId: "segment-2", text: "Second phrase." }, + ]) + expect(value.events).toContain("close") + expect(value.failures).toEqual([]) + }) + + test("destructive Stop interrupts graceful draining before a queued successor reaches the API", async () => { + const first = deferred() + const attempts: string[] = [] + let id = 0 + const value = fixture({ api: { + async admit() { return `segment-${++id}` }, + async final(turnId) { attempts.push(turnId); await first.promise }, + } }) + await utterance(value) + await utterance(value) + value.final("segment-1", "First phrase.") + value.final("segment-2", "Discard this queued phrase.") + const stopping = value.worker.stop(true) + await tick() + await value.worker.stop(false) + first.resolve() + await stopping + await tick(); await tick() + expect(attempts).toEqual(["segment-1"]) + expect(value.failures).toEqual([]) + }) + + test("a lost first response retries the same final before posting its successor", async () => { + const first = deferred() + const attempts: FinalTurn[] = [] + const saved: FinalTurn[] = [] + let id = 0 + const value = fixture({ api: { + async admit() { return `segment-${++id}` }, + async final(turnId, text) { + attempts.push({ turnId, text }) + if (attempts.length === 1) { await first.promise; throw new TranscriptionError("provider") } + saved.push({ turnId, text }) + }, + } }) + await utterance(value) + await utterance(value) + value.final("segment-1", "First phrase.") + value.final("segment-2", "Second phrase.") + await tick() + expect(attempts).toEqual([{ turnId: "segment-1", text: "First phrase." }]) + const stopping = value.worker.stop(true) + first.resolve() + await stopping + expect(attempts).toEqual([ + { turnId: "segment-1", text: "First phrase." }, + { turnId: "segment-1", text: "First phrase." }, + { turnId: "segment-2", text: "Second phrase." }, + ]) + expect(saved.map((turn) => turn.turnId)).toEqual(["segment-1", "segment-2"]) + expect(value.failures).toEqual([]) + }) + + test("exhausted final retries discard the queued successor", async () => { + const first = deferred() + const attempts: string[] = [] + let id = 0 + const value = fixture({ api: { + async admit() { return `segment-${++id}` }, + async final(turnId) { attempts.push(turnId); await first.promise; throw new TranscriptionError("provider") }, + } }) + await utterance(value) + await utterance(value) + value.final("segment-1", "First phrase.") + value.final("segment-2", "Must not pass the failed predecessor.") + first.resolve() + await until(() => value.failures.length === 1) + await value.worker.stop(true) + expect(attempts).toEqual(["segment-1", "segment-1"]) + expect(value.failures.map((failure) => failure.code)).toEqual(["provider"]) + }) + + test("run authority is rechecked before allowing a queued successor", async () => { + const first = deferred() + const attempts: string[] = [] + let id = 0 + const value = fixture({ api: { + async admit() { return `segment-${++id}` }, + async final(turnId) { attempts.push(turnId); await first.promise }, + } }) + await utterance(value) + await utterance(value) + value.final("segment-1", "First phrase.") + value.final("segment-2", "Must not persist after authority loss.") + value.revokeRun() + first.resolve() + await until(() => value.failures.length === 1) + expect(attempts).toEqual(["segment-1"]) + expect(value.failures[0]?.code).toBe("expired") + await value.worker.stop(false) + }) + + test("graceful Stop times out a held post and discards its queued successor", async () => { + const first = deferred() + const attempts: string[] = [] + let id = 0 + const value = fixture({ api: { + async admit() { return `segment-${++id}` }, + async final(turnId) { attempts.push(turnId); await first.promise }, + } }) + await utterance(value) + await utterance(value) + value.final("segment-1", "First phrase.") + value.final("segment-2", "Must not persist after the drain deadline.") + const began = performance.now() + await value.worker.stop(true) + const elapsed = performance.now() - began + expect(elapsed).toBeGreaterThanOrEqual(4_900) + expect(elapsed).toBeLessThan(6_000) + expect(value.events).toContain("close") + first.resolve() + await tick(); await tick() + expect(attempts).toEqual(["segment-1"]) + expect(value.failures).toEqual([]) + }, 7_000) + test("queued samples are discarded if membership changes before inference", async () => { const value = fixture() value.worker.push(frame(true)) @@ -109,6 +261,38 @@ describe("per microphone finite turn owner", () => { expect(value.audio).toHaveLength(0) }) + test("late admission retirement waits behind an earlier final during graceful Stop", async () => { + const first = deferred() + const admitted = deferred() + const attempts: string[] = [] + const saved: FinalTurn[] = [] + let admissions = 0 + const value = fixture({ api: { + async admit() { return ++admissions === 1 ? "segment-1" : admitted.promise }, + async final(turnId, text) { + attempts.push(turnId) + if (turnId === "segment-1") await first.promise + saved.push({ turnId, text }) + }, + } }) + await utterance(value) + value.final("segment-1", "First phrase.") + value.worker.push(frame(true)) + await until(() => admissions === 2) + const stopping = value.worker.stop(true) + admitted.resolve("segment-2") + await tick() + expect(attempts).toEqual(["segment-1"]) + first.resolve() + await stopping + expect(saved).toEqual([ + { turnId: "segment-1", text: "First phrase." }, + { turnId: "segment-2", text: "" }, + ]) + expect(value.commits).toEqual(["segment-1"]) + expect(value.failures).toEqual([]) + }) + test("late provider connect is closed after destructive stop and never sends PCM", async () => { const connected = deferred() let started = false diff --git a/server/packages/grid-transcription/src/track-worker.ts b/server/packages/grid-transcription/src/track-worker.ts index 561c53838..fe961f232 100644 --- a/server/packages/grid-transcription/src/track-worker.ts +++ b/server/packages/grid-transcription/src/track-worker.ts @@ -100,7 +100,7 @@ export class TrackWorker { const id = await this.options.api.admit(this.options.participantIdentity, this.options.trackSid, randomUUID()) // A late admission never grants permission to resume ingestion after a stop or leave. if (!this.accepting) { - if (this.graceful && !this.closed) await this.postEmpty(id) + if (this.graceful && !this.closed) this.trackPost(() => this.postEmpty(id)) return } this.options.assertRunAuthority() @@ -145,7 +145,7 @@ export class TrackWorker { if (!current) return this.assertFinalAuthority() if (!this.connection || current.samples === 0) { - this.trackPost(this.postEmpty(current.id)) + this.trackPost(() => this.postEmpty(current.id)) return } if (this.pending.size >= MAX_PENDING_TURNS) throw new TranscriptionError("overflow") @@ -171,7 +171,7 @@ export class TrackWorker { if (!pending || pending.posting) throw new TranscriptionError("protocol") pending.posting = true clearTimeout(pending.timer) - this.trackPost(this.postFinal(turn).finally(() => { + this.trackPost(() => this.postFinal(turn).finally(() => { this.pending.delete(turn.turnId) this.maybeDrained() })) @@ -191,7 +191,11 @@ export class TrackWorker { } } private async postEmpty(id: string): Promise { await this.postFinal({ turnId: id, text: "" }) } - private trackPost(operation: Promise): void { + private trackPost(post: () => Promise): void { + // Provider finals arrive in commit order. Retain that order through API retries + // using the existing bounded posting ownership, while capture keeps consuming. + const predecessor = [...this.posting].at(-1) + const operation = predecessor ? predecessor.then(post) : post() this.posting.add(operation) void operation.catch((error: unknown) => { if (!this.closed) this.fail(error) }).finally(() => { this.posting.delete(operation) From b65554f2d46e7ec483b17f78ebf17cbe07beb34d Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Thu, 8 Oct 2026 02:00:43 +0330 Subject: [PATCH 14/14] apple: preserve cold snapshot rollback and align history fixtures --- .../Transactions2/GetChatsTransaction.swift | 39 ++++++++--- .../InlineKitTests/AcknowledgementTests.swift | 11 ++- .../AnchoredMessageWindowTests.swift | 11 ++- .../ChatRepairReplacementTests.swift | 67 +++++++++++++++---- .../DialogTranslationMigrationTests.swift | 2 +- .../GetChatsHistoryFenceTests.swift | 29 ++++++++ .../GetChatsSnapshotTests.swift | 56 ++++++++++++---- .../MessagesSectionedViewModelTests.swift | 9 +-- .../InlineKitTests/RealtimeV2/SyncTests.swift | 10 +++ .../UserRepairSnapshotTests.swift | 23 ++++++- 10 files changed, 210 insertions(+), 47 deletions(-) diff --git a/apple/InlineKit/Sources/InlineKit/Transactions2/GetChatsTransaction.swift b/apple/InlineKit/Sources/InlineKit/Transactions2/GetChatsTransaction.swift index 7f3dff9aa..5a5e1e341 100644 --- a/apple/InlineKit/Sources/InlineKit/Transactions2/GetChatsTransaction.swift +++ b/apple/InlineKit/Sources/InlineKit/Transactions2/GetChatsTransaction.swift @@ -386,12 +386,19 @@ public struct GetChatsTransaction: Transaction2 { attemptedCount += 1 if coldBootstrapChatIDs.contains(pending.chat.id) { - if let state = try applyColdChatSnapshot( + switch try applyColdChatSnapshot( pending, messages: messagesByChatID[pending.chat.id] ?? [], in: db, failures: &failures ) { - seededStates[pending.bucketKey] = state - continue + case let .seeded(state): + seededStates[pending.bucketKey] = state + continue + case .cursorRejected: + // Cursor admission owns the whole cold snapshot savepoint. + // A metadata retry must not resurrect its rolled-back rows. + continue + case .metadataOnly: + break } // A malformed/missing body must not certify a cursor. Metadata is // still useful, with unknown holes and no last-message pointer. @@ -460,18 +467,24 @@ public struct GetChatsTransaction: Transaction2 { ) } + private enum ColdChatSnapshotResult { + case seeded(BucketState) + case metadataOnly + case cursorRejected + } + private static func applyColdChatSnapshot( _ pending: PendingChat, messages: [InlineProtocol.Message], in db: Database, failures: inout SnapshotFailureAccumulator - ) throws -> BucketState? { + ) throws -> ColdChatSnapshotResult { guard let sequence = pending.bucketSequence, sequence >= 0, let lastMsgId = pending.lastMsgId, 1 ... MessageHistoryHole.positiveMessageIDMax ~= lastMsgId - else { return nil } + else { return .metadataOnly } let lastRows = messages.filter { $0.id == lastMsgId } guard lastRows.count == 1 else { failures.record(.lastMessages) - return nil + return .metadataOnly } do { try HistoryPageReducer.validateMessages( @@ -479,32 +492,38 @@ public struct GetChatsTransaction: Transaction2 { ) } catch { failures.record(.messages) - return nil + return .metadataOnly } var state: BucketState? + var phase = SnapshotImportPhase.chats do { try db.inSavepoint { var chat = pending.chat try chat.saveFull(db) // Catalog users/sidecars must already satisfy the full row. Do not // fabricate references for this one-message cold snapshot. + phase = .messages _ = try Message.save(db, protocolMessage: lastRows[0], authoritativeSnapshot: true) chat.lastMsgId = lastMsgId + phase = .lastMessages try chat.saveFull(db) + phase = .chats try Acknowledgement.save( db, cursors: pending.protocolChat.acknowledgements.cursors, chatId: chat.id, publishChanges: true ) + phase = .chatCursors state = try GRDBSyncStorage.seedSnapshotBucketState(for: pending.bucketKey, seq: sequence, in: db) // The last-message row supplies a preview, never range coverage. return .commit } } catch { guard isRecoverableRecordError(error) || error is HistoryPageAdmissionError else { throw error } - failures.record(.messages) - return nil + failures.record(phase) + return phase == .chatCursors ? .cursorRejected : .metadataOnly } - return state + guard let state else { return .metadataOnly } + return .seeded(state) } private static func applyPristineChatSnapshot( diff --git a/apple/InlineKit/Tests/InlineKitTests/AcknowledgementTests.swift b/apple/InlineKit/Tests/InlineKitTests/AcknowledgementTests.swift index 16ba624de..767a9ebe3 100644 --- a/apple/InlineKit/Tests/InlineKitTests/AcknowledgementTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/AcknowledgementTests.swift @@ -215,11 +215,15 @@ struct AcknowledgementTests { } } - @Test func coldThreadHistoryHydratesCursorBeforeFirstProjection() throws { + @Test func admittedThreadHistoryHydratesCursorBeforeFirstProjection() throws { let queue = try DatabaseQueue(configuration: AppDatabase.makeConfiguration(passphrase: "123")) _ = try AppDatabase(queue) try queue.write { db in + try InlineKit.Chat(id: 100, date: Date(), type: .thread, title: "Cold thread", spaceId: nil).insert(db) + var transaction = GetChatHistoryTransaction(peer: .thread(id: 100)) + transaction.context.admissionToken = try HistoryPageAdmissionToken.capture(db, chatId: 100) var response = InlineProtocol.GetChatHistoryResult() + response.seq = 0 response.messages = [.with { $0.id = 10 $0.chatID = 100 @@ -232,10 +236,10 @@ struct AcknowledgementTests { $0.chatID = 100; $0.userID = 1; $0.maxID = 10; $0.revision = 7 $0.user = .with { $0.id = 1; $0.firstName = "Actor" } }] - #expect(try InlineKit.Chat.fetchCount(db) == 0) + #expect(try InlineKit.Chat.fetchCount(db) == 1) try GetChatHistoryTransaction.apply( response, - context: GetChatHistoryTransaction(peer: .thread(id: 100)).context, + context: transaction.context, db: db ) let firstFrame = try #require(try FullMessage.queryRequest(currentUserId: 1).fetchOne(db)) @@ -258,6 +262,7 @@ struct AcknowledgementTests { ) try message.saveMessage(db) } + try MessageHistoryCoverageStore.subtract(db, chatId: 100, lowerId: 10, upperId: 14) for cleared in [false, true] { let cursor = InlineProtocol.ChatAcknowledgement.with { $0.chatID = 100; $0.userID = 1; $0.maxID = 12 diff --git a/apple/InlineKit/Tests/InlineKitTests/AnchoredMessageWindowTests.swift b/apple/InlineKit/Tests/InlineKitTests/AnchoredMessageWindowTests.swift index 7379285c1..3a1db5961 100644 --- a/apple/InlineKit/Tests/InlineKitTests/AnchoredMessageWindowTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/AnchoredMessageWindowTests.swift @@ -20,12 +20,21 @@ struct AnchoredMessageWindowTests { let fixture = try await Fixture() let model = fixture.model(rows: Array(fixture.messages[9 ... 19]), limit: nil) defer { model.dispose() } - model.setHistoryAnchor(15) // ignored without the experimental window limit fixture.publisher.publisher.send(.add(.init(messages: [fixture.messages[99]], peer: .thread(id: 1)))) #expect(model.messages.last?.message.messageId == 100) #expect(model.messages.count == 12) } + @Test @MainActor + func unboundedHistoricalWindowDoesNotAppendTheLiveTail() async throws { + let fixture = try await Fixture() + let model = fixture.model(rows: Array(fixture.messages[9 ... 19]), limit: nil) + defer { model.dispose() } + model.setHistoryAnchor(15) + fixture.publisher.publisher.send(.add(.init(messages: [fixture.messages[99]], peer: .thread(id: 1)))) + #expect(model.messages.map(\.message.messageId) == Array(Int64(10) ... 20)) + } + @Test @MainActor func aroundAndLatestReplacementKeepPreparedThreadContext() async throws { let fixture = try await Fixture() diff --git a/apple/InlineKit/Tests/InlineKitTests/ChatRepairReplacementTests.swift b/apple/InlineKit/Tests/InlineKitTests/ChatRepairReplacementTests.swift index 6dcdcc29b..c9654729f 100644 --- a/apple/InlineKit/Tests/InlineKitTests/ChatRepairReplacementTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/ChatRepairReplacementTests.swift @@ -210,9 +210,11 @@ struct ChatRepairReplacementTests { #expect(dialog.readInboxMaxId == 3) #expect(dialog.unreadCount == 9) #expect(dialog.open) - #expect(try MessageHistoryCoverageStore.holes(db, chatId: 7) == [ - MessageHistoryHole(chatId: 7, lowerId: 1, upperId: 10), - ]) + for scope in MessageHistoryScope.allCases { + #expect(try MessageHistoryCoverageStore.holes(db, chatId: 7, scope: scope) == [ + MessageHistoryHole(chatId: 7, scope: scope, lowerId: 1, upperId: MessageHistoryHole.positiveMessageIDMax), + ]) + } } } @@ -345,8 +347,8 @@ struct ChatRepairReplacementTests { } } - @Test("a parent reference requires the exact parent message row") - func missingParentMessageDoesNotAdvance() async throws { + @Test("valid parent coordinates survive an unavailable parent message body") + func parentCoordinatesOutliveMissingMessage() async throws { let (queue, engine) = try makeRepairDatabase(cursor: 1, title: "Stale") try await queue.write { (db: Database) throws in try Chat( @@ -361,7 +363,7 @@ struct ChatRepairReplacementTests { snapshot.chat.parentChatID = 8 snapshot.chat.parentMessageID = 55 - let missing = await engine.applyChatRepair(ChatRepairSnapshot( + let withoutBody = await engine.applyChatRepair(ChatRepairSnapshot( peer: chatPeer(7), chat: snapshot, pinnedMessages: [], @@ -369,12 +371,16 @@ struct ChatRepairReplacementTests { mutationToken: accountToken(), reason: "missing-parent-message" )) - #expect(missing == nil) + #expect(withoutBody?.seq == 5) try await queue.read { (db: Database) throws in - #expect(try Chat.fetchOne(db, id: 7)?.title == "Stale") + let repaired = try #require(try Chat.fetchOne(db, id: 7)) + #expect(repaired.title == "Repaired") + #expect(repaired.parentChatId == 8) + #expect(repaired.parentMessageId == 55) + #expect(try Message.filter(Message.Columns.chatId == 8).fetchCount(db) == 0) #expect(try DbBucketState .filter(DbBucketState.Columns.entityId == BucketKey.chat(peer: chatPeer(7)).getEntityId()) - .fetchOne(db)?.seq == 1) + .fetchOne(db)?.seq == 5) } try await queue.write { (db: Database) throws in @@ -406,24 +412,56 @@ struct ChatRepairReplacementTests { } } - @Test("an exact authoritative anchor resolves the parent dependency") - func anchorMessageResolvesParentReference() async throws { + @Test("child parent coordinates never authorize an anchor body or overlay", arguments: [false, true]) + func childSnapshotCannotAuthorizeParentAnchor(anchorMatches: Bool) async throws { let (queue, engine) = try makeRepairDatabase(cursor: 1, title: "Stale") var snapshot = repairedChatResult() snapshot.chat.parentChatID = 8 snapshot.chat.parentMessageID = 55 - snapshot.anchorMessage = protocolMessage(id: 55, chatID: 8, fromID: 1) + snapshot.anchorMessage = protocolMessage(id: anchorMatches ? 55 : 54, chatID: 8, fromID: 1) + + try await queue.write { (db: Database) throws in + try Chat(id: 8, date: Date(timeIntervalSince1970: 10), type: .thread, title: "Parent", spaceId: nil).insert(db) + _ = try GRDBSyncStorage.seedSnapshotBucketState(for: .chat(peer: chatPeer(8)), seq: 9, in: db) + } + let withoutParentAdmission = await engine.applyChatRepair(ChatRepairSnapshot( + peer: chatPeer(7), chat: snapshot, pinnedMessages: [], + targetState: BucketState(date: 20, seq: 5), mutationToken: accountToken(), + reason: "unadmitted-parent-anchor" + )) + #expect(withoutParentAdmission?.seq == 5) + try await queue.read { (db: Database) throws in + #expect(try Chat.fetchOne(db, id: 7)?.title == "Repaired") + #expect(try Chat.fetchOne(db, id: 7)?.parentMessageId == 55) + #expect(try Message.filter(Message.Columns.chatId == 8).fetchCount(db) == 0) + #expect(try DbBucketState + .filter(DbBucketState.Columns.entityId == BucketKey.chat(peer: chatPeer(7)).getEntityId()) + .fetchOne(db)?.seq == 5) + } + try await queue.write { (db: Database) throws in + let token = try HistoryPageAdmissionToken.capture(db, chatId: 8) + var parentResponse = InlineProtocol.GetMessagesResult() + parentResponse.seq = 9 + parentResponse.messages = [protocolMessage(id: 55, chatID: 8, fromID: 1)] + try GetMessagesTransaction.apply( + parentResponse, + context: .init(peer: .thread(id: 8), messageIds: [55], admissionToken: token), + db: db + ) + } + snapshot.anchorMessage.message = "An unadmitted parent overlay" + snapshot.chat.seq = 6 let committed = await engine.applyChatRepair(ChatRepairSnapshot( peer: chatPeer(7), chat: snapshot, pinnedMessages: [], - targetState: BucketState(date: 20, seq: 5), + targetState: BucketState(date: 20, seq: 6), mutationToken: accountToken(), reason: "anchor-resolves-parent" )) - #expect(committed?.seq == 5) + #expect(committed?.seq == 6) try await queue.read { (db: Database) throws in let repaired = try #require(try Chat.fetchOne(db, id: 7)) #expect(repaired.parentChatId == 8) @@ -431,6 +469,7 @@ struct ChatRepairReplacementTests { #expect(try Message .filter(Message.Columns.chatId == 8 && Message.Columns.messageId == 55) .fetchCount(db) == 1) + #expect(try Message.fetchOne(db, key: ["chatId": 8, "messageId": 55])?.text == "pinned") } } diff --git a/apple/InlineKit/Tests/InlineKitTests/DialogTranslationMigrationTests.swift b/apple/InlineKit/Tests/InlineKitTests/DialogTranslationMigrationTests.swift index 1907ed6c0..80e32069e 100644 --- a/apple/InlineKit/Tests/InlineKitTests/DialogTranslationMigrationTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/DialogTranslationMigrationTests.swift @@ -23,7 +23,7 @@ struct DialogTranslationMigrationTests { INSERT INTO dialog (id, peerUserId, translationEnabled) VALUES (8002, 8002, 0); INSERT INTO dialog (id, peerUserId, translationEnabled) VALUES (8003, 8003, NULL); INSERT INTO chat (id, date) VALUES (9001, '2026-09-05 00:00:00'); - INSERT INTO message (messageId, chatId, date, text) VALUES (1, 9001, '2026-09-05 00:00:00', 'Original'); + INSERT INTO message (messageId, chatId, fromId, date, text) VALUES (1, 9001, 8001, '2026-09-05 00:00:00', 'Original'); INSERT INTO translation (messageId, chatId, date, language, translation) VALUES (1, 9001, '2026-09-05 00:00:00', 'en', 'Preserved translation'); """) diff --git a/apple/InlineKit/Tests/InlineKitTests/GetChatsHistoryFenceTests.swift b/apple/InlineKit/Tests/InlineKitTests/GetChatsHistoryFenceTests.swift index c91f6258f..d79f3d570 100644 --- a/apple/InlineKit/Tests/InlineKitTests/GetChatsHistoryFenceTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/GetChatsHistoryFenceTests.swift @@ -115,6 +115,35 @@ struct GetChatsHistoryFenceTests { } } + @Test("cold cursor rejection rolls back its model preview and acknowledgements") + func coldCatalogCursorFailureIsAtomic() throws { + let queue = try makeDatabase() + try queue.write { (db: Database) throws in + let context = try GetChatsTransaction.Context(expectedRemovalRevision: SyncRemovalRevision.read(db)) + try db.execute(sql: """ + CREATE TRIGGER reject_cold_cursor BEFORE INSERT ON sync_bucket_state + WHEN NEW.bucketType = 1 AND NEW.entityId = -10 + BEGIN SELECT RAISE(ABORT, 'reject cold cursor'); END; + """) + var result = page() + result.chats[0].acknowledgements.cursors = [.with { + $0.chatID = 10 + $0.userID = 1 + $0.maxID = 50 + $0.revision = 1 + }] + let imported = try GetChatsTransaction.applyCatalog( + result, context: context, allowedChatIDs: [10], in: db + ) + #expect(imported.failures == [.init(phase: .chatCursors, count: 1)]) + #expect(imported.seededStates.isEmpty) + #expect(try Chat.fetchOne(db, id: 10) == nil) + #expect(try Message.fetchCount(db) == 0) + #expect(try Acknowledgement.fetchCount(db) == 0) + #expect(try DbBucketState.fetchCount(db) == 0) + } + } + @Test("cold bootstrap requires both its dispatch removal fence and history permission", arguments: [false, true]) func coldCatalogAdmissionFence(removalChanged: Bool) throws { let queue = try makeDatabase() diff --git a/apple/InlineKit/Tests/InlineKitTests/GetChatsSnapshotTests.swift b/apple/InlineKit/Tests/InlineKitTests/GetChatsSnapshotTests.swift index 7d2123c80..2eb211397 100644 --- a/apple/InlineKit/Tests/InlineKitTests/GetChatsSnapshotTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/GetChatsSnapshotTests.swift @@ -29,7 +29,8 @@ struct GetChatsSnapshotTests { ] try queue.write { (db: Database) throws in - let imported = try GetChatsTransaction.applySnapshot(result, in: db) + addColdPreviews(to: &result, chatIDs: [10, 11]) + let imported = try GetChatsTransaction.applySnapshot(result, coldBootstrapChatIDs: [10, 11], in: db) #expect(imported.failures.isEmpty) #expect(imported.seededStates.count == 2) @@ -53,7 +54,8 @@ struct GetChatsSnapshotTests { ] try queue.write { (db: Database) throws in - let imported = try GetChatsTransaction.applySnapshot(result, in: db) + addColdPreviews(to: &result, chatIDs: [10]) + let imported = try GetChatsTransaction.applySnapshot(result, coldBootstrapChatIDs: [10], in: db) #expect(failureCount(in: imported, phase: .chats) == 4) #expect(try Chat.fetchAll(db).map(\.id) == [10]) @@ -73,6 +75,8 @@ struct GetChatsSnapshotTests { result.spaces = [makeSpace(id: 1, seq: 7), makeSpace(id: 2, seq: 8)] result.users = [makeUser(id: 1), makeUser(id: 2)] result.chats = [makeChat(id: 10, seq: 10), makeChat(id: 20, seq: 20)] + result.chats[0].lastMsgID = 1 + result.chats[1].lastMsgID = 2 result.messages = [ makeMessage(id: 1, chatID: 10, fromID: 1), makeMessage(id: 2, chatID: 20, fromID: 1), @@ -83,6 +87,7 @@ struct GetChatsSnapshotTests { let imported = try GetChatsTransaction.applySnapshot( result, userProjectionAdmission: .alreadyValidated, + coldBootstrapChatIDs: [10, 20], in: db ) @@ -114,7 +119,7 @@ struct GetChatsSnapshotTests { result.messages = [makeMessage(id: 1, chatID: 10, fromID: 1)] try queue.write { (db: Database) throws in - let imported = try GetChatsTransaction.applySnapshot(result, in: db) + let imported = try GetChatsTransaction.applySnapshot(result, coldBootstrapChatIDs: [10, 20], in: db) #expect(failureCount(in: imported, phase: .lastMessages) == 1) #expect(try Chat.fetchOne(db, key: 10)?.lastMsgId == 1) @@ -137,6 +142,7 @@ struct GetChatsSnapshotTests { var result = InlineProtocol.GetChatsResult() result.chats = [makeChat(id: 10, seq: 10), invalidChat] + result.chats[0].lastMsgID = 1 result.messages = [invalidMessage] result.dialogs = [makeDialog(chatID: 10), InlineProtocol.Dialog()] @@ -144,6 +150,7 @@ struct GetChatsSnapshotTests { let imported = try GetChatsTransaction.applySnapshot( result, userProjectionAdmission: .alreadyValidated, + coldBootstrapChatIDs: [10], in: db ) @@ -180,7 +187,8 @@ struct GetChatsSnapshotTests { result.chats = [makeChat(id: 2, spaceID: 1, seq: 9)] try queue.write { (db: Database) throws in - let imported = try GetChatsTransaction.applySnapshot(result, in: db) + addColdPreviews(to: &result, chatIDs: [2]) + let imported = try GetChatsTransaction.applySnapshot(result, coldBootstrapChatIDs: [2], in: db) let spaceKey = BucketKey.space(id: 1) let chatKey = BucketKey.chat(peer: makeChatPeer(id: 2)) @@ -247,7 +255,7 @@ struct GetChatsSnapshotTests { #expect(try Chat.fetchOne(db, key: 2)?.title == "Local Chat") #expect(try Chat.fetchOne(db, key: 2)?.lastMsgId == nil) - #expect(try Message.fetchOne(db, key: ["chatId": 2, "messageId": 1]) != nil) + #expect(try Message.fetchOne(db, key: ["chatId": 2, "messageId": 1]) == nil) #expect(try Dialog.fetchCount(db) == 1) #expect(try bucketState(for: chatKey, in: db)?.seq == 10) #expect(imported.seededStates.isEmpty) @@ -255,7 +263,7 @@ struct GetChatsSnapshotTests { } } - @Test("ordinary catalogs expose only pristine seeds to sync actors") + @Test("cold catalogs expose only coherent pristine seeds to sync actors") func ordinaryCatalogActorStatesExcludeRepairTargets() throws { let queue = try makeInMemoryDB() let populatedKey = BucketKey.chat(peer: makeChatPeer(id: 2)) @@ -270,7 +278,8 @@ struct GetChatsSnapshotTests { makeChat(id: 2, seq: 30, title: "Repair Target"), makeChat(id: 3, seq: 20, title: "Pristine Seed"), ] - let imported = try GetChatsTransaction.applySnapshot(result, in: db) + addColdPreviews(to: &result, chatIDs: [3]) + let imported = try GetChatsTransaction.applySnapshot(result, coldBootstrapChatIDs: [3], in: db) #expect(imported.catchUpTargets[populatedKey] == 30) #expect(imported.seededStates[pristineKey]?.seq == 20) @@ -339,7 +348,8 @@ struct GetChatsSnapshotTests { ] try queue.write { (db: Database) throws in - let imported = try GetChatsTransaction.applySnapshot(result, in: db) + addColdPreviews(to: &result, chatIDs: [2]) + let imported = try GetChatsTransaction.applySnapshot(result, coldBootstrapChatIDs: [2], in: db) #expect(imported.failures.isEmpty) #expect(try Space.fetchOne(db, key: 1)?.name == "Fresh Space") @@ -413,7 +423,8 @@ struct GetChatsSnapshotTests { ] try queue.write { (db: Database) throws in - let imported = try GetChatsTransaction.applySnapshot(result, in: db) + addColdPreviews(to: &result, chatIDs: [2]) + let imported = try GetChatsTransaction.applySnapshot(result, coldBootstrapChatIDs: [2], in: db) #expect(failureCount(in: imported, phase: .spaceCursors) == 1) #expect(failureCount(in: imported, phase: .chatCursors) == 1) @@ -539,9 +550,11 @@ struct GetChatsSnapshotTests { result.dialogs = [makeDialog(chatID: 20)] try queue.write { (db: Database) throws in + addColdPreviews(to: &result, chatIDs: [20]) let imported = try GetChatsTransaction.applySnapshot( result, userProjectionAdmission: .alreadyValidated, + coldBootstrapChatIDs: [20], in: db ) @@ -690,7 +703,8 @@ struct GetChatsSnapshotTests { result.messages = [makeMessage(id: 1, chatID: 20, fromID: 1)] try queue.write { (db: Database) throws in - let imported = try GetChatsTransaction.applySnapshot(result, in: db) + addColdPreviews(to: &result, chatIDs: [10, 20]) + let imported = try GetChatsTransaction.applySnapshot(result, coldBootstrapChatIDs: [10, 20], in: db) #expect(failureCount(in: imported, phase: .chatCursors) == 1) #expect(try Chat.fetchOne(db, key: 20) == nil) @@ -904,7 +918,7 @@ struct GetChatsSnapshotTests { #expect(imported.failures.isEmpty) #expect(imported.catchUpTargets.isEmpty) #expect(imported.seededStates[.space(id: 1)]?.seq == 6) - #expect(imported.seededStates[.chat(peer: makeChatPeer(id: 10))]?.seq == 6) + #expect(imported.seededStates[.chat(peer: makeChatPeer(id: 10))] == nil) #expect(imported.retiredBucketKeys == Set([ .space(id: 2), .chat(peer: makeChatPeer(id: 20)), @@ -915,7 +929,7 @@ struct GetChatsSnapshotTests { #expect(try Space.fetchOne(db, key: 1)?.name == "Rebased Space") #expect(try Chat.fetchOne(db, key: 10)?.title == "Rebased Chat") #expect(try bucketState(for: .space(id: 1), in: db)?.seq == 6) - #expect(try bucketState(for: .chat(peer: makeChatPeer(id: 10)), in: db)?.seq == 6) + #expect(try bucketState(for: .chat(peer: makeChatPeer(id: 10)), in: db)?.seq == 5) #expect(try bucketState(for: .space(id: 2), in: db)?.seq == 5) #expect(try bucketState(for: .chat(peer: makeChatPeer(id: 20)), in: db)?.seq == 5) #expect(try Space.catalogActive().fetchAll(db).map(\.id) == [1]) @@ -932,7 +946,7 @@ struct GetChatsSnapshotTests { #expect(try DialogFolder.fetchOne(db, key: 1) != nil) #expect(try DialogFolder.fetchOne(db, key: 2) == nil) #expect(try MessageHistoryCoverageStore.holes(db, chatId: 10) == [ - MessageHistoryHole(chatId: 10, lowerId: 1, upperId: 4), + MessageHistoryHole(chatId: 10, lowerId: 1, upperId: MessageHistoryHole.positiveMessageIDMax), ]) } } @@ -984,6 +998,22 @@ struct GetChatsSnapshotTests { } } + /// Fixtures that exercise cold seeding must supply the exact last-message + /// preview; ordinary metadata catalogs deliberately leave this opt-in absent. + private func addColdPreviews(to result: inout InlineProtocol.GetChatsResult, chatIDs: Set) { + if !result.users.contains(where: { $0.id == 1 }) { + result.users.append(makeUser(id: 1)) + } + for index in result.chats.indices where chatIDs.contains(result.chats[index].id) { + if !result.chats[index].hasLastMsgID { result.chats[index].lastMsgID = 1 } + let chatID = result.chats[index].id + let lastID = result.chats[index].lastMsgID + if !result.messages.contains(where: { $0.chatID == chatID && $0.id == lastID }) { + result.messages.append(makeMessage(id: lastID, chatID: chatID, fromID: 1)) + } + } + } + private func installRejectingTriggers(_ db: Database) throws { try db.execute(sql: """ CREATE TRIGGER reject_test_space BEFORE INSERT ON space diff --git a/apple/InlineKit/Tests/InlineKitTests/MessagesSectionedViewModelTests.swift b/apple/InlineKit/Tests/InlineKitTests/MessagesSectionedViewModelTests.swift index 1dd1fe31f..ab2d14a70 100644 --- a/apple/InlineKit/Tests/InlineKitTests/MessagesSectionedViewModelTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/MessagesSectionedViewModelTests.swift @@ -5,7 +5,7 @@ import Testing @Suite("MessagesSectionedViewModel Ordering Tests") struct MessagesSectionedViewModelOrderingTests { - @Test("sectioned projection exposes the immutable history coverage snapshot") + @Test("sectioned projection exposes the contiguous window and its immutable coverage") @MainActor func projectsHistoryCoverage() { let date = Date(timeIntervalSince1970: 1_700_000_000) @@ -23,9 +23,9 @@ struct MessagesSectionedViewModelOrderingTests { peerUserId: peer.id ) let coverage = MessageHistoryCoverageProjection( - messages: [older, newer], + messages: [newer], holes: [MessageHistoryHole(chatId: 1, lowerId: 11, upperId: 19)], - olderCandidateMessageID: nil, + olderCandidateMessageID: 10, newerCandidateMessageID: nil ) let viewModel = MessagesSectionedViewModel( @@ -41,6 +41,7 @@ struct MessagesSectionedViewModelOrderingTests { ) #expect(viewModel.historyCoverage == coverage) + #expect(viewModel.messages.map(\.message.messageId) == [20]) #expect(!viewModel.isCertifiedHistoryContinuation(between: 10, and: 20)) } @@ -61,7 +62,7 @@ struct MessagesSectionedViewModelOrderingTests { ) let initialState = MessagesProgressiveViewModel.InitialState( messages: [old, boundary, visible, pending], - loadedWindowMetadata: testSectionLoadedWindowMetadata(messages: [old, boundary, visible, pending]) + loadedWindowMetadata: .init(messages: [old, boundary, visible, pending], holes: []) ) let viewModel = MessagesSectionedViewModel( diff --git a/apple/InlineKit/Tests/InlineKitTests/RealtimeV2/SyncTests.swift b/apple/InlineKit/Tests/InlineKitTests/RealtimeV2/SyncTests.swift index 544751dbe..1c8a8d41f 100644 --- a/apple/InlineKit/Tests/InlineKitTests/RealtimeV2/SyncTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/RealtimeV2/SyncTests.swift @@ -2837,8 +2837,18 @@ final class SyncTests { chat.title = "Recovered chat" chat.peerID = makeChatPeer(chatId: 8) chat.seq = 7 + chat.lastMsgID = 1 var chats = InlineProtocol.GetChatsResult() chats.chats = [chat] + chats.users = [me.user] + chats.messages = [.with { + $0.id = 1 + $0.chatID = 8 + $0.peerID = makeChatPeer(chatId: 8) + $0.fromID = 1 + $0.date = 90 + $0.message = "Recovered preview" + }] var settings = InlineProtocol.GetUserSettingsResult() settings.userSettings = .init() var updatedUser = InlineProtocol.UpdateUpdatedUser() diff --git a/apple/InlineKit/Tests/InlineKitTests/UserRepairSnapshotTests.swift b/apple/InlineKit/Tests/InlineKitTests/UserRepairSnapshotTests.swift index d2da939e0..82b4e89d8 100644 --- a/apple/InlineKit/Tests/InlineKitTests/UserRepairSnapshotTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/UserRepairSnapshotTests.swift @@ -137,8 +137,12 @@ struct UserRepairSnapshotTests { firstChat.title = "First import" firstChat.peerID = chatPeer(8) firstChat.seq = 10 + firstChat.lastMsgID = 1 var firstChats = InlineProtocol.GetChatsResult() firstChats.chats = [firstChat] + firstChats.users = [firstMe.user] + firstChats.messages = [coldPreview(chatID: 8)] + let removalRevision = try await queue.read { db in try SyncRemovalRevision.read(db) } #expect(await engine.persistUserBootstrapProjection(.init( projection: .me(firstMe), @@ -154,7 +158,8 @@ struct UserRepairSnapshotTests { guard case let .chats(persistence)? = await engine.persistUserBootstrapProjection(.init( projection: .chats(firstChats), checkpointState: checkpoint, - mutationToken: accountToken() + mutationToken: accountToken(), + catalogHistoryAdmission: .init(expectedRemovalRevision: removalRevision, allowedChatIDs: [8]) )) else { Issue.record("Expected the catalog projection to persist") return @@ -845,6 +850,7 @@ struct UserRepairSnapshotTests { validChat.title = "Healthy" validChat.peerID = chatPeer(8) validChat.seq = 10 + validChat.lastMsgID = 1 var chats = InlineProtocol.GetChatsResult() chats.chats = [invalidChat, validChat] @@ -854,6 +860,9 @@ struct UserRepairSnapshotTests { var me = InlineProtocol.GetMeResult() me.user = user + chats.users = [user] + chats.messages = [coldPreview(chatID: 8)] + let removalRevision = try await queue.read { db in try SyncRemovalRevision.read(db) } let committed = await engine.applyUserRepair(UserRepairSnapshot( chats: chats, me: me, @@ -862,6 +871,7 @@ struct UserRepairSnapshotTests { replayThroughState: BucketState(date: 200, seq: 50), targetState: BucketState(date: 200, seq: 50), mutationToken: accountToken(), + catalogHistoryAdmission: .init(expectedRemovalRevision: removalRevision, allowedChatIDs: [8]), replacesActiveCatalog: true, reason: "invalid-snapshot-test" )) @@ -1042,6 +1052,17 @@ struct UserRepairSnapshotTests { } } + private func coldPreview(chatID: Int64) -> InlineProtocol.Message { + .with { + $0.id = 1 + $0.chatID = chatID + $0.peerID = chatPeer(chatID) + $0.fromID = 42 + $0.date = 20 + $0.message = "Cold preview" + } + } + private func userSettingsResult() -> InlineProtocol.GetUserSettingsResult { var result = InlineProtocol.GetUserSettingsResult() result.userSettings = .init()

      S*ImlFzx(a`o-Lkc2PRE zB!;}dn2(2dtcHo}6RDB)=FK3#tHKb3D+hBSWn z2V=z;YsX0_48F$ad1B#xkr=1pG@rTBu!0v2WT@*!x*05Z7{!HlnQg74CraNnbe zPF*mCne=@-t!`{5na&SE&io|I-KkFZ=V&pmYb4nl?{pw7*qa0uE2H8S5RJw?KC|C?f zw7lU_%tmsc{Si6W%#ve$5=`Y&6^!gjrnhVa)Rga2n|{oPp|NUs{xbzO=mb2tFcnsW zhuZ$s7AND5r{LFyqeT4GI&2#PSo!5BZZTW}r)##LmZ~^D8`7cXJrR)5R1ec5TA<VBahPAZop&Mo>Jz(WC zGw77`o3!|&CsF&p4h#2AL+eZ>ZtLU{_zj|x+1uZ=eNnz>M#-$=T;WVmCu z6OqhtLlbpL(D;t*C!57 z=uq{+Y{x*n&`?Z1I;UW3_cXpQ?8N%N2KruC4ZBVF_rL`=%5rRx=FW?LgP zy!VP!bRHwOUB#g9tQ%P8=YsGC1N-@2e3;jA{IZw|)Ef{>b=Ss0yIB?6Z+;#>2Hl~Wmaga)D359DF3{&=Ieh(69D+P` z_&$RI30rwyP>`xbACD+-6O^Cfg(E)LF0g}&u@rmGRFSj4Glg~|KInKuf(y$qfjtuy zz~%wK^nnM&pzs5ZwfIZD=T#CP4LuwErcun6&rfNnXA4=E7Xil&-jVBCS8=ftGAeWF*vDc+YZb$i2}<=H5CvTBTKo&#E{%|NBBFy#Dx;kr8o zIQo$#wVca)fe+XV=0A_5nRG}Xwzr*h)XA`tN@5TbaG&mJ;klWI#LzBMgq!l%k1DZq zh}a!_=1}rQ=ye+=5>5utyIhO0Hu^)f{^j#-v@tkINfrb>QJ~{w0h8{TVWy-DEI8v2 zG0((c!`xupd3-$m+LcR=>AfPiW!j0k$T%jwl)r}+TH#is0N8hpqXhyMO?IW@#unZ& zefc`{uL#C*v={k7EBC5fA2k=eLajN8Wbx`w2z`7PzWy=dIq>=9Ug|+uJ@7(MP&k(8 zHBBL58$+o6{jg2%`a-98>p<`62U?=0i<|URL2>aa zy#MAb$TCsnTA>t&GbZBPhA!gKngYW%e{pX9JzI^SJEZh=E4(?ko0_KXpf`Jlu~YLN zzmL;`kGr4J@{t(SHJ;2BIL!sDe@sL27m<_(uv1aVv?Iu%b7^n@EKWuSnrDRrhxCT$aN&=oY4ka#EDem>Ca4^}I*K@GL7c zYyd&4m8oS+2uKc83Wg&dvG$I-xOm{Cz%w?TSR32&{MQNe)s|ec)24_Gn4rrp-+Y|5 zX}yI{%GErVq=?U?i_o*{!l;MoT1@&`gy$xHfmyv3^q*l2&dpXA_Rcndt1`jFy(~!J zvh5cAq7g<-Bqrh+riC2!S_~qaYx#HKdU`U`0#=+UfT;P=;Kmq%^h|4*cjqBy&3-_m z|2eWTh4Tf8{P}$Q*A*;zVgtQfZJ9IA7Sd~dXW*$+3oPyu!Y#Wl(w2*?&;B-erX0cxPo1^f2>{ZsY{t{{bEXhR2t$^f*=c$Ej2tL;NN_17$F>f^|!}~lTMDyqH zuakWFdou4Me^Wr_54O|MvnUnqn8jG?hlAUPi+GG@uD)6$goVEr;y&eQ5dU%r3Y{*1 z!6Hd!!7DxJz8wU&y88v}!Vae{3vMN+k%WDPkUUit zyU*9!`lY<3CzGeqz$-}*K57{p+2VpLT8ijs{6j4I zG_HOJ;vXhM|Ee6AAd?J7&g!Gj@LlrgvNY6`ti_{}R#fw90x=ss5gs8&e(x*5hv~}9 z%2C6xAu@$EjuK)Ml_0vQ7AW~!8$QhXgiSv=v|bbgKPRj39AszMpqRxND(F!S2_5c% z_CItEJed5R`e(M6vWOfmde;l1a{3ST4_${dlwht@6+JGw3>=HXp;;uEF1QtiN%hr`)!z+rf5)SWhdlG? z*bsKt%QGg^T|qKZoKy0+M|ylEImvo4Zs?^uiTKz+qE{<2UONgfDt97Ny?Z0vupx{d z1VHw+NS*>5Jg?$b%FYkk(<&iiy{SFJkNvGI53&pvAR|TA7 z`ckl-?1*AMi2WnjRs20#0uXm2E+Nej`}<@#;SS7Wx{RbISHxtS# zLrB7uYv{zYF>bt*fHfZF@Wd_`*Unx~ceV|~XE#OI;t~sG*Zxt7{?VLLvk^YrI~7L{ zXv6HPHWHvEP4p!#Q6m2i=EQ}=c9U7ms^1scOML`9k7UBP<&n6>VlByyV+GQdQ~9h# z1z9pY!dfM{b5i1SaKjxh$eNQ14=qpON2&;bZ!2y{4;G%9VaKILoPle#dGz7$Ve&(=9hywm(b5N%c(BA2r4ly6{wIFm=kALE ztt+U0?h0Cb(g{9&>cwM~6wAwU`R8T>UJb?2oZkbOXUdf9aREhKK~<$+k;u*e$OzwO zpSxw7P`ulQdV6oCRoZRfdaE2?e#wAI3#P&4Gdu_fa#w95 z++A*j^IbIIFPQtxWoQ`r+8c1$3i+IdQ-7sM?UffXHQzVeXo!@;P@S=y>Bn zzZt}#aYz{NkY7)p6n}=U!8}-#T2FdDz9ywNbb0qjBmeou;F6Xm1g_CTjp28=`|}zM zk$;1oZC_A({TOCc*)m|SNHHa;i4b@q6I>r^K(6I>+?#(B{DT)U6F6h|B4|e$^$1e$1Yf0vF*1LBh@Xh$?poDSrWGd^0)I>{=xueaccrC zFr5U}Y17cNk|Ps8N^>8zmNBAoitxUV=Xt&}W>4ReAyG=wj0sWY8V8iA;)hs>c*~z@ z)Sgk>6JdCF=sZrpxf8>zFGJ ZcsAAw8Csj@3G?IKe6d1m&tsU`{hgzFtXZt;)oG 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 z zG=FA6Gt#gxcnYn2WRF_5t$1Mc41BkXf4<%yA+9Q|V014Wj2^z>&9f44x8M^-%`Ow( z>oMfMWGtv|Zao8+=1u~m8;^1S{W;uq#Zaoo9|fX3bIHj}ADWjskFLtHq>~TlafYE2 zxbq7mVfsX#_I@gtxBC|3=JWf}W#Jg1qvs!T+)0rWu5Dqijwu*=Mud5G1YwntC`w)% zfX#ktT!*T!;MYzIoHj8V)r-$#vBy79P0X+qJ6T!fo6Vb77wSOYfCMzc8I%o=A}hb~ zmWI_qFm;tWw{!6ux;tV$PI;e+9<6_1_@^swXwHTYBfCiPp-Py)OAVjSTnLZ*1BJh{ zwqr?oA>0kTMy@U~hgV82oLrMQwHjLhe$}z)Za9W(L?w7IZ-9NeaEIvBt-*iRl@M>w zQ&?uKgncg-!HJezQlV5_69f6H^6Ny~q zGn^1wfwW}_7;e5pG8#0w;Q3uRN$(+CTU(A(jPBrqn}q-Ibm|iFy*hP7n&s`gnsly{ zDSbTpB5raxPjoHhxY9}6VEsa4OnY~U&06x32$$VvO{RAQ9_Izv6 znNL$a-H68LI!TjINc0Lvk>z?5xdS6RnL1C@*=0Wu&r5wHfh(?)_vgM=FW;z!#x~`+ z$TlCY2G-+NGh48e2^6kL^kOm#U*V8Gfa6L-eCu2a)2jaytmczOF0<~jvqS=rpHznO z?bB?Yto}ay8Jn@N3YsRa5PVhD;!ke zKVk(Zq~Ub-HQ~WA;&|n28u+qXIDOJ2p|+E_YdaJT9p?2(8QOrM~~{R}%z zq-u#^zg{F8;#>8v^jk63Qy1!a3gS8w4P5#W+P?0~E%jas*$r^Ux z6zvI5hmNw~&4plL6Ngc5&W~A3h(EL(GNNaXusPA75<8H)+IqS^l>3Zza3AThCahA$0GDD=&4?^wFsmS zjv`+U2yyx8BCO^oRf(r9=(!kY^dM8?s&KdL&r$4T8YS z^T<5U({SY%Pk&Ykz=%ZQT;q@?@a*WpF6*TNr4P>BU}`yT9lik}b1Q(j<>M14ahfhM zhf92~2=%I4$(bA#f%A+Am@T3Qjn7+&^ur6#859pwuaBfN61q?%eH>SmxDvMW+XfZ8 zW++c11DO{tw&7jf|G{x$I5kk4*XGk8;91WLRu zO8Djw%jTO&#k*pVtm}q9LoTf0^+Xia^?;qp7c38k-XpGY=Y%yUTS?u&`CzqYB6o26 zZ?-b8Pgtn2o#uVt1PKe`pwF*_oAW50RhBQrNWUQrS$_q7!o=j*ZlgRBE zvmXzZl#(%oOpyAozaa!a&m$l-;cuP z-38#5U5~GaLvZHm$#^i$os*WdWb1y}u(f7J+?U&@suS5mayNqiA0!a-EH{Hyu^&nX zb@SWA7GgK8mdW2_Gmd@6#d6tDr#X@4{%1rK&&&j& za2LMrv4h=dM~Q*2F4Zqn=GeL#VaQ+#_PPyWaEPK9Qq<;YEsT<5^y}^#oVqv` zuliPy8znQjVMAT6iJxxnv}q){Ue#QCe+u6BO^4oBZv-h*bwuIf81$y`ajEzj6KxmT5zU5w$xa84K5$!jwgiSSh`q+ zR+#d4+wb^|d*JP=mOx4F=(y#a_kT_p9A(ILEa@jZC(6LHl4y9M_#DQ$U#UJ8b{$NA z48SM1`RwkIIDyQDr(EOLpZxn{HGJDT4x_i7g1K#LS&U6f_0q^(=ElES{ziO&+m}Bu zscDLw;-5dT=K2O!6(K>DV;Q#ld?Ic;LvZKAF<|rJkU+w?1wOkkBjaZ$f_-iwPm{hu zn)o(W&F^jWx1bYNr6_Qt?0&*y^Q)+MQj2R}et_OG(&ryv3%t2>gYebR6?*R9QnKaz zcJ8#zNP26Xkjj`+|dTasMyyvQ39kF==SgyqmKxi08_V ztzyL{$<$JL4p-WEnx${cq_RgWpr-3A4vV_r#BEE##HSk6_H`3&-nc6F_bG@x9AJvs zJ9w)}GhF7kW@;<;abH(0qG!X5*! z|1>YLDzVpsZ5|(CihcpQs_F1l>Md-lj5o&KHsdzQ%!X~cV6)pnnnp4Z&8Hb>t=!S+i7HLvLan}R3KcRday~dJ;?a(tU2HP%% z!!*7De$um&6)dm26Ig0c8b`5y_4D-FJ3r?ynWGhd> zKUQx@y&@4Kw(Nl=HNP>jV?12)zY4B7`nYA&X=dZ?jgD)!f|u4xa34luc)<@oh4+&) z*{=jQbT@LUBY`YyI>pmAz0hFVLb~|R6WkRa1bo4cKIG{I6*o(9Z|fB9M4T(U*v z+jq?0Y8))^IYUbGCBiXOqL1#x53Ct>kzfL=a#qa(>_WO4@O=}`4 z*j~-tMoyw@f0tnInrKwgvnG2hO<==>1)Tct4E)h=g}IYg(em`GOowmVOfoycJnTL} zuo6#vZl;i&JB&-5-d5{15;Xp{m1({@$`$gTqyA;@u=>JT@_GIq_IBYVHg`!4%sG4y z1Froem%qfL&opr`{c@Rfh{dujnhH?@)XG3evM4Y$>O&wohUzgXVr}<+E97o2zx#9 zGO3PI!FPkcm>Ze_8+vx*j@>U<(Ut&^usOx{O^nCTDUw{)@iwA!s}V;{s>3W(c`iF~ z1pJ&K?~Uh1XG?M#i~Ybp$41bI zGsutg3x!i39|Os~>9G6a0>M@X4SJzvHT&c|54XJWqD9pTU}?|W441pXt}p(e;1fq$ z4lY7Jok5}Rfi)mj_*RhUs)`G0a!CB4V>n`-CS2WL%JMEJ3YJ*9VRyJBCvA5c3J&L1 zKh!TG+mo*{`P!4@T|o{?mZu2w3(l~54Q~>bTYy7r^dURz2kS`tNW3yNP{;5ZE9u_` zXRppA*@johv+=FOd0+(Vk*Hv^xmi%Xd@UBf{Rl$CJUIXF8k2I$#H4I7e4?`x)0AoWBrZK}8h+0Iiw?t{?8qt;P*d8CKAL~A z9eG3C4_THj{Q)9Pev+KV0e1hs1NMK7hmWgnq387ngvv^gkj(2mxhjmk-e(RkbDuyB z2_ufZ65Qg;u2|CJPTmIxGY_3a5<1`|m}PmF&6d}NthvP`r}8I!aWvpI?pn+}UUdW3 zy%j=Ta6I`OH=Fx;UX|N=dLA?+XrL@3;}o zN_YXvjj6CCFA64m@fPZ$d^8l>1g3lTlb?kfxl>&a;IyU_3_ZS2;$Ez0Y4X#!mqChL z(I|%69k&6hoEPy9H+Q*25ztDCktl{;Tnf3 zln=N>>>c^U#$Olec|01Xtxuub2E8$G>~3z}g;=QHd=*qa0}dA~#*tly=s9kdP>D|j zqoV*e52moL%oBL~%RMBMMQG+pW6&I3PrT}DEC<3IVbv|3Zma(ioRf6v4tX0oq1YSl zp9`ZedsM(@N2+DB>^iP2KY}znM!|(2C~)j}30)oq%-H5R#zame#{(Xa&1qF&t+0?~ z&F(}tRgG;;-3iA}TtMf{6YO8#K~&tj4t@p#J?5)MR@I5oJy&_V-ScqavX28o8RMON z!d{*Gwd*_foE%TLmz&aa-xvsdl<9HIM4BN*F zti%J$v%xp*Af*zjY)`u__q665Rroj$)pvT3ZU6Ggk=)7j$#|ZObN zK~448lIc7tP?Iw@T1^bCzp$o5gKX=hh4|$35pwiHAb6F#TiO{!L0`NbR@jHK%Ob0x z;PQ{Td*=RPVe{36qubIf(|AIw{x`&1rEw&n@F&jt^dGoonQ$+@nPQF-f3M~x!O2St z(4k733)<*HOkPF7#aaHGk%v2+4Ss<}N1fpFkxA&@KFkg-7vMO{lLA{8FWgs&q;i8R zTzT%v6n0-?q7BW$?+W}*!AJ~@S8Yf6Zx#5_tC{==cjiq{H`uH%<4Kw88@zSx4e3-` zj_q@6S;K!LxYC=G@z~`@IO`;TpgVnsi6l+sg10ncSyMPScj{F3K}Qo6CU9V?wFO;I z?18i}S5|kxiL-OML2m7pWfOYYrQeC!#susxQpMALya7P-s*Co9jO{peKPR(O#v=H?yTVUwD0lVcE3XZzU zG@!H_Tjzx%+a66W%JgDg{49`WSFyTGpXhH$;;m-K@KDfrytQm3cb6jG@6hMoi?=bC zBad+Q)O?iMQ^E{yWZ>mFh@zM)efJtdTVlz`sinNS(u$7@F6`3Zo)W{>W%}_OuZC+m_SE?eQ>OFck*u zGb}9>gb2!6UDH@HE!d82>W#y+v16%=d=Z=5CW}5*A&j2y$G3b5R=+R=$xuk#VQS!0oAqH)^f@%t> zxD@M&N{6?lO(fs!HVT3-yg77UUp++UuY}!W)#3T%_1ruB!qz5@LH`m}?nRXp)$@#n zz3~ayB*{}$MyjK7-5y-HahRp~w+Wp;^`OT8h_2cukepEq2M!OAb*|y?THFDY-~AwJ z;W13;^OpSl?aw)Mp1@4Y9O6|S$^5Kkp*Nd?|K(a%lSqY{%O11h+lL@%dpYFUCQ z>G<}{4f5doVapGTtH>L#b11oY6E`xqoGeb5L`9eV#;Yf-nPzgM;IUyBE816wW+h8l zYtn7d5gf;!E8Fnp_d%!P#^@39X?bKtbTx$=iQ# zE$4Ja!|{jE2M3DAL%D(muKY28bN(h`XKfX;6xE`?#2e7l_9gBu35CmK0#wUwg~Z`p zqP6u1We~lDVR^m#_}_qz$^$h_Mzu@zeH8{eJuNrDABp7i#rR# zIqTJ0IO}O2I?Xta3i?M-Z;m&vyp;k&n~#EHS|qs?F30^CAHX(Lu7Jd_SmLxL5*B?a zf@?L2lZV35!AJ?xCM~}`VB02Iuy6<}=1a`z>M}oib!(k43g2!-= zhE=#rKlJD(zX9fZ>oVH;?1s1N3WO)-z5uVW&oJf0F%b7h46~Y#u3hg)lkZ8488w0k z4?SjwRHCr1z7MZTCc&NGtLW23C$M0}mR2?xatq9lyFvQbee|5;Io{5t$rDpsn02of+}DmJJ0imb z8P4KN-Clx6Z}Rjl{_iZL7F_0A7rNEZmD`wA!Cpxtex2^d{i;xGEduXd0o#hqB-JK{+6;ASv z_~tCklb=qfYsJwwN+ytPf0!?E@8Je*;)Eu;?&SE&=S(Kpo96#={}^2PK*q#IL*uFEVE1GLbiIF#?*^{H zCoci_%Ca7s%Qs@P`D=J!RL5sVPQ&tQ1qv$aT(gB8CtTqMt7f@y%ax~cVRZ#yM&yMt z_9--JDo?%}H3QCBM&iU@cga(^3H0RY+4QnQB77Zj28CM3sm}{5u0-}03$x^RL{j$j zk7z zGPPY8y`_c=Iqt#j_+r65u-ydj6#ipH^_STC8Beh}G7XDfuV?O6W}vmeg19Vp(mlk0E%w~aKSpdqI--SKjj-y`dMEbyc1pR8Y z9y?~u2Xo%^8{AS2c0qyg-Y$!@4+RTm-tK3A#~(&lv-8*<@&xas^rKp!kx>6$J@oq! zIAwH@9DS9Ft!olNO(OsXZU4bO?{!$wRRtH43ZFDd6)lqbxx5h&;A>)RX44TQX&Ho*YQF+d`?{opXv5#1LTmf6tUk2S46)?Yj9Blim4$Cr7xTM^S zyT;GBp~B(ev`!(^gV-|f$hwiN8a>YCpge z_r}A=KeE(veJ``*$x-!hR-zZ5m9cut(5(7AM$i3#v@Zgk<8@H;!D~8R{0%7Gmc#uk z=HLP6bo}~6lv9{{0Y)urM*FfD955Bf@#{5E<>*M-5-&;T=cllXLtK)ZS*P$Syqi1&qr@U$^4PumwcCjOj?W+@&IynCQuox5|*5ujmINmVDI-38og>BeXiq1 zMa=@C|CJ7$cyfnrY#wB>UrSN5;VL@tsm_xNhR9^+K5PgZ3l?S{a8u(YnBpWx+T2Cx z&6`2IQ1~$9U7A5b!3Fa-e-lb?wZf4+ablvYKK&l_kohl-zy$?5G-YBI`iyddWslFn z*pz6PUZnu8%;-rb&=Cqw;2b&QBo42tN;~GaAD=4`yJMj1%<{ zmuGHbi8w7I4W>;-5QvJ?1HVFHiuVVh$qI8;{+~28ofbouq(#8L+RM1t^(drGWvF9Q z0sF`4a*wCaregZRxMz$yn*T+jsk@VJ`F})_EODy)H3Gkti}Gfct8mFp1i9=7`1-+Y zDw$9LbCwrk8K0o^F4~5uatrr|4wI+n$8kN^{JC*!{Biuj(PV1wIBvq| zzbK}e0bkl`p)>U)*eeynan*hS9Ox66c@~rCurFl#19M?Ae{UN;k1@AR$pZH^Bk{n1 zEj_ct3*jQV{aWYq@Y{ibQSj-R6(kEw$9HJY?H^BBv>Qs-Jy zizt(k!r~qAR9rP?B!Wh3}K)Ovf(eRz6SW><=1%@9GtJaCAD_ zOsZq!JWgQSpEnSzFc<3t4)CIE5xUh8!1_cc(-8~bVu3ziy#O~}N`&~v>+DX54!6?X z9>kvL@j1R85+o^yll&9}cT<1CkNSmNKu9#RjE*DfA}`ptlTqmWslih1)E#Uok%D^H z$hjq-1~EmglD%?_VUiIR+}el(Z1Cm~zQ3x3UUt{e0#D(VNL4s4l8+BE?dhR}22vEfu%=fD%v$N$W6S1dQeNC^8C&0+mXE11PNvZ6zS2G%4ArBe^X2g^CI zI=2Z>w?J&TEQZUU>HdYCZlHG5^3fJbf|#bndx?3{BJoN~M%(9B;BD%v|R zD$N})m7c_7BgT?5tCn$bd`oG`DoHrx*hW4nT5=Le$8c=hH8xOs9vaOvsz0yLhRh5h z+4^!dD#lteoq*X?>2L^|d6mJXpSqmJpE5GhRSNT-gtM5Ma-QO-f+1e-*q30wb-zNC zw{1ia#nn6s@62M>pd};B`!`4`s=L5I(F9Jn-(^paj1hdaqI`BQ6=se$pCt9ebhX!3Z`bSA#1YuZs?EwCS_bEHt_B8x+<= zF_qd`gsIE&WOQv@8}kX0CVd2(?1yAnu2g7!ybS4u#hBr$LMOVmvCpZi>6xd~IsKf-c#a5{GjB9#@4rJ%E1iYN+4?ltMpaVvee}Qc1OAJ%8Bn{;W zI9MZxtNxvXgeyi6l^=isCn|WW#!2{0=Hb&LL9lqt4Pw{+4@yRJa8U9RsH!i;nb|=w z_FoZ(K1$~5%3kqyf?v=fGmGrsH;FaaCiBSiew<)(1tCd{cIBnPaB@C*_o5C8=O=LU z!&=DgI%)2!c{9^o(ZgH?mE7u3j%+@2g;b$7J@Bs;R+mnKh$AOp!|HgJf8aUn%on5P zkM*gk*9cl@UqX0BEdDkYaJj~D!W}ibR3)SV8WwIPOV7Jg-?{;o_)D3dm$1iesmf>_ zR!d(sn{sIQ9R{16_!%9Cp6{c;QSkuTsBH;Jv&MnL$5}MJ)PUM+Sdy$K{G2L{5J7zv zaUUARt$!;5mn$Vfzwij9HjdD};T2K8F_-3ME8`TCEo|w>-PrQ1j&-I=a5+8ka3;`@ z^Z(9gjZ9VG+529uVp9n2T9ZL?{Csdt+D@oZjiX@=*C4oen1ybZgeHv{+}UpS4OKC``y^nX_wUI-J6^pX__ODcC(fiXqDm zL$I9=4JMo6)iO1t4eprz=qLI2c^?#pp0-@8_7o<*Q{mgU3+c_qXc8u8hU<(Xp{}om z99w!8s~Y3k==e@%aiD}PY0BiLIdia}uO2s0ee{qK&>wl4G)8LN7{fxoI2IyX$YYWQi6r~M#Ny71gCB?g;RS|#zfsS@Z4SAQdfVG&4oi) zdhZq4GpUT6TYZ$2EDk64)1O0FW(OH)@#b2B4#TkQe4KanB#i9R;uKmcAh@pwil_Jz zkLF>xxR2l2E_sg%`Qf}7wU*gW6L1s4%xGo&99oxC58k`?fq6&>t@3_~L1W{pb8}kB z2fy7osXGadZ`tub@)h;%7NZ)^&Ju?|f1zQU7}wS+%3RJa2BGT!_#Jly=l;KJ-nTGt zd+tD&UNYbwALxdX=OWbG^(xtwIfLd6OedEMc-r_(p5Cdq3pOulM)NhIbb{|k-hi}` zF4}h=j$~xvGY4xFY`G3?`G(wWo1ak2Q{&~nR*;>u@?p^~0~(un18g#Mu$sHYK7RLs zdl`B->Y+Hq+`2~|U&%!4HBp!YE2!71`LMXMl5|CtkVdy-g0ja0y+`0=4S-ad1oz@q6WW_<(VZEE==h@&;%-a>HBAk^?T`d9 z^G&(iMjhx!M{{pn+u>j37;eGjOP~?ImSs1;hTS$r7|k6ZX2(r1@}>{x8{Nv3jJ4>+ zw};?CN*9aXHi1)4{K$GO96>$D1R7`PaBiQb;6(p!w!xF9lst=N->)^m{+G&}g!e;S z@2bJ=Rn|qB!5z5X!I|0K*AtG96oGzO6L@fA7QCwt3^EckF3v`vq3 z57)=SuAq7U$IyBBW7&mqob1^U4XZLDLIdGB*P*0Pi70O=BkhzDX(&`OB0`i3NlHc~ z3D3FiibxXD(vT2^N;^dF{U`8wKKD82cm1yKmlobq<4i1?!GH2f>Y3?*2iy%g)rqmJ zx~dDJpUafL+KAy_dg?<{Rc;q`LrD`BrM?qn-}tS zt3BxYSRZ)>8vd9y#G0m##MsRLASwC0u^ zMu~io-N`CIS`gjpHsSVl4cI4|j9-lx($kWEV9(=ZR^fKuE*Tk2 z|NbOWwqOM+?RZ5}Q}nIEGp>??Lz4tIr0ax%A7;WuLmAjTDjkAcRCrqSQFfHOB-o!V ziX(%M!M~4_FsI%cT3i95ns36`ca&Hi)Z`>4_Yl>$bMPJC9lLn(ChOTvxzLSr*vtQ> zR6WPi>Ec6O#6B&F z?2mtd5n^Xydu|>(yGDd-^^75#4XVjv)5%=0CI<X<)kn^_7X=DF?{8?uTN=s$XK;Z?jGH2qXj~pg6PR65q$155nl|4W3!GcQ5Wq;6>y=_B%ix{(-ss~PQ^^6 z%aGo?A5QrfGAq$s-0tL$Lppkbg4bTS=~fF__;4A#jxhlFziBXeeK_`yODmtMv6~r< zI1h7A?t#Qt&*9*hybD^|XLmzrBM& zWPtSVacB1PpX0PFEn(W@(=fx+7LpRwx#@<^FkWXd&blZA)rV(c%ufmWs`xXsA6KTf zGnK(=`Xt!Xa1vrpZbhZ{lTk`>GM7Aa7Wkg6BAfmk#_=DAq2BQ*1h1Yzht0!Th?za) z_(sFJutQAtNHiHObpkKhjAJdPH4w1u4ilb#Lsm8Zf|v=NWVKE(95q`4tvhakepWGC zy<-g5X`P8xJ3qpUP$lk`e-C(T?}aHdTJhZ1MA-T;8QyLZ!E>3bsj^Q4$!q4IKBp0k zL7x83Y=*Uw5Ak;DBx2@rU0~bN3Bp$=n1Yok2ma}p^*aeh6eytUk3v={pDnm6dlAN; zmw%4e-Pd!Dq?)WWl!TdgtMu@t=CO49zeQnGLX-@me`M2RZ}=;5|X*s8dQ z1Z}ZEnK5HYZ?YUE{vA*=u1^>%_7uf;cA?DpEwJ)yKIC1CgEBb*T#z42Qw$Eltk7om zBDMmf?dm|zTa+%?@D@wP?-dR^C}96DZ~8zY5v~~zA?e7+jULyB=P)3r3&V7(}N`2>dW4Jm+^7Qtn)U4!LDa_lYA}GS!!MUy|V>0=7cuE`G+4 zt;a>gKZDuTkHF8N86G^1z_^3i=qBS#ep*U!-JK@jR)3D!L?>aATOhXX9YH&tG-yzI z9ul%uUE__ADIhxE&Z_eX-c_rpfcxL z=Lh9l2{7->F>cXAL+fEmWE+yE<@JT&|-Jau7YQygadaI&AyZD{N8ifzKlh zh)SD03muvWgWspo@fX)&>4rIExOfY9BX0$>lAFl=waw&hCA~y!nKpNLv;x=~Oo3DT zq6Mq_ayZ@Gc1(Me2O^T0Ad+l|G|q|2h@Zr^A~EXvBLu>n3NeefF8aAgLGHsCJZ88R zMBi=W^S|+Y-mXbKlGNZIPZ?Rmc`SZs4X@XkviGjB{9czd z&C@C*)1R$Hk?;AiIP(%b;!(V-M_dpZ+VrElSrR8#-iBwpr!mLx0dUcNCTHJ&k!u&2 zk#*_zTybkCXS<&;^O=j#J)X~lmg>=f*E)DsegJRRd2t&{x^RyvpUvs0a`!FfldYd( zU>x?cob5f_^{msNzj_a89ykiu3z86Gs>#Ap513cO7S1Vd5D$OufrRkG++gPetl;G7 zD?X<^vT6j{^0R`*vK%Ja8wRp|xp<;k3w2-FlDpr3knK}=0)@+5dR#dJPIlMf-i0A} zCzqdV44ovKLROH)I~`CXJ(HaI_6QxOhOyUbu`DVskvHqy$Nr45)X(7st9-N&lHD`# zHH(1Km{1}&mlD$;Cmh%!dt2Xb4cmV+8MW;Q?77qfa%bD%K>v99_k$EXYO=tH(3%6|e3*1Tu+xrKILQyKWR6(7);AO^b?zXkaG?%006Yqi5 z>S5mWI+1R>(1kflsWAT1GnO-QJeID@MW$=bJ)?p{Z! zUjBc~=O%(+b8Z>mU3D;1qmy6jGx={W^vO4MGWCQrtXq2;o*6GiKmAFtsGJIGO6x&m z@oe~z>cCw#I>Q!?m8a%bC!whS7^mL#o+qIvvZtf7@o>Kz)!re;i5sqjf`sRoA6JM< z-nqhYg=@GDMOn`Mus;`mTAT}Y_kbnkfAID#-riI75|7L=r@pGypl$UL=k1W-3Y?FS zH=YfcKKC^C{c~rLECfBbHwcJ7B!X}rVn}Y>Z)Q%>#D;yxZIz^5EGuy z`CeJeq&|dl>mF89ePuK1KC}v@PL#0&>-E|E<8xt9dn_(J^BVr8@#oBjo8V+1$@W|s z7L39-5cpjapXF{xu~L2De1Kb`+JT#1%8>90?d*zL4^h{#L2U;ukVilCsqF!+_hFzj z`6PZGo{0-bM6l4=J8}1`5Y&Bq6KgLmrJ4>IG|IaW=&5erV(g6%9hbo6{qh0}n{sp< zGgWA_y@L5|HUqBZJBaQ)iH+8>IJ{4rZC-K+KW~1;+VYD@!WS$2r+FStMfje`>j|K+ zY6!efgtGKE+fYV*57UTtfCklC++BT^E#g|(+6!^)%DLsB+ccd>Jh%l`2A-JSW=PF5 z=L^17WfQ%yr+9c5LT1}U;_f?!$+ec@JMIsZ4oT9-vPEV=sz796@PeD*T~lqO$e zcgy9O*1$YE_Lvf5qca39()Uk__IM=f;~a z;u?Ed@~(X_cA^>8KXo1xmG^LE+h^j@n;WV1!Q;>#lZTpr9f?R1h5VP&@GU`+tNCI} ztA>uCLf#Tg;O&ihQ6uQKAMNnqUJ^4~t_9Ci6uBscM`&U{hMyIolV3*rS%+i?WHXmIEqjmSdv7Gdj(_f)d`&D7JA9H2AK7T`ujGLr23& zkK8zNil=t9U6tkbJh>?-7Hr2}>4)U_`PbO=@f}pOwgJaN81J+s%cY0Vz|9Q4WQAf> zR2Px@Aw|P(zp;87X9bzAMX1A@hvax$_kl_cI`u<1M77$J>pXe+`wJy{b&@$fFi{bV zx0K-C!%K0K&sUsa&6A;RCE)iSe%97wi6;cd*?yf~^7DoWRjNsWN43?c7o-Mqe9qo> zH3w(i4u#vifg){`1Q#(Fk3ZgPvS48YBwjlUeuw3_HL?bf_4^|B@2-NU7JtZ#9*&7f zRspm}lP+9LSH&!Vfrq}ddS5)-_EDBQc&rsl4z9+h)d}G9+!v1T_`pUSzYIr$w+Upt zZ?aE26}XDnS1@_)L1_6`LAL%5C!IO*uxtHV%&t2}x>qa&wX*jtS#&mp>&rqTpFPw* zdk6n%M!}UcCo$5ig`~t^1#;VvNZTTu*5awX?y2a%tqHYnt%Tv`AGl!aL&4X%zOekg zF-l9JP)_?5lh?Bo=#S$q!k9A;-6zAe)fHOYN zMU(35bjI`<_~-d=h_9TiP%|Y3W0NTSSVLbil!{&J1vjVf2JtRm>mui59y*~!cOk7S1BFt zSPlpG-lr?#c)EK0AV_BCmL<-QC*u|?K(l`VTrnAi3mo=iq9xBd)^xDjgVVX+ zR|c@2+LLRIHRN^oL3~!V58fDV2ak&vAY{h~ZqVf?-FP4j_IGyUp2Qxkn{t|c-e*De zR~-{pUe+n|h&7~c%SUnUXEv}({=KRa`wkM+!V!0^!kv!cES9GnrB4qcadj8ihgZYo z@`dRjnIlOp=4bPkS8Ker=r)Y$y#!}NEAZPr9i|poLRSoy!}}M91#1G=K+^CUYRGrD zG7jj`w_$yF7^T?a1Uq{Dsv?)@{~lbMenW%oUH0YCH&9GkZunR!0&Po zSzbmZtI*P+sq-Gd!L8qM0dGv!KiJ6}mTpCl_o?7rT8DY_odsF<-$3Y!Wu$F^F6D-S z44Fj0mNllZ;_D_>FdPc4`^s@Rx`bFCIsw;1ZV`vUma>iJ(a;`s7@T9J=#0KFws+wk zsD5ZJj9nJa8D7|f$pJI?9fBg9=z0roo$MniyXEmif&!hI=SXe#Z^uur=WzM@3-HPP zG0e{i6zp=&fUZf>pknGrLfx8#vuggMJ@>zZo%s}uAFdVJJX|8!qZCeGjGWF+7<+Ty zL#?<*{$BmM_Y9YpSqtx?rqY<%iy^}$Lg2aV7n+nG#~C%(F=DDCovmID8ivzA^*Emu zeNp4)9J&tSGBG$ZRnkgr`7ubWROLF~@_8n|7kz7kD6BbY#JL!zVvXoao}jA3c};l^ zd1mXluLoz-t_Pp6tu>JDxT_4XWDlN}(S=9*=7VLN4LVwO(m+>Jw)ezZW@3I0H@qE5 zH#c!CR8xf|e7*~FpFc&N_G3i)^ev)$Z9RO@J_A81i|LMw&oKQ@K8BktphBl{Si4J} z8ob*^L*DS;eV(~6zk-9RUNQbnZ_LR?Z^k*+C*iOEF<7}%pRVHf(8YSxc=N3(sS#v? z*cc6Nk@qFRpa0ycdQYiPeS8;a9`}R%lmIASyB(i>$%G%XC*i`GHmEqS853h7&^=Ow z3rJ67A=)`;9xZ~?2W7#qJ(L7S|3&+M8fewv$@T0S0UL(SU~lvQ(Gm|lRk)`!rZb(M(}M#H1M_hI1JdnlRr3jTZf$ZFl2 z#~}6X1Jv_(VYSm=1p|&!WU#s#i<2&b@vJ|X<$sJb``rXu8-55Ye)bVfJded;+GLUH zR9bAV!x`wDgy!GY*e5cBD||DJYVL319<@op?N)IbZ@d6Rey@ZBlLpJ(W@>T)!NFK` z*_6IJ62lgQ`i40PW)N$k4Mr_{6uzsupbLt9&(7L5mDf1xb=g|yDMP|`GT1Q}GYz(_E z-bCMx>xXK+RIELw#C^^xN5gG0sB=P5+CGO>AwO{+ z|Hc)ooIoU7_kgn1b6j$5FKw5MVyPBuxsCk3tK-ypEM!3uuKTtJ%=T&F>Z|i;&%>9v zAt;g!%8ua9x3@q^PCLqZW^zNPmIwzPisL#AC3T@a0wVK(^gp+OA-QiLZs`WA?>W=e zIf`7h9!Hk{lcbX($8w&3LioGHS}tp76|0=(LT~&|Cc7pGS>uL3%%4kwv=V#ve$sLH zaB3v6a+*m7u2iAM(g~dZqC?>Gtr}MP*wcyn2}EJFFY}z7jYrN#LqyXmL3E}~dAadv z*e-@7HeQ3K-@cB!9SksVK#%0EO#)|rpSQU|m#+AC91jWnY51!wcDPXy_h}|W>*)w2 zRiSL7)eUgJQG%-uYI93^?8)NF7Ity*a-rqfK6tdU6kOkiV%GAj#QNJ(nho*j$G=;w zUGrJz6Mx9=cq=T*`2yX1*Y01NDOdOQ5WH~t1)`gWn8=4~xbCwk=Y3BU!iv*@YnI?n zx{jqnljE4AlnBg{&lHbLrXf-m^k$_uZU~5n{FN%)iRN!8*82pLyV7v`R7$5dzY|M!&#nZG{t8Ln)*(q?~kQW5s%GK8EFl{RDrIRsUq8_yHYF5D^Okb z8(fqp2!CH?_&&c78Xv3BC4QrEO^E{x*!Dwq+#uP!aF{fp4+L8nfJav;PjkD1%Txwn zspMCS+^7H8ZM94I{kb+9v_6UkqBgMGi0@MGn+h_IyUIWCcf*~ZRgvYJ@q8##4g(!H zb6ei<^LsaZed9>Sy{N#eP1pH%u{1YXGKDy%hvSWeA5dOY3^Uyp({B$nA^&R|>SwoM z*X(1^IlLWzxom)U!7I7u_s>8ng@+_8_v4zSR|uBlZ+$3||GQcEsEEeu~NFEPd z7Zm@e58B41uu=aH-zIm4rN8aah3_97D&NHP`o?gj{GDv*dpIVxO`yx{JJ?C5>#*fL zZ>6({#KC1eZ-|I;^x1nd{c$}!3p54~FJkZl}F4u?(!+PGW!q7n7@gwDpJA^Z(os&yN7V$O<&w#u8n;^>fp|$lkl>x z0Or}uBd_)|>`S-+T9N|1+$;qHCI(RQ(35Hz`%u|%Ptd!d4ngUv-0W-h%;{YithLny zh2f(#(@z=7t|(#WXFK})vp=7ajG!wFKM00p`{4yM<##ebFy~{eV8j)BOo6e~dfE|8 z8@U%I#JZ!ooH5M2djy?ljzXgkZk$n56zM*F0qZXZ;g3lRVBxcmWYSXw_QZ%<};a;epCV^GLFub;i!IO@yf|m}X>7~2+lqeX{ zbSY_ckCMfGorFTbYPxV~HXK?0n0(3F0I@GUd8+9JyuVC?bJMzv&jj+AO1=`qlfT&a zC>#1x`UQMA8zPwhYXt2(Fbp>z-D779dx@G&J8;yRiymLXl08PY!fV*KSfwC_8)!mD2b@|j^_TmFq+lpiLezO zDzx&28e@+Iq(&(V26H009b^U>RlkDvG-@*str!$bN``3*Qed}Da@j{QXW`9`zwq*( z7W?h>9T&TDB;lGI8kh>mzwUCxo|9xMn-4_-6RW=)g0WX-6wOYP7N|Df!-Aj?tl8BK z%AdvHOu-SHY<>h9Kg>j>p#!KEn1Ts=GvJkzCHxk9#+%>1vw7>*;+AvjWEJO!xtbKO z%@CuO0h;vhLs#Z;EESFfK7f;P+u*j&5SboOk3rLnarc`W&|sHB5=Z92`L-d__Bj-9 z)!zc~`2)g=g{36EtCc8;oMNkMtU$J>6y3Lo!zNjtGV3^>jWa0*vn3tS=&J-TjN{k` z{VQ;O>qCB)TvYz(@p++MfGZXA4#3Wk-8feei5rKWl7%jOH+91s$XMS4<)Xi^T(U`c z!1)_$`0b-VxBNs6@6l{(K{oUEzJ-Z@<3X#+7cG5ff=k+X^q=>Y9eAgSVsqr6$Yvzp z+pmDCXi4FLCHq**uPF$IJR!3vlB_Pj1o=m_aN)NUl>fJzJrI2lz8^-j=c)`(T^oY~ zH#Mk?n*wy^j3yf&*>SN>)x^Fc3*Q>GpqI)h+|`)^w=_p#cg-l?q?ZV*eq`dO8Pibm zFW)iUeG>vw7lQ4Yd~6I#5qe1E5R-56^lqvM=iWYtJ7oEbHU8KP`?SJvL}UbPzFbVk z6})D%bKk(B3lktXOdh>w8_+LxU$HdiBFNw~2=?zH2DQngg_kX`eJ! z*setXZ}-Tyfo$%g&&Ign6>lhXCO#M0TtZ!fmH*8tDk~~h&X~YQgkNiAVMz}H}6({^DA{+Wf zar36=60-#&Xs><{)zhYM*86XR#MAA#`L6_&wjeGGxWZf)-o&ubbn@+x7`Msp3S8o@ zLehe8obv89n15}6qv2xZ)Arh+|G`L14xNel8P+H$+Xzd?lnJFal?rYpsF2Re9>{+1 z9~OmAMWcy(NR0ItJlj5sTDgV8dVeKIkKfDuzHft}tRDPTD9yDpL*c#)8^G3kr&ae^ zbx!L=Ke%oBA&`HSUN(Mb6D(8~XS>TKIq5Z$m^s4{*0d_4nprwdud)CWFGY^|{t*Z# z83=biS^+6(F-*-Phqo}k!!NJ59e0?L-nOud9zYv;z^igci<18|H$rv;?NykC2$*|Ho4?J^ZIP-LC@^+sz-(}3g zKl>sH=l275w_e8VI2rn68AJ@!)b~4yX)L5*hE0Ce7{8 z)@g+kV_F1Tul^JEuKvhdU8S(tPZwnxia~SwEpWBm!S#Q#;FR;)!CL7|`IdV*!o5E7 zoau(?An>rHgWD$3lj}bTe9K!&M$LT`tUXM_L>h@2PGxE#!8mE42WBt!f+4+wP!f28 zy$RN)lP1TJZ3=JD@PrcHTRRGzqzj4N3{7rL`^WO#(m&yda1PJp+Y8Qd^>9kzB1fI{ zIFW>4sCZ|OPkT9hG^-CZRtC`d*XpqJhaxUlpF*Em6yqKdQF2gKg>(3~fd+MMgOrVP z>AA7WT;;V87!(U2*_YA<^EJ$Ig}MT0j^NMllVUXYm^K{|G*+N6_A9w{M+7IxMML>P zewH_GHVnH)W7_(EDf1v>5q`T5b%BiEnZa$C+6kj*#Q9P;$+c+kU_(qYWvMUXswHjHXYg-PAku(OrWU0=fC#nd_k z<(XWJ-CYv@#}%r#+OTzd?C80XGl{i{6LpJf6PVrVgNZVAcsfD}K3xw0nK5Ufy+sbZ zS1dr2F<-H1{ygwax&+$U#!TOAEHwfP+;C$PeiI8|_ih1BP4^`eX9=L;Z3#(oHiIh- z|45krURtv3q|k7Z6comnLTUe9xL^MsMxJOU{~T9R1DzklXoDBrWYJX4!TcI`i{@Z(#B5UEqfYdW z%YvGB9K3#*z-Bg|BvU56TB?b^JHE{ncgRb@TfmU$+%n#wC)aHwuJpdh)Q} z^)Zv^8wc9`JU?l_4_YjJ0%rfxNu-t;5IYNqsof3RJ?BulZ&kv(cUN=1QX@H))N7Ut z>T1#TT`)EWk3zMnjIa|z93vYi9I5C=U-3JUss%@w(Z)0o?LAI@?wyM2WF@MI9_NYD zp%@`Q1X&};a^{C_3M04o;lqd&I(SDBoojjXSBe)*bMGJ_J~i0)!ksF{JHf_x2gv46 zvr*zfE`D*2#|Cl$=Js!4N@2;YwI*M1YO5MHr$s=)+@t8%F&RU~rdU#4TTr}r7KEF5 ztiStSLFnl3tfViEq{N!hJl<^cujnZ`k?Tmk^WHKy-&8bc>VOBQ>Pe=9K3+J>zpKpM z>BV2gtT^HiT-J`KI&NC11>ivSZ-eqv}M-AK1W6SJo6=_IJD+@jT z0G#a(f}-6VSTcVN5#79o8{WQwt;u;U=s5I?yGhy&db@%Fnw0 zT*j5pT_NiFc-pyXBM~_o1Y05}2_?qGqr;N~*&qTX0r{IOytF>1c|9`ql(CVDg?!$P)*<@MH}C-9fU>Na52(1~gvNkk)Nc zL(_C`re(p~z;3Ssua*d`T~^Et%%iLV&N@I~gAD#$zm=BVx5u4!lj%VpW3I?Y89r+t z7EX1yf#hE-6cuyj($m6U9Le9KjR7e@c=%&`oKIM zpAp`(xd8{2U!(X&4i~(xfbEx4!O*z|4qnpZ;<{cEl`Wp^!GHsGzNmAB z67KPb0Kuc_6VM^9n%`4BE;QI;!$nM8%(Jtn;r098bi$RrLgxirVCJr7k{(=0QC&0;uyKm>C=zk;wvg## zt)@1bEgwVwD^sJPzc-P9ZS%>#X;*P{a}NAmWezTpW4OPE!U(VA6Ruvl6>ItPy=~oc z(9fHN@8*1m?!uQ)^L;+eSFI z*9n*1Rc9SPe_1~48)8N+TUg=TkElE88X>=z@V537T%q$lV!OkL*j4Yx_*f&ZEG8TV zUA~}Vkr~MzmnOG0(RPNBd|EJaJuJ9#!$^BFrP9-EIrleO7z zoWfO))T6p8y14A|EFjI%d|yr#hc|h#gi;lvx@;Yj`1lDVum=+=>tU86M^M){m9 z{C@l?)Tc{8+w(%GPpTAp4rY_#jxZQJEd-TmhOlm?FH`^euKd>`V;ui8UXapqnMJSj z!JuLx$;em+z4LEEO2l#YWYjO*DHbId43eXPJPWx!Xf1CcJc^(EYuVH8vx2~1e8y3u zgg56tM#GXZIHzWi-8Z)+`k7mx3+6D>Zcm^c*U6~LCUEJ}BkBX*q{MF)4*raUgG0m2 zHYgpdPj092qP&$bfHy&{IgS}G^f2R+BloGS7-iNez#|D6Sbtas!lNaGTdK-R6FgtC zuF1#YQDr*Wm+sDaG{9%CK0VsK z7CcsMgqgOy82HOv7WN_*BeX;X^+SQYH91mHG~kGiv&u2_Tt4}DMV5xn6GPLMa^58K zk{35M2?lLK;G@G)rV%d+${npN(2ogL7kBV%Fn$;6l@*wZOVWm*>3H(KJEW}MOm6Fa zvU)Klf%O|q!=05!a7?c%JU^5JD(~k**CU>xk~qMw|1Chxnij#TXAkl5t2&lC-+*S8 zMdMzF5VoT$0w2YC&=YqrKv9x7JyUWPo^ItinJSTZ&8ZONt1iF~aZjw{vyxNO7m@E8 z?acE;4a(N$L6*M?Zwoqu@;u-2Ex$XEdguo1yF;nd)CBO`_8*%4S_=K=J_+cwLWq2o z!q&@Np`=QO^4Be}y}BC0RQC~ElW#m{aIQe-*#&%7vJf+>Qt^%IKX_ZT3vPB>aSn!u zd3$Of$R=LJhOjwckT{;|=U=dTIdvLc|JfOgmu8Z45m$)hl9?d!sRN!WnR9pL@6%g6 zhw(Vidii#MLW$Ha7MvbM51JgrUpBgQobOZko(m3SUQ3s$sthu4GBzNS@pB2lhh$%6_=9x*QLP7;?9r8!>U$ zG0yUK1ZZZ6bA!Dc8#iVmXXXD0j^D1qROtd7^327)$p~AoPUEislLme1`7~(H0iIJ- zAguBdp%2EI!AVk!W6kZj&(}tA2K@eK;*MjyxndG`F=!*15Pgw22q$C0`yDibXC`M( zXdy*r{H}~oI4(B~LlsqJyt^YE=OqVmhtnc(>cXF-Hs6>`k?=t8S8_Pij3nKT@%w#0$;V$94~ z1&XIgU|@6xoO$#Mb@=WDWL*_n91J77XDx>0kaMuv#hBh3RSnG%3S7F>bYM$LVbjNH z)NjxO>z)d%WS&N_hqu$9VY(VOI^GLB!;WKp#C@=yFUR@CH(|}RBg|Z7I(Wa9!c`3y zi0&cltDE8u>Oh@d8I2m_qXaD%}K_x#^A=>s%cH1Q1wKDDlcvVb;AriAK0gr0x~lC&N!1OVOcgo3v9=hc zlMPd|$3ox~Q%Idy%bX`_O3xe(mz z!?dDftmK=+nZn~7c-CIZu9y_yUfu~JGjxNMS!hA!m7h>>+SaPf?GlL?zl)@aR|!T} zxUql*a`<$25zE?a3%W*jkatK2CAMv6&zlo4$cTgG3TN<1dYf=Zx--{MRf2OwRH1LQ zAB0IP$L_UCP#)|LYSf5|*0e%jqcMO~F@*Xx;_vH+$j-4g7k;c`N5^Gg{@!^Y zwk;3eE^HQz%ihBIZ#jx99kW13EeJ0=jix)gr_sF1&D?$aXJq|rO(LJR7&j-)hI94j zV9wPSZ0Bt!?db$l+pc*URAe%>H(`Th;elK+F>FU)CH!zeKO7*8IN2{0wl zlPrFt58K+BF{M-io-X&N-Bap#r-lvPG{Tlk`%*2;1|`m{v;b>5Ed*P$?D!d9G2PEm23DIQuSR0#eDpPUXc{;c zQeRzy5WK*e+%ot~Seny~kSX7>PoEljZU>zTMRIwh8rT(QLm%>PiX+o#u$C2gpZa7W zZ9NBLf4l_u@MLIgJBl8+Q;EZq?O>W!49{#I2~C>JnJ#bmS!AL}!xpaF`pjpS}z&TFwdGns!l-&DSyQ(|P!7-$Z{Ta`4LVI9?xnmt1J#8Gdy~g@ez25vjT< zT<@p#+?>i=(6E0EJlhlw4L)IbclZLvzdHk#nE}+Zx&Rhm5aF6b45+`_1uP|<=qocB z_SB}~VU-!+JZ>V+OYdhD=26sa(P%i~6~-cy7BDd_Yf7?=VC0Y#RW0^Fm49O~`Axc@ z@{A#k$eYN$zVTQ1PiTZDv#X%QIttq|Qo+cegn!4R!S2A{IP-9mV6N9SwpXDEmmU_O ze=`@NmCQWU#ws{jww3mF&7!woSQejNb;Z!6KT#|@|bX9=$S3@0#uK2<8d1pR#;v@h5PXTN_A8*Km+~$!< z?kbGtP8i0c@n#QFBeP$q5PJxd4$Knlc$`Ab9-Gpy@`tF6bSzh~y#$Q@wZi#B0=hZo z66j=k)4AV`@h#ZWx0zC`w~_K))imfYH-g|1%Smy@e<1t7oy#rwhZcIWAZO@`Ss#9q zvK?oDD;~@4WX7^55yrHw= zxb;z(?a<3J&Yq$F0!z5F{5>cr{UC=9R|t~&^|&P&CbTU}4>m=|bI)#Caxr<6@X{t9 z7@00drP*~>^;eBb+zq2%ztVBq2L&qg+?)HiYciHL9)`=syQrS{Bu+{ZxH}xrzaA;Z zS;pjW7MWHgxrg7~Y#fP2n&ATJ#m!J#kjlklTzN;_MEb`zpEZ;!a_9GDv*SPVVNLfl zFv@xijUHPm$vMRm+>GdZv3PcC##oHM6$nz1oiHZs33?Sw+r_AiD&4`ZD2c?%t!>Br^!^rF%$2@KkxLmw@UAvF9DG_c3` z!SpoD;--T+Zzna2=D!tw_FT!=G}tsD77kXZ;v&y5oN?L_>b%79NKiY8xpM;>6?Nd+ zZ#OVF8_RR8PNAQTLDP2lRD zyd#Ices+*gE-PNi(mLq9sl%@mCyb-^CfUd|cR$|y! z{%5K#?|w*zBZ_Z@6I%87-MK8Dkx|2{Ja~?MY7Tzx9f#$0o3OyO3^UhjqEe#~iM4xx zwWpNmd%oW#-#(7K^eGU^M_F=NGVP4y4D!E;Gpy$8f6PnlAL@K}f+3xWkX<*0&be0v z7kAGs};X^ylt=*gQ=d!t{N>Qgj&>nPkHEf8tbY_&-6W`~ajiW@1gM zHdnOrJGo({Luct_2}{X6nB(ruMn&n8+ZMg>BBmCktYpw?PBhdT01T<5_%5H+UC;hg==uwK!|Qd&LmP=8lHb#h<594K|%gnXrG}8#A!0U%~?nWA4PomBN&S6liASnT?wiU6dX~{jTnz67hUzz2qlIOYH>1qvo7b z*hs-|>jqSKW&zqSn@FS>f95tc;1W|)R9<(OXgJTH-v^FyV*V0zN8~?&jHoQP+ff$2 zdn;1e?2~lf=&!JRi#WB8cui6kOX8s^JTK{YDD}{IDiFRIhns7D5S<2bNKI-vPQohuqW2nSUD zkkyyMK#%rX8qcVP{7fdSu6P9MCJDqXU4*Rq@}1pHdP}M z>5(ZhWXgal;asP2GvWrA#cmTW>})4zuuqaryc$o;orQwKVwA2v7z7WVnPS$#JSaFj z4O1V#M*XlLR_9;G^d3kthy6Lw z{%o90d|LfTmhEUr1`e;MuO_~w@7Ze!O{nipC8Mt$A__TEPy{|(jo{aLD>RArF z$8R+C>yV&-zxD|QXXdh?2_Iq2CU4=%Q+cR0Plaok^;X!tQXca{j9`)UR1lV%;q0VC z_+pm=4VvM`d!=@x!IF3!6xTwNndb0HZF%_`_hN>DJOk$BD)37S62x3RM(((Or&_c7 z$@MG7AjWf%dlp-Ai>k+vN`(cu_h$+GcTJ7%kep24y+}aS`XP8CVuZ@kp}2eIO>#8& z5%KvE1o}LO%Gl;4Ncei=++s1%F;ZlSr|v*_fif{uHRK+dzU7@d<7wf9KKS`20Sx$k z@dc;|PrZ*|_i8=3(%44+st*#Wr@vT2uL3uI@CnSzivV`Y2Vz=gumkCvVM=~KOC0wc zqv!HFGLs~^u7{`D;AsM;SGCK-Zztm7vC42ikzlOh1D?5YlgO@`4n`vk>0H4-_CzNi zqUP^MlPXYgUk0+5Lf<1L@w_*|@xohY1jY%l6fYZC0A&<`ilPhh85Dilr+h0pg=VXAjB z+31}>D(j}=rRWFv{>dM*bz=c(xE6=gD&Dc_xniVesRiBoQG>RzJaWE4jYa$DRDvfr#4?#hfN!@C0L&|58XC*yyP&O03I?~UUmv+SfY zQ$$iB3D3DtgJ{!IQna zYeHYQPlfJMBR*)*CVJQI4xT7hLN-g0y6tdamOD+T%Doy=^H|6cEDdK57g>@KRU#PN zolG8lD7UHn|2GFuv4cFc}z&tOkhn1JUkO``XM5iqV zfu6txQMU=Es{bt^Mo%Qk%0satrfbHwRn(%Qc`Nz(B$bQ`ixTgB9gO9XePq(EjnI5w zhnLo@5IR^VaLk`8WP0*q-ehwMlU_U(kG?$@D@M$~b^kor_|fxV(LznM8CpxmT~8*V z{Zsgz4I{w2`z`znEr7Jxa5AD|Et9K=!cJXtu(oet58Tw@_iuM>d|QOYYA47(IaNOM zR3*sEMPT?7G0K86#Mv)^?!A-HF8>;K3eGxRQ6e+_$Jpc3S0Ua(go@?OaC)i>n0~te zGY6Ez3_m9ryrdI<96Bg+HmiWF5)XJc`3vc?jEAevp-?*CmK}<76WvVT0HY5KBwjI} zNPxs?m>F^xJ@%OJUFDh7UUEDxAN`8ttcxN42Ciq1D{szJUh2g3$oMu zIwK+LWE#+`f1U|CFpilJzU`b3=#S#`vDH+jVnUT3kJ2A$6iP4eg(+r z{A>4en$VqkG#y`ZJE;HV#9Ojnqu;`8{4+)jS;T~{ZajgR5h+3^_?6%psD*L1dtqXC z0+cAo;s%9Y5`1qQpCHT;-<$ryy2A^3ZG0n46EgR!miyz^%y70QPH=Hn^+M^P$@KP% z40fS14$~woAtEJ;9MXLWuT>mi>+Bzxb0(B+9XyBE7B;c!w|bB)sl&UqJ;>0Dim=t_ z4+}W(RdjsCeri45AHD>{V$$$(xa*UNLlnpJhI|P^tzJUq!9=u3bptbHQ~Juxm{oj^ zquHl)@W_KcQn_am7rRgAmBQWAnWfF*4GSg27k^*FGx1We+scsM>Jj1N((~l7!&z}a zV>yv3Y{cNb|KObSVaO<`0@a^G`LH%kn${7|T4qI%bL+!dh*+6-AO3@z%J;FdzXQs{ zg0=aRkgvpB@Db>)I*ThSBG|$%d1k!Y3rjsraGpM6cRQc(!k$oJHWFNiyS}jVW7V)_ zfGo=Ictn=ZlcR~_!o;&L9Ra9S$Gd4qN#Sh+Na(wVm5LmLF6`lxg`CsQxN5R`*GbXJ zIj#7kZY4MvoMyQ?3Q%J|Q#@i;B3NI~E5q1&Lbk$7^tMZoVk%yYL=(S%$qj zMY#8jBJ961oX=h1hLb!q@QS)IE_q=HlkIZJ*J0VPIMbHjGZAdb3RM{Dm5zol^Ko|m z6RcI%<$G3bg!)UZcCGQ|Sb4h&Wqyx_^`;8sop1*bZsCuEE>9t6rCUUrZ_YvJyf5s( zBY!bK=NdNk2f}nqD}KUn8lQe?0o3$efIihjQ0euV$c%MI$+}G#CG0uYcztD`3P*Cy zsY&P~VNbR`IgDKIIvJ)njK*y?q5FOH@wJ=*L`Q^^`u8iyO=T~7>{bl6-11=KwwI#0 zxd*#geT5a+|6+=U32h1LX{d1s&FXy&%jSBqjRtpwOjoV4PnX+Y1idI>U+8)_IT#+QLqNX!0#G>G<&`Wdly6 zGpSwq_~M@uAG5s%x}|rcqrl@8eLX8Y%N)dy1dg=Ihn>txK?@@G%kg&gEU0g2CF|)S zp6vgb#U@9>3Zt25yikb-ZH~oj?cL;A?nJonnn0g+&chjXCE|11!Ek)`d@}#)6CC|b zlV|O^3U?<<;=b4)c2jJU@Ps2h} z4@uMcW@dQj3ezr&!Yrksw6S~+4D$*Sw_3emeTt?yc+w@@@nI2sd$x=>=@Qx!z7!{~ zT0|VToaBS8Y>9($GA`7#qRzww{%p5pha)$$p(_L)S;IY$R9XTyHcL@|!lp96kyo+u ztORzHEXJarciHr$$z)Qw3fp?9i%IWQqrStgQnxQ>gzll8V0rfd_sLTT*=mdBo)5tJ z=xz3~I|bj|(uV9|*Fj`50ouN7rus*O9GUqQd}DKieZCL?ioJfKt8TUISVa|4uJJ~T zOI#Ehn$Avad4>u}liB-O(`eGW+ZdCX3BptX&*~Op@8X3_MXLr)T_vdQ)mS1Dtz`Sy z2DA<;VUKhIS%FLtZJ#fAVlOnpO>qUQGEhUO_*dXNWE$J*^7Q4nunl?i-^ArbfItm$#d1= zk(lpz3vg9D8yD~!cJ6yF>WQ5K!6BAdeEKqOEz?5PTXn2CW)hR=%Vskc9$>2jlbP$^ zgSdJ1B>ZWy4u=V6hX5u97q|Jm{efx#^KY9x0%TS%(UyQ8B< z54;SFB|rA)LG6W?X!cqaHGa(IZl1rHl0%%`pU*XTTrm_HCY&Hq&qc5&D~f#-Sm=^c zMbMWVLyS_4spZESkbDwER?B{a$syO^g}fd(kM|Nym{?!tw&J+ZQE&$L&+>ex;PC6Z z^$$P)_G8jjL$U5%kZ4rrA>vlyjX#>+@hJ&qXzvukwgd<{=Ow}Xmc5n0{+NMN=I4>0 zsVm{dr+U2FFd4^Iwd04&f%x$65UAI70lO_eP`mXse))NTjFK40x3}$vz6WQS-qsS} zqH$%{ELP#@1qo1bM_DwuYCQM!=)-%yMJ%Xd9j-BYgsVFeVDRr++#P-bk2= z-tyP3G=4epORq#x)+wC2^fNIzI}ab}j^ShWd}iB=p5f-h_2j3^aA*%oBF(o#c*7)t zvH!3aH3t48a>0Ay<;fXb!LOEu?)Ij7-J^lr+=SlR73^F2Q_vD}S~0bMNas^$*uO$V z-0C0VoyQa4!o!oGw*DnPQ`LYc$sa{Ba%b%#+wKX>;yj#uFN93>s$~uP!dQ`!kC1zl zr02Ru!46+9vQ@4}?AZK(c<0O0dvRmA;qceY*}y>Xtv$zr3HzDxs3*V%D$xJZ7qHJW zLohDZm&q+Yhb{Szcp-Wk%k$lV<#C1Bb$A$yIqQr=MQLzmu{4C^!F8m>ugOHz2H4`^eEF2q=W34zYls2Uj{hyK=iU! zid&~j&_z}$kR;2&@r@!)vWXKJw|B6G8v0=5_z`n1jimd+1WxKZKhZpqE9y7P@w-uR zutqf#KgRknnR^mY|IrEq7jPU@`jGsRdy3`VdB9f2vEX4>1TMBE^gEBIX>azR^HHQ<1M}4_sQvS^EKJV|Oyfc!MN67DPF3Mu3^`&0sUSov19n zCwQDD^TNahblh}51cRZxd)s}Y+t&@N#me|={CU>1^RIZ}wbyWT)l$qnJr}D)LF8eh zm#{wyV=6goFgLV=EH^Vjs@<*x%><4{2WO#Y`&6*8H*tPY8&_!Uf~jx2Y_GMT^^Wn z3f8Ju;QqeJbpFFDqD@EpK{I|o`N=-`j-Z|G7-fxuNUyY8^bX4 ziXy$d!J3aUR^snmY9Q_Xa9kxR&11i&k&bl^^rf;Be2DKQpS})YsZCj|zsU#-e=Y*u z*+a?BGy2dP62j|lNb>JeKcW7yEI0n{58i4+`0gzQVEz6KT^X~0n=cy=3jzl4iWD(x z-kr$uXQk8R9W7v#J_GK1?&adPd~n%rg(3rif01*BRWH5EGyJNcWq3M7pL-5{rX}>Q zcK}PKm!WU836HTI3&Z23sGOb_i&>oqwWDLX@edK{k#*)(^`3C@PZ1en(N>nSUxoi$ zdB3dm)i{0%+psJ&2Xf1AK$&E0E+tI9Ii(ruSX@qRymm+9<*QwUefT1{>I z3UKl1FuFdf04hfYQKb_>X!6k*Hhz=hiqfSZb3KX=RddDh0~i|?+=x zjv$vALvsp~KyEl*EY14GHXKogN2dOuf*~YeS3hl969c>bme8l|Mnbkm zm9JB&1KC4!=+6@$FmB@z+q<=WX4A{rs*nNcMtwwZlS^-Yj-Vc{& zMX_=BCFm5J;l%qx1k``)g$wsikog~E>26UvTm4@k{PU5f9qxfRDr6C)$1CyKH8Gek z1YCl3;$hDTbFuLbD{iHe$?E=&rBdy>BFCt9w*KcM-1h1q9XwDUN?;aNZWa-DgI~CN z=5T(}cQKCta11`J_$16bY4~bw5|;jIVcuUGAR{;)Z)pe9GaKC5o4=tvJ9~_+twB9# zgw@~#X+>`uo@40~LLTjj67j1@hoce8-;wcF6vEQ*JW*~=~; z?16r6fzK}}W85+k)W5s~-d@7zJ2?VI739GuGgYkdxQ{35(#qbP(q~Socfr8x7jr-7 zk0#1j&{n<@Gua0)dwCl3v+~G+`NG~TLxQ`rOH^snMEauL5AR)C#l8DZh+d^I;(FjR z86YrmissFP!`DyoO@DN$*Ajp6xBVYs$Sc4R=aRtZ#t^~b9Z4V6M3O_jr!Y@qEKgao z6N4IotLCnzdirDNwtrcesIVDQ-l*cWJ7FZv_&Giwk^+u`pfXGMTG^AtayC=wYyW<< zgGkDmP_r^)(J^60otyK4JdoSZoTq+AIP`+ZNQJ`XtK0b3i9={-;%73zw1Vwy45#U0 zJ&>M%7^>pT=&S{vG-s0%_x`Lx=ZH7(7aHq?qHqfQ-PLE8dHE=^E$eMJIYwZ2kt$Vc zSE8B^k720#XJ+6s6j$V1gPy<=T`+PCf6>t?-WFC(mJGIl^D#xxeBuaEYqtwr>x_dCB_%R!z zk$}Id_u|r~Kp3(0biVwM59GNaMnEq%&w}2RX20U z(BUC?JU0>9H#NGm?+g?x%F%lvC8Ee-b&y^26zcOeC=ZCh11_I%gL5&t-unr(uEH`I8zU&mX^OJxQ@k5_+@uh{4piL`6ysmbzDyYexEbb&ERv-8B`Cd(FaU zQR{JZJ;9;oV^Mn7X!v1)={_8WL zBV2<~+os@A@5`X~saIrqG>Xa%{|=+Z4nxoCJkgDn-tb9xF!9ha#h2B|xOib2YZp9_ zx#dGJl|+!N@IaXOVn2H~C>rnePA7Y|>R`?41iXE}2!><eKh6P7nej=0*F;%~>%)U_raZ}zs6k42Yp?@W1a^YbE4NJ%G?ZayPZ{O93_ zhi0TLO#)LbtYBt`63r+Ig#1)ZqJA=i=@#mV-kluyeVy3)`yXuc zy3fj(EZgULnkdKr26OQeIxw*SLs#o^vvc8OtYJLM_E}BWnk9?wU-(PxABKVY(hvA` z{&JW{P;a6ve+JbJ#e;Gotf$COEU+jGs_`36~BQpt?*B zd8!;&)|P0BLr!lLyS?$j9WPSEpSsMkd*615u8fCU1BcP+YXhO zApW(6kygbO;BWu`0lV*6)rj1>hZ3HJD=E9*^DZ0c~V8qN& z0IBQqFg-UI=jomlWhBmqrS}^}%|5@;dX&IydNKpYszuO~>%WM-w#Sn#@2BC>t&Z^b zk{VCB9!8cQmc`yhOJT@EZLTY1?TeRBgco^3sN~H@a9gXC=|>Fa&9@GSb$bDS_@5N5 z-8YSYw@QTxG3wN%^FGPnQOCx)p9T9DdK^y-pq;`V=)2WEFzV|C+Yt;+H@%0=rp^%e zE)D{dmHCC}DNyC!4QbZ`pxmqj-D->Q;i*DlpKD7`jSh!JpIgAoWbPnEdo2*(+sBhrHT=U#HZ7jbAJiDlb&EY6RX>jfKX}U>K5` z2KTF~iBy3MC_I@369slj*a0WF<+KGVo-O4e+DD+;gkZ}nBkuX(BaGeUz{cA-vglV@ zGTZ*!oFv%ADQg0<27m@1D5OQY)o zrnBGMhr%?0$+CKxC4F$xkLa{0@SY35%eD-DKteQxzTEtgWa^*?qF}Ar^t6-~b(f@c zQ`i!;3D%+hPrS&Ozhh{w))Bjgm~q6<_#r;ZUr4W}y@Q@15%l)BAb4S}%iS9#@Xs-&=wBes{ZqV7~hu69y*ktc#aNPbl ze#q6OD^9Hj>(oTDvnhlXk1v5OpAL!!7PnIMac*F3pT z0H2x#vMWygOwm3Bwm$ABH)XqEb&4VPKXepYOm~YaSI>k$#v;=BeIkEh{gV0Q3U~BX zZ@}h?D*9Yrz(#i!p#P7x?A)_0BzDz$eCGLrMNQJ=>$L}9%t1|bORI+EbIzh%(Q|Rj zu}ln+(xiK42ExetQG8GPJ7(K$#chtuunC{X;8gnn{43#veGPHo=cIve7s$cn&9NZ0 zcsntBV+Xa#RU&ommNLtkdhGqg<#0RNozAK@p<$`lK!3eHFWfIB8Xz%T+%m?AB@aAL zhQ}@7hwjV+je_B5y;=B-4mqLza-kEh-i7IUZt&OtIZW|3#T#?8$$@l9dS|T^jy!%# zY-qY1FTC|)(ev%>UQ7Hen?33{xjTM`$hL7SIB&4Qjwco@RcS5iEk8!@hQCIw=3us2 zVhed?wOWLK`@quD42~*CiN-ohQ{xrKuN$3L4x%e0 zY?*9gH1>^_rRAe!#nW%plgN>AFxoMKBtDzM`5rYtorODw2jMShM7|_J@N#!NrYmEQ7}3U1RB^R_==am z#w~_Z*(u*9QD=!dERXy`{(F8wv|;K_p3$0wXUHeffs{rvY~Bl2IP)gF!FaMcy$yaU zd$FP4GV!y0G2Z>)ON`BSve|X-AwMC5ST7N>@f!qA-n0X3{@wX3|JZraFY_D--BW~G z_A*d$ESZfT7Ks(}R*}~mFN(hI;EL(=ZlZ;bhBwY(e%;2v_MNQI#^ZI!~T1XdFxjx95v!)Frr7 z(G!Y~Byc&^B%B=>OeYVLrm~IiQ9VVMHcLs^Nnb7(E(7~{t#D0W5QQ8Xm0qLGqpDc+lFh?0o5KENnZ5 zpViM|$#zFlbVHZts!P+sYlGqGqpxHMi^TGTGWej-hvU~Iz@nCTH0X-6vm(nx+G7WS zwxS*P`zX)f8msa31M|RRp(O-`ofj)^;$-W%G~wCp!8ZR)gSl)tx{fv#tIb=)j^5_x$r^4A)p7=icO~&U&+k`&7$&N_91v!@rfpNmv_A@mk z<_l8vRrzgIyn+IAaKR~qWN0u&R66<@Sac;^6sL+Uj%r!D_c;!m* zuw9yL{u5(-XPJgW3$I{Eq2P=hED7(u*Rb5sQE+72Ly$e1iVu$k+dV!LD3Q3gQ&$$n>m92P(K`snpQer8eL|iv(9<EhVm zxPL|u^PEsaRx4$}nOCQ9)oTYl-Sr;{eSQ@sEibV-=GqiK-DkcMbxh&1;CCHnME@?g z0PT&l@rTD%)}8QHoKSfgic7@VP9g=Tydz6f4Q`oVHX6UM$M z!`fLRa6*A8#@ufMk~ah1yb*k*yxPD2F#`Yz(MIqlzMXAjV)2fBeh`e@1|b(PkfGf)&62&l0r|6DIx2QhEp@a zAD*kM#WWQcAQKU?{_r{2=c0ywJ~Eh)Q3XpYqd8BhVyX+@;$QD(^0kHI@#aggWZ5Jh z?AQbfw>9w3QI5a&35?;PjGa5apPrP7BG-Q`LDKjPkN9?oUoY#z_a|lO2k{wPF5wN{ z^L9eZvN*ECc`A~5i>OVrI{tZii#&S!hqS)l4W2XlKvU)flgms3%gdMO7Udmy?mvC} zsFVyEi9+UKnDp zokFf&MSe9uaU=!fJ*CJ8wLMUC;Jv^SxWIPD>A}#6el)b_F_o}djQ1WpVaNJoP+(?> zx&b0K>sbK!d{u&}Rv(z$x-k4Z#-DyM&&3-ldi1b(0MGyUnz;qau+fH_iTIc{KdD`h zb-jb>;=mD9T_*u>eHfU;tihI0fBJjh3|=78AjLwaUfoHKXLeOXam#Mv8XylzI!n01 zvvWA%Yin7T!#$>3nnH$c9n1@iOJJ;OTck0Z`Yh5~_gp6-z>l3X{~F1ytiM#^{Nru<8co6E!6 zulaDy=o)I?|G;KXvmwt@w!@w!1Bs|cW!5|>KB6XpOsjV znTg&XXQQWr56Tk@8qpen1GXO#{BJq9OL$+ccL-#TW%k&;&=yr68iB=rO{SD$0hdo2 z(Lbk;*xe2+g4a*00EF*@yYUd>btYBdTD0Pul|tTXqYVUAZRSUec97BXDO7oMCb8&M z=PJvyP`XJUcj;@QK*eP43Zl2~TvJvT+N) z;NtP-B(E}@t!C>nN$7a3_<0s5{&c6ux@PblzD;DY%r_Qs^9IH|eM4R<7Lnuj&6w98 z3cs41QMNAylcVH$)Z@XF7mXCFrKBQHyMP;lf52wXN(?mkXUmqJfUd(G;QF{4mK>}i zYbqVN-HU~^Zp338)}~Iv|0?tGImr+U#`i^;&nz$%Z7z{gFLS$w=BFl;;6na(4k zk1-G6%o|ro8mdE|xql*+Yy^`X@Kw|$w-DM+F9e%TC#KN05+br2MD?5MP`0Rqb?W@a zg@31G+@+hK<|5BMcP7HPzCEN(z81%b$It?$@C1UQ85?pg~G7K0WS?02T z4<-prxIL%uci=C)q=U#|;><;DKzsdH4 z4yfC+gvT$PBJ6T!qQ{z>IQ(`H3u>B1Oa7LyWg7xp0!J0d;PF09Px%poU?+}Ypx@b z7c6a~EPH+=08Zp~!IaN&+&aBM6yBf1#Je2@7nn42-!L0b>mEf^v10bqcX8KV&fe?W zW8bqHvc#%RILq46+=APrV^%22JZcuN8U7TWx5{>&nsfvfHSDQ>;uzt zP{WHQ*I>BDL#FV>0E2|-DNE%oaj{AwAO3w{RXdN1D#lM^0fl~;@hqQQ_w`3zqgC8l zv;srSteAUH91(5Rz**(qc6L{jMXK(51@`|j?Do_}pQ;V~u%#Cx4zl#l+%M$#yb=&; zL__Dm+4w4Yj>vX|8-G>dhCw$B@YB6Y9Pm7xJ$Uwo$oDj0|C&)~wJaOh2pQf|q9V*! zo9LjF@%(i_7)*3+6uL*kZ)&5k>-bOT=b2~VSlJFNIi;Arb_aM@#@?oTNZBL z&l1ullQE`jHXT$SXxnz^EVOt@K(^!@TC>ZX@`pLF`EEak|GtUV?+wv3d?vZNrxbRn z%klr#FQL!WtHo>1J;&DvWO?eqDbS_84OU&Yre~h_lKFqU;oXy?cv#4R@D5!Jog9T* z*9@gTLI%*tI2i&jE@Sy?wocOXk`}!d?ITrEkt5A@6>xinKQX%3)01yB64E{8f zlQ<1kc&VQWQ)a%!1u?%sBH$|-J$V30d-sTEm^Nb6%q4cKdUnI9kua4baLzsGh8d;V=vLwQozJ)$pJNfnlb z;*g0F{LV|tFP6?Crx%%G(Z0iYdAT(W*Xn19KTYA=MO`}MQa{O_ACA7xSK0iiF=Rv1 z74{-(3pmYGrB~Li<&(@6u+s7otKX9hZ5}e*SEE{VXm=@7`WVE4aetiPc(MwA{lgHeYySgn>tXmS zcLqf6=_Trm&q2(TQ((8wo^8-Gr8oYXf%H^{6T-)#!Nfdr#UjG)swj$kuk@t1QWVOh zO#0a8SUaTsF>r2`BaWTrjHND`{ANxi`|@W5Op*DA+e>R<%!p}_5-|q0914K#spp8F z$cdzu$f3%%Ag~fR;b+GU;FmqWvc*f+vA{XUaq6~BINc>0YfM~0>&9y~d+kenkuFb% zTsp(8rUF2f83cqcM*Rjg@tziWDCzMOx>5lkxlfW7SJa?(;%Iu;zZxGGXQJn`Ky+|2 zA|G!rMds>3USvvv(^E4V@Y9L>87aoD@OyB{Vk39G`ISum&1j6`E23;Y3mt1KAb)H>XPZ1u4-8;$Ck??veY4nu>rbKbv>e@6bB5$@E5tiL zt}x5ARJ>_ngi~%kz@pd}?BJ8TgbSUwr}F2~tXD5oVb}Jx@Oq~zouW1mZn^~$^*syl@Vjsp74JYdDK(SupdwTX>%{Cv z3)1k(fyXH2v$gH5n6IToBfBNhyGV-f@0ri*qkQ>t2X!!#euPyEB=MPs5>>Yk!|btw zXZ5KLYEL_dD%XCCdsI@y2Hm|RYte38cS8rRZ`#L(J}oBWw=HMeQu<)YrPDa}#7WfY z`GaBJv1DJlq{ysPh02fgBp3I2mc_0uM*m_PSa5d$+x^Lxbs0xObXyyiZZM&41G;eI znFKs&&iSG|IXL%90m~H#23m zG$zqj1=^`&c|=(VNtx*m>Gmloxh$VerEka(x2^1Cgd5DeVnJgk3A~Fnn!Hxn#q53a z3r_}><1B&2GSw>?->mzLsF#Q(9xqwoj)zbQsX^T|azY=t(So6`=F z18ksm;U)I*hYFW`kq;wI*04GM?CI~}YU1Qex#%CJgDo`)5VPblwEf&f?KGa_7jp+p z)&7iM%7SsDUxMA&{?iyFJ)9>=nt)bS1sP;k2c|ktnV(WJZ<*f=`^;5f;I)^iJgo-; zUZi7bi8>p8ZW^fX-Hnyca##?ZL;CB_V|>tPGffU4d)AQv0t?pq4&$|GQ(I)dTB}-POP3u4@oPC^dJ7l z#n+c(xwHf~uF%BP&G*EA4piWQrw>^4*K%_E@;j&?Q|Q~N1NhHIGq@zW!gf~;WAD9= z(1x*zsIw`O?40-pj~!7L-&q+)H~k*Xry6A9T=f%h`JgsEl(m55?2_OPhwh0V9~{U- z(}s#}&+TA_izZOVQE52s@h*V+{oFJD2iy5w0wfjhvfMRkD0@}#*VcGL;aE2|YQ7wI zmbyrPW%P<2?dox8Ryf3+lAz@#Qmk#{V|H}UAlxw}AD<5l!YQNyR{V8^JCAI|=M}Zt zhB5~@u5uifg(&cw0jlI@NDcOs6rw2l5YDX~&VtG-K_Mue&CQbFzLSGs@YVt7XgrSm zH)kd(m+HVb8euRg;|YT`{_w5H29<^>&|B9R3KhUyz0gdu{2C z)C@c^<{{g0(F-q&sxiz<2PW3<#q=#P(0;>*Z5AC6`(L2=Zk@C^Fn%DlNVdfj*-yzM z>vlAkyM@8ZBj`K#eByIu8arJ*p2xaY;gcOJL2GKcI8{>21`T(DpLUJxR@w}DXJP}9 z6IszYi#y4N@MZ9zwhulHlm|;$5zVkLh# zxeoAVow#hhO#|*YuL!Ym=KP~f4^c&FEDsRQB~q>gT>m2vH@0G;u@&w-vI$F4hJ)WJ zd32pMok%bfzS!J}rv@ydSK2HD4&y<7DrX~ITkR^c`gRK!tBasdss>^;Gf~IvC1mSV zmOY-34kiMN=#a@>$Z|W&)HGJ$=MC>mV`s%fu(65QAuk!O$p~DoCL>yR^$w|w%V$lK z+~NKkpo`DVg|&(*`P(Ka><9w$PHQ%yubdRuk4OLN2GN@pWYEw??N=9F6B~^{`pqJyp#ABZ^)5;GR;2sV-lSi?YeaCRb zV?Q*mdW4;`Z{Ti;Ml7<7#NvUv^tABzhf5dG_&)^hl}Pa)+L`D)br?if^$F+UC>mEQ zjoVHJve{9Fbh+m!I;b@nHs&a!N1Sj^Sa=1FHOcV8rd%@c`!ehQ#%JN3=L&q}gk5;; z8lrHiMWT;w#0C}(|12$NP<&`%gkamLJSDxP^(%OR?Zkns~GgP=g`!_>>cmaG>cO+^~5&(=@+DcBSNs z)n)a$;$A7PRS*hSEUajy%}DBeBat|)FoD)!T~@MCnXWt10kYPPbSxXkG`jwvfk!%) zEQ??^8M|m%xi+c{wg<`3LA1{5o4~{Ui~RQ-mvMB3tuKlaNFa77`Ac^7XFANH~qI@ zzrj5`u31BG9$P{irU`s4VYi>-rcEW5FJSS7l5|dV6HHKl2P@|+LG_Gges`!hj);CB zc-tw_b}SSZd(48a11lhnIEw5eY-m(c5_mZ7=e?V+z>;uV{^isGeog4c9}oTx<0G%& zwzL1(%En!#s;{0w+zw|L|N9Z+>&~N>{xKx&Yq;FqW*n+f0^6Tv;PCUG%YK9=iR#mO z!CJ_B>46;g5uDs_en`;RHE+c}&*!n$m{l|`BS-KG4CBM2>)6F&NT9&0=jCVVodu|a;g%tY`3 zU&K3XiU?<-9GbeS10?1suaE@&Y8f;`{#R5Uecr1(Vl2-qty$o@2c6~BHHf<2K} znfcpNVlv?>T+dEGqlpdJ)ZUIR--(^i(0F)Nk&hv5IjlGQK7RGe7Y~a`!$kXEU>IP7 zL-iW*{w|K6?u>)9@ju~FVz{`(;V0Vm-y?IIZ<4zO3F7o&>1^wU;W)~5I7#U61F5a+ z*yUn1zH`NIC|9WqSz3TCxMw9cm1|+&N@B_5UI*AS zHVhVh+l$9n0rQjDj&AV{qHmiP;XX}0v5v_4%&Cnz^a$P~lxP z*m@P$37qt}ZL+Yb@j0>kTwoW!E*#$d9D)lD<_Y28v2vWy(UBHr zT#IH%(0R{He>p+%KwC7HybG`GC*oo$>+WZ`)F%q=OlZTW zb1bmaOB;P=XmXc{|1pbmdh}GxT3DqO58m--AbHCtv4zbh_R(Vk)(JZ{3CDkU&)x)G z-lw6jf+76aQi`7~w25WVf1<l#DJQ2pF>`t2@DS@ z!|gU>v8G`Zs#RUYAqPg`;|m|qdf;X7*mqkT<*LBPDAbX>t5x`Jixu;@dKS;NJ|yM` z!%NMMJSCNBf5`QbwJ3eG91Zz3&{?wuQ#*t+k%rK(Y@dUf&XdqDERsxCBFuZ)f4HOH z3#UBGf?z{gxbx%`ORvjjzFs#V*Y8AW@_tGDH~$(=@y)_hmIv_PF&(&lG76PnWV0IQ zW@4yhTsG{Z1M-?-^v-{S;D+v5EZhlLHjKj4)T!Lbe?BWXIhk#Hs75;uPRGd?%Rw$p z57$ha$iJ&x6&dJ-(W@!3;;qrAF?+HSE_m`1barnOu^r)5>RTixY`Biv^VG3_b|997 zxbVa0jWK(!B0t-I4;QTK5X;&gXQQ78?izm|R5A?&<>|(lkmQfY{IA>f+^WG3&esWT zHQ}wE-&yJl8L%r(5LhO6$Vbf=0Ma*wZo@S(^Lxss*jZuOH+#N*gc%%pI7YnjM>2M8 zA#n1A6}a1a@X*;~VNjzncOzZ2OtA`no%F-+@6;gD#uUfJhOqo+eWITCQ^0n4DQcfq zWoByo7+XAmS9;%OztY=T=1?OpJz@}l;Fb^Og-KZH+Xs5Z=b-G419{!kNnGcv^4I=X zLCa|l?&!M({rd;OD_tEdEOBJ7)Cypty)%#7(F*lvMci%N5`L$sp0(5<&JSHuyyt z3CcEwyn8nEYw;4eJGFsD7N5dX_nmR_z-m1CaWC66H5|4oR3NoV!?!M~^eXPft%EZ~ z2c9-zoRkViJ!`~|m;71KxQV1jdMK6ek`c~9vUE<|n=*Y-JTx~okXBz8kyhe8tbg!b z+;z#6l|_Z&N1DJUO)|&XZUNx%JP*9`|BB`pg`n1vI6V2-naqzB_J;{8;NF$@U|#Pk zUTy!0OxGOD^FGyzsv`5yLq7+|hG$TkcA3rejDo*%`C>`!SSA~BlPvjEjVp3LV62-r zKT`PCy%8R+hN165@zxz(f0h1YBJNx64=CI zCS6~T$7HohEAxQHq$=FnD=<<8rs22T!|YWDip{5JlJEJ;aiW|PbW~j>YlSX(sjCr0 zzH49xwwfzKr}z*A>5cba01b3HSlQ*9><2-W~kr!WlKbsc+bhOoT@&kC$=1wOiV zJfzGOI`nI~0qSbrE^Et>%E%2Z)fLn#`S_zKHahYV8pix>xEB6=DH8H(1ZOs7;HIjVIO5ALlCR~0zP))YsW<@kzB@*C z$H>yhZAPec>>4bGheUpbV2`}4Ujblr2t)>in;$<9Y8yI&3T(j4G)kP9xd^ zIaa(S5Q5U=`TaIoI;_&0+*7onK5zR84I0bs!s^K&zXlSv>Mkx$b)ohPC&2v*FV-8- zi>Izm$3wzlvD)cBJlcC56gonPRfj5?RQTZ;Z#A0mbQFL1wN-rBeL2qla7~nWfFnE% zr=cH~Qp3CIpnE?UA!iCroVJGa?BnoH%9l@3bA{Qnb%mqwK4sVyDc z^&M7O)RDv?vCwWQ2Zh3&i@S*y9~NkXrY`eoZ*C6I(A(@(wVe2*^HhFgt|jXK^d<*a z$KqTgb+{XF0G>FxRR8-+l|s(g;z=MMZLW zvMuJ$ljJha1$4pNa`FAu=kZ{w99QWJ!0MTcML9bLP^%@0Y(ZE&YONiEp&KYH5qc7y z#{}m0oxa-D?jO|AvFaCxv8KFNP ztzs6V))P%fN026ksJ}lK0vk>XGyFrayRi`5$G;JE_W8qsTn%h&4~I7y+i_u*Ce?^L zi&?D`c>G&q>N;x_^-F&apCS}_Z~b*x)D{JveQvaTA{B@^pvQO&DWA)8-4S!JGq&x6C8G~AI9LqKy|*Y=`$Pm zTAOSUUPEM~61>~w!T;^5wGFcyLr+|kAa@tf#K3uZXgYoaJ`EhmiwzP$+NFwxuHOi* z*9wTu+A`FR^(Usw2hw6!H?i5&g>b!Y4}@tZu)fK{dhVK!Yh5B?p@%PYo!Nv#CCfm; z^*Q8}7s02<61?6;LH5!}zPovb_;;8ZEq0ng&aD0iTW2(qfUWy+T;@*}8)+)|>ZbAf zdnZIY7FR)}$}U)CzZRE1S_;b6Iv{nw5*Pa!fOFO^bY5-6J8Jc)pUhcaFklwGz4!vR zorpmhe|@eHl?~08=I~p;<>~A08PMCYmKV7UAV>c=lVmk{dS;OX4a`^PW_l+?5*K8+ zl9k}Ua_AH%#QNjsw^Dq^Vq+?Q`UPnZv8K|K&2Zzx>4emMBjXJ8X@jgb*iRh_cGu3~ z3{^SuPQe7{>tsNU+f;h3K$_p(Sd7`7W>j&BC44>goEdI+qW3kms9Tyem+p+F*98`d zs?>76+(VZizTANaqrD*Yo~fv2RRyCJeXv$-3Ji8QhC1twA?=0)9r$i2JsEsjG|9Ca z^1ip*y8UZ|-Dh)grIaN z0u!+k3I&JS`#GH`bJJPyId!qG5*y&-f@E^b^9&w1aZ5C8r3#gKy$Hj4?qNxU1$-87 zCvc793wv$8;^Yt<^>I11y&%hv3;##^=p-B#3Uud&I?Rp%+C=Xv+74kj!*(%;R^cIO-Jc zn|Kq-s*Wm7rwnJt&qnB4buI;=5O7{CCxHk#gn(yxy~cPv2X{s=oX{ z&Dke#=t^_U+cSmiZWd+&R}xW`9wJgpxc5TsO?_*GAVCjNO&P8@T_ zk~i^WPg65d{rw2k{);9HS|766ue^l&*2~zUrOK*gp5n>9GpX$CiFkXIDRqB7h+KcD zOsjld`Pd$y|6OTeHFiQKV)sRmS)UJ^b|*s3^OLalcbYKsQ{!VaR*_A)=By>;o>*h{ zDAF|kqNqX0u*#L6WQP}cQ>SqsaBEa7d^#Nm%31S7b7!l;+uksYT=0<~*F_PFmm= z^$D=rQUg27Y~k#>99WZUMV@Un!u@alkotq8X`OJkxU}*e7^IzqZ93Tki)lFwvMEHR zvHQi1+NsDBI`Q$#G7Q!?5xNA6aD`Z&GuKn%wM`qziM?guDRBYJ2Q4Oz4O6*-Xb&{a zK8xGRtzg^^V=SN4MW(?VKK;m1xS4$xET4W8ryUQ+%wwLgOFN4E655!G@+-)$ukKu8 z_!CfyrRXFZA-*bU0;iH1;b^rJ{{Ar^O1<{6uC*_a8f56Mz|ocVI!> zWzl1;vDkkk4K6J`3%|Q+`OucV7+)uYJ)`Am((Vpok+%>c^&SzY4;<%(ZiQWSQ`u>; z(6RRw?lb&WMXy&9|5cIDA1uw&%Z0Q4%xy4xtqZS>pGaqyPU8p9 z7?^W=#*%M6V^riVfE4(|x_xn3Or1XL+gM zpoHgeBs3GP^19$t|7P;%t~9aVTLaJG4LbY~$b8{FICalUvgi2*fk7k9dlV4D;&X|n zkW(wZQUmR4n(=#vq`&Ol zDKDAR%m~rQ;r+NE#Rp83eEHw~1lJE9%R~OTVT|C6IMezb6&jy|!893s$A=>fZ$lZa zdy8@CMjZggFN(K^#Fy_OTlON3w#`P9_^bh zlG=1Fm@?UcUfcB7R?pF%N91c#1$Sf{+E$>Q!UdKrGlZ<{_{Tz%AF+d<1=iEvfjqr6 z4<^4l3}2SqC;n4YVe04=I4WWVE|X4yoT*W$<#Q0K1a5QtV?EURZyag_Wa7MPJ#O=J zEHEn{Y*t*wch_b7(**vrt)#|CO~42;HjArq<%vOs>jcxWA&mnvNbl5!U97@!7k?@FU@OrhA-kfR7@4eLK z`Kp~|5tkBJ&c+iEEE-PB417?`UgOvD&EWESEXD}=uRx*}mwH!pKA z@;^no4<9Fym(Jmc6%nLO=n&)>9E3ZmNwz)8$8ccsb|QH4aO0-8*xUXL3}a)-gxV~4 zFtUM&hlDdrfd%MmDuwcG!tAr{k~k_~nO-!R1o}fvsO9{1WTBN48*qCh-C~f(^Y5$C zDIZ!Pykk4~*%G{X(jMiHBZ->)3~!B}i)v;=`S3JDdb@TfP8;}uZ+7#+EhS$eYxO;L z&{2_o)T@Gyx#QX4Mcw4Gz6JbOGK)Uw4d>&=#K4Ma@1Y~A4Q(eIz{AF4aOqwl@C+$B zX;1*nw@~E2JYNY}&Pvi4V#GZ(B=PVg6|~h}kMF}>(CkRF;LLnSj((W}sXJccitR2e z=ED~_HdqBb)&65r^K@|ZTOWveXhH8SZNn2Mjzadfarmfc5gE5B9DCJ_VCnlV@>SlZorU9uY`N|bm}QlR)sVlrC&Di=4(4q>8<0J`XT6U($n!?)(D;O4jy z4ElbP$Dc>ShG8Svzt#gV?z<@lPd$YHeUHZ0duQRafdp%t83lht4!C)XJQ+2p6xFrA z!`Ph}n3ZP%+vsmxqgsH`8x^qdr#yY>c}eV`_mfop-i1f+)w7dh-m>t%M~HHJ;aFD> z^R({AexF|~S|^UF{48Vn`VJ^6qh#Q&(W1u9PWW8mB+OO)%dFH4QEIA$IK%M;1ho#M z+wa@aE0M1BQpOY8q{}k6U|A$p`{KwC2KpdgbG4nFHUNi=NW(Mg6Jfoj1hiQ95yNjM z;JZZ-ch9JS@5Z^{wlN%JH*3N7kVubfd zmfujDLPrf<0$tfVc$?uf!L@e*J@meUHXHN66 zTy7R*7w+detA5ZX{ii5DT=<(LOl~I+qE|(Er3R#KjUm9TQ_fMHG#g8V@mZO7Uvp z63{uY0OcedIlt!*W!-Mr9jj0GriF8Zk!3*4Z-@fSbm`Bsf8Z5s%%_i8N`kUq;seix z_{-re=zY5izZ5&jr3elB%ES(tmkdr_Ybdr$Sq=S*1h2jN5OmzDK#hk+@QED_@ZT1Z zc(uz{!23$vWQY#;+_jWg+0JL%GQX0rW-oX&+?G;jXD1_vD{u zp~Jix{wc-{k1Fx@nUV19K4+gK55UN6?&$tIhx}W(od`QCk>NIH^q6ObjgDM!=>2AU z%=E>rsR8UzUk=Q@rA2J##^cc7IQSy5oa@E{8E?1&t&Wd{Dcx_Gwp19LjBI9>73)AF zq8WdURHI`?-e6Pir;39w<_q4HBKYY!T{JXhCZ>#3#HjoPQQxu#oY@u)DwE~d)oclJ z_{0_QwvyR&%*}V$(kIKq@g5}gX;5Z-4%S_~3{9=gAn)rhF1$Mo-*t4L=b;AX^&$vN z#m9iE_>sIcU&!8MM3dB|#gm86g#E*8$c@KdFv~N}c0o)6Ed2GZ?1!={E_xe;M~@nk z$%mv$gK8g>U-37{_A^4_wz;VP!Md& zSNzGslJ_BGm(g$BWmyDx*a~(jMB)sR%vbt*v)tV^u%sY^WLPAz_Fdyi|Lb+Q_{(5O zz0^TOa>Z=THe-VJV)XgX8e{Yf$>wqXWXhktpe6L1?Rv6Vk=Hjo^twVk-~SBUYn;YM zsuy77TqQRD+Y3CJaT-1cOY-r#&&hR@*W%tWUZ|^m6P};ZhfCZKK6?wisKdX>`s)L^ z`iXN;H&+Idr^k_n>AkqN{3m<=;SS8nCS}rneQ@P|2IRMA6R!nxk!Vr)v2hD}wSL3y zGrLjhiUlgVDM5bMLpI|2Bz$jti>23pBn2l*L_PNx;?!}1zhKr*T>Yt%q@^&7clax^ zlwXcNmR`b>e+~JN);lb9r4rqd8;M#9eK_p=MzU>P9TfZRAZLWU!q0u)5I%k|Z$CJX zN`ziT3IAmAhrgpSH?mb*vM zB$2Z4xaDZwl#E_W-ovV=zOa9#28~LoCV|&NY*$2mBPLIr;bL(V6cJZ8wPyvpK9J)YL+?I5!(}A{vD}riSIH2pK;6NSqgT3apoJ!zb5N#gTr|Ol|sVl6!e3RR6ibl4&;i)R9lFG@8@N zQFRcdnu_LGTgCrbE{CxdSMipsH8z;fBjqy?zv4g;pag}TTE%gm<%GdvKsQF zr?IX($+%xP8qABMQ1RCfVyF^?uv3O>6_qf-Pb%))tw?XL^WlG)Hl^~3e7k=GkfJ7$ z)ddm$cR&N(TheV42ZZB?NDrtI8kP>#ZkXzP1zuU#!gS?8uqvw})gcdsvzr3Xiq?l+ zfofFVWhXeaJp{MxQ9MJ-71ZbD;$KZ|xN%!VroZ`(p*9mqa8?pr2}{HCCN^Nu@D2Bj zP$wtGSkjl)60p1@65ZE7BX3rH#_Pj%`4@e_dz&M{E?1krcOOjcJqFYF&dIQRUIBRj z`hX$A`96B(F?K>VkxWunwv8Jp z`9G0H(q%ZFD9_JPd-`Y8LKyo^mrwIfgz_;huq#0mWEu~Fy54mROjV+hHEN`{vI~Qj z??Kb}PFB}Dj|~w>_^UcLvv$)kJ~`H(Pn+jWzl)yYrp>v_ z9S6}*6DEVv$F=N|Y%%;@Bk(Ma9l=9lLbRiOM3<$vq4DV?AuIm?p28cJcux=fHYP&y zSZ$cDPzqz*lxhFhY_`ifkiG751|l0u_N};ueXrfAmiz;u-=)PT#yw=d0-JB!i4U-S zuL2}F33p;wJD|c)N90(x6YeGVqV|@27}3~^c4v3eYa0WwFK{Z9ew#0fKH<(Q&*ZWy z<0$5#aRZ;EDHm(5j|^c}i5l-tR+cO>}PAqsX(^YSM$n z_x{2F^&0r=ZG+QC=fm+Ai{OC9OfvRKF}eO)6^cjX!_I3bh);Ek`0D1Lu;SEX{3-Oj zJQr}-vmgaZ9vTt1$wzq4wTHy(O<38*odW?(3HEEPp(pBsVfKOa}hCnkb3VR!?4+L47; z3blB@WGhuze~%2my9wK-cw*gxQaov5!s^|;m|x{wdKyFc;>14quVon64cJIzZRgQk zoBi-aClBF?w0NON3LOs)=Jh#o80q(y^*84Tze_1PeS!;DoO_3akGx3KT;(7!WH|)L zE`}NFd)Yj{V6>T_Axg;^LT7mdfa|$Ua7(N}Q}+v*(W_5j%AJ|myfYWYlZIlr8l^L< zh3x#TI3{L`>6W?%4A4CY1~;^!Zd)qc+gA&U@`iN#Iu*XsE(H8Xsc|piUO{0+3)-dj zLv5}Vx<@AAz(<{69^wZPZ}Uit5I45Cn*r;`6;RU~?}ZtXESH!5$jH-qP@kAa_FCIh zZ~GxwwmN|x7kKdVb}uKV&WFLSlv(7a*JO6yE`}MTPk@`Us&t;v11LH=lfQ^F5;!O3 ztnJf#@rO}IiDjX{nwhp8v)}Edvp@8awqG4&r*8#I9yEb3k3WUSZmID66Cc4}kC2ib zOTgM}HR=8mh>jbIA^(yszyH7zcCD+!tdB8ZNepP=Od*$Zu?t#8Ok%rioCIf^8aI;} zP7@w{Av=#plKboovn*cmt;QsoL^#J&$0-+va{GNZt3#B^Bt;0W^!R)pWu znM7g#K4|j30tv6;apmkxP&p^X%oZ&X^&Z(zH%;FmIBmL7<47|*+By+oIcz4+xx@mGI3@e&V_nDkxs;Mb3Vp1W@j=Yb1&Yi`7U8?NC%^7T0 z#BTf?HXSo`Q>fczGji$mB=RzG2t1zHO4gY;iFaLIBe0B0nc~p|=$ShhTQj8a?4CA} zU)CE4o_rqs7lh(`k0q!Vy9Bh6~!=BW-P59xsH69RzzC^>Y=YdosuV8_)MR@sQX-vDtz~#QrlMZt)}0_g`daJ z4YQi)^7-{(ak3sV-y8F0JP*I5Tlt`XIh#&lJ#Z3n;@>f zS=6d(NKS9x1QF_X^i_fxrmnMKl~Yr&Io(v?p6XM_mC8grcsI7CJj5j3L1^*kFX|PB zVekFL>~~1LSkHGdK5iXHkBnUe(Z9>ldW}13%_)W>)Rez2@&%Z&k{xOEpuq`2a3Fjk zI$hty^S2wa!R^tsl-+wb4bOxY;LwJF zyr%Q2;9*j++kLeX-hM!~aElJNu+A&X6FLq9KAeE};Zyj?xx<)k@lm1UA;U`>5Mupj z!`R#q2z~NZJV#_j6GE4vq4`ZzzqB20%vk{C_uHZN;%q)%?guyf7cqMu{PQWfiKNnnMmh{wH+bNApifUD)ZD1I4MaG)-0+7fxv>50_TK z@|$YBqUa&qm>Y&q;XHb}^rPjSJUCPR7aEMBVcC;RQOlesw3t#_kC8 z@fP8L_WLybrzF29lLhG$1i$CEGay>>mX&{QfsmJVINs+Sl~yAJt}CPFxHx?LBasy@ zQV~s9s3v6RLIrohf27y<1DF1B9Bx$PLC*INFtl|K(<+MrV;UyRYvlNT3%e&L8gz*5jh-&{)Jj$~lm-^e_Ave~&Hd&B9=9E&f=3Bl$BS zoywWq7VR&nBeTw0^AloWmYqW|Rqrm4?nZbLX2`EtRAI^zHB?KorT<<3zdJVqXT80` z&Kksn!v_((Go1{H*7m%uWF8d!bYmx}9j_F=C*!@O*$C;CqHA7-EKl&Y+gHicNK0EN z4-sMUoMho(I*uOFwqt2KB*E@>10LKZ63Y&FfKqFx&>hlO&{um2I1hgh8FH2AX+0MV zF0G|8f!fs4Z8ZNWR|P?%^l2U60o;nh3U&%t`Nd&1lcUCZJ6ZZ8CtCl07*?FphF<+r zJe&0s;v}*#a7P)dOlEK`e}s^Ss)OY(D#c+X(L7`@XOfhen zzO4j1RTop+!w&>DrWU++(L*<)yS&TqnnVgve;}#3^k;r$D=W>C+ zqk24`xgX}=FyX6$=VShpaWM9mGnINuAxX~=KN*E)kpj{ zO&1#MUO}78S4b*O$M1pKV5$9)j6L)kD)QC%E}aRYu?K~IW!-%+vQ2}orF&7PkAnD= zC5(P=NF(oyfz62|8N+R0@2dt>$heEkry9`brojS--XC?98o*ik9O@t5%0AvnA(z55 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%0Ai1vl5?w$_dH0qOjRlKgxQKVZ_3>x;&f6s9 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¥Cd{RmLJLv8>MbmJ(gV}~i-6n^Kfdyd8J3@& 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 => ws.once("listening", resolve)) + const endpoint = `ws://127.0.0.1:${(ws.address() as AddressInfo).port}` + const factory: SocketFactory = (_url, headers) => new WebSocket(endpoint, { headers }) + const close = () => { for (const client of ws.clients) client.terminate(); ws.close() } + return { ws, factory, close } +} + +describe("provider wire qualification over actual local WebSockets", () => { + test("Standard config, exact PCM, ack and final reach the local admitted turn", async () => { + const fixture = await server() + const packets: Record[] = [] + let authorization: string | undefined + let resolveTurn!: (turn: FinalTurn) => void + const completed = new Promise((resolve) => { resolveTurn = resolve }) + fixture.ws.on("connection", (socket, request) => { + authorization = request.headers.authorization + socket.on("message", (data) => { + const event = JSON.parse(data.toString()) as Record + packets.push(event) + if (event.type === "session.update") socket.send(JSON.stringify({ type: "session.updated", session: openAIConfiguration().session })) + if (event.type === "input_audio_buffer.commit") { + socket.send(JSON.stringify({ type: "input_audio_buffer.committed", item_id: "item", previous_item_id: null })) + socket.send(JSON.stringify({ type: "conversation.item.input_audio_transcription.delta", item_id: "item", delta: "wrong temporary" })) + socket.send(JSON.stringify({ type: "conversation.item.input_audio_transcription.completed", item_id: "item", content_index: 0, transcript: "Ship it." })) + } + }) + }) + const failures: string[] = [] + let connection: ProviderConnection | undefined + try { + connection = await ProviderConnection.connect({ model: "standard", apiKey: "test-key", assertAuthority: () => {}, + onFinal: resolveTurn, onFailure: (error) => failures.push(error.code), socketFactory: fixture.factory }) + connection.appendAudio(new Int16Array([-1, 0x1234])) + connection.commitTurn("admitted-turn") + expect(await completed).toEqual({ turnId: "admitted-turn", text: "Ship it." }) + expect(authorization).toBe("Bearer test-key") + const audio = packets.find((packet) => packet.type === "input_audio_buffer.append")?.audio + expect([...Buffer.from(audio as string, "base64")]).toEqual([255, 255, 52, 18]) + expect(failures).toEqual([]) + } finally { connection?.close(); fixture.close() } + }) + + test("Meeting sends binary PCM and publishes final tokens only after its flush marker", async () => { + const fixture = await server() + const frames: Uint8Array[] = [] + let config: Record | undefined + let resolveTurn!: (turn: FinalTurn) => void + const completed = new Promise((resolve) => { resolveTurn = resolve }) + fixture.ws.on("connection", (socket) => socket.on("message", (data, binary) => { + if (binary) { frames.push(new Uint8Array(Buffer.from(data as Buffer))); return } + const event = JSON.parse(data.toString()) as Record + if (event.model) config = event + if (event.type === "finalize") { + socket.send(JSON.stringify({ tokens: [{ text: "wrong temporary", is_final: false }] })) + socket.send(JSON.stringify({ tokens: [{ text: "Ready.", is_final: true }, { text: "", is_final: true }] })) + } + })) + let connection: ProviderConnection | undefined + try { + connection = await ProviderConnection.connect({ model: "meeting", apiKey: "test-key", assertAuthority: () => {}, + onFinal: resolveTurn, onFailure: () => {}, socketFactory: fixture.factory }) + expect(connection.sampleRate).toBe(16_000) + connection.appendAudio(new Int16Array([-1, 0x1234])) + connection.commitTurn("meeting-turn") + expect(await completed).toEqual({ turnId: "meeting-turn", text: "Ready." }) + expect(config?.model).toBe("stt-rt-v5") + expect(config?.enable_speaker_diarization).toBe(false) + expect(config?.language_hints).toBeUndefined() + expect([...frames[0]!]).toEqual([255, 255, 52, 18]) + } finally { connection?.close(); fixture.close() } + }) + + test("expired authority tears down the actual socket and rejects further writes", async () => { + const fixture = await server() + let expired = false + let resolveClosed!: () => void + const closed = new Promise((resolve) => { resolveClosed = resolve }) + const failures: TranscriptionError[] = [] + fixture.ws.on("connection", (socket) => { + socket.on("close", resolveClosed) + socket.on("message", () => socket.send(JSON.stringify({ type: "session.updated", session: openAIConfiguration().session }))) + }) + let connection: ProviderConnection | undefined + try { + connection = await ProviderConnection.connect({ model: "standard", apiKey: "test-key", + assertAuthority: () => { if (expired) throw new TranscriptionError("expired") }, + onFinal: () => {}, onFailure: (error) => failures.push(error), socketFactory: fixture.factory }) + expired = true + expect(() => connection.appendAudio(new Int16Array([1]))).toThrow("expired") + await closed + expect(failures.map((error) => error.code)).toEqual(["expired"]) + expect(() => connection.commitTurn("late")).toThrow("expired") + } finally { connection?.close(); fixture.close() } + }) + + test("provider error contents never enter surfaced error messages", async () => { + const fixture = await server() + fixture.ws.on("connection", (socket) => socket.on("message", () => socket.send(JSON.stringify({ + type: "error", error: { message: "private transcript or secret", code: "bad_key" }, + })))) + const failures: TranscriptionError[] = [] + try { + await expect(ProviderConnection.connect({ model: "standard", apiKey: "test-key", assertAuthority: () => {}, + onFinal: () => {}, onFailure: (error) => failures.push(error), socketFactory: fixture.factory })).rejects.toThrow("provider") + expect(failures[0]?.message).toBe("Grid transcription provider") + } finally { fixture.close() } + }) + + test("closing on the first turn prevents delivery of a later final in the same event", async () => { + const fixture = await server() + let commits = 0 + fixture.ws.on("connection", (socket) => socket.on("message", (data, binary) => { + if (binary) return + const event = JSON.parse(data.toString()) as Record + if (event.type === "finalize" && ++commits === 2) socket.send(JSON.stringify({ tokens: [ + { text: "one", is_final: true }, { text: "", is_final: true }, + { text: "two", is_final: true }, { text: "", is_final: true }, + ] })) + })) + const delivered: string[] = [] + let resolveDone!: () => void + const done = new Promise((resolve) => { resolveDone = resolve }) + let connection: ProviderConnection | undefined + try { + connection = await ProviderConnection.connect({ model: "meeting", apiKey: "test-key", assertAuthority: () => {}, + onFinal: (turn) => { delivered.push(turn.turnId); connection?.close(); resolveDone() }, + onFailure: () => {}, socketFactory: fixture.factory }) + connection.commitTurn("one") + connection.commitTurn("two") + await done + expect(delivered).toEqual(["one"]) + } finally { connection?.close(); fixture.close() } + }) + + test("unexpected Meeting EOS closes transport and forbids later audio", async () => { + const fixture = await server() + let frames = 0 + fixture.ws.on("connection", (socket) => socket.on("message", (_data, binary) => { + if (binary) frames++ + else socket.send(JSON.stringify({ tokens: [], finished: true })) + })) + let resolveFailure!: () => void + const failed = new Promise((resolve) => { resolveFailure = resolve }) + let connection: ProviderConnection | undefined + try { + connection = await ProviderConnection.connect({ model: "meeting", apiKey: "test-key", assertAuthority: () => {}, + onFinal: () => {}, onFailure: resolveFailure, socketFactory: fixture.factory }) + await failed + expect(() => connection?.appendAudio(new Int16Array([1]))).toThrow("provider") + expect(frames).toBe(0) + } finally { connection?.close(); fixture.close() } + }) + + test("invalid connect options allocate no socket and factory errors stay coarse", async () => { + let allocations = 0 + const options = { model: "standard" as const, apiKey: "test-key", assertAuthority: () => {}, + onFinal: () => {}, onFailure: () => {}, socketFactory: () => { allocations++; throw new Error("private factory detail") } } + await expect(ProviderConnection.connect({ ...options, connectTimeoutMs: 0 })).rejects.toThrow("protocol") + expect(allocations).toBe(0) + await expect(ProviderConnection.connect(options)).rejects.toThrow("Grid transcription provider") + expect(allocations).toBe(1) + }) + + test("missing configuration acknowledgement times out and closes instead of hanging", async () => { + const fixture = await server() + const failures: TranscriptionError[] = [] + try { + await expect(ProviderConnection.connect({ model: "standard", apiKey: "test-key", assertAuthority: () => {}, + onFinal: () => {}, onFailure: (error) => failures.push(error), socketFactory: fixture.factory, connectTimeoutMs: 30 })).rejects.toThrow("provider") + expect(failures.map((error) => error.code)).toEqual(["provider"]) + } finally { fixture.close() } + }) +}) diff --git a/server/packages/grid-transcription/src/socket.ts b/server/packages/grid-transcription/src/socket.ts new file mode 100644 index 000000000..da0d2cc33 --- /dev/null +++ b/server/packages/grid-transcription/src/socket.ts @@ -0,0 +1,193 @@ +import WebSocket from "ws" +import { pcm16le } from "./pcm.js" +import { TranscriptionError, type FinalTurn, type Model, type TurnDecoder } from "./protocol.js" +import { OPENAI_URL, OPENAI_SAMPLE_RATE, OpenAITurnDecoder, openAIConfiguration, openAIConfigurationAccepted, openAIAppendAudio, openAICommit } from "./providers/openai.js" +import { SONIOX_URL, SONIOX_SAMPLE_RATE, SonioxTurnDecoder, sonioxConfiguration, sonioxAudio, sonioxFinalize, sonioxKeepalive } from "./providers/soniox.js" + +const MAX_SOCKET_BYTES = 128 * 1024 +const MAX_EVENT_BYTES = 64 * 1024 + +export type SocketFactory = (url: string, headers: Readonly>) => WebSocket +export type ProviderOptions = { + model: Model; apiKey: string; + assertAuthority: () => void; + onFinal: (turn: FinalTurn) => void; + onFailure: (error: TranscriptionError) => void; + connectTimeoutMs?: number; + // Trusted dependency injection for controlled local qualification, never client-supplied. + socketFactory?: SocketFactory; +} + +export class ProviderConnection { + readonly sampleRate: number + private readonly socket: WebSocket + private readonly decoder: TurnDecoder + private closed = false + private ready = false + private keepalive: ReturnType | undefined + private failure: TranscriptionError | undefined + + private constructor(private readonly options: ProviderOptions) { + options.assertAuthority() + if (options.model !== "standard" && options.model !== "meeting") throw new TranscriptionError("protocol") + if (typeof options.apiKey !== "string" || !options.apiKey || options.apiKey.length > 4096 || /[\r\n]/.test(options.apiKey)) { + throw new TranscriptionError("protocol") + } + const timeoutMs = options.connectTimeoutMs ?? 5_000 + if (!Number.isFinite(timeoutMs) || timeoutMs < 1 || timeoutMs > 30_000) throw new TranscriptionError("protocol") + this.sampleRate = options.model === "standard" ? OPENAI_SAMPLE_RATE : SONIOX_SAMPLE_RATE + this.decoder = options.model === "standard" ? new OpenAITurnDecoder() : new SonioxTurnDecoder() + const headers: Record = options.model === "standard" ? { Authorization: `Bearer ${options.apiKey}` } : {} + const factory = options.socketFactory ?? ((url, values) => new WebSocket(url, { headers: values, maxPayload: MAX_EVENT_BYTES })) + this.socket = factory(options.model === "standard" ? OPENAI_URL : SONIOX_URL, headers) + } + + static async connect(options: ProviderOptions): Promise { + let connection: ProviderConnection | undefined + try { + connection = new ProviderConnection(options) + await connection.open() + return connection + } catch (error) { + connection?.close() + throw error instanceof TranscriptionError ? error : new TranscriptionError("provider") + } + } + + private open(): Promise { + const timeoutMs = this.options.connectTimeoutMs ?? 5_000 + return new Promise((resolve, reject) => { + let settled = false + const finish = (error?: TranscriptionError) => { + if (settled) return + settled = true + clearTimeout(timer) + if (error) reject(error) + else { this.ready = true; resolve() } + } + const timer = setTimeout(() => { + const error = new TranscriptionError("provider") + finish(error) + this.fail(error) + }, timeoutMs) + this.socket.on("error", () => { + const error = new TranscriptionError("provider") + finish(error) + this.fail(error) + }) + this.socket.on("close", () => { + if (this.closed) return + const error = new TranscriptionError("provider") + finish(error) + this.fail(error) + }) + this.socket.on("message", (data, isBinary) => { + if (this.closed) return + try { + this.options.assertAuthority() + const bytes = Array.isArray(data) ? Buffer.concat(data) : data instanceof ArrayBuffer ? Buffer.from(data) : data + if (isBinary || bytes.byteLength > MAX_EVENT_BYTES) throw new TranscriptionError("protocol") + const event: unknown = JSON.parse(bytes.toString("utf8")) + const finals = this.decoder.accept(event) + if (this.options.model === "standard" && openAIConfigurationAccepted(event)) finish() + for (const turn of finals) { + if (this.closed) break + this.options.assertAuthority() + this.options.onFinal(turn) + } + if (this.options.model === "meeting" && typeof event === "object" && event !== null + && "finished" in event && event.finished === true && !this.closed) { + // No owner-requested end-input exists here: unexpected EOS retires this socket. + this.fail(new TranscriptionError("provider")) + } + } catch (error) { + const safe = error instanceof TranscriptionError ? error : new TranscriptionError("protocol") + finish(safe) + this.fail(safe) + } + }) + this.socket.once("open", () => { + if (this.closed) return + try { + this.write(JSON.stringify(this.options.model === "standard" + ? openAIConfiguration() : sonioxConfiguration(this.options.apiKey)), false) + if (this.options.model === "meeting") { + // Soniox has no distinct configuration-ack event. This is transport-ready, not audio proof. + finish() + this.keepalive = setInterval(() => { + try { this.write(JSON.stringify(sonioxKeepalive())) } + catch (error) { this.fail(error instanceof TranscriptionError ? error : new TranscriptionError("provider")) } + }, 10_000) + } + } catch (error) { + const safe = error instanceof TranscriptionError ? error : new TranscriptionError("provider") + finish(safe) + this.fail(safe) + } + }) + }) + } + + appendAudio(samples: Int16Array): void { + this.perform(() => { + this.assertWritable() + const bytes = pcm16le(samples) + this.write(this.options.model === "standard" ? JSON.stringify(openAIAppendAudio(bytes)) : sonioxAudio(bytes)) + }) + } + + // Call only after API admission, immediately before wire commit in this same track actor. + commitTurn(turnId: string): void { + this.perform(() => { + this.assertWritable() + this.decoder.beginTurn(turnId) + this.write(JSON.stringify(this.options.model === "standard" ? openAICommit() : sonioxFinalize())) + }) + } + + private perform(operation: () => void): void { + try { operation() } + catch (error) { + const safe = error instanceof TranscriptionError ? error : new TranscriptionError("protocol") + this.fail(safe) + throw safe + } + } + + private assertWritable(requireReady = true): void { + if (this.closed) throw this.failure ?? new TranscriptionError("stopped") + this.options.assertAuthority() + if ((requireReady && !this.ready) || this.socket.readyState !== WebSocket.OPEN) throw new TranscriptionError("provider") + if (this.socket.bufferedAmount > MAX_SOCKET_BYTES) { + const error = new TranscriptionError("overflow") + this.fail(error) + throw error + } + } + + private write(data: string | Uint8Array, requireReady = true): void { + this.assertWritable(requireReady) + if (this.socket.bufferedAmount + Buffer.byteLength(data) > MAX_SOCKET_BYTES) { + const error = new TranscriptionError("overflow") + this.fail(error) + throw error + } + this.socket.send(data, (error) => { if (error) this.fail(new TranscriptionError("provider")) }) + } + + private fail(error: TranscriptionError): void { + if (this.closed) return + this.failure = error + this.close() + this.options.onFailure(error) + } + + // No admission/queue flush occurs here. Its owner completes accepted turns before this deadline. + close(): void { + if (this.closed) return + this.closed = true + clearInterval(this.keepalive) + this.decoder.close() + this.socket.terminate() + } +} diff --git a/server/packages/grid-transcription/src/supervisor.test.ts b/server/packages/grid-transcription/src/supervisor.test.ts new file mode 100644 index 000000000..a209be744 --- /dev/null +++ b/server/packages/grid-transcription/src/supervisor.test.ts @@ -0,0 +1,275 @@ +import { describe, expect, test } from "bun:test" +import { execFile, execFileSync } from "node:child_process" +import { mkdtempSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { join } from "node:path" +import { fileURLToPath, pathToFileURL } from "node:url" +import { WatchdogLease } from "./watchdog.js" + +const identity = { runId: "fixture-run", claimEpoch: 1, generation: 2 } +const fixtureDirectory = mkdtempSync(join(tmpdir(), "grid-supervisor-test-")) +const nodeExecutable = execFileSync(process.env.GRID_TRANSCRIPTION_TEST_NODE ?? "node", ["--print", "process.execPath"], { encoding: "utf8" }).trim() +const nodeVersion = execFileSync(nodeExecutable, ["--version"], { encoding: "utf8" }).trim() +// Keep fixtures as bounded diagnostic evidence; no workspace or environment files are read. +writeFileSync(join(fixtureDirectory, "package.json"), '{"type":"module"}') +execFileSync(nodeExecutable, [fileURLToPath(new URL("../node_modules/typescript/bin/tsc", import.meta.url)), + "--ignoreConfig", "--target", "ES2022", "--module", "NodeNext", "--moduleResolution", "NodeNext", "--strict", "--noUncheckedIndexedAccess", + "--skipLibCheck", "--types", "node", "--verbatimModuleSyntax", "--outDir", fixtureDirectory, + fileURLToPath(new URL("./supervisor.ts", import.meta.url)), fileURLToPath(new URL("./watchdog.ts", import.meta.url)), +], { cwd: fileURLToPath(new URL("..", import.meta.url)), env: {}, encoding: "utf8" }) +const roomChildPath = join(fixtureDirectory, "room-fixture.js") +writeFileSync(roomChildPath, ` +import { appendFileSync } from 'node:fs'; +let config; +const record = (event) => appendFileSync(config.events, JSON.stringify({event, at:Number(process.hrtime.bigint()/1000000n)})+'\\n'); +process.on('SIGTERM', () => { record('term'); if(config.mode !== 'stubborn') process.exit(0); }); +process.on('disconnect', () => process.exit(0)); +process.on('message', (message) => { + if(message.type === 'start') { + config=message.payload; + setInterval(() => process.send?.({type:'heartbeat',sentAtMs:Number(process.hrtime.bigint()/1000000n)}),100); + record(process.env.GRID_SUPERVISOR_INHERITED_FIXTURE === undefined ? 'environment-clean' : 'environment-leaked'); + record(process.env.EXPECTED_CREDENTIAL === 'fixture-value' ? 'credential-present' : 'credential-missing'); + if(config.mode !== 'never-ready') process.send({type:'ready'}); + } else if(message.type === 'stop') { + record('stop-'+message.reason); + process.send({type:'stopped'}); + if(config.mode === 'graceful') setTimeout(() => process.exit(0), 80); + } else if(message.type === 'renew') record('renew-'+message.payload?.marker); +}); +`) +const moduleUrl = pathToFileURL(join(fixtureDirectory, "supervisor.js")).href +const watchdogUrl = pathToFileURL(join(fixtureDirectory, "watchdog.js")).href +const standardHarness = ` +import assert from 'node:assert/strict'; +import {readFileSync} from 'node:fs'; +import {setTimeout as delay} from 'node:timers/promises'; +import {NativeRoomSupervisor} from ${JSON.stringify(moduleUrl)}; +import {monotonicMilliseconds as now} from ${JSON.stringify(watchdogUrl)}; +process.env.GRID_SUPERVISOR_INHERITED_FIXTURE='must-not-leak'; +const events=${JSON.stringify(join(fixtureDirectory, "events-"))}+process.pid+'.jsonl'; +const identity={runId:'fixture-run',claimEpoch:1,generation:2}; +const supervisor=new NativeRoomSupervisor({roomChildPath:${JSON.stringify(roomChildPath)},environment:{EXPECTED_CREDENTIAL:'fixture-value'}, + heartbeatIntervalMs:100,heartbeatTimeoutMs:2500,stoppingGraceMs:5000,authorityGraceMs:10,termGraceMs:100,killExitTimeoutMs:2000,startupTimeoutMs:10000}); +const start=(mode,leaseMs=15000)=>supervisor.start({identity,requestedAtMs:now(),leaseMs,expiresAtMs:now()+30000,payload:{mode,events}}); +const records=()=>readFileSync(events,'utf8').trim().split('\\n').map(line=>JSON.parse(line)); +const absent=pid=>{try{process.kill(pid,0);return false;}catch(error){return error.code==='ESRCH';}}; +` +const crashParentPath = join(fixtureDirectory, "crash-parent.js") +writeFileSync(crashParentPath, standardHarness + "const ready=await start('stubborn');process.send({type:'ready',pid:ready.pid});") +const brokenWatchdogPath = join(fixtureDirectory, "broken-watchdog.js") +writeFileSync(brokenWatchdogPath, "process.disconnect();setTimeout(()=>process.exit(0),50);") +function runNode(script: string): Promise { + return new Promise((resolve, reject) => { + execFile(nodeExecutable, ["--input-type=module", "--eval", standardHarness + script], { + timeout: 25_000, maxBuffer: 16 * 1024, env: {}, + }, (error, stdout, stderr) => { + if (error) { reject(new Error(`Compiled ${nodeVersion} fixture failed: ${stderr.slice(0, 4_000)}`)); return } + try { resolve(JSON.parse(stdout)) } catch { reject(new Error("Fixture result protocol")) } + }) + }) +} + +describe("native room supervisor on compiled Node", () => { + test("a stopped native child is killed while parent and API lease stay healthy", async () => { + const result = await runNode(` +const ready=await start('stubborn'); +const renew=setInterval(()=>{try{supervisor.renew(identity,now(),15000);}catch{}},250); +process.kill(ready.pid,'SIGSTOP'); +const receipt=await supervisor.exitReceipt; +clearInterval(renew); +assert.equal(receipt.reason,'child_stalled'); +assert.equal(receipt.signal,'SIGKILL'); +assert.equal(absent(ready.pid),true); +console.log(JSON.stringify({reason:receipt.reason,parentHealthy:true})); +`) + expect(result).toEqual({ reason: "child_stalled", parentHealthy: true }) + }, 30_000) + test("stop closes admissions synchronously; stopped IPC cannot unlock replacement before SIGKILL exit", async () => { + const result = await runNode(` +const ready=await start('stubborn'); +const exit=supervisor.stop('authority'); +assert.equal(supervisor.admitting,false); +assert.throws(()=>start('stubborn'),/stopped/); +await delay(30); +assert.equal(supervisor.hasActiveChild,true); +const receipt=await exit; +assert.equal(receipt.pid,ready.pid); +assert.equal(receipt.signal,'SIGKILL'); +assert.equal(receipt.hardKilled,true); +assert.equal(absent(ready.pid),true); +assert.equal(supervisor.hasActiveChild,false); +const second=await start('graceful'); +const secondExit=await supervisor.stop('stopping'); +assert.equal(secondExit.pid,second.pid); +assert.equal(secondExit.code,0); +const list=records(); +assert(list.some(entry=>entry.event==='stop-authority')); +assert(list.some(entry=>entry.event==='term')); +assert(!list.some(entry=>entry.event==='environment-leaked')); +assert(list.some(entry=>entry.event==='credential-present')); +console.log(JSON.stringify({signal:receipt.signal,secondCode:secondExit.code,environmentClean:true})); +`) + expect(result).toEqual({ signal: "SIGKILL", secondCode: 0, environmentClean: true }) + }, 30_000) + test("watchdog kills its owned native child while the parent event loop is stalled", async () => { + const result = await runNode(` +const ready=await start('stubborn'); +const exit=supervisor.exitReceipt; +assert(exit); +const blockedAt=now(); +while(now()-blockedAt<5000) {} +assert.equal(absent(ready.pid),true); +const receipt=await exit; +assert.equal(receipt.reason,'parent_stalled'); +assert.equal(receipt.signal,'SIGKILL'); +assert(receipt.exitedAtMs { + const result = await runNode(` +const ready=await start('stubborn'); +await delay(5); +supervisor.renew(identity,now(),750); +const receipt=await supervisor.exitReceipt; +assert(receipt); +assert.equal(receipt.reason,'expired'); +assert.equal(receipt.signal,'SIGKILL'); +assert.equal(absent(ready.pid),true); +console.log(JSON.stringify({reason:receipt.reason})); +`) + expect(result).toEqual({ reason: "expired" }) + }, 30_000) + test("parent stall also ends the graceful stopping window", async () => { + const result = await runNode(` +const ready=await start('stubborn'); +const exit=supervisor.stop('stopping'); +await delay(40); +const blockedAt=now(); +while(now()-blockedAt<5000) {} +assert.equal(absent(ready.pid),true); +const receipt=await exit; +assert.equal(receipt.reason,'parent_stalled'); +assert.equal(receipt.signal,'SIGKILL'); +console.log(JSON.stringify({reason:receipt.reason})); +`) + expect(result).toEqual({ reason: "parent_stalled" }) + }, 30_000) + test("parent SIGKILL disconnect lets the independent watchdog reap the native child", async () => { + const result = await runNode(` +import {fork} from 'node:child_process'; +const parent=fork(${JSON.stringify(crashParentPath)},[],{execPath:process.execPath,execArgv:[],env:{},stdio:['ignore','ignore','ignore','ipc']}); +const nativePid=await new Promise((resolve,reject)=>{parent.once('message',message=>resolve(message.pid));parent.once('error',reject);}); +const parentExit=new Promise(resolve=>parent.once('exit',resolve)); +parent.kill('SIGKILL'); +await parentExit; +await delay(1000); +assert.equal(absent(nativePid),true); +console.log(JSON.stringify({parentCrashReapedChild:true})); +`) + expect(result).toEqual({ parentCrashReapedChild: true }) + }, 30_000) + test("a queued start after a terminal pre-start stop cannot create an unsupervised child", async () => { + const result = await runNode(` +import {fork} from 'node:child_process'; +import {existsSync} from 'node:fs'; +const watchdog=fork(${JSON.stringify(join(fixtureDirectory, "watchdog.js"))},[],{execPath:process.execPath,execArgv:[],env:{},stdio:['ignore','ignore','ignore','ipc']}); +const watchdogExit=new Promise(resolve=>watchdog.once('exit',resolve)); +let rejectedBeforeStart=false; +watchdog.on('message',message=>{if(message.type==='error'&&message.code==='protocol')rejectedBeforeStart=true;}); +watchdog.send({type:'stop',reason:'authority'},()=>{}); +watchdog.send({type:'start',childPath:${JSON.stringify(roomChildPath)},environment:{EXPECTED_CREDENTIAL:'fixture-value'},identity, + requestedAtMs:now(),leaseMs:3000,expiresAtMs:now()+10000,payload:{mode:'stubborn',events}, + timings:{heartbeatTimeoutMs:1000,stoppingGraceMs:1500,authorityGraceMs:10,termGraceMs:100,killExitTimeoutMs:1000,startupTimeoutMs:2000}},()=>{}); +await watchdogExit; +assert.equal(rejectedBeforeStart,true); +assert.equal(existsSync(events),false); +console.log(JSON.stringify({terminalStartRejected:true})); +`) + expect(result).toEqual({ terminalStartRejected: true }) + }, 30_000) + test("lost watchdog IPC rejects an unconfirmed exit and leaves replacement fenced", async () => { + const result = await runNode(` +const broken=new NativeRoomSupervisor({roomChildPath:${JSON.stringify(roomChildPath)},watchdogPath:${JSON.stringify(brokenWatchdogPath)},environment:{}}); +const pending=broken.start({identity,requestedAtMs:now(),leaseMs:15000,expiresAtMs:now()+30000,payload:{}}); +const exit=broken.exitReceipt; +await assert.rejects(pending,/watchdog|exit_unconfirmed/); +await assert.rejects(exit,/exit_unconfirmed/); +assert.equal(broken.admitting,false); +assert.equal(broken.hasActiveChild,true); +assert.throws(()=>broken.start({identity,requestedAtMs:now(),leaseMs:15000,expiresAtMs:now()+30000,payload:{}}),/stopped/); +console.log(JSON.stringify({unconfirmedFenced:true})); +`) + expect(result).toEqual({ unconfirmedFenced: true }) + }, 30_000) + test("ordinary stop allows admitted work to flush, then authority loss shortens that grace", async () => { + const result = await runNode(` +const ready=await start('stubborn'); +const stoppingAt=now(); +const exit=supervisor.stop('stopping'); +await delay(80); +assert.equal(absent(ready.pid),false); +supervisor.stop('authority'); +const receipt=await exit; +assert.equal(receipt.reason,'authority'); +assert(receipt.exitedAtMs-stoppingAt<1000); +assert(records().some(entry=>entry.event==='stop-stopping')); +assert(records().some(entry=>entry.event==='stop-authority')); +console.log(JSON.stringify({reason:receipt.reason})); +`) + expect(result).toEqual({ reason: "authority" }) + }, 30_000) + test("ready-less child startup is terminated, and replacement waits for its exit", async () => { + const result = await runNode(` +const ready=start('never-ready'); +const exit=supervisor.exitReceipt; +await assert.rejects(ready,/stopped/); +const receipt=await exit; +assert(receipt); +assert.equal(receipt.reason,'startup'); +assert.equal(absent(receipt.pid),true); +console.log(JSON.stringify({reason:receipt.reason})); +`) + expect(result).toEqual({ reason: "startup" }) + }, 30_000) + test("renewal forwards participant replacement only while the original authority is current", async () => { + const result = await runNode(` +await start('graceful'); +await delay(5); +supervisor.renew(identity,now(),3000,{marker:'participants-replaced'}); +await delay(30); +await supervisor.stop('stopping'); +assert(records().some(entry=>entry.event==='renew-participants-replaced')); +console.log(JSON.stringify({renewed:true})); +`) + expect(result).toEqual({ renewed: true }) + }, 30_000) +}) + +describe("watchdog lease monotonic fencing", () => { + test("expiry is irreversible, delayed request start never grants a fresh duration", () => { + let clock = 1_000 + const lease = new WatchdogLease(identity, clock, 1_000, 10_000, () => clock) + clock = 1_900 + lease.renew(identity, 1_100, 1_000) + expect(lease.deadline).toBe(2_100) + clock = 2_100 + expect(() => lease.renew(identity, clock, 1_000)).toThrow("expired") + clock = 2_200 + expect(() => lease.renew(identity, clock, 1_000)).toThrow("stopped") + }) + test("out of order and stale identity renewals cannot extend the accepted authority", () => { + let clock = 1_000 + const lease = new WatchdogLease(identity, clock, 1_000, 4_000, () => clock) + clock = 1_100 + expect(lease.renew(identity, clock, 2_000)).toBe(true) + expect(lease.renew(identity, 1_050, 30_000)).toBe(false) + expect(lease.deadline).toBe(3_100) + expect(() => lease.renew({ ...identity, claimEpoch: 2 }, clock, 2_000)).toThrow("protocol") + clock = 2_000 + lease.renew(identity, clock, 30_000) + expect(lease.deadline).toBe(4_000) + }) +}) diff --git a/server/packages/grid-transcription/src/supervisor.ts b/server/packages/grid-transcription/src/supervisor.ts new file mode 100644 index 000000000..b28ac64a7 --- /dev/null +++ b/server/packages/grid-transcription/src/supervisor.ts @@ -0,0 +1,197 @@ +import { fork, type ChildProcess } from "node:child_process" +import { fileURLToPath } from "node:url" +import type { CaptureIdentity } from "./authority.js" +import { monotonicMilliseconds, sameIdentity, validIdentity, validStopReason, WatchdogLease } from "./watchdog.js" +import type { ChildErrorCode, NativeExitReceipt, StopReason, WatchdogCommand, WatchdogMessage, WatchdogTimings } from "./watchdog.js" + +export type { RoomChildCommand, RoomChildMessage, NativeExitReceipt, StopReason } from "./watchdog.js" +export type NativeRoomStart = Readonly<{ + identity: CaptureIdentity; requestedAtMs: number; leaseMs: number; expiresAtMs: number; payload: unknown; +}> +export type NativeReadyReceipt = Readonly<{ identity: CaptureIdentity; pid: number }> +export type NativeRoomSupervisorOptions = Readonly<{ + roomChildPath: string | URL; environment: Readonly>; watchdogPath?: string | URL; + heartbeatIntervalMs?: number; heartbeatTimeoutMs?: number; stoppingGraceMs?: number; authorityGraceMs?: number; + termGraceMs?: number; killExitTimeoutMs?: number; startupTimeoutMs?: number; +}> +export class SupervisionError extends Error { + constructor(readonly code: ChildErrorCode | "exit_unconfirmed" | "watchdog") { + super(`Grid transcription supervision ${code}`) + this.name = "SupervisionError" + } +} +type Deferred = Readonly<{ promise: Promise; resolve: (value: T) => void; reject: (error: SupervisionError) => void }> +function deferred(): Deferred { + let resolve!: (value: T) => void + let reject!: (error: SupervisionError) => void + const promise = new Promise((yes, no) => { resolve = yes; reject = no }) + // Exit may be observed after spontaneous failure without a caller awaiting stop. + void promise.catch(() => undefined) + return { promise, resolve, reject } +} +type ActiveRoom = { + identity: CaptureIdentity; watchdog: ChildProcess; lease: WatchdogLease; + ready: Deferred; exit: Deferred; + timer?: NodeJS.Timeout; readyReceived: boolean; admission: boolean; receipt?: NativeExitReceipt; +} +const defaults: WatchdogTimings = { + heartbeatTimeoutMs: 1_500, stoppingGraceMs: 5_000, authorityGraceMs: 50, + termGraceMs: 250, killExitTimeoutMs: 1_000, startupTimeoutMs: 10_000, +} +function path(value: string | URL): string { return typeof value === "string" ? value : fileURLToPath(value) } +function object(value: unknown): Record | undefined { + return value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record : undefined +} +function parseMessage(value: unknown, identity: CaptureIdentity): WatchdogMessage | undefined { + const message = object(value) + if (message?.type === "ready" && Number.isSafeInteger(message.pid) && Number(message.pid) > 0) { + return { type: "ready", pid: Number(message.pid) } + } + if (message?.type === "error" && typeof message.code === "string" + && ["protocol", "provider", "overflow", "stopped", "expired", "audio", "native", "api", "internal", "exit_unconfirmed", "watchdog"].includes(message.code)) { + return { type: "error", code: message.code as SupervisionError["code"] } + } + const receipt = object(message?.receipt) + if (message?.type !== "exit" || !receipt || !validIdentity(receipt.identity) || !sameIdentity(identity, receipt.identity) + || !Number.isSafeInteger(receipt.pid) || Number(receipt.pid) <= 0 + || !(receipt.code === null || Number.isInteger(receipt.code)) + || !(receipt.signal === null || typeof receipt.signal === "string") + || !(receipt.reason === "exited" || validStopReason(receipt.reason)) + || typeof receipt.hardKilled !== "boolean" || !Number.isSafeInteger(receipt.exitedAtMs)) return undefined + return { type: "exit", receipt: { identity: receipt.identity, pid: Number(receipt.pid), + code: receipt.code as number | null, signal: receipt.signal as NodeJS.Signals | null, + reason: receipt.reason, hardKilled: receipt.hardKilled, exitedAtMs: Number(receipt.exitedAtMs) } } +} +function validatePayload(payload: unknown): void { + // Validate JSON IPC before allocating a child; never log or echo the serialized value. + try { const encoded = JSON.stringify(payload); if (encoded !== undefined && Buffer.byteLength(encoded) > 256 * 1024) throw new Error() } + catch { throw new SupervisionError("protocol") } +} + +export class NativeRoomSupervisor { + private active: ActiveRoom | undefined + private readonly timings: WatchdogTimings + private readonly heartbeatIntervalMs: number + constructor(private readonly options: NativeRoomSupervisorOptions) { + this.timings = { heartbeatTimeoutMs: options.heartbeatTimeoutMs ?? defaults.heartbeatTimeoutMs, + stoppingGraceMs: options.stoppingGraceMs ?? defaults.stoppingGraceMs, + authorityGraceMs: options.authorityGraceMs ?? defaults.authorityGraceMs, + termGraceMs: options.termGraceMs ?? defaults.termGraceMs, + killExitTimeoutMs: options.killExitTimeoutMs ?? defaults.killExitTimeoutMs, + startupTimeoutMs: options.startupTimeoutMs ?? defaults.startupTimeoutMs } + this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? 250 + if (!Object.values(this.timings).every((value) => Number.isSafeInteger(value) && value >= 0 && value <= 30_000) + || this.timings.heartbeatTimeoutMs <= 0 || this.timings.killExitTimeoutMs <= 0 || this.timings.startupTimeoutMs <= 0 + || !Number.isSafeInteger(this.heartbeatIntervalMs) || this.heartbeatIntervalMs <= 0 + || this.heartbeatIntervalMs >= this.timings.heartbeatTimeoutMs + || !Object.entries(options.environment).every(([key, value]) => /^[A-Z_][A-Z0-9_]*$/.test(key) && typeof value === "string" + && key !== "NODE_OPTIONS" && key !== "NODE_PATH")) throw new SupervisionError("protocol") + } + get hasActiveChild(): boolean { return this.active !== undefined } + get admitting(): boolean { return this.active?.admission ?? false } + get exitReceipt(): Promise | undefined { return this.active?.exit.promise } + + start(input: NativeRoomStart): Promise { + if (this.active) throw new SupervisionError("stopped") + validatePayload(input.payload) + let lease: WatchdogLease + try { lease = new WatchdogLease(Object.freeze({ ...input.identity }), input.requestedAtMs, input.leaseMs, input.expiresAtMs) } + catch { throw new SupervisionError("expired") } + const watchdog = fork(path(this.options.watchdogPath ?? new URL("./watchdog.js", import.meta.url)), [], { + execPath: process.execPath, execArgv: [], env: {}, stdio: ["ignore", "ignore", "ignore", "ipc"], serialization: "json", + }) + const active: ActiveRoom = { identity: lease.identity, watchdog, lease, ready: deferred(), exit: deferred(), + readyReceived: false, admission: true } + this.active = active + watchdog.on("message", (value: unknown) => { + const message = parseMessage(value, active.identity) + if (!message) { this.fail(active, "protocol"); return } + if (message.type === "ready") { + if (!active.admission || active.readyReceived) return + try { active.lease.assertCurrent() } catch { this.fail(active, "expired"); return } + active.readyReceived = true + active.ready.resolve({ identity: active.identity, pid: message.pid }) + } else if (message.type === "error") this.fail(active, message.code) + else { + active.receipt = message.receipt + this.send(active, { type: "ack_exit", identity: active.identity, pid: message.receipt.pid }) + active.admission = false + active.lease.stop() + clearInterval(active.timer) + if (!active.readyReceived) active.ready.reject(new SupervisionError("stopped")) + active.exit.resolve(message.receipt) + if (this.active === active) this.active = undefined + } + }) + watchdog.on("error", () => { + this.fail(active, "watchdog") + clearInterval(active.timer) + active.exit.reject(new SupervisionError("exit_unconfirmed")) + // Spawn failure can emit error without exit. Keep the slot fenced, but never hang its caller. + }) + watchdog.once("disconnect", () => { + // Let already-buffered IPC receipts drain before classifying a bare channel closure. + setImmediate(() => { if (!active.receipt) this.transportFailed(active) }) + }) + watchdog.once("close", () => { + clearInterval(active.timer) + if (active.receipt) return + active.admission = false + active.lease.stop() + const failure = new SupervisionError("exit_unconfirmed") + active.ready.reject(failure) + active.exit.reject(failure) + // Keep the occupied slot: watchdog death alone does not prove native exit. + }) + active.timer = setInterval(() => { + if (active.receipt) return + this.send(active, { type: "heartbeat", sentAtMs: monotonicMilliseconds() }) + }, this.heartbeatIntervalMs) + this.send(active, { type: "start", childPath: path(this.options.roomChildPath), environment: this.options.environment, + ...input, identity: active.identity, timings: this.timings }) + return active.ready.promise + } + renew(identity: CaptureIdentity, requestedAtMs: number, leaseMs: number, payload?: unknown): void { + const active = this.active + if (!active || !active.admission) throw new SupervisionError("stopped") + validatePayload(payload) + try { + if (!active.lease.renew(identity, requestedAtMs, leaseMs)) return + } catch { this.fail(active, "expired"); throw new SupervisionError("expired") } + this.send(active, { type: "renew", identity, requestedAtMs, leaseMs, payload }) + } + stop(reason: StopReason = "authority"): Promise { + const active = this.active + if (!active) return Promise.reject(new SupervisionError("stopped")) + if (!validStopReason(reason)) return Promise.reject(new SupervisionError("protocol")) + // Synchronous parent admission barrier, followed by the child's IPC barrier. + active.admission = false + active.lease.stop() + this.send(active, { type: "stop", reason }) + return active.exit.promise + } + private fail(active: ActiveRoom, code: SupervisionError["code"]): void { + if (active.receipt) return + active.admission = false + active.lease.stop() + active.ready.reject(new SupervisionError(code)) + if (code === "exit_unconfirmed") active.exit.reject(new SupervisionError(code)) + this.send(active, { type: "stop", reason: "authority" }) + } + private send(active: ActiveRoom, message: WatchdogCommand): void { + if (!active.watchdog.connected) { if (!active.receipt) this.transportFailed(active); return } + try { active.watchdog.send(message, (error) => { if (error && !active.receipt) this.transportFailed(active) }) } + catch { this.transportFailed(active) } + } + private transportFailed(active: ActiveRoom): void { + if (active.receipt) return + active.admission = false + active.lease.stop() + active.ready.reject(new SupervisionError("watchdog")) + active.exit.reject(new SupervisionError("exit_unconfirmed")) + clearInterval(active.timer) + // Closing the control channel makes the independent watchdog stop its owned child. + // Without that channel, its later OS exit cannot substitute for a native exit receipt. + if (active.watchdog.connected) { try { active.watchdog.disconnect() } catch { /* Already disconnected. */ } } + } +} diff --git a/server/packages/grid-transcription/src/track-worker.test.ts b/server/packages/grid-transcription/src/track-worker.test.ts new file mode 100644 index 000000000..91e523117 --- /dev/null +++ b/server/packages/grid-transcription/src/track-worker.test.ts @@ -0,0 +1,176 @@ +import { describe, expect, test } from "bun:test" +import { TrackWorker, type SpeechConnection, type TrackWorkerOptions } from "./track-worker.js" +import { TranscriptionError, type FinalTurn } from "./protocol.js" +import type { ProviderOptions } from "./socket.js" + +function deferred() { + let resolve!: (value: T) => void + const promise = new Promise((done) => { resolve = done }) + return { promise, resolve } +} +const tick = () => new Promise((resolve) => setImmediate(resolve)) +async function until(predicate: () => boolean): Promise { + for (let count = 0; count < 100; count++) { if (predicate()) return; await tick() } + throw new Error("test event did not occur") +} +function fixture(overrides: Partial = {}) { + let microphone = true + let authority = true + let options: ProviderOptions | undefined + let nextId = 0 + const events: string[] = [] + const audio: number[][] = [] + const commits: string[] = [] + const finals: FinalTurn[] = [] + const failures: TranscriptionError[] = [] + const connection: SpeechConnection = { + appendAudio(frame) { audio.push([...frame]); events.push("audio") }, + commitTurn(id) { commits.push(id); events.push("commit") }, close() { events.push("close") }, + } + const worker = new TrackWorker({ model: "meeting", apiKey: "test-key", participantIdentity: "speaker-1", trackSid: "TR_test", + api: { async admit() { events.push("admit"); return `segment-${++nextId}` }, + async final(turnId, text) { finals.push({ turnId, text }); events.push("final") } }, + detector: { async probability(frame) { return frame[0] === 1 ? 1 : 0 }, close() { events.push("detector-close") } }, + assertRunAuthority() { if (!authority) throw new TranscriptionError("expired") }, + assertMicrophone() { if (!microphone) throw new TranscriptionError("stopped") }, + onFailure(error) { failures.push(error) }, + async connect(value) { options = value; events.push("connect"); return connection }, ...overrides, + }) + return { worker, connection, events, audio, commits, finals, failures, + final(turnId: string, text: string) { options?.onFinal({ turnId, text }) }, + revokeMicrophone() { microphone = false }, revokeRun() { authority = false } } +} +function frame(speech: boolean): Int16Array { const data = new Int16Array(320); data[0] = speech ? 1 : 0; return data } +async function utterance(value: ReturnType): Promise { + value.worker.push(frame(true)) + for (let index = 0; index < 28; index++) value.worker.push(frame(false)) + await until(() => value.commits.length > 0 || value.failures.length > 0) + await tick() +} + +describe("per microphone finite turn owner", () => { + test("a drained socket rotates at its age bound without replaying an admitted turn", async () => { + let clock = 0 + const value = fixture({ now: () => clock }) + await utterance(value) + clock = 9 * 60 * 1000 + value.final("segment-1", "First completed turn.") + await until(() => value.events.includes("close")) + await utterance(value) + await until(() => value.commits.length === 2) + expect(value.events.filter((event) => event === "connect")).toHaveLength(2) + expect(value.commits).toEqual(["segment-1", "segment-2"]) + await value.worker.stop(false) + }) + test("a non-cancellable inference promise fails boundedly even without more room audio", async () => { + const failed = deferred() + const inference = deferred() + const value = fixture({ detector: { probability: () => inference.promise, close() {} }, onFailure: failed.resolve }) + value.worker.push(frame(true)) + expect((await failed.promise).code).toBe("audio") + expect(value.audio).toHaveLength(0) + expect(value.events).not.toContain("admit") + await value.worker.stop(false) + }, 12_000) + test("API admission precedes every first provider byte; finals retain admitted ID", async () => { + const value = fixture() + await utterance(value) + expect(value.events.slice(0, 3)).toEqual(["admit", "connect", "audio"]) + expect(value.commits).toEqual(["segment-1"]) + value.final("segment-1", "Meeting sentence.") + await until(() => value.finals.length === 1) + expect(value.finals).toEqual([{ turnId: "segment-1", text: "Meeting sentence." }]) + await value.worker.stop(false) + expect(value.failures).toEqual([]) + }) + + test("queued samples are discarded if membership changes before inference", async () => { + const value = fixture() + value.worker.push(frame(true)) + value.revokeMicrophone() + await until(() => value.failures.length === 1) + expect(value.events).not.toContain("admit") + expect(value.audio).toHaveLength(0) + expect(value.failures[0]?.code).toBe("stopped") + }) + + test("late admission after Stop retires empty without opening the provider", async () => { + const admitted = deferred() + const value = fixture({ api: { admit() { return admitted.promise }, async final(turnId, text) { + value.finals.push({ turnId, text }) + } } }) + value.worker.push(frame(true)) + await tick(); await tick() + const stopped = value.worker.stop(true) + admitted.resolve("late-segment") + await stopped + expect(value.finals).toEqual([{ turnId: "late-segment", text: "" }]) + expect(value.events).not.toContain("connect") + expect(value.audio).toHaveLength(0) + }) + + test("late provider connect is closed after destructive stop and never sends PCM", async () => { + const connected = deferred() + let started = false + const value = fixture({ connect() { started = true; return connected.promise } }) + value.worker.push(frame(true)) + await until(() => started) + await value.worker.stop(false) + connected.resolve(value.connection) + await tick(); await tick() + expect(value.audio).toHaveLength(0) + expect(value.events).toContain("close") + expect(value.finals).toHaveLength(0) + }) + + test("ordinary voice leave accepts only the already admitted final", async () => { + const value = fixture() + await utterance(value) + const capturedBeforeLeave = value.audio.length + value.revokeMicrophone() + const stopping = value.worker.stop(true) + expect(() => value.worker.push(frame(true))).toThrow("stopped") + value.final("segment-1", "Finished before leaving.") + await stopping + expect(value.audio).toHaveLength(capturedBeforeLeave) + expect(value.finals).toEqual([{ turnId: "segment-1", text: "Finished before leaving." }]) + expect(value.failures).toHaveLength(0) + }) + + test("loss of run authority discards provider finals before API persistence", async () => { + const value = fixture() + await utterance(value) + value.revokeRun() + expect(() => value.final("segment-1", "Must not persist.")).toThrow("expired") + expect(value.finals).toHaveLength(0) + await value.worker.stop(false) + }) + + test("a slow provider cannot create an unbounded native PCM queue", async () => { + const connected = deferred() + let started = false + const value = fixture({ connect() { started = true; return connected.promise } }) + value.worker.push(frame(true)) + await until(() => started) + for (let index = 0; index < 100; index++) value.worker.push(frame(true)) + expect(() => value.worker.push(frame(true))).toThrow("overflow") + connected.resolve(value.connection) + await tick(); await tick() + expect(value.failures[0]?.code).toBe("overflow") + expect(value.audio).toHaveLength(0) + }) + + test("two unresolved commits bound provider and server admission backlog", async () => { + const value = fixture() + await utterance(value) + const before = value.commits.length + value.worker.push(frame(true)) + for (let index = 0; index < 28; index++) value.worker.push(frame(false)) + await until(() => value.commits.length > before) + value.worker.push(frame(true)) + await until(() => value.failures.length > 0) + expect(value.commits).toEqual(["segment-1", "segment-2"]) + expect(value.events.filter((event) => event === "admit")).toHaveLength(2) + expect(value.failures[0]?.code).toBe("overflow") + }) +}) diff --git a/server/packages/grid-transcription/src/track-worker.ts b/server/packages/grid-transcription/src/track-worker.ts new file mode 100644 index 000000000..561c53838 --- /dev/null +++ b/server/packages/grid-transcription/src/track-worker.ts @@ -0,0 +1,267 @@ +import { randomUUID } from "node:crypto" +import { BoundedPcmQueue } from "./pcm.js" +import { MAX_PENDING_TURNS, TranscriptionError, type FinalTurn, type Model } from "./protocol.js" +import { SpeechSegmenter } from "./segmenter.js" +import type { SpeechDetector } from "./silero.js" +import { ProviderConnection, type ProviderOptions } from "./socket.js" +import { monotonicMilliseconds } from "./watchdog.js" + +export interface TurnApi { + admit(participantIdentity: string, trackSid: string, sourceTurnKey: string): Promise + final(segmentId: string, text: string): Promise +} +export interface SpeechConnection { appendAudio(samples: Int16Array): void; commitTurn(id: string): void; close(): void } +export type TrackWorkerOptions = Readonly<{ + model: Model; apiKey: string; participantIdentity: string; trackSid: string; + api: TurnApi; detector: SpeechDetector; + assertRunAuthority: () => void; assertMicrophone: () => void; + onFailure: (error: TranscriptionError) => void; + connect?: (options: ProviderOptions) => Promise; + now?: () => number; +}> +type PendingTurn = { timer: NodeJS.Timeout; posting: boolean } +const FINAL_TIMEOUT_MS = 5_000 +const SOCKET_MAX_AGE_MS = 9 * 60 * 1000 +const SOCKET_MAX_TURNS = 900 + +// Exactly one consumer owns each speaker's VAD, admission and provider commit order. +// Native RTC readers only push owned copies and never wait for inference or network I/O. +export class TrackWorker { + readonly sampleRate: number + private readonly queue: BoundedPcmQueue + private readonly segmenter: SpeechSegmenter + private readonly now: () => number + private readonly consumer: Promise + private accepting = true + private closed = false + private graceful = false + private connection: SpeechConnection | undefined + private connectionStartedAt = 0 + private socketTurns = 0 + private rotationTimer: NodeJS.Timeout | undefined + private current: { id: string; samples: number } | undefined + private readonly pending = new Map() + private readonly posting = new Set>() + private closePromise: Promise | undefined + private drainResolve: (() => void) | undefined + private drainTimer: NodeJS.Timeout | undefined + private processingTimer: NodeJS.Timeout | undefined + + constructor(private readonly options: TrackWorkerOptions) { + this.sampleRate = options.model === "meeting" ? 16_000 : 24_000 + this.queue = new BoundedPcmQueue(this.sampleRate * 2) + this.segmenter = new SpeechSegmenter(this.sampleRate) + this.now = options.now ?? monotonicMilliseconds + options.assertRunAuthority() + options.assertMicrophone() + this.consumer = this.consume().catch((error: unknown) => { + if (this.accepting) this.fail(error) + }) + } + + push(samples: Int16Array): void { + if (!this.accepting || this.closed) throw new TranscriptionError("stopped") + try { + this.options.assertRunAuthority() + this.options.assertMicrophone() + if (samples.length > this.sampleRate / 10) throw new TranscriptionError("audio") + this.queue.push(samples) + } catch (error) { this.fail(error); throw this.safe(error) } + } + + private async consume(): Promise { + while (this.accepting) { + const frame = await this.queue.take() + if (!frame || !this.accepting) return + // Native inference may not support cancellation. Failure closes the queue; + // room/process supervision contains a late or permanently blocked operation. + this.processingTimer = setTimeout(() => this.fail(new TranscriptionError("audio")), 7_000) + try { + this.options.assertRunAuthority() + this.options.assertMicrophone() + const probability = await this.options.detector.probability(frame) + if (!this.accepting) return + this.options.assertRunAuthority() + this.options.assertMicrophone() + for (const action of this.segmenter.accept(frame, probability)) { + if (!this.accepting) return + if (action.kind === "start") await this.startTurn(action.frames) + else if (action.kind === "audio") this.append(action.frame) + else this.commit() + } + } finally { clearTimeout(this.processingTimer); this.processingTimer = undefined } + } + } + + private async startTurn(frames: readonly Int16Array[]): Promise { + if (this.current || this.pending.size >= MAX_PENDING_TURNS) throw new TranscriptionError("overflow") + this.options.assertRunAuthority() + this.options.assertMicrophone() + const id = await this.options.api.admit(this.options.participantIdentity, this.options.trackSid, randomUUID()) + // A late admission never grants permission to resume ingestion after a stop or leave. + if (!this.accepting) { + if (this.graceful && !this.closed) await this.postEmpty(id) + return + } + this.options.assertRunAuthority() + this.options.assertMicrophone() + this.current = { id, samples: 0 } + if (this.connection && (this.now() - this.connectionStartedAt >= SOCKET_MAX_AGE_MS || this.socketTurns >= SOCKET_MAX_TURNS)) { + // No unbounded completion history: rotate only with every old commit acknowledged. + if (this.pending.size !== 0) throw new TranscriptionError("overflow") + this.connection.close() + this.connection = undefined + clearTimeout(this.rotationTimer) + } + if (!this.connection) { + const connection = await (this.options.connect ?? ProviderConnection.connect)({ + model: this.options.model, apiKey: this.options.apiKey, + assertAuthority: () => this.assertFinalAuthority(), + onFinal: (turn) => this.receiveFinal(turn), + onFailure: (error) => this.fail(error), + }) + if (!this.accepting || this.closed) { connection.close(); return } + this.options.assertRunAuthority() + this.options.assertMicrophone() + this.connection = connection + this.connectionStartedAt = this.now() + this.socketTurns = 0 + this.rotationTimer = setTimeout(() => this.rotateIfIdle(), SOCKET_MAX_AGE_MS) + } + for (const frame of frames) this.append(frame) + } + + private append(frame: Int16Array): void { + if (!this.accepting || this.closed || !this.current || !this.connection) throw new TranscriptionError("stopped") + this.options.assertRunAuthority() + this.options.assertMicrophone() + this.connection.appendAudio(frame) + this.current.samples += frame.length + } + + private commit(): void { + const current = this.current + this.current = undefined + if (!current) return + this.assertFinalAuthority() + if (!this.connection || current.samples === 0) { + this.trackPost(this.postEmpty(current.id)) + return + } + if (this.pending.size >= MAX_PENDING_TURNS) throw new TranscriptionError("overflow") + // Manual finalize works best with real trailing silence; hard speech boundaries and + // manual Stop add a finite 200ms zero tail, never more captured room audio. + this.connection.appendAudio(new Int16Array(this.sampleRate / 5)) + const timer = setTimeout(() => this.fail(new TranscriptionError("provider")), FINAL_TIMEOUT_MS) + this.pending.set(current.id, { timer, posting: false }) + this.socketTurns++ + this.connection.commitTurn(current.id) + } + + private assertFinalAuthority(): void { + if (this.closed) throw new TranscriptionError("stopped") + this.options.assertRunAuthority() + // A pre-admitted speaker can leave voice while its immutable turn completes. + // The API independently fences current Space, destination and admitted attribution. + } + + private receiveFinal(turn: FinalTurn): void { + this.assertFinalAuthority() + const pending = this.pending.get(turn.turnId) + if (!pending || pending.posting) throw new TranscriptionError("protocol") + pending.posting = true + clearTimeout(pending.timer) + this.trackPost(this.postFinal(turn).finally(() => { + this.pending.delete(turn.turnId) + this.maybeDrained() + })) + } + + private async postFinal(turn: FinalTurn): Promise { + // Idempotent delivery may retry a lost response once; no other turn or text is substituted. + for (let attempt = 0; attempt < 2; attempt++) { + this.assertFinalAuthority() + try { + await this.options.api.final(turn.turnId, turn.text) + this.assertFinalAuthority() + return + } catch (error) { + if (attempt === 1 || (error instanceof TranscriptionError && error.code !== "provider")) throw error + } + } + } + private async postEmpty(id: string): Promise { await this.postFinal({ turnId: id, text: "" }) } + private trackPost(operation: Promise): void { + this.posting.add(operation) + void operation.catch((error: unknown) => { if (!this.closed) this.fail(error) }).finally(() => { + this.posting.delete(operation) + this.maybeDrained() + }) + } + private maybeDrained(): void { + this.rotateIfIdle() + if (!this.accepting && !this.current && this.pending.size === 0 && this.posting.size === 0) this.drainResolve?.() + } + private rotateIfIdle(): void { + if (!this.accepting || !this.connection || this.current || this.pending.size || this.posting.size + || (this.now() - this.connectionStartedAt < SOCKET_MAX_AGE_MS && this.socketTurns < SOCKET_MAX_TURNS)) return + this.connection.close() + this.connection = undefined + clearTimeout(this.rotationTimer) + this.rotationTimer = undefined + } + private safe(error: unknown): TranscriptionError { + return error instanceof TranscriptionError ? error : new TranscriptionError("provider") + } + private fail(error: unknown): void { + if (this.closed) return + this.discard() + this.options.onFailure(this.safe(error)) + } + + // This barrier is synchronous. Its promise represents bounded completion of accepted work. + stop(graceful: boolean): Promise { + if (this.closePromise) { + if (!graceful) this.discard() + return this.closePromise + } + this.accepting = false + this.graceful = graceful + this.queue.close() + this.segmenter.stop() + if (!graceful) { this.discard(); this.closePromise = Promise.resolve(); return this.closePromise } + this.closePromise = this.finishGraceful() + return this.closePromise + } + private async finishGraceful(): Promise { + // Inference/admit/connect can be pending; late results are checked before all writes. + await this.consumer + if (this.closed) return + this.commit() + if (this.pending.size || this.posting.size) { + await new Promise((resolve) => { + this.drainResolve = resolve + this.drainTimer = setTimeout(resolve, FINAL_TIMEOUT_MS) + this.maybeDrained() + }) + } + this.discard() + } + private discard(): void { + if (this.closed) return + this.closed = true + this.accepting = false + this.queue.close() + this.segmenter.stop() + this.connection?.close() + this.connection = undefined + this.current = undefined + for (const pending of this.pending.values()) clearTimeout(pending.timer) + this.pending.clear() + clearTimeout(this.drainTimer) + clearTimeout(this.processingTimer) + clearTimeout(this.rotationTimer) + this.options.detector.close() + this.drainResolve?.() + } +} diff --git a/server/packages/grid-transcription/src/watchdog.ts b/server/packages/grid-transcription/src/watchdog.ts new file mode 100644 index 000000000..cdbe622b8 --- /dev/null +++ b/server/packages/grid-transcription/src/watchdog.ts @@ -0,0 +1,284 @@ +import { fork, type ChildProcess } from "node:child_process" +import { realpathSync } from "node:fs" +import { fileURLToPath } from "node:url" +import type { CaptureIdentity } from "./authority.js" + +// hrtime is the system monotonic clock, shared by processes on Linux and macOS. +export function monotonicMilliseconds(): number { return Number(process.hrtime.bigint() / 1_000_000n) } + +export type StopReason = "stopping" | "authority" | "shutdown" | "startup" | "child_error" + | "parent_stalled" | "parent_disconnected" | "child_stalled" | "expired" +export type ChildErrorCode = "protocol" | "provider" | "overflow" | "stopped" | "expired" | "audio" | "native" | "api" | "internal" +export type RoomChildCommand = + | Readonly<{ type: "start"; identity: CaptureIdentity; payload: unknown; requestedAtMs: number; leaseMs: number; leaseDeadlineMs: number }> + | Readonly<{ type: "renew"; identity: CaptureIdentity; payload?: unknown; requestedAtMs: number; leaseMs: number; leaseDeadlineMs: number }> + | Readonly<{ type: "stop"; reason: StopReason }> +export type RoomChildMessage = Readonly<{ type: "ready" }> | Readonly<{ type: "stopped" }> + | Readonly<{ type: "heartbeat"; sentAtMs: number }> + | Readonly<{ type: "error"; code: ChildErrorCode }> +export type NativeExitReceipt = Readonly<{ + identity: CaptureIdentity; pid: number; code: number | null; signal: NodeJS.Signals | null; + reason: StopReason | "exited"; hardKilled: boolean; exitedAtMs: number; +}> +export type WatchdogTimings = Readonly<{ + heartbeatTimeoutMs: number; stoppingGraceMs: number; authorityGraceMs: number; + termGraceMs: number; killExitTimeoutMs: number; startupTimeoutMs: number; +}> +export type WatchdogStart = Readonly<{ + type: "start"; childPath: string; environment: Readonly>; + identity: CaptureIdentity; requestedAtMs: number; leaseMs: number; expiresAtMs: number; + payload: unknown; timings: WatchdogTimings; +}> +export type WatchdogCommand = WatchdogStart + | Readonly<{ type: "heartbeat"; sentAtMs: number }> + | Readonly<{ type: "renew"; identity: CaptureIdentity; requestedAtMs: number; leaseMs: number; payload?: unknown }> + | Readonly<{ type: "ack_exit"; identity: CaptureIdentity; pid: number }> + | Readonly<{ type: "stop"; reason: StopReason }> +export type WatchdogMessage = Readonly<{ type: "ready"; pid: number }> + | Readonly<{ type: "exit"; receipt: NativeExitReceipt }> + | Readonly<{ type: "error"; code: ChildErrorCode | "exit_unconfirmed" | "watchdog" }> + +export function sameIdentity(left: CaptureIdentity, right: CaptureIdentity): boolean { + return left.runId === right.runId && left.claimEpoch === right.claimEpoch && left.generation === right.generation +} +function object(value: unknown): Record | undefined { + return value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record : undefined +} +export function validIdentity(value: unknown): value is CaptureIdentity { + const identity = object(value) + return !!identity && typeof identity.runId === "string" && identity.runId.length > 0 && identity.runId.length <= 256 + && Number.isSafeInteger(identity.claimEpoch) && Number(identity.claimEpoch) > 0 + && Number.isSafeInteger(identity.generation) && Number(identity.generation) >= 0 +} +const stopReasons: readonly StopReason[] = ["stopping", "authority", "shutdown", "startup", "child_error", "parent_stalled", "parent_disconnected", "child_stalled", "expired"] +const errorCodes: readonly ChildErrorCode[] = ["protocol", "provider", "overflow", "stopped", "expired", "audio", "native", "api", "internal"] +export function validStopReason(value: unknown): value is StopReason { + return typeof value === "string" && stopReasons.some((reason) => reason === value) +} +export function parseChildMessage(value: unknown): RoomChildMessage | undefined { + const message = object(value) + if (message?.type === "ready" || message?.type === "stopped") return { type: message.type } + if (message?.type === "heartbeat" && Number.isSafeInteger(message.sentAtMs) && Number(message.sentAtMs) >= 0) { + return { type: "heartbeat", sentAtMs: Number(message.sentAtMs) } + } + if (message?.type === "error" && errorCodes.some((code) => code === message.code)) { + return { type: "error", code: message.code as ChildErrorCode } + } + return undefined +} + +// An expired or stopped lease is permanent; delayed responses cannot resurrect it. +export class WatchdogLease { + private stopped = false + private lastRequestMs: number + private deadlineMs: number + constructor(readonly identity: CaptureIdentity, requestedAtMs: number, leaseMs: number, + private readonly expiresAtMs: number, private readonly now = monotonicMilliseconds) { + if (!validIdentity(identity) || !Number.isFinite(expiresAtMs)) throw new Error("Grid watchdog protocol") + this.lastRequestMs = requestedAtMs + this.deadlineMs = this.calculateDeadline(requestedAtMs, leaseMs) + this.assertCurrent() + } + private calculateDeadline(requestedAtMs: number, leaseMs: number): number { + if (!Number.isSafeInteger(requestedAtMs) || requestedAtMs > this.now() + || !Number.isSafeInteger(leaseMs) || leaseMs <= 0 || leaseMs > 30_000) throw new Error("Grid watchdog protocol") + return Math.min(requestedAtMs + leaseMs, this.expiresAtMs) + } + get deadline(): number { return this.deadlineMs } + assertCurrent(): void { + if (this.stopped) throw new Error("Grid watchdog stopped") + if (this.now() >= this.deadlineMs) { this.stopped = true; throw new Error("Grid watchdog expired") } + } + renew(identity: CaptureIdentity, requestedAtMs: number, leaseMs: number): boolean { + this.assertCurrent() + if (!sameIdentity(identity, this.identity)) throw new Error("Grid watchdog protocol") + const deadline = this.calculateDeadline(requestedAtMs, leaseMs) + if (requestedAtMs <= this.lastRequestMs) return false + if (deadline <= this.now()) { this.stopped = true; throw new Error("Grid watchdog expired") } + this.lastRequestMs = requestedAtMs + this.deadlineMs = deadline + return true + } + stop(): void { this.stopped = true } +} + +function validTimings(value: unknown): value is WatchdogTimings { + const timings = object(value) + return !!timings && ["heartbeatTimeoutMs", "stoppingGraceMs", "authorityGraceMs", "termGraceMs", "killExitTimeoutMs", "startupTimeoutMs"] + .every((key) => Number.isSafeInteger(timings[key]) && Number(timings[key]) >= 0 && Number(timings[key]) <= 30_000) + && Number(timings.heartbeatTimeoutMs) > 0 && Number(timings.killExitTimeoutMs) > 0 && Number(timings.startupTimeoutMs) > 0 +} +function parseStart(value: unknown): WatchdogStart | undefined { + const message = object(value) + const environment = object(message?.environment) + if (message?.type !== "start" || typeof message.childPath !== "string" || !message.childPath + || !validIdentity(message.identity) || !validTimings(message.timings) || !environment + || !Object.entries(environment).every(([key, entry]) => /^[A-Z_][A-Z0-9_]*$/.test(key) && typeof entry === "string" + && key !== "NODE_OPTIONS" && key !== "NODE_PATH")) return undefined + if (typeof message.requestedAtMs !== "number" || typeof message.leaseMs !== "number" || typeof message.expiresAtMs !== "number") return undefined + return { type: "start", childPath: message.childPath, environment: environment as Record, + identity: message.identity, requestedAtMs: message.requestedAtMs, leaseMs: message.leaseMs, + expiresAtMs: message.expiresAtMs, payload: message.payload, timings: message.timings } +} + +// The watchdog is the child's actual parent. It never signals a caller-supplied PID. +// It remains alive if SIGKILL has no bounded exit receipt, keeping replacement fenced. +export function runWatchdog(): void { + let child: ChildProcess | undefined + let start: WatchdogStart | undefined + let lease: WatchdogLease | undefined + let heartbeatDeadlineMs = 0 + let childHeartbeatDeadlineMs = 0 + let startupDeadlineMs = 0 + let ready = false + let stopped: StopReason | undefined + let hardKilled = false + let exited = false + let exitReceipt: NativeExitReceipt | undefined + let supervisionTimer: NodeJS.Timeout | undefined + let escalationTimer: NodeJS.Timeout | undefined + const send = (message: WatchdogMessage, done?: () => void): void => { + if (!process.connected || !process.send) { done?.(); return } + try { process.send(message, (error) => { if (error) stop("parent_disconnected"); done?.() }) } + catch { stop("parent_disconnected"); done?.() } + } + const sendChild = (message: RoomChildCommand): void => { + if (!child?.connected) return + try { child.send(message, (error) => { if (error && !exited) stop("authority") }) } + catch { stop("authority") } + } + const kill = (signal: NodeJS.Signals): void => { + if (!child || exited) return + try { child.kill(signal) } catch { send({ type: "error", code: "watchdog" }) } + } + const escalate = (): void => { + kill("SIGTERM") + escalationTimer = setTimeout(() => { + if (exited) return + hardKilled = true + kill("SIGKILL") + escalationTimer = setTimeout(() => { + if (!exited) send({ type: "error", code: "exit_unconfirmed" }) + }, start?.timings.killExitTimeoutMs ?? 1_000) + }, start?.timings.termGraceMs ?? 250) + } + const stop = (reason: StopReason): void => { + if (exited) return + // Authority loss overrides an existing graceful stop immediately. + const shorten = stopped === "stopping" && reason !== "stopping" + if (stopped && !shorten) return + stopped = reason + lease?.stop() + clearTimeout(supervisionTimer) + clearTimeout(escalationTimer) + sendChild({ type: "stop", reason }) + if (!child) { process.exitCode = 1; process.disconnect?.(); return } + const grace = reason === "stopping" ? start?.timings.stoppingGraceMs : start?.timings.authorityGraceMs + escalationTimer = setTimeout(escalate, grace ?? 50) + if (reason === "stopping") supervise() + } + const supervise = (): void => { + clearTimeout(supervisionTimer) + if (!lease || exited || (stopped !== undefined && stopped !== "stopping")) return + const now = monotonicMilliseconds() + if (!stopped && now >= lease.deadline) { stop("expired"); return } + if (now >= heartbeatDeadlineMs) { stop("parent_stalled"); return } + if (ready && now >= childHeartbeatDeadlineMs) { stop("child_stalled"); return } + if (!stopped && !ready && now >= startupDeadlineMs) { stop("startup"); return } + const deadline = stopped === "stopping" ? Math.min(heartbeatDeadlineMs, ready ? childHeartbeatDeadlineMs : Infinity) + : Math.min(lease.deadline, heartbeatDeadlineMs, ready ? childHeartbeatDeadlineMs : startupDeadlineMs) + supervisionTimer = setTimeout(supervise, Math.max(1, deadline - now)) + } + process.on("message", (value: unknown) => { + const message = object(value) + if (exited) { + if (message?.type === "ack_exit" && exitReceipt && validIdentity(message.identity) + && sameIdentity(message.identity, exitReceipt.identity) && message.pid === exitReceipt.pid) { + if (process.connected) process.disconnect() + } + return + } + if (!start) { + // Signals or queued IPC can arrive between rejection/disconnect and process exit. + // A terminal pre-start stop must never allocate a child afterward. + if (stopped || exited) return + const input = parseStart(value) + if (!input) { send({ type: "error", code: "protocol" }); stop("authority"); return } + try { lease = new WatchdogLease(input.identity, input.requestedAtMs, input.leaseMs, input.expiresAtMs) } + catch { send({ type: "error", code: "expired" }); stop("expired"); return } + start = input + const now = monotonicMilliseconds() + heartbeatDeadlineMs = now + input.timings.heartbeatTimeoutMs + startupDeadlineMs = now + input.timings.startupTimeoutMs + try { child = fork(input.childPath, [], { execPath: process.execPath, execArgv: [], env: { ...input.environment }, + stdio: ["ignore", "ignore", "ignore", "ipc"], serialization: "json" }) } + catch { send({ type: "error", code: "native" }); stop("authority"); return } + const ownedChild = child + ownedChild.on("error", () => { send({ type: "error", code: "native" }); stop("child_error") }) + ownedChild.on("message", (event: unknown) => { + const native = parseChildMessage(event) + if (!native) { send({ type: "error", code: "protocol" }); stop("child_error"); return } + if (native.type === "ready" && !ready && !stopped) { + supervise() + if (stopped) return + ready = true + childHeartbeatDeadlineMs = monotonicMilliseconds() + input.timings.heartbeatTimeoutMs + if (ownedChild.pid !== undefined) send({ type: "ready", pid: ownedChild.pid }) + supervise() + } else if (native.type === "heartbeat") { + const now = monotonicMilliseconds() + if (native.sentAtMs > now || (ready && now >= childHeartbeatDeadlineMs)) { stop("child_stalled"); return } + childHeartbeatDeadlineMs = Math.max(childHeartbeatDeadlineMs, native.sentAtMs + input.timings.heartbeatTimeoutMs) + supervise() + } else if (native.type === "error") { send(native); stop("child_error") } + }) + ownedChild.once("exit", (code, signal) => { + exited = true + clearTimeout(supervisionTimer) + clearTimeout(escalationTimer) + const pid = ownedChild.pid + if (pid === undefined) { send({ type: "error", code: "native" }); process.disconnect?.(); return } + exitReceipt = { identity: input.identity, pid, code, signal, + reason: stopped ?? "exited", hardKilled, exitedAtMs: monotonicMilliseconds() } + // Keep the IPC channel alive until the parent observes this receipt. A stalled + // parent's queued heartbeat must not hit EPIPE before its buffered receipt. + send({ type: "exit", receipt: exitReceipt }) + }) + sendChild({ type: "start", identity: input.identity, payload: input.payload, requestedAtMs: input.requestedAtMs, + leaseMs: input.leaseMs, leaseDeadlineMs: lease.deadline }) + supervise() + return + } + if (message?.type === "heartbeat" && typeof message.sentAtMs === "number") { + const now = monotonicMilliseconds() + if (message.sentAtMs > now || !Number.isSafeInteger(message.sentAtMs)) { stop("authority"); return } + if ((stopped === undefined || stopped === "stopping") && now >= heartbeatDeadlineMs) { stop("parent_stalled"); return } + heartbeatDeadlineMs = Math.max(heartbeatDeadlineMs, message.sentAtMs + start.timings.heartbeatTimeoutMs) + supervise() + } else if (message?.type === "renew" && validIdentity(message.identity) + && typeof message.requestedAtMs === "number" && typeof message.leaseMs === "number") { + supervise() + if (stopped || !lease) return + try { + if (lease.renew(message.identity, message.requestedAtMs, message.leaseMs)) { + sendChild({ type: "renew", identity: message.identity, payload: message.payload, + requestedAtMs: message.requestedAtMs, leaseMs: message.leaseMs, leaseDeadlineMs: lease.deadline }) + } + supervise() + } catch { stop("authority") } + } else if (message?.type === "stop" && validStopReason(message.reason)) stop(message.reason) + else stop("authority") + }) + process.on("disconnect", () => stop("parent_disconnected")) + process.on("SIGTERM", () => stop("shutdown")) + process.on("SIGINT", () => stop("shutdown")) + // A parent that never delivers the initial start cannot leave a watchdog behind. + supervisionTimer = setTimeout(() => stop("startup"), 10_000) +} + +function isEntrypoint(): boolean { + if (!process.argv[1]) return false + try { return realpathSync(fileURLToPath(import.meta.url)) === realpathSync(process.argv[1]) } + catch { return false } +} +if (isEntrypoint()) runWatchdog() diff --git a/server/packages/grid-transcription/tsconfig.json b/server/packages/grid-transcription/tsconfig.json new file mode 100644 index 000000000..dbca062cd --- /dev/null +++ b/server/packages/grid-transcription/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", + "strict": true, "noUncheckedIndexedAccess": true, "skipLibCheck": true, + "rootDir": "src", "outDir": "dist", "declaration": true, + "types": ["node"], "verbatimModuleSyntax": true + }, + "include": ["src/**/*.ts"], "exclude": ["src/**/*.test.ts"] +} From 7ec6d0d8ff19d6648cb215d27a47ae64f33e58b4 Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Mon, 5 Oct 2026 06:53:55 +0330 Subject: [PATCH 04/14] macos: add grid transcription controls and quiet history --- .../Features/Message/UIMessageView.swift | 2 + .../Grid/GridTranscriptionControl.swift | 101 +++++++++ .../MacNotifications/MacNotifications.swift | 2 + .../Sources/InlineKit/Models/Chat.swift | 2 +- .../Sources/InlineKit/Models/Message.swift | 91 +++++++- .../ProtocolMessageContentPayload.swift | 45 +++- .../InlineKit/RealtimeAPI/Updates.swift | 19 +- .../Transactions2/GridTransactions.swift | 98 ++++++++ .../Grid/GridTranscriptionControlTests.swift | 87 +++++++ .../MacNotificationsTests.swift | 15 ++ .../MessageServicePersistenceTests.swift | 66 ++++++ .../UnreadReplayGuardTests.swift | 57 +++++ .../InlineMac/Features/Grid/GridRoomAPI.swift | 24 ++ apple/InlineMac/Features/Grid/GridStore.swift | 82 +++++++ .../Grid/GridTranscriptionControls.swift | 212 ++++++++++++++++++ .../GridTranscriptionSidebarIndicator.swift | 59 +++++ apple/InlineMac/Features/Grid/GridView.swift | 55 +++-- .../Features/Sidebar/SidebarView.swift | 15 ++ .../MessageList/ServiceMessageRowView.swift | 69 ++++-- 19 files changed, 1046 insertions(+), 55 deletions(-) create mode 100644 apple/InlineKit/Sources/InlineKit/Grid/GridTranscriptionControl.swift create mode 100644 apple/InlineKit/Tests/InlineKitTests/Grid/GridTranscriptionControlTests.swift create mode 100644 apple/InlineMac/Features/Grid/GridTranscriptionControls.swift create mode 100644 apple/InlineMac/Features/Grid/GridTranscriptionSidebarIndicator.swift diff --git a/apple/InlineIOS/Features/Message/UIMessageView.swift b/apple/InlineIOS/Features/Message/UIMessageView.swift index 29e52375d..eaea29480 100644 --- a/apple/InlineIOS/Features/Message/UIMessageView.swift +++ b/apple/InlineIOS/Features/Message/UIMessageView.swift @@ -683,6 +683,8 @@ class UIMessageView: UIView { private func serviceTextColor(for tone: MessageServiceDisplaySegment.Tone) -> UIColor { switch tone { + case .primary: + return .label case .secondary: return theme.incomingSecondaryText.uiColor case .tertiary: diff --git a/apple/InlineKit/Sources/InlineKit/Grid/GridTranscriptionControl.swift b/apple/InlineKit/Sources/InlineKit/Grid/GridTranscriptionControl.swift new file mode 100644 index 000000000..99fdcf590 --- /dev/null +++ b/apple/InlineKit/Sources/InlineKit/Grid/GridTranscriptionControl.swift @@ -0,0 +1,101 @@ +import Foundation +import InlineProtocol + +/// A single server snapshot fences a room action. A retry keeps this request ID +/// and never turns a stale stop into a stop for a replacement run. +public struct GridTranscriptionRequest: Codable, Sendable, Equatable { + public enum Destination: Codable, Sendable, Equatable { + case last + case new + case existing(Int64) + } + + public let roomID: Int64 + public let membershipID: String + public let generation: Int32 + public let runID: String? + public let revision: Int32 + public let enabled: Bool + public let destination: Destination + public let requestID: String + + public init?(room: GridRoom, enabled: Bool, destination: Destination = .last, requestID: String = UUID().uuidString) { + guard room.hasConnection, + let avatar = room.avatars.first(where: \.ownedByCurrentSession), + !avatar.membershipID.isEmpty, + room.connection.generation > 0, + enabled ? room.canStartTranscription : room.canStopTranscription + else { return nil } + if case let .existing(chatID) = destination, chatID <= 0 { + return nil + } + + roomID = room.id + membershipID = avatar.membershipID + generation = room.connection.generation + runID = room.hasTranscription && !room.transcription.runID.isEmpty ? room.transcription.runID : nil + revision = room.hasTranscription ? room.transcription.revision : 0 + self.enabled = enabled + self.destination = destination + self.requestID = requestID + } + + public var input: SetGridTranscriptionInput { + .with { + $0.roomID = roomID + $0.expectedMembershipID = membershipID + $0.expectedGeneration = generation + if let runID { + $0.expectedRunID = runID + } + $0.expectedRevision = revision + $0.enabled = enabled + switch destination { + case .last: $0.destination = .gridTranscriptLast + case .new: $0.destination = .gridTranscriptNew + case let .existing(chatID): + $0.destination = .gridTranscriptExisting + $0.transcriptChatID = chatID + } + $0.requestID = requestID + } + } +} + +public extension GridRoom { + var transcriptChatID: Int64? { + guard hasTranscription, transcription.hasTranscriptChatID, transcription.transcriptChatID > 0 else { return nil } + return transcription.transcriptChatID + } + + var transcriptionIsRunning: Bool { + hasTranscription && [.gridTranscriptionStarting, .gridTranscriptionActive, .gridTranscriptionStopping] + .contains(transcription.state) + } + + var transcriptionCanChange: Bool { + canStartTranscription || canStopTranscription + } + + var canStartTranscription: Bool { + guard transcriptionAvailable else { return false } + guard hasTranscription else { return true } + // Unknown future states are read-only until a client understands them. + return [.gridTranscriptionStopped, .gridTranscriptionInterrupted].contains(transcription.state) + } + + var canStopTranscription: Bool { + hasTranscription && [.gridTranscriptionStarting, .gridTranscriptionActive].contains(transcription.state) + } + + var transcriptionControlTitle: String { + guard hasTranscription else { return "Start transcription" } + switch transcription.state { + case .gridTranscriptionStarting: return "Cancel transcription" + case .gridTranscriptionActive: return "Stop transcription" + case .gridTranscriptionStopping: return "Stopping…" + case .gridTranscriptionStopped, .gridTranscriptionInterrupted: return "Start transcription" + default: return "Transcription unavailable" + } + } +} diff --git a/apple/InlineKit/Sources/InlineKit/MacNotifications/MacNotifications.swift b/apple/InlineKit/Sources/InlineKit/MacNotifications/MacNotifications.swift index 7984f60aa..5c3c88ee2 100644 --- a/apple/InlineKit/Sources/InlineKit/MacNotifications/MacNotifications.swift +++ b/apple/InlineKit/Sources/InlineKit/MacNotifications/MacNotifications.swift @@ -239,6 +239,8 @@ public actor MacNotifications { ) -> Bool { guard source == .newMessage else { return false } guard deliveryState.isNewlyInserted, deliveryState.isUnread else { return false } + guard !message.hasCountsAsUnread || message.countsAsUnread else { return false } + if case .gridTranscript = message.serviceMessage.event { return false } guard message.sendMode != .modeSilent else { return false } guard isFreshMessage(message, now: now) else { return false } diff --git a/apple/InlineKit/Sources/InlineKit/Models/Chat.swift b/apple/InlineKit/Sources/InlineKit/Models/Chat.swift index 01f846a5a..948522c1f 100644 --- a/apple/InlineKit/Sources/InlineKit/Models/Chat.swift +++ b/apple/InlineKit/Sources/InlineKit/Models/Chat.swift @@ -497,7 +497,7 @@ public extension Chat { static func updateLastMsgIds(_ db: Database, messages: [Message]) throws { var newestByChatId: [Int64: Message] = [:] - for message in messages { + for message in messages where !message.isGridTranscript { if let current = newestByChatId[message.chatId], !shouldAdvanceLastMessage( currentLastMsgId: current.messageId, diff --git a/apple/InlineKit/Sources/InlineKit/Models/Message.swift b/apple/InlineKit/Sources/InlineKit/Models/Message.swift index cb8246497..ad0d53799 100644 --- a/apple/InlineKit/Sources/InlineKit/Models/Message.swift +++ b/apple/InlineKit/Sources/InlineKit/Models/Message.swift @@ -621,7 +621,7 @@ public struct Message: FetchableRecord, Identifiable, Codable, Hashable, Persist } private static func hasContentPayload(_ payload: Client_MessageContentPayload) -> Bool { - payload.hasVoice || payload.hasActions || payload.hasReplies || payload.hasServiceMessage || payload.hasSubthread + payload.hasVoice || payload.hasActions || payload.hasReplies || payload.hasServiceMessage || payload.hasSubthread || payload.hasCountsAsUnread } private static func contentPayload(from voice: InlineProtocol.Voice) -> Client_MessageContentPayload { @@ -652,6 +652,7 @@ public struct Message: FetchableRecord, Identifiable, Codable, Hashable, Persist if message.hasSubthread { payload.subthread = message.subthread } + if message.hasCountsAsUnread { payload.countsAsUnread = message.countsAsUnread } return hasContentPayload(payload) ? payload : nil } @@ -759,6 +760,12 @@ public struct Message: FetchableRecord, Identifiable, Codable, Hashable, Persist } } + if incoming.hasCountsAsUnread { + merged.countsAsUnread = incoming.countsAsUnread + } else if existing.hasCountsAsUnread { + merged.countsAsUnread = existing.countsAsUnread + } + return hasContentPayload(merged) ? merged : nil case let (incoming?, nil): @@ -816,6 +823,7 @@ public struct MessageServiceDisplaySegment: Equatable, Sendable { } public enum Tone: Equatable, Sendable { + case primary case secondary case tertiary } @@ -841,8 +849,26 @@ public extension Message { serviceMessage?.event != nil } + var isGridTranscript: Bool { + if case .gridTranscript = serviceMessage?.event { + return true + } + return false + } + + /// Missing fields preserve ordinary-message behavior. Typed transcripts are + /// also quiet if a transitional server omitted the explicit flag. + var countsAsUnread: Bool { + if isGridTranscript { + return false + } + guard let contentPayload, contentPayload.hasCountsAsUnread else { return true } + return contentPayload.countsAsUnread + } + var serviceFallbackText: String? { guard let serviceMessage else { return nil } + if isGridTranscript { return text } return serviceMessage.fallbackText } @@ -888,11 +914,48 @@ public extension Message { actorSegment, MessageServiceDisplaySegment(text: " pinned a message"), ] + case let .gridTranscript(transcript): + let body = text ?? "" + if transcript.kind == .gridTranscriptTurn { + let prefix = "Transcript · " + let speech = body.hasPrefix(prefix) ? String(body.dropFirst(prefix.count)) : body + return [ + MessageServiceDisplaySegment(text: prefix, tone: .tertiary), + MessageServiceDisplaySegment(text: speech, tone: .primary), + ] + } + // Status and continuation links retain the server's self-contained + // text. The sender is an attribution owner, not the speaker. + return transcriptStatusSegments(text: body) case nil: return nil } } + private func transcriptStatusSegments(text: String) -> [MessageServiceDisplaySegment] { + let source = text as NSString + let threadEntities = (entities?.entities ?? []).filter { + $0.type == .thread && $0.thread.chatID > 0 && $0.offset >= 0 && $0.length > 0 + }.sorted { $0.offset < $1.offset } + var cursor = 0 + var segments: [MessageServiceDisplaySegment] = [] + for entity in threadEntities { + guard entity.offset <= Int64(source.length), + entity.length <= Int64(source.length) - entity.offset else { continue } + let range = NSRange(location: Int(entity.offset), length: Int(entity.length)) + guard range.location >= cursor else { continue } + if range.location > cursor { + segments.append(.init(text: source.substring(with: NSRange(location: cursor, length: range.location - cursor)))) + } + segments.append(.init(text: source.substring(with: range), link: .thread(entity.thread.chatID))) + cursor = NSMaxRange(range) + } + if cursor < source.length { + segments.append(.init(text: source.substring(from: cursor))) + } + return segments.isEmpty ? [.init(text: text)] : segments + } + /// Returns a string representation of the message, including emojis for different media types. var stringRepresentationWithEmoji: String { if let serviceFallbackText { @@ -1017,6 +1080,9 @@ public extension MessageService { return "Linked from another thread" case .pinnedMessage: return "Pinned a message" + case .gridTranscript: + // Its meaningful fallback is the encrypted Message.message text. + return nil case nil: return nil } @@ -1720,11 +1786,7 @@ public extension Message { // If the message we are about to delete is the last message of the chat, // we need to promote the previous message (if any) to be the new last one. if prevChatLastMsgId == messageId { - let previousMessage = try Message - .filter(Column("chatId") == chat?.id) - .order(Column("date").desc) - .limit(1, offset: 1) - .fetchOne(db) + let previousMessage = try latestActivityMessage(db, chatId: chatId, excluding: messageIds) var updatedChat = chat updatedChat?.lastMsgId = previousMessage?.messageId @@ -1733,7 +1795,7 @@ public extension Message { // Preserve the information that we have already handled the current // `lastMsgId` so subsequent deletions in the same batch don't repeat // the update work unnecessarily. - prevChatLastMsgId = messageId + prevChatLastMsgId = previousMessage?.messageId } // Remove the message itself. @@ -1743,4 +1805,19 @@ public extension Message { .deleteAll(db) } } + + /// Generated transcript rows are history, never sidebar activity. The + /// existing protobuf payload requires decoding; a cursor avoids loading a + /// whole transcript into memory when an ordinary chat-tail message is deleted. + static func latestActivityMessage(_ db: Database, chatId: Int64, excluding messageIds: [Int64]) throws -> Message? { + let cursor = try Message + .filter(Column("chatId") == chatId) + .filter(!messageIds.contains(Column("messageId"))) + .order(Column("date").desc, Column("messageId").desc) + .fetchCursor(db) + while let message = try cursor.next() { + if !message.isGridTranscript { return message } + } + return nil + } } diff --git a/apple/InlineKit/Sources/InlineKit/ProtocolHelpers/ProtocolMessageContentPayload.swift b/apple/InlineKit/Sources/InlineKit/ProtocolHelpers/ProtocolMessageContentPayload.swift index 8abe55ecf..d6800269e 100644 --- a/apple/InlineKit/Sources/InlineKit/ProtocolHelpers/ProtocolMessageContentPayload.swift +++ b/apple/InlineKit/Sources/InlineKit/ProtocolHelpers/ProtocolMessageContentPayload.swift @@ -10,6 +10,7 @@ extension Client_MessageContentPayload: Codable { case replies case serviceMessage case subthread + case countsAsUnread } public init(from decoder: Decoder) throws { @@ -19,6 +20,7 @@ extension Client_MessageContentPayload: Codable { let replies = try container.decodeIfPresent(MessageReplies.self, forKey: .replies) let serviceMessage = try container.decodeIfPresent(MessageService.self, forKey: .serviceMessage) let subthread = try container.decodeIfPresent(MessageSubthread.self, forKey: .subthread) + let countsAsUnread = try container.decodeIfPresent(Bool.self, forKey: .countsAsUnread) self.init() if let voice { @@ -36,6 +38,9 @@ extension Client_MessageContentPayload: Codable { if let subthread { self.subthread = subthread } + if let countsAsUnread { + self.countsAsUnread = countsAsUnread + } } public func encode(to encoder: Encoder) throws { @@ -55,6 +60,9 @@ extension Client_MessageContentPayload: Codable { if hasSubthread { try container.encode(subthread, forKey: .subthread) } + if hasCountsAsUnread { + try container.encode(countsAsUnread, forKey: .countsAsUnread) + } } } @@ -63,11 +71,13 @@ extension MessageService: Codable { case event case threadBacklink case pinnedMessage + case gridTranscript } private enum Event: String, Codable { case threadBacklink case pinnedMessage + case gridTranscript } public init(from decoder: Decoder) throws { @@ -89,6 +99,10 @@ extension MessageService: Codable { if let message = try container.decodeIfPresent(MessageServicePinnedMessage.self, forKey: .pinnedMessage) { pinnedMessage = message } + case .gridTranscript: + if let transcript = try container.decodeIfPresent(MessageServiceGridTranscript.self, forKey: .gridTranscript) { + gridTranscript = transcript + } } } @@ -101,12 +115,41 @@ extension MessageService: Codable { case .pinnedMessage: try container.encode(Event.pinnedMessage, forKey: .event) try container.encode(pinnedMessage, forKey: .pinnedMessage) + case .gridTranscript: + try container.encode(Event.gridTranscript, forKey: .event) + try container.encode(gridTranscript, forKey: .gridTranscript) case nil: break } } } +extension MessageServiceGridTranscript: Codable { + private enum CodingKeys: String, CodingKey { case runID, segmentID, speakerUserID, kind } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.init() + runID = try container.decodeIfPresent(String.self, forKey: .runID) ?? "" + segmentID = try container.decodeIfPresent(String.self, forKey: .segmentID) ?? "" + if let speakerUserID = try container.decodeIfPresent(Int64.self, forKey: .speakerUserID) { + self.speakerUserID = speakerUserID + } + let rawKind = try container.decodeIfPresent(Int.self, forKey: .kind) ?? 0 + kind = GridTranscriptMessageKind(rawValue: rawKind) ?? .UNRECOGNIZED(rawKind) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(runID, forKey: .runID) + try container.encode(segmentID, forKey: .segmentID) + if hasSpeakerUserID { + try container.encode(speakerUserID, forKey: .speakerUserID) + } + try container.encode(kind.rawValue, forKey: .kind) + } +} + extension MessageServiceThreadBacklink: Codable { private enum CodingKeys: String, CodingKey { case sourceChatID @@ -200,7 +243,7 @@ extension MessageSubthread: Codable { self.init() chatID = try container.decodeIfPresent(Int64.self, forKey: .chatID) ?? 0 - kind = Kind(rawValue: try container.decodeIfPresent(Int.self, forKey: .kind) ?? 0) ?? .unspecified + kind = try Kind(rawValue: container.decodeIfPresent(Int.self, forKey: .kind) ?? 0) ?? .unspecified if let title = try container.decodeIfPresent(String.self, forKey: .title) { self.title = title } diff --git a/apple/InlineKit/Sources/InlineKit/RealtimeAPI/Updates.swift b/apple/InlineKit/Sources/InlineKit/RealtimeAPI/Updates.swift index 5e1390ae9..847d2469d 100644 --- a/apple/InlineKit/Sources/InlineKit/RealtimeAPI/Updates.swift +++ b/apple/InlineKit/Sources/InlineKit/RealtimeAPI/Updates.swift @@ -2387,7 +2387,9 @@ extension InlineProtocol.UpdateNewMessage { materializeMissingReferences: materializeMissingReferences ) - try Chat.updateLastMsgId(db, chatId: message.chatID, lastMsgId: msg.messageId, date: msg.date) + if !msg.isGridTranscript { + try Chat.updateLastMsgId(db, chatId: message.chatID, lastMsgId: msg.messageId, date: msg.date) + } // Increase unread count only when this message is newly inserted, not ours, // and newer than the dialog's read cursor. Catch-up applies dialog sidecars @@ -2399,7 +2401,9 @@ extension InlineProtocol.UpdateNewMessage { var didIncrement = false var reason = "not_newer_than_read_max" - if !incrementUnreadCount { + if !msg.countsAsUnread { + reason = "quiet_message" + } else if !incrementUnreadCount { reason = "increment_disabled" } else if hadMessage { reason = "duplicate_message" @@ -2429,6 +2433,7 @@ extension InlineProtocol.UpdateNewMessage { if !suppressNotifications, !hadMessage, msg.out == false, + msg.countsAsUnread, MacNotifications.isFreshMessage(message, now: Date()), let currentUserID = Auth.shared.getCurrentUserId() { db.afterNextTransaction { committedDB in @@ -2502,7 +2507,9 @@ extension InlineProtocol.UpdateMessageId { publishChanges: true ) - try Chat.updateLastMsgId(db, chatId: message.chatId, lastMsgId: message.messageId, date: message.date) + if !message.isGridTranscript { + try Chat.updateLastMsgId(db, chatId: message.chatId, lastMsgId: message.messageId, date: message.date) + } } } } @@ -2585,11 +2592,7 @@ extension InlineProtocol.UpdateDeleteMessages { for messageId in messageIds { // Update last message first if prevChatLastMsgId == messageId { - let previousMessage = try Message - .filter(Column("chatId") == chat.id) - .order(Column("date").desc, Column("messageId").desc) - .limit(1, offset: 1) - .fetchOne(db) + let previousMessage = try Message.latestActivityMessage(db, chatId: chat.id, excluding: messageIds) var updatedChat = chat updatedChat.lastMsgId = previousMessage?.messageId diff --git a/apple/InlineKit/Sources/InlineKit/Transactions2/GridTransactions.swift b/apple/InlineKit/Sources/InlineKit/Transactions2/GridTransactions.swift index 81c309193..2ca1e8d61 100644 --- a/apple/InlineKit/Sources/InlineKit/Transactions2/GridTransactions.swift +++ b/apple/InlineKit/Sources/InlineKit/Transactions2/GridTransactions.swift @@ -265,3 +265,101 @@ public extension Transaction2 where Self == SetGridAvatarMicTransaction { .init(roomID: roomID, enabled: enabled) } } + +public struct OpenGridThreadTransaction: Transaction2 { + public var method: InlineProtocol.Method = .openGridThread + public var context: Context + public var type: TransactionKindType = .mutation(.init(transient: true)) + public var reconnectReplayPolicy: TransactionReconnectPolicy? { + .neverReplay + } + + public struct Context: Sendable, Codable { + let roomID: Int64 + let membershipID: String + } + + enum CodingKeys: String, CodingKey { case context } + public init(roomID: Int64, membershipID: String) { + context = Context(roomID: roomID, membershipID: membershipID) + } + + public func input(from context: Context) -> RpcCall.OneOf_Input? { + .openGridThread(.with { + $0.roomID = context.roomID + $0.expectedMembershipID = context.membershipID + }) + } + + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { + guard case let .openGridThread(response) = result else { throw .invalid } + await Api.realtime.applyUpdatesAndWait(response.updates) + } +} + +public struct SetGridTranscriptionTransaction: Transaction2 { + public var method: InlineProtocol.Method = .setGridTranscription + public var context: GridTranscriptionRequest + public var type: TransactionKindType = .mutation(.init(transient: true)) + public var reconnectReplayPolicy: TransactionReconnectPolicy? { + .neverReplay + } + + enum CodingKeys: String, CodingKey { case context } + public init(request: GridTranscriptionRequest) { + context = request + } + + public func input(from context: GridTranscriptionRequest) -> RpcCall.OneOf_Input? { + .setGridTranscription(context.input) + } + + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { + guard case let .setGridTranscription(response) = result else { throw .invalid } + await Api.realtime.applyUpdatesAndWait(response.updates) + } +} + +public struct ListGridTranscriptsTransaction: Transaction2 { + public var method: InlineProtocol.Method = .listGridTranscripts + public var context: Context + public var type: TransactionKindType = .ephemeral() + public var ephemeralCoalescingKey: String? { + "room:\(context.roomID)" + } + + public struct Context: Sendable, Codable { let roomID: Int64 } + enum CodingKeys: String, CodingKey { case context } + public init(roomID: Int64) { + context = Context(roomID: roomID) + } + + public func input(from context: Context) -> RpcCall.OneOf_Input? { + .listGridTranscripts(.with { $0.roomID = context.roomID }) + } + + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { + guard case .listGridTranscripts = result else { throw .invalid } + } +} + +public extension Transaction2 where Self == OpenGridThreadTransaction { + static func openGridThread(roomID: Int64, membershipID: String) -> Self { + .init( + roomID: roomID, + membershipID: membershipID + ) + } +} + +public extension Transaction2 where Self == SetGridTranscriptionTransaction { + static func setGridTranscription(request: GridTranscriptionRequest) -> Self { + .init(request: request) + } +} + +public extension Transaction2 where Self == ListGridTranscriptsTransaction { + static func listGridTranscripts(roomID: Int64) -> Self { + .init(roomID: roomID) + } +} diff --git a/apple/InlineKit/Tests/InlineKitTests/Grid/GridTranscriptionControlTests.swift b/apple/InlineKit/Tests/InlineKitTests/Grid/GridTranscriptionControlTests.swift new file mode 100644 index 000000000..2311214de --- /dev/null +++ b/apple/InlineKit/Tests/InlineKitTests/Grid/GridTranscriptionControlTests.swift @@ -0,0 +1,87 @@ +import Foundation +@testable import InlineKit +import InlineProtocol +import Testing + +@Suite("Grid transcription control") +struct GridTranscriptionControlTests { + private func room(state: Int? = nil) -> GridRoom { + .with { + $0.id = 7 + $0.spaceID = 42 + $0.transcriptionAvailable = true + $0.connection = .with { $0.generation = 3 } + $0.avatars = [.with { $0.ownedByCurrentSession = true + $0.membershipID = "member-1" + }] + if let state { + $0.transcription = .with { + $0.runID = "run-1" + $0.revision = 8 + $0.state = GridTranscriptionState(rawValue: state)! + $0.transcriptChatID = 999 + } + } + } + } + + @Test("the action captures membership, generation, run and revision in one snapshot") + func fencedSnapshotSurvivesRetryEncoding() throws { + let request = try #require(GridTranscriptionRequest(room: room(state: 2), enabled: false, requestID: "intent-1")) + let encoded = try JSONEncoder().encode(request) + let decoded = try JSONDecoder().decode(GridTranscriptionRequest.self, from: encoded) + let input = decoded.input + #expect(decoded == request) + #expect(input.roomID == 7) + #expect(input.expectedMembershipID == "member-1") + #expect(input.expectedGeneration == 3) + #expect(input.expectedRunID == "run-1") + #expect(input.expectedRevision == 8) + #expect(input.requestID == "intent-1") + #expect(!input.enabled) + #expect(SetGridTranscriptionTransaction(request: request).effectiveReconnectReplayPolicy == .neverReplay) + } + + @Test("new destination and existing destination stay explicit without model UI fields") + func explicitDestination() throws { + let fresh = try #require(GridTranscriptionRequest(room: room(), enabled: true, destination: .new)) + #expect(fresh.input.destination.rawValue == 1) + #expect(!fresh.input.hasExpectedRunID) + #expect(!fresh.input.hasTranscriptChatID) + let continued = try #require(GridTranscriptionRequest(room: room(), enabled: true, destination: .existing(99))) + #expect(continued.input.destination.rawValue == 2) + #expect(continued.input.transcriptChatID == 99) + #expect(GridTranscriptionRequest(room: room(), enabled: true, destination: .existing(0)) == nil) + } + + @Test("unavailable, unowned, pending and future states cannot advertise an action") + func noInertOrUnfencedControl() { + var unavailable = room() + unavailable.transcriptionAvailable = false + #expect(GridTranscriptionRequest(room: unavailable, enabled: true) == nil) + var observer = room() + observer.avatars[0].ownedByCurrentSession = false + #expect(GridTranscriptionRequest(room: observer, enabled: true) == nil) + var noGeneration = room() + noGeneration.clearConnection() + #expect(GridTranscriptionRequest(room: noGeneration, enabled: true) == nil) + #expect(GridTranscriptionRequest(room: room(state: 1), enabled: true) == nil) + #expect(GridTranscriptionRequest(room: room(state: 1), enabled: false) != nil) + #expect(GridTranscriptionRequest(room: room(state: 3), enabled: false) == nil) + #expect(GridTranscriptionRequest(room: room(state: 99), enabled: true) == nil) + #expect(room(state: 1).transcriptionControlTitle == "Cancel transcription") + #expect(room(state: 3).transcriptionControlTitle == "Stopping…") + #expect(room(state: 5).transcriptionControlTitle == "Start transcription") + } + + @Test("worker availability cannot strand an existing capture without stop") + func stopRemainsAvailableWhenWorkerDrops() { + var active = room(state: 2) + active.transcriptionAvailable = false + #expect(GridTranscriptionRequest(room: active, enabled: false) != nil) + #expect(GridTranscriptionRequest(room: active, enabled: true) == nil) + var starting = room(state: 1) + starting.transcriptionAvailable = false + #expect(GridTranscriptionRequest(room: starting, enabled: false) != nil) + } +} diff --git a/apple/InlineKit/Tests/InlineKitTests/MacNotificationsTests.swift b/apple/InlineKit/Tests/InlineKitTests/MacNotificationsTests.swift index 863725d90..2a3d12b95 100644 --- a/apple/InlineKit/Tests/InlineKitTests/MacNotificationsTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/MacNotificationsTests.swift @@ -10,6 +10,21 @@ import Testing @Suite("Mac notifications") struct MacNotificationsTests { + @Test("quiet generated transcripts cannot notify even when personally addressed") + func transcriptsNeverNotify() { + var message = makeMessage(mentioned: true) + message.countsAsUnread = false + message.serviceMessage.gridTranscript = .init() + #expect(!shouldSchedule(message, mode: .all, isPersonallyAddressed: true)) + message.clearCountsAsUnread() + #expect(!shouldSchedule(message, mode: .all, isPersonallyAddressed: true)) + message.clearServiceMessage() + message.countsAsUnread = false + #expect(!shouldSchedule(message, mode: .all, isPersonallyAddressed: true)) + message.clearCountsAsUnread() + #expect(shouldSchedule(message, mode: .all, isPersonallyAddressed: true)) + } + private let now = Date(timeIntervalSince1970: 1_000) private func makeMessage( diff --git a/apple/InlineKit/Tests/InlineKitTests/MessageServicePersistenceTests.swift b/apple/InlineKit/Tests/InlineKitTests/MessageServicePersistenceTests.swift index 59788b5f2..25ce7a92f 100644 --- a/apple/InlineKit/Tests/InlineKitTests/MessageServicePersistenceTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/MessageServicePersistenceTests.swift @@ -6,6 +6,72 @@ import Testing @Suite("Message Service Persistence") struct MessageServicePersistenceTests { + @Test("transcript metadata and quietness survive durable JSON before row construction") + func transcriptPersistenceAndProvenance() throws { + var proto = InlineProtocol.Message.with { + $0.id = 101 + $0.chatID = 44 + $0.fromID = 9 + $0.date = 1 + $0.out = true + $0.peerID.chat.chatID = 44 + $0.message = "Transcript · Ada: Let’s ship the small version." + $0.countsAsUnread = false + $0.serviceMessage.gridTranscript = .with { + $0.runID = "run-1" + $0.segmentID = "segment-2" + $0.speakerUserID = 10 + $0.kind = GridTranscriptMessageKind(rawValue: 0)! + } + } + let message = Message(from: proto) + let payload = try #require(message.contentPayload) + let restored = try JSONDecoder().decode(Client_MessageContentPayload.self, from: JSONEncoder().encode(payload)) + #expect(restored.hasCountsAsUnread) + #expect(!restored.countsAsUnread) + #expect(restored.serviceMessage.gridTranscript.runID == "run-1") + #expect(restored.serviceMessage.gridTranscript.speakerUserID == 10) + #expect(!message.countsAsUnread) + #expect(message.serviceDisplayText(actorName: "Creator") == "Transcript · Ada: Let’s ship the small version.") + #expect(message.stringRepresentationPlain == proto.message) + #expect(try !(#require(message.serviceDisplayText(actorName: "Creator")?.contains("You")))) + proto.clearCountsAsUnread() + #expect(!Message(from: proto).countsAsUnread) + proto.clearServiceMessage() + #expect(Message(from: proto).countsAsUnread) + } + + @Test("original anchors and continuation links retain validated UTF16 thread ranges", arguments: [ + GridTranscriptMessageKind.gridTranscriptStarted, .gridTranscriptLink, + ]) + func continuationNavigation(kind: GridTranscriptMessageKind) { + let isAnchor = kind == .gridTranscriptStarted + let body = isAnchor ? "Grid transcription" : "Continue in Planning" + let proto = InlineProtocol.Message.with { + $0.message = body + $0.serviceMessage.gridTranscript.kind = kind + $0.entities.entities = [.with { + $0.type = .thread + $0.offset = isAnchor ? 0 : 12 + $0.length = isAnchor ? 18 : 8 + $0.thread.chatID = 77 + }, .with { + $0.type = .thread + $0.offset = Int64.max + $0.length = 8 + $0.thread.chatID = 88 + }] + } + let message = Message(from: proto) + let expected: [MessageServiceDisplaySegment] = isAnchor ? [ + .init(text: body, link: .thread(77)), + ] : [ + .init(text: "Continue in "), + .init(text: "Planning", link: .thread(77)), + ] + #expect(message.serviceDisplaySegments(actorName: "Creator") == expected) + } + @Test("protocol service payload is persisted and exposed via helpers") func protocolServicePayloadPersistedAndReadable() { var proto = InlineProtocol.Message() diff --git a/apple/InlineKit/Tests/InlineKitTests/UnreadReplayGuardTests.swift b/apple/InlineKit/Tests/InlineKitTests/UnreadReplayGuardTests.swift index eb11d18ce..b7714df71 100644 --- a/apple/InlineKit/Tests/InlineKitTests/UnreadReplayGuardTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/UnreadReplayGuardTests.swift @@ -8,6 +8,63 @@ import Testing @Suite("Unread Replay Guard") struct UnreadReplayGuardTests { + @Test("quiet transcript rows persist and advance history without changing unread") + func quietTranscriptDoesNotIncrementUnread() throws { + let dbQueue = try makeInMemoryDB() + try dbQueue.write { (db: Database) throws in + try seedDialog(db, readInboxMaxId: 20, unreadCount: 2) + var update = makeNewMessageUpdate(messageId: 21) + update.message.countsAsUnread = false + update.message.serviceMessage.gridTranscript = .with { + $0.runID = "run-1" + $0.segmentID = "segment-1" + } + try update.apply(db, publishChanges: false, suppressNotifications: true) + #expect(try Dialog.get(peerId: .thread(id: chatId)).fetchOne(db)?.unreadCount == 2) + let fetched = try Message.fetchOne(db, key: ["messageId": 21, "chatId": chatId]) + let saved = try #require(fetched) + #expect(saved.isGridTranscript) + #expect(!saved.countsAsUnread) + #expect(try Chat.fetchOne(db, key: chatId)?.lastMsgId == nil) + // A replay must preserve durable quietness as well as deduplication. + try update.apply(db, publishChanges: false, suppressNotifications: true) + #expect(try Dialog.get(peerId: .thread(id: chatId)).fetchOne(db)?.unreadCount == 2) + } + } + + @Test("transcripts stay out of sidebar activity during history and chat-tail deletion") + func transcriptActivityHistoryAndDeletion() throws { + let queue = try makeInMemoryDB() + try queue.write { (db: Database) throws in + try seedDialog(db, readInboxMaxId: 20, unreadCount: 0) + let earlier = makeNewMessageUpdate(messageId: 1) + try earlier.apply(db, publishChanges: false, suppressNotifications: true) + var transcript = makeNewMessageUpdate(messageId: 2) + transcript.message.date = 5 + transcript.message.serviceMessage.gridTranscript = .init() + transcript.message.countsAsUnread = false + try transcript.apply(db, publishChanges: false, suppressNotifications: true) + #expect(try Chat.fetchOne(db, key: chatId)?.lastMsgId == 1) + var later = makeNewMessageUpdate(messageId: 3) + later.message.date = 4 + try later.apply(db, publishChanges: false, suppressNotifications: true) + try Chat.updateLastMsgIds(db, messages: [Message(from: earlier.message), Message(from: transcript.message)]) + #expect(try Chat.fetchOne(db, key: chatId)?.lastMsgId == 3) + + let deletion = InlineProtocol.UpdateDeleteMessages.with { + $0.peerID.chat.chatID = chatId + $0.messageIds = [3] + } + try deletion.apply(db, publishChanges: false) + #expect(try Chat.fetchOne(db, key: chatId)?.lastMsgId == 1) + #expect(try Message.filter(Message.Columns.chatId == chatId && Message.Columns.messageId == 1) + .fetchOne(db)?.date == Date(timeIntervalSince1970: 2)) + try Message.deleteMessages(db, messageIds: [1], chatId: chatId) + #expect(try Chat.fetchOne(db, key: chatId)?.lastMsgId == nil) + #expect(try Message.fetchCount(db) == 1) + } + } + private let chatId: Int64 = 1_000 private let senderId: Int64 = 99 diff --git a/apple/InlineMac/Features/Grid/GridRoomAPI.swift b/apple/InlineMac/Features/Grid/GridRoomAPI.swift index 565fd3958..eea3527dc 100644 --- a/apple/InlineMac/Features/Grid/GridRoomAPI.swift +++ b/apple/InlineMac/Features/Grid/GridRoomAPI.swift @@ -51,6 +51,30 @@ final class GridRoomAPI { return response.spaces } + func openRoomThread(roomID: Int64, membershipID: String) async throws -> Int64 { + let result = try await realtime.send(.openGridThread(roomID: roomID, membershipID: membershipID)) + guard case let .openGridThread(response)? = result, response.chatID > 0 else { + throw GridRoomAPIError.invalidResponse + } + return response.chatID + } + + func setTranscription(request: GridTranscriptionRequest) async throws -> InlineProtocol.Grid { + let result = try await realtime.send(.setGridTranscription(request: request)) + guard case let .setGridTranscription(response)? = result, response.hasGrid else { + throw GridRoomAPIError.invalidResponse + } + return response.grid + } + + func listTranscripts(roomID: Int64) async throws -> [GridTranscriptDestinationInfo] { + let result = try await realtime.send(.listGridTranscripts(roomID: roomID)) + guard case let .listGridTranscripts(response)? = result else { + throw GridRoomAPIError.invalidResponse + } + return Array(response.transcripts.prefix(20)) + } + func createRoom( spaceID: Int64, microphoneEnabled: Bool diff --git a/apple/InlineMac/Features/Grid/GridStore.swift b/apple/InlineMac/Features/Grid/GridStore.swift index 245ad65eb..52115aeb6 100644 --- a/apple/InlineMac/Features/Grid/GridStore.swift +++ b/apple/InlineMac/Features/Grid/GridStore.swift @@ -20,6 +20,7 @@ final class GridRoomService { private(set) var lastError: String? private(set) var networkRefreshRevision = 0 private(set) var networkAvailable = true + private(set) var pendingTranscriptionRequestIDs: [Int64: String] = [:] var connectionRecoveryAttempt: Int { media.recoveryAttempt } let media: GridMediaPresentation @@ -176,6 +177,7 @@ final class GridRoomService { homeLoadRevision &+= 1 roomMutationRevisions.removeAll() pendingRoomMutations.removeAll() + pendingTranscriptionRequestIDs.removeAll() membershipMutationRevision &+= 1 pendingMembershipMutations.removeAll() membershipRollbackBaseline.clear() @@ -189,6 +191,17 @@ final class GridRoomService { grids[spaceID] } + var activeTranscriptionRoom: GridRoom? { + grids.values.lazy.compactMap { grid in + guard grid.hasCurrentRoomID, + let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), + room.transcriptionIsRunning, + room.avatars.contains(where: { $0.ownedByCurrentSession && !$0.membershipID.isEmpty }) + else { return nil } + return room + }.first + } + func recentAvatars(spaceID: Int64, limit: Int = 4) -> [GridAvatar] { guard let grid = grids[spaceID] else { return [] } return Array( @@ -468,6 +481,75 @@ final class GridRoomService { reconcileMediaDemand() } + func openRoomThread(roomID: Int64) async throws -> Int64 { + let room = try transcriptionRoom(roomID: roomID) + guard let avatar = room.avatars.first(where: \.ownedByCurrentSession), !avatar.membershipID.isEmpty else { + throw GridRoomAPIError.invalidResponse + } + let chatID = try await api.openRoomThread(roomID: roomID, membershipID: avatar.membershipID) + let latestRoom = try transcriptionRoom(roomID: roomID) + guard latestRoom.avatars.contains(where: { $0.ownedByCurrentSession && $0.membershipID == avatar.membershipID }) + else { + throw GridRoomAPIError.invalidResponse + } + // Thread sidecars are applied by the transaction before navigation. + return chatID + } + + func setTranscription( + room: GridRoom, + enabled: Bool, + destination: GridTranscriptionRequest.Destination = .last + ) async throws { + let roomID = room.id + guard pendingTranscriptionRequestIDs[roomID] == nil else { throw GridRoomAPIError.invalidResponse } + let latestRoom = try transcriptionRoom(roomID: roomID) + guard let request = GridTranscriptionRequest(room: room, enabled: enabled, destination: destination) else { + throw GridRoomAPIError.invalidResponse + } + guard latestRoom.avatars.contains(where: { $0.ownedByCurrentSession && $0.membershipID == request.membershipID }), + latestRoom.hasConnection, latestRoom.connection.generation == request.generation + else { throw GridRoomAPIError.invalidResponse } + let accessRevision = spaceAccessRevisions[room.spaceID, default: 0] + pendingTranscriptionRequestIDs[roomID] = request.requestID + defer { + if pendingTranscriptionRequestIDs[roomID] == request.requestID { + pendingTranscriptionRequestIDs.removeValue(forKey: roomID) + } + } + do { + let grid = try await api.setTranscription(request: request) + guard accessRevision == spaceAccessRevisions[room.spaceID, default: 0] else { return } + applySnapshot([grid]) + } catch { + // A rejected fence or uncertain response must repair from the authority, + // never synthesize an active/stopped state in the client. + if accessRevision == spaceAccessRevisions[room.spaceID, default: 0] { + try? await reloadGrid(spaceID: room.spaceID) + } + throw error + } + } + + func listTranscripts(roomID: Int64) async throws -> [GridTranscriptDestinationInfo] { + let room = try transcriptionRoom(roomID: roomID) + let membershipID = room.avatars.first(where: \.ownedByCurrentSession)?.membershipID + let transcripts = try await api.listTranscripts(roomID: roomID) + let latestRoom = try transcriptionRoom(roomID: roomID) + guard latestRoom.avatars.contains(where: { $0.ownedByCurrentSession && $0.membershipID == membershipID }) else { + throw GridRoomAPIError.invalidResponse + } + return transcripts + } + + private func transcriptionRoom(roomID: Int64) throws -> GridRoom { + guard let grid = grids.values.first(where: { $0.hasCurrentRoomID && $0.currentRoomID == roomID }), + let room = grid.rooms.first(where: { $0.id == roomID }), + room.avatars.contains(where: { $0.ownedByCurrentSession && !$0.membershipID.isEmpty }) + else { throw GridRoomAPIError.invalidResponse } + return room + } + func toggleCurrentMicrophone() { guard let grid = grids.values.first(where: { grid in guard grid.hasCurrentRoomID, diff --git a/apple/InlineMac/Features/Grid/GridTranscriptionControls.swift b/apple/InlineMac/Features/Grid/GridTranscriptionControls.swift new file mode 100644 index 000000000..650d880af --- /dev/null +++ b/apple/InlineMac/Features/Grid/GridTranscriptionControls.swift @@ -0,0 +1,212 @@ +import InlineKit +import InlineProtocol +import SwiftUI + +/// Room snapshots own capture state. View state is limited to an open picker +/// and presentation of a failed request. +struct GridTranscriptionControls: View { + let room: GridRoom + let isBusy: Bool + let onSetTranscription: (Bool, GridTranscriptionRequest.Destination) async throws -> Void + let onOpenRoomThread: () async throws -> Void + let onOpenTranscript: () -> Void + let onListTranscripts: () async throws -> [GridTranscriptDestinationInfo] + + @State private var showsPicker = false + @State private var isOpeningRoomThread = false + @State private var errorText: String? + + var body: some View { + VStack(spacing: 6) { + if let errorText { + Text(errorText) + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + } + if room.hasTranscription, room.transcription.state == .gridTranscriptionStarting { + Text("Starting transcription…").font(.caption).foregroundStyle(.secondary) + } + HStack(spacing: 8) { + if room.transcriptionAvailable || room.transcriptionIsRunning { + Button { + setTranscription(enabled: !room.transcriptionIsRunning, destination: .last) + } label: { + Label( + room.transcriptionControlTitle, + systemImage: room.transcriptionIsRunning ? "stop.circle" : "text.bubble" + ) + } + .disabled(isBusy || !canControl) + .help(room.transcriptionControlTitle) + } + + if room.hasTranscription, room.transcription.state == .gridTranscriptionActive { + Circle().fill(.green).frame(width: 6, height: 6) + .accessibilityLabel("Transcription is active") + } + + if room.transcriptionAvailable { + Menu { + Button("Start a new transcript") { + setTranscription(enabled: true, destination: .new) + } + Button("Continue earlier transcript…") { showsPicker = true } + } label: { + Label("Transcript options", systemImage: "ellipsis") + .labelStyle(.iconOnly) + } + .menuIndicator(.hidden) + .disabled(isBusy || !canControl || !room.canStartTranscription) + .help("Transcript options") + .popover(isPresented: $showsPicker) { + GridTranscriptPicker( + isBusy: isBusy, + onLoad: onListTranscripts, + onSelect: { chatID in + try await onSetTranscription(true, .existing(chatID)) + showsPicker = false + } + ) + .id(room.id) + } + } + + Divider().frame(height: 16) + + if room.transcriptChatID != nil { + Button("Open transcript", action: onOpenTranscript) + } + Button { + isOpeningRoomThread = true + errorText = nil + Task { + defer { isOpeningRoomThread = false } + do { try await onOpenRoomThread() } + catch { errorText = "Couldn’t open the room thread. Try again." } + } + } label: { + Label("Room thread", systemImage: "bubble.left.and.bubble.right") + } + .disabled(isOpeningRoomThread || !ownsMembership) + } + .buttonStyle(.plain) + .font(.system(size: 12, weight: .medium)) + .padding(.horizontal, 12) + .padding(.vertical, 9) + .background(.regularMaterial, in: Capsule()) + + if room.hasTranscription, room.transcription.state == .gridTranscriptionInterrupted { + Text("Transcription was interrupted. Start again to continue.") + .font(.caption) + .foregroundStyle(.secondary) + } + } + .onChange(of: room.id) { + showsPicker = false + errorText = nil + } + } + + private var ownsMembership: Bool { + room.avatars.contains { $0.ownedByCurrentSession && !$0.membershipID.isEmpty } + } + + private var canControl: Bool { + ownsMembership && room.hasConnection && room.connection.generation > 0 && room.transcriptionCanChange + } + + private func setTranscription(enabled: Bool, destination: GridTranscriptionRequest.Destination) { + errorText = nil + Task { + do { try await onSetTranscription(enabled, destination) } + catch { errorText = "Couldn’t confirm the change. Check transcription and try again." } + } + } +} + +private struct GridTranscriptPicker: View { + let isBusy: Bool + let onLoad: () async throws -> [GridTranscriptDestinationInfo] + let onSelect: (Int64) async throws -> Void + + @State private var transcripts: [GridTranscriptDestinationInfo] = [] + @State private var isLoading = true + @State private var errorText: String? + @State private var loadRevision = 0 + @State private var selectedChatID: Int64? + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + Text("Continue earlier transcript").font(.headline) + Text("Current and future participants get access to the earlier room’s full thread history.") + .font(.caption) + .foregroundStyle(.secondary) + .fixedSize(horizontal: false, vertical: true) + + if isLoading { + ProgressView().frame(maxWidth: .infinity).padding(.vertical, 18) + } else if transcripts.isEmpty, errorText == nil { + Text("No earlier transcripts are available to share.") + .foregroundStyle(.secondary) + .padding(.vertical, 12) + } else { + ScrollView { + VStack(alignment: .leading, spacing: 4) { + ForEach(transcripts, id: \.transcriptChatID) { transcript in + Button { + selectedChatID = transcript.transcriptChatID + errorText = nil + Task { + defer { selectedChatID = nil } + do { try await onSelect(transcript.transcriptChatID) } + catch { errorText = "Couldn’t continue this transcript. Try again." } + } + } label: { + HStack { + Text(transcript.title.isEmpty ? "Transcript" : transcript.title) + .lineLimit(2) + .multilineTextAlignment(.leading) + Spacer(minLength: 8) + if transcript.busy { + Text("In use").font(.caption).foregroundStyle(.secondary) + } else if selectedChatID == transcript.transcriptChatID { + ProgressView().controlSize(.small) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 7) + .padding(.horizontal, 8) + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .disabled(transcript.busy || isBusy || selectedChatID != nil) + } + } + } + .frame(maxHeight: 270) + } + if let errorText { + Text(errorText).font(.caption).foregroundStyle(.secondary) + if selectedChatID == nil { + Button("Refresh") { loadRevision += 1 } + } + } + } + .padding(16) + .frame(width: 330) + .task(id: loadRevision) { + isLoading = true + errorText = nil + do { + let result = try await onLoad() + guard !Task.isCancelled else { return } + transcripts = Array(result.prefix(20)) + } catch { + guard !Task.isCancelled else { return } + errorText = "Couldn’t load earlier transcripts." + } + isLoading = false + } + } +} diff --git a/apple/InlineMac/Features/Grid/GridTranscriptionSidebarIndicator.swift b/apple/InlineMac/Features/Grid/GridTranscriptionSidebarIndicator.swift new file mode 100644 index 000000000..9cbf6ba01 --- /dev/null +++ b/apple/InlineMac/Features/Grid/GridTranscriptionSidebarIndicator.swift @@ -0,0 +1,59 @@ +import InlineKit +import InlineProtocol +import SwiftUI + +/// Persistent disclosure in the existing sidebar footer, including while the +/// participant reads another chat. It projects the shared room snapshot. +struct GridTranscriptionSidebarIndicator: View { + let room: GridRoom + let isBusy: Bool + let onOpenGrid: () -> Void + let onOpenTranscript: () -> Void + let onStop: () async throws -> Void + + @State private var errorText: String? + + var body: some View { + VStack(alignment: .leading, spacing: 7) { + Button(action: onOpenGrid) { + Label(statusTitle, systemImage: "text.bubble") + .frame(maxWidth: .infinity, alignment: .leading) + } + .font(.system(size: 12, weight: .medium)) + .help("Return to Grid") + + HStack(spacing: 10) { + if room.transcriptChatID != nil { + Button("Open transcript", action: onOpenTranscript) + } + Button(room.transcription.state == .gridTranscriptionStarting ? "Cancel" : "Stop") { + errorText = nil + Task { + do { try await onStop() } + catch { errorText = "Couldn’t confirm the stop. Check transcription and try again." } + } + } + .disabled(isBusy || !room.canStopTranscription) + .accessibilityLabel("Stop transcription") + } + .font(.system(size: 11)) + + if let errorText { + Text(errorText).font(.caption).foregroundStyle(.secondary) + } + } + .buttonStyle(.plain) + .padding(.horizontal, 12) + .padding(.vertical, 9) + .frame(maxWidth: .infinity, alignment: .leading) + .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 12)) + } + + private var statusTitle: String { + switch room.transcription.state { + case .gridTranscriptionStarting: "Starting transcription…" + case .gridTranscriptionStopping: "Stopping transcription…" + default: "Transcribing" + } + } +} diff --git a/apple/InlineMac/Features/Grid/GridView.swift b/apple/InlineMac/Features/Grid/GridView.swift index 8a323659b..ce9792887 100644 --- a/apple/InlineMac/Features/Grid/GridView.swift +++ b/apple/InlineMac/Features/Grid/GridView.swift @@ -66,23 +66,44 @@ struct GridView: View { } } .safeAreaInset(edge: .bottom) { - if currentRoom != nil { - GridControlPill( - media: store.media, - onToggleMicrophone: { store.toggleMicrophone(spaceID: spaceID) }, - onLeave: { store.leaveCurrentRoom(spaceID: spaceID) }, - onSelectInput: store.setInputSelection, - onRefreshInputDevices: store.refreshInputDevices, - onSelectOutput: store.setOutputSelection, - onRefreshOutputDevices: store.refreshOutputDevices, - onToggleScreenShare: store.toggleScreenShare, - onSelectScreenCaptureSource: store.startScreenSharing, - onSelectScreenShareQualityProfile: store.setScreenShareQualityProfile, - onRefreshScreenCaptureSources: store.refreshScreenCaptureSources, - onStopScreenShare: store.stopScreenSharing, - onSetOutputVolume: store.setOutputVolume, - onRetryAudio: store.retryAudio - ) + if let currentRoom { + VStack(spacing: 8) { + if currentRoom.transcriptionAvailable || currentRoom.hasTranscription || currentRoom.hasRoomThreadID { + GridTranscriptionControls( + room: currentRoom, + isBusy: store.pendingTranscriptionRequestIDs[currentRoom.id] != nil, + onSetTranscription: { enabled, destination in + try await store.setTranscription(room: currentRoom, enabled: enabled, destination: destination) + }, + onOpenRoomThread: { + let chatID = try await store.openRoomThread(roomID: currentRoom.id) + dependencies?.requestOpenChat(peer: .thread(id: chatID)) + }, + onOpenTranscript: { + guard let chatID = currentRoom.transcriptChatID else { return } + dependencies?.requestOpenChat(peer: .thread(id: chatID)) + }, + onListTranscripts: { try await store.listTranscripts(roomID: currentRoom.id) } + ) + .id(currentRoom.id) + } + GridControlPill( + media: store.media, + onToggleMicrophone: { store.toggleMicrophone(spaceID: spaceID) }, + onLeave: { store.leaveCurrentRoom(spaceID: spaceID) }, + onSelectInput: store.setInputSelection, + onRefreshInputDevices: store.refreshInputDevices, + onSelectOutput: store.setOutputSelection, + onRefreshOutputDevices: store.refreshOutputDevices, + onToggleScreenShare: store.toggleScreenShare, + onSelectScreenCaptureSource: store.startScreenSharing, + onSelectScreenShareQualityProfile: store.setScreenShareQualityProfile, + onRefreshScreenCaptureSources: store.refreshScreenCaptureSources, + onStopScreenShare: store.stopScreenSharing, + onSetOutputVolume: store.setOutputVolume, + onRetryAudio: store.retryAudio + ) + } .padding(.bottom, 18) .transition(.move(edge: .bottom).combined(with: .opacity)) } diff --git a/apple/InlineMac/Features/Sidebar/SidebarView.swift b/apple/InlineMac/Features/Sidebar/SidebarView.swift index 3c6d0abe1..b982fbb2f 100644 --- a/apple/InlineMac/Features/Sidebar/SidebarView.swift +++ b/apple/InlineMac/Features/Sidebar/SidebarView.swift @@ -1462,6 +1462,21 @@ struct SidebarView: View { @ViewBuilder private func bottomBar(scrollProxy: ScrollViewProxy) -> some View { VStack(spacing: 3) { + if let room = gridStore.activeTranscriptionRoom { + GridTranscriptionSidebarIndicator( + room: room, + isBusy: gridStore.pendingTranscriptionRequestIDs[room.id] != nil, + onOpenGrid: { openGrid(spaceID: room.spaceID) }, + onOpenTranscript: { + guard let chatID = room.transcriptChatID else { return } + dependencies?.requestOpenChat(peer: .thread(id: chatID)) + }, + onStop: { try await gridStore.setTranscription(room: room, enabled: false) } + ) + .id(room.id) + .padding(.horizontal, Theme.sidebarItemOuterSpacing + 4) + } + // Temporarily hide the sidebar connection indicator. // if let state = sidebarConnectionState { // SidebarConnectionStatePill(state: state) diff --git a/apple/InlineMac/Views/MessageList/ServiceMessageRowView.swift b/apple/InlineMac/Views/MessageList/ServiceMessageRowView.swift index 2b01972ea..31412ffea 100644 --- a/apple/InlineMac/Views/MessageList/ServiceMessageRowView.swift +++ b/apple/InlineMac/Views/MessageList/ServiceMessageRowView.swift @@ -39,7 +39,7 @@ enum ServiceMessageRowLayout { static func plan(for message: FullMessage, rowSize: NSSize) -> Plan { let textSize = textSize(for: message, rowWidth: rowSize.width) - return plan(textSize: textSize, rowSize: rowSize) + return plan(textSize: textSize, rowSize: rowSize, leading: message.message.isGridTranscript) } static func calculateSize( @@ -48,7 +48,7 @@ enum ServiceMessageRowLayout { ) -> (size: NSSize, textSize: NSSize, layout: MessageSizeCalculator.LayoutPlans) { let textSize = textSize(for: message, rowWidth: width) let rowSize = NSSize(width: width, height: rowHeight(textHeight: textSize.height)) - let plan = plan(textSize: textSize, rowSize: rowSize) + let plan = plan(textSize: textSize, rowSize: rowSize, leading: message.message.isGridTranscript) let wrapperPlan = MessageSizeCalculator.LayoutPlan(size: plan.rowSize, spacing: .zero) let zeroPlan = MessageSizeCalculator.LayoutPlan(size: .zero, spacing: .zero) let layout = MessageSizeCalculator.LayoutPlans( @@ -83,13 +83,14 @@ enum ServiceMessageRowLayout { return (plan.rowSize, plan.textSize, layout) } - private static func plan(textSize: NSSize, rowSize: NSSize) -> Plan { + private static func plan(textSize: NSSize, rowSize: NSSize, leading: Bool) -> Plan { let textWidth = min(maxTextWidth(rowWidth: rowSize.width), max(1, textSize.width)) let textHeight = max(1, textSize.height) let containerWidth = textWidth + horizontalInset * 2 let containerHeight = textHeight + verticalInset * 2 let containerFrame = NSRect( - x: floor((rowSize.width - containerWidth) / 2), + x: leading ? floor((rowSize.width - maxContainerWidth(rowWidth: rowSize.width)) / 2) : + floor((rowSize.width - containerWidth) / 2), y: floor((rowSize.height - containerHeight) / 2), width: ceil(containerWidth), height: ceil(containerHeight) @@ -138,6 +139,7 @@ final class ServiceMessageViewAppKit: NSView, MessageTableRenderableView { private lazy var textView = ServiceMessageTextView { [weak self] link in self?.open(link) } + private var fullMessage: FullMessage private var dependencies: AppDependencies? @@ -166,11 +168,12 @@ final class ServiceMessageViewAppKit: NSView, MessageTableRenderableView { } override func hitTest(_ point: NSPoint) -> NSView? { - let textPoint = convert(point, to: textView) + let localPoint = convert(point, from: superview) + let textPoint = containerView.convert(localPoint, from: self) if let hit = textView.hitTest(textPoint) { return hit } - if containerView.frame.contains(point) { + if containerView.frame.contains(localPoint) { return self } return nil @@ -214,7 +217,10 @@ final class ServiceMessageViewAppKit: NSView, MessageTableRenderableView { } private func updateText() { - textView.setSegments(ServiceMessageRowLayout.segments(for: fullMessage)) + textView.setSegments( + ServiceMessageRowLayout.segments(for: fullMessage), + leading: fullMessage.message.isGridTranscript + ) needsLayout = true needsDisplay = true } @@ -233,15 +239,20 @@ final class ServiceMessageViewAppKit: NSView, MessageTableRenderableView { private func open(_ link: MessageServiceDisplaySegment.Link) { switch link { - case let .user(userId): - dependencies?.requestOpenChat(peer: .user(id: userId)) - case let .thread(chatId): - dependencies?.requestOpenChat(peer: .thread(id: chatId)) + case let .user(userId): + dependencies?.requestOpenChat(peer: .user(id: userId)) + case let .thread(chatId): + dependencies?.requestOpenChat(peer: .thread(id: chatId)) } } private func makeContextMenu() -> NSMenu { let menu = NSMenu() + if fullMessage.message.isGridTranscript { + let copyItem = NSMenuItem(title: "Copy", action: #selector(copyTranscript), keyEquivalent: "") + copyItem.target = self + menu.addItem(copyItem) + } let deleteItem = NSMenuItem(title: "Delete", action: #selector(deleteMessage), keyEquivalent: "delete") deleteItem.target = self deleteItem.image = NSImage(systemSymbolName: "trash", accessibilityDescription: "Delete") @@ -249,6 +260,11 @@ final class ServiceMessageViewAppKit: NSView, MessageTableRenderableView { return menu } + @objc private func copyTranscript() { + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(fullMessage.message.text ?? "", forType: .string) + } + @objc private func deleteMessage() { let message = fullMessage.message Task(priority: .userInitiated) { @MainActor in @@ -262,10 +278,12 @@ final class ServiceMessageViewAppKit: NSView, MessageTableRenderableView { fileprivate static func textColor(for tone: MessageServiceDisplaySegment.Tone) -> NSColor { switch tone { - case .secondary: - return .secondaryLabelColor - case .tertiary: - return .tertiaryLabelColor + case .primary: + .labelColor + case .secondary: + .secondaryLabelColor + case .tertiary: + .tertiaryLabelColor } } @@ -284,7 +302,9 @@ private final class ServiceMessageTextView: NSView { private let onOpen: (MessageServiceDisplaySegment.Link) -> Void private var linkRuns: [LinkRun] = [] - override var isFlipped: Bool { true } + override var isFlipped: Bool { + true + } init(onOpen: @escaping (MessageServiceDisplaySegment.Link) -> Void) { self.onOpen = onOpen @@ -304,13 +324,16 @@ private final class ServiceMessageTextView: NSView { fatalError("init(coder:) has not been implemented") } - func setSegments(_ segments: [MessageServiceDisplaySegment]) { + func setSegments(_ segments: [MessageServiceDisplaySegment], leading: Bool) { let text = NSMutableAttributedString() var runs: [LinkRun] = [] for segment in segments where segment.text.isEmpty == false { let start = text.length - text.append(NSAttributedString(string: segment.text, attributes: Self.attributes(for: segment.tone))) + text.append(NSAttributedString( + string: segment.text, + attributes: Self.attributes(for: segment.tone, leading: leading) + )) if let link = segment.link { let range = NSRange(location: start, length: text.length - start) @@ -332,7 +355,8 @@ private final class ServiceMessageTextView: NSView { } override func hitTest(_ point: NSPoint) -> NSView? { - guard bounds.contains(point), link(at: point) != nil else { return nil } + let localPoint = convert(point, from: superview) + guard bounds.contains(localPoint), link(at: localPoint) != nil else { return nil } return self } @@ -392,9 +416,12 @@ private final class ServiceMessageTextView: NSView { return linkRuns.first { NSLocationInRange(charIndex, $0.range) }?.link } - private static func attributes(for tone: MessageServiceDisplaySegment.Tone) -> [NSAttributedString.Key: Any] { + private static func attributes( + for tone: MessageServiceDisplaySegment.Tone, + leading: Bool + ) -> [NSAttributedString.Key: Any] { let paragraphStyle = NSMutableParagraphStyle() - paragraphStyle.alignment = .center + paragraphStyle.alignment = leading ? .left : .center paragraphStyle.lineBreakMode = .byCharWrapping return [ From 79c3e2fd3a9be5c9ec309ea71dc083e2a9424230 Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Tue, 6 Oct 2026 18:47:22 +0330 Subject: [PATCH 05/14] proto: add fenced grid call transfer contract --- .../Sources/InlineProtocol/core.pb.swift | 585 +++++++++++++++++- crates/protocol/proto/core.proto | 76 ++- packages/protocol/src/core.ts | 532 +++++++++++++++- .../protocol/tests/grid-call-transfer.test.ts | 60 ++ proto/core.proto | 76 ++- 5 files changed, 1290 insertions(+), 39 deletions(-) create mode 100644 packages/protocol/tests/grid-call-transfer.test.ts diff --git a/apple/InlineKit/Sources/InlineProtocol/core.pb.swift b/apple/InlineKit/Sources/InlineProtocol/core.pb.swift index 6ef6b319a..ef79f5a43 100644 --- a/apple/InlineKit/Sources/InlineProtocol/core.pb.swift +++ b/apple/InlineKit/Sources/InlineProtocol/core.pb.swift @@ -571,6 +571,7 @@ public nonisolated enum Method: SwiftProtobuf.Enum, Swift.CaseIterable { case requestBotFilesystem // = 143 case answerBotFilesystem // = 144 case setSpacePhoto // = 145 + case moveGridCallHere // = 146 case openGridThread // = 147 case setGridTranscription // = 148 case listGridTranscripts // = 149 @@ -727,6 +728,7 @@ public nonisolated enum Method: SwiftProtobuf.Enum, Swift.CaseIterable { case 143: self = .requestBotFilesystem case 144: self = .answerBotFilesystem case 145: self = .setSpacePhoto + case 146: self = .moveGridCallHere case 147: self = .openGridThread case 148: self = .setGridTranscription case 149: self = .listGridTranscripts @@ -881,6 +883,7 @@ public nonisolated enum Method: SwiftProtobuf.Enum, Swift.CaseIterable { case .requestBotFilesystem: return 143 case .answerBotFilesystem: return 144 case .setSpacePhoto: return 145 + case .moveGridCallHere: return 146 case .openGridThread: return 147 case .setGridTranscription: return 148 case .listGridTranscripts: return 149 @@ -1035,6 +1038,7 @@ public nonisolated enum Method: SwiftProtobuf.Enum, Swift.CaseIterable { .requestBotFilesystem, .answerBotFilesystem, .setSpacePhoto, + .moveGridCallHere, .openGridThread, .setGridTranscription, .listGridTranscripts, @@ -8424,6 +8428,14 @@ public nonisolated struct RpcCall: Sendable { set {input = .setSpacePhoto(newValue)} } + public var moveGridCallHere: MoveGridCallHereInput { + get { + if case .moveGridCallHere(let v)? = input {return v} + return MoveGridCallHereInput() + } + set {input = .moveGridCallHere(newValue)} + } + public var openGridThread: OpenGridThreadInput { get { if case .openGridThread(let v)? = input {return v} @@ -8595,6 +8607,7 @@ public nonisolated struct RpcCall: Sendable { case requestBotFilesystem(RequestBotFilesystemInput) case answerBotFilesystem(AnswerBotFilesystemInput) case setSpacePhoto(SetSpacePhotoInput) + case moveGridCallHere(MoveGridCallHereInput) case openGridThread(OpenGridThreadInput) case setGridTranscription(SetGridTranscriptionInput) case listGridTranscripts(ListGridTranscriptsInput) @@ -9771,6 +9784,14 @@ public nonisolated struct RpcResult: @unchecked Sendable { set {_uniqueStorage()._result = .setSpacePhoto(newValue)} } + public var moveGridCallHere: MoveGridCallHereResult { + get { + if case .moveGridCallHere(let v)? = _storage._result {return v} + return MoveGridCallHereResult() + } + set {_uniqueStorage()._result = .moveGridCallHere(newValue)} + } + public var openGridThread: OpenGridThreadResult { get { if case .openGridThread(let v)? = _storage._result {return v} @@ -9942,6 +9963,7 @@ public nonisolated struct RpcResult: @unchecked Sendable { case requestBotFilesystem(RequestBotFilesystemResult) case answerBotFilesystem(AnswerBotFilesystemResult) case setSpacePhoto(SetSpacePhotoResult) + case moveGridCallHere(MoveGridCallHereResult) case openGridThread(OpenGridThreadResult) case setGridTranscription(SetGridTranscriptionResult) case listGridTranscripts(ListGridTranscriptsResult) @@ -10720,6 +10742,10 @@ public nonisolated struct Grid: Sendable { /// Clients must not replace a newer snapshot with a lower revision. public var revision: Int64 = 0 + /// Effective server capability, including ownership fence enforcement. + /// New clients must not admit media through an older/disabled server. + public var callTransferEnabled: Bool = false + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -10727,6 +10753,32 @@ public nonisolated struct Grid: Sendable { fileprivate var _currentRoomID: Int64? = nil } +/// Personalized control ownership, disclosed only to the authenticated user. +/// call_id stays stable across an explicit device transfer; membership_id is +/// replaced on each transfer/resume and fences commands and media credentials. +public nonisolated struct GridCurrentCall: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var callID: String = String() + + public var spaceID: Int64 = 0 + + public var roomID: Int64 = 0 + + public var membershipID: String = String() + + public var ownedByCurrentSession: Bool = false + + /// Coarse existing session client kind (ios, macos, etc.), not a device ID. + public var ownerClientType: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + /// Short-lived, session-targeted LiveKit credentials. Never persist or include /// this message in repairable Update buckets. public nonisolated struct GridConnectionCredentials: Sendable { @@ -10751,6 +10803,10 @@ public nonisolated struct GridConnectionCredentials: Sendable { public var expiresAt: Int64 = 0 + public var callID: String = String() + + public var membershipID: String = String() + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -10765,9 +10821,22 @@ public nonisolated struct GetGridInput: Sendable { public var spaceID: Int64 = 0 + /// Explicit renewal by the admitted owner. Omit for read-only browsing when + /// call-transfer enforcement is active. A stale fence never renews presence. + public var expectedMembershipID: String { + get {_expectedMembershipID ?? String()} + set {_expectedMembershipID = newValue} + } + /// Returns true if `expectedMembershipID` has been explicitly set. + public var hasExpectedMembershipID: Bool {self._expectedMembershipID != nil} + /// Clears the value of `expectedMembershipID`. Subsequent reads from it will return its default value. + public mutating func clearExpectedMembershipID() {self._expectedMembershipID = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _expectedMembershipID: String? = nil } public nonisolated struct GetGridResult: Sendable { @@ -10784,11 +10853,21 @@ public nonisolated struct GetGridResult: Sendable { /// Clears the value of `grid`. Subsequent reads from it will return its default value. public mutating func clearGrid() {self._grid = nil} + public var currentCall: GridCurrentCall { + get {_currentCall ?? GridCurrentCall()} + set {_currentCall = newValue} + } + /// Returns true if `currentCall` has been explicitly set. + public var hasCurrentCall: Bool {self._currentCall != nil} + /// Clears the value of `currentCall`. Subsequent reads from it will return its default value. + public mutating func clearCurrentCall() {self._currentCall = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} fileprivate var _grid: Grid? = nil + fileprivate var _currentCall: GridCurrentCall? = nil } /// Home-level discovery for every Grid-enabled Space the current user can see. @@ -10828,9 +10907,22 @@ public nonisolated struct GetGridHomeResult: Sendable { public var spaces: [GridHomeSpace] = [] + public var currentCall: GridCurrentCall { + get {_currentCall ?? GridCurrentCall()} + set {_currentCall = newValue} + } + /// Returns true if `currentCall` has been explicitly set. + public var hasCurrentCall: Bool {self._currentCall != nil} + /// Clears the value of `currentCall`. Subsequent reads from it will return its default value. + public mutating func clearCurrentCall() {self._currentCall = nil} + + public var callTransferEnabled: Bool = false + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _currentCall: GridCurrentCall? = nil } public nonisolated struct CreateGridRoomInput: Sendable { @@ -10849,11 +10941,23 @@ public nonisolated struct CreateGridRoomInput: Sendable { /// Clears the value of `microphoneEnabled`. Subsequent reads from it will return its default value. public mutating func clearMicrophoneEnabled() {self._microphoneEnabled = nil} + /// Required when call_transfer_enabled: empty expects no active presence; + /// nonempty must match the current session's live membership exactly. + public var expectedMembershipID: String { + get {_expectedMembershipID ?? String()} + set {_expectedMembershipID = newValue} + } + /// Returns true if `expectedMembershipID` has been explicitly set. + public var hasExpectedMembershipID: Bool {self._expectedMembershipID != nil} + /// Clears the value of `expectedMembershipID`. Subsequent reads from it will return its default value. + public mutating func clearExpectedMembershipID() {self._expectedMembershipID = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} fileprivate var _microphoneEnabled: Bool? = nil + fileprivate var _expectedMembershipID: String? = nil } public nonisolated struct CreateGridRoomResult: Sendable { @@ -10872,11 +10976,23 @@ public nonisolated struct CreateGridRoomResult: Sendable { /// Clears the value of `connection`. Subsequent reads from it will return its default value. public mutating func clearConnection() {self._connection = nil} + /// Successful claims report the projection captured at commit. A newer + /// accepted Grid/self state wins; this field alone cannot admit media. + public var currentCall: GridCurrentCall { + get {_currentCall ?? GridCurrentCall()} + set {_currentCall = newValue} + } + /// Returns true if `currentCall` has been explicitly set. + public var hasCurrentCall: Bool {self._currentCall != nil} + /// Clears the value of `currentCall`. Subsequent reads from it will return its default value. + public mutating func clearCurrentCall() {self._currentCall = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} fileprivate var _connection: GridConnectionCredentials? = nil + fileprivate var _currentCall: GridCurrentCall? = nil } public nonisolated struct JoinGridRoomInput: Sendable { @@ -10895,11 +11011,21 @@ public nonisolated struct JoinGridRoomInput: Sendable { /// Clears the value of `microphoneEnabled`. Subsequent reads from it will return its default value. public mutating func clearMicrophoneEnabled() {self._microphoneEnabled = nil} + public var expectedMembershipID: String { + get {_expectedMembershipID ?? String()} + set {_expectedMembershipID = newValue} + } + /// Returns true if `expectedMembershipID` has been explicitly set. + public var hasExpectedMembershipID: Bool {self._expectedMembershipID != nil} + /// Clears the value of `expectedMembershipID`. Subsequent reads from it will return its default value. + public mutating func clearExpectedMembershipID() {self._expectedMembershipID = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} fileprivate var _microphoneEnabled: Bool? = nil + fileprivate var _expectedMembershipID: String? = nil } public nonisolated struct JoinGridRoomResult: Sendable { @@ -10918,11 +11044,21 @@ public nonisolated struct JoinGridRoomResult: Sendable { /// Clears the value of `connection`. Subsequent reads from it will return its default value. public mutating func clearConnection() {self._connection = nil} + public var currentCall: GridCurrentCall { + get {_currentCall ?? GridCurrentCall()} + set {_currentCall = newValue} + } + /// Returns true if `currentCall` has been explicitly set. + public var hasCurrentCall: Bool {self._currentCall != nil} + /// Clears the value of `currentCall`. Subsequent reads from it will return its default value. + public mutating func clearCurrentCall() {self._currentCall = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} fileprivate var _connection: GridConnectionCredentials? = nil + fileprivate var _currentCall: GridCurrentCall? = nil } public nonisolated struct LeaveGridRoomInput: Sendable { @@ -10932,9 +11068,20 @@ public nonisolated struct LeaveGridRoomInput: Sendable { public var expectedRoomID: Int64 = 0 + public var expectedMembershipID: String { + get {_expectedMembershipID ?? String()} + set {_expectedMembershipID = newValue} + } + /// Returns true if `expectedMembershipID` has been explicitly set. + public var hasExpectedMembershipID: Bool {self._expectedMembershipID != nil} + /// Clears the value of `expectedMembershipID`. Subsequent reads from it will return its default value. + public mutating func clearExpectedMembershipID() {self._expectedMembershipID = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _expectedMembershipID: String? = nil } public nonisolated struct LeaveGridRoomResult: Sendable { @@ -10944,9 +11091,79 @@ public nonisolated struct LeaveGridRoomResult: Sendable { public var grids: [Grid] = [] + /// Display/repair only; leaving never grants a replacement admission. + public var currentCall: GridCurrentCall { + get {_currentCall ?? GridCurrentCall()} + set {_currentCall = newValue} + } + /// Returns true if `currentCall` has been explicitly set. + public var hasCurrentCall: Bool {self._currentCall != nil} + /// Clears the value of `currentCall`. Subsequent reads from it will return its default value. + public mutating func clearCurrentCall() {self._currentCall = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _currentCall: GridCurrentCall? = nil +} + +/// Explicitly move this user's existing room participation to the authenticated +/// calling session (also used for a same-login cold-process Resume). +public nonisolated struct MoveGridCallHereInput: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var callID: String = String() + + public var expectedMembershipID: String = String() + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} +} + +public nonisolated struct MoveGridCallHereResult: @unchecked Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + public var grids: [Grid] { + get {_storage._grids} + set {_uniqueStorage()._grids = newValue} + } + + public var connection: GridConnectionCredentials { + get {_storage._connection ?? GridConnectionCredentials()} + set {_uniqueStorage()._connection = newValue} + } + /// Returns true if `connection` has been explicitly set. + public var hasConnection: Bool {_storage._connection != nil} + /// Clears the value of `connection`. Subsequent reads from it will return its default value. + public mutating func clearConnection() {_uniqueStorage()._connection = nil} + + /// Captured at the claim's commit when moved=true; display/repair otherwise. + public var currentCall: GridCurrentCall { + get {_storage._currentCall ?? GridCurrentCall()} + set {_uniqueStorage()._currentCall = newValue} + } + /// Returns true if `currentCall` has been explicitly set. + public var hasCurrentCall: Bool {_storage._currentCall != nil} + /// Clears the value of `currentCall`. Subsequent reads from it will return its default value. + public mutating func clearCurrentCall() {_uniqueStorage()._currentCall = nil} + + /// False means the expected call/membership changed; no claim was performed. + public var moved: Bool { + get {_storage._moved} + set {_uniqueStorage()._moved = newValue} + } + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _storage = _StorageClass.defaultInstance } public nonisolated struct SetGridRoomTitleInput: Sendable { @@ -11062,9 +11279,20 @@ public nonisolated struct PrepareGridConnectionInput: Sendable { public var generation: Int32 = 0 + public var expectedMembershipID: String { + get {_expectedMembershipID ?? String()} + set {_expectedMembershipID = newValue} + } + /// Returns true if `expectedMembershipID` has been explicitly set. + public var hasExpectedMembershipID: Bool {self._expectedMembershipID != nil} + /// Clears the value of `expectedMembershipID`. Subsequent reads from it will return its default value. + public mutating func clearExpectedMembershipID() {self._expectedMembershipID = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _expectedMembershipID: String? = nil } public nonisolated struct PrepareGridConnectionResult: Sendable { @@ -11099,9 +11327,20 @@ public nonisolated struct SetGridAvatarMicrophoneEnabledInput: Sendable { public var enabled: Bool = false + public var expectedMembershipID: String { + get {_expectedMembershipID ?? String()} + set {_expectedMembershipID = newValue} + } + /// Returns true if `expectedMembershipID` has been explicitly set. + public var hasExpectedMembershipID: Bool {self._expectedMembershipID != nil} + /// Clears the value of `expectedMembershipID`. Subsequent reads from it will return its default value. + public mutating func clearExpectedMembershipID() {self._expectedMembershipID = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} + + fileprivate var _expectedMembershipID: String? = nil } public nonisolated struct SetGridAvatarMicrophoneEnabledResult: Sendable { @@ -22586,7 +22825,7 @@ nonisolated extension AgentSessionMessageSyncState: SwiftProtobuf._ProtoNameProv } nonisolated extension Method: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNSPECIFIED\0\u{1}GET_ME\0\u{1}SEND_MESSAGE\0\u{1}GET_PEER_PHOTO\0\u{1}DELETE_MESSAGES\0\u{1}GET_CHAT_HISTORY\0\u{1}ADD_REACTION\0\u{1}DELETE_REACTION\0\u{1}EDIT_MESSAGE\0\u{1}CREATE_CHAT\0\u{1}GET_SPACE_MEMBERS\0\u{1}DELETE_CHAT\0\u{1}INVITE_TO_SPACE\0\u{1}GET_CHAT_PARTICIPANTS\0\u{1}ADD_CHAT_PARTICIPANT\0\u{1}REMOVE_CHAT_PARTICIPANT\0\u{1}TRANSLATE_MESSAGES\0\u{1}GET_CHATS\0\u{1}UPDATE_USER_SETTINGS\0\u{1}GET_USER_SETTINGS\0\u{1}SEND_COMPOSE_ACTION\0\u{1}CREATE_BOT\0\u{1}DELETE_MEMBER\0\u{1}MARK_AS_UNREAD\0\u{1}GET_UPDATES_STATE\0\u{1}GET_CHAT\0\u{1}GET_UPDATES\0\u{1}UPDATE_MEMBER_ACCESS\0\u{1}SEARCH_MESSAGES\0\u{1}FORWARD_MESSAGES\0\u{1}UPDATE_CHAT_VISIBILITY\0\u{1}PIN_MESSAGE\0\u{1}UPDATE_CHAT_INFO\0\u{1}LIST_BOTS\0\u{1}REVEAL_BOT_TOKEN\0\u{1}MOVE_THREAD\0\u{1}ROTATE_BOT_TOKEN\0\u{1}UPDATE_BOT_PROFILE\0\u{1}GET_MESSAGES\0\u{1}UPDATE_DIALOG_NOTIFICATION_SETTINGS\0\u{1}READ_MESSAGES\0\u{1}REGISTER_DEVICE\0\u{1}CREATE_SUBTHREAD\0\u{1}GET_BOT_COMMANDS\0\u{1}SET_BOT_COMMANDS\0\u{1}GET_PEER_BOT_COMMANDS\0\u{1}SHOW_IN_CHAT_LIST\0\u{1}RESERVE_CHAT_IDS\0\u{1}INVOKE_MESSAGE_ACTION\0\u{1}ANSWER_MESSAGE_ACTION\0\u{1}REVOKE_SESSION\0\u{1}UPDATE_DIALOG_OPEN\0\u{1}UPDATE_DIALOG_ORDER\0\u{1}CLEAR_CHAT_HISTORY\0\u{1}DELETE_BOT\0\u{1}DELETE_MESSAGE_ATTACHMENT\0\u{1}SET_BOT_AVATAR\0\u{1}CLEAR_BOT_AVATAR\0\u{1}GET_BOT_PRESENCE\0\u{1}SET_BOT_PRESENCE_STATE\0\u{1}UPDATE_DIALOG_FOLLOW_MODE\0\u{1}GET_SESSIONS\0\u{1}CHECK_USERNAME\0\u{1}CHANGE_USERNAME\0\u{1}UPDATE_PROFILE\0\u{1}GET_SPACE_URL_PREVIEW_EXCLUSIONS\0\u{1}ADD_SPACE_URL_PREVIEW_EXCLUSION\0\u{1}REMOVE_SPACE_URL_PREVIEW_EXCLUSION\0\u{1}GET_USER_GROUPS\0\u{1}CREATE_USER_GROUP\0\u{1}UPDATE_USER_GROUP\0\u{1}DELETE_USER_GROUP\0\u{1}GET_SPACE_SETTINGS\0\u{1}TOGGLE_SPACE_GRID\0\u{1}GET_THREAD_REFERENCES\0\u{1}GET_THREAD_SUBTHREADS\0\u{1}GET_PEER_BOTS\0\u{1}GET_MY_BOT_CAPABILITIES\0\u{1}SET_MY_BOT_CAPABILITIES\0\u{1}REQUEST_BOT_CHAT_SETTINGS\0\u{1}INVOKE_BOT_CHAT_SETTINGS_ITEM\0\u{1}ANSWER_BOT_CHAT_SETTINGS\0\u{2}\u{2}GET_GRID\0\u{1}CREATE_GRID_ROOM\0\u{1}JOIN_GRID_ROOM\0\u{1}LEAVE_GRID_ROOM\0\u{1}SET_GRID_ROOM_TITLE\0\u{1}SET_GRID_ROOM_LOCKED\0\u{1}DELETE_GRID_ROOM\0\u{1}PREPARE_GRID_CONNECTION\0\u{1}SET_GRID_AVATAR_MICROPHONE_ENABLED\0\u{1}GET_GRID_HOME\0\u{1}CREATE_CLI_SESSION\0\u{1}SET_PROFILE_PHOTO\0\u{1}GET_EXTERNAL_PROFILE_PHOTO\0\u{1}GET_CHAT_TRANSCRIPT\0\u{1}SEARCH_EXTERNAL_RESOURCES\0\u{1}JOIN_PUBLIC_SPACE\0\u{1}COLLAPSE_HISTORY\0\u{1}LIST_CONNECTORS\0\u{1}PREPARE_CONNECTOR_OAUTH\0\u{1}DISCONNECT_CONNECTOR\0\u{1}SEARCH_USERS\0\u{1}INVITE_TO_INLINE\0\u{1}RESOLVE_URL_PREVIEW\0\u{1}CREATE_BOT_AGENT\0\u{1}GET_BOT_AGENT\0\u{1}LIST_BOT_AGENTS\0\u{1}CREATE_SPACE\0\u{1}DELETE_SPACE\0\u{1}LEAVE_SPACE\0\u{1}GET_CONNECTOR_CONFIG\0\u{1}SET_CONNECTOR_CONFIG\0\u{1}CREATE_EXTERNAL_TASK\0\u{1}UNREGISTER_DEVICE\0\u{1}LOG_OUT\0\u{1}CREATE_UPLOAD\0\u{1}SAVE_UPLOAD_PART\0\u{1}GET_UPLOAD_STATE\0\u{1}FINISH_UPLOAD\0\u{1}CANCEL_UPLOAD\0\u{1}UPDATE_SESSION\0\u{1}UPDATE_DIALOG_ARCHIVED\0\u{1}CREATE_DIALOG_FOLDER\0\u{1}UPDATE_DIALOG_FOLDER\0\u{1}DELETE_DIALOG_FOLDER\0\u{1}GET_SPACE\0\u{1}CONNECT_AGENT_SESSION\0\u{1}SYNC_AGENT_SESSION_MESSAGES\0\u{1}GET_AGENT_SESSION\0\u{1}UPDATE_BOT_AGENT\0\u{1}DELETE_BOT_AGENT\0\u{1}JOIN_SPACE_BY_INVITE_TOKEN\0\u{1}GET_SPACE_INVITE_LINK\0\u{1}SET_SPACE_INVITE_LINK_ENABLED\0\u{1}GET_FILE_PART\0\u{1}ACKNOWLEDGE_MESSAGES\0\u{1}GET_USERS\0\u{1}GET_BOT_SKILLS\0\u{1}GET_BOT_CONFIGURATION_CATALOG\0\u{1}UPDATE_DIALOG_TRANSLATION\0\u{1}TRANSCRIBE_VOICE_DRAFT\0\u{1}REQUEST_BOT_FILESYSTEM\0\u{1}ANSWER_BOT_FILESYSTEM\0\u{1}SET_SPACE_PHOTO\0\u{2}\u{2}OPEN_GRID_THREAD\0\u{1}SET_GRID_TRANSCRIPTION\0\u{1}LIST_GRID_TRANSCRIPTS\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{2}\0UNSPECIFIED\0\u{1}GET_ME\0\u{1}SEND_MESSAGE\0\u{1}GET_PEER_PHOTO\0\u{1}DELETE_MESSAGES\0\u{1}GET_CHAT_HISTORY\0\u{1}ADD_REACTION\0\u{1}DELETE_REACTION\0\u{1}EDIT_MESSAGE\0\u{1}CREATE_CHAT\0\u{1}GET_SPACE_MEMBERS\0\u{1}DELETE_CHAT\0\u{1}INVITE_TO_SPACE\0\u{1}GET_CHAT_PARTICIPANTS\0\u{1}ADD_CHAT_PARTICIPANT\0\u{1}REMOVE_CHAT_PARTICIPANT\0\u{1}TRANSLATE_MESSAGES\0\u{1}GET_CHATS\0\u{1}UPDATE_USER_SETTINGS\0\u{1}GET_USER_SETTINGS\0\u{1}SEND_COMPOSE_ACTION\0\u{1}CREATE_BOT\0\u{1}DELETE_MEMBER\0\u{1}MARK_AS_UNREAD\0\u{1}GET_UPDATES_STATE\0\u{1}GET_CHAT\0\u{1}GET_UPDATES\0\u{1}UPDATE_MEMBER_ACCESS\0\u{1}SEARCH_MESSAGES\0\u{1}FORWARD_MESSAGES\0\u{1}UPDATE_CHAT_VISIBILITY\0\u{1}PIN_MESSAGE\0\u{1}UPDATE_CHAT_INFO\0\u{1}LIST_BOTS\0\u{1}REVEAL_BOT_TOKEN\0\u{1}MOVE_THREAD\0\u{1}ROTATE_BOT_TOKEN\0\u{1}UPDATE_BOT_PROFILE\0\u{1}GET_MESSAGES\0\u{1}UPDATE_DIALOG_NOTIFICATION_SETTINGS\0\u{1}READ_MESSAGES\0\u{1}REGISTER_DEVICE\0\u{1}CREATE_SUBTHREAD\0\u{1}GET_BOT_COMMANDS\0\u{1}SET_BOT_COMMANDS\0\u{1}GET_PEER_BOT_COMMANDS\0\u{1}SHOW_IN_CHAT_LIST\0\u{1}RESERVE_CHAT_IDS\0\u{1}INVOKE_MESSAGE_ACTION\0\u{1}ANSWER_MESSAGE_ACTION\0\u{1}REVOKE_SESSION\0\u{1}UPDATE_DIALOG_OPEN\0\u{1}UPDATE_DIALOG_ORDER\0\u{1}CLEAR_CHAT_HISTORY\0\u{1}DELETE_BOT\0\u{1}DELETE_MESSAGE_ATTACHMENT\0\u{1}SET_BOT_AVATAR\0\u{1}CLEAR_BOT_AVATAR\0\u{1}GET_BOT_PRESENCE\0\u{1}SET_BOT_PRESENCE_STATE\0\u{1}UPDATE_DIALOG_FOLLOW_MODE\0\u{1}GET_SESSIONS\0\u{1}CHECK_USERNAME\0\u{1}CHANGE_USERNAME\0\u{1}UPDATE_PROFILE\0\u{1}GET_SPACE_URL_PREVIEW_EXCLUSIONS\0\u{1}ADD_SPACE_URL_PREVIEW_EXCLUSION\0\u{1}REMOVE_SPACE_URL_PREVIEW_EXCLUSION\0\u{1}GET_USER_GROUPS\0\u{1}CREATE_USER_GROUP\0\u{1}UPDATE_USER_GROUP\0\u{1}DELETE_USER_GROUP\0\u{1}GET_SPACE_SETTINGS\0\u{1}TOGGLE_SPACE_GRID\0\u{1}GET_THREAD_REFERENCES\0\u{1}GET_THREAD_SUBTHREADS\0\u{1}GET_PEER_BOTS\0\u{1}GET_MY_BOT_CAPABILITIES\0\u{1}SET_MY_BOT_CAPABILITIES\0\u{1}REQUEST_BOT_CHAT_SETTINGS\0\u{1}INVOKE_BOT_CHAT_SETTINGS_ITEM\0\u{1}ANSWER_BOT_CHAT_SETTINGS\0\u{2}\u{2}GET_GRID\0\u{1}CREATE_GRID_ROOM\0\u{1}JOIN_GRID_ROOM\0\u{1}LEAVE_GRID_ROOM\0\u{1}SET_GRID_ROOM_TITLE\0\u{1}SET_GRID_ROOM_LOCKED\0\u{1}DELETE_GRID_ROOM\0\u{1}PREPARE_GRID_CONNECTION\0\u{1}SET_GRID_AVATAR_MICROPHONE_ENABLED\0\u{1}GET_GRID_HOME\0\u{1}CREATE_CLI_SESSION\0\u{1}SET_PROFILE_PHOTO\0\u{1}GET_EXTERNAL_PROFILE_PHOTO\0\u{1}GET_CHAT_TRANSCRIPT\0\u{1}SEARCH_EXTERNAL_RESOURCES\0\u{1}JOIN_PUBLIC_SPACE\0\u{1}COLLAPSE_HISTORY\0\u{1}LIST_CONNECTORS\0\u{1}PREPARE_CONNECTOR_OAUTH\0\u{1}DISCONNECT_CONNECTOR\0\u{1}SEARCH_USERS\0\u{1}INVITE_TO_INLINE\0\u{1}RESOLVE_URL_PREVIEW\0\u{1}CREATE_BOT_AGENT\0\u{1}GET_BOT_AGENT\0\u{1}LIST_BOT_AGENTS\0\u{1}CREATE_SPACE\0\u{1}DELETE_SPACE\0\u{1}LEAVE_SPACE\0\u{1}GET_CONNECTOR_CONFIG\0\u{1}SET_CONNECTOR_CONFIG\0\u{1}CREATE_EXTERNAL_TASK\0\u{1}UNREGISTER_DEVICE\0\u{1}LOG_OUT\0\u{1}CREATE_UPLOAD\0\u{1}SAVE_UPLOAD_PART\0\u{1}GET_UPLOAD_STATE\0\u{1}FINISH_UPLOAD\0\u{1}CANCEL_UPLOAD\0\u{1}UPDATE_SESSION\0\u{1}UPDATE_DIALOG_ARCHIVED\0\u{1}CREATE_DIALOG_FOLDER\0\u{1}UPDATE_DIALOG_FOLDER\0\u{1}DELETE_DIALOG_FOLDER\0\u{1}GET_SPACE\0\u{1}CONNECT_AGENT_SESSION\0\u{1}SYNC_AGENT_SESSION_MESSAGES\0\u{1}GET_AGENT_SESSION\0\u{1}UPDATE_BOT_AGENT\0\u{1}DELETE_BOT_AGENT\0\u{1}JOIN_SPACE_BY_INVITE_TOKEN\0\u{1}GET_SPACE_INVITE_LINK\0\u{1}SET_SPACE_INVITE_LINK_ENABLED\0\u{1}GET_FILE_PART\0\u{1}ACKNOWLEDGE_MESSAGES\0\u{1}GET_USERS\0\u{1}GET_BOT_SKILLS\0\u{1}GET_BOT_CONFIGURATION_CATALOG\0\u{1}UPDATE_DIALOG_TRANSLATION\0\u{1}TRANSCRIBE_VOICE_DRAFT\0\u{1}REQUEST_BOT_FILESYSTEM\0\u{1}ANSWER_BOT_FILESYSTEM\0\u{1}SET_SPACE_PHOTO\0\u{1}MOVE_GRID_CALL_HERE\0\u{1}OPEN_GRID_THREAD\0\u{1}SET_GRID_TRANSCRIPTION\0\u{1}LIST_GRID_TRANSCRIPTS\0") } nonisolated extension GridTranscriptionState: SwiftProtobuf._ProtoNameProviding { @@ -29885,7 +30124,7 @@ nonisolated extension RpcError.Code: SwiftProtobuf._ProtoNameProviding { nonisolated extension RpcCall: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "RpcCall" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}method\0\u{1}getMe\0\u{1}getPeerPhoto\0\u{1}deleteMessages\0\u{1}sendMessage\0\u{1}getChatHistory\0\u{1}addReaction\0\u{1}deleteReaction\0\u{1}editMessage\0\u{1}createChat\0\u{1}getSpaceMembers\0\u{1}deleteChat\0\u{1}inviteToSpace\0\u{1}getChatParticipants\0\u{1}addChatParticipant\0\u{1}removeChatParticipant\0\u{1}translateMessages\0\u{1}getChats\0\u{1}updateUserSettings\0\u{1}getUserSettings\0\u{1}sendComposeAction\0\u{1}createBot\0\u{1}deleteMember\0\u{1}markAsUnread\0\u{1}getUpdatesState\0\u{1}getChat\0\u{1}getUpdates\0\u{1}updateMemberAccess\0\u{1}searchMessages\0\u{1}forwardMessages\0\u{1}updateChatVisibility\0\u{1}pinMessage\0\u{1}updateChatInfo\0\u{1}listBots\0\u{1}revealBotToken\0\u{1}moveThread\0\u{1}rotateBotToken\0\u{1}updateBotProfile\0\u{1}getMessages\0\u{1}updateDialogNotificationSettings\0\u{1}readMessages\0\u{1}registerDevice\0\u{1}createSubthread\0\u{1}getBotCommands\0\u{1}setBotCommands\0\u{1}getPeerBotCommands\0\u{1}showInChatList\0\u{1}reserveChatIds\0\u{1}invokeMessageAction\0\u{1}answerMessageAction\0\u{1}revokeSession\0\u{1}updateDialogOpen\0\u{1}updateDialogOrder\0\u{1}clearChatHistory\0\u{1}deleteBot\0\u{1}deleteMessageAttachment\0\u{1}setBotAvatar\0\u{1}clearBotAvatar\0\u{1}getBotPresence\0\u{1}setBotPresenceState\0\u{1}updateDialogFollowMode\0\u{1}getSessions\0\u{1}checkUsername\0\u{1}changeUsername\0\u{1}updateProfile\0\u{1}getSpaceUrlPreviewExclusions\0\u{1}addSpaceUrlPreviewExclusion\0\u{1}removeSpaceUrlPreviewExclusion\0\u{1}getUserGroups\0\u{1}createUserGroup\0\u{1}updateUserGroup\0\u{1}deleteUserGroup\0\u{1}getSpaceSettings\0\u{1}toggleSpaceGrid\0\u{1}getThreadReferences\0\u{1}getThreadSubthreads\0\u{1}getPeerBots\0\u{1}getMyBotCapabilities\0\u{1}setMyBotCapabilities\0\u{1}requestBotChatSettings\0\u{1}invokeBotChatSettingsItem\0\u{1}answerBotChatSettings\0\u{2}\u{2}getGrid\0\u{1}createGridRoom\0\u{1}joinGridRoom\0\u{1}leaveGridRoom\0\u{1}setGridRoomTitle\0\u{1}setGridRoomLocked\0\u{1}deleteGridRoom\0\u{1}prepareGridConnection\0\u{1}setGridAvatarMicrophoneEnabled\0\u{1}getGridHome\0\u{1}createCliSession\0\u{1}setProfilePhoto\0\u{1}getExternalProfilePhoto\0\u{1}getChatTranscript\0\u{1}searchExternalResources\0\u{1}joinPublicSpace\0\u{1}collapseHistory\0\u{1}listConnectors\0\u{1}prepareConnectorOAuth\0\u{1}disconnectConnector\0\u{1}searchUsers\0\u{1}inviteToInline\0\u{1}resolveUrlPreview\0\u{1}createBotAgent\0\u{1}getBotAgent\0\u{1}listBotAgents\0\u{1}createSpace\0\u{1}deleteSpace\0\u{1}leaveSpace\0\u{1}getConnectorConfig\0\u{1}setConnectorConfig\0\u{1}createExternalTask\0\u{1}unregisterDevice\0\u{1}logOut\0\u{1}createUpload\0\u{1}saveUploadPart\0\u{1}getUploadState\0\u{1}finishUpload\0\u{1}cancelUpload\0\u{1}updateSession\0\u{1}updateDialogArchived\0\u{1}createDialogFolder\0\u{1}updateDialogFolder\0\u{1}deleteDialogFolder\0\u{1}getSpace\0\u{1}connectAgentSession\0\u{1}syncAgentSessionMessages\0\u{1}getAgentSession\0\u{1}updateBotAgent\0\u{1}deleteBotAgent\0\u{1}joinSpaceByInviteToken\0\u{1}getSpaceInviteLink\0\u{1}setSpaceInviteLinkEnabled\0\u{1}getFilePart\0\u{1}acknowledgeMessages\0\u{1}getUsers\0\u{1}getBotSkills\0\u{1}getBotConfigurationCatalog\0\u{1}updateDialogTranslation\0\u{1}transcribeVoiceDraft\0\u{1}requestBotFilesystem\0\u{1}answerBotFilesystem\0\u{1}setSpacePhoto\0\u{2}\u{2}openGridThread\0\u{1}setGridTranscription\0\u{1}listGridTranscripts\0\u{c}S\u{1}\u{1}") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}method\0\u{1}getMe\0\u{1}getPeerPhoto\0\u{1}deleteMessages\0\u{1}sendMessage\0\u{1}getChatHistory\0\u{1}addReaction\0\u{1}deleteReaction\0\u{1}editMessage\0\u{1}createChat\0\u{1}getSpaceMembers\0\u{1}deleteChat\0\u{1}inviteToSpace\0\u{1}getChatParticipants\0\u{1}addChatParticipant\0\u{1}removeChatParticipant\0\u{1}translateMessages\0\u{1}getChats\0\u{1}updateUserSettings\0\u{1}getUserSettings\0\u{1}sendComposeAction\0\u{1}createBot\0\u{1}deleteMember\0\u{1}markAsUnread\0\u{1}getUpdatesState\0\u{1}getChat\0\u{1}getUpdates\0\u{1}updateMemberAccess\0\u{1}searchMessages\0\u{1}forwardMessages\0\u{1}updateChatVisibility\0\u{1}pinMessage\0\u{1}updateChatInfo\0\u{1}listBots\0\u{1}revealBotToken\0\u{1}moveThread\0\u{1}rotateBotToken\0\u{1}updateBotProfile\0\u{1}getMessages\0\u{1}updateDialogNotificationSettings\0\u{1}readMessages\0\u{1}registerDevice\0\u{1}createSubthread\0\u{1}getBotCommands\0\u{1}setBotCommands\0\u{1}getPeerBotCommands\0\u{1}showInChatList\0\u{1}reserveChatIds\0\u{1}invokeMessageAction\0\u{1}answerMessageAction\0\u{1}revokeSession\0\u{1}updateDialogOpen\0\u{1}updateDialogOrder\0\u{1}clearChatHistory\0\u{1}deleteBot\0\u{1}deleteMessageAttachment\0\u{1}setBotAvatar\0\u{1}clearBotAvatar\0\u{1}getBotPresence\0\u{1}setBotPresenceState\0\u{1}updateDialogFollowMode\0\u{1}getSessions\0\u{1}checkUsername\0\u{1}changeUsername\0\u{1}updateProfile\0\u{1}getSpaceUrlPreviewExclusions\0\u{1}addSpaceUrlPreviewExclusion\0\u{1}removeSpaceUrlPreviewExclusion\0\u{1}getUserGroups\0\u{1}createUserGroup\0\u{1}updateUserGroup\0\u{1}deleteUserGroup\0\u{1}getSpaceSettings\0\u{1}toggleSpaceGrid\0\u{1}getThreadReferences\0\u{1}getThreadSubthreads\0\u{1}getPeerBots\0\u{1}getMyBotCapabilities\0\u{1}setMyBotCapabilities\0\u{1}requestBotChatSettings\0\u{1}invokeBotChatSettingsItem\0\u{1}answerBotChatSettings\0\u{2}\u{2}getGrid\0\u{1}createGridRoom\0\u{1}joinGridRoom\0\u{1}leaveGridRoom\0\u{1}setGridRoomTitle\0\u{1}setGridRoomLocked\0\u{1}deleteGridRoom\0\u{1}prepareGridConnection\0\u{1}setGridAvatarMicrophoneEnabled\0\u{1}getGridHome\0\u{1}createCliSession\0\u{1}setProfilePhoto\0\u{1}getExternalProfilePhoto\0\u{1}getChatTranscript\0\u{1}searchExternalResources\0\u{1}joinPublicSpace\0\u{1}collapseHistory\0\u{1}listConnectors\0\u{1}prepareConnectorOAuth\0\u{1}disconnectConnector\0\u{1}searchUsers\0\u{1}inviteToInline\0\u{1}resolveUrlPreview\0\u{1}createBotAgent\0\u{1}getBotAgent\0\u{1}listBotAgents\0\u{1}createSpace\0\u{1}deleteSpace\0\u{1}leaveSpace\0\u{1}getConnectorConfig\0\u{1}setConnectorConfig\0\u{1}createExternalTask\0\u{1}unregisterDevice\0\u{1}logOut\0\u{1}createUpload\0\u{1}saveUploadPart\0\u{1}getUploadState\0\u{1}finishUpload\0\u{1}cancelUpload\0\u{1}updateSession\0\u{1}updateDialogArchived\0\u{1}createDialogFolder\0\u{1}updateDialogFolder\0\u{1}deleteDialogFolder\0\u{1}getSpace\0\u{1}connectAgentSession\0\u{1}syncAgentSessionMessages\0\u{1}getAgentSession\0\u{1}updateBotAgent\0\u{1}deleteBotAgent\0\u{1}joinSpaceByInviteToken\0\u{1}getSpaceInviteLink\0\u{1}setSpaceInviteLinkEnabled\0\u{1}getFilePart\0\u{1}acknowledgeMessages\0\u{1}getUsers\0\u{1}getBotSkills\0\u{1}getBotConfigurationCatalog\0\u{1}updateDialogTranslation\0\u{1}transcribeVoiceDraft\0\u{1}requestBotFilesystem\0\u{1}answerBotFilesystem\0\u{1}setSpacePhoto\0\u{1}moveGridCallHere\0\u{1}openGridThread\0\u{1}setGridTranscription\0\u{1}listGridTranscripts\0\u{c}S\u{1}\u{1}") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -31766,6 +32005,19 @@ nonisolated extension RpcCall: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl self.input = .setSpacePhoto(v) } }() + case 147: try { + var v: MoveGridCallHereInput? + var hadOneofValue = false + if let current = self.input { + hadOneofValue = true + if case .moveGridCallHere(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + self.input = .moveGridCallHere(v) + } + }() case 148: try { var v: OpenGridThreadInput? var hadOneofValue = false @@ -32395,6 +32647,10 @@ nonisolated extension RpcCall: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl guard case .setSpacePhoto(let v)? = self.input else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 146) }() + case .moveGridCallHere?: try { + guard case .moveGridCallHere(let v)? = self.input else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 147) + }() case .openGridThread?: try { guard case .openGridThread(let v)? = self.input else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 148) @@ -32422,7 +32678,7 @@ nonisolated extension RpcCall: SwiftProtobuf.Message, SwiftProtobuf._MessageImpl nonisolated extension RpcResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "RpcResult" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}req_msg_id\0\u{1}getMe\0\u{1}getPeerPhoto\0\u{1}deleteMessages\0\u{1}sendMessage\0\u{1}getChatHistory\0\u{1}addReaction\0\u{1}deleteReaction\0\u{1}editMessage\0\u{1}createChat\0\u{1}getSpaceMembers\0\u{1}deleteChat\0\u{1}inviteToSpace\0\u{1}getChatParticipants\0\u{1}addChatParticipant\0\u{1}removeChatParticipant\0\u{1}translateMessages\0\u{1}getChats\0\u{1}updateUserSettings\0\u{1}getUserSettings\0\u{1}sendComposeAction\0\u{1}createBot\0\u{1}deleteMember\0\u{1}markAsUnread\0\u{1}getUpdatesState\0\u{1}getChat\0\u{1}getUpdates\0\u{1}updateMemberAccess\0\u{1}searchMessages\0\u{1}forwardMessages\0\u{1}updateChatVisibility\0\u{1}pinMessage\0\u{1}updateChatInfo\0\u{1}listBots\0\u{1}revealBotToken\0\u{1}moveThread\0\u{1}rotateBotToken\0\u{1}updateBotProfile\0\u{1}getMessages\0\u{1}updateDialogNotificationSettings\0\u{1}readMessages\0\u{1}registerDevice\0\u{1}createSubthread\0\u{1}getBotCommands\0\u{1}setBotCommands\0\u{1}getPeerBotCommands\0\u{1}showInChatList\0\u{1}reserveChatIds\0\u{1}invokeMessageAction\0\u{1}answerMessageAction\0\u{1}revokeSession\0\u{1}updateDialogOpen\0\u{1}updateDialogOrder\0\u{1}clearChatHistory\0\u{1}deleteBot\0\u{1}deleteMessageAttachment\0\u{1}setBotAvatar\0\u{1}clearBotAvatar\0\u{1}getBotPresence\0\u{1}setBotPresenceState\0\u{1}updateDialogFollowMode\0\u{1}getSessions\0\u{1}checkUsername\0\u{1}changeUsername\0\u{1}updateProfile\0\u{1}getSpaceUrlPreviewExclusions\0\u{1}addSpaceUrlPreviewExclusion\0\u{1}removeSpaceUrlPreviewExclusion\0\u{1}getUserGroups\0\u{1}createUserGroup\0\u{1}updateUserGroup\0\u{1}deleteUserGroup\0\u{1}getSpaceSettings\0\u{1}toggleSpaceGrid\0\u{1}getThreadReferences\0\u{1}getThreadSubthreads\0\u{1}getPeerBots\0\u{1}getMyBotCapabilities\0\u{1}setMyBotCapabilities\0\u{1}requestBotChatSettings\0\u{1}invokeBotChatSettingsItem\0\u{1}answerBotChatSettings\0\u{2}\u{2}getGrid\0\u{1}createGridRoom\0\u{1}joinGridRoom\0\u{1}leaveGridRoom\0\u{1}setGridRoomTitle\0\u{1}setGridRoomLocked\0\u{1}deleteGridRoom\0\u{1}prepareGridConnection\0\u{1}setGridAvatarMicrophoneEnabled\0\u{1}getGridHome\0\u{1}createCliSession\0\u{1}setProfilePhoto\0\u{1}getExternalProfilePhoto\0\u{1}getChatTranscript\0\u{1}searchExternalResources\0\u{1}joinPublicSpace\0\u{1}collapseHistory\0\u{1}listConnectors\0\u{1}prepareConnectorOAuth\0\u{1}disconnectConnector\0\u{1}searchUsers\0\u{1}inviteToInline\0\u{1}resolveUrlPreview\0\u{1}createBotAgent\0\u{1}getBotAgent\0\u{1}listBotAgents\0\u{1}createSpace\0\u{1}deleteSpace\0\u{1}leaveSpace\0\u{1}getConnectorConfig\0\u{1}setConnectorConfig\0\u{1}createExternalTask\0\u{1}unregisterDevice\0\u{1}logOut\0\u{1}createUpload\0\u{1}saveUploadPart\0\u{1}getUploadState\0\u{1}finishUpload\0\u{1}cancelUpload\0\u{1}updateSession\0\u{1}updateDialogArchived\0\u{1}createDialogFolder\0\u{1}updateDialogFolder\0\u{1}deleteDialogFolder\0\u{1}getSpace\0\u{1}connectAgentSession\0\u{1}syncAgentSessionMessages\0\u{1}getAgentSession\0\u{1}updateBotAgent\0\u{1}deleteBotAgent\0\u{1}joinSpaceByInviteToken\0\u{1}getSpaceInviteLink\0\u{1}setSpaceInviteLinkEnabled\0\u{1}getFilePart\0\u{1}acknowledgeMessages\0\u{1}getUsers\0\u{1}getBotSkills\0\u{1}getBotConfigurationCatalog\0\u{1}updateDialogTranslation\0\u{1}transcribeVoiceDraft\0\u{1}requestBotFilesystem\0\u{1}answerBotFilesystem\0\u{1}setSpacePhoto\0\u{2}\u{2}openGridThread\0\u{1}setGridTranscription\0\u{1}listGridTranscripts\0\u{c}S\u{1}\u{1}") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}req_msg_id\0\u{1}getMe\0\u{1}getPeerPhoto\0\u{1}deleteMessages\0\u{1}sendMessage\0\u{1}getChatHistory\0\u{1}addReaction\0\u{1}deleteReaction\0\u{1}editMessage\0\u{1}createChat\0\u{1}getSpaceMembers\0\u{1}deleteChat\0\u{1}inviteToSpace\0\u{1}getChatParticipants\0\u{1}addChatParticipant\0\u{1}removeChatParticipant\0\u{1}translateMessages\0\u{1}getChats\0\u{1}updateUserSettings\0\u{1}getUserSettings\0\u{1}sendComposeAction\0\u{1}createBot\0\u{1}deleteMember\0\u{1}markAsUnread\0\u{1}getUpdatesState\0\u{1}getChat\0\u{1}getUpdates\0\u{1}updateMemberAccess\0\u{1}searchMessages\0\u{1}forwardMessages\0\u{1}updateChatVisibility\0\u{1}pinMessage\0\u{1}updateChatInfo\0\u{1}listBots\0\u{1}revealBotToken\0\u{1}moveThread\0\u{1}rotateBotToken\0\u{1}updateBotProfile\0\u{1}getMessages\0\u{1}updateDialogNotificationSettings\0\u{1}readMessages\0\u{1}registerDevice\0\u{1}createSubthread\0\u{1}getBotCommands\0\u{1}setBotCommands\0\u{1}getPeerBotCommands\0\u{1}showInChatList\0\u{1}reserveChatIds\0\u{1}invokeMessageAction\0\u{1}answerMessageAction\0\u{1}revokeSession\0\u{1}updateDialogOpen\0\u{1}updateDialogOrder\0\u{1}clearChatHistory\0\u{1}deleteBot\0\u{1}deleteMessageAttachment\0\u{1}setBotAvatar\0\u{1}clearBotAvatar\0\u{1}getBotPresence\0\u{1}setBotPresenceState\0\u{1}updateDialogFollowMode\0\u{1}getSessions\0\u{1}checkUsername\0\u{1}changeUsername\0\u{1}updateProfile\0\u{1}getSpaceUrlPreviewExclusions\0\u{1}addSpaceUrlPreviewExclusion\0\u{1}removeSpaceUrlPreviewExclusion\0\u{1}getUserGroups\0\u{1}createUserGroup\0\u{1}updateUserGroup\0\u{1}deleteUserGroup\0\u{1}getSpaceSettings\0\u{1}toggleSpaceGrid\0\u{1}getThreadReferences\0\u{1}getThreadSubthreads\0\u{1}getPeerBots\0\u{1}getMyBotCapabilities\0\u{1}setMyBotCapabilities\0\u{1}requestBotChatSettings\0\u{1}invokeBotChatSettingsItem\0\u{1}answerBotChatSettings\0\u{2}\u{2}getGrid\0\u{1}createGridRoom\0\u{1}joinGridRoom\0\u{1}leaveGridRoom\0\u{1}setGridRoomTitle\0\u{1}setGridRoomLocked\0\u{1}deleteGridRoom\0\u{1}prepareGridConnection\0\u{1}setGridAvatarMicrophoneEnabled\0\u{1}getGridHome\0\u{1}createCliSession\0\u{1}setProfilePhoto\0\u{1}getExternalProfilePhoto\0\u{1}getChatTranscript\0\u{1}searchExternalResources\0\u{1}joinPublicSpace\0\u{1}collapseHistory\0\u{1}listConnectors\0\u{1}prepareConnectorOAuth\0\u{1}disconnectConnector\0\u{1}searchUsers\0\u{1}inviteToInline\0\u{1}resolveUrlPreview\0\u{1}createBotAgent\0\u{1}getBotAgent\0\u{1}listBotAgents\0\u{1}createSpace\0\u{1}deleteSpace\0\u{1}leaveSpace\0\u{1}getConnectorConfig\0\u{1}setConnectorConfig\0\u{1}createExternalTask\0\u{1}unregisterDevice\0\u{1}logOut\0\u{1}createUpload\0\u{1}saveUploadPart\0\u{1}getUploadState\0\u{1}finishUpload\0\u{1}cancelUpload\0\u{1}updateSession\0\u{1}updateDialogArchived\0\u{1}createDialogFolder\0\u{1}updateDialogFolder\0\u{1}deleteDialogFolder\0\u{1}getSpace\0\u{1}connectAgentSession\0\u{1}syncAgentSessionMessages\0\u{1}getAgentSession\0\u{1}updateBotAgent\0\u{1}deleteBotAgent\0\u{1}joinSpaceByInviteToken\0\u{1}getSpaceInviteLink\0\u{1}setSpaceInviteLinkEnabled\0\u{1}getFilePart\0\u{1}acknowledgeMessages\0\u{1}getUsers\0\u{1}getBotSkills\0\u{1}getBotConfigurationCatalog\0\u{1}updateDialogTranslation\0\u{1}transcribeVoiceDraft\0\u{1}requestBotFilesystem\0\u{1}answerBotFilesystem\0\u{1}setSpacePhoto\0\u{1}moveGridCallHere\0\u{1}openGridThread\0\u{1}setGridTranscription\0\u{1}listGridTranscripts\0\u{c}S\u{1}\u{1}") fileprivate class _StorageClass { var _reqMsgID: UInt64 = 0 @@ -34330,6 +34586,19 @@ nonisolated extension RpcResult: SwiftProtobuf.Message, SwiftProtobuf._MessageIm _storage._result = .setSpacePhoto(v) } }() + case 147: try { + var v: MoveGridCallHereResult? + var hadOneofValue = false + if let current = _storage._result { + hadOneofValue = true + if case .moveGridCallHere(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._result = .moveGridCallHere(v) + } + }() case 148: try { var v: OpenGridThreadResult? var hadOneofValue = false @@ -34961,6 +35230,10 @@ nonisolated extension RpcResult: SwiftProtobuf.Message, SwiftProtobuf._MessageIm guard case .setSpacePhoto(let v)? = _storage._result else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 146) }() + case .moveGridCallHere?: try { + guard case .moveGridCallHere(let v)? = _storage._result else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 147) + }() case .openGridThread?: try { guard case .openGridThread(let v)? = _storage._result else { preconditionFailure() } try visitor.visitSingularMessageField(value: v, fieldNumber: 148) @@ -36270,7 +36543,7 @@ nonisolated extension ListGridTranscriptsResult: SwiftProtobuf.Message, SwiftPro nonisolated extension Grid: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "Grid" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}space_id\0\u{1}enabled\0\u{1}rooms\0\u{3}current_room_id\0\u{1}revision\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}space_id\0\u{1}enabled\0\u{1}rooms\0\u{3}current_room_id\0\u{1}revision\0\u{3}call_transfer_enabled\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36283,6 +36556,7 @@ nonisolated extension Grid: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme case 3: try { try decoder.decodeRepeatedMessageField(value: &self.rooms) }() case 4: try { try decoder.decodeSingularInt64Field(value: &self._currentRoomID) }() case 5: try { try decoder.decodeSingularInt64Field(value: &self.revision) }() + case 6: try { try decoder.decodeSingularBoolField(value: &self.callTransferEnabled) }() default: break } } @@ -36308,6 +36582,9 @@ nonisolated extension Grid: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme if self.revision != 0 { try visitor.visitSingularInt64Field(value: self.revision, fieldNumber: 5) } + if self.callTransferEnabled != false { + try visitor.visitSingularBoolField(value: self.callTransferEnabled, fieldNumber: 6) + } try unknownFields.traverse(visitor: &visitor) } @@ -36317,6 +36594,62 @@ nonisolated extension Grid: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme if lhs.rooms != rhs.rooms {return false} if lhs._currentRoomID != rhs._currentRoomID {return false} if lhs.revision != rhs.revision {return false} + if lhs.callTransferEnabled != rhs.callTransferEnabled {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +nonisolated extension GridCurrentCall: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "GridCurrentCall" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}call_id\0\u{3}space_id\0\u{3}room_id\0\u{3}membership_id\0\u{3}owned_by_current_session\0\u{3}owner_client_type\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.callID) }() + case 2: try { try decoder.decodeSingularInt64Field(value: &self.spaceID) }() + case 3: try { try decoder.decodeSingularInt64Field(value: &self.roomID) }() + case 4: try { try decoder.decodeSingularStringField(value: &self.membershipID) }() + case 5: try { try decoder.decodeSingularBoolField(value: &self.ownedByCurrentSession) }() + case 6: try { try decoder.decodeSingularStringField(value: &self.ownerClientType) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.callID.isEmpty { + try visitor.visitSingularStringField(value: self.callID, fieldNumber: 1) + } + if self.spaceID != 0 { + try visitor.visitSingularInt64Field(value: self.spaceID, fieldNumber: 2) + } + if self.roomID != 0 { + try visitor.visitSingularInt64Field(value: self.roomID, fieldNumber: 3) + } + if !self.membershipID.isEmpty { + try visitor.visitSingularStringField(value: self.membershipID, fieldNumber: 4) + } + if self.ownedByCurrentSession != false { + try visitor.visitSingularBoolField(value: self.ownedByCurrentSession, fieldNumber: 5) + } + if !self.ownerClientType.isEmpty { + try visitor.visitSingularStringField(value: self.ownerClientType, fieldNumber: 6) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: GridCurrentCall, rhs: GridCurrentCall) -> Bool { + if lhs.callID != rhs.callID {return false} + if lhs.spaceID != rhs.spaceID {return false} + if lhs.roomID != rhs.roomID {return false} + if lhs.membershipID != rhs.membershipID {return false} + if lhs.ownedByCurrentSession != rhs.ownedByCurrentSession {return false} + if lhs.ownerClientType != rhs.ownerClientType {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -36324,7 +36657,7 @@ nonisolated extension Grid: SwiftProtobuf.Message, SwiftProtobuf._MessageImpleme nonisolated extension GridConnectionCredentials: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "GridConnectionCredentials" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}connection\0\u{3}server_url\0\u{3}participant_identity\0\u{1}token\0\u{3}expires_at\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}connection\0\u{3}server_url\0\u{3}participant_identity\0\u{1}token\0\u{3}expires_at\0\u{3}call_id\0\u{3}membership_id\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36337,6 +36670,8 @@ nonisolated extension GridConnectionCredentials: SwiftProtobuf.Message, SwiftPro case 3: try { try decoder.decodeSingularStringField(value: &self.participantIdentity) }() case 4: try { try decoder.decodeSingularStringField(value: &self.token) }() case 5: try { try decoder.decodeSingularInt64Field(value: &self.expiresAt) }() + case 6: try { try decoder.decodeSingularStringField(value: &self.callID) }() + case 7: try { try decoder.decodeSingularStringField(value: &self.membershipID) }() default: break } } @@ -36362,6 +36697,12 @@ nonisolated extension GridConnectionCredentials: SwiftProtobuf.Message, SwiftPro if self.expiresAt != 0 { try visitor.visitSingularInt64Field(value: self.expiresAt, fieldNumber: 5) } + if !self.callID.isEmpty { + try visitor.visitSingularStringField(value: self.callID, fieldNumber: 6) + } + if !self.membershipID.isEmpty { + try visitor.visitSingularStringField(value: self.membershipID, fieldNumber: 7) + } try unknownFields.traverse(visitor: &visitor) } @@ -36371,6 +36712,8 @@ nonisolated extension GridConnectionCredentials: SwiftProtobuf.Message, SwiftPro if lhs.participantIdentity != rhs.participantIdentity {return false} if lhs.token != rhs.token {return false} if lhs.expiresAt != rhs.expiresAt {return false} + if lhs.callID != rhs.callID {return false} + if lhs.membershipID != rhs.membershipID {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -36378,7 +36721,7 @@ nonisolated extension GridConnectionCredentials: SwiftProtobuf.Message, SwiftPro nonisolated extension GetGridInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "GetGridInput" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}space_id\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}space_id\0\u{3}expected_membership_id\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36387,20 +36730,29 @@ nonisolated extension GetGridInput: SwiftProtobuf.Message, SwiftProtobuf._Messag // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularInt64Field(value: &self.spaceID) }() + case 2: try { try decoder.decodeSingularStringField(value: &self._expectedMembershipID) }() default: break } } } public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if self.spaceID != 0 { try visitor.visitSingularInt64Field(value: self.spaceID, fieldNumber: 1) } + try { if let v = self._expectedMembershipID { + try visitor.visitSingularStringField(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: GetGridInput, rhs: GetGridInput) -> Bool { if lhs.spaceID != rhs.spaceID {return false} + if lhs._expectedMembershipID != rhs._expectedMembershipID {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -36408,7 +36760,7 @@ nonisolated extension GetGridInput: SwiftProtobuf.Message, SwiftProtobuf._Messag nonisolated extension GetGridResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "GetGridResult" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}grid\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}grid\0\u{3}current_call\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36417,6 +36769,7 @@ nonisolated extension GetGridResult: SwiftProtobuf.Message, SwiftProtobuf._Messa // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularMessageField(value: &self._grid) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._currentCall) }() default: break } } @@ -36430,11 +36783,15 @@ nonisolated extension GetGridResult: SwiftProtobuf.Message, SwiftProtobuf._Messa try { if let v = self._grid { try visitor.visitSingularMessageField(value: v, fieldNumber: 1) } }() + try { if let v = self._currentCall { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: GetGridResult, rhs: GetGridResult) -> Bool { if lhs._grid != rhs._grid {return false} + if lhs._currentCall != rhs._currentCall {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -36506,7 +36863,7 @@ nonisolated extension GetGridHomeInput: SwiftProtobuf.Message, SwiftProtobuf._Me nonisolated extension GetGridHomeResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "GetGridHomeResult" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}spaces\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}spaces\0\u{3}current_call\0\u{3}call_transfer_enabled\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36515,20 +36872,34 @@ nonisolated extension GetGridHomeResult: SwiftProtobuf.Message, SwiftProtobuf._M // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeRepeatedMessageField(value: &self.spaces) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._currentCall) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self.callTransferEnabled) }() default: break } } } public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if !self.spaces.isEmpty { try visitor.visitRepeatedMessageField(value: self.spaces, fieldNumber: 1) } + try { if let v = self._currentCall { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + if self.callTransferEnabled != false { + try visitor.visitSingularBoolField(value: self.callTransferEnabled, fieldNumber: 3) + } try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: GetGridHomeResult, rhs: GetGridHomeResult) -> Bool { if lhs.spaces != rhs.spaces {return false} + if lhs._currentCall != rhs._currentCall {return false} + if lhs.callTransferEnabled != rhs.callTransferEnabled {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -36536,7 +36907,7 @@ nonisolated extension GetGridHomeResult: SwiftProtobuf.Message, SwiftProtobuf._M nonisolated extension CreateGridRoomInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "CreateGridRoomInput" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}space_id\0\u{3}microphone_enabled\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}space_id\0\u{3}microphone_enabled\0\u{3}expected_membership_id\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36546,6 +36917,7 @@ nonisolated extension CreateGridRoomInput: SwiftProtobuf.Message, SwiftProtobuf. switch fieldNumber { case 1: try { try decoder.decodeSingularInt64Field(value: &self.spaceID) }() case 2: try { try decoder.decodeSingularBoolField(value: &self._microphoneEnabled) }() + case 3: try { try decoder.decodeSingularStringField(value: &self._expectedMembershipID) }() default: break } } @@ -36562,12 +36934,16 @@ nonisolated extension CreateGridRoomInput: SwiftProtobuf.Message, SwiftProtobuf. try { if let v = self._microphoneEnabled { try visitor.visitSingularBoolField(value: v, fieldNumber: 2) } }() + try { if let v = self._expectedMembershipID { + try visitor.visitSingularStringField(value: v, fieldNumber: 3) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: CreateGridRoomInput, rhs: CreateGridRoomInput) -> Bool { if lhs.spaceID != rhs.spaceID {return false} if lhs._microphoneEnabled != rhs._microphoneEnabled {return false} + if lhs._expectedMembershipID != rhs._expectedMembershipID {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -36575,7 +36951,7 @@ nonisolated extension CreateGridRoomInput: SwiftProtobuf.Message, SwiftProtobuf. nonisolated extension CreateGridRoomResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "CreateGridRoomResult" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}grids\0\u{1}connection\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}grids\0\u{1}connection\0\u{3}current_call\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36585,6 +36961,7 @@ nonisolated extension CreateGridRoomResult: SwiftProtobuf.Message, SwiftProtobuf switch fieldNumber { case 1: try { try decoder.decodeRepeatedMessageField(value: &self.grids) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._connection) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._currentCall) }() default: break } } @@ -36601,12 +36978,16 @@ nonisolated extension CreateGridRoomResult: SwiftProtobuf.Message, SwiftProtobuf try { if let v = self._connection { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() + try { if let v = self._currentCall { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: CreateGridRoomResult, rhs: CreateGridRoomResult) -> Bool { if lhs.grids != rhs.grids {return false} if lhs._connection != rhs._connection {return false} + if lhs._currentCall != rhs._currentCall {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -36614,7 +36995,7 @@ nonisolated extension CreateGridRoomResult: SwiftProtobuf.Message, SwiftProtobuf nonisolated extension JoinGridRoomInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "JoinGridRoomInput" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}room_id\0\u{3}microphone_enabled\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}room_id\0\u{3}microphone_enabled\0\u{3}expected_membership_id\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36624,6 +37005,7 @@ nonisolated extension JoinGridRoomInput: SwiftProtobuf.Message, SwiftProtobuf._M switch fieldNumber { case 1: try { try decoder.decodeSingularInt64Field(value: &self.roomID) }() case 2: try { try decoder.decodeSingularBoolField(value: &self._microphoneEnabled) }() + case 3: try { try decoder.decodeSingularStringField(value: &self._expectedMembershipID) }() default: break } } @@ -36640,12 +37022,16 @@ nonisolated extension JoinGridRoomInput: SwiftProtobuf.Message, SwiftProtobuf._M try { if let v = self._microphoneEnabled { try visitor.visitSingularBoolField(value: v, fieldNumber: 2) } }() + try { if let v = self._expectedMembershipID { + try visitor.visitSingularStringField(value: v, fieldNumber: 3) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: JoinGridRoomInput, rhs: JoinGridRoomInput) -> Bool { if lhs.roomID != rhs.roomID {return false} if lhs._microphoneEnabled != rhs._microphoneEnabled {return false} + if lhs._expectedMembershipID != rhs._expectedMembershipID {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -36653,7 +37039,7 @@ nonisolated extension JoinGridRoomInput: SwiftProtobuf.Message, SwiftProtobuf._M nonisolated extension JoinGridRoomResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "JoinGridRoomResult" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}grids\0\u{1}connection\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}grids\0\u{1}connection\0\u{3}current_call\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36663,6 +37049,7 @@ nonisolated extension JoinGridRoomResult: SwiftProtobuf.Message, SwiftProtobuf._ switch fieldNumber { case 1: try { try decoder.decodeRepeatedMessageField(value: &self.grids) }() case 2: try { try decoder.decodeSingularMessageField(value: &self._connection) }() + case 3: try { try decoder.decodeSingularMessageField(value: &self._currentCall) }() default: break } } @@ -36679,12 +37066,16 @@ nonisolated extension JoinGridRoomResult: SwiftProtobuf.Message, SwiftProtobuf._ try { if let v = self._connection { try visitor.visitSingularMessageField(value: v, fieldNumber: 2) } }() + try { if let v = self._currentCall { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: JoinGridRoomResult, rhs: JoinGridRoomResult) -> Bool { if lhs.grids != rhs.grids {return false} if lhs._connection != rhs._connection {return false} + if lhs._currentCall != rhs._currentCall {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -36692,7 +37083,7 @@ nonisolated extension JoinGridRoomResult: SwiftProtobuf.Message, SwiftProtobuf._ nonisolated extension LeaveGridRoomInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "LeaveGridRoomInput" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}expected_room_id\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}expected_room_id\0\u{3}expected_membership_id\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36701,20 +37092,29 @@ nonisolated extension LeaveGridRoomInput: SwiftProtobuf.Message, SwiftProtobuf._ // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularInt64Field(value: &self.expectedRoomID) }() + case 2: try { try decoder.decodeSingularStringField(value: &self._expectedMembershipID) }() default: break } } } public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if self.expectedRoomID != 0 { try visitor.visitSingularInt64Field(value: self.expectedRoomID, fieldNumber: 1) } + try { if let v = self._expectedMembershipID { + try visitor.visitSingularStringField(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: LeaveGridRoomInput, rhs: LeaveGridRoomInput) -> Bool { if lhs.expectedRoomID != rhs.expectedRoomID {return false} + if lhs._expectedMembershipID != rhs._expectedMembershipID {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -36722,7 +37122,7 @@ nonisolated extension LeaveGridRoomInput: SwiftProtobuf.Message, SwiftProtobuf._ nonisolated extension LeaveGridRoomResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "LeaveGridRoomResult" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}grids\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}grids\0\u{3}current_call\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36731,20 +37131,155 @@ nonisolated extension LeaveGridRoomResult: SwiftProtobuf.Message, SwiftProtobuf. // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeRepeatedMessageField(value: &self.grids) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._currentCall) }() default: break } } } public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if !self.grids.isEmpty { try visitor.visitRepeatedMessageField(value: self.grids, fieldNumber: 1) } + try { if let v = self._currentCall { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: LeaveGridRoomResult, rhs: LeaveGridRoomResult) -> Bool { if lhs.grids != rhs.grids {return false} + if lhs._currentCall != rhs._currentCall {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +nonisolated extension MoveGridCallHereInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "MoveGridCallHereInput" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}call_id\0\u{3}expected_membership_id\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self.callID) }() + case 2: try { try decoder.decodeSingularStringField(value: &self.expectedMembershipID) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + if !self.callID.isEmpty { + try visitor.visitSingularStringField(value: self.callID, fieldNumber: 1) + } + if !self.expectedMembershipID.isEmpty { + try visitor.visitSingularStringField(value: self.expectedMembershipID, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: MoveGridCallHereInput, rhs: MoveGridCallHereInput) -> Bool { + if lhs.callID != rhs.callID {return false} + if lhs.expectedMembershipID != rhs.expectedMembershipID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +nonisolated extension MoveGridCallHereResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = "MoveGridCallHereResult" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}grids\0\u{1}connection\0\u{3}current_call\0\u{1}moved\0") + + fileprivate class _StorageClass { + var _grids: [Grid] = [] + var _connection: GridConnectionCredentials? = nil + var _currentCall: GridCurrentCall? = nil + var _moved: Bool = false + + // This property is used as the initial default value for new instances of the type. + // The type itself is protecting the reference to its storage via CoW semantics. + // This will force a copy to be made of this reference when the first mutation occurs; + // hence, it is safe to mark this as `nonisolated(unsafe)`. + static nonisolated(unsafe) let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _grids = source._grids + _connection = source._connection + _currentCall = source._currentCall + _moved = source._moved + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + + public mutating func decodeMessage(decoder: inout D) throws { + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedMessageField(value: &_storage._grids) }() + case 2: try { try decoder.decodeSingularMessageField(value: &_storage._connection) }() + case 3: try { try decoder.decodeSingularMessageField(value: &_storage._currentCall) }() + case 4: try { try decoder.decodeSingularBoolField(value: &_storage._moved) }() + default: break + } + } + } + } + + public func traverse(visitor: inout V) throws { + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !_storage._grids.isEmpty { + try visitor.visitRepeatedMessageField(value: _storage._grids, fieldNumber: 1) + } + try { if let v = _storage._connection { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._currentCall { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + if _storage._moved != false { + try visitor.visitSingularBoolField(value: _storage._moved, fieldNumber: 4) + } + } + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: MoveGridCallHereResult, rhs: MoveGridCallHereResult) -> Bool { + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._grids != rhs_storage._grids {return false} + if _storage._connection != rhs_storage._connection {return false} + if _storage._currentCall != rhs_storage._currentCall {return false} + if _storage._moved != rhs_storage._moved {return false} + return true + } + if !storagesAreEqual {return false} + } if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -36954,7 +37489,7 @@ nonisolated extension DeleteGridRoomResult: SwiftProtobuf.Message, SwiftProtobuf nonisolated extension PrepareGridConnectionInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "PrepareGridConnectionInput" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}room_id\0\u{1}generation\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}room_id\0\u{1}generation\0\u{3}expected_membership_id\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -36964,24 +37499,33 @@ nonisolated extension PrepareGridConnectionInput: SwiftProtobuf.Message, SwiftPr switch fieldNumber { case 1: try { try decoder.decodeSingularInt64Field(value: &self.roomID) }() case 2: try { try decoder.decodeSingularInt32Field(value: &self.generation) }() + case 3: try { try decoder.decodeSingularStringField(value: &self._expectedMembershipID) }() default: break } } } public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if self.roomID != 0 { try visitor.visitSingularInt64Field(value: self.roomID, fieldNumber: 1) } if self.generation != 0 { try visitor.visitSingularInt32Field(value: self.generation, fieldNumber: 2) } + try { if let v = self._expectedMembershipID { + try visitor.visitSingularStringField(value: v, fieldNumber: 3) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: PrepareGridConnectionInput, rhs: PrepareGridConnectionInput) -> Bool { if lhs.roomID != rhs.roomID {return false} if lhs.generation != rhs.generation {return false} + if lhs._expectedMembershipID != rhs._expectedMembershipID {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -37028,7 +37572,7 @@ nonisolated extension PrepareGridConnectionResult: SwiftProtobuf.Message, SwiftP nonisolated extension SetGridAvatarMicrophoneEnabledInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = "SetGridAvatarMicrophoneEnabledInput" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}expected_room_id\0\u{1}enabled\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{3}expected_room_id\0\u{1}enabled\0\u{3}expected_membership_id\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -37038,24 +37582,33 @@ nonisolated extension SetGridAvatarMicrophoneEnabledInput: SwiftProtobuf.Message switch fieldNumber { case 1: try { try decoder.decodeSingularInt64Field(value: &self.expectedRoomID) }() case 2: try { try decoder.decodeSingularBoolField(value: &self.enabled) }() + case 3: try { try decoder.decodeSingularStringField(value: &self._expectedMembershipID) }() default: break } } } public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 if self.expectedRoomID != 0 { try visitor.visitSingularInt64Field(value: self.expectedRoomID, fieldNumber: 1) } if self.enabled != false { try visitor.visitSingularBoolField(value: self.enabled, fieldNumber: 2) } + try { if let v = self._expectedMembershipID { + try visitor.visitSingularStringField(value: v, fieldNumber: 3) + } }() try unknownFields.traverse(visitor: &visitor) } public static func ==(lhs: SetGridAvatarMicrophoneEnabledInput, rhs: SetGridAvatarMicrophoneEnabledInput) -> Bool { if lhs.expectedRoomID != rhs.expectedRoomID {return false} if lhs.enabled != rhs.enabled {return false} + if lhs._expectedMembershipID != rhs._expectedMembershipID {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } diff --git a/crates/protocol/proto/core.proto b/crates/protocol/proto/core.proto index e3269be83..bf7d0a8c7 100644 --- a/crates/protocol/proto/core.proto +++ b/crates/protocol/proto/core.proto @@ -1621,6 +1621,7 @@ enum Method { REQUEST_BOT_FILESYSTEM = 143; ANSWER_BOT_FILESYSTEM = 144; SET_SPACE_PHOTO = 145; + MOVE_GRID_CALL_HERE = 146; OPEN_GRID_THREAD = 147; SET_GRID_TRANSCRIPTION = 148; LIST_GRID_TRANSCRIPTS = 149; @@ -1774,6 +1775,7 @@ message RpcCall { RequestBotFilesystemInput requestBotFilesystem = 144; AnswerBotFilesystemInput answerBotFilesystem = 145; SetSpacePhotoInput setSpacePhoto = 146; + MoveGridCallHereInput moveGridCallHere = 147; OpenGridThreadInput openGridThread = 148; SetGridTranscriptionInput setGridTranscription = 149; ListGridTranscriptsInput listGridTranscripts = 150; @@ -1929,6 +1931,7 @@ message RpcResult { RequestBotFilesystemResult requestBotFilesystem = 144; AnswerBotFilesystemResult answerBotFilesystem = 145; SetSpacePhotoResult setSpacePhoto = 146; + MoveGridCallHereResult moveGridCallHere = 147; OpenGridThreadResult openGridThread = 148; SetGridTranscriptionResult setGridTranscription = 149; ListGridTranscriptsResult listGridTranscripts = 150; @@ -2167,6 +2170,22 @@ message Grid { // Server-owned monotonic revision for this Space's complete Grid snapshot. // Clients must not replace a newer snapshot with a lower revision. int64 revision = 5; + // Effective server capability, including ownership fence enforcement. + // New clients must not admit media through an older/disabled server. + bool call_transfer_enabled = 6; +} + +// Personalized control ownership, disclosed only to the authenticated user. +// call_id stays stable across an explicit device transfer; membership_id is +// replaced on each transfer/resume and fences commands and media credentials. +message GridCurrentCall { + string call_id = 1; + int64 space_id = 2; + int64 room_id = 3; + string membership_id = 4; + bool owned_by_current_session = 5; + // Coarse existing session client kind (ios, macos, etc.), not a device ID. + string owner_client_type = 6; } // Short-lived, session-targeted LiveKit credentials. Never persist or include @@ -2177,11 +2196,21 @@ message GridConnectionCredentials { string participant_identity = 3; string token = 4; int64 expires_at = 5; + string call_id = 6; + string membership_id = 7; } -message GetGridInput { int64 space_id = 1; } +message GetGridInput { + int64 space_id = 1; + // Explicit renewal by the admitted owner. Omit for read-only browsing when + // call-transfer enforcement is active. A stale fence never renews presence. + optional string expected_membership_id = 2; +} -message GetGridResult { Grid grid = 1; } +message GetGridResult { + Grid grid = 1; + optional GridCurrentCall current_call = 2; +} // Home-level discovery for every Grid-enabled Space the current user can see. // This is a bounded summary: full rooms remain available only through GetGrid. @@ -2194,31 +2223,66 @@ message GridHomeSpace { message GetGridHomeInput {} -message GetGridHomeResult { repeated GridHomeSpace spaces = 1; } +message GetGridHomeResult { + repeated GridHomeSpace spaces = 1; + optional GridCurrentCall current_call = 2; + bool call_transfer_enabled = 3; +} message CreateGridRoomInput { int64 space_id = 1; optional bool microphone_enabled = 2; + // Required when call_transfer_enabled: empty expects no active presence; + // nonempty must match the current session's live membership exactly. + optional string expected_membership_id = 3; } message CreateGridRoomResult { repeated Grid grids = 1; optional GridConnectionCredentials connection = 2; + // Successful claims report the projection captured at commit. A newer + // accepted Grid/self state wins; this field alone cannot admit media. + optional GridCurrentCall current_call = 3; } message JoinGridRoomInput { int64 room_id = 1; optional bool microphone_enabled = 2; + optional string expected_membership_id = 3; } message JoinGridRoomResult { repeated Grid grids = 1; optional GridConnectionCredentials connection = 2; + optional GridCurrentCall current_call = 3; +} + +message LeaveGridRoomInput { + int64 expected_room_id = 1; + optional string expected_membership_id = 2; } -message LeaveGridRoomInput { int64 expected_room_id = 1; } +message LeaveGridRoomResult { + repeated Grid grids = 1; + // Display/repair only; leaving never grants a replacement admission. + optional GridCurrentCall current_call = 2; +} -message LeaveGridRoomResult { repeated Grid grids = 1; } +// Explicitly move this user's existing room participation to the authenticated +// calling session (also used for a same-login cold-process Resume). +message MoveGridCallHereInput { + string call_id = 1; + string expected_membership_id = 2; +} + +message MoveGridCallHereResult { + repeated Grid grids = 1; + optional GridConnectionCredentials connection = 2; + // Captured at the claim's commit when moved=true; display/repair otherwise. + optional GridCurrentCall current_call = 3; + // False means the expected call/membership changed; no claim was performed. + bool moved = 4; +} message SetGridRoomTitleInput { int64 room_id = 1; @@ -2242,6 +2306,7 @@ message DeleteGridRoomResult { Grid grid = 1; } message PrepareGridConnectionInput { int64 room_id = 1; int32 generation = 2; + optional string expected_membership_id = 3; } enum GridConnectionUnavailableReason { @@ -2258,6 +2323,7 @@ message PrepareGridConnectionResult { message SetGridAvatarMicrophoneEnabledInput { int64 expected_room_id = 1; bool enabled = 2; + optional string expected_membership_id = 3; } message SetGridAvatarMicrophoneEnabledResult { bool enabled = 1; } diff --git a/packages/protocol/src/core.ts b/packages/protocol/src/core.ts index daeaba6aa..86a3c3277 100644 --- a/packages/protocol/src/core.ts +++ b/packages/protocol/src/core.ts @@ -4606,6 +4606,12 @@ export interface RpcCall { * @generated from protobuf field: SetSpacePhotoInput setSpacePhoto = 146; */ setSpacePhoto: SetSpacePhotoInput; + } | { + oneofKind: "moveGridCallHere"; + /** + * @generated from protobuf field: MoveGridCallHereInput moveGridCallHere = 147; + */ + moveGridCallHere: MoveGridCallHereInput; } | { oneofKind: "openGridThread"; /** @@ -5503,6 +5509,12 @@ export interface RpcResult { * @generated from protobuf field: SetSpacePhotoResult setSpacePhoto = 146; */ setSpacePhoto: SetSpacePhotoResult; + } | { + oneofKind: "moveGridCallHere"; + /** + * @generated from protobuf field: MoveGridCallHereResult moveGridCallHere = 147; + */ + moveGridCallHere: MoveGridCallHereResult; } | { oneofKind: "openGridThread"; /** @@ -6132,6 +6144,48 @@ export interface Grid { * @generated from protobuf field: int64 revision = 5; */ revision: bigint; + /** + * Effective server capability, including ownership fence enforcement. + * New clients must not admit media through an older/disabled server. + * + * @generated from protobuf field: bool call_transfer_enabled = 6; + */ + callTransferEnabled: boolean; +} +/** + * Personalized control ownership, disclosed only to the authenticated user. + * call_id stays stable across an explicit device transfer; membership_id is + * replaced on each transfer/resume and fences commands and media credentials. + * + * @generated from protobuf message GridCurrentCall + */ +export interface GridCurrentCall { + /** + * @generated from protobuf field: string call_id = 1; + */ + callId: string; + /** + * @generated from protobuf field: int64 space_id = 2; + */ + spaceId: bigint; + /** + * @generated from protobuf field: int64 room_id = 3; + */ + roomId: bigint; + /** + * @generated from protobuf field: string membership_id = 4; + */ + membershipId: string; + /** + * @generated from protobuf field: bool owned_by_current_session = 5; + */ + ownedByCurrentSession: boolean; + /** + * Coarse existing session client kind (ios, macos, etc.), not a device ID. + * + * @generated from protobuf field: string owner_client_type = 6; + */ + ownerClientType: string; } /** * Short-lived, session-targeted LiveKit credentials. Never persist or include @@ -6160,6 +6214,14 @@ export interface GridConnectionCredentials { * @generated from protobuf field: int64 expires_at = 5; */ expiresAt: bigint; + /** + * @generated from protobuf field: string call_id = 6; + */ + callId: string; + /** + * @generated from protobuf field: string membership_id = 7; + */ + membershipId: string; } /** * @generated from protobuf message GetGridInput @@ -6169,6 +6231,13 @@ export interface GetGridInput { * @generated from protobuf field: int64 space_id = 1; */ spaceId: bigint; + /** + * Explicit renewal by the admitted owner. Omit for read-only browsing when + * call-transfer enforcement is active. A stale fence never renews presence. + * + * @generated from protobuf field: optional string expected_membership_id = 2; + */ + expectedMembershipId?: string; } /** * @generated from protobuf message GetGridResult @@ -6178,6 +6247,10 @@ export interface GetGridResult { * @generated from protobuf field: Grid grid = 1; */ grid?: Grid; + /** + * @generated from protobuf field: optional GridCurrentCall current_call = 2; + */ + currentCall?: GridCurrentCall; } /** * Home-level discovery for every Grid-enabled Space the current user can see. @@ -6216,6 +6289,14 @@ export interface GetGridHomeResult { * @generated from protobuf field: repeated GridHomeSpace spaces = 1; */ spaces: GridHomeSpace[]; + /** + * @generated from protobuf field: optional GridCurrentCall current_call = 2; + */ + currentCall?: GridCurrentCall; + /** + * @generated from protobuf field: bool call_transfer_enabled = 3; + */ + callTransferEnabled: boolean; } /** * @generated from protobuf message CreateGridRoomInput @@ -6229,6 +6310,13 @@ export interface CreateGridRoomInput { * @generated from protobuf field: optional bool microphone_enabled = 2; */ microphoneEnabled?: boolean; + /** + * Required when call_transfer_enabled: empty expects no active presence; + * nonempty must match the current session's live membership exactly. + * + * @generated from protobuf field: optional string expected_membership_id = 3; + */ + expectedMembershipId?: string; } /** * @generated from protobuf message CreateGridRoomResult @@ -6242,6 +6330,13 @@ export interface CreateGridRoomResult { * @generated from protobuf field: optional GridConnectionCredentials connection = 2; */ connection?: GridConnectionCredentials; + /** + * Successful claims report the projection captured at commit. A newer + * accepted Grid/self state wins; this field alone cannot admit media. + * + * @generated from protobuf field: optional GridCurrentCall current_call = 3; + */ + currentCall?: GridCurrentCall; } /** * @generated from protobuf message JoinGridRoomInput @@ -6255,6 +6350,10 @@ export interface JoinGridRoomInput { * @generated from protobuf field: optional bool microphone_enabled = 2; */ microphoneEnabled?: boolean; + /** + * @generated from protobuf field: optional string expected_membership_id = 3; + */ + expectedMembershipId?: string; } /** * @generated from protobuf message JoinGridRoomResult @@ -6268,6 +6367,10 @@ export interface JoinGridRoomResult { * @generated from protobuf field: optional GridConnectionCredentials connection = 2; */ connection?: GridConnectionCredentials; + /** + * @generated from protobuf field: optional GridCurrentCall current_call = 3; + */ + currentCall?: GridCurrentCall; } /** * @generated from protobuf message LeaveGridRoomInput @@ -6277,6 +6380,10 @@ export interface LeaveGridRoomInput { * @generated from protobuf field: int64 expected_room_id = 1; */ expectedRoomId: bigint; + /** + * @generated from protobuf field: optional string expected_membership_id = 2; + */ + expectedMembershipId?: string; } /** * @generated from protobuf message LeaveGridRoomResult @@ -6286,6 +6393,53 @@ export interface LeaveGridRoomResult { * @generated from protobuf field: repeated Grid grids = 1; */ grids: Grid[]; + /** + * Display/repair only; leaving never grants a replacement admission. + * + * @generated from protobuf field: optional GridCurrentCall current_call = 2; + */ + currentCall?: GridCurrentCall; +} +/** + * Explicitly move this user's existing room participation to the authenticated + * calling session (also used for a same-login cold-process Resume). + * + * @generated from protobuf message MoveGridCallHereInput + */ +export interface MoveGridCallHereInput { + /** + * @generated from protobuf field: string call_id = 1; + */ + callId: string; + /** + * @generated from protobuf field: string expected_membership_id = 2; + */ + expectedMembershipId: string; +} +/** + * @generated from protobuf message MoveGridCallHereResult + */ +export interface MoveGridCallHereResult { + /** + * @generated from protobuf field: repeated Grid grids = 1; + */ + grids: Grid[]; + /** + * @generated from protobuf field: optional GridConnectionCredentials connection = 2; + */ + connection?: GridConnectionCredentials; + /** + * Captured at the claim's commit when moved=true; display/repair otherwise. + * + * @generated from protobuf field: optional GridCurrentCall current_call = 3; + */ + currentCall?: GridCurrentCall; + /** + * False means the expected call/membership changed; no claim was performed. + * + * @generated from protobuf field: bool moved = 4; + */ + moved: boolean; } /** * @generated from protobuf message SetGridRoomTitleInput @@ -6363,6 +6517,10 @@ export interface PrepareGridConnectionInput { * @generated from protobuf field: int32 generation = 2; */ generation: number; + /** + * @generated from protobuf field: optional string expected_membership_id = 3; + */ + expectedMembershipId?: string; } /** * @generated from protobuf message PrepareGridConnectionResult @@ -6389,6 +6547,10 @@ export interface SetGridAvatarMicrophoneEnabledInput { * @generated from protobuf field: bool enabled = 2; */ enabled: boolean; + /** + * @generated from protobuf field: optional string expected_membership_id = 3; + */ + expectedMembershipId?: string; } /** * @generated from protobuf message SetGridAvatarMicrophoneEnabledResult @@ -14566,6 +14728,10 @@ export enum Method { * @generated from protobuf enum value: SET_SPACE_PHOTO = 145; */ SET_SPACE_PHOTO = 145, + /** + * @generated from protobuf enum value: MOVE_GRID_CALL_HERE = 146; + */ + MOVE_GRID_CALL_HERE = 146, /** * @generated from protobuf enum value: OPEN_GRID_THREAD = 147; */ @@ -23743,6 +23909,7 @@ class RpcCall$Type extends MessageType { { no: 144, name: "requestBotFilesystem", kind: "message", oneof: "input", T: () => RequestBotFilesystemInput }, { no: 145, name: "answerBotFilesystem", kind: "message", oneof: "input", T: () => AnswerBotFilesystemInput }, { no: 146, name: "setSpacePhoto", kind: "message", oneof: "input", T: () => SetSpacePhotoInput }, + { no: 147, name: "moveGridCallHere", kind: "message", oneof: "input", T: () => MoveGridCallHereInput }, { no: 148, name: "openGridThread", kind: "message", oneof: "input", T: () => OpenGridThreadInput }, { no: 149, name: "setGridTranscription", kind: "message", oneof: "input", T: () => SetGridTranscriptionInput }, { no: 150, name: "listGridTranscripts", kind: "message", oneof: "input", T: () => ListGridTranscriptsInput } @@ -24628,6 +24795,12 @@ class RpcCall$Type extends MessageType { setSpacePhoto: SetSpacePhotoInput.internalBinaryRead(reader, reader.uint32(), options, (message.input as any).setSpacePhoto) }; break; + case /* MoveGridCallHereInput moveGridCallHere */ 147: + message.input = { + oneofKind: "moveGridCallHere", + moveGridCallHere: MoveGridCallHereInput.internalBinaryRead(reader, reader.uint32(), options, (message.input as any).moveGridCallHere) + }; + break; case /* OpenGridThreadInput openGridThread */ 148: message.input = { oneofKind: "openGridThread", @@ -25093,6 +25266,9 @@ class RpcCall$Type extends MessageType { /* SetSpacePhotoInput setSpacePhoto = 146; */ if (message.input.oneofKind === "setSpacePhoto") SetSpacePhotoInput.internalBinaryWrite(message.input.setSpacePhoto, writer.tag(146, WireType.LengthDelimited).fork(), options).join(); + /* MoveGridCallHereInput moveGridCallHere = 147; */ + if (message.input.oneofKind === "moveGridCallHere") + MoveGridCallHereInput.internalBinaryWrite(message.input.moveGridCallHere, writer.tag(147, WireType.LengthDelimited).fork(), options).join(); /* OpenGridThreadInput openGridThread = 148; */ if (message.input.oneofKind === "openGridThread") OpenGridThreadInput.internalBinaryWrite(message.input.openGridThread, writer.tag(148, WireType.LengthDelimited).fork(), options).join(); @@ -25261,6 +25437,7 @@ class RpcResult$Type extends MessageType { { no: 144, name: "requestBotFilesystem", kind: "message", oneof: "result", T: () => RequestBotFilesystemResult }, { no: 145, name: "answerBotFilesystem", kind: "message", oneof: "result", T: () => AnswerBotFilesystemResult }, { no: 146, name: "setSpacePhoto", kind: "message", oneof: "result", T: () => SetSpacePhotoResult }, + { no: 147, name: "moveGridCallHere", kind: "message", oneof: "result", T: () => MoveGridCallHereResult }, { no: 148, name: "openGridThread", kind: "message", oneof: "result", T: () => OpenGridThreadResult }, { no: 149, name: "setGridTranscription", kind: "message", oneof: "result", T: () => SetGridTranscriptionResult }, { no: 150, name: "listGridTranscripts", kind: "message", oneof: "result", T: () => ListGridTranscriptsResult } @@ -26146,6 +26323,12 @@ class RpcResult$Type extends MessageType { setSpacePhoto: SetSpacePhotoResult.internalBinaryRead(reader, reader.uint32(), options, (message.result as any).setSpacePhoto) }; break; + case /* MoveGridCallHereResult moveGridCallHere */ 147: + message.result = { + oneofKind: "moveGridCallHere", + moveGridCallHere: MoveGridCallHereResult.internalBinaryRead(reader, reader.uint32(), options, (message.result as any).moveGridCallHere) + }; + break; case /* OpenGridThreadResult openGridThread */ 148: message.result = { oneofKind: "openGridThread", @@ -26611,6 +26794,9 @@ class RpcResult$Type extends MessageType { /* SetSpacePhotoResult setSpacePhoto = 146; */ if (message.result.oneofKind === "setSpacePhoto") SetSpacePhotoResult.internalBinaryWrite(message.result.setSpacePhoto, writer.tag(146, WireType.LengthDelimited).fork(), options).join(); + /* MoveGridCallHereResult moveGridCallHere = 147; */ + if (message.result.oneofKind === "moveGridCallHere") + MoveGridCallHereResult.internalBinaryWrite(message.result.moveGridCallHere, writer.tag(147, WireType.LengthDelimited).fork(), options).join(); /* OpenGridThreadResult openGridThread = 148; */ if (message.result.oneofKind === "openGridThread") OpenGridThreadResult.internalBinaryWrite(message.result.openGridThread, writer.tag(148, WireType.LengthDelimited).fork(), options).join(); @@ -28387,7 +28573,8 @@ class Grid$Type extends MessageType { { no: 2, name: "enabled", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, { no: 3, name: "rooms", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => GridRoom }, { no: 4, name: "current_room_id", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 5, name: "revision", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 5, name: "revision", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 6, name: "call_transfer_enabled", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } ]); } create(value?: PartialMessage): Grid { @@ -28396,6 +28583,7 @@ class Grid$Type extends MessageType { message.enabled = false; message.rooms = []; message.revision = 0n; + message.callTransferEnabled = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -28420,6 +28608,9 @@ class Grid$Type extends MessageType { case /* int64 revision */ 5: message.revision = reader.int64().toBigInt(); break; + case /* bool call_transfer_enabled */ 6: + message.callTransferEnabled = reader.bool(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -28447,6 +28638,9 @@ class Grid$Type extends MessageType { /* int64 revision = 5; */ if (message.revision !== 0n) writer.tag(5, WireType.Varint).int64(message.revision); + /* bool call_transfer_enabled = 6; */ + if (message.callTransferEnabled !== false) + writer.tag(6, WireType.Varint).bool(message.callTransferEnabled); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -28458,6 +28652,93 @@ class Grid$Type extends MessageType { */ export const Grid = new Grid$Type(); // @generated message type with reflection information, may provide speed optimized methods +class GridCurrentCall$Type extends MessageType { + constructor() { + super("GridCurrentCall", [ + { no: 1, name: "call_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "space_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "room_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "membership_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 5, name: "owned_by_current_session", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 6, name: "owner_client_type", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): GridCurrentCall { + const message = globalThis.Object.create((this.messagePrototype!)); + message.callId = ""; + message.spaceId = 0n; + message.roomId = 0n; + message.membershipId = ""; + message.ownedByCurrentSession = false; + message.ownerClientType = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: GridCurrentCall): GridCurrentCall { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string call_id */ 1: + message.callId = reader.string(); + break; + case /* int64 space_id */ 2: + message.spaceId = reader.int64().toBigInt(); + break; + case /* int64 room_id */ 3: + message.roomId = reader.int64().toBigInt(); + break; + case /* string membership_id */ 4: + message.membershipId = reader.string(); + break; + case /* bool owned_by_current_session */ 5: + message.ownedByCurrentSession = reader.bool(); + break; + case /* string owner_client_type */ 6: + message.ownerClientType = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: GridCurrentCall, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string call_id = 1; */ + if (message.callId !== "") + writer.tag(1, WireType.LengthDelimited).string(message.callId); + /* int64 space_id = 2; */ + if (message.spaceId !== 0n) + writer.tag(2, WireType.Varint).int64(message.spaceId); + /* int64 room_id = 3; */ + if (message.roomId !== 0n) + writer.tag(3, WireType.Varint).int64(message.roomId); + /* string membership_id = 4; */ + if (message.membershipId !== "") + writer.tag(4, WireType.LengthDelimited).string(message.membershipId); + /* bool owned_by_current_session = 5; */ + if (message.ownedByCurrentSession !== false) + writer.tag(5, WireType.Varint).bool(message.ownedByCurrentSession); + /* string owner_client_type = 6; */ + if (message.ownerClientType !== "") + writer.tag(6, WireType.LengthDelimited).string(message.ownerClientType); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message GridCurrentCall + */ +export const GridCurrentCall = new GridCurrentCall$Type(); +// @generated message type with reflection information, may provide speed optimized methods class GridConnectionCredentials$Type extends MessageType { constructor() { super("GridConnectionCredentials", [ @@ -28465,7 +28746,9 @@ class GridConnectionCredentials$Type extends MessageType): GridConnectionCredentials { @@ -28474,6 +28757,8 @@ class GridConnectionCredentials$Type extends MessageType(this, message, value); return message; @@ -28498,6 +28783,12 @@ class GridConnectionCredentials$Type extends MessageType { constructor() { super("GetGridInput", [ - { no: 1, name: "space_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "space_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "expected_membership_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ } ]); } create(value?: PartialMessage): GetGridInput { @@ -28557,6 +28855,9 @@ class GetGridInput$Type extends MessageType { case /* int64 space_id */ 1: message.spaceId = reader.int64().toBigInt(); break; + case /* optional string expected_membership_id */ 2: + message.expectedMembershipId = reader.string(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -28572,6 +28873,9 @@ class GetGridInput$Type extends MessageType { /* int64 space_id = 1; */ if (message.spaceId !== 0n) writer.tag(1, WireType.Varint).int64(message.spaceId); + /* optional string expected_membership_id = 2; */ + if (message.expectedMembershipId !== undefined) + writer.tag(2, WireType.LengthDelimited).string(message.expectedMembershipId); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -28586,7 +28890,8 @@ export const GetGridInput = new GetGridInput$Type(); class GetGridResult$Type extends MessageType { constructor() { super("GetGridResult", [ - { no: 1, name: "grid", kind: "message", T: () => Grid } + { no: 1, name: "grid", kind: "message", T: () => Grid }, + { no: 2, name: "current_call", kind: "message", T: () => GridCurrentCall } ]); } create(value?: PartialMessage): GetGridResult { @@ -28603,6 +28908,9 @@ class GetGridResult$Type extends MessageType { case /* Grid grid */ 1: message.grid = Grid.internalBinaryRead(reader, reader.uint32(), options, message.grid); break; + case /* optional GridCurrentCall current_call */ 2: + message.currentCall = GridCurrentCall.internalBinaryRead(reader, reader.uint32(), options, message.currentCall); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -28618,6 +28926,9 @@ class GetGridResult$Type extends MessageType { /* Grid grid = 1; */ if (message.grid) Grid.internalBinaryWrite(message.grid, writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + /* optional GridCurrentCall current_call = 2; */ + if (message.currentCall) + GridCurrentCall.internalBinaryWrite(message.currentCall, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -28728,12 +29039,15 @@ export const GetGridHomeInput = new GetGridHomeInput$Type(); class GetGridHomeResult$Type extends MessageType { constructor() { super("GetGridHomeResult", [ - { no: 1, name: "spaces", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => GridHomeSpace } + { no: 1, name: "spaces", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => GridHomeSpace }, + { no: 2, name: "current_call", kind: "message", T: () => GridCurrentCall }, + { no: 3, name: "call_transfer_enabled", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } ]); } create(value?: PartialMessage): GetGridHomeResult { const message = globalThis.Object.create((this.messagePrototype!)); message.spaces = []; + message.callTransferEnabled = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -28746,6 +29060,12 @@ class GetGridHomeResult$Type extends MessageType { case /* repeated GridHomeSpace spaces */ 1: message.spaces.push(GridHomeSpace.internalBinaryRead(reader, reader.uint32(), options)); break; + case /* optional GridCurrentCall current_call */ 2: + message.currentCall = GridCurrentCall.internalBinaryRead(reader, reader.uint32(), options, message.currentCall); + break; + case /* bool call_transfer_enabled */ 3: + message.callTransferEnabled = reader.bool(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -28761,6 +29081,12 @@ class GetGridHomeResult$Type extends MessageType { /* repeated GridHomeSpace spaces = 1; */ for (let i = 0; i < message.spaces.length; i++) GridHomeSpace.internalBinaryWrite(message.spaces[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + /* optional GridCurrentCall current_call = 2; */ + if (message.currentCall) + GridCurrentCall.internalBinaryWrite(message.currentCall, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* bool call_transfer_enabled = 3; */ + if (message.callTransferEnabled !== false) + writer.tag(3, WireType.Varint).bool(message.callTransferEnabled); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -28776,7 +29102,8 @@ class CreateGridRoomInput$Type extends MessageType { constructor() { super("CreateGridRoomInput", [ { no: 1, name: "space_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 2, name: "microphone_enabled", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ } + { no: 2, name: "microphone_enabled", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 3, name: "expected_membership_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ } ]); } create(value?: PartialMessage): CreateGridRoomInput { @@ -28797,6 +29124,9 @@ class CreateGridRoomInput$Type extends MessageType { case /* optional bool microphone_enabled */ 2: message.microphoneEnabled = reader.bool(); break; + case /* optional string expected_membership_id */ 3: + message.expectedMembershipId = reader.string(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -28815,6 +29145,9 @@ class CreateGridRoomInput$Type extends MessageType { /* optional bool microphone_enabled = 2; */ if (message.microphoneEnabled !== undefined) writer.tag(2, WireType.Varint).bool(message.microphoneEnabled); + /* optional string expected_membership_id = 3; */ + if (message.expectedMembershipId !== undefined) + writer.tag(3, WireType.LengthDelimited).string(message.expectedMembershipId); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -28830,7 +29163,8 @@ class CreateGridRoomResult$Type extends MessageType { constructor() { super("CreateGridRoomResult", [ { no: 1, name: "grids", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Grid }, - { no: 2, name: "connection", kind: "message", T: () => GridConnectionCredentials } + { no: 2, name: "connection", kind: "message", T: () => GridConnectionCredentials }, + { no: 3, name: "current_call", kind: "message", T: () => GridCurrentCall } ]); } create(value?: PartialMessage): CreateGridRoomResult { @@ -28851,6 +29185,9 @@ class CreateGridRoomResult$Type extends MessageType { case /* optional GridConnectionCredentials connection */ 2: message.connection = GridConnectionCredentials.internalBinaryRead(reader, reader.uint32(), options, message.connection); break; + case /* optional GridCurrentCall current_call */ 3: + message.currentCall = GridCurrentCall.internalBinaryRead(reader, reader.uint32(), options, message.currentCall); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -28869,6 +29206,9 @@ class CreateGridRoomResult$Type extends MessageType { /* optional GridConnectionCredentials connection = 2; */ if (message.connection) GridConnectionCredentials.internalBinaryWrite(message.connection, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* optional GridCurrentCall current_call = 3; */ + if (message.currentCall) + GridCurrentCall.internalBinaryWrite(message.currentCall, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -28884,7 +29224,8 @@ class JoinGridRoomInput$Type extends MessageType { constructor() { super("JoinGridRoomInput", [ { no: 1, name: "room_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 2, name: "microphone_enabled", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ } + { no: 2, name: "microphone_enabled", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 3, name: "expected_membership_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ } ]); } create(value?: PartialMessage): JoinGridRoomInput { @@ -28905,6 +29246,9 @@ class JoinGridRoomInput$Type extends MessageType { case /* optional bool microphone_enabled */ 2: message.microphoneEnabled = reader.bool(); break; + case /* optional string expected_membership_id */ 3: + message.expectedMembershipId = reader.string(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -28923,6 +29267,9 @@ class JoinGridRoomInput$Type extends MessageType { /* optional bool microphone_enabled = 2; */ if (message.microphoneEnabled !== undefined) writer.tag(2, WireType.Varint).bool(message.microphoneEnabled); + /* optional string expected_membership_id = 3; */ + if (message.expectedMembershipId !== undefined) + writer.tag(3, WireType.LengthDelimited).string(message.expectedMembershipId); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -28938,7 +29285,8 @@ class JoinGridRoomResult$Type extends MessageType { constructor() { super("JoinGridRoomResult", [ { no: 1, name: "grids", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Grid }, - { no: 2, name: "connection", kind: "message", T: () => GridConnectionCredentials } + { no: 2, name: "connection", kind: "message", T: () => GridConnectionCredentials }, + { no: 3, name: "current_call", kind: "message", T: () => GridCurrentCall } ]); } create(value?: PartialMessage): JoinGridRoomResult { @@ -28959,6 +29307,9 @@ class JoinGridRoomResult$Type extends MessageType { case /* optional GridConnectionCredentials connection */ 2: message.connection = GridConnectionCredentials.internalBinaryRead(reader, reader.uint32(), options, message.connection); break; + case /* optional GridCurrentCall current_call */ 3: + message.currentCall = GridCurrentCall.internalBinaryRead(reader, reader.uint32(), options, message.currentCall); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -28977,6 +29328,9 @@ class JoinGridRoomResult$Type extends MessageType { /* optional GridConnectionCredentials connection = 2; */ if (message.connection) GridConnectionCredentials.internalBinaryWrite(message.connection, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* optional GridCurrentCall current_call = 3; */ + if (message.currentCall) + GridCurrentCall.internalBinaryWrite(message.currentCall, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -28991,7 +29345,8 @@ export const JoinGridRoomResult = new JoinGridRoomResult$Type(); class LeaveGridRoomInput$Type extends MessageType { constructor() { super("LeaveGridRoomInput", [ - { no: 1, name: "expected_room_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "expected_room_id", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "expected_membership_id", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ } ]); } create(value?: PartialMessage): LeaveGridRoomInput { @@ -29009,6 +29364,9 @@ class LeaveGridRoomInput$Type extends MessageType { case /* int64 expected_room_id */ 1: message.expectedRoomId = reader.int64().toBigInt(); break; + case /* optional string expected_membership_id */ 2: + message.expectedMembershipId = reader.string(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -29024,6 +29382,9 @@ class LeaveGridRoomInput$Type extends MessageType { /* int64 expected_room_id = 1; */ if (message.expectedRoomId !== 0n) writer.tag(1, WireType.Varint).int64(message.expectedRoomId); + /* optional string expected_membership_id = 2; */ + if (message.expectedMembershipId !== undefined) + writer.tag(2, WireType.LengthDelimited).string(message.expectedMembershipId); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -29038,7 +29399,8 @@ export const LeaveGridRoomInput = new LeaveGridRoomInput$Type(); class LeaveGridRoomResult$Type extends MessageType { constructor() { super("LeaveGridRoomResult", [ - { no: 1, name: "grids", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Grid } + { no: 1, name: "grids", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Grid }, + { no: 2, name: "current_call", kind: "message", T: () => GridCurrentCall } ]); } create(value?: PartialMessage): LeaveGridRoomResult { @@ -29056,6 +29418,9 @@ class LeaveGridRoomResult$Type extends MessageType { case /* repeated Grid grids */ 1: message.grids.push(Grid.internalBinaryRead(reader, reader.uint32(), options)); break; + case /* optional GridCurrentCall current_call */ 2: + message.currentCall = GridCurrentCall.internalBinaryRead(reader, reader.uint32(), options, message.currentCall); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -29071,6 +29436,9 @@ class LeaveGridRoomResult$Type extends MessageType { /* repeated Grid grids = 1; */ for (let i = 0; i < message.grids.length; i++) Grid.internalBinaryWrite(message.grids[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + /* optional GridCurrentCall current_call = 2; */ + if (message.currentCall) + GridCurrentCall.internalBinaryWrite(message.currentCall, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -29082,6 +29450,130 @@ class LeaveGridRoomResult$Type extends MessageType { */ export const LeaveGridRoomResult = new LeaveGridRoomResult$Type(); // @generated message type with reflection information, may provide speed optimized methods +class MoveGridCallHereInput$Type extends MessageType { + constructor() { + super("MoveGridCallHereInput", [ + { no: 1, name: "call_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "expected_membership_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): MoveGridCallHereInput { + const message = globalThis.Object.create((this.messagePrototype!)); + message.callId = ""; + message.expectedMembershipId = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MoveGridCallHereInput): MoveGridCallHereInput { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string call_id */ 1: + message.callId = reader.string(); + break; + case /* string expected_membership_id */ 2: + message.expectedMembershipId = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MoveGridCallHereInput, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string call_id = 1; */ + if (message.callId !== "") + writer.tag(1, WireType.LengthDelimited).string(message.callId); + /* string expected_membership_id = 2; */ + if (message.expectedMembershipId !== "") + writer.tag(2, WireType.LengthDelimited).string(message.expectedMembershipId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MoveGridCallHereInput + */ +export const MoveGridCallHereInput = new MoveGridCallHereInput$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MoveGridCallHereResult$Type extends MessageType { + constructor() { + super("MoveGridCallHereResult", [ + { no: 1, name: "grids", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => Grid }, + { no: 2, name: "connection", kind: "message", T: () => GridConnectionCredentials }, + { no: 3, name: "current_call", kind: "message", T: () => GridCurrentCall }, + { no: 4, name: "moved", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } + ]); + } + create(value?: PartialMessage): MoveGridCallHereResult { + const message = globalThis.Object.create((this.messagePrototype!)); + message.grids = []; + message.moved = false; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MoveGridCallHereResult): MoveGridCallHereResult { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated Grid grids */ 1: + message.grids.push(Grid.internalBinaryRead(reader, reader.uint32(), options)); + break; + case /* optional GridConnectionCredentials connection */ 2: + message.connection = GridConnectionCredentials.internalBinaryRead(reader, reader.uint32(), options, message.connection); + break; + case /* optional GridCurrentCall current_call */ 3: + message.currentCall = GridCurrentCall.internalBinaryRead(reader, reader.uint32(), options, message.currentCall); + break; + case /* bool moved */ 4: + message.moved = reader.bool(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MoveGridCallHereResult, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated Grid grids = 1; */ + for (let i = 0; i < message.grids.length; i++) + Grid.internalBinaryWrite(message.grids[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + /* optional GridConnectionCredentials connection = 2; */ + if (message.connection) + GridConnectionCredentials.internalBinaryWrite(message.connection, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* optional GridCurrentCall current_call = 3; */ + if (message.currentCall) + GridCurrentCall.internalBinaryWrite(message.currentCall, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + /* bool moved = 4; */ + if (message.moved !== false) + writer.tag(4, WireType.Varint).bool(message.moved); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MoveGridCallHereResult + */ +export const MoveGridCallHereResult = new MoveGridCallHereResult$Type(); +// @generated message type with reflection information, may provide speed optimized methods class SetGridRoomTitleInput$Type extends MessageType { constructor() { super("SetGridRoomTitleInput", [ @@ -29381,7 +29873,8 @@ class PrepareGridConnectionInput$Type extends MessageType): PrepareGridConnectionInput { @@ -29403,6 +29896,9 @@ class PrepareGridConnectionInput$Type extends MessageType): SetGridAvatarMicrophoneEnabledInput { @@ -29512,6 +30012,9 @@ class SetGridAvatarMicrophoneEnabledInput$Type extends MessageType { + test("Move keeps its allocation and exact ownership fence through binary encoding", () => { + expect(P.Method.MOVE_GRID_CALL_HERE).toBe(146) + expect(P.RpcCall.fields.find((field) => field.localName === "moveGridCallHere")?.no).toBe(147) + expect(P.RpcResult.fields.find((field) => field.localName === "moveGridCallHere")?.no).toBe(147) + + const call = P.RpcCall.fromJson({ + method: "MOVE_GRID_CALL_HERE", + moveGridCallHere: { callId, expectedMembershipId: membershipId }, + }) + expect(P.RpcCall.fromBinary(P.RpcCall.toBinary(call))).toEqual(call) + + for (const moved of [false, true]) { + const result = P.RpcResult.fromJson({ + reqMsgId: "9007199254740993", + moveGridCallHere: { + moved, + grids: [{ spaceId: "9007199254740995", callTransferEnabled: true }], + currentCall: { + callId, + membershipId, + spaceId: "9007199254740995", + roomId: "9007199254740997", + ownedByCurrentSession: moved, + ownerClientType: "ios", + }, + ...(moved ? { connection: { + serverUrl: "wss://fixture.invalid", + participantIdentity: "fixture-participant", + token: "public-fixture-token", + expiresAt: "9007199254740999", + callId, + membershipId, + } } : {}), + }, + }) + expect(P.RpcResult.fromBinary(P.RpcResult.toBinary(result))).toEqual(result) + expect(result.result.oneofKind).toBe("moveGridCallHere") + } + }) + + test("an omitted renewal fence remains distinct from explicit no-presence", () => { + const legacy = P.GetGridInput.fromJson({ spaceId: "9007199254740995" }) + const expectNoPresence = P.GetGridInput.fromJson({ + spaceId: "9007199254740995", + expectedMembershipId: "", + }) + const decodedLegacy = P.GetGridInput.fromBinary(P.GetGridInput.toBinary(legacy)) + const decodedFenced = P.GetGridInput.fromBinary(P.GetGridInput.toBinary(expectNoPresence)) + expect(decodedLegacy.expectedMembershipId).toBeUndefined() + expect(decodedFenced.expectedMembershipId).toBe("") + expect(P.GetGridInput.toBinary(legacy)).not.toEqual(P.GetGridInput.toBinary(expectNoPresence)) + }) +}) diff --git a/proto/core.proto b/proto/core.proto index e3269be83..bf7d0a8c7 100644 --- a/proto/core.proto +++ b/proto/core.proto @@ -1621,6 +1621,7 @@ enum Method { REQUEST_BOT_FILESYSTEM = 143; ANSWER_BOT_FILESYSTEM = 144; SET_SPACE_PHOTO = 145; + MOVE_GRID_CALL_HERE = 146; OPEN_GRID_THREAD = 147; SET_GRID_TRANSCRIPTION = 148; LIST_GRID_TRANSCRIPTS = 149; @@ -1774,6 +1775,7 @@ message RpcCall { RequestBotFilesystemInput requestBotFilesystem = 144; AnswerBotFilesystemInput answerBotFilesystem = 145; SetSpacePhotoInput setSpacePhoto = 146; + MoveGridCallHereInput moveGridCallHere = 147; OpenGridThreadInput openGridThread = 148; SetGridTranscriptionInput setGridTranscription = 149; ListGridTranscriptsInput listGridTranscripts = 150; @@ -1929,6 +1931,7 @@ message RpcResult { RequestBotFilesystemResult requestBotFilesystem = 144; AnswerBotFilesystemResult answerBotFilesystem = 145; SetSpacePhotoResult setSpacePhoto = 146; + MoveGridCallHereResult moveGridCallHere = 147; OpenGridThreadResult openGridThread = 148; SetGridTranscriptionResult setGridTranscription = 149; ListGridTranscriptsResult listGridTranscripts = 150; @@ -2167,6 +2170,22 @@ message Grid { // Server-owned monotonic revision for this Space's complete Grid snapshot. // Clients must not replace a newer snapshot with a lower revision. int64 revision = 5; + // Effective server capability, including ownership fence enforcement. + // New clients must not admit media through an older/disabled server. + bool call_transfer_enabled = 6; +} + +// Personalized control ownership, disclosed only to the authenticated user. +// call_id stays stable across an explicit device transfer; membership_id is +// replaced on each transfer/resume and fences commands and media credentials. +message GridCurrentCall { + string call_id = 1; + int64 space_id = 2; + int64 room_id = 3; + string membership_id = 4; + bool owned_by_current_session = 5; + // Coarse existing session client kind (ios, macos, etc.), not a device ID. + string owner_client_type = 6; } // Short-lived, session-targeted LiveKit credentials. Never persist or include @@ -2177,11 +2196,21 @@ message GridConnectionCredentials { string participant_identity = 3; string token = 4; int64 expires_at = 5; + string call_id = 6; + string membership_id = 7; } -message GetGridInput { int64 space_id = 1; } +message GetGridInput { + int64 space_id = 1; + // Explicit renewal by the admitted owner. Omit for read-only browsing when + // call-transfer enforcement is active. A stale fence never renews presence. + optional string expected_membership_id = 2; +} -message GetGridResult { Grid grid = 1; } +message GetGridResult { + Grid grid = 1; + optional GridCurrentCall current_call = 2; +} // Home-level discovery for every Grid-enabled Space the current user can see. // This is a bounded summary: full rooms remain available only through GetGrid. @@ -2194,31 +2223,66 @@ message GridHomeSpace { message GetGridHomeInput {} -message GetGridHomeResult { repeated GridHomeSpace spaces = 1; } +message GetGridHomeResult { + repeated GridHomeSpace spaces = 1; + optional GridCurrentCall current_call = 2; + bool call_transfer_enabled = 3; +} message CreateGridRoomInput { int64 space_id = 1; optional bool microphone_enabled = 2; + // Required when call_transfer_enabled: empty expects no active presence; + // nonempty must match the current session's live membership exactly. + optional string expected_membership_id = 3; } message CreateGridRoomResult { repeated Grid grids = 1; optional GridConnectionCredentials connection = 2; + // Successful claims report the projection captured at commit. A newer + // accepted Grid/self state wins; this field alone cannot admit media. + optional GridCurrentCall current_call = 3; } message JoinGridRoomInput { int64 room_id = 1; optional bool microphone_enabled = 2; + optional string expected_membership_id = 3; } message JoinGridRoomResult { repeated Grid grids = 1; optional GridConnectionCredentials connection = 2; + optional GridCurrentCall current_call = 3; +} + +message LeaveGridRoomInput { + int64 expected_room_id = 1; + optional string expected_membership_id = 2; } -message LeaveGridRoomInput { int64 expected_room_id = 1; } +message LeaveGridRoomResult { + repeated Grid grids = 1; + // Display/repair only; leaving never grants a replacement admission. + optional GridCurrentCall current_call = 2; +} -message LeaveGridRoomResult { repeated Grid grids = 1; } +// Explicitly move this user's existing room participation to the authenticated +// calling session (also used for a same-login cold-process Resume). +message MoveGridCallHereInput { + string call_id = 1; + string expected_membership_id = 2; +} + +message MoveGridCallHereResult { + repeated Grid grids = 1; + optional GridConnectionCredentials connection = 2; + // Captured at the claim's commit when moved=true; display/repair otherwise. + optional GridCurrentCall current_call = 3; + // False means the expected call/membership changed; no claim was performed. + bool moved = 4; +} message SetGridRoomTitleInput { int64 room_id = 1; @@ -2242,6 +2306,7 @@ message DeleteGridRoomResult { Grid grid = 1; } message PrepareGridConnectionInput { int64 room_id = 1; int32 generation = 2; + optional string expected_membership_id = 3; } enum GridConnectionUnavailableReason { @@ -2258,6 +2323,7 @@ message PrepareGridConnectionResult { message SetGridAvatarMicrophoneEnabledInput { int64 expected_room_id = 1; bool enabled = 2; + optional string expected_membership_id = 3; } message SetGridAvatarMicrophoneEnabledResult { bool enabled = 1; } From f8bad335bc21f1ea8f6134c6c3299aa0ab99fdd0 Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Tue, 6 Oct 2026 18:47:24 +0330 Subject: [PATCH 06/14] server: preserve grid call identity across device transfers --- .../functions/grid.callTransfer.test.ts | 656 ++++++++++++++++++ server/src/__tests__/functions/grid.test.ts | 1 + .../__tests__/modules/grid.livekit.test.ts | 25 + server/src/env.ts | 7 + server/src/functions/_functions.ts | 2 + server/src/functions/grid.ts | 301 +++++++- server/src/functions/gridTranscription.ts | 12 +- server/src/modules/grid/avatarState.ts | 4 + server/src/modules/grid/livekit.ts | 6 + .../grid/transcription/control.test.ts | 33 +- .../redis.integration.test.ts | 14 +- .../modules/internalMessaging/schemas.test.ts | 14 +- .../src/modules/internalMessaging/schemas.ts | 10 +- server/src/realtime/handlers/_rpc.ts | 7 + server/src/realtime/handlers/grid.ts | 7 + 15 files changed, 1059 insertions(+), 40 deletions(-) create mode 100644 server/src/__tests__/functions/grid.callTransfer.test.ts diff --git a/server/src/__tests__/functions/grid.callTransfer.test.ts b/server/src/__tests__/functions/grid.callTransfer.test.ts new file mode 100644 index 000000000..8234504c9 --- /dev/null +++ b/server/src/__tests__/functions/grid.callTransfer.test.ts @@ -0,0 +1,656 @@ +import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test" +import { randomUUID } from "node:crypto" +import { db } from "@in/server/db" +import { + gridPresence, + gridProviderEffects, + gridTranscriptionRuns, + gridTranscriptionSegments, + members, + messages, +} from "@in/server/db/schema" +import { waitForPostCommitHooks } from "@in/server/db/commitHooks" +import { + createGridRoom, + getGrid, + getGridHome, + joinGridRoom, + leaveGridRoom, + moveGridCallHere, + prepareGridConnection, + setGridAvatarMicrophoneEnabled, + setGridRoomTitle, +} from "@in/server/functions/grid" +import { toggleSpaceGrid } from "@in/server/functions/space.settings" +import { setGridTranscription } from "@in/server/functions/gridTranscription" +import * as livekit from "@in/server/modules/grid/livekit" +import { handleGridTranscriptionWorkerRequest } from "@in/server/modules/grid/transcription/worker" +import { GridConnectionUnavailableReason, GridTranscriptDestination } from "@inline-chat/protocol/core" +import { eq } from "drizzle-orm" +import { setupTestLifecycle, testUtils } from "../setup" + +describe("Grid call ownership", () => { + setupTestLifecycle() + const originalActivation = process.env["GRID_CALL_TRANSFER_ENABLED"] + beforeEach(() => { + process.env["GRID_CALL_TRANSFER_ENABLED"] = "true" + }) + afterEach(() => { + process.env["GRID_CALL_TRANSFER_ENABLED"] = originalActivation ?? "" + }) + + test("defaults off without changing legacy ownership or renewal, and checks supplied fences", async () => { + process.env["GRID_CALL_TRANSFER_ENABLED"] = "" + const { space, contexts } = await fixture(1) + const created = await createGridRoom({ spaceId: BigInt(space.id) }, contexts[0]!) + expect(created.grids[0]?.callTransferEnabled).toBe(false) + expect((await getGridHome({}, contexts[0]!)).callTransferEnabled).toBe(false) + const call = created.currentCall! + await expect( + moveGridCallHere({ callId: call.callId, expectedMembershipId: call.membershipId }, contexts[0]!), + ).rejects.toThrow() + await expect( + setGridAvatarMicrophoneEnabled( + { + expectedRoomId: call.roomId, + expectedMembershipId: randomUUID(), + enabled: true, + }, + contexts[0]!, + ), + ).rejects.toThrow() + const shortenedLease = new Date(Date.now() + 60_000) + await db + .update(gridPresence) + .set({ leaseExpiresAt: shortenedLease }) + .where(eq(gridPresence.userId, contexts[0]!.currentUserId)) + await getGrid({ spaceId: BigInt(space.id) }, contexts[0]!) + expect((await ownPresence(contexts[0]!)).leaseExpiresAt.getTime()).toBeGreaterThan(shortenedLease.getTime()) + }) + + test("activation rejects legacy mutations and Prepare, including an unfenced initial join", async () => { + const { space, contexts } = await fixture(2) + await expect(createGridRoom({ spaceId: BigInt(space.id) }, contexts[0]!)).rejects.toThrow() + const created = await createGridRoom({ spaceId: BigInt(space.id), expectedMembershipId: "" }, contexts[0]!) + const call = created.currentCall! + await expect(joinGridRoom({ roomId: call.roomId }, contexts[1]!)).rejects.toThrow() + const joined = await joinGridRoom({ roomId: call.roomId, expectedMembershipId: "" }, contexts[1]!) + const generation = joined.grids[0]!.rooms[0]!.connection!.generation + await expect(joinGridRoom({ roomId: call.roomId }, contexts[0]!)).rejects.toThrow() + await expect(createGridRoom({ spaceId: BigInt(space.id) }, contexts[0]!)).rejects.toThrow() + await expect( + setGridAvatarMicrophoneEnabled({ expectedRoomId: call.roomId, enabled: true }, contexts[0]!), + ).rejects.toThrow() + await expect(leaveGridRoom({ expectedRoomId: call.roomId }, contexts[0]!)).rejects.toThrow() + await expect(prepareGridConnection({ roomId: call.roomId, generation }, contexts[0]!)).rejects.toThrow() + expect((await ownPresence(contexts[0]!)).mediaMembershipId).toBe(call.membershipId) + }) + + test("OFF exact-fenced legacy Join can replace a foreign device while stale Join/Create remain rejected", async () => { + const { space, contexts, created, targetContext, generation } = await activeCall() + process.env["GRID_CALL_TRANSFER_ENABLED"] = "" + const old = created.currentCall! + const joined = await joinGridRoom( + { roomId: old.roomId, expectedMembershipId: old.membershipId }, + targetContext, + ) + const current = joined.currentCall! + expect((await ownPresence(contexts[0]!)).ownerSessionId).toBe(targetContext.currentSessionId) + expect(current.membershipId).not.toBe(old.membershipId) + // OFF retains legacy Join semantics; stable call identity belongs to Move. + expect(current.callId).not.toBe(old.callId) + expect(joined.grids[0]!.rooms[0]!.connection!.generation).not.toBe(generation) + const effects = await db.select().from(gridProviderEffects) + expect( + effects.some( + (effect) => effect.kind === "revoke_participant" && + effect.participantIdentity === `inline-grid-user-${contexts[0]!.currentUserId}-${old.membershipId}`, + ), + ).toBe(true) + expect(effects.some((effect) => effect.kind === "close_connection" && effect.connectionGeneration === generation)).toBe(true) + await expect(joinGridRoom({ roomId: old.roomId, expectedMembershipId: old.membershipId }, contexts[0]!)).rejects.toThrow() + await expect(createGridRoom({ spaceId: BigInt(space.id), expectedMembershipId: old.membershipId }, contexts[0]!)).rejects.toThrow() + expect((await ownPresence(contexts[0]!)).mediaMembershipId).toBe(current.membershipId) + }) + + test("OFF exact-fenced legacy Create can replace a foreign device without weakening its CAS", async () => { + const { space, contexts, created, targetContext } = await activeCall() + process.env["GRID_CALL_TRANSFER_ENABLED"] = "" + const old = created.currentCall! + const claimed = await createGridRoom( + { spaceId: BigInt(space.id), expectedMembershipId: old.membershipId }, + targetContext, + ) + const current = claimed.currentCall! + expect((await ownPresence(contexts[0]!)).ownerSessionId).toBe(targetContext.currentSessionId) + expect(current.roomId).not.toBe(old.roomId) + expect(current.membershipId).not.toBe(old.membershipId) + await expect(createGridRoom({ spaceId: BigInt(space.id), expectedMembershipId: old.membershipId }, contexts[0]!)).rejects.toThrow() + await expect(joinGridRoom({ roomId: old.roomId, expectedMembershipId: old.membershipId }, contexts[0]!)).rejects.toThrow() + expect((await ownPresence(contexts[0]!)).mediaMembershipId).toBe(current.membershipId) + }) + + test("OFF foreign exact fences cannot Leave, mute, Prepare, or renew; ON claims still require the owning device", async () => { + const { space, contexts, created, targetContext, generation } = await activeCall() + process.env["GRID_CALL_TRANSFER_ENABLED"] = "" + const call = created.currentCall! + const foreignInput = { expectedRoomId: call.roomId, expectedMembershipId: call.membershipId } + expect((await leaveGridRoom(foreignInput, targetContext)).grids).toEqual([]) + await expect(setGridAvatarMicrophoneEnabled({ ...foreignInput, enabled: true }, targetContext)).rejects.toThrow() + const prepared = await prepareGridConnection( + { roomId: call.roomId, generation, expectedMembershipId: call.membershipId }, + targetContext, + ) + expect(prepared.connection).toBeUndefined() + expect(prepared.unavailableReason).toBe(GridConnectionUnavailableReason.NOT_ACTIVE) + const shortenedLease = new Date(Date.now() + 60_000) + await db.update(gridPresence).set({ leaseExpiresAt: shortenedLease }).where(eq(gridPresence.userId, contexts[0]!.currentUserId)) + await getGrid({ spaceId: BigInt(space.id), expectedMembershipId: call.membershipId }, targetContext) + expect((await ownPresence(contexts[0]!)).leaseExpiresAt).toEqual(shortenedLease) + process.env["GRID_CALL_TRANSFER_ENABLED"] = "true" + await expect(joinGridRoom({ roomId: call.roomId, expectedMembershipId: call.membershipId }, targetContext)).rejects.toThrow() + await expect(createGridRoom({ spaceId: BigInt(space.id), expectedMembershipId: call.membershipId }, targetContext)).rejects.toThrow() + const unchanged = await ownPresence(contexts[0]!) + expect(unchanged.ownerSessionId).toBe(contexts[0]!.currentSessionId) + expect(unchanged.mediaMembershipId).toBe(call.membershipId) + }) + + test("browsing is read-only; only an exact admitted owner heartbeat renews", async () => { + const { space, contexts } = await fixture(1) + const created = await createGridRoom({ spaceId: BigInt(space.id), expectedMembershipId: "" }, contexts[0]!) + const shortenedLease = new Date(Date.now() + 60_000) + await db + .update(gridPresence) + .set({ leaseExpiresAt: shortenedLease }) + .where(eq(gridPresence.userId, contexts[0]!.currentUserId)) + await getGrid({ spaceId: BigInt(space.id) }, contexts[0]!) + await getGridHome({}, contexts[0]!) + await getGrid({ spaceId: BigInt(space.id), expectedMembershipId: randomUUID() }, contexts[0]!) + expect((await ownPresence(contexts[0]!)).leaseExpiresAt).toEqual(shortenedLease) + await getGrid({ spaceId: BigInt(space.id), expectedMembershipId: created.currentCall!.membershipId }, contexts[0]!) + expect((await ownPresence(contexts[0]!)).leaseExpiresAt.getTime()).toBeGreaterThan(shortenedLease.getTime()) + }) + + test("moves one stable presence to another device and isolates self-hosted media generations", async () => { + const { contexts, created, generation, targetContext } = await activeCall() + const before = await ownPresence(contexts[0]!) + await setGridAvatarMicrophoneEnabled( + { + expectedRoomId: created.currentCall!.roomId, + expectedMembershipId: before.mediaMembershipId, + enabled: true, + }, + contexts[0]!, + ) + const moved = await moveGridCallHere( + { callId: before.callId, expectedMembershipId: before.mediaMembershipId }, + targetContext, + ) + const after = await ownPresence(contexts[0]!) + expect(moved.moved).toBe(true) + expect(after.callId).toBe(before.callId) + expect(after.roomId).toBe(before.roomId) + expect(after.joinedAt).toEqual(before.joinedAt) + expect(after.ownerSessionId).toBe(targetContext.currentSessionId) + expect(after.mediaMembershipId).not.toBe(before.mediaMembershipId) + expect(after.microphoneEnabled).toBe(false) + expect(after.microphoneRevision).toBe(0) + expect(moved.currentCall).toMatchObject({ + callId: before.callId, + membershipId: after.mediaMembershipId, + ownedByCurrentSession: true, + ownerClientType: "ios", + }) + expect(moved.grids[0]!.rooms[0]!.avatars).toHaveLength(2) + expect(moved.grids[0]!.rooms[0]!.connection!.generation).toBe(generation + 1) + const oldView = await getGridHome({}, contexts[0]!) + expect(oldView.currentCall?.ownedByCurrentSession).toBe(false) + expect(oldView.currentCall?.membershipId).toBe(after.mediaMembershipId) + const effects = await db.select().from(gridProviderEffects) + expect( + effects.some( + (effect) => + effect.kind === "revoke_participant" && + effect.participantIdentity === `inline-grid-user-${contexts[0]!.currentUserId}-${before.mediaMembershipId}`, + ), + ).toBe(true) + expect( + effects.some((effect) => effect.kind === "close_connection" && effect.connectionGeneration === generation), + ).toBe(true) + }) + + test("concurrent transfer intents claim the observed membership only once", async () => { + const { contexts, created, targetContext } = await activeCall() + const otherSession = await testUtils.createSessionForUser(contexts[0]!.currentUserId, { clientType: "macos" }) + const otherContext = testUtils.functionContext({ + userId: contexts[0]!.currentUserId, + sessionId: otherSession.session.id, + }) + const input = { callId: created.currentCall!.callId, expectedMembershipId: created.currentCall!.membershipId } + const results = await Promise.all([moveGridCallHere(input, targetContext), moveGridCallHere(input, otherContext)]) + expect(results.filter((result) => result.moved)).toHaveLength(1) + const presence = await ownPresence(contexts[0]!) + const winnerContext = results[0]!.moved ? targetContext : otherContext + expect(presence.ownerSessionId).toBe(winnerContext.currentSessionId) + expect(presence.callId).toBe(input.callId) + expect(await db.select().from(gridPresence)).toHaveLength(2) + }) + + test("A to B to A cannot apply A's old Leave, mute, join, create, renew, Prepare, or move", async () => { + const { space, contexts, created, targetContext } = await activeCall() + const old = created.currentCall! + const toPhone = await moveGridCallHere( + { callId: old.callId, expectedMembershipId: old.membershipId }, + targetContext, + ) + const back = await moveGridCallHere( + { callId: old.callId, expectedMembershipId: toPhone.currentCall!.membershipId }, + contexts[0]!, + ) + const current = back.currentCall! + const generation = back.grids[0]!.rooms[0]!.connection!.generation + expect(current.membershipId).not.toBe(old.membershipId) + expect( + (await leaveGridRoom({ expectedRoomId: old.roomId, expectedMembershipId: old.membershipId }, contexts[0]!)).grids, + ).toEqual([]) + await expect( + setGridAvatarMicrophoneEnabled( + { expectedRoomId: old.roomId, expectedMembershipId: old.membershipId, enabled: true }, + contexts[0]!, + ), + ).rejects.toThrow() + await expect( + joinGridRoom({ roomId: old.roomId, expectedMembershipId: old.membershipId }, contexts[0]!), + ).rejects.toThrow() + await expect( + createGridRoom({ spaceId: BigInt(space.id), expectedMembershipId: old.membershipId }, contexts[0]!), + ).rejects.toThrow() + const prepared = await prepareGridConnection( + { roomId: old.roomId, generation, expectedMembershipId: old.membershipId }, + contexts[0]!, + ) + expect(prepared.connection).toBeUndefined() + expect(prepared.unavailableReason).toBe(GridConnectionUnavailableReason.NOT_ACTIVE) + const shortenedLease = new Date(Date.now() + 60_000) + await db + .update(gridPresence) + .set({ leaseExpiresAt: shortenedLease }) + .where(eq(gridPresence.userId, contexts[0]!.currentUserId)) + await getGrid({ spaceId: BigInt(space.id), expectedMembershipId: old.membershipId }, contexts[0]!) + expect((await ownPresence(contexts[0]!)).leaseExpiresAt).toEqual(shortenedLease) + const staleMove = await moveGridCallHere( + { callId: old.callId, expectedMembershipId: old.membershipId }, + targetContext, + ) + expect(staleMove.moved).toBe(false) + expect(staleMove.connection).toBeUndefined() + expect((await ownPresence(contexts[0]!)).mediaMembershipId).toBe(current.membershipId) + }) + + test("explicit same-login cold Resume replaces ownership; retrying its old CAS does not replace it again", async () => { + const { contexts, created } = await activeCall() + const before = await ownPresence(contexts[0]!) + const input = { callId: created.currentCall!.callId, expectedMembershipId: before.mediaMembershipId } + const resumed = await moveGridCallHere(input, contexts[0]!) + expect(resumed.moved).toBe(true) + expect(resumed.currentCall!.callId).toBe(before.callId) + expect(resumed.currentCall!.membershipId).not.toBe(before.mediaMembershipId) + expect((await ownPresence(contexts[0]!)).joinedAt).toEqual(before.joinedAt) + const retried = await moveGridCallHere(input, contexts[0]!) + expect(retried.moved).toBe(false) + expect(retried.currentCall!.membershipId).toBe(resumed.currentCall!.membershipId) + }) + + test("Leave ends the call episode even when the named room is reused", async () => { + const { space, contexts } = await fixture(1) + const created = await createGridRoom({ spaceId: BigInt(space.id), expectedMembershipId: "" }, contexts[0]!) + const old = created.currentCall! + await setGridRoomTitle({ roomId: old.roomId, title: "Persistent room" }, contexts[0]!) + const left = await leaveGridRoom({ expectedRoomId: old.roomId, expectedMembershipId: old.membershipId }, contexts[0]!) + expect(left.currentCall).toBeUndefined() + const rejoined = await joinGridRoom({ roomId: old.roomId, expectedMembershipId: "" }, contexts[0]!) + expect(rejoined.currentCall!.callId).not.toBe(old.callId) + const staleMove = await moveGridCallHere({ callId: old.callId, expectedMembershipId: rejoined.currentCall!.membershipId }, contexts[0]!) + expect(staleMove.moved).toBe(false) + expect((await ownPresence(contexts[0]!)).callId).toBe(rejoined.currentCall!.callId) + }) + + test("an expired call cannot be moved or renewed back into existence", async () => { + const { space, contexts, created, targetContext } = await activeCall() + const old = created.currentCall! + await db.update(gridPresence).set({ leaseExpiresAt: new Date(Date.now() - 1) }).where(eq(gridPresence.userId, contexts[0]!.currentUserId)) + const moved = await moveGridCallHere({ callId: old.callId, expectedMembershipId: old.membershipId }, targetContext) + expect(moved.moved).toBe(false) + expect(moved.currentCall).toBeUndefined() + expect(moved.connection).toBeUndefined() + await getGrid({ spaceId: BigInt(space.id), expectedMembershipId: old.membershipId }, contexts[0]!) + expect(await db.select().from(gridPresence).where(eq(gridPresence.userId, contexts[0]!.currentUserId))).toEqual([]) + }) + + test("Home returns self ownership independently of its capped public avatars", async () => { + const { space, contexts } = await fixture(5) + const created = await createGridRoom({ spaceId: BigInt(space.id), expectedMembershipId: "" }, contexts[0]!) + for (const context of contexts.slice(1)) { + await joinGridRoom({ roomId: created.currentCall!.roomId, expectedMembershipId: "" }, context) + } + const home = await getGridHome({}, contexts[0]!) + expect(home.spaces[0]!.recentAvatars).toHaveLength(4) + expect(home.currentCall?.callId).toBe(created.currentCall!.callId) + expect(home.currentCall?.ownerClientType).toBe("macos") + const otherHome = await getGridHome({}, contexts[1]!) + expect(otherHome.currentCall?.callId).not.toBe(created.currentCall!.callId) + expect(otherHome.currentCall?.ownedByCurrentSession).toBe(true) + expect(otherHome.currentCall).not.toHaveProperty("ownerSessionId") + expect(otherHome.currentCall).not.toHaveProperty("deviceId") + }) + + test("a delayed mint cannot return credentials for newer ownership on the same login", async () => { + const { contexts, created, targetContext } = await activeCall() + const enteredMint = Promise.withResolvers() + const releaseMint = Promise.withResolvers() + const pending = moveGridCallHere( + { + callId: created.currentCall!.callId, + expectedMembershipId: created.currentCall!.membershipId, + }, + targetContext, + async (input) => { + enteredMint.resolve() + await releaseMint.promise + return { + connection: input.connection, + serverUrl: "wss://grid.example.test", + participantIdentity: input.participantIdentity!, + token: "test-only-credential", + expiresAt: BigInt(Math.floor(Date.now() / 1000) + 300), + callId: input.callId!, + membershipId: input.mediaMembershipId!, + } + }, + ) + await enteredMint.promise + const firstPhoneMembership = (await ownPresence(contexts[0]!)).mediaMembershipId + let latestPhoneMembership = firstPhoneMembership + try { + const toMac = await moveGridCallHere( + { callId: created.currentCall!.callId, expectedMembershipId: firstPhoneMembership }, + contexts[0]!, + ) + const toPhoneAgain = await moveGridCallHere( + { callId: created.currentCall!.callId, expectedMembershipId: toMac.currentCall!.membershipId }, + targetContext, + ) + latestPhoneMembership = toPhoneAgain.currentCall!.membershipId + } finally { + releaseMint.resolve() + } + const oldResult = await pending + expect(oldResult.moved).toBe(true) + expect(oldResult.currentCall!.membershipId).toBe(firstPhoneMembership) + expect(oldResult.currentCall!.membershipId).not.toBe(latestPhoneMembership) + expect(oldResult.connection).toBeUndefined() + expect((await ownPresence(targetContext)).mediaMembershipId).toBe(latestPhoneMembership) + }) +}) + +describe("Grid call transfer transcription authority", () => { + setupTestLifecycle() + const secret = "grid-transfer-test-secret-0123456789012345" + const original = new Map() + let providerSpy: ReturnType> | undefined + + beforeEach(() => { + for (const [key, value] of Object.entries({ + GRID_CALL_TRANSFER_ENABLED: "true", + GRID_TRANSCRIPTION_ENABLED: "true", + GRID_TRANSCRIPTION_WORKER_SECRET: secret, + GRID_TRANSCRIPTION_MODEL: "meeting", + GRID_TRANSCRIPTION_ALLOWED_CLIENT_VERSIONS: "qualified-transfer-test", + })) { + original.set(key, process.env[key]) + process.env[key] = value + } + }) + afterEach(async () => { + await waitForPostCommitHooks() + providerSpy?.mockRestore() + providerSpy = undefined + for (const [key, value] of original) { + if (value === undefined) delete process.env[key] + else process.env[key] = value + } + original.clear() + }) + + async function workerApi(action: string, body: unknown, token = secret) { + return handleGridTranscriptionWorkerRequest( + new Request(`http://localhost/_internal/grid-transcription/${action}`, { + method: "POST", + headers: { authorization: `Bearer ${token}`, "content-type": "application/json" }, + body: JSON.stringify(body), + }), + ) + } + + for (const provider of ["self_hosted", "cloud"] as const) { + for (const sameLogin of [false, true]) { + const replacement = sameLogin ? "same-login Resume" : "device Move" + test(`${provider} ${replacement} discards admitted speech before ownership replacement`, async () => { + providerSpy = spyOn(livekit, "getLiveKitGridConfig").mockReturnValue({ + serverUrl: "wss://grid-transfer.example.test", + apiKey: "transfer-test-key", + apiSecret: "transfer-test-secret-0123456789", + provider, + }) + const { space, contexts } = await fixture(2, "qualified-transfer-test") + const owner = contexts[0]! + const created = await createGridRoom( + { spaceId: BigInt(space.id), expectedMembershipId: "", microphoneEnabled: true }, + owner, + ) + const roomId = created.currentCall!.roomId + await joinGridRoom({ roomId, expectedMembershipId: "", microphoneEnabled: true }, contexts[1]!) + const before = await ownPresence(owner) + const target = sameLogin + ? owner + : testUtils.functionContext({ + userId: owner.currentUserId, + sessionId: ( + await testUtils.createSessionForUser(owner.currentUserId, { + clientType: "macos", + clientVersion: "qualified-transfer-test", + }) + ).session.id, + }) + const currentRoom = async (context: typeof owner) => { + const result = await getGrid({ spaceId: BigInt(space.id) }, context) + return result.grid!.rooms.find((room) => room.id === roomId)! + } + const start = async (context: typeof owner) => { + const room = await currentRoom(context) + const presence = await ownPresence(context) + return setGridTranscription( + { + roomId, + enabled: true, + requestId: randomUUID(), + expectedMembershipId: presence.mediaMembershipId, + expectedGeneration: room.connection!.generation, + expectedRunId: room.transcription?.runId, + expectedRevision: room.transcription?.revision ?? 0, + destination: GridTranscriptDestination.GRID_TRANSCRIPT_NEW, + }, + context, + ) + } + const generation = (await currentRoom(owner)).connection!.generation + const heartbeat = await workerApi("heartbeat", { + workerId: "grid-transfer-test", + model: "meeting", + ready: true, + }) + expect(heartbeat.status).toBe(200) + await start(owner) + const claimed = await workerApi("claim", { workerId: "grid-transfer-test" }) + expect(claimed.status).toBe(200) + const claim = (await claimed.json()) as { + runId: string + runToken: string + participants: { identity: string; userId: number; membershipId: string }[] + } + const participant = claim.participants.find((speaker) => speaker.userId === owner.currentUserId)! + expect(participant.membershipId).toBe(before.mediaMembershipId) + const admission = { + participantIdentity: participant.identity, + trackSid: "TR-transfer-old", + sourceTurnKey: "pending-before-move", + } + const admitted = await workerApi("admit", admission, claim.runToken) + expect(admitted.status).toBe(200) + const ticket = (await admitted.json()) as { segmentId: string } + const peer = claim.participants.find((speaker) => speaker.userId === contexts[1]!.currentUserId)! + const admittedPeer = await workerApi( + "admit", + { participantIdentity: peer.identity, trackSid: "TR-transfer-peer", sourceTurnKey: "peer-before-move" }, + claim.runToken, + ) + expect(admittedPeer.status).toBe(200) + const peerTicket = (await admittedPeer.json()) as { segmentId: string } + const run = ( + await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, claim.runId)) + )[0]! + const priorMessages = await db.select().from(messages).where(eq(messages.chatId, run.transcriptChatId)) + + const moved = await moveGridCallHere( + { callId: before.callId, expectedMembershipId: before.mediaMembershipId }, + target, + ) + const after = await ownPresence(target) + expect(moved.moved).toBe(true) + expect(after.callId).toBe(before.callId) + expect(after.roomId).toBe(before.roomId) + expect(after.joinedAt).toEqual(before.joinedAt) + expect(after.ownerSessionId).toBe(target.currentSessionId) + expect(after.mediaMembershipId).not.toBe(before.mediaMembershipId) + expect(after.microphoneEnabled).toBe(false) + expect((await currentRoom(target)).connection!.generation).toBe( + generation + (provider === "self_hosted" ? 1 : 0), + ) + const stoppedRun = ( + await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, claim.runId)) + )[0] + expect(stoppedRun).toMatchObject({ + state: "stopping", + interruptionReason: provider === "self_hosted" ? "generation_changed" : "authority_lost", + }) + const oldSegment = ( + await db.select().from(gridTranscriptionSegments).where(eq(gridTranscriptionSegments.id, ticket.segmentId)) + )[0] + expect(oldSegment?.state).toBe("discarded") + expect( + (await db.select().from(gridTranscriptionSegments).where(eq(gridTranscriptionSegments.id, peerTicket.segmentId)))[0]?.state, + ).toBe("discarded") + expect((await workerApi("admit", { ...admission, sourceTurnKey: "after-move" }, claim.runToken)).status).toBe(409) + const final = await workerApi( + "final", + { segmentId: ticket.segmentId, text: "Late old membership" }, + claim.runToken, + ) + expect(final.status).toBe(200) + expect(await final.json()).toEqual({ messageId: null }) + expect( + await (await workerApi("final", { segmentId: peerTicket.segmentId, text: "Late peer turn" }, claim.runToken)).json(), + ).toEqual({ messageId: null }) + expect(await db.select().from(messages).where(eq(messages.chatId, run.transcriptChatId))).toEqual(priorMessages) + expect(await (await workerApi("renew", {}, claim.runToken)).json()).toMatchObject({ + state: "stopping", + allowFinalFlush: false, + }) + const staleCredentials = await prepareGridConnection( + { roomId, generation, expectedMembershipId: before.mediaMembershipId }, + owner, + ) + expect(staleCredentials.connection).toBeUndefined() + expect(staleCredentials.unavailableReason).toBe(GridConnectionUnavailableReason.NOT_ACTIVE) + const freshCredentials = await prepareGridConnection( + { + roomId, + generation: (await currentRoom(target)).connection!.generation, + expectedMembershipId: after.mediaMembershipId, + }, + target, + ) + expect(freshCredentials.connection).toMatchObject({ + callId: before.callId, + membershipId: after.mediaMembershipId, + connection: { generation: generation + (provider === "self_hosted" ? 1 : 0) }, + }) + await expect(start(target)).rejects.toThrow() + expect((await workerApi("stopped", {}, claim.runToken)).status).toBe(200) + await start(target) + const newClaimResponse = await workerApi("claim", { workerId: "grid-transfer-test" }) + expect(newClaimResponse.status).toBe(200) + const newClaim = (await newClaimResponse.json()) as typeof claim + expect(newClaim.runId).not.toBe(claim.runId) + const newParticipant = newClaim.participants.find((speaker) => speaker.userId === owner.currentUserId)! + expect(newParticipant.membershipId).toBe(after.mediaMembershipId) + const newAdmission = await workerApi( + "admit", + { participantIdentity: newParticipant.identity, trackSid: "TR-transfer-new", sourceTurnKey: "new-turn" }, + newClaim.runToken, + ) + expect(newAdmission.status).toBe(200) + const newTicket = (await newAdmission.json()) as { segmentId: string } + const newFinal = await workerApi("final", { segmentId: newTicket.segmentId, text: "New owner turn" }, newClaim.runToken) + expect(newFinal.status).toBe(200) + const committed = (await newFinal.json()) as { messageId: number | null } + expect(committed.messageId).not.toBeNull() + const newRun = ( + await db.select().from(gridTranscriptionRuns).where(eq(gridTranscriptionRuns.id, newClaim.runId)) + )[0]! + expect( + (await db.select().from(messages).where(eq(messages.chatId, newRun.transcriptChatId))) + .some((message) => message.messageId === committed.messageId), + ).toBe(true) + }) + } + } +}) + +async function fixture(count: number, clientVersion?: string) { + const space = await testUtils.createSpace("Grid call transfer") + if (!space) throw new Error("Expected fixture Space") + const users = await Promise.all( + Array.from({ length: count }, () => testUtils.createUser(`${randomUUID()}@example.test`)), + ) + await db.insert(members).values( + users.map((user, index) => ({ + spaceId: space.id, + userId: user.id, + role: index === 0 ? ("owner" as const) : ("member" as const), + })), + ) + const sessions = await Promise.all( + users.map((user) => testUtils.createSessionForUser(user.id, { clientType: "macos", clientVersion })), + ) + const contexts = users.map((user, index) => + testUtils.functionContext({ userId: user.id, sessionId: sessions[index]!.session.id }), + ) + await toggleSpaceGrid({ spaceId: BigInt(space.id), enabled: true }, contexts[0]!) + return { space, users, contexts } +} + +async function activeCall() { + const { space, users, contexts } = await fixture(2) + const created = await createGridRoom({ spaceId: BigInt(space.id), expectedMembershipId: "" }, contexts[0]!) + const joined = await joinGridRoom({ roomId: created.currentCall!.roomId, expectedMembershipId: "" }, contexts[1]!) + const target = await testUtils.createSessionForUser(users[0]!.id, { clientType: "ios" }) + const targetContext = testUtils.functionContext({ userId: users[0]!.id, sessionId: target.session.id }) + return { space, contexts, created, generation: joined.grids[0]!.rooms[0]!.connection!.generation, targetContext } +} + +async function ownPresence(context: ReturnType) { + const [presence] = await db.select().from(gridPresence).where(eq(gridPresence.userId, context.currentUserId)) + if (!presence) throw new Error("Expected live fixture presence") + return presence +} diff --git a/server/src/__tests__/functions/grid.test.ts b/server/src/__tests__/functions/grid.test.ts index 12bb32e4e..a28a3c19f 100644 --- a/server/src/__tests__/functions/grid.test.ts +++ b/server/src/__tests__/functions/grid.test.ts @@ -38,6 +38,7 @@ describe("grid", () => { enabled: false, rooms: [], revision: 0n, + callTransferEnabled: false, }) await expect(createGridRoom({ spaceId: BigInt(space.id) }, contexts[0]!)).rejects.toThrow() diff --git a/server/src/__tests__/modules/grid.livekit.test.ts b/server/src/__tests__/modules/grid.livekit.test.ts index cb5121296..9dafece8d 100644 --- a/server/src/__tests__/modules/grid.livekit.test.ts +++ b/server/src/__tests__/modules/grid.livekit.test.ts @@ -176,6 +176,31 @@ describe("Grid LiveKit credentials", () => { expect(oldClaims.video?.room).not.toBe(currentClaims.video?.room) }) + test("binds private credentials to ownership without disclosing the stable call in room metadata", async () => { + const config = { + serverUrl: "wss://grid.example.test", + apiKey: "test-key", + apiSecret: "test-secret-that-is-long-enough-for-hmac", + } + const callId = "00000000-0000-4000-8000-000000000001" + const mediaMembershipId = "00000000-0000-4000-8000-000000000002" + const credentials = await createGridConnectionCredentials({ + connection: { roomId: 42n, generation: 3, startedAt: 100n }, + userId: 7, + callId, + mediaMembershipId, + participantIdentity: `inline-grid-user-7-${mediaMembershipId}`, + }, config) + expect(credentials?.callId).toBe(callId) + expect(credentials?.membershipId).toBe(mediaMembershipId) + const claims = await new TokenVerifier(config.apiKey, config.apiSecret).verify(credentials!.token) + const metadata = JSON.parse(claims.metadata!) + expect(metadata).not.toHaveProperty("callId") + expect(metadata).not.toHaveProperty("membershipId") + expect(metadata).toEqual({ inlineUserId: 7, gridRoomId: "42", generation: 3 }) + expect(claims.sub).toBe(`inline-grid-user-7-${mediaMembershipId}`) + }) + test("returns unavailable when the provider is not configured", async () => { await expect( createGridConnectionCredentials( diff --git a/server/src/env.ts b/server/src/env.ts index a14cec6bc..de4ecbc59 100644 --- a/server/src/env.ts +++ b/server/src/env.ts @@ -113,6 +113,13 @@ export const LIVEKIT_URL = process.env["LIVEKIT_URL"] export const LIVEKIT_API_KEY = process.env["LIVEKIT_API_KEY"] export const LIVEKIT_API_SECRET = process.env["LIVEKIT_API_SECRET"] export const LIVEKIT_PROVIDER = process.env["LIVEKIT_PROVIDER"] ?? "self_hosted" +// Coordinate activation across the server fleet and upgraded Apple clients. +// Do not turn this off with live transferred calls: doing so restores legacy +// unfenced behavior. Production process environment remains immutable. +export const isGridCallTransferEnabled = () => { + const value = process.env["GRID_CALL_TRANSFER_ENABLED"]?.trim().toLowerCase() + return value === "true" || value === "1" +} export const LIVEKIT_CLOUD_URL = process.env["LIVEKIT_CLOUD_URL"] export const LIVEKIT_CLOUD_API_KEY = process.env["LIVEKIT_CLOUD_API_KEY"] export const LIVEKIT_CLOUD_API_SECRET = process.env["LIVEKIT_CLOUD_API_SECRET"] diff --git a/server/src/functions/_functions.ts b/server/src/functions/_functions.ts index 2adcfee07..592c88b02 100644 --- a/server/src/functions/_functions.ts +++ b/server/src/functions/_functions.ts @@ -87,6 +87,7 @@ import { getGrid, getGridHome, joinGridRoom, + moveGridCallHere, leaveGridRoom, prepareGridConnection, setGridAvatarMicrophoneEnabled, @@ -201,6 +202,7 @@ export const Functions = { getHome: getGridHome, createRoom: createGridRoom, joinRoom: joinGridRoom, + moveCallHere: moveGridCallHere, leaveRoom: leaveGridRoom, setRoomTitle: setGridRoomTitle, setRoomLocked: setGridRoomLocked, diff --git a/server/src/functions/grid.ts b/server/src/functions/grid.ts index c7e4355c7..58fd1448a 100644 --- a/server/src/functions/grid.ts +++ b/server/src/functions/grid.ts @@ -10,11 +10,14 @@ import type { GridHomeSpace, Grid, GridConnection, + GridCurrentCall, GridRoom, JoinGridRoomInput, JoinGridRoomResult, LeaveGridRoomInput, LeaveGridRoomResult, + MoveGridCallHereInput, + MoveGridCallHereResult, PrepareGridConnectionInput, PrepareGridConnectionResult, SetGridAvatarMicrophoneEnabledInput, @@ -41,8 +44,10 @@ import { sessions, spaces, users, + type DbGridPresence, type DbGridRoom, } from "@in/server/db/schema" +import { isGridCallTransferEnabled } from "@in/server/env" import type { Transaction } from "@in/server/db/types" import type { FunctionContext } from "@in/server/functions/_types" import { AccessGuards } from "@in/server/modules/authorization/accessGuards" @@ -85,12 +90,28 @@ const MAX_NAMED_ROOMS_PER_SPACE = 50 const MAX_ROOM_TITLE_LENGTH = 80 const log = new Log("grid") +type GridClaim = { + roomId: number + callId: string + mediaMembershipId: string + currentCall: GridCurrentCall +} + +type GridClaimMutationState = GridMutationState & { claim?: GridClaim } + +type GridCredentialIssuer = typeof createGridConnectionCredentials + +const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i + export async function getGrid(input: GetGridInput, context: FunctionContext): Promise { const spaceId = positiveId(input.spaceId, RealtimeRpcError.SpaceIdInvalid) await AccessGuards.ensureSpaceMember(spaceId, context.currentUserId) - await refreshPresenceLeases({ spaceId, context }) - return { grid: await buildGrid(spaceId, context) } + const expectedMembershipId = + input.expectedMembershipId === undefined ? undefined : membershipFence(input.expectedMembershipId) + await refreshPresenceLeases({ spaceId, context, expectedMembershipId }) + const [grid, currentCall] = await Promise.all([buildGrid(spaceId, context), buildCurrentCall(context)]) + return { grid, currentCall } } export async function getGridHome( @@ -157,12 +178,16 @@ export async function getGridHome( } summaries.sort((a, b) => { - if ((a.activeAvatarCount > 0) !== (b.activeAvatarCount > 0)) { + if (a.activeAvatarCount > 0 !== b.activeAvatarCount > 0) { return a.activeAvatarCount > 0 ? -1 : 1 } return Number(b.latestActivityAt - a.latestActivityAt) }) - return { spaces: summaries } + return { + spaces: summaries, + currentCall: await buildCurrentCall(context), + callTransferEnabled: isGridCallTransferEnabled(), + } } export async function createGridRoom( @@ -185,11 +210,12 @@ export async function createGridRoom( // The preflight can race a Space leave or deletion before this lock. await ensureGridAvailable(spaceId, context.currentUserId, tx) - const state: GridMutationState = { + const state: GridClaimMutationState = { affectedSpaceIds: new Set([spaceId]), endedConnections: [], } const existing = await getActivePresenceWithRoom(tx, context.currentUserId, state) + assertPresenceFence(existing, context, input.expectedMembershipId, { allowLegacyTakeover: true }) if (existing && existing.room.spaceId === spaceId && existing.room.title === null) { const [occupancy] = await tx .select({ value: count() }) @@ -217,9 +243,7 @@ export async function createGridRoom( await notifyGridChanged(state) const [grids, connection] = await Promise.all([ buildGrids(state.affectedSpaceIds, context), - state.changedRoomId - ? credentialsForParticipant(state.changedRoomId, context.currentUserId, context.currentSessionId) - : undefined, + state.claim ? credentialsForParticipant(state.claim, context) : undefined, state.changedRoomId ? notifyConnectionReady(state.changedRoomId) : undefined, ]) log.debug("GRID_TRACE phase=create_rpc_done", { @@ -231,6 +255,7 @@ export async function createGridRoom( return { grids, connection, + currentCall: state.claim?.currentCall, } } @@ -255,12 +280,13 @@ export async function joinGridRoom( if (!room) throw RealtimeRpcError.BadRequest() await ensureGridAvailable(room.spaceId, context.currentUserId, tx) - const state: GridMutationState = { + const state: GridClaimMutationState = { affectedSpaceIds: new Set([room.spaceId]), changedRoomId: room.id, endedConnections: [], } const existing = await getActivePresenceWithRoom(tx, context.currentUserId, state) + assertPresenceFence(existing, context, input.expectedMembershipId, { allowLegacyTakeover: true }) if (existing?.room.id === room.id) { await movePresence(tx, context, room, existing, state, input.microphoneEnabled) return state @@ -286,7 +312,7 @@ export async function joinGridRoom( await notifyGridChanged(state) const [grids, connection] = await Promise.all([ buildGrids(state.affectedSpaceIds, context), - credentialsForParticipant(roomId, context.currentUserId, context.currentSessionId), + state.claim ? credentialsForParticipant(state.claim, context) : undefined, notifyConnectionReady(roomId), ]) log.debug("GRID_TRACE phase=join_rpc_done", { @@ -298,6 +324,7 @@ export async function joinGridRoom( return { grids, connection, + currentCall: state.claim?.currentCall, } } @@ -306,12 +333,19 @@ export async function leaveGridRoom( context: FunctionContext, ): Promise { const expectedRoomId = positiveId(input.expectedRoomId, RealtimeRpcError.BadRequest) + const expectedMembershipId = commandMembershipFence(input.expectedMembershipId) const state = await db.transaction(async (tx) => { await lockGridMutations(tx) + await ensureCurrentSession(tx, context) await lockUser(tx, context.currentUserId) const existing = await getPresenceWithRoom(tx, context.currentUserId) - if (!existing || existing.room.id !== expectedRoomId || existing.presence.ownerSessionId !== context.currentSessionId) { + if ( + !existing || + existing.room.id !== expectedRoomId || + existing.presence.ownerSessionId !== context.currentSessionId || + (expectedMembershipId !== undefined && existing.presence.mediaMembershipId !== expectedMembershipId) + ) { return { affectedSpaceIds: new Set(), endedConnections: [] } } @@ -336,7 +370,51 @@ export async function leaveGridRoom( }) await notifyGridChanged(state) - return { grids: await buildGrids(state.affectedSpaceIds, context) } + const [grids, currentCall] = await Promise.all([ + buildGrids(state.affectedSpaceIds, context), + buildCurrentCall(context), + ]) + return { grids, currentCall } +} + +/** Atomic control transfer. Media cleanup remains asynchronous and provider-specific. */ +export async function moveGridCallHere( + input: MoveGridCallHereInput, + context: FunctionContext, + issueCredentials: GridCredentialIssuer = createGridConnectionCredentials, +): Promise { + if (!isGridCallTransferEnabled()) throw gridClientUpgradeRequired() + const callId = membershipFence(input.callId) + const expectedMembershipId = membershipFence(input.expectedMembershipId) + const state = await db.transaction(async (tx) => { + await lockGridMutations(tx) + await ensureCurrentSession(tx, context) + await lockUser(tx, context.currentUserId) + const state: GridClaimMutationState = { affectedSpaceIds: new Set(), endedConnections: [] } + const existing = await getActivePresenceWithRoom(tx, context.currentUserId, state) + if ( + !existing || + existing.presence.callId !== callId || + existing.presence.mediaMembershipId !== expectedMembershipId + ) { + return state + } + await ensureGridAvailable(existing.room.spaceId, context.currentUserId, tx) + state.changedRoomId = existing.room.id + await movePresence(tx, context, existing.room, existing, state, false, { + preserveCallId: callId, + replaceMembership: true, + }) + return state + }) + await notifyGridChanged(state) + const [grids, connection, currentCall] = await Promise.all([ + buildGrids(state.affectedSpaceIds, context), + state.claim ? credentialsForParticipant(state.claim, context, issueCredentials) : undefined, + state.claim?.currentCall ?? buildCurrentCall(context), + state.changedRoomId ? notifyConnectionReady(state.changedRoomId) : undefined, + ]) + return { grids, connection, currentCall, moved: state.claim !== undefined } } export async function setGridRoomTitle( @@ -461,6 +539,7 @@ export async function prepareGridConnection( ): Promise { const startedAt = Date.now() const roomId = positiveId(input.roomId, RealtimeRpcError.BadRequest) + const expectedMembershipId = commandMembershipFence(input.expectedMembershipId) log.debug("GRID_TRACE phase=prepare_rpc_start", { roomId, generation: input.generation, @@ -484,6 +563,7 @@ export async function prepareGridConnection( eq(gridRooms.id, roomId), eq(gridRooms.connectionGeneration, input.generation), eq(gridPresence.ownerSessionId, context.currentSessionId), + expectedMembershipId === undefined ? undefined : eq(gridPresence.mediaMembershipId, expectedMembershipId), gt(gridPresence.leaseExpiresAt, new Date()), ), ) @@ -501,6 +581,7 @@ export async function prepareGridConnection( displayName: displayName(row.user), participantIdentity: gridParticipantIdentity(row.user.id, row.presence.mediaMembershipId), mediaMembershipId: row.presence.mediaMembershipId, + callId: row.presence.callId, spaceId: row.room.spaceId, } }) @@ -520,6 +601,8 @@ export async function prepareGridConnection( userId: preparation.userId, displayName: preparation.displayName, participantIdentity: preparation.participantIdentity, + callId: preparation.callId, + mediaMembershipId: preparation.mediaMembershipId, }) } catch (error) { Log.shared.warn("Failed to mint prepared Grid connection credentials", { @@ -538,6 +621,7 @@ export async function prepareGridConnection( userId: context.currentUserId, sessionId: context.currentSessionId, mediaMembershipId: preparation.mediaMembershipId, + callId: preparation.callId, }) if (!stillActive) { log.debug("GRID_TRACE phase=prepare_rpc_stale_after_mint", { @@ -566,6 +650,7 @@ async function gridCredentialAuthorityIsActive(input: { userId: number sessionId: number mediaMembershipId: string + callId: string }): Promise { return db.transaction(async (tx) => { await lockGridMutations(tx) @@ -580,6 +665,7 @@ async function gridCredentialAuthorityIsActive(input: { eq(gridPresence.userId, input.userId), eq(gridPresence.ownerSessionId, input.sessionId), eq(gridPresence.mediaMembershipId, input.mediaMembershipId), + eq(gridPresence.callId, input.callId), gt(gridPresence.leaseExpiresAt, new Date()), ), ) @@ -617,8 +703,10 @@ export async function setGridAvatarMicrophoneEnabled( context: FunctionContext, ): Promise { const roomId = positiveId(input.expectedRoomId, RealtimeRpcError.BadRequest) + const expectedMembershipId = commandMembershipFence(input.expectedMembershipId) const stateUpdate = await db.transaction(async (tx) => { await lockGridMutations(tx) + await ensureCurrentSession(tx, context) const [row] = await tx .select({ spaceId: gridRooms.spaceId }) .from(gridPresence) @@ -628,6 +716,7 @@ export async function setGridAvatarMicrophoneEnabled( eq(gridPresence.userId, context.currentUserId), eq(gridPresence.ownerSessionId, context.currentSessionId), eq(gridPresence.roomId, roomId), + expectedMembershipId === undefined ? undefined : eq(gridPresence.mediaMembershipId, expectedMembershipId), gt(gridPresence.leaseExpiresAt, new Date()), ), ) @@ -639,6 +728,7 @@ export async function setGridAvatarMicrophoneEnabled( ownerSessionId: context.currentSessionId, roomId, enabled: input.enabled, + expectedMembershipId, }) if (!updated) throw RealtimeRpcError.BadRequest() return { spaceId: row.spaceId, ...updated } @@ -674,16 +764,19 @@ async function movePresence( context: FunctionContext, targetRoom: DbGridRoom, existing: Awaited>, - state: GridMutationState, + state: GridClaimMutationState, microphoneEnabled?: boolean, + options: { preserveCallId?: string; replaceMembership?: boolean } = {}, ) { if (existing) { state.affectedSpaceIds.add(existing.room.spaceId) } - const ownerChanged = existing !== undefined && existing.presence.ownerSessionId !== context.currentSessionId - const rotateForAccessRevocation = ownerChanged && liveKitRequiresGenerationRotation() - if (existing && ownerChanged) { + const ownershipReplaced = + existing !== undefined && + (existing.presence.ownerSessionId !== context.currentSessionId || options.replaceMembership === true) + const rotateForAccessRevocation = ownershipReplaced && liveKitRequiresGenerationRotation() + if (existing && ownershipReplaced) { await stopGridTranscription( tx, existing.room.id, @@ -691,7 +784,7 @@ async function movePresence( true, ) } - if (existing && (existing.room.id !== targetRoom.id || ownerChanged)) { + if (existing && (existing.room.id !== targetRoom.id || ownershipReplaced)) { const connection = activeGridConnection(existing.room) if (connection) { await recordGridParticipantRevocation( @@ -704,7 +797,7 @@ async function movePresence( } } - await claimPresence(tx, context, targetRoom.id, microphoneEnabled) + const presence = await claimPresence(tx, context, targetRoom.id, microphoneEnabled, options) await enrollGridRoomHistory(tx, targetRoom.id, [context.currentUserId]) const roster = await tx .select({ userId: gridPresence.userId }) @@ -718,6 +811,12 @@ async function movePresence( .limit(1) if (!ownerSession || !supportedGridTranscriptionSession(ownerSession)) await stopGridTranscription(tx, targetRoom.id, "unsupported_client", true) + state.claim = { + roomId: presence.roomId, + callId: presence.callId, + mediaMembershipId: presence.mediaMembershipId, + currentCall: encodeCurrentCall(presence, targetRoom, context, ownerSession?.clientType), + } if (existing && existing.room.id !== targetRoom.id) { const endedConnection = await reconcileGridRoom(tx, existing.room.id, { rotateForAccessRevocation }) @@ -734,14 +833,16 @@ async function claimPresence( context: FunctionContext, roomId: number, microphoneEnabled?: boolean, + options: { preserveCallId?: string; replaceMembership?: boolean } = {}, ) { const now = new Date() - await tx + const [presence] = await tx .insert(gridPresence) .values({ userId: context.currentUserId, roomId, ownerSessionId: context.currentSessionId, + callId: options.preserveCallId, microphoneEnabled: microphoneEnabled ?? false, joinedAt: now, leaseExpiresAt: new Date(now.getTime() + PRESENCE_LEASE_MS), @@ -751,22 +852,38 @@ async function claimPresence( set: { roomId, ownerSessionId: context.currentSessionId, - joinedAt: sql`case when ${gridPresence.roomId} = ${roomId} and ${gridPresence.ownerSessionId} = ${context.currentSessionId} then ${gridPresence.joinedAt} else now() end`, - mediaMembershipId: sql`case when ${gridPresence.roomId} = ${roomId} and ${gridPresence.ownerSessionId} = ${context.currentSessionId} then ${gridPresence.mediaMembershipId} else gen_random_uuid() end`, + joinedAt: + options.preserveCallId !== undefined + ? gridPresence.joinedAt + : sql`case when ${gridPresence.roomId} = ${roomId} and ${gridPresence.ownerSessionId} = ${context.currentSessionId} then ${gridPresence.joinedAt} else now() end`, + callId: + options.preserveCallId ?? + sql`case when ${gridPresence.roomId} = ${roomId} and ${gridPresence.ownerSessionId} = ${context.currentSessionId} then ${gridPresence.callId} else gen_random_uuid() end`, + mediaMembershipId: options.replaceMembership + ? sql`gen_random_uuid()` + : sql`case when ${gridPresence.roomId} = ${roomId} and ${gridPresence.ownerSessionId} = ${context.currentSessionId} then ${gridPresence.mediaMembershipId} else gen_random_uuid() end`, microphoneEnabled: microphoneEnabled === undefined ? sql`case when ${gridPresence.ownerSessionId} = ${context.currentSessionId} then ${gridPresence.microphoneEnabled} else false end` : microphoneEnabled, - microphoneRevision: - microphoneEnabled === undefined - ? sql`case when ${gridPresence.roomId} = ${roomId} and ${gridPresence.ownerSessionId} = ${context.currentSessionId} then ${gridPresence.microphoneRevision} else 0 end` - : sql`case when ${gridPresence.roomId} = ${roomId} and ${gridPresence.ownerSessionId} = ${context.currentSessionId} and ${gridPresence.microphoneEnabled} != ${microphoneEnabled} then ${gridPresence.microphoneRevision} + 1 when ${gridPresence.roomId} = ${roomId} and ${gridPresence.ownerSessionId} = ${context.currentSessionId} then ${gridPresence.microphoneRevision} else 0 end`, + microphoneRevision: options.replaceMembership + ? 0 + : microphoneEnabled === undefined + ? sql`case when ${gridPresence.roomId} = ${roomId} and ${gridPresence.ownerSessionId} = ${context.currentSessionId} then ${gridPresence.microphoneRevision} else 0 end` + : sql`case when ${gridPresence.roomId} = ${roomId} and ${gridPresence.ownerSessionId} = ${context.currentSessionId} and ${gridPresence.microphoneEnabled} != ${microphoneEnabled} then ${gridPresence.microphoneRevision} + 1 when ${gridPresence.roomId} = ${roomId} and ${gridPresence.ownerSessionId} = ${context.currentSessionId} then ${gridPresence.microphoneRevision} else 0 end`, leaseExpiresAt: new Date(now.getTime() + PRESENCE_LEASE_MS), }, }) + .returning() + if (!presence) throw RealtimeRpcError.InternalError() + return presence } -async function refreshPresenceLeases(input?: { spaceId: number; context: FunctionContext }) { +async function refreshPresenceLeases(input?: { + spaceId: number + context: FunctionContext + expectedMembershipId?: string +}) { const affected = await db.transaction(async (tx) => { await lockGridMutations(tx) @@ -790,7 +907,9 @@ async function refreshPresenceLeases(input?: { spaceId: number; context: Functio const endedConnection = await reconcileGridRoom(tx, roomId) if (endedConnection) endedConnections.push(endedConnection) } - if (input) await renewOwnedPresence(tx, input.spaceId, input.context, cutoff) + if (input && (input.expectedMembershipId !== undefined || !isGridCallTransferEnabled())) { + await renewOwnedPresence(tx, input.spaceId, input.context, cutoff, input.expectedMembershipId) + } const state: GridMutationState = { affectedSpaceIds: new Set(expired.map(({ room }) => room.spaceId)), endedConnections, @@ -811,6 +930,7 @@ async function renewOwnedPresence( spaceId: number, context: FunctionContext, cutoff: Date, + expectedMembershipId?: string, ) { const [presence] = await tx .select({ roomId: gridPresence.roomId }) @@ -820,6 +940,7 @@ async function renewOwnedPresence( and( eq(gridPresence.userId, context.currentUserId), eq(gridPresence.ownerSessionId, context.currentSessionId), + expectedMembershipId === undefined ? undefined : eq(gridPresence.mediaMembershipId, expectedMembershipId), gt(gridPresence.leaseExpiresAt, cutoff), ), ) @@ -834,6 +955,7 @@ async function renewOwnedPresence( eq(gridPresence.userId, context.currentUserId), eq(gridPresence.ownerSessionId, context.currentSessionId), eq(gridPresence.roomId, presence.roomId), + expectedMembershipId === undefined ? undefined : eq(gridPresence.mediaMembershipId, expectedMembershipId), gt(gridPresence.leaseExpiresAt, cutoff), ), ) @@ -843,6 +965,94 @@ async function buildGrids(spaceIds: Set, context: FunctionContext): Prom return Promise.all([...spaceIds].sort((a, b) => a - b).map((spaceId) => buildGrid(spaceId, context))) } +async function buildCurrentCall(context: FunctionContext): Promise { + return db.transaction(async (tx) => { + await lockGridMutations(tx) + const [row] = await tx + .select({ presence: gridPresence, room: gridRooms, ownerClientType: sessions.clientType }) + .from(gridPresence) + .innerJoin(gridRooms, eq(gridRooms.id, gridPresence.roomId)) + .innerJoin(spaces, and(eq(spaces.id, gridRooms.spaceId), isNull(spaces.deleted))) + .innerJoin(members, and(eq(members.spaceId, gridRooms.spaceId), eq(members.userId, gridPresence.userId))) + .innerJoin( + sessions, + and( + eq(sessions.id, gridPresence.ownerSessionId), + eq(sessions.userId, gridPresence.userId), + isNull(sessions.revoked), + ), + ) + .where(and(eq(gridPresence.userId, context.currentUserId), gt(gridPresence.leaseExpiresAt, new Date()))) + .limit(1) + if (!row || !(await SpaceSettingsModel.getStored(row.room.spaceId, tx)).gridEnabled) return undefined + return encodeCurrentCall(row.presence, row.room, context, row.ownerClientType) + }) +} + +function encodeCurrentCall( + presence: DbGridPresence, + room: DbGridRoom, + context: FunctionContext, + ownerClientType?: string | null, +): GridCurrentCall { + return { + callId: presence.callId, + spaceId: BigInt(room.spaceId), + roomId: BigInt(room.id), + membershipId: presence.mediaMembershipId, + ownedByCurrentSession: presence.ownerSessionId === context.currentSessionId, + ownerClientType: ownerClientType ?? "", + } +} + +function membershipFence(value: string, allowEmpty = false): string { + if (allowEmpty && value === "") return value + if (!uuidPattern.test(value)) throw RealtimeRpcError.BadRequest() + return value.toLowerCase() +} + +function gridClientUpgradeRequired(): RealtimeRpcError { + return new RealtimeRpcError( + RealtimeRpcError.Code.BAD_REQUEST, + "Grid call ownership requires an updated client and enabled server.", + 400, + ) +} + +function commandMembershipFence(value: string | undefined): string | undefined { + if (value === undefined) { + if (isGridCallTransferEnabled()) throw gridClientUpgradeRequired() + return undefined + } + return membershipFence(value) +} + +function assertPresenceFence( + existing: Awaited>, + context: FunctionContext, + value: string | undefined, + options: { allowLegacyTakeover?: boolean } = {}, +): void { + if (value === undefined) { + if (isGridCallTransferEnabled()) throw gridClientUpgradeRequired() + return + } + const expected = membershipFence(value, true) + if (!existing && expected === "") return + if ( + existing && + expected === existing.presence.mediaMembershipId && + (existing.presence.ownerSessionId === context.currentSessionId || + (options.allowLegacyTakeover === true && !isGridCallTransferEnabled())) + ) + return + throw new RealtimeRpcError( + RealtimeRpcError.Code.BAD_REQUEST, + "The Grid call changed. Refresh before trying again.", + 400, + ) +} + async function buildGrid(spaceId: number, context: FunctionContext): Promise { // Snapshot content and its revision are read under the same lock used by // every Grid mutation. A response can therefore be older than a later @@ -988,6 +1198,7 @@ async function buildGrid(spaceId: number, context: FunctionContext): Promise internalMessaging.publish({ @@ -1335,8 +1563,19 @@ async function notifyConnectionReadyUnchecked(roomId: number) { export function subscribeGridCredentialHints(): () => void { return internalMessaging.on("SessionRealtime", async ({ target, event }) => { if (event.payload.kind !== "gridCredentials") return - const { roomId, spaceId, generation, mediaMembershipId, encodedPayload } = event.payload - if (!(await gridCredentialAuthorityIsActive({ roomId, spaceId, generation, mediaMembershipId, userId: target.userId, sessionId: target.sessionId }))) return + const { roomId, spaceId, generation, mediaMembershipId, callId, encodedPayload } = event.payload + if ( + !(await gridCredentialAuthorityIsActive({ + roomId, + spaceId, + generation, + mediaMembershipId, + callId, + userId: target.userId, + sessionId: target.sessionId, + })) + ) + return let payload: ServerMessage["payload"] try { payload = ServerMessage.fromBinary(Buffer.from(encodedPayload, "base64")).payload diff --git a/server/src/functions/gridTranscription.ts b/server/src/functions/gridTranscription.ts index 27bd049b9..451df4806 100644 --- a/server/src/functions/gridTranscription.ts +++ b/server/src/functions/gridTranscription.ts @@ -49,6 +49,7 @@ import { import { insertGridTranscriptMessage } from "@in/server/modules/grid/transcription/messages" import { RealtimeRpcError } from "@in/server/realtime/errors" import { and, eq, gt, inArray, isNull, desc, or, notInArray } from "drizzle-orm" +import { alias } from "drizzle-orm/pg-core" const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i function id(value: bigint) { @@ -223,6 +224,7 @@ export async function listGridTranscripts( .orderBy(gridTranscriptionRuns.transcriptChatId, desc(gridTranscriptionRuns.createdAt)) : [] const acceptedByChat = new Map(acceptedRows.map((run) => [run.transcriptChatId, run])) + const parent = alias(chats, "grid_transcript_parent") const seen = new Set() const transcripts: ListGridTranscriptsResult["transcripts"] = [] for (const run of rows) { @@ -240,7 +242,13 @@ export async function listGridTranscripts( ) continue seen.add(run.transcriptChatId) - const [chat] = await tx.select().from(chats).where(eq(chats.id, run.transcriptChatId)).limit(1) + const [destination] = await tx + .select({ title: chats.title, number: chats.threadNumber, parentTitle: parent.title }) + .from(chats) + .innerJoin(parent, eq(parent.id, chats.parentChatId)) + .where(eq(chats.id, run.transcriptChatId)) + .limit(1) + if (!destination) continue const [active] = await tx .select({ id: gridTranscriptionRuns.id }) .from(gridTranscriptionRuns) @@ -253,7 +261,7 @@ export async function listGridTranscripts( .limit(1) transcripts.push({ transcriptChatId: BigInt(run.transcriptChatId), - title: chat?.title ?? "Transcript", + title: `${destination.parentTitle?.trim() || "Grid room"} · ${destination.title?.trim() || "Transcript"}${destination.number === null ? "" : ` #${destination.number}`}`, busy: !!active, }) if (transcripts.length === 20) break diff --git a/server/src/modules/grid/avatarState.ts b/server/src/modules/grid/avatarState.ts index d04736d54..f24cd42b4 100644 --- a/server/src/modules/grid/avatarState.ts +++ b/server/src/modules/grid/avatarState.ts @@ -17,6 +17,7 @@ export async function setGridAvatarMicrophoneState( ownerSessionId: number roomId: number enabled: boolean + expectedMembershipId?: string }, ): Promise<{ membershipId: string; revision: number } | undefined> { const updated = await tx @@ -30,6 +31,9 @@ export async function setGridAvatarMicrophoneState( eq(gridPresence.userId, input.userId), eq(gridPresence.ownerSessionId, input.ownerSessionId), eq(gridPresence.roomId, input.roomId), + input.expectedMembershipId === undefined + ? undefined + : eq(gridPresence.mediaMembershipId, input.expectedMembershipId), gt(gridPresence.leaseExpiresAt, new Date()), ), ) diff --git a/server/src/modules/grid/livekit.ts b/server/src/modules/grid/livekit.ts index 63b233b76..72458418c 100644 --- a/server/src/modules/grid/livekit.ts +++ b/server/src/modules/grid/livekit.ts @@ -63,6 +63,8 @@ export async function createGridConnectionCredentials( userId: number displayName?: string participantIdentity?: string + callId?: string + mediaMembershipId?: string }, config: LiveKitGridConfig | null | undefined = getLiveKitGridConfig(), ): Promise { @@ -71,6 +73,8 @@ export async function createGridConnectionCredentials( log.debug("GRID_TRACE phase=credentials_config_unavailable", { roomId: input.connection.roomId.toString(), generation: input.connection.generation, + callId: input.callId, + membershipId: input.mediaMembershipId, userId: input.userId, }) return undefined @@ -111,6 +115,8 @@ export async function createGridConnectionCredentials( participantIdentity, token: jwt, expiresAt: BigInt(Math.floor(Date.now() / 1000) + TOKEN_TTL_SECONDS), + callId: input.callId ?? "", + membershipId: input.mediaMembershipId ?? "", } } diff --git a/server/src/modules/grid/transcription/control.test.ts b/server/src/modules/grid/transcription/control.test.ts index 6714742a6..3fe9ce3c9 100644 --- a/server/src/modules/grid/transcription/control.test.ts +++ b/server/src/modules/grid/transcription/control.test.ts @@ -117,14 +117,15 @@ async function fixture(label: string) { transcriptChatId?: bigint, actor = 0, requestId = randomUUID(), + controlledRoomId = roomId, ) => { const room = (await getGrid({ spaceId: BigInt(space.id) }, contexts[actor]!)).grid!.rooms.find( - (room) => room.id === roomId, + (room) => room.id === controlledRoomId, )! const avatar = room.avatars.find((avatar) => avatar.user?.id === BigInt(users[actor]!.id))! return setGridTranscription( { - roomId, + roomId: controlledRoomId, enabled, requestId, expectedMembershipId: avatar.membershipId, @@ -440,6 +441,34 @@ describe("Grid transcription control and worker authorization", () => { expect(listed.transcripts.every((entry) => !entry.busy)).toBe(true) }) + test("picker identifies source parents and distinct transcripts without changing or sharing history", async () => { + const f = await fixture("picker-labels") + await db.update(gridRooms).set({ title: "Product planning" }).where(eq(gridRooms.id, Number(f.roomId))) + await f.control(true) + await f.control(false) + await f.control(true, GridTranscriptDestination.GRID_TRANSCRIPT_NEW) + await f.control(false) + const created = await createGridRoom({ spaceId: BigInt(f.space.id), microphoneEnabled: true }, f.contexts[0]!) + const otherRoomId = created.grids[0]!.rooms.find((room) => room.avatars.some((avatar) => avatar.ownedByCurrentSession))!.id + await db.update(gridRooms).set({ title: "Design review" }).where(eq(gridRooms.id, Number(otherRoomId))) + await joinGridRoom({ roomId: otherRoomId, microphoneEnabled: true }, f.contexts[1]!) + await f.control(true, GridTranscriptDestination.GRID_TRANSCRIPT_NEW, undefined, 0, randomUUID(), otherRoomId) + await f.control(false, GridTranscriptDestination.GRID_TRANSCRIPT_LAST, undefined, 0, randomUUID(), otherRoomId) + const runs = await db.select().from(gridTranscriptionRuns) + expect(runs).toHaveLength(3) + const before = await db.select().from(chatParticipants) + const listed = await listGridTranscripts({ roomId: otherRoomId }, f.contexts[0]!) + const labels = new Map(listed.transcripts.map((entry) => [Number(entry.transcriptChatId), entry.title])) + for (const run of runs) { + const [child] = await db.select().from(chats).where(eq(chats.id, run.transcriptChatId)) + expect(child!.title).toBe("Transcript") + const source = run.sourceRoomId === Number(f.roomId) ? "Product planning" : "Design review" + expect(labels.get(run.transcriptChatId)).toBe(`${source} · Transcript #${child!.threadNumber}`) + } + expect(new Set(labels.values()).size).toBe(3) + expect(await db.select().from(chatParticipants)).toEqual(before) + }) + test("lease-only renewals keep Grid revision and hints quiet while authority loss publishes once", async () => { const f = await fixture("lease-only-renewal") await f.control(true) diff --git a/server/src/modules/internalMessaging/redis.integration.test.ts b/server/src/modules/internalMessaging/redis.integration.test.ts index 1b6347d25..7e9b51cf9 100644 --- a/server/src/modules/internalMessaging/redis.integration.test.ts +++ b/server/src/modules/internalMessaging/redis.integration.test.ts @@ -62,8 +62,18 @@ describe("two-service disposable broker transport", () => { const unsubscribeSession = b.on("SessionRealtime", ({ target }) => receivedSession.resolve(target.bootId)) const directed = await a.publish({ target: { kind: "session", bootId: b.bootId, userId: UserId.make(9001), sessionId: SessionId.make(9002) }, - event: { kind: "SessionRealtime", payload: { kind: "gridCredentials", roomId: 1, - spaceId: SpaceId.make(2), generation: 3, mediaMembershipId: randomUUID(), encodedPayload: "e30=" } }, + event: { + kind: "SessionRealtime", + payload: { + kind: "gridCredentials", + roomId: 1, + spaceId: SpaceId.make(2), + generation: 3, + mediaMembershipId: randomUUID(), + callId: randomUUID(), + encodedPayload: "e30=", + }, + }, }) expect(directed).toEqual({ status: "published", subscribers: 1 }) expect(await waitFor(receivedSession.promise)).toBe(b.bootId) diff --git a/server/src/modules/internalMessaging/schemas.test.ts b/server/src/modules/internalMessaging/schemas.test.ts index bfd8cd666..3aab1a951 100644 --- a/server/src/modules/internalMessaging/schemas.test.ts +++ b/server/src/modules/internalMessaging/schemas.test.ts @@ -86,8 +86,18 @@ describe("internal messaging contracts", () => { const envelope = { version: 1 as const, eventId: randomUUID(), originBootId: randomUUID(), target: { kind: "session" as const, bootId: randomUUID(), userId: UserId.make(1), sessionId: SessionId.make(2) }, - event: { kind: "SessionRealtime" as const, payload: { kind: "gridCredentials" as const, - roomId: 3, spaceId: SpaceId.make(4), generation: 5, mediaMembershipId: randomUUID(), encodedPayload: "e30=" } }, + event: { + kind: "SessionRealtime" as const, + payload: { + kind: "gridCredentials" as const, + roomId: 3, + spaceId: SpaceId.make(4), + generation: 5, + mediaMembershipId: randomUUID(), + callId: randomUUID(), + encodedPayload: "e30=", + }, + }, } expect(decodeEnvelope(encodeEnvelope(envelope))).toEqual(envelope) expect(() => decodeEnvelope(JSON.stringify({ ...envelope, target: { kind: "session", userId: 1, sessionId: 2 } }))).toThrow() diff --git a/server/src/modules/internalMessaging/schemas.ts b/server/src/modules/internalMessaging/schemas.ts index f5b002a1a..f4a53988d 100644 --- a/server/src/modules/internalMessaging/schemas.ts +++ b/server/src/modules/internalMessaging/schemas.ts @@ -92,7 +92,15 @@ const PrivateReply = Schema.Struct({ const SessionRealtime = Schema.Struct({ kind: Schema.Literal("SessionRealtime"), payload: Schema.Union([ - Schema.Struct({ kind: Schema.Literal("gridCredentials"), roomId: WirePositiveInteger, spaceId: SpaceId, generation: WireNonNegativeInteger, mediaMembershipId: Uuid, encodedPayload: Schema.String.check(Schema.isMaxLength(32768)) }), + Schema.Struct({ + kind: Schema.Literal("gridCredentials"), + roomId: WirePositiveInteger, + spaceId: SpaceId, + generation: WireNonNegativeInteger, + mediaMembershipId: Uuid, + callId: Uuid, + encodedPayload: Schema.String.check(Schema.isMaxLength(32768)), + }), ]), }) diff --git a/server/src/realtime/handlers/_rpc.ts b/server/src/realtime/handlers/_rpc.ts index 34741bac8..e0a952f6c 100644 --- a/server/src/realtime/handlers/_rpc.ts +++ b/server/src/realtime/handlers/_rpc.ts @@ -114,6 +114,7 @@ import { getGridHomeHandler, joinGridRoomHandler, leaveGridRoomHandler, + moveGridCallHereHandler, prepareGridConnectionHandler, setGridAvatarMicrophoneEnabledHandler, setGridRoomLockedHandler, @@ -518,6 +519,12 @@ export const handleRpcCall = async (call: RpcCall, handlerContext: HandlerContex return { oneofKind: "leaveGridRoom", leaveGridRoom: result } } + case Method.MOVE_GRID_CALL_HERE: { + if (call.input.oneofKind !== "moveGridCallHere") throw RealtimeRpcError.BadRequest() + const result = await moveGridCallHereHandler(call.input.moveGridCallHere, handlerContext) + return { oneofKind: "moveGridCallHere", moveGridCallHere: result } + } + case Method.SET_GRID_ROOM_TITLE: { if (call.input.oneofKind !== "setGridRoomTitle") throw RealtimeRpcError.BadRequest() const result = await setGridRoomTitleHandler(call.input.setGridRoomTitle, handlerContext) diff --git a/server/src/realtime/handlers/grid.ts b/server/src/realtime/handlers/grid.ts index 8cc2aa051..13a2de28d 100644 --- a/server/src/realtime/handlers/grid.ts +++ b/server/src/realtime/handlers/grid.ts @@ -17,6 +17,8 @@ import type { JoinGridRoomResult, LeaveGridRoomInput, LeaveGridRoomResult, + MoveGridCallHereInput, + MoveGridCallHereResult, PrepareGridConnectionInput, PrepareGridConnectionResult, SetGridAvatarMicrophoneEnabledInput, @@ -57,6 +59,11 @@ export const leaveGridRoomHandler = ( context: HandlerContext, ): Promise => Functions.grid.leaveRoom(input, functionContext(context)) +export const moveGridCallHereHandler = ( + input: MoveGridCallHereInput, + context: HandlerContext, +): Promise => Functions.grid.moveCallHere(input, functionContext(context)) + export const setGridRoomTitleHandler = ( input: SetGridRoomTitleInput, context: HandlerContext, From e22440e4e0d6c72b686a4eed6dae1ccfa7523dc6 Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Tue, 6 Oct 2026 18:47:25 +0330 Subject: [PATCH 07/14] apple: add gated grid rooms and serialize media ownership --- apple/Inline.xcodeproj/project.pbxproj | 12 + apple/InlineIOS/ExperimentalRootView.swift | 28 +- .../Features/Grid/GridHomeEntry.swift | 166 ++ .../Features/Grid/GridRoomsView.swift | 633 +++++ .../Features/Grid/GridScreenShareViewer.swift | 119 + .../Features/Settings/ExperimentalView.swift | 14 + apple/InlineIOS/Info.plist | 3 +- apple/InlineIOS/InlineApp.swift | 7 + .../MainViews/HomeToolbarContent.swift | 12 +- apple/InlineIOS/MainViews/HomeView.swift | 10 +- apple/InlineIOS/Shared/VoiceInputPicker.swift | 4 + apple/InlineIOS/Utils/LogoutPerformer.swift | 12 + apple/InlineKit/Package.swift | 17 + apple/InlineKit/Sources/Auth/Auth2Types.swift | 57 + apple/InlineKit/Sources/Auth/AuthStore.swift | 47 +- .../InlineAudioSession.swift | 358 ++- .../InlineGrid/GridAvatarStateSync.swift | 117 + .../InlineGrid/GridCallClaimAdmission.swift | 35 + .../InlineGrid/GridCredentialBinding.swift | 26 + .../InlineGrid/GridHomePreferences.swift | 40 + .../InlineGrid/GridMediaCoordinator.swift | 597 +++++ .../InlineGrid/GridMediaPresentation.swift | 272 ++ .../Sources/InlineGrid/GridMediaTarget.swift | 18 + .../InlineGrid/GridMembershipSync.swift | 173 ++ .../InlineGrid/GridNetworkMonitor.swift | 78 + .../InlineGrid/GridPlatformEffects.swift | 31 + .../Sources/InlineGrid/GridRoomAPI.swift | 256 ++ .../InlineGrid/GridRoomLifecycle.swift | 83 + .../Sources/InlineGrid/GridRuntime.swift | 55 + .../Sources/InlineGrid/GridStore.swift | 2236 +++++++++++++++++ .../ExperimentalFeatureFlags.swift | 7 + .../Transactions2/GridTransactions.swift | 271 +- .../InlineRTC/Engine/GridAudioEngine.swift | 68 +- .../Sources/InlineRTC/Engine/GridEngine.swift | 60 +- .../Engine/GridEngineCommandMailbox.swift | 6 +- .../InlineRTC/Engine/GridMediaTypes.swift | 62 +- .../InlineRTC/Engine/GridRTCEngine.swift | 139 +- .../InlineRTC/Engine/GridScreenShare.swift | 2 +- .../Engine/InlineRTCVideoView+iOS.swift | 82 + .../Engine/LiveKitGridAUHALAudioDriver.swift | 158 +- .../Engine/LiveKitGridIOSAudioDriver.swift | 521 ++++ .../InlineRTC/GridLiveKitConfiguration.swift | 15 +- .../Connection/ConnectionEvent.swift | 2 + .../Connection/ConnectionManager.swift | 34 +- .../RealtimeV2/Realtime/Realtime.swift | 15 + .../InlineAudioSessionGridTests.swift | 631 +++++ .../GridAuthAuthorityRetirementTests.swift | 284 +++ .../GridAuthorityTestSupport.swift | 446 ++++ .../GridCallClaimAdmissionTests.swift | 85 + .../GridCredentialBindingTests.swift | 80 + .../GridMembershipRevocationTests.swift | 89 + .../GridPendingAuthorityRetirementTests.swift | 169 ++ .../Grid/GridTransactionLifetimeTests.swift | 60 +- .../Engine/GridAudioEngineTests.swift | 61 + .../Engine/GridRTCEngineTests.swift | 525 +++- .../LiveKitGridIOSAudioDriverTests.swift | 247 ++ .../UserInitiatedConnectionTests.swift | 46 + apple/InlineMac/App/AppDependencies.swift | 7 +- .../Features/Grid/GridAvatarStateSync.swift | 103 +- .../Features/Grid/GridControlsView.swift | 1 + .../Features/Grid/GridHomePreferences.swift | 42 +- .../Features/Grid/GridMediaCoordinator.swift | 507 +--- .../Features/Grid/GridMediaPresentation.swift | 272 +- .../Features/Grid/GridMediaTarget.swift | 18 +- .../Features/Grid/GridMembershipSync.swift | 146 +- .../Features/Grid/GridNetworkMonitor.swift | 80 +- .../Features/Grid/GridPlatformEffects.swift | 70 + .../InlineMac/Features/Grid/GridRoomAPI.swift | 166 +- .../Features/Grid/GridRoomLifecycle.swift | 85 +- .../Features/Grid/GridRoomView.swift | 24 +- .../InlineMac/Features/Grid/GridRuntime.swift | 38 +- .../Features/Grid/GridScreenShareWindow.swift | 1 + apple/InlineMac/Features/Grid/GridStore.swift | 1822 +------------- apple/InlineMac/Features/Grid/GridView.swift | 50 +- .../Features/Sidebar/SidebarView.swift | 1 + .../MessageList/ServiceMessageRowView.swift | 36 +- .../AudioAndVideoSettingsDetailView.swift | 1 + .../SpaceSettings/SpaceSettingsView.swift | 1 + 78 files changed, 9576 insertions(+), 3576 deletions(-) create mode 100644 apple/InlineIOS/Features/Grid/GridHomeEntry.swift create mode 100644 apple/InlineIOS/Features/Grid/GridRoomsView.swift create mode 100644 apple/InlineIOS/Features/Grid/GridScreenShareViewer.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridAvatarStateSync.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridCallClaimAdmission.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridCredentialBinding.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridHomePreferences.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridMediaCoordinator.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridMediaPresentation.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridMediaTarget.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridMembershipSync.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridNetworkMonitor.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridPlatformEffects.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridRoomAPI.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridRoomLifecycle.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridRuntime.swift create mode 100644 apple/InlineKit/Sources/InlineGrid/GridStore.swift create mode 100644 apple/InlineKit/Sources/InlineRTC/Engine/InlineRTCVideoView+iOS.swift create mode 100644 apple/InlineKit/Sources/InlineRTC/Engine/LiveKitGridIOSAudioDriver.swift create mode 100644 apple/InlineKit/Tests/InlineAudioPlaybackTests/InlineAudioSessionGridTests.swift create mode 100644 apple/InlineKit/Tests/InlineGridTests/GridAuthAuthorityRetirementTests.swift create mode 100644 apple/InlineKit/Tests/InlineGridTests/GridAuthorityTestSupport.swift create mode 100644 apple/InlineKit/Tests/InlineGridTests/GridCallClaimAdmissionTests.swift create mode 100644 apple/InlineKit/Tests/InlineGridTests/GridCredentialBindingTests.swift create mode 100644 apple/InlineKit/Tests/InlineGridTests/GridMembershipRevocationTests.swift create mode 100644 apple/InlineKit/Tests/InlineGridTests/GridPendingAuthorityRetirementTests.swift create mode 100644 apple/InlineKit/Tests/InlineRTCTests/Engine/LiveKitGridIOSAudioDriverTests.swift create mode 100644 apple/InlineMac/Features/Grid/GridPlatformEffects.swift diff --git a/apple/Inline.xcodeproj/project.pbxproj b/apple/Inline.xcodeproj/project.pbxproj index 16285f6b6..4ac6ac35a 100644 --- a/apple/Inline.xcodeproj/project.pbxproj +++ b/apple/Inline.xcodeproj/project.pbxproj @@ -85,6 +85,9 @@ E609060C2FA1000100000001 /* InlineSyntaxHighlighting in Frameworks */ = {isa = PBXBuildFile; productRef = E5A100022FA0000100000001 /* InlineSyntaxHighlighting */; }; E95DD2F1FC76F473B6D31EB6 /* InlineMacWindow in Frameworks */ = {isa = PBXBuildFile; productRef = 7BABF642C785B35DB0C34BF2 /* InlineMacWindow */; }; F17C11012F9A000100000001 /* InlineCLIInstaller in Frameworks */ = {isa = PBXBuildFile; productRef = F17C11002F9A000100000000 /* InlineCLIInstaller */; }; + F00200022FA2000100000001 /* InlineGrid in Frameworks */ = {isa = PBXBuildFile; productRef = F00200012FA2000100000001 /* InlineGrid */; }; + F00200042FA2000100000001 /* InlineGrid in Frameworks */ = {isa = PBXBuildFile; productRef = F00200032FA2000100000001 /* InlineGrid */; }; + F00200062FA2000100000001 /* InlineRTC in Frameworks */ = {isa = PBXBuildFile; productRef = F00200052FA2000100000001 /* InlineRTC */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -281,6 +284,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F00200022FA2000100000001 /* InlineGrid in Frameworks */, + F00200062FA2000100000001 /* InlineRTC in Frameworks */, E12407240000000100000003 /* InlineIntents in Frameworks */, CD735FF02E493C8E00F7BFFE /* Translation in Frameworks */, CD4E8E2C2E088F6B00CE6BC0 /* TextProcessing in Frameworks */, @@ -312,6 +317,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F00200042FA2000100000001 /* InlineGrid in Frameworks */, A83010022F52000100000001 /* InlineAppIntents in Frameworks */, A9E100042F50000100000001 /* MemojiKit in Frameworks */, A9E100052F50000100000001 /* MemojiPickerUI in Frameworks */, @@ -490,6 +496,8 @@ ); name = InlineIOS; packageProductDependencies = ( + F00200012FA2000100000001 /* InlineGrid */, + F00200052FA2000100000001 /* InlineRTC */, E12407240000000100000002 /* InlineIntents */, C35E950D2CAC497700D569AF /* GRDB */, C3E6D5CA2CAE82B00048D2D8 /* GRDB */, @@ -536,6 +544,7 @@ ); name = InlineMac; packageProductDependencies = ( + F00200032FA2000100000001 /* InlineGrid */, A83010002F52000100000001 /* InlineAppIntents */, A9E100022F50000100000001 /* MemojiKit */, A9E100032F50000100000001 /* MemojiPickerUI */, @@ -2349,6 +2358,9 @@ isa = XCSwiftPackageProductDependency; productName = InlineCLIInstaller; }; + F00200012FA2000100000001 /* InlineGrid */ = {isa = XCSwiftPackageProductDependency; productName = InlineGrid; }; + F00200032FA2000100000001 /* InlineGrid */ = {isa = XCSwiftPackageProductDependency; productName = InlineGrid; }; + F00200052FA2000100000001 /* InlineRTC */ = {isa = XCSwiftPackageProductDependency; productName = InlineRTC; }; /* End XCSwiftPackageProductDependency section */ }; rootObject = CD2660FC2CA01CAE00EB186F /* Project object */; diff --git a/apple/InlineIOS/ExperimentalRootView.swift b/apple/InlineIOS/ExperimentalRootView.swift index c634a5b5c..7dd9784ed 100644 --- a/apple/InlineIOS/ExperimentalRootView.swift +++ b/apple/InlineIOS/ExperimentalRootView.swift @@ -1,4 +1,5 @@ import Auth +import InlineGrid import InlineKit import InlineUI import Logger @@ -132,6 +133,8 @@ private struct ExperimentalAuthedRootView: View { @State private var lastContentRootTab: RootTab = .allChats @State private var pendingSearchExit: PendingSearchExit? @State private var isCreatingThread = false + @State private var isGridPresented = false + @AppStorage(ExperimentalFeatureFlags.gridIOSKey) private var gridIOSEnabled = false @State private var isCleaningOpenChats = false @State private var iPadCommandRequest: IPadCommandRequest? @State private var homeBootstrapTask: Task? @@ -456,6 +459,11 @@ private struct ExperimentalAuthedRootView: View { .toolbar { experimentalToolbarContent() } + .gridHomeEntry( + isPresented: $isGridPresented, + spaceID: nav.activeSpaceId, + spaces: compactSpaceList.spaces + ) .safeAreaInset(edge: .bottom, spacing: 0) { IPadSidebarSwitcher(selection: iPadSidebarScopeBinding) } @@ -476,6 +484,12 @@ private struct ExperimentalAuthedRootView: View { .toolbar { experimentalToolbarContent() } + .gridHomeEntry( + isPresented: $isGridPresented, + spaceID: nav.activeSpaceId, + spaces: compactSpaceList.spaces, + isVisible: !isSearchActivePresentation + ) .navigationDestination(for: Destination.self) { destination in ExperimentalDestinationView( nav: bindableNav, @@ -1191,7 +1205,11 @@ private struct ExperimentalAuthedRootView: View { }, onManage: activeSpace.map { space in { openHomeDestination(.spaceSettings(spaceId: space.id)) } - } + }, + onGrid: gridIOSEnabled + && GridHomeEntryProjection(store: GridRuntime.shared.rooms, spaceID: nil).showsMenuEntry + ? { isGridPresented = true } + : nil ) // Fill the iPad toolbar target without changing the phone toolbar layout. .frame(width: usesIPadSplitView ? 44 : 28, height: usesIPadSplitView ? 44 : 28) @@ -1404,6 +1422,7 @@ private struct ExperimentalOverflowMenuButton: UIViewRepresentable { let onInvite: () -> Void let onMembers: (() -> Void)? let onManage: (() -> Void)? + let onGrid: (() -> Void)? func makeUIView(context: Context) -> UIButton { let button = UIButton(type: .system) @@ -1422,6 +1441,11 @@ private struct ExperimentalOverflowMenuButton: UIViewRepresentable { } private func makeMenu() -> UIMenu { + let grid = onGrid.map { onGrid in + UIAction(title: String(localized: "Grid"), image: UIImage(systemName: "square.grid.2x2")) { _ in + onGrid() + } + } let archivedChats = UIAction( title: "Archived Chats", image: UIImage(systemName: "archivebox") @@ -1537,7 +1561,7 @@ private struct ExperimentalOverflowMenuButton: UIViewRepresentable { } return UIMenu( - children: [viewSection] + (cleanupSection.map { [$0] } ?? []) + [spaceSection] + children: (grid.map { [$0] } ?? []) + [viewSection] + (cleanupSection.map { [$0] } ?? []) + [spaceSection] ) } } diff --git a/apple/InlineIOS/Features/Grid/GridHomeEntry.swift b/apple/InlineIOS/Features/Grid/GridHomeEntry.swift new file mode 100644 index 000000000..f581b5e70 --- /dev/null +++ b/apple/InlineIOS/Features/Grid/GridHomeEntry.swift @@ -0,0 +1,166 @@ +import InlineGrid +import InlineKit +import InlineProtocol +import InlineUI +import SwiftUI + +/// Discovery is deliberately separate from call ownership. A bounded avatar +/// preview must never decide which device owns the user's current call. +@MainActor +struct GridHomeEntryProjection { + let isEligible: Bool + let avatars: [GridAvatar] + let activeCount: Int + + init(store: GridRoomService, spaceID: Int64?) { + let homes = spaceID.map { selectedID in + store.orderedHomeSpaces.filter { $0.spaceID == selectedID } + } ?? store.orderedHomeSpaces + isEligible = !homes.isEmpty + activeCount = homes.reduce(0) { $0 + Int($1.activeAvatarCount) } + var seen = Set() + var preview: [GridAvatar] = [] + for home in homes { + for avatar in home.recentAvatars where seen.insert(avatar.user.id).inserted { + preview.append(avatar) + if preview.count == 4 { break } + } + if preview.count == 4 { break } + } + avatars = preview + } + + var showsPill: Bool { isEligible && activeCount > 0 && !avatars.isEmpty } + var showsMenuEntry: Bool { isEligible && !showsPill } +} + +extension View { + func gridHomeEntry( + isPresented: Binding, + spaceID: Int64? = nil, + spaces: [InlineKit.Space] = [], + isVisible: Bool = true + ) -> some View { + modifier(GridHomeEntryModifier( + isPresented: isPresented, + spaceID: spaceID, + spaces: spaces, + isVisible: isVisible + )) + } +} + +private struct GridHomeEntryModifier: ViewModifier { + @Binding var isPresented: Bool + let spaceID: Int64? + let spaces: [InlineKit.Space] + let isVisible: Bool + + @AppStorage(ExperimentalFeatureFlags.gridIOSKey) private var isEnabled = false + + func body(content: Content) -> some View { + Group { + if isEnabled { + content.modifier(GridEnabledHomeEntryModifier( + isPresented: $isPresented, + spaceID: spaceID, + spaces: spaces, + isVisible: isVisible + )) + } else { + content + } + } + .onChange(of: isEnabled) { wasEnabled, enabled in + if wasEnabled && !enabled { + isPresented = false + } + } + } +} + +private struct GridEnabledHomeEntryModifier: ViewModifier { + @Binding var isPresented: Bool + let spaceID: Int64? + let spaces: [InlineKit.Space] + let isVisible: Bool + @Environment(\.scenePhase) private var scenePhase + private let store = GridRuntime.shared.rooms + + func body(content: Content) -> some View { + let projection = GridHomeEntryProjection(store: store, spaceID: nil) + let showsPill = isVisible && projection.showsPill + let presentation = Binding( + get: { isPresented && projection.isEligible }, + set: { isPresented = $0 } + ) + + Group { + if #available(iOS 26.0, *) { + content.safeAreaBar(edge: .top, spacing: 0) { + GridHomePill( + avatars: projection.avatars, + activeCount: projection.activeCount, + isVisible: showsPill, + onOpen: { isPresented = true } + ) + } + } else { + content.safeAreaInset(edge: .top, spacing: 0) { + GridHomePill( + avatars: projection.avatars, + activeCount: projection.activeCount, + isVisible: showsPill, + onOpen: { isPresented = true } + ) + } + } + } + .popover(isPresented: presentation, attachmentAnchor: .point(.top), arrowEdge: .top) { + GridRoomsView(store: store, spaces: spaces, initialSpaceID: spaceID) + .presentationCompactAdaptation(.sheet) + .presentationDetents([.height(280), .medium, .large]) + .presentationDragIndicator(.visible) + } + .task { await store.loadHome() } + .onChange(of: scenePhase) { _, phase in + if phase == .active { + Task { await store.loadHome() } + } + } + .onChange(of: projection.isEligible) { _, eligible in + if !eligible { isPresented = false } + } + } +} + +private struct GridHomePill: View { + let avatars: [GridAvatar] + let activeCount: Int + let isVisible: Bool + let onOpen: () -> Void + + var body: some View { + if isVisible { + Button(action: onOpen) { + HStack(spacing: -6) { + ForEach(avatars, id: \.user.id) { avatar in + UserAvatar(user: InlineKit.User(from: avatar.user), size: 22) + .overlay { Circle().stroke(.background, lineWidth: 1.5) } + } + } + .padding(.horizontal, 7) + .padding(.vertical, 4) + .background(.thinMaterial, in: Capsule()) + .contentShape(Capsule()) + .frame(minHeight: 44) + } + .buttonStyle(.plain) + .accessibilityElement(children: .ignore) + .accessibilityLabel("Grid, \(activeCount) people present") + .accessibilityHint("Open voice rooms") + .accessibilityIdentifier("gridHomePill") + .frame(maxWidth: .infinity) + } + } +} diff --git a/apple/InlineIOS/Features/Grid/GridRoomsView.swift b/apple/InlineIOS/Features/Grid/GridRoomsView.swift new file mode 100644 index 000000000..2838fbf59 --- /dev/null +++ b/apple/InlineIOS/Features/Grid/GridRoomsView.swift @@ -0,0 +1,633 @@ +import AVKit +import InlineAudioPlayback +import InlineGrid +import InlineKit +import InlineProtocol +import InlineRTC +import InlineUI +import SwiftUI +import UIKit + +struct GridRoomsView: View { + let store: GridRoomService + let spaces: [InlineKit.Space] + @State private var selectedSpaceID: Int64? + @State private var selectedShare: GridScreenShareSelection? + @State private var presentedFailure: GridActionFailure? + @Environment(\.dismiss) private var dismiss + + init(store: GridRoomService, spaces: [InlineKit.Space], initialSpaceID: Int64?) { + self.store = store + self.spaces = spaces + _selectedSpaceID = State(initialValue: initialSpaceID) + } + + private var effectiveSpaceID: Int64? { + if let selectedSpaceID, store.isEnabled(spaceID: selectedSpaceID) { + return selectedSpaceID + } + if let call = store.currentCall, store.isEnabled(spaceID: call.spaceID) { + return call.spaceID + } + return store.orderedHomeSpaces.first?.spaceID + } + + private var actionFailure: GridActionFailure? { + let hasConnectionFailure = store.media.connectionState == .failed + let connectionError = hasConnectionFailure ? store.media.lastConnectionError : nil + let audioError: String? + if case let .failed(message) = store.media.audioState { + audioError = message + } else { + audioError = nil + } + let storeError = store.membershipMutationInFlight ? nil : store.lastError + let errors = [storeError, connectionError, audioError].compactMap { $0 } + let hasMediaFailure = hasConnectionFailure || audioError != nil || store.media.providerCircuitOpen + let audio = InlineAudioSession.shared + if audio.isQuarantined && (store.hasLocalAdmission || !errors.isEmpty) + || errors.contains(InlineAudioSessionError.quarantined.localizedDescription) { + return .restartRequired + } + guard !errors.isEmpty || hasMediaFailure else { return nil } + if errors.contains(InlineAudioSessionError.recordingOwnsAudio.localizedDescription) { + return .recordingConflict + } + if errors.contains(InlineAudioSessionError.retirementPending.localizedDescription) { + return .retirementPending + } + return hasMediaFailure ? .audio : .update + } + + var body: some View { + NavigationStack { + VStack(spacing: 16) { + if let spaceID = effectiveSpaceID { + GridRoomCollection(store: store, spaceID: spaceID, onViewShare: { avatar, share in + guard let identity = store.mediaSessionIdentity else { return } + selectedShare = GridScreenShareSelection( + mediaSessionIdentity: identity, + participantIdentity: share.participantIdentity, + publicationID: share.publicationID, + displayName: InlineKit.User(from: avatar.user).displayName + ) + }) + + GridCurrentRoomControls(store: store, spaceID: store.currentCall?.spaceID ?? spaceID) + if !store.callTransferEnabled { + Text("Grid is unavailable") + .font(.footnote) + .foregroundStyle(.secondary) + } + } + } + .padding(20) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) + .navigationTitle("Grid") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .principal) { + GridSpacePicker( + homeSpaces: store.orderedHomeSpaces, + spaces: spaces, + selectedSpaceID: effectiveSpaceID, + onSelect: { selectedSpaceID = $0 } + ) + } + ToolbarItem(placement: .topBarTrailing) { + Button("Done", systemImage: "xmark") { dismiss() } + .labelStyle(.iconOnly) + .accessibilityLabel("Close Grid") + } + } + .task(id: effectiveSpaceID) { + if let spaceID = effectiveSpaceID { await store.load(spaceID: spaceID) } + } + .onChange(of: actionFailure, initial: true) { _, failure in + if let failure { + presentedFailure = failure + store.clearLastError() + } + } + .alert(presentedFailure?.title ?? "Unable to update Grid", isPresented: Binding( + get: { presentedFailure != nil }, + set: { if !$0 { presentedFailure = nil } } + ), presenting: presentedFailure) { failure in + if failure.canRetryAudio && store.hasLocalAdmission { + Button("Retry Audio") { + guard store.hasLocalAdmission, store.currentCall?.ownedByCurrentSession == true, + !InlineAudioSession.shared.isQuarantined else { return } + store.retryAudio() + } + .disabled(store.membershipMutationInFlight) + } else if failure == .update { + Button("Refresh") { + Task { + await store.loadHome() + if let spaceID = effectiveSpaceID { await store.load(spaceID: spaceID) } + } + } + } + if let call = store.currentCall, call.ownedByCurrentSession { + Button("Leave Grid", role: .destructive) { + if let current = store.currentCall, current.ownedByCurrentSession { + store.leaveCurrentRoom(spaceID: current.spaceID) + } + } + } + if failure == .recordingConflict || failure == .restartRequired { + Button("Close Grid", role: .cancel) { dismiss() } + } else { + Button("Cancel", role: .cancel) {} + } + } message: { failure in + Text(failure.message) + } + .fullScreenCover(item: $selectedShare) { selection in + GridScreenShareViewer(store: store, selection: selection) + } + } + .frame(idealWidth: 360, idealHeight: 280) + } +} + +/// Only known recovery messages reach the native surface. SDK and RPC error +/// descriptions can contain diagnostics that do not belong in the interface. +private enum GridActionFailure: Equatable { + case recordingConflict + case restartRequired + case retirementPending + case audio + case update + + var title: LocalizedStringKey { + self == .update ? "Unable to update Grid" : "Unable to start Grid audio" + } + + var message: String { + switch self { + case .recordingConflict: InlineAudioSessionError.recordingOwnsAudio.localizedDescription + case .restartRequired: InlineAudioSessionError.quarantined.localizedDescription + case .retirementPending: InlineAudioSessionError.retirementPending.localizedDescription + case .audio: String(localized: "Grid audio could not start. Try again.") + case .update: String(localized: "Grid could not be updated. Refresh and try again.") + } + } + + var canRetryAudio: Bool { self == .audio || self == .retirementPending } +} + +private struct GridSpacePicker: View { + let homeSpaces: [GridHomeSpace] + let spaces: [InlineKit.Space] + let selectedSpaceID: Int64? + let onSelect: (Int64) -> Void + + private func title(for spaceID: Int64) -> String { + spaces.first { $0.id == spaceID }?.displayName ?? String(localized: "Grid") + } + + var body: some View { + if homeSpaces.count > 1 { + Menu { + ForEach(homeSpaces, id: \.spaceID) { home in + Button { + onSelect(home.spaceID) + } label: { + if home.spaceID == selectedSpaceID { + Label(title(for: home.spaceID), systemImage: "checkmark") + } else { + Text(title(for: home.spaceID)) + } + } + } + } label: { + HStack(spacing: 4) { + Text(selectedSpaceID.map(title(for:)) ?? String(localized: "Grid")) + .lineLimit(1) + Image(systemName: "chevron.down").font(.caption2) + } + .font(.headline) + .foregroundStyle(.primary) + } + .accessibilityLabel("Choose a space for Grid") + } else { + Text(selectedSpaceID.map(title(for:)) ?? String(localized: "Grid")) + .font(.headline) + .lineLimit(1) + } + } +} + +private struct GridRoomCollection: View { + let store: GridRoomService + let spaceID: Int64 + let onViewShare: (GridAvatar, InlineRTCScreenShare) -> Void + + var body: some View { + ScrollView { + if let grid = store.grid(spaceID: spaceID) { + GridRoomFlowLayout(spacing: 12) { + ForEach(grid.rooms, id: \.id) { room in + GridRoomCapsule(store: store, room: room, onViewShare: onViewShare) + } + Button { + store.createAndJoin(spaceID: spaceID) + } label: { + Image(systemName: "plus") + .font(.body.weight(.medium)) + .foregroundStyle(.secondary) + .frame(width: 62, height: 62) + .background(.quaternary.opacity(0.5), in: RoundedRectangle(cornerRadius: 16)) + } + .buttonStyle(.plain) + .disabled(store.membershipMutationInFlight || !store.callTransferEnabled + || store.currentCall?.ownedByCurrentSession == false) + .accessibilityLabel("Create and join a Grid room") + } + .padding(.top, 8) + } else if store.failedLoadSpaceIDs.contains(spaceID) { + Button("Try Again") { Task { await store.load(spaceID: spaceID) } } + .frame(maxWidth: .infinity, minHeight: 80) + } else { + ProgressView().frame(maxWidth: .infinity, minHeight: 80) + } + } + } +} + +private struct GridRoomCapsule: View { + let store: GridRoomService + let room: GridRoom + let onViewShare: (GridAvatar, InlineRTCScreenShare) -> Void + + private var isCurrent: Bool { + store.currentCall?.roomID == room.id && store.currentCall?.ownedByCurrentSession == true + && store.hasLocalAdmission + } + + var body: some View { + Group { + if isCurrent { + GridRoomAvatars(store: store, avatars: room.avatars, isCurrent: true, onViewShare: onViewShare) + } else { + Button { + if store.currentCall?.roomID == room.id { + store.moveCallHere() + } else { + store.join(roomID: room.id) + } + } label: { + GridRoomAvatars(store: store, avatars: room.avatars, isCurrent: false, onViewShare: onViewShare) + } + .buttonStyle(.plain) + .disabled(store.membershipMutationInFlight || !store.callTransferEnabled + || (room.locked && store.currentCall?.roomID != room.id) + || (store.currentCall?.ownedByCurrentSession == false && store.currentCall?.roomID != room.id)) + .accessibilityLabel(joinAccessibilityLabel) + } + } + .padding(8) + .frame(minWidth: 62, minHeight: 62) + .background( + isCurrent ? Color.accentColor.opacity(0.12) : Color.primary.opacity(0.045), + in: RoundedRectangle(cornerRadius: 16) + ) + .overlay { + RoundedRectangle(cornerRadius: 16) + .stroke(isCurrent ? Color.accentColor.opacity(0.55) : Color.primary.opacity(0.07), lineWidth: 1) + } + .overlay(alignment: .top) { + if room.hasTitle { + Text(room.title) + .font(.caption2) + .lineLimit(1) + .padding(.horizontal, 6) + .padding(.vertical, 2) + .background(.regularMaterial, in: Capsule()) + .offset(y: -8) + } + } + .overlay(alignment: .bottomTrailing) { + if room.locked { + Image(systemName: "lock.fill") + .font(.caption2) + .foregroundStyle(.secondary) + .padding(5) + .accessibilityLabel("Locked room") + } + } + } + + private var joinAccessibilityLabel: String { + if let call = store.currentCall, call.roomID == room.id { + return call.ownedByCurrentSession ? String(localized: "Resume Grid here") : String(localized: "Move Grid here") + } + return room.hasTitle ? String(localized: "Join \(room.title)") : String(localized: "Join Grid room") + } +} + +private struct GridRoomAvatars: View { + let store: GridRoomService + let avatars: [GridAvatar] + let isCurrent: Bool + let onViewShare: (GridAvatar, InlineRTCScreenShare) -> Void + + var body: some View { + HStack(spacing: 4) { + ForEach(avatars.prefix(4), id: \.user.id) { avatar in + GridRoomAvatar(store: store, avatar: avatar, isCurrent: isCurrent, onViewShare: onViewShare) + } + if avatars.count > 4 { + GridRoomAvatarOverflow( + store: store, + avatars: Array(avatars.dropFirst(4)), + isCurrent: isCurrent, + onViewShare: onViewShare + ) + } + } + .frame(minWidth: 44, minHeight: 44) + } +} + +private struct GridRoomAvatarOverflow: View { + let store: GridRoomService + let avatars: [GridAvatar] + let isCurrent: Bool + let onViewShare: (GridAvatar, InlineRTCScreenShare) -> Void + + var body: some View { + Menu { + ForEach(avatars, id: \.user.id) { avatar in + if isCurrent { + ForEach(store.screenShares(avatar: avatar)) { share in + Button { + onViewShare(avatar, share) + } label: { + Label("View \(InlineKit.User(from: avatar.user).displayName)’s screen", systemImage: "rectangle.on.rectangle") + } + } + } + } + } label: { + Text("+\(avatars.count)") + .font(.caption.monospacedDigit()) + .foregroundStyle(.secondary) + .frame(width: 30, height: 44) + } + .disabled(!isCurrent || !avatars.contains { !store.screenShares(avatar: $0).isEmpty }) + .accessibilityLabel("\(avatars.count) more people") + } +} + +private struct GridRoomAvatar: View { + let store: GridRoomService + let avatar: GridAvatar + let isCurrent: Bool + let onViewShare: (GridAvatar, InlineRTCScreenShare) -> Void + + var body: some View { + let level = isCurrent ? store.audioLevel(userID: avatar.user.id) : 0 + let shares = isCurrent ? store.screenShares(avatar: avatar) : [] + + UserAvatar(user: InlineKit.User(from: avatar.user), size: 42) + .padding(2) + .overlay { + Circle().stroke(level > 0.01 ? Color.green : .clear, lineWidth: 1.5) + } + .overlay(alignment: .bottomTrailing) { + if !shares.isEmpty { + Menu { + ForEach(shares) { share in + Button("View Screen", systemImage: "rectangle.on.rectangle") { + onViewShare(avatar, share) + } + } + } label: { + Image(systemName: "rectangle.on.rectangle.fill") + .font(.caption2) + .padding(4) + .background(.regularMaterial, in: Circle()) + .frame(minWidth: 44, minHeight: 44, alignment: .bottomTrailing) + } + .accessibilityLabel("View \(InlineKit.User(from: avatar.user).displayName)’s screen") + } + } + .accessibilityLabel(InlineKit.User(from: avatar.user).displayName) + } +} + +struct GridCurrentRoomControls: View { + let store: GridRoomService + let spaceID: Int64 + + var body: some View { + if let call = store.currentCall, call.spaceID == spaceID { + if call.ownedByCurrentSession && store.hasLocalAdmission { + GridLocalAudioControls(store: store, spaceID: spaceID) + } else { + HStack(spacing: 16) { + Button(call.ownedByCurrentSession ? "Resume here" : "Move here", systemImage: "arrow.down.to.line") { + store.moveCallHere() + } + .buttonStyle(.bordered) + .disabled(store.membershipMutationInFlight || !store.callTransferEnabled) + .accessibilityHint("Move your active Grid call to this device") + + if store.membershipMutationInFlight { + Button("Cancel") { store.withdrawLocalAdmission() } + } else if call.ownedByCurrentSession { + Button(role: .destructive) { + store.leaveCurrentRoom(spaceID: spaceID) + } label: { + Image(systemName: "phone.down.fill").frame(width: 44, height: 44) + } + .accessibilityLabel("Leave Grid room") + } + } + } + } else if store.membershipMutationInFlight { + Button("Cancel") { store.withdrawLocalAdmission() } + .accessibilityLabel("Cancel joining Grid") + } + } +} + +private struct GridLocalAudioControls: View { + private enum AudioAlert: Equatable { + case microphonePermission + case outputRoute + } + + let store: GridRoomService + let spaceID: Int64 + private let audio = InlineAudioSession.shared + @State private var requestedUnmute = false + @State private var audioAlert: AudioAlert? + @Environment(\.openURL) private var openURL + + private var canUnmute: Bool { + guard !store.media.isAudioSafetyPaused, + let roomID = store.currentCall?.roomID, + let room = store.grid(spaceID: spaceID)?.rooms.first(where: { $0.id == roomID }) + else { return false } + return room.avatars.count > 1 + } + + var body: some View { + VStack(spacing: 8) { + if store.media.isAudioSafetyPaused { + Button("Resume Audio", systemImage: "play.fill") { store.retryAudio() } + .buttonStyle(.bordered) + .disabled(store.membershipMutationInFlight) + } + + audioControls + } + .frame(maxWidth: .infinity) + .onChange(of: store.media.microphonePermission) { _, permission in + if requestedUnmute && (permission == .denied || permission == .restricted) { + audioAlert = .microphonePermission + requestedUnmute = false + } else if permission == .authorized { + requestedUnmute = false + } + } + .alert(alertTitle, isPresented: Binding( + get: { audioAlert != nil }, + set: { if !$0 { audioAlert = nil } } + )) { + if audioAlert == .microphonePermission { + Button("Open Settings") { + if let url = URL(string: UIApplication.openSettingsURLString) { openURL(url) } + } + Button("Cancel", role: .cancel) {} + } else { + Button("OK", role: .cancel) {} + } + } + } + + private var audioControls: some View { + HStack(spacing: 16) { + Button { + if !store.media.isMicrophoneEnabled { + if store.media.microphonePermission == .denied || store.media.microphonePermission == .restricted { + audioAlert = .microphonePermission + return + } + requestedUnmute = true + } else { + requestedUnmute = false + } + store.toggleMicrophone(spaceID: spaceID) + } label: { + Image(systemName: store.media.isMicrophoneEnabled ? "mic.fill" : "mic.slash.fill") + .frame(width: 44, height: 44) + .background(.thinMaterial, in: Circle()) + } + .disabled((!canUnmute || store.membershipMutationInFlight) && !store.media.isMicrophoneEnabled) + .accessibilityLabel(store.media.isMicrophoneEnabled + ? String(localized: "Mute microphone") + : String(localized: "Unmute microphone")) + + if audio.supportsReceiverRouting { + Button { + let preferred = !audio.isSpeakerOutputActive + Task { @MainActor in + do { + try await audio.setSpeakerPreferred(preferred) + } catch { + if !(error is CancellationError) { audioAlert = .outputRoute } + } + } + } label: { + Image(systemName: audio.isSpeakerOutputActive ? "speaker.wave.2.fill" : "speaker.wave.2") + .frame(width: 44, height: 44) + .background(.thinMaterial, in: Circle()) + } + .accessibilityLabel(audio.isSpeakerOutputActive + ? String(localized: "Turn off speaker") + : String(localized: "Use speaker")) + .accessibilityValue(audio.currentRouteName) + } + + GridNativeAudioRoutePicker(routeName: audio.currentRouteName) + .frame(width: 44, height: 44) + + Button(role: .destructive) { + store.leaveCurrentRoom(spaceID: spaceID) + } label: { + Image(systemName: "phone.down.fill") + .frame(width: 44, height: 44) + .background(.thinMaterial, in: Circle()) + } + .accessibilityLabel("Leave Grid room") + } + .buttonStyle(.plain) + } + + private var alertTitle: LocalizedStringKey { + audioAlert == .microphonePermission ? "Microphone access is off" : "Unable to change audio output" + } +} + +private struct GridNativeAudioRoutePicker: UIViewRepresentable { + let routeName: String + + func makeUIView(context: Context) -> AVRoutePickerView { + let view = AVRoutePickerView() + view.prioritizesVideoDevices = false + view.tintColor = .label + view.activeTintColor = UIColor(Color.accentColor) + view.accessibilityLabel = String(localized: "Audio output") + view.accessibilityValue = routeName + return view + } + + func updateUIView(_ uiView: AVRoutePickerView, context: Context) { + uiView.accessibilityValue = routeName + } +} + +private struct GridRoomFlowLayout: Layout { + let spacing: CGFloat + + func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize { + let width = proposal.width ?? .infinity + var x: CGFloat = 0 + var y: CGFloat = 0 + var rowHeight: CGFloat = 0 + var usedWidth: CGFloat = 0 + for view in subviews { + let size = view.sizeThatFits(.unspecified) + if x > 0 && x + size.width > width { + y += rowHeight + spacing + x = 0 + rowHeight = 0 + } + usedWidth = max(usedWidth, x + size.width) + x += size.width + spacing + rowHeight = max(rowHeight, size.height) + } + return CGSize(width: proposal.width ?? usedWidth, height: y + rowHeight) + } + + func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) { + var x = bounds.minX + var y = bounds.minY + var rowHeight: CGFloat = 0 + for view in subviews { + let size = view.sizeThatFits(.unspecified) + if x > bounds.minX && x + size.width > bounds.maxX { + y += rowHeight + spacing + x = bounds.minX + rowHeight = 0 + } + view.place(at: CGPoint(x: x, y: y), proposal: ProposedViewSize(size)) + x += size.width + spacing + rowHeight = max(rowHeight, size.height) + } + } +} diff --git a/apple/InlineIOS/Features/Grid/GridScreenShareViewer.swift b/apple/InlineIOS/Features/Grid/GridScreenShareViewer.swift new file mode 100644 index 000000000..a73ff00cb --- /dev/null +++ b/apple/InlineIOS/Features/Grid/GridScreenShareViewer.swift @@ -0,0 +1,119 @@ +import InlineGrid +import InlineRTC +import SwiftUI +import UIKit + +struct GridScreenShareSelection: Identifiable { + let mediaSessionIdentity: String + let participantIdentity: String + let publicationID: String + let displayName: String + var id: String { "\(mediaSessionIdentity):\(publicationID)" } +} + +struct GridScreenShareViewer: View { + let store: GridRoomService + let selection: GridScreenShareSelection + @Environment(\.dismiss) private var dismiss + @Environment(\.scenePhase) private var scenePhase + + private var selectedShare: InlineRTCScreenShare? { + guard store.mediaSessionIdentity == selection.mediaSessionIdentity, + let share = store.screenShare(publicationID: selection.publicationID), + share.participantIdentity == selection.participantIdentity + else { return nil } + return share + } + + var body: some View { + NavigationStack { + GridVideoRenderer(screenShare: selectedShare, isVideoEnabled: scenePhase == .active) + .background(.black) + .navigationTitle(selection.displayName) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("Done", systemImage: "xmark") { dismiss() } + .labelStyle(.iconOnly) + .accessibilityLabel("Close shared screen") + } + } + .safeAreaInset(edge: .bottom) { + if let call = store.currentCall { + GridCurrentRoomControls(store: store, spaceID: call.spaceID) + .padding(.vertical, 8) + .background(.regularMaterial) + } + } + .onChange(of: selectedShare?.publicationID) { _, publicationID in + if publicationID == nil { dismiss() } + } + .onAppear { + if selectedShare == nil { dismiss() } + } + .onChange(of: store.mediaSessionIdentity) { _, identity in + if identity != selection.mediaSessionIdentity { dismiss() } + } + } + } +} + +private struct GridVideoRenderer: UIViewRepresentable { + let screenShare: InlineRTCScreenShare? + let isVideoEnabled: Bool + + func makeUIView(context: Context) -> GridVideoScrollView { + let view = GridVideoScrollView() + view.videoView.screenShare = screenShare + view.videoView.isVideoEnabled = isVideoEnabled + return view + } + + func updateUIView(_ uiView: GridVideoScrollView, context: Context) { + if uiView.videoView.screenShare?.publicationID != screenShare?.publicationID { + uiView.setZoomScale(1, animated: false) + } + uiView.videoView.screenShare = screenShare + uiView.videoView.isVideoEnabled = isVideoEnabled + } + + static func dismantleUIView(_ uiView: GridVideoScrollView, coordinator: ()) { + uiView.videoView.isVideoEnabled = false + uiView.videoView.screenShare = nil + } +} + +private final class GridVideoScrollView: UIScrollView, UIScrollViewDelegate { + let videoView = InlineRTCVideoView(frame: .zero) + private var viewportSize: CGSize = .zero + + init() { + super.init(frame: .zero) + delegate = self + minimumZoomScale = 1 + maximumZoomScale = 4 + showsHorizontalScrollIndicator = false + showsVerticalScrollIndicator = false + contentInsetAdjustmentBehavior = .never + backgroundColor = .black + addSubview(videoView) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + guard bounds.size != viewportSize else { return } + viewportSize = bounds.size + setZoomScale(1, animated: false) + videoView.frame = CGRect(origin: .zero, size: viewportSize) + contentSize = viewportSize + } + + func viewForZooming(in scrollView: UIScrollView) -> UIView? { + videoView + } +} diff --git a/apple/InlineIOS/Features/Settings/ExperimentalView.swift b/apple/InlineIOS/Features/Settings/ExperimentalView.swift index ec691d251..91a7bc87c 100644 --- a/apple/InlineIOS/Features/Settings/ExperimentalView.swift +++ b/apple/InlineIOS/Features/Settings/ExperimentalView.swift @@ -6,6 +6,9 @@ struct ExperimentalView: View { @AppStorage(ExperimentalFeatureFlags.agentActivityKey) private var agentActivityEnabled = false + @AppStorage(ExperimentalFeatureFlags.gridIOSKey) + private var gridIOSEnabled = false + @AppStorage(ExperimentalFeatureFlags.nativeFileDownloadsKey) private var nativeFileDownloadsEnabled = false @@ -17,6 +20,17 @@ struct ExperimentalView: View { var body: some View { List { + Section { + SettingsItem(icon: "waveform", iconColor: .green, title: "Grid") { + Toggle("Grid", isOn: $gridIOSEnabled) + .labelsHidden() + } + } header: { + Text("Audio") + } footer: { + Text("Join voice rooms and view shared screens in spaces with Grid enabled.") + } + // Use the same purpose-based groups as macOS; see AGENTS.md when adding experiments. Section { Toggle("Agent Activity", isOn: $agentActivityEnabled) diff --git a/apple/InlineIOS/Info.plist b/apple/InlineIOS/Info.plist index c632c5c78..f86c84eb1 100644 --- a/apple/InlineIOS/Info.plist +++ b/apple/InlineIOS/Info.plist @@ -55,7 +55,7 @@ NSDocumentsFolderUsageDescription Inline needs access to documents to share files in your conversations. NSMicrophoneUsageDescription - Inline needs access to your microphone to record audio messages and videos. + Inline needs access to your microphone to speak in Grid and record audio messages and videos. NSPhotoLibraryUsageDescription Inline needs access to your photo library to share photos and videos in your conversations. PHPhotoLibraryPreventAutomaticLimitedAccessAlert @@ -66,6 +66,7 @@ UIBackgroundModes + audio remote-notification audio diff --git a/apple/InlineIOS/InlineApp.swift b/apple/InlineIOS/InlineApp.swift index 99c633b46..d448f55a6 100644 --- a/apple/InlineIOS/InlineApp.swift +++ b/apple/InlineIOS/InlineApp.swift @@ -1,5 +1,6 @@ import Auth import Foundation +import InlineGrid import InlineKit import Sentry import SwiftUI @@ -39,6 +40,7 @@ private struct InlineSceneRoot: View { @State private var sceneID = UUID() @State private var didRestoreScene = false @ObservedObject private var auth = Auth.shared + @AppStorage(ExperimentalFeatureFlags.gridIOSKey) private var gridEnabled = false @SceneStorage("ios.navigation.routerState.v1") private var routerState: Data? @Environment(\.scenePhase) private var scenePhase @@ -110,6 +112,11 @@ private struct InlineSceneRoot: View { appDelegate.resumePendingSpaceJoin(router: router) } } + .onChange(of: gridEnabled) { _, enabled in + if !enabled { + GridRuntime.existing?.rooms.withdrawLocalAdmission() + } + } .onChange(of: router.persistenceRevision) { _, _ in guard didRestoreScene else { return } routerState = router.encodedPersistentState() diff --git a/apple/InlineIOS/MainViews/HomeToolbarContent.swift b/apple/InlineIOS/MainViews/HomeToolbarContent.swift index aab76d0ec..86c653664 100644 --- a/apple/InlineIOS/MainViews/HomeToolbarContent.swift +++ b/apple/InlineIOS/MainViews/HomeToolbarContent.swift @@ -1,3 +1,4 @@ +import InlineGrid import InlineKit import InlineUI import RealtimeV2 @@ -8,15 +9,19 @@ struct HomeToolbarContent: ToolbarContent { private let router: Router @ObservedObject private var realtimeState: RealtimeState @ObservedObject private var notificationSettings: NotificationSettingsManager + @AppStorage(ExperimentalFeatureFlags.gridIOSKey) private var gridIOSEnabled = false + private let onOpenGrid: () -> Void init( router: Router, realtimeState: RealtimeState, - notificationSettings: NotificationSettingsManager + notificationSettings: NotificationSettingsManager, + onOpenGrid: @escaping () -> Void ) { self.router = router _realtimeState = ObservedObject(wrappedValue: realtimeState) _notificationSettings = ObservedObject(wrappedValue: notificationSettings) + self.onOpenGrid = onOpenGrid } var body: some ToolbarContent { @@ -64,6 +69,11 @@ struct HomeToolbarContent: ToolbarContent { @ViewBuilder private var dotsButton: some View { Menu { + if gridIOSEnabled, + GridHomeEntryProjection(store: GridRuntime.shared.rooms, spaceID: nil).showsMenuEntry { + Button("Grid", systemImage: "square.grid.2x2", action: onOpenGrid) + } + Button { router.presentSheet(.inviteToInline) } label: { diff --git a/apple/InlineIOS/MainViews/HomeView.swift b/apple/InlineIOS/MainViews/HomeView.swift index 413e856c2..6f540f294 100644 --- a/apple/InlineIOS/MainViews/HomeView.swift +++ b/apple/InlineIOS/MainViews/HomeView.swift @@ -1,3 +1,4 @@ +import InlineGrid import InlineKit import InlineSearch import InlineUI @@ -23,6 +24,7 @@ struct HomeView: View { @State private var text = "" @State private var searchModel: InlineSearchViewModel? + @State private var isGridPresented = false private var chatItems: [HomeChatItem] { let visibleChats = home.chats.filter { $0.dialog.archived != true } @@ -40,9 +42,15 @@ struct HomeView: View { HomeToolbarContent( router: router, realtimeState: realtimeState, - notificationSettings: notificationSettings + notificationSettings: notificationSettings, + onOpenGrid: { isGridPresented = true } ) } + .gridHomeEntry( + isPresented: $isGridPresented, + spaces: home.spaces.map(\.space), + isVisible: text.isEmpty + ) .navigationBarTitleDisplayMode(.inline) .navigationBarBackButtonHidden() .onAppear { diff --git a/apple/InlineIOS/Shared/VoiceInputPicker.swift b/apple/InlineIOS/Shared/VoiceInputPicker.swift index 53e4e3841..dbf974d95 100644 --- a/apple/InlineIOS/Shared/VoiceInputPicker.swift +++ b/apple/InlineIOS/Shared/VoiceInputPicker.swift @@ -1,5 +1,6 @@ import AVFoundation import Combine +import InlineAudioPlayback import Logger import SwiftUI @@ -130,6 +131,7 @@ final class VoiceInputController: ObservableObject { @discardableResult func applyPreferredInput(to session: AVAudioSession = .sharedInstance()) throws -> VoiceInputState { + guard !InlineAudioSession.shared.isGridActive else { throw InlineAudioSessionError.gridOwnsAudio } guard let preferredDeviceId = activePreferredDeviceId else { log.debug("apply preferred=nil before \(routeSnapshot(for: session))") try session.setPreferredInput(nil) @@ -155,6 +157,7 @@ final class VoiceInputController: ObservableObject { persistence: VoiceInputPreferencePersistence = .persistent, in session: AVAudioSession = .sharedInstance() ) throws -> VoiceInputState { + guard !InlineAudioSession.shared.isGridActive else { throw InlineAudioSessionError.gridOwnsAudio } let unchanged: Bool switch persistence { case .persistent: @@ -205,6 +208,7 @@ final class VoiceInputController: ObservableObject { persistence: VoiceInputPreferencePersistence = .persistent, in session: AVAudioSession = .sharedInstance() ) throws -> VoiceInputState { + guard !InlineAudioSession.shared.isGridActive else { throw InlineAudioSessionError.gridOwnsAudio } let currentState = inputState(for: session) if deviceId == pendingDeviceId, currentState.selectedDeviceId != deviceId { diff --git a/apple/InlineIOS/Utils/LogoutPerformer.swift b/apple/InlineIOS/Utils/LogoutPerformer.swift index e21b2c6c5..55a87047b 100644 --- a/apple/InlineIOS/Utils/LogoutPerformer.swift +++ b/apple/InlineIOS/Utils/LogoutPerformer.swift @@ -1,4 +1,5 @@ import Auth +import InlineGrid import InlineKit import Logger import UIKit @@ -29,6 +30,17 @@ enum LogoutPerformer { // Fence playback and pending download intent before the first suspension. SharedAudioPlayer.shared.stop() + // Close media admission before logout's first suspension. Account fencing + // alone does not stop a sender already running in the RTC engine. + if let grid = GridRuntime.existing { + grid.rooms.withdrawLocalAdmission() + let mediaShutdown = await grid.prepareForLogout() + guard mediaShutdown.isLocallyQuiescent else { + Log.shared.error("iOS logout stopped: Grid local media shutdown could not be proven") + return + } + } + (UIApplication.shared.delegate as? AppDelegate)?.cancelPendingSpaceJoin() ProviderSignInCoordinator.shared.cancelPendingAttempt() diff --git a/apple/InlineKit/Package.swift b/apple/InlineKit/Package.swift index 9fa36e150..155904c1e 100644 --- a/apple/InlineKit/Package.swift +++ b/apple/InlineKit/Package.swift @@ -56,6 +56,10 @@ let package = Package( name: "InlineAudioPlayback", targets: ["InlineAudioPlayback"] ), + .library( + name: "InlineGrid", + targets: ["InlineGrid"] + ), .library( name: "InlineRTC", targets: ["InlineRTC"] @@ -133,12 +137,19 @@ let package = Package( swiftSettings: swiftSettings ), + .target( + name: "InlineGrid", + dependencies: ["InlineKit", "InlineRTC", "InlineAudioPlayback", "InlineProtocol", "RealtimeV2", "Auth", "Logger"], + swiftSettings: swiftSettings + ), + .target( name: "InlineRTC", dependencies: [ .product(name: "Atomics", package: "swift-atomics"), .product(name: "LiveKit", package: "client-sdk-swift"), "Logger", + "InlineAudioPlayback", ], // Retain failed and superseded audio backends as source-only archives. // The compiled macOS path is the directional AUHAL RTCAudioDevice. @@ -263,6 +274,12 @@ let package = Package( swiftSettings: swiftSettings ), + .testTarget( + name: "InlineGridTests", + dependencies: ["InlineGrid", "InlineProtocol", "InlineRTC", "Auth", "RealtimeV2"], + swiftSettings: swiftSettings + ), + .testTarget( name: "InlineRTCTests", dependencies: ["InlineRTC"], diff --git a/apple/InlineKit/Sources/Auth/Auth2Types.swift b/apple/InlineKit/Sources/Auth/Auth2Types.swift index 6c3764fc4..622599c04 100644 --- a/apple/InlineKit/Sources/Auth/Auth2Types.swift +++ b/apple/InlineKit/Sources/Auth/Auth2Types.swift @@ -10,6 +10,45 @@ public extension Notification.Name { ) } +extension Notification.Name { + /// Posted on MainActor after staging closes mutation admission, before replacement writes. + /// The object identifies the exact Auth cache and the payload carries its retirement request. + static let authAccountAuthorityWillChange = Notification.Name("inline.auth.accountAuthorityWillChange") +} + +/// A single staged authority transition. Process owners withdraw synchronously, then attach +/// their local retirement acknowledgement; Auth awaits it before replacing credentials. +@MainActor +public final class AuthAccountAuthorityRetirement { + public let previousAccount: AuthAccountMutationToken + private let stagingIsCurrent: @Sendable () -> Bool + private var retirement: Task? + + init(previousAccount: AuthAccountMutationToken, isCurrent: @escaping @Sendable () -> Bool) { + self.previousAccount = previousAccount + stagingIsCurrent = isCurrent + } + + public var isCurrent: Bool { stagingIsCurrent() } + + public func requireRetirement(_ task: Task) { + if let previous = retirement { + retirement = Task { + let previousStopped = await previous.value + let stopped = await task.value + return previousStopped && stopped + } + } else { + retirement = task + } + } + + func waitForRetirement() async -> Bool { + guard let retirement else { return true } + return await retirement.value + } +} + public struct InlineProtocolSessionCredentials: Sendable, Codable, Equatable { public var userId: Int64 public var accountSessionId: Int64 @@ -343,6 +382,24 @@ public struct AuthHandle: Sendable { try cache.validateAccountMutationToken(token) } + /// Stops process-owned work before a login or credential write replaces server authority. + /// Delivery is synchronous on MainActor and scoped to this handle's Auth store. The handler + /// must attach any required retirement task before returning. + @MainActor + public func observeAccountAuthorityWillChange( + _ handler: @escaping @MainActor @Sendable (AuthAccountAuthorityRetirement) -> Void + ) -> NSObjectProtocol { + NotificationCenter.default.addObserver( + forName: .authAccountAuthorityWillChange, object: cache, queue: .main + ) { notification in + guard let retirement = notification.userInfo?["retirement"] as? AuthAccountAuthorityRetirement else { return } + MainActor.assumeIsolated { + guard retirement.isCurrent else { return } + handler(retirement) + } + } + } + @discardableResult public func cancelLoginAttempt(_ attempt: AuthLoginAttempt) -> Bool { cache.invalidateLoginAttempt(attempt) diff --git a/apple/InlineKit/Sources/Auth/AuthStore.swift b/apple/InlineKit/Sources/Auth/AuthStore.swift index 8825c147d..421139678 100644 --- a/apple/InlineKit/Sources/Auth/AuthStore.swift +++ b/apple/InlineKit/Sources/Auth/AuthStore.swift @@ -294,7 +294,7 @@ actor AuthStore { status: .authenticated(record), didHydrate: true ) - try persistCredentialAuthority(snapshot: snapshot, loginAttempt: loginAttempt) { + try await persistCredentialAuthority(snapshot: snapshot, loginAttempt: loginAttempt) { try self.replaceStoredAuthority( with: [ Self.legacyTokenKey: Data(token.utf8), @@ -365,7 +365,7 @@ actor AuthStore { log.info("AUTH2_TEMPORARY_ROTATION persisted") return } - try persistCredentialAuthority(snapshot: snapshot, loginAttempt: loginAttempt) { + try await persistCredentialAuthority(snapshot: snapshot, loginAttempt: loginAttempt) { try self.replaceStoredAuthority( with: [Self.inlineProtocolCredentialsKey: data], replacing: [Self.legacyTokenKey, Self.credentialsV2Key], @@ -696,12 +696,25 @@ actor AuthStore { snapshot: AuthSnapshot, loginAttempt: AuthLoginAttempt?, writeAuthority: () throws -> Void - ) throws { + ) async throws { let finalizeImmediately = loginAttempt == nil let currentSnapshot = cache.snapshot() let preservesAccountMutationGeneration = finalizeImmediately && currentSnapshot.isLoggedIn && currentSnapshot.currentUserId == snapshot.currentUserId + let identicalImplicitBearer: Bool + if loginAttempt == nil, + case let .authenticated(current) = currentSnapshot.status, + case let .authenticated(next) = snapshot.status + { + identicalImplicitBearer = current.userId == next.userId && current.token == next.token + } else { + identicalImplicitBearer = false + } + // Preserve the existing mutation-generation policy, but require a physical retirement + // whenever server authority may change. Only an identical implicit bearer is provably + // unchanged here; same-session native temporary-key renewal bypasses this helper above. + let previousAccount = identicalImplicitBearer ? nil : try? cache.makeAccountMutationToken() let authorityAttempt: AuthLoginAttempt if let loginAttempt { authorityAttempt = loginAttempt @@ -741,6 +754,28 @@ actor AuthStore { stagedAuthorityBaseline = baseline do { + if let previousAccount { + // An already-running media engine does not perform account mutations on every frame. + // Retire its old authority before storage/projection work can suspend a native login. + let retirement = await MainActor.run { + let retirement = AuthAccountAuthorityRetirement(previousAccount: previousAccount) { + self.cache.isStagedAuthorityOwned(by: authorityAttempt) + && self.cache.isLoginAttemptCurrent(authorityAttempt) + } + NotificationCenter.default.post( + name: .authAccountAuthorityWillChange, + object: self.cache, + userInfo: ["retirement": retirement] + ) + return retirement + } + guard await retirement.waitForRetirement() else { throw AuthStorageError.loginUnavailable } + } + // The MainActor delivery can let logout or a newer login win on this actor. + try validateLoginAttempt(authorityAttempt) + guard cache.isStagedAuthorityOwned(by: authorityAttempt) else { + throw credentialCommitFenceError(for: authorityAttempt) + } try writeAuthority() if let userID = snapshot.currentUserId { UserDefaults.standard.set(NSNumber(value: userID), forKey: userDefaultsKey) @@ -757,7 +792,7 @@ actor AuthStore { _ = try finalizeCredentialsCommittedByLoginAttempt(authorityAttempt) } } catch { - if hasPendingLogout() == false { + if hasPendingLogout() == false, cache.isStagedAuthorityOwned(by: authorityAttempt) { do { try restoreStoredAuthority(baseline) UserDefaults.standard.removeObject(forKey: loginCommitPendingKey) @@ -773,7 +808,9 @@ actor AuthStore { "AUTH2_LOGIN failed to restore prior authority; retaining transition fence", error: error ) - promoteFailedLoginToRecovery(correlationID: authorityAttempt.correlationID, reason: "authority_restore_failed") + if hasPendingLogout() == false, cache.isStagedAuthorityOwned(by: authorityAttempt) { + promoteFailedLoginToRecovery(correlationID: authorityAttempt.correlationID, reason: "authority_restore_failed") + } } } throw error diff --git a/apple/InlineKit/Sources/InlineAudioPlayback/InlineAudioSession.swift b/apple/InlineKit/Sources/InlineAudioPlayback/InlineAudioSession.swift index cd676435c..fcfc9a9e0 100644 --- a/apple/InlineKit/Sources/InlineAudioPlayback/InlineAudioSession.swift +++ b/apple/InlineKit/Sources/InlineAudioPlayback/InlineAudioSession.swift @@ -1,4 +1,5 @@ import Foundation +import Observation #if os(iOS) import AVFAudio #endif @@ -6,6 +7,7 @@ import AVFAudio /// A generation fence for the few Inline features that share the process audio session. /// It does not own players or decide when they should resume. @MainActor +@Observable public final class InlineAudioSession { public static let shared = InlineAudioSession() @@ -19,23 +21,60 @@ public final class InlineAudioSession { } public private(set) var current: Token? - public var willAcquire: ((Owner) -> Void)? - private var generation: UInt64 = 0 - private let deactivate: () -> Void - private let backend: (any InlineAudioSessionBackend)? - private var transitionTail: Task? + @ObservationIgnored public var willAcquire: ((Owner) -> Void)? + @ObservationIgnored private var generation: UInt64 = 0 + @ObservationIgnored private let deactivate: () -> Void + @ObservationIgnored private let backend: (any InlineAudioSessionBackend)? + @ObservationIgnored private var transitionTail: Task? + + public private(set) var isQuarantined = false + public private(set) var isSpeakerPreferred = false + public private(set) var isSpeakerOutputActive = false + public private(set) var currentRouteName = "Audio" + public private(set) var supportsReceiverRouting = false + public var isGridActive: Bool { + gridToken != nil + } + + @ObservationIgnored private var gridToken: Token? + @ObservationIgnored private var gridEpoch: UInt64 = 0 + @ObservationIgnored private var gridRequestedEpoch: UInt64 = 0 + @ObservationIgnored private var gridDemandActive = false + @ObservationIgnored private var gridMediaQuiescent = true + @ObservationIgnored private var gridDirectionsIdle = true + @ObservationIgnored private var gridNativeActive = false + @ObservationIgnored private var gridTransitions = 0 + @ObservationIgnored private var gridRetiring = false + @ObservationIgnored private var gridRetirementTask: Task? + @ObservationIgnored private var gridRestartRequired = false struct Configuration: Sendable { let category: String let mode: String let options: UInt + var preferredIOBufferDuration: TimeInterval? + var usesAutomaticInput = false static let voicePlayback = Configuration( category: "AVAudioSessionCategoryPlayback", mode: "AVAudioSessionModeSpokenAudio", options: 0 ) + + static let gridVoice: Configuration = { + #if os(iOS) + let options = AVAudioSession.CategoryOptions.allowBluetoothHFP.rawValue + #else + let options: UInt = 0 + #endif + return Configuration( + category: "AVAudioSessionCategoryPlayAndRecord", mode: "AVAudioSessionModeVoiceChat", + options: options, preferredIOBufferDuration: 0.02, usesAutomaticInput: true + ) + }() } - var requiresNativeTransition: Bool { backend != nil } + var requiresNativeTransition: Bool { + backend != nil + } public init(deactivate: @escaping () -> Void = {}) { self.deactivate = deactivate @@ -52,34 +91,51 @@ public final class InlineAudioSession { } public var voiceDenial: AudioPlaybackError? { - switch current?.owner { - case .recording: .audioInUse("Finish recording to play this message") - case .draftPreview: .audioInUse("Pause the recording preview to play this message") - case .video: .audioInUse("Pause the video to play this message") - case .call: .audioInUse("Finish the call to play this message") - case .voice, nil: nil + if isQuarantined { + return .audioInUse(InlineAudioSessionError.quarantined.localizedDescription) + } + return switch current?.owner { + case .recording: .audioInUse("Finish recording to play this message") + case .draftPreview: .audioInUse("Pause the recording preview to play this message") + case .video: .audioInUse("Pause the video to play this message") + case .call: .audioInUse("Finish the call to play this message") + case .voice, nil: nil } } public func acquire(_ owner: Owner) throws -> Token { - if owner == .voice, let current, current.owner == .voice { return current } - if owner == .voice, let denial = voiceDenial { throw denial } + guard !isQuarantined else { throw InlineAudioSessionError.quarantined } + if owner == .voice, let current, current.owner == .voice { + return current + } + if owner == .voice, let denial = voiceDenial { + throw denial + } if let current, current.owner != .voice { throw AudioPlaybackError.audioInUse("Other Inline audio is playing") } // A non-voice client fences pending requests even while no engine is playing. willAcquire?(owner) + // A takeover callback can synchronously change ownership itself. + if let current, current.owner != .voice { + throw AudioPlaybackError.audioInUse("Other Inline audio is playing") + } generation &+= 1 let token = Token(owner: owner, generation: generation) current = token return token } - public func owns(_ token: Token) -> Bool { current == token } + public func owns(_ token: Token) -> Bool { + current == token + } /// Revokes the logical lease immediately. Its native teardown is ordered before the next activation. @discardableResult public func release(_ token: Token) -> Task? { + // Grid's reservation survives logical withdrawal until provider/native idle + // and the matching native teardown both succeed. + guard token != gridToken else { return nil } guard owns(token) else { return nil } current = nil deactivate() @@ -88,6 +144,10 @@ public final class InlineAudioSession { } public func invalidate() { + if gridToken != nil { + quarantineAfterMediaServicesReset() + return + } generation &+= 1 current = nil if let backend { @@ -96,6 +156,7 @@ public final class InlineAudioSession { } func activate(_ token: Token, configuration: Configuration) async throws { + guard token != gridToken else { throw InlineAudioSessionError.retirementPending } guard owns(token) else { throw CancellationError() } guard let backend else { return } let transition = enqueue { [weak self] in @@ -111,6 +172,189 @@ public final class InlineAudioSession { } } + /// Read-only preflight; actual token acquisition repeats it after the claim. + public func validateGridAdmission() throws { + try validateGridAdmission(reusingReservation: false) + } + + private func validateGridAdmission(reusingReservation: Bool) throws { + guard !isQuarantined, !gridRestartRequired else { throw InlineAudioSessionError.quarantined } + guard !gridRetiring, gridToken == nil || gridDemandActive || reusingReservation else { + throw InlineAudioSessionError.retirementPending + } + switch current?.owner { + case .recording: throw InlineAudioSessionError.recordingOwnsAudio + case .draftPreview: throw AudioPlaybackError.audioInUse("Pause the recording preview before joining Grid.") + case .video: throw AudioPlaybackError.audioInUse("Pause the video before joining Grid.") + case .call where current != gridToken: throw AudioPlaybackError.audioInUse("Other Inline audio is playing") + default: break + } + } + + public func setGridDemandActive(_ active: Bool, epoch: UInt64) async throws { + guard epoch >= gridRequestedEpoch else { return } + gridRequestedEpoch = epoch + if let retirement = gridRetirementTask { + // Once native release has been committed, wait for its token cleanup as + // well. A successor cannot reuse a session that is being deactivated. + await retirement.value + guard epoch == gridRequestedEpoch else { return } + try Task.checkCancellation() + } + guard epoch >= gridEpoch else { return } + if active { + let reusesReservation = epoch > gridEpoch && gridToken.map(owns) == true + // A replacement room inherits the still-owned reservation while old + // provider work retires; its stale receipts must not release the new room. + try validateGridAdmission(reusingReservation: reusesReservation) + if gridToken == nil { + gridToken = try acquire(.call) + } + } + if epoch != gridEpoch || active != gridDemandActive { + gridMediaQuiescent = false + gridDirectionsIdle = false + } + gridEpoch = epoch + gridDemandActive = active + guard active else { + await finishGridRetirementIfReady() + return + } + try await performGridTransition(epoch: epoch) { [weak self] token in + guard let self, !gridNativeActive else { return } + try await activateGridNative(token, epoch: epoch) + } + } + + public func gridMediaDidQuiesce(epoch: UInt64) async { + guard epoch == gridEpoch, !gridDemandActive else { return } + gridMediaQuiescent = true + await finishGridRetirementIfReady() + } + + public func observeGridDirections( + recording: Bool, playing: Bool, nativeOperationsQuiescent: Bool, epoch: UInt64 + ) async { + guard epoch == gridEpoch else { return } + gridDirectionsIdle = nativeOperationsQuiescent && !recording && !playing + refreshGridRoute() + await finishGridRetirementIfReady() + } + + /// Driver keeps ADM input/output closed across this receive-only recovery. + public func resumeGridSession(epoch: UInt64) async throws { + try await performGridTransition(epoch: epoch) { [weak self] token in + guard let self else { throw CancellationError() } + try await activateGridNative(token, epoch: epoch) + } + } + + private func activateGridNative(_ token: Token, epoch: UInt64) async throws { + do { + try await backend?.activate(token: token, configuration: .gridVoice) + guard gridToken == token, owns(token), gridEpoch == epoch, gridDemandActive, + !isQuarantined else { throw CancellationError() } + if let backend { + try await backend.setSpeakerPreferred(isSpeakerPreferred, token: token) + } + } catch { + // Readiness includes the requested route. Only an entered native failure + // can change it; rejected stale recovery never reaches this operation. + if gridToken == token, owns(token) { + gridNativeActive = false + } + throw error + } + if gridToken == token, owns(token) { + gridNativeActive = true + } + } + + public func quarantineAfterMediaServicesReset() { + gridRestartRequired = true + // The persistent Grid observer must not change standalone reset behavior. + if gridToken != nil { + isQuarantined = true + } + } + + public func setSpeakerPreferred(_ preferred: Bool) async throws { + let epoch = gridEpoch + try await performGridTransition(epoch: epoch) { [weak self] token in + guard let self, gridNativeActive else { throw InlineAudioSessionError.routeUnavailable } + try await backend?.setSpeakerPreferred(preferred, token: token) + } + isSpeakerPreferred = preferred + refreshGridRoute() + } + + private func performGridTransition( + epoch: UInt64, _ operation: @escaping @MainActor (Token) async throws -> Void + ) async throws { + guard let token = gridToken, owns(token), gridDemandActive, epoch == gridEpoch, !gridRetiring, + !isQuarantined else { throw InlineAudioSessionError.retirementPending } + gridTransitions += 1 + let transition = enqueue { [weak self] in + guard let self, owns(token), gridToken == token, epoch == gridEpoch, + gridDemandActive, !gridRetiring, !isQuarantined else { throw CancellationError() } + try await operation(token) + } + let result: Result + do { try await transition.value + result = .success(()) + } catch { result = .failure(error) } + gridTransitions -= 1 + await finishGridRetirementIfReady() + try result.get() + guard gridToken == token, owns(token), epoch == gridEpoch, gridDemandActive, + !isQuarantined, !Task.isCancelled else { throw CancellationError() } + refreshGridRoute() + } + + private func finishGridRetirementIfReady() async { + guard let token = gridToken, owns(token), !gridDemandActive, gridMediaQuiescent, + gridDirectionsIdle, gridTransitions == 0, !gridRetiring, !isQuarantined else { return } + gridRetiring = true + let epoch = gridEpoch + let retirement = Task { [weak self] in + await self?.retireGridToken(token, epoch: epoch) + } + gridRetirementTask = retirement + await retirement.value + } + + private func retireGridToken(_ token: Token, epoch: UInt64) async { + defer { gridRetirementTask = nil } + let transition = enqueue { [weak self] in + guard let self, gridToken == token, owns(token), gridEpoch == epoch, + !gridDemandActive, gridMediaQuiescent, gridDirectionsIdle else { throw CancellationError() } + try await backend?.release(token: token) + } + do { + try await transition.value + guard gridToken == token, owns(token) else { return } + current = nil + gridToken = nil + gridNativeActive = false + gridRetiring = false + deactivate() + refreshGridRoute() + } catch { + isQuarantined = true + gridRetiring = false + } + } + + private func refreshGridRoute() { + #if os(iOS) + let session = AVAudioSession.sharedInstance() + currentRouteName = session.currentRoute.outputs.first?.portName ?? "Audio" + isSpeakerOutputActive = session.currentRoute.outputs.contains { $0.portType == .builtInSpeaker } + supportsReceiverRouting = isGridActive && session.category == .playAndRecord + #endif + } + /// Only this chain may enter the native backend. Actor reentrancy alone does not order async I/O. private func enqueue(_ operation: @escaping @MainActor () async throws -> Void) -> Task { let previous = transitionTail @@ -141,6 +385,25 @@ protocol InlineAudioSessionBackend: Sendable { func activate(token: InlineAudioSession.Token, configuration: InlineAudioSession.Configuration) async throws func release(token: InlineAudioSession.Token) async throws func invalidate() async + func setSpeakerPreferred(_ preferred: Bool, token: InlineAudioSession.Token) async throws +} + +extension InlineAudioSessionBackend { + func setSpeakerPreferred(_: Bool, token _: InlineAudioSession.Token) async throws {} +} + +public enum InlineAudioSessionError: LocalizedError { + case gridOwnsAudio, recordingOwnsAudio, quarantined, retirementPending, routeUnavailable + + public var errorDescription: String? { + switch self { + case .gridOwnsAudio: "Leave Grid to record a voice message." + case .recordingOwnsAudio: "Finish recording your voice message before joining Grid." + case .quarantined: "Audio needs to restart. Quit and reopen Inline before starting audio again." + case .retirementPending: "Audio is still stopping. Try again shortly." + case .routeUnavailable: "Audio output is unavailable. Resume Grid audio and try again." + } + } } #if os(iOS) @@ -150,31 +413,56 @@ private actor NativeAudioSessionBackend: InlineAudioSessionBackend { let category: AVAudioSession.Category let mode: AVAudioSession.Mode let options: AVAudioSession.CategoryOptions + let preferredIOBufferDuration: TimeInterval? } + private var token: InlineAudioSession.Token? private var snapshot: Snapshot? func activate(token: InlineAudioSession.Token, configuration: InlineAudioSession.Configuration) async throws { // A failed release keeps its snapshot. Retry it before capturing or configuring a successor. - if let previous = self.token { try await release(token: previous) } + let recoveringCall = self.token == token && token.owner == .call + if !recoveringCall, let previous = self.token { + try await release(token: previous) + } let session = AVAudioSession.sharedInstance() - snapshot = Snapshot(category: session.category, mode: session.mode, options: session.categoryOptions) + if !recoveringCall { + snapshot = Snapshot( + category: session.category, + mode: session.mode, + options: session.categoryOptions, + preferredIOBufferDuration: configuration.preferredIOBufferDuration == nil + ? nil : session.preferredIOBufferDuration + ) + } self.token = token do { - try session.setCategory(AVAudioSession.Category(rawValue: configuration.category), - mode: AVAudioSession.Mode(rawValue: configuration.mode), - options: AVAudioSession.CategoryOptions(rawValue: configuration.options)) + try session.setCategory( + AVAudioSession.Category(rawValue: configuration.category), + mode: AVAudioSession.Mode(rawValue: configuration.mode), + options: AVAudioSession.CategoryOptions(rawValue: configuration.options) + ) + if let duration = configuration.preferredIOBufferDuration { + try session.setPreferredIOBufferDuration(duration) + } if #available(iOS 27.0, *) { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in session.activate(options: []) { activated, error in - if let error { continuation.resume(throwing: error) } - else if activated { continuation.resume() } - else { continuation.resume(throwing: AudioPlaybackError.playbackUnavailable) } + if let error { + continuation.resume(throwing: error) + } else if activated { + continuation.resume() + } else { + continuation.resume(throwing: AudioPlaybackError.playbackUnavailable) + } } } } else { try session.setActive(true) } + if configuration.usesAutomaticInput { + try session.setPreferredInput(nil) + } } catch { // Preserve cleanup state if teardown fails; a later activation retries it in this same lane. try? await release(token: token) @@ -188,9 +476,13 @@ private actor NativeAudioSessionBackend: InlineAudioSessionBackend { if #available(iOS 27.0, *) { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in session.deactivate(options: [.notifyOthersOnDeactivation]) { deactivated, error in - if let error { continuation.resume(throwing: error) } - else if deactivated { continuation.resume() } - else { continuation.resume(throwing: AudioPlaybackError.playbackUnavailable) } + if let error { + continuation.resume(throwing: error) + } else if deactivated { + continuation.resume() + } else { + continuation.resume(throwing: AudioPlaybackError.playbackUnavailable) + } } } } else { @@ -198,16 +490,26 @@ private actor NativeAudioSessionBackend: InlineAudioSessionBackend { } if let snapshot { try session.setCategory(snapshot.category, mode: snapshot.mode, options: snapshot.options) + if let duration = snapshot.preferredIOBufferDuration { + try session.setPreferredIOBufferDuration(duration) + } } - self.snapshot = nil + snapshot = nil self.token = nil } + func setSpeakerPreferred(_ preferred: Bool, token: InlineAudioSession.Token) async throws { + guard self.token == token, token.owner == .call else { throw CancellationError() } + try AVAudioSession.sharedInstance().overrideOutputAudioPort(preferred ? .speaker : .none) + } + func invalidate() async { // A reset invalidates restoration, but an in-flight activation may have just completed. // Retire that native lease before allowing any successor to snapshot/configure the session. snapshot = nil - if let token { try? await release(token: token) } + if let token { + try? await release(token: token) + } } } #endif diff --git a/apple/InlineKit/Sources/InlineGrid/GridAvatarStateSync.swift b/apple/InlineKit/Sources/InlineGrid/GridAvatarStateSync.swift new file mode 100644 index 000000000..1f3f20767 --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridAvatarStateSync.swift @@ -0,0 +1,117 @@ +import Auth +import Logger + +/// Serializes the transient state owned by the current Grid avatar. +/// +/// Rapid UI changes are coalesced to the latest value while one RPC is in +/// flight, so task scheduling cannot reorder mute/unmute updates on the wire. +/// Failed latest state is retained for a network/wake retry, but ownership +/// changes immediately discard work for a room this process no longer owns. +@MainActor +final class GridAvatarStateSync { + private struct Request: Equatable { + let roomID: Int64 + let membershipID: String + let accountToken: AuthAccountMutationToken + let microphoneEnabled: Bool + } + + private let api: GridRoomAPI + private let log = Log.scoped("GridAvatarStateSync") + private var ownedRoomID: Int64? + private var ownedMembershipID: String? + private var ownedAccountToken: AuthAccountMutationToken? + private var queued: Request? + private var failed: Request? + private var generation = 0 + private var syncTask: Task? + private var workerGeneration: Int? + + init(api: GridRoomAPI) { + self.api = api + } + + deinit { + syncTask?.cancel() + } + + func setOwnedRoom(_ roomID: Int64?, membershipID: String? = nil, accountToken: AuthAccountMutationToken? = nil) { + guard ownedRoomID != roomID || ownedMembershipID != membershipID || ownedAccountToken != accountToken + else { return } + generation &+= 1 + ownedRoomID = roomID + ownedMembershipID = membershipID + ownedAccountToken = accountToken + if queued?.roomID != roomID || queued?.membershipID != membershipID { + queued = nil + } + if failed?.roomID != roomID || failed?.membershipID != membershipID { + failed = nil + } + syncTask?.cancel() + syncTask = nil + workerGeneration = nil + startIfNeeded() + } + + func submitMicrophoneState(roomID: Int64, enabled: Bool) { + guard ownedRoomID == roomID, let membershipID = ownedMembershipID, let accountToken = ownedAccountToken, + !membershipID.isEmpty else { return } + queued = Request(roomID: roomID, membershipID: membershipID, accountToken: accountToken, microphoneEnabled: enabled) + failed = nil + startIfNeeded() + } + + func retryLatestFailure() { + guard queued == nil, let failed, failed.roomID == ownedRoomID, + failed.membershipID == ownedMembershipID else { return } + queued = failed + self.failed = nil + startIfNeeded() + } + + private func startIfNeeded() { + guard syncTask == nil, queued != nil else { return } + let workerGeneration = generation + self.workerGeneration = workerGeneration + syncTask = Task { [weak self] in + await self?.run(workerGeneration: workerGeneration) + } + } + + private func run(workerGeneration: Int) async { + while !Task.isCancelled, generation == workerGeneration, let request = queued { + queued = nil + do { + try await api.setMicrophoneEnabled( + request.microphoneEnabled, + roomID: request.roomID, + expectedMembershipID: request.membershipID, + accountToken: request.accountToken + ) + log.debug( + "GRID_TRACE phase=avatar_microphone_sent room=\(request.roomID) enabled=\(request.microphoneEnabled)" + ) + } catch { + guard !Task.isCancelled, generation == workerGeneration else { continue } + // Preserve only the latest still-relevant request. A newer UI intent + // already waiting in `queued` supersedes the failed value. + if ownedRoomID == request.roomID, ownedMembershipID == request.membershipID, queued == nil { + failed = request + } + log.warning( + "GRID_TRACE phase=avatar_microphone_send_failed room=\(request.roomID) enabled=\(request.microphoneEnabled)" + ) + PerformanceTrace.breadcrumb( + "Grid avatar microphone sync failed", + category: "Grid.State", + level: .warning + ) + } + } + guard self.workerGeneration == workerGeneration else { return } + syncTask = nil + self.workerGeneration = nil + startIfNeeded() + } +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridCallClaimAdmission.swift b/apple/InlineKit/Sources/InlineGrid/GridCallClaimAdmission.swift new file mode 100644 index 000000000..019f675f7 --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridCallClaimAdmission.swift @@ -0,0 +1,35 @@ +import InlineProtocol + +/// Validates the committed claim against the latest projections already +/// accepted by this process. A successful RPC alone cannot authorize audio. +enum GridCallClaimAdmission { + static func accepts( + committedCall: GridCurrentCall, + newestGrid: InlineProtocol.Grid?, + acceptedAuthority: GridOwnedRoomAuthority? + ) -> Bool { + guard committedCall.ownedByCurrentSession, + !committedCall.callID.isEmpty, + !committedCall.membershipID.isEmpty, + acceptedAuthority?.matches(committedCall) == true, + newestGrid?.enabled == true, + newestGrid?.spaceID == committedCall.spaceID, + newestGrid?.rooms.first(where: { $0.id == committedCall.roomID })?.avatars.contains(where: { + $0.ownedByCurrentSession && $0.membershipID == committedCall.membershipID + }) == true + else { return false } + return true + } +} + +/// Only this small self tuple is retained before optimistic UI overlays. +/// It prevents a pending Join's avatar projection from granting media access. +struct GridOwnedRoomAuthority: Equatable, Sendable { + let spaceID: Int64 + let roomID: Int64 + let membershipID: String + + func matches(_ call: GridCurrentCall) -> Bool { + spaceID == call.spaceID && roomID == call.roomID && membershipID == call.membershipID + } +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridCredentialBinding.swift b/apple/InlineKit/Sources/InlineGrid/GridCredentialBinding.swift new file mode 100644 index 000000000..eaae166e5 --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridCredentialBinding.swift @@ -0,0 +1,26 @@ +import InlineProtocol + +/// New credentials bind to the stable call and rotating owner. Legacy +/// compatibility is limited to an explicit macOS admission while capability is +/// OFF, with the existing user/membership participant identity still exact. +enum GridCredentialBinding { + static func accepts( + credentials: GridConnectionCredentials, + target: GridMediaTarget, + userID: Int64, + callTransferEnabled: Bool + ) -> Bool { + guard credentials.hasConnection, + credentials.connection.roomID == target.roomID, + credentials.connection.generation == target.generation, + !target.membershipID.isEmpty, + credentials.participantIdentity == "inline-grid-user-\(userID)-\(target.membershipID)" + else { return false } + if callTransferEnabled { + return !target.callID.isEmpty && credentials.callID == target.callID + && credentials.membershipID == target.membershipID + } + return (credentials.callID.isEmpty || credentials.callID == target.callID) + && (credentials.membershipID.isEmpty || credentials.membershipID == target.membershipID) + } +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridHomePreferences.swift b/apple/InlineKit/Sources/InlineGrid/GridHomePreferences.swift new file mode 100644 index 000000000..b7b7dc160 --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridHomePreferences.swift @@ -0,0 +1,40 @@ +import Foundation +import InlineProtocol + +/// Local navigation history used only as a stable tie-breaker after live Grid +/// activity. It does not own eligibility or presence; the server summary does. +@MainActor +final class GridHomePreferences { + private let defaults: UserDefaults + private let key = "grid.home.lastOpenedSpaces" + + init(defaults: UserDefaults = .standard) { + self.defaults = defaults + } + + func recordOpened(spaceID: Int64) { + var values = timestamps + values[String(spaceID)] = Date().timeIntervalSince1970 + defaults.set(values, forKey: key) + } + + func ordered(_ spaces: [GridHomeSpace]) -> [GridHomeSpace] { + let timestamps = timestamps + return spaces.sorted { lhs, rhs in + let lhsActive = lhs.activeAvatarCount > 0 + let rhsActive = rhs.activeAvatarCount > 0 + if lhsActive != rhsActive { return lhsActive } + if lhs.latestActivityAt != rhs.latestActivityAt { + return lhs.latestActivityAt > rhs.latestActivityAt + } + let lhsOpened = timestamps[String(lhs.spaceID)] ?? 0 + let rhsOpened = timestamps[String(rhs.spaceID)] ?? 0 + if lhsOpened != rhsOpened { return lhsOpened > rhsOpened } + return lhs.spaceID < rhs.spaceID + } + } + + private var timestamps: [String: Double] { + defaults.dictionary(forKey: key) as? [String: Double] ?? [:] + } +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridMediaCoordinator.swift b/apple/InlineKit/Sources/InlineGrid/GridMediaCoordinator.swift new file mode 100644 index 000000000..3bb9d51ec --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridMediaCoordinator.swift @@ -0,0 +1,597 @@ +import Foundation +import InlineKit +import InlineRTC + +enum GridMediaCoordinatorEvent: Sendable { + case credentialsNeeded(GridMediaTarget) + case connected(GridMediaTarget, rtcConnectMilliseconds: Int?) + case screenShareContextChanged + case microphoneSafetyPaused + case audioEligibilityChanged(Bool) +} + +struct GridAutomaticMicrophoneChange: Equatable, Sendable { + let revision: Int + let previousEnabled: Bool + let appliedEnabled: Bool +} + +/// Main-actor bridge between room product state and the process-wide media +/// engine. This owns media intent, preference persistence, credentials, and +/// snapshot projection; `GridRoomService` never reaches into audio or RTC. +@MainActor +final class GridMediaCoordinator { + let presentation: GridMediaPresentation + + private var platformEffects = GridPlatformEffects() + private let engine: InlineRTCSession + private let presentationController: GridMediaPresentationController + private let inputPreferences: AudioInputPreferenceStore + private let outputPreferences: AudioOutputPreferenceStore + private let defaults: UserDefaults + private var microphoneEnabled: Bool + private var microphoneCapturePrepared = false + private var lastSafetyPauseRevision: UInt64 = 0 + private var lastAudioEligibility = false + private var microphoneIntentRevision = 0 + private var activeAutomaticMicrophoneChange: GridAutomaticMicrophoneChange? + private var autoUnmuteOnJoin: Bool + private var autoMuteWhenAlone: Bool + private var inputSelection: AudioInputSelection + private var outputSelection: AudioOutputSelection + private var outputVolume: Float = 1 + private var screenCaptureSource: InlineRTCScreenCaptureSource? + private var screenShareQualityProfile: InlineRTCScreenShareQualityProfile + private var screenCaptureSourceRefresh: GridScreenCaptureSourceRefresh? + private var target: GridMediaTarget? + private var connectedTarget: GridMediaTarget? + private var credentials: InlineRTCCredentials? + private var lastDemand: InlineRTCDemand? + private var playedConnectionSoundKeys = Set() + private var engineTask: Task? + private var subscribers: [UUID: AsyncStream.Continuation] = [:] + + private static let microphoneEnabledKey = "grid.microphoneEnabled" + private static let autoUnmuteOnJoinKey = "grid.autoUnmuteOnJoin" + private static let autoMuteWhenAloneKey = "grid.autoMuteWhenAlone" + private static let screenShareQualityProfileKey = "grid.screenShareQualityProfile" + + init( + engine: InlineRTCSession, + defaults: UserDefaults, + inputPreferences: AudioInputPreferenceStore, + outputPreferences: AudioOutputPreferenceStore + ) { + self.engine = engine + self.defaults = defaults + self.inputPreferences = inputPreferences + self.outputPreferences = outputPreferences + #if os(iOS) + inputSelection = .automatic + outputSelection = .automatic + microphoneEnabled = false + autoUnmuteOnJoin = false + autoMuteWhenAlone = false + #else + inputSelection = inputPreferences.selection + outputSelection = outputPreferences.selection + microphoneEnabled = defaults.bool(forKey: Self.microphoneEnabledKey) + autoUnmuteOnJoin = defaults.bool(forKey: Self.autoUnmuteOnJoinKey) + autoMuteWhenAlone = defaults.bool(forKey: Self.autoMuteWhenAloneKey) + #endif + screenShareQualityProfile = defaults.string(forKey: Self.screenShareQualityProfileKey) + .flatMap(InlineRTCScreenShareQualityProfile.init(rawValue:)) + ?? .automatic + let controller = GridMediaPresentationController( + microphoneEnabled: microphoneEnabled, + autoUnmuteOnJoin: autoUnmuteOnJoin, + autoMuteWhenAlone: autoMuteWhenAlone, + inputSelection: inputSelection, + screenShareQualityProfile: screenShareQualityProfile, + outputSelection: outputSelection + ) + presentationController = controller + presentation = controller.presentation + + engineTask = Task { [weak self, engine] in + await engine.start() + guard !Task.isCancelled else { return } + engine.refreshDevices() + self?.submitDemand() + let snapshots = await engine.subscribe() + for await snapshot in snapshots { + guard !Task.isCancelled else { return } + self?.apply(snapshot) + } + } + } + + deinit { + engineTask?.cancel() + subscribers.values.forEach { $0.finish() } + engine.requestShutdown() + } + + func subscribe() -> AsyncStream { + let id = UUID() + let stream = AsyncStream.makeStream( + of: GridMediaCoordinatorEvent.self, + // These are edge notifications backed by complete engine snapshots and + // target-scoped credential retry. A stalled observer must stay bounded. + bufferingPolicy: .bufferingNewest(16) + ) + stream.continuation.onTermination = { [weak self] _ in + Task { @MainActor in self?.subscribers.removeValue(forKey: id) } + } + subscribers[id] = stream.continuation + return stream.stream + } + + func configurePlatformEffects(_ effects: GridPlatformEffects) { + platformEffects = effects + } + + func setTarget(_ target: GridMediaTarget?) { + #if os(iOS) + if target == nil || self.target?.callID != target?.callID + || self.target?.membershipID != target?.membershipID + { + microphoneCapturePrepared = false + setMicrophoneEnabled(false) + } + #endif + guard self.target != target else { + submitDemand() + return + } + screenCaptureSource = nil + presentationController.setSelectedScreenCaptureSource(nil) + presentationController.clearScreenCaptureError() + platformEffects.updateScreenShareOutline?(nil) + platformEffects.closeScreenShares?() + self.target = target + if credentials?.target != target?.rtcSessionID { + credentials = nil + } + submitDemand() + } + + func accept(_ credentials: InlineRTCCredentials) -> Bool { + guard credentials.target == target?.rtcSessionID else { return false } + self.credentials = credentials + submitDemand() + return true + } + + func hasUsableCredentials(for target: GridMediaTarget) -> Bool { + credentials?.target == target.rtcSessionID + && (credentials?.expiresAt.timeIntervalSinceNow ?? 0) > 5 + } + + func isConnected(to target: GridMediaTarget) -> Bool { + connectedTarget == target + } + + func invalidateCredentials(for target: GridMediaTarget) { + guard credentials?.target == target.rtcSessionID else { return } + credentials = nil + submitDemand() + } + + func clearCredentials() { + guard credentials != nil else { return } + credentials = nil + submitDemand() + } + + @discardableResult + func withdrawLocalMedia() -> Task { + microphoneCapturePrepared = false + #if os(iOS) + setMicrophoneEnabled(false) + #endif + clearCredentials() + setTarget(nil) + lastDemand = nil + return engine.requestShutdownReceipt() + } + + func toggleMicrophone() -> Bool { + setMicrophoneEnabled(!microphoneEnabled) + } + + @discardableResult + func setMicrophoneEnabled(_ enabled: Bool) -> Bool { + #if os(iOS) + if enabled, target == nil { + return false + } + if enabled { + microphoneCapturePrepared = true + } + #endif + guard microphoneEnabled != enabled else { + submitDemand() + return enabled + } + if enabled, presentation.microphonePermission != .authorized { + engine.requestMicrophonePermission() + } + activeAutomaticMicrophoneChange = nil + microphoneIntentRevision &+= 1 + microphoneEnabled = enabled + presentationController.setMicrophoneEnabled(enabled) + defaults.set(enabled, forKey: Self.microphoneEnabledKey) + submitDemand() + return enabled + } + + func applyAutoUnmuteOnJoin() -> GridAutomaticMicrophoneChange? { + guard autoUnmuteOnJoin else { return nil } + if microphoneEnabled { + return activeAutomaticMicrophoneChange + } + let previousEnabled = microphoneEnabled + setMicrophoneEnabled(true) + let change = GridAutomaticMicrophoneChange( + revision: microphoneIntentRevision, + previousEnabled: previousEnabled, + appliedEnabled: true + ) + activeAutomaticMicrophoneChange = change + return change + } + + @discardableResult + func restoreAutomaticMicrophoneChangeIfCurrent( + _ change: GridAutomaticMicrophoneChange? + ) -> Bool { + guard let change, + activeAutomaticMicrophoneChange == change, + microphoneIntentRevision == change.revision, + microphoneEnabled == change.appliedEnabled + else { return false } + setMicrophoneEnabled(change.previousEnabled) + return true + } + + func commitAutomaticMicrophoneChange(_ change: GridAutomaticMicrophoneChange?) { + guard activeAutomaticMicrophoneChange == change else { return } + activeAutomaticMicrophoneChange = nil + } + + func setAutoUnmuteOnJoin(_ enabled: Bool) { + guard autoUnmuteOnJoin != enabled else { return } + autoUnmuteOnJoin = enabled + defaults.set(enabled, forKey: Self.autoUnmuteOnJoinKey) + presentationController.setAutoUnmuteOnJoin(enabled) + } + + func setAutoMuteWhenAlone(_ enabled: Bool) { + guard autoMuteWhenAlone != enabled else { return } + autoMuteWhenAlone = enabled + defaults.set(enabled, forKey: Self.autoMuteWhenAloneKey) + presentationController.setAutoMuteWhenAlone(enabled) + } + + func requestMicrophonePermission() { + engine.requestMicrophonePermission() + } + + func setInput(_ selection: AudioInputSelection) { + if inputSelection == selection { + if presentation.isFallingBackToAutomaticInput || presentation.audioState.isFailed { + engine.retryInput(selection) + } + return + } + inputSelection = selection + inputPreferences.setSelection(selection) + presentationController.setDesiredInputSelection(selection) + submitDemand() + } + + func setOutput(_ selection: AudioOutputSelection) { + if outputSelection == selection { + if presentation.isFallingBackToAutomaticOutput || presentation.audioState.isFailed { + engine.retryOutput(selection) + } + return + } + outputSelection = selection + outputPreferences.setSelection(selection) + presentationController.setDesiredOutputSelection(selection) + submitDemand() + } + + func setOutputVolume(_ volume: Float) { + outputVolume = min(max(volume, 0), 1) + presentationController.setDesiredOutputVolume(outputVolume) + submitDemand() + } + + func refreshInputDevices() { + engine.refreshDevices() + } + + func refreshOutputDevices() { + engine.refreshDevices() + } + + @discardableResult + func refreshScreenCaptureSources() async -> [InlineRTCScreenCaptureSource] { + if let refresh = screenCaptureSourceRefresh { + return await finishScreenCaptureSourceRefresh( + refresh.task.value, + id: refresh.id + ) + } + + let refresh = GridScreenCaptureSourceRefresh( + id: UUID(), + task: Task { [engine] in + do { + let sources = try await engine.screenCaptureSources() + guard !sources.isEmpty else { + return .failure("No displays are available") + } + return .success(sources) + } catch { + return .failure(error.localizedDescription) + } + } + ) + screenCaptureSourceRefresh = refresh + presentationController.setRefreshingScreenCaptureSources(true) + return await finishScreenCaptureSourceRefresh( + refresh.task.value, + id: refresh.id + ) + } + + func startScreenSharing(displayID: UInt32?) async { + guard let expectedTarget = target else { return } + let sources = await refreshScreenCaptureSources() + guard target == expectedTarget else { return } + let source = displayID.flatMap { displayID in + sources.first { $0.displayID == displayID } + } ?? sources.first + guard let source else { return } + startScreenSharing(source) + } + + func startScreenSharing(_ source: InlineRTCScreenCaptureSource) { + guard target != nil else { return } + screenCaptureSource = source + presentationController.setSelectedScreenCaptureSource(source) + presentationController.clearScreenCaptureError() + submitDemand() + } + + func stopScreenSharing() { + screenCaptureSource = nil + presentationController.setSelectedScreenCaptureSource(nil) + presentationController.clearScreenCaptureError() + platformEffects.updateScreenShareOutline?(nil) + submitDemand() + } + + func setScreenShareQualityProfile(_ profile: InlineRTCScreenShareQualityProfile) { + guard screenShareQualityProfile != profile else { return } + screenShareQualityProfile = profile + defaults.set(profile.rawValue, forKey: Self.screenShareQualityProfileKey) + presentationController.setScreenShareQualityProfile(profile) + submitDemand() + } + + func retryAudio() { + engine.retryAudio() + } + + func networkBecameAvailable() { + engine.networkBecameAvailable() + } + + func applicationDidWake() { + engine.applicationDidWake() + } + + @discardableResult + func shutdown() async -> GridMediaShutdownReceipt { + target = nil + connectedTarget = nil + credentials = nil + screenCaptureSource = nil + screenCaptureSourceRefresh?.task.cancel() + screenCaptureSourceRefresh = nil + presentationController.setRefreshingScreenCaptureSources(false) + presentationController.setSelectedScreenCaptureSource(nil) + presentationController.clearScreenCaptureError() + platformEffects.updateScreenShareOutline?(nil) + platformEffects.closeScreenShares?() + lastDemand = nil + engine.setDemand(InlineRTCDemand()) + return await engine.requestShutdownReceipt().value + } + + var isMicrophoneEnabled: Bool { + microphoneEnabled + } + + var shouldAutoMuteWhenAlone: Bool { + autoMuteWhenAlone + } + + private var microphoneCapturePreparedForDemand: Bool { + #if os(iOS) + microphoneCapturePrepared && target != nil + #else + target != nil + #endif + } + + private func submitDemand() { + let rtcTarget = target?.rtcSessionID + if credentials?.target != rtcTarget { + credentials = nil + } + let demand = InlineRTCDemand( + target: rtcTarget, + credentials: credentials, + microphoneEnabled: microphoneEnabled, + microphoneCapturePrepared: microphoneCapturePreparedForDemand, + screenCaptureSource: screenCaptureSource, + screenShareQualityProfile: screenShareQualityProfile, + input: inputSelection, + output: outputSelection, + outputVolume: outputVolume + ) + guard demand != lastDemand else { return } + lastDemand = demand + engine.setDemand(demand) + } + + private func finishScreenCaptureSourceRefresh( + _ result: GridScreenCaptureSourceRefreshResult, + id: UUID + ) -> [InlineRTCScreenCaptureSource] { + let sources = result.sources + guard screenCaptureSourceRefresh?.id == id else { return sources } + screenCaptureSourceRefresh = nil + presentationController.setRefreshingScreenCaptureSources(false) + switch result { + case let .success(sources): + presentationController.applyScreenCaptureSources(.success(sources)) + case let .failure(message): + presentationController.applyScreenCaptureSources( + .failure(GridScreenCaptureSourceRefreshError(message: message)) + ) + } + reconcileScreenShareOutline() + return sources + } + + private func apply(_ snapshot: InlineRTCState) { + #if os(iOS) + if snapshot.audio.safetyPauseRevision != lastSafetyPauseRevision { + lastSafetyPauseRevision = snapshot.audio.safetyPauseRevision + microphoneCapturePrepared = false + setMicrophoneEnabled(false) + broadcast(.microphoneSafetyPaused) + } + if snapshot.audio.microphonePermission == .denied || snapshot.audio.microphonePermission == .restricted, + microphoneEnabled + { + microphoneCapturePrepared = false + setMicrophoneEnabled(false) + broadcast(.microphoneSafetyPaused) + } + #endif + presentationController.apply(audio: snapshot.audio) + if let devices = snapshot.devices { + presentationController.apply(devices: devices) + } + if let outputDevices = snapshot.outputDevices { + presentationController.apply(outputDevices: outputDevices) + } + + let wasConnected = presentation.connectionState == .connected + presentationController.apply(rtc: snapshot.rtc) + reconcileScreenShareOutline() + broadcast(.screenShareContextChanged) + if case .failed = snapshot.rtc.screenShareState, screenCaptureSource != nil { + screenCaptureSource = nil + presentationController.setSelectedScreenCaptureSource(nil) + submitDemand() + } + if case let .connected(sessionID) = snapshot.rtc.state, + let target, + target.rtcSessionID == sessionID + { + connectedTarget = target + } else { + connectedTarget = nil + } + let audioEligible = target != nil && connectedTarget != nil + && (snapshot.audio.isCaptureHealthy || snapshot.audio.isPlayoutHealthy) + if audioEligible != lastAudioEligibility { + lastAudioEligibility = audioEligible + broadcast(.audioEligibilityChanged(audioEligible)) + } + if !wasConnected, + presentation.connectionState == .connected, + let sessionID = snapshot.rtc.target, + let target, + target.rtcSessionID == sessionID + { + let soundKey = sessionID.rawValue + if playedConnectionSoundKeys.count >= 128 { + playedConnectionSoundKeys.removeAll(keepingCapacity: true) + } + if playedConnectionSoundKeys.insert(soundKey).inserted { + platformEffects.playSound?(.connected) + } + broadcast(.connected(target, rtcConnectMilliseconds: snapshot.rtc.lastConnectMilliseconds)) + } + if case let .waitingForCredentials(sessionID) = snapshot.rtc.state, + let target, + target.rtcSessionID == sessionID + { + broadcast(.credentialsNeeded(target)) + } + } + + private func broadcast(_ event: GridMediaCoordinatorEvent) { + subscribers.values.forEach { $0.yield(event) } + } + + private func reconcileScreenShareOutline() { + guard let localShare = presentation.screenShares.first(where: \.isLocal), + let sourceID = localShare.captureSourceID + else { + platformEffects.updateScreenShareOutline?(nil) + return + } + let sources = presentation.screenCaptureSources + + [presentation.selectedScreenCaptureSource].compactMap(\.self) + guard let source = sources.first(where: { $0.id == sourceID }) else { + platformEffects.updateScreenShareOutline?(nil) + return + } + platformEffects.updateScreenShareOutline?(source) + } +} + +private struct GridScreenCaptureSourceRefresh { + let id: UUID + let task: Task +} + +private enum GridScreenCaptureSourceRefreshResult: Sendable { + case success([InlineRTCScreenCaptureSource]) + case failure(String) + + var sources: [InlineRTCScreenCaptureSource] { + switch self { + case let .success(sources): sources + case .failure: [] + } + } +} + +private struct GridScreenCaptureSourceRefreshError: LocalizedError, Sendable { + let message: String + + var errorDescription: String? { + message + } +} + +private extension InlineRTCAudioState { + var isFailed: Bool { + if case .failed = self { + return true + } + return false + } +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridMediaPresentation.swift b/apple/InlineKit/Sources/InlineGrid/GridMediaPresentation.swift new file mode 100644 index 000000000..31a83edfc --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridMediaPresentation.swift @@ -0,0 +1,272 @@ +import Foundation +import InlineKit +import InlineRTC +import Observation + +public enum GridMediaConnectionStatus: Equatable, Sendable { + case disconnected + case connecting + case connected + case failed +} + +public enum GridScreenCaptureIssue: Equatable, Sendable { + case sourceDiscovery + case sharing +} + +@MainActor +@Observable +public final class GridMediaPresentation { + public fileprivate(set) var connectionState: GridMediaConnectionStatus = .disconnected + public fileprivate(set) var audioState: InlineRTCAudioState = .cold + public fileprivate(set) var isAudioSafetyPaused = false + public fileprivate(set) var isMicrophoneEnabled: Bool + public fileprivate(set) var autoUnmuteOnJoin: Bool + public fileprivate(set) var autoMuteWhenAlone: Bool + public fileprivate(set) var inputDevices: [AudioInputDeviceDescriptor] = [] + public fileprivate(set) var automaticInputDeviceName = "System Default" + public fileprivate(set) var activeInputDeviceID: String? + public fileprivate(set) var inputSelection: AudioInputSelection + public fileprivate(set) var isFallingBackToAutomaticInput = false + public fileprivate(set) var outputDevices: [AudioOutputDeviceDescriptor] = [] + public fileprivate(set) var automaticOutputDeviceName = "System Default" + public fileprivate(set) var activeOutputDeviceID: String? + public fileprivate(set) var outputSelection: AudioOutputSelection + public fileprivate(set) var isFallingBackToAutomaticOutput = false + public fileprivate(set) var participantAudioLevels: [String: Float] = [:] + public fileprivate(set) var participantScreenShareIntents: [String: Bool] = [:] + public fileprivate(set) var connectedParticipantIdentities = Set() + public fileprivate(set) var hasParticipantMediaSnapshot = false + public fileprivate(set) var reconnectCount = 0 + public fileprivate(set) var recoveryAttempt = 0 + public fileprivate(set) var lastConnectDurationMilliseconds: Int? + public fileprivate(set) var lastDisconnectDurationMilliseconds: Int? + public fileprivate(set) var lastConnectionError: String? + public fileprivate(set) var isMicrophoneCapturePrepared = false + public fileprivate(set) var microphonePermission: InlineRTCMicrophonePermission = .notDetermined + public fileprivate(set) var microphonePublicationState: InlineRTCMicrophoneState = .notRequested + public fileprivate(set) var localAudioFlowState: InlineRTCAudioFlowState = .unknown + public fileprivate(set) var remoteAudioFlowStates: [String: InlineRTCAudioFlowState] = [:] + public fileprivate(set) var abandonedProviderOperationCount = 0 + public fileprivate(set) var providerCircuitOpen = false + public fileprivate(set) var outputVolume: Float + public fileprivate(set) var screenCaptureSources: [InlineRTCScreenCaptureSource] = [] + public fileprivate(set) var selectedScreenCaptureSource: InlineRTCScreenCaptureSource? + public fileprivate(set) var screenShareQualityProfile: InlineRTCScreenShareQualityProfile + public fileprivate(set) var screenShareEpisodeID: UInt64 = 0 + public fileprivate(set) var screenShareState: InlineRTCScreenShareState = .off + public fileprivate(set) var screenShares: [InlineRTCScreenShare] = [] + public fileprivate(set) var isRefreshingScreenCaptureSources = false + public fileprivate(set) var screenCaptureError: String? + public fileprivate(set) var screenCaptureIssue: GridScreenCaptureIssue? + + fileprivate init( + microphoneEnabled: Bool, + autoUnmuteOnJoin: Bool, + autoMuteWhenAlone: Bool, + inputSelection: AudioInputSelection, + screenShareQualityProfile: InlineRTCScreenShareQualityProfile = .automatic, + outputSelection: AudioOutputSelection = .automatic, + outputVolume: Float = 1 + ) { + isMicrophoneEnabled = microphoneEnabled + self.autoUnmuteOnJoin = autoUnmuteOnJoin + self.autoMuteWhenAlone = autoMuteWhenAlone + self.inputSelection = inputSelection + self.screenShareQualityProfile = screenShareQualityProfile + self.outputSelection = outputSelection + self.outputVolume = min(max(outputVolume, 0), 1) + } + + public var isUsingAutomaticInput: Bool { + inputSelection == .automatic + } + + public var hasRemoteAudioFlowFailure: Bool { + remoteAudioFlowStates.values.contains(.missing) + } + + public var isScreenSharing: Bool { + screenShares.contains(where: \.isLocal) + } + + public var isScreenShareRequested: Bool { + selectedScreenCaptureSource != nil + } + + public func prefersInputDevice(_ device: AudioInputDeviceDescriptor) -> Bool { + inputSelection.matches(device, among: inputDevices) + } + + public func prefersOutputDevice(_ device: AudioOutputDeviceDescriptor) -> Bool { + outputSelection.matches(device, among: outputDevices) + } + +} + +/// The sole writer for `GridMediaPresentation`. Views only receive the +/// presentation reference and therefore cannot feed state back into its owner. +@MainActor +final class GridMediaPresentationController { + let presentation: GridMediaPresentation + + init( + microphoneEnabled: Bool, + autoUnmuteOnJoin: Bool, + autoMuteWhenAlone: Bool, + inputSelection: AudioInputSelection, + screenShareQualityProfile: InlineRTCScreenShareQualityProfile = .automatic, + outputSelection: AudioOutputSelection = .automatic, + outputVolume: Float = 1 + ) { + presentation = GridMediaPresentation( + microphoneEnabled: microphoneEnabled, + autoUnmuteOnJoin: autoUnmuteOnJoin, + autoMuteWhenAlone: autoMuteWhenAlone, + inputSelection: inputSelection, + screenShareQualityProfile: screenShareQualityProfile, + outputSelection: outputSelection, + outputVolume: outputVolume + ) + } + + func setMicrophoneEnabled(_ enabled: Bool) { + presentation.isMicrophoneEnabled = enabled + } + + func setAutoUnmuteOnJoin(_ enabled: Bool) { + presentation.autoUnmuteOnJoin = enabled + } + + func setAutoMuteWhenAlone(_ enabled: Bool) { + presentation.autoMuteWhenAlone = enabled + } + + func setDesiredInputSelection(_ selection: AudioInputSelection) { + presentation.inputSelection = selection + } + + func setDesiredOutputSelection(_ selection: AudioOutputSelection) { + presentation.outputSelection = selection + } + + func setDesiredOutputVolume(_ volume: Float) { + presentation.outputVolume = min(max(volume, 0), 1) + } + + func setSelectedScreenCaptureSource(_ source: InlineRTCScreenCaptureSource?) { + if presentation.selectedScreenCaptureSource == nil, source != nil { + presentation.screenShareEpisodeID &+= 1 + } + presentation.selectedScreenCaptureSource = source + } + + func setScreenShareQualityProfile(_ profile: InlineRTCScreenShareQualityProfile) { + presentation.screenShareQualityProfile = profile + } + + func clearScreenCaptureError() { + presentation.screenCaptureError = nil + presentation.screenCaptureIssue = nil + } + + func setRefreshingScreenCaptureSources(_ isRefreshing: Bool) { + presentation.isRefreshingScreenCaptureSources = isRefreshing + } + + func applyScreenCaptureSources( + _ result: Result<[InlineRTCScreenCaptureSource], Error> + ) { + switch result { + case let .success(sources): + presentation.screenCaptureSources = sources + presentation.screenCaptureError = nil + presentation.screenCaptureIssue = nil + case let .failure(error): + presentation.screenCaptureSources = [] + presentation.screenCaptureError = error.localizedDescription + presentation.screenCaptureIssue = .sourceDiscovery + } + } + + func apply(audio snapshot: InlineRTCAudioSnapshot) { + presentation.audioState = snapshot.state + presentation.isAudioSafetyPaused = snapshot.isSafetyPaused + presentation.isMicrophoneCapturePrepared = snapshot.isPrepared + presentation.microphonePermission = snapshot.microphonePermission + if let resolvedInput = snapshot.input { + presentation.activeInputDeviceID = resolvedInput.activeDeviceID + presentation.isFallingBackToAutomaticInput = resolvedInput.isFallingBackToAutomatic + } + if let resolvedOutput = snapshot.output { + presentation.activeOutputDeviceID = resolvedOutput.activeDeviceID + presentation.isFallingBackToAutomaticOutput = resolvedOutput.isFallingBackToAutomatic + } + } + + func apply(rtc snapshot: InlineRTCConnectionSnapshot) { + presentation.connectionState = switch snapshot.state { + case .idle: .disconnected + case .connected: .connected + case .failed: .failed + default: .connecting + } + presentation.participantAudioLevels = Dictionary( + uniqueKeysWithValues: snapshot.participants.map { + ($0.identity, $0.isSpeaking ? $0.audioLevel : 0) + } + ) + presentation.participantScreenShareIntents = Dictionary( + uniqueKeysWithValues: snapshot.participants.compactMap { participant in + participant.screenShareIntent.map { (participant.identity, $0) } + } + ) + presentation.connectedParticipantIdentities = Set(snapshot.participants.map(\.identity)) + presentation.hasParticipantMediaSnapshot = snapshot.hasParticipantMediaSnapshot + presentation.reconnectCount = snapshot.reconnectCount + presentation.recoveryAttempt = snapshot.recoveryAttempt + presentation.lastConnectDurationMilliseconds = snapshot.lastConnectMilliseconds + presentation.lastDisconnectDurationMilliseconds = snapshot.lastDisconnectMilliseconds + presentation.lastConnectionError = snapshot.lastError + presentation.microphonePublicationState = snapshot.microphonePublicationState + presentation.localAudioFlowState = snapshot.localAudioFlowState + presentation.remoteAudioFlowStates = snapshot.remoteAudioFlowStates + presentation.screenShareState = snapshot.screenShareState + presentation.screenShares = snapshot.screenShares + if case let .failed(message) = snapshot.screenShareState, + message != "Screen sharing stopped" { + presentation.screenCaptureError = message + presentation.screenCaptureIssue = .sharing + } else if case let .failed(message) = snapshot.screenShareState, + message == "Screen sharing stopped" { + presentation.screenCaptureError = nil + presentation.screenCaptureIssue = nil + } else if snapshot.screenShareState == .off, + presentation.connectionState == .connected, + presentation.screenCaptureError == "Screen sharing did not stop" { + // A failed Stop is recoverable through room reconstruction. Keep the + // warning visible until the fresh connected projection proves that no + // local publication remains, then retire the transient recovery notice. + presentation.screenCaptureError = nil + presentation.screenCaptureIssue = nil + } + presentation.abandonedProviderOperationCount = snapshot.abandonedProviderOperationCount + presentation.providerCircuitOpen = snapshot.providerCircuitOpen + } + + func apply(devices snapshot: AudioInputDeviceSnapshot) { + presentation.automaticInputDeviceName = snapshot.automaticDeviceName + presentation.inputDevices = snapshot.devices + presentation.activeInputDeviceID = snapshot.resolvedInput.activeDeviceID + presentation.isFallingBackToAutomaticInput = snapshot.resolvedInput.isFallingBackToAutomatic + } + + func apply(outputDevices snapshot: AudioOutputDeviceSnapshot) { + presentation.automaticOutputDeviceName = snapshot.automaticDeviceName + presentation.outputDevices = snapshot.devices + presentation.activeOutputDeviceID = snapshot.resolvedOutput.activeDeviceID + presentation.isFallingBackToAutomaticOutput = + snapshot.resolvedOutput.isFallingBackToAutomatic + } +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridMediaTarget.swift b/apple/InlineKit/Sources/InlineGrid/GridMediaTarget.swift new file mode 100644 index 000000000..74d364a05 --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridMediaTarget.swift @@ -0,0 +1,18 @@ +import Foundation +import InlineRTC + +/// Grid's product identity for its current media session. +/// +/// Space, room, and generation remain app-owned. InlineRTC receives only the +/// stable opaque identity produced below and never interprets these fields. +struct GridMediaTarget: Equatable, Hashable, Sendable { + let callID: String + let membershipID: String + let spaceID: Int64 + let roomID: Int64 + let generation: Int32 + + var rtcSessionID: InlineRTCSessionID { + InlineRTCSessionID("grid:\(callID):\(membershipID):\(spaceID):\(roomID):\(generation)") + } +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridMembershipSync.swift b/apple/InlineKit/Sources/InlineGrid/GridMembershipSync.swift new file mode 100644 index 000000000..303912132 --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridMembershipSync.swift @@ -0,0 +1,173 @@ +import Auth +import Foundation +import InlineProtocol +import Logger + +struct GridMembershipOperation: Equatable, Sendable { + enum Kind: Equatable, Sendable { + case create + case join(roomID: Int64) + case leave(roomID: Int64) + case move(callID: String, roomID: Int64) + } + + let kind: Kind + let spaceID: Int64 + let expectedMembershipID: String + let intentRevision: Int + let accountToken: AuthAccountMutationToken + let microphoneEnabled: Bool + let automaticMicrophoneChange: GridAutomaticMicrophoneChange? + let accessRevision: Int + let revision: Int + let startedAt: Date +} + +enum GridMembershipSyncEvent: Sendable { + case created(GridMembershipOperation, GridRoomMutationResult) + case joined(GridMembershipOperation, GridRoomMutationResult) + case left(GridMembershipOperation, GridRoomMutationResult) + case moved(GridMembershipOperation, GridRoomMutationResult) + case failed(GridMembershipOperation, message: String) +} + +/// The sole remote executor for Grid avatar membership changes. +/// +/// The UI applies product state optimistically, then submits operations here. +/// A single retained worker preserves create/join/leave order at the server even +/// during rapid interaction. Events carry revisions so the room service can +/// ignore responses superseded by newer local intent without losing wire order. +@MainActor +final class GridMembershipSync { + private struct QueuedOperation { + let generation: Int + let operation: GridMembershipOperation + } + + private let api: GridRoomAPI + private let auth: AuthHandle + private let log = Log.scoped("GridMembershipSync") + private var generation = 0 + private var queue: [QueuedOperation] = [] + private var workerTask: Task? + private var workerGeneration: Int? + private var subscribers: [UUID: AsyncStream.Continuation] = [:] + + init(api: GridRoomAPI, auth: AuthHandle) { + self.api = api + self.auth = auth + } + + deinit { + workerTask?.cancel() + subscribers.values.forEach { $0.finish() } + } + + func subscribe() -> AsyncStream { + let id = UUID() + let stream = AsyncStream.makeStream( + of: GridMembershipSyncEvent.self, + // Results are revisioned and every success carries authoritative Grid + // state. A newer result supersedes an older event for a stalled consumer. + bufferingPolicy: .bufferingNewest(64) + ) + stream.continuation.onTermination = { [weak self] _ in + Task { @MainActor in self?.subscribers.removeValue(forKey: id) } + } + subscribers[id] = stream.continuation + return stream.stream + } + + func submit(_ operation: GridMembershipOperation) { + queue.append(QueuedOperation(generation: generation, operation: operation)) + startIfNeeded() + } + + /// Invalidates queued work during logout. An in-flight RPC may still finish + /// at the transport, but its event is discarded and it cannot retain the + /// worker slot needed by a later authenticated generation. + func reset() { + generation &+= 1 + queue.removeAll() + workerTask?.cancel() + workerTask = nil + workerGeneration = nil + } + + private func startIfNeeded() { + guard workerTask == nil, !queue.isEmpty else { return } + let workerGeneration = generation + self.workerGeneration = workerGeneration + workerTask = Task { [weak self] in + await self?.run(workerGeneration: workerGeneration) + } + } + + private func run(workerGeneration: Int) async { + while !Task.isCancelled, generation == workerGeneration, !queue.isEmpty { + let queued = queue.removeFirst() + guard queued.generation == workerGeneration else { continue } + let operation = queued.operation + do { + try auth.validateAccountMutation(operation.accountToken) + let event: GridMembershipSyncEvent = switch operation.kind { + case .create: + try await .created( + operation, + api.createRoom( + spaceID: operation.spaceID, + microphoneEnabled: operation.microphoneEnabled, + expectedMembershipID: operation.expectedMembershipID, + accountToken: operation.accountToken + ) + ) + case let .join(roomID): + try await .joined( + operation, + api.joinRoom( + roomID: roomID, + microphoneEnabled: operation.microphoneEnabled, + expectedMembershipID: operation.expectedMembershipID, + accountToken: operation.accountToken + ) + ) + case let .leave(roomID): + try await .left( + operation, + api.leaveRoom( + roomID: roomID, + expectedMembershipID: operation.expectedMembershipID, + accountToken: operation.accountToken + ) + ) + case let .move(callID, _): + try await .moved( + operation, + api.moveCallHere( + callID: callID, + expectedMembershipID: operation.expectedMembershipID, + accountToken: operation.accountToken + ) + ) + } + guard !Task.isCancelled, workerGeneration == generation else { continue } + broadcast(event) + } catch { + guard !Task.isCancelled, workerGeneration == generation else { continue } + log.error( + "GRID_TRACE phase=membership_rpc_failed revision=\(operation.revision)", + error: error + ) + broadcast(.failed(operation, message: String(describing: error))) + } + } + guard self.workerGeneration == workerGeneration else { return } + workerTask = nil + self.workerGeneration = nil + startIfNeeded() + } + + private func broadcast(_ event: GridMembershipSyncEvent) { + subscribers.values.forEach { $0.yield(event) } + } +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridNetworkMonitor.swift b/apple/InlineKit/Sources/InlineGrid/GridNetworkMonitor.swift new file mode 100644 index 000000000..cd9bbbc1b --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridNetworkMonitor.swift @@ -0,0 +1,78 @@ +import Foundation +import Network + +struct GridNetworkPathSnapshot: Equatable, Sendable { + let available: Bool + let usesWiFi: Bool + let usesWiredEthernet: Bool + let usesCellular: Bool + let constrained: Bool + let expensive: Bool + + init(path: NWPath) { + available = path.status == .satisfied + usesWiFi = path.usesInterfaceType(.wifi) + usesWiredEthernet = path.usesInterfaceType(.wiredEthernet) + usesCellular = path.usesInterfaceType(.cellular) + constrained = path.isConstrained + expensive = path.isExpensive + } + + var interfaceDescription: String { + if usesWiredEthernet { return "ethernet" } + if usesWiFi { return "wifi" } + if usesCellular { return "cellular" } + return "other" + } +} + +/// Owns `NWPathMonitor` and vends independent watch streams. Consumers never +/// compete for values from one shared `AsyncStream` iterator. +@MainActor +final class GridNetworkMonitor { + private let monitor = NWPathMonitor() + private let queue = DispatchQueue(label: "Grid.NetworkMonitor") + private var subscribers: [UUID: AsyncStream.Continuation] = [:] + private var latest: GridNetworkPathSnapshot? + + init() { + monitor.pathUpdateHandler = { [weak self] path in + let snapshot = GridNetworkPathSnapshot(path: path) + Task { @MainActor [weak self] in + self?.broadcast(snapshot) + } + } + monitor.start(queue: queue) + } + + deinit { + monitor.cancel() + subscribers.values.forEach { $0.finish() } + } + + func subscribe() -> AsyncStream { + let id = UUID() + let stream = AsyncStream.makeStream( + of: GridNetworkPathSnapshot.self, + bufferingPolicy: .bufferingNewest(1) + ) + stream.continuation.onTermination = { [weak self] _ in + Task { @MainActor in self?.subscribers.removeValue(forKey: id) } + } + subscribers[id] = stream.continuation + if let latest { + stream.continuation.yield(latest) + } + return stream.stream + } + + private func broadcast(_ snapshot: GridNetworkPathSnapshot) { + // Realtime and LiveKit observe their own socket/path transitions, including + // same-interface Wi-Fi changes. Grid only needs a separate recovery epoch + // when this coarse availability/interface state actually changes; emitting + // identical callbacks can otherwise create a refresh/retry feedback loop. + guard latest != snapshot else { return } + latest = snapshot + subscribers.values.forEach { $0.yield(snapshot) } + } +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridPlatformEffects.swift b/apple/InlineKit/Sources/InlineGrid/GridPlatformEffects.swift new file mode 100644 index 000000000..6866bd63c --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridPlatformEffects.swift @@ -0,0 +1,31 @@ +import InlineProtocol +import InlineRTC + +/// Presentation effects supplied by the host app. Room authority, media +/// admission, and transport ownership remain in the shared Grid service. +@MainActor +public struct GridPlatformEffects { + public enum Sound { + case join + case leave + case connected + } + + public var playSound: ((Sound) -> Void)? + public var displayIDUnderPointer: (() -> UInt32?)? + public var updateScreenShareOutline: ((InlineRTCScreenCaptureSource?) -> Void)? + public var closeScreenShares: (() -> Void)? + public var openScreenShare: ((InlineProtocol.User, InlineRTCScreenShare, GridRoomService) -> Void)? + public var presentScreenShareChange: ((GridScreenShareChange, GridRoomService) -> Void)? + public var showError: ((String) -> Void)? + + public init() {} +} + +public struct GridScreenShareChange { + public let spaceID: Int64 + public let roomID: Int64 + public let user: InlineProtocol.User + public let participantIdentity: String + public let started: Bool +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridRoomAPI.swift b/apple/InlineKit/Sources/InlineGrid/GridRoomAPI.swift new file mode 100644 index 000000000..1b7ee9477 --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridRoomAPI.swift @@ -0,0 +1,256 @@ +import Auth +import InlineKit +import InlineProtocol +import RealtimeV2 + +struct GridRoomMutationResult: Sendable { + let grids: [InlineProtocol.Grid] + let credentials: GridConnectionCredentials? + let currentCall: GridCurrentCall? + var moved = true +} + +struct GridReadResult { + let grid: InlineProtocol.Grid + let currentCall: GridCurrentCall? +} + +struct GridHomeResult { + let spaces: [GridHomeSpace] + let currentCall: GridCurrentCall? + let callTransferEnabled: Bool +} + +/// The typed server boundary for Grid rooms. +/// +/// Product state and optimistic UI never inspect RPC oneofs directly. Keeping +/// validation here makes every room operation return one predictable shape. +@MainActor +final class GridRoomAPI { + typealias Send = @MainActor @Sendable (any Transaction2, AuthAccountMutationToken) async throws -> RpcResult + .OneOf_Result? + private let sendTransaction: Send + private let auth: AuthHandle + + init(realtime: RealtimeV2, auth: AuthHandle, send: Send? = nil) { + sendTransaction = send ?? { transaction, token in + try await realtime.send(transaction, expectedAccount: token) + } + self.auth = auth + } + + func settings(spaceID: Int64) async throws -> SpaceSettings { + let result = try await send(.getSpaceSettings(spaceID: spaceID)) + guard case let .getSpaceSettings(response)? = result, response.hasSettings else { + throw GridRoomAPIError.invalidResponse + } + return response.settings + } + + func setEnabled(_ enabled: Bool, spaceID: Int64) async throws -> SpaceSettings { + let result = try await send(.toggleSpaceGrid(spaceID: spaceID, enabled: enabled)) + guard case let .toggleSpaceGrid(response)? = result, response.hasSettings else { + throw GridRoomAPIError.invalidResponse + } + return response.settings + } + + func grid( + spaceID: Int64, + expectedMembershipID: String? = nil, + accountToken: AuthAccountMutationToken + ) async throws -> GridReadResult { + let result = try await send( + .getGrid(spaceID: spaceID, expectedMembershipID: expectedMembershipID), + accountToken: accountToken + ) + guard case let .getGrid(response)? = result, response.hasGrid else { + throw GridRoomAPIError.invalidResponse + } + return GridReadResult(grid: response.grid, currentCall: response.hasCurrentCall ? response.currentCall : nil) + } + + func home(accountToken: AuthAccountMutationToken) async throws -> GridHomeResult { + let result = try await send(.getGridHome(), accountToken: accountToken) + guard case let .getGridHome(response)? = result else { + throw GridRoomAPIError.invalidResponse + } + return GridHomeResult( + spaces: response.spaces, + currentCall: response.hasCurrentCall ? response.currentCall : nil, + callTransferEnabled: response.callTransferEnabled + ) + } + + func openRoomThread(roomID: Int64, membershipID: String) async throws -> Int64 { + let result = try await send(.openGridThread(roomID: roomID, membershipID: membershipID)) + guard case let .openGridThread(response)? = result, response.chatID > 0 else { + throw GridRoomAPIError.invalidResponse + } + return response.chatID + } + + func setTranscription(request: GridTranscriptionRequest) async throws -> InlineProtocol.Grid { + let result = try await send(.setGridTranscription(request: request)) + guard case let .setGridTranscription(response)? = result, response.hasGrid else { + throw GridRoomAPIError.invalidResponse + } + return response.grid + } + + func listTranscripts(roomID: Int64) async throws -> [GridTranscriptDestinationInfo] { + let result = try await send(.listGridTranscripts(roomID: roomID)) + guard case let .listGridTranscripts(response)? = result else { + throw GridRoomAPIError.invalidResponse + } + return Array(response.transcripts.prefix(20)) + } + + func createRoom( + spaceID: Int64, + microphoneEnabled: Bool, + expectedMembershipID: String, + accountToken: AuthAccountMutationToken + ) async throws -> GridRoomMutationResult { + let result = try await send( + .createGridRoom( + spaceID: spaceID, + microphoneEnabled: microphoneEnabled, + expectedMembershipID: expectedMembershipID + ), + accountToken: accountToken + ) + guard case let .createGridRoom(response)? = result else { + throw GridRoomAPIError.invalidResponse + } + return GridRoomMutationResult( + grids: response.grids, + credentials: response.hasConnection ? response.connection : nil, + currentCall: response.hasCurrentCall ? response.currentCall : nil + ) + } + + func joinRoom( + roomID: Int64, + microphoneEnabled: Bool, + expectedMembershipID: String, + accountToken: AuthAccountMutationToken + ) async throws -> GridRoomMutationResult { + let result = try await send( + .joinGridRoom(roomID: roomID, microphoneEnabled: microphoneEnabled, expectedMembershipID: expectedMembershipID), + accountToken: accountToken + ) + guard case let .joinGridRoom(response)? = result else { + throw GridRoomAPIError.invalidResponse + } + return GridRoomMutationResult( + grids: response.grids, + credentials: response.hasConnection ? response.connection : nil, + currentCall: response.hasCurrentCall ? response.currentCall : nil + ) + } + + func leaveRoom( + roomID: Int64, + expectedMembershipID: String, + accountToken: AuthAccountMutationToken + ) async throws -> GridRoomMutationResult { + let result = try await send( + .leaveGridRoom(roomID: roomID, expectedMembershipID: expectedMembershipID), + accountToken: accountToken + ) + guard case let .leaveGridRoom(response)? = result else { + throw GridRoomAPIError.invalidResponse + } + return GridRoomMutationResult( + grids: response.grids, + credentials: nil, + currentCall: response.hasCurrentCall ? response.currentCall : nil + ) + } + + func moveCallHere( + callID: String, + expectedMembershipID: String, + accountToken: AuthAccountMutationToken + ) async throws -> GridRoomMutationResult { + let result = try await send( + .moveGridCallHere(callID: callID, expectedMembershipID: expectedMembershipID), + accountToken: accountToken + ) + guard case let .moveGridCallHere(response)? = result else { throw GridRoomAPIError.invalidResponse } + return GridRoomMutationResult( + grids: response.grids, + credentials: response.hasConnection ? response.connection : nil, + currentCall: response.hasCurrentCall ? response.currentCall : nil, + moved: response.moved + ) + } + + func setLocked(_ locked: Bool, roomID: Int64) async throws -> InlineProtocol.Grid { + let result = try await send(.setGridRoomLocked(roomID: roomID, locked: locked)) + guard case let .setGridRoomLocked(response)? = result, response.hasGrid else { + throw GridRoomAPIError.invalidResponse + } + return response.grid + } + + func setTitle(_ title: String, roomID: Int64) async throws -> InlineProtocol.Grid { + let result = try await send(.setGridRoomTitle(roomID: roomID, title: title)) + guard case let .setGridRoomTitle(response)? = result, response.hasGrid else { + throw GridRoomAPIError.invalidResponse + } + return response.grid + } + + func deleteRoom(roomID: Int64) async throws -> InlineProtocol.Grid { + let result = try await send(.deleteGridRoom(roomID: roomID)) + guard case let .deleteGridRoom(response)? = result, response.hasGrid else { + throw GridRoomAPIError.invalidResponse + } + return response.grid + } + + func prepareConnection( + roomID: Int64, + generation: Int32, + expectedMembershipID: String, + accountToken: AuthAccountMutationToken + ) async throws -> GridConnectionCredentials? { + let result = try await send( + .prepareGridConnection(roomID: roomID, generation: generation, expectedMembershipID: expectedMembershipID), + accountToken: accountToken + ) + guard case let .prepareGridConnection(response)? = result else { + throw GridRoomAPIError.invalidResponse + } + return response.hasConnection ? response.connection : nil + } + + func setMicrophoneEnabled( + _ enabled: Bool, + roomID: Int64, + expectedMembershipID: String, + accountToken: AuthAccountMutationToken + ) async throws { + let result = try await send( + .setGridAvatarMicrophoneEnabled(roomID: roomID, enabled: enabled, expectedMembershipID: expectedMembershipID), + accountToken: accountToken + ) + guard case .setGridAvatarMicrophoneEnabled? = result else { + throw GridRoomAPIError.invalidResponse + } + } + + private func send( + _ transaction: any Transaction2, + accountToken: AuthAccountMutationToken? = nil + ) async throws -> RpcResult.OneOf_Result? { + let token = try accountToken ?? auth.beginAccountMutation() + return try await sendTransaction(transaction, token) + } +} + +enum GridRoomAPIError: Error { + case invalidResponse +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridRoomLifecycle.swift b/apple/InlineKit/Sources/InlineGrid/GridRoomLifecycle.swift new file mode 100644 index 000000000..c7f326b1b --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridRoomLifecycle.swift @@ -0,0 +1,83 @@ +import Foundation +import InlineProtocol +import RealtimeV2 + +enum GridRoomLifecycleEvent: Sendable { + case grid(GridEvent) + case realtimeConnection(RealtimeConnectionState) + case network(GridNetworkPathSnapshot) + case heartbeat +} + +/// Collects external lifecycle sources without owning Grid product state. +/// Every subscriber receives its own watch/event stream. +@MainActor +final class GridRoomLifecycle { + private let realtime: RealtimeV2 + private let network: GridNetworkMonitor + private var sourceTasks: [Task] = [] + private var subscribers: [UUID: AsyncStream.Continuation] = [:] + private var started = false + + init(realtime: RealtimeV2, network: GridNetworkMonitor) { + self.realtime = realtime + self.network = network + } + + deinit { + sourceTasks.forEach { $0.cancel() } + subscribers.values.forEach { $0.finish() } + } + + func subscribe() -> AsyncStream { + startIfNeeded() + let id = UUID() + let stream = AsyncStream.makeStream( + of: GridRoomLifecycleEvent.self, + // Lifecycle edges only trigger authoritative refetch/reconciliation; + // retain a bounded recent window for a temporarily suspended observer. + bufferingPolicy: .bufferingNewest(64) + ) + stream.continuation.onTermination = { [weak self] _ in + Task { @MainActor in self?.subscribers.removeValue(forKey: id) } + } + subscribers[id] = stream.continuation + return stream.stream + } + + private func startIfNeeded() { + guard !started else { return } + started = true + sourceTasks = [ + Task { [weak self, realtime] in + for await event in await realtime.gridEvents() { + guard !Task.isCancelled else { return } + self?.broadcast(.grid(event)) + } + }, + Task { [weak self, realtime] in + for await state in await realtime.connectionStates() { + guard !Task.isCancelled else { return } + self?.broadcast(.realtimeConnection(state)) + } + }, + Task { [weak self, network] in + for await snapshot in network.subscribe() { + guard !Task.isCancelled else { return } + self?.broadcast(.network(snapshot)) + } + }, + Task { [weak self] in + while !Task.isCancelled { + try? await Task.sleep(for: .seconds(30)) + guard !Task.isCancelled else { return } + self?.broadcast(.heartbeat) + } + }, + ] + } + + private func broadcast(_ event: GridRoomLifecycleEvent) { + subscribers.values.forEach { $0.yield(event) } + } +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridRuntime.swift b/apple/InlineKit/Sources/InlineGrid/GridRuntime.swift new file mode 100644 index 000000000..cae9ec691 --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridRuntime.swift @@ -0,0 +1,55 @@ +import Foundation +import InlineKit +import InlineRTC +import RealtimeV2 + +/// One authenticated-process owner for Grid's product, RTC, and audio domains. +/// `AppDependencies` is copied per window, but this reference is shared. +@MainActor +public final class GridRuntime { + private static var cachedRuntime: GridRuntime? + + public static var shared: GridRuntime { + if let cachedRuntime { + return cachedRuntime + } + let runtime = GridRuntime() + cachedRuntime = runtime + return runtime + } + + /// Feature-off and logout teardown can inspect the owner without creating + /// media engines for a process that has never opened Grid. + public static var existing: GridRuntime? { + cachedRuntime + } + + private let engine: InlineRTCSession + public let rooms: GridRoomService + + private init( + realtime: RealtimeV2 = Api.realtime, + userDefaults: UserDefaults = .standard + ) { + let engine = InlineRTCSession() + self.engine = engine + rooms = GridRoomService( + realtime: realtime, + userDefaults: userDefaults, + engine: engine + ) + } + + public func configurePlatformEffects(_ effects: GridPlatformEffects) { + rooms.configurePlatformEffects(effects) + } + + public func prepareForLogout() async -> GridMediaShutdownReceipt { + await rooms.prepareForLogout() + return await engine.shutdown() + } + + public func applicationDidWake() async { + await rooms.applicationDidWake() + } +} diff --git a/apple/InlineKit/Sources/InlineGrid/GridStore.swift b/apple/InlineKit/Sources/InlineGrid/GridStore.swift new file mode 100644 index 000000000..5ed230ad2 --- /dev/null +++ b/apple/InlineKit/Sources/InlineGrid/GridStore.swift @@ -0,0 +1,2236 @@ +import Auth +import Foundation +import InlineKit +#if os(iOS) +import InlineAudioPlayback +#endif +import InlineProtocol +import InlineRTC +import Logger +import Observation +import RealtimeV2 + +@MainActor +@Observable +public final class GridRoomService { + public private(set) var grids: [Int64: InlineProtocol.Grid] = [:] + public private(set) var enabledSpaceIDs = Set() + public private(set) var homeSpaces: [GridHomeSpace] = [] + public private(set) var currentCall: GridCurrentCall? + public private(set) var callTransferEnabled = false + public private(set) var membershipMutationInFlight = false + private var localAdmission: GridLocalAdmission? + public var hasLocalAdmission: Bool { + localAdmission != nil + } + + public var mediaSessionIdentity: String? { + currentMediaTarget()?.rtcSessionID.rawValue + } + + public private(set) var loadingSpaceIDs = Set() + public private(set) var failedLoadSpaceIDs = Set() + public private(set) var lastError: String? + public private(set) var networkRefreshRevision = 0 + public private(set) var networkAvailable = true + public private(set) var pendingTranscriptionRequestIDs: [Int64: String] = [:] + public var connectionRecoveryAttempt: Int { + media.recoveryAttempt + } + + public let media: GridMediaPresentation + + public var inputSelection: AudioInputSelection { + get { media.inputSelection } + set { mediaCoordinator.setInput(newValue) } + } + + public var outputSelection: AudioOutputSelection { + get { media.outputSelection } + set { mediaCoordinator.setOutput(newValue) } + } + + public var autoUnmuteOnJoin: Bool { + get { media.autoUnmuteOnJoin } + set { mediaCoordinator.setAutoUnmuteOnJoin(newValue) } + } + + public var autoMuteWhenAlone: Bool { + get { media.autoMuteWhenAlone } + set { + mediaCoordinator.setAutoMuteWhenAlone(newValue) + reconcileAloneAutoMute() + } + } + + @ObservationIgnored private var platformEffects = GridPlatformEffects() + @ObservationIgnored private let realtime: RealtimeV2 + @ObservationIgnored private let auth: AuthHandle + @ObservationIgnored private var authAuthorityObserver: NSObjectProtocol? + @ObservationIgnored private var localIntentRevision = 0 + @ObservationIgnored private var selfReadEpoch = 0 + @ObservationIgnored private var selfReadSequence = 0 + @ObservationIgnored private var lastAcceptedSelfReadSequence = 0 + @ObservationIgnored private var cancelPendingMembershipClaim = false + @ObservationIgnored private var retentionTask: Task? + @ObservationIgnored private var pendingRetention: (eligible: Bool, accountToken: AuthAccountMutationToken)? + @ObservationIgnored private var lastRetainedMediaEligible = false + @ObservationIgnored private let api: GridRoomAPI + @ObservationIgnored private let avatarStateSync: GridAvatarStateSync + @ObservationIgnored private let membershipSync: GridMembershipSync + @ObservationIgnored private let mediaCoordinator: GridMediaCoordinator + @ObservationIgnored private let homePreferences: GridHomePreferences + @ObservationIgnored private var lifecycleTask: Task? + @ObservationIgnored private var mediaEventsTask: Task? + @ObservationIgnored private var membershipEventsTask: Task? + @ObservationIgnored private var lastNetworkPathSnapshot: GridNetworkPathSnapshot? + @ObservationIgnored private let lifecycle: GridRoomLifecycle + @ObservationIgnored private var pendingReloadSpaceIDs = Set() + @ObservationIgnored private var pendingConnectionSpaceIDs = Set() + @ObservationIgnored private var roomMutationRevisions: [OptimisticRoomMutationKey: Int] = [:] + @ObservationIgnored private var pendingRoomMutations: [OptimisticRoomMutationKey: OptimisticRoomMutation] = [:] + @ObservationIgnored private var membershipMutationRevision = 0 + @ObservationIgnored private var retiredMembershipRevision = 0 + @ObservationIgnored private var pendingMembershipMutations: [Int: OptimisticMembershipMutation] = [:] + @ObservationIgnored private var membershipRollbackBaseline = + GridOptimisticState.MembershipRollbackBaseline() + @ObservationIgnored private var acceptedOwnedRoom: GridOwnedRoomAuthority? + @ObservationIgnored private var lastAcceptedSnapshotRevisions: [Int64: Int64] = [:] + @ObservationIgnored private var spaceAccessRevisions: [Int64: Int] = [:] + @ObservationIgnored private var homeLoadRevision = 0 + @ObservationIgnored private var lastRealtimeConnectionState: RealtimeConnectionState? + @ObservationIgnored private var pendingCredentialTarget: GridMediaTarget? + @ObservationIgnored private var credentialRetryTask: Task? + @ObservationIgnored private var credentialRetryTarget: GridMediaTarget? + @ObservationIgnored private var credentialRetryAttempt = 0 + @ObservationIgnored private var aloneAutoMuteTask: Task? + @ObservationIgnored private var aloneAutoMuteTarget: GridAloneAutoMuteTarget? + @ObservationIgnored private var screenShareAloneTask: Task? + @ObservationIgnored private var screenShareAloneTarget: GridScreenShareAloneTarget? + @ObservationIgnored private var screenShareAloneGrace = GridScreenShareAloneGraceState() + @ObservationIgnored private var screenShareNotificationTarget: GridMediaTarget? + @ObservationIgnored private var observedRemoteScreenShares: [String: GridScreenShareNotice] = [:] + @ObservationIgnored private var hasPrimedScreenSharePresentationEdges = false + @ObservationIgnored private var pendingScreenShareOpenTasks: + [String: GridPendingScreenShareOpen] = [:] + @ObservationIgnored private var mediaInteractionStartedAt: Date? + @ObservationIgnored private let log = Log.scoped("GridRoomService") + private static let aloneMediaGraceSeconds = 5 + + init( + realtime: RealtimeV2 = Api.realtime, + userDefaults: UserDefaults = .standard, + engine: InlineRTCSession, + auth: AuthHandle = Auth.shared.handle, + sendGridTransaction: GridRoomAPI.Send? = nil + ) { + self.realtime = realtime + self.auth = auth + let api = GridRoomAPI(realtime: realtime, auth: auth, send: sendGridTransaction) + self.api = api + avatarStateSync = GridAvatarStateSync(api: api) + let membershipSync = GridMembershipSync(api: api, auth: auth) + self.membershipSync = membershipSync + let networkMonitor = GridNetworkMonitor() + lifecycle = GridRoomLifecycle(realtime: realtime, network: networkMonitor) + let inputPreferences = AudioInputPreferenceStore( + defaults: userDefaults, + deviceIDKey: "grid.preferredInputDeviceID", + deviceNameKey: "grid.preferredInputDeviceName" + ) + let outputPreferences = AudioOutputPreferenceStore( + defaults: userDefaults, + deviceIDKey: "grid.preferredOutputDeviceID", + deviceNameKey: "grid.preferredOutputDeviceName" + ) + homePreferences = GridHomePreferences(defaults: userDefaults) + let mediaCoordinator = GridMediaCoordinator( + engine: engine, + defaults: userDefaults, + inputPreferences: inputPreferences, + outputPreferences: outputPreferences + ) + self.mediaCoordinator = mediaCoordinator + media = mediaCoordinator.presentation + authAuthorityObserver = auth.observeAccountAuthorityWillChange { [weak self] retirement in + self?.retireAdmission(for: retirement) + } + lifecycleTask = Task { [weak self, lifecycle] in + for await event in lifecycle.subscribe() { + guard !Task.isCancelled else { return } + await self?.handle(event) + } + } + mediaEventsTask = Task { [weak self, mediaCoordinator] in + for await event in mediaCoordinator.subscribe() { + guard !Task.isCancelled else { return } + await self?.handle(event) + } + } + membershipEventsTask = Task { [weak self, membershipSync] in + for await event in membershipSync.subscribe() { + guard !Task.isCancelled else { return } + await self?.handle(event) + } + } + } + + isolated deinit { + if let authAuthorityObserver { + NotificationCenter.default.removeObserver(authAuthorityObserver) + } + lifecycleTask?.cancel() + mediaEventsTask?.cancel() + membershipEventsTask?.cancel() + credentialRetryTask?.cancel() + aloneAutoMuteTask?.cancel() + screenShareAloneTask?.cancel() + retentionTask?.cancel() + pendingScreenShareOpenTasks.values.forEach { $0.task.cancel() } + } + + public func configurePlatformEffects(_ effects: GridPlatformEffects) { + platformEffects = effects + mediaCoordinator.configurePlatformEffects(effects) + } + + public func clearLastError() { + lastError = nil + } + + public func isEnabled(spaceID: Int64) -> Bool { + enabledSpaceIDs.contains(spaceID) + } + + /// Ends this process's media authority immediately. It does not move or + /// remove a call owned by another device. + @discardableResult + public func withdrawLocalAdmission() -> Task { + localIntentRevision &+= 1 + selfReadEpoch &+= 1 + if membershipMutationInFlight { + cancelPendingMembershipClaim = true + } + clearLocalAdmission() + return mediaCoordinator.withdrawLocalMedia() + } + + /// An older authority transition cannot withdraw a call admitted by a newer login. + private func retireAdmission(for retirement: AuthAccountAuthorityRetirement) { + guard retirement.isCurrent, + localAdmission == nil || localAdmission?.accountToken == retirement.previousAccount + else { return } + // Submission is synchronous. Waiting for this receipt cannot enqueue a second shutdown + // after logout and a successor admission have overtaken this authority transition. + let receipt = withdrawLocalAdmission() + // A held RPC cannot keep the old account's worker slot or claim gate. Restore only the + // confirmed display baseline; neither rollback nor a failed login readmits local media. + if let pending = pendingMembershipMutations[membershipMutationRevision] { + rollbackMembershipMutation(pending) + } + membershipMutationRevision &+= 1 + retiredMembershipRevision = membershipMutationRevision + membershipMutationInFlight = false + cancelPendingMembershipClaim = false + pendingMembershipMutations.removeAll() + membershipRollbackBaseline.clear() + membershipSync.reset() + retirement.requireRetirement(Task { await receipt.value.isLocallyQuiescent }) + } + + public func prepareForLogout() async { + withdrawLocalAdmission() + currentCall = nil + callTransferEnabled = false + membershipMutationInFlight = false + cancelPendingMembershipClaim = false + let invalidatedSpaceIDs = Set(spaceAccessRevisions.keys) + .union(grids.keys) + .union(enabledSpaceIDs) + .union(loadingSpaceIDs) + .union(pendingReloadSpaceIDs) + .union(pendingConnectionSpaceIDs) + pendingCredentialTarget = nil + resetCredentialRetry() + cancelAloneAutoMute() + resetScreenShareAloneGrace() + resetScreenSharePresentationEdges() + grids.removeAll() + homeSpaces.removeAll() + enabledSpaceIDs.removeAll() + loadingSpaceIDs.removeAll() + failedLoadSpaceIDs.removeAll() + pendingReloadSpaceIDs.removeAll() + pendingConnectionSpaceIDs.removeAll() + for spaceID in invalidatedSpaceIDs { + spaceAccessRevisions[spaceID, default: 0] &+= 1 + } + homeLoadRevision &+= 1 + roomMutationRevisions.removeAll() + pendingRoomMutations.removeAll() + pendingTranscriptionRequestIDs.removeAll() + membershipMutationRevision &+= 1 + retiredMembershipRevision = membershipMutationRevision + pendingMembershipMutations.removeAll() + membershipRollbackBaseline.clear() + lastAcceptedSnapshotRevisions.removeAll() + acceptedOwnedRoom = nil + membershipSync.reset() + avatarStateSync.setOwnedRoom(nil) + await mediaCoordinator.shutdown() + } + + public func grid(spaceID: Int64) -> InlineProtocol.Grid? { + grids[spaceID] + } + + public var activeTranscriptionRoom: GridRoom? { + grids.values.lazy.compactMap { grid in + guard grid.hasCurrentRoomID, + let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), + room.transcriptionIsRunning, + room.avatars.contains(where: { $0.ownedByCurrentSession && !$0.membershipID.isEmpty }) + else { return nil } + return room + }.first + } + + public func recentAvatars(spaceID: Int64, limit: Int = 4) -> [GridAvatar] { + guard let grid = grids[spaceID] else { return [] } + return Array( + grid.rooms + .flatMap(\.avatars) + .sorted { $0.joinedAt > $1.joinedAt } + .prefix(limit) + ) + } + + public func loadHome() async { + guard realtimeReady else { + log.debug("GRID_TRACE phase=home_load_deferred reason=realtime_not_ready") + return + } + homeLoadRevision &+= 1 + let revision = homeLoadRevision + do { + guard let read = beginSelfRead() else { return } + let response = try await api.home(accountToken: read.accountToken) + guard revision == homeLoadRevision, isCurrent(read) else { return } + applySelfRead(response.currentCall, capability: response.callTransferEnabled, fence: read) + let nextHomeSpaces = response.spaces + let previouslyLoadedSpaceIDs = Set(grids.keys) + homeSpaces = nextHomeSpaces + let eligibleSpaceIDs = Set(homeSpaces.map(\.spaceID)) + enabledSpaceIDs = eligibleSpaceIDs + for spaceID in previouslyLoadedSpaceIDs.subtracting(eligibleSpaceIDs) { + clearLocalGrid(spaceID: spaceID, reason: "home_reconciliation") + } + lastError = nil + } catch { + guard revision == homeLoadRevision else { return } + guard realtimeReady, networkAvailable else { + log.debug("GRID_TRACE phase=home_load_deferred reason=connection_lost") + return + } + lastError = String(describing: error) + log.warning("GRID_TRACE phase=home_load_failed") + PerformanceTrace.breadcrumb( + "Grid Home discovery failed", + category: "Grid.Home", + level: .warning + ) + } + } + + public var orderedHomeSpaces: [GridHomeSpace] { + homePreferences.ordered(homeSpaces) + } + + public func recordGridOpened(spaceID: Int64) { + homePreferences.recordOpened(spaceID: spaceID) + } + + public func audioLevel(userID: Int64) -> Float { + let legacyIdentity = Self.liveKitIdentityPrefix(userID: userID) + return media.participantAudioLevels + .filter { $0.key == legacyIdentity || $0.key.hasPrefix("\(legacyIdentity)-") } + .map(\.value) + .max() ?? 0 + } + + public func isScreenSharing(avatar: GridAvatar) -> Bool { + if avatar.ownedByCurrentSession { + return media.isScreenShareRequested + } + let identities = participantIdentities(for: avatar) + let knownIntents = identities.compactMap { media.participantScreenShareIntents[$0] } + if !knownIntents.isEmpty { + return knownIntents.contains(true) + } + let shares = screenShares(avatar: avatar) + return !shares.isEmpty + } + + public func isScreenShareIntended(participantIdentity: String) -> Bool { + if let intent = media.participantScreenShareIntents[participantIdentity] { + return intent + } + return media.screenShares.contains { $0.participantIdentity == participantIdentity } + } + + public func isScreenSharing(userID: Int64) -> Bool { + let shares = screenShares(userID: userID) + if isOwnedUser(userID) { + return shares.contains(where: \.isLocal) + } + return !shares.isEmpty + } + + public func screenShare(userID: Int64) -> InlineRTCScreenShare? { + let shares = screenShares(userID: userID) + if isOwnedUser(userID) { + return shares.first(where: \.isLocal) + } + return shares.first + } + + public func screenShare(avatar: GridAvatar) -> InlineRTCScreenShare? { + let shares = screenShares(avatar: avatar) + return avatar.ownedByCurrentSession + ? shares.first(where: \.isLocal) + : shares.first + } + + public func screenShares(avatar: GridAvatar) -> [InlineRTCScreenShare] { + let identities = participantIdentities(for: avatar) + return media.screenShares.filter { identities.contains($0.participantIdentity) } + } + + public func screenShares(userID: Int64) -> [InlineRTCScreenShare] { + media.screenShares.filter { + Self.identity($0.participantIdentity, matchesUserID: userID) + } + } + + public func screenShares(participantIdentity: String) -> [InlineRTCScreenShare] { + media.screenShares.filter { $0.participantIdentity == participantIdentity } + } + + public func screenShare(publicationID: String) -> InlineRTCScreenShare? { + media.screenShares.first { $0.publicationID == publicationID } + } + + public func load(spaceID: Int64) async { + guard realtimeReady else { + failedLoadSpaceIDs.remove(spaceID) + pendingConnectionSpaceIDs.insert(spaceID) + log.debug( + "GRID_TRACE phase=load_deferred space=\(spaceID) reason=realtime_not_ready" + ) + return + } + pendingConnectionSpaceIDs.remove(spaceID) + let accessRevision = spaceAccessRevisions[spaceID, default: 0] + guard loadingSpaceIDs.insert(spaceID).inserted else { + pendingReloadSpaceIDs.insert(spaceID) + return + } + failedLoadSpaceIDs.remove(spaceID) + defer { + loadingSpaceIDs.remove(spaceID) + if pendingReloadSpaceIDs.remove(spaceID) != nil { + Task { [weak self] in await self?.load(spaceID: spaceID) } + } + } + + do { + let settings = try await api.settings(spaceID: spaceID) + guard accessRevision == spaceAccessRevisions[spaceID, default: 0] else { return } + applyEnabled(settings.gridEnabled, spaceID: spaceID) + lastError = nil + guard settings.gridEnabled else { + reconcileMediaDemand() + return + } + try await reloadGrid(spaceID: spaceID) + } catch { + guard accessRevision == spaceAccessRevisions[spaceID, default: 0] else { return } + guard realtimeReady, networkAvailable else { + pendingConnectionSpaceIDs.insert(spaceID) + log.debug( + "GRID_TRACE phase=load_deferred space=\(spaceID) reason=connection_lost" + ) + return + } + failedLoadSpaceIDs.insert(spaceID) + lastError = String(describing: error) + log.error("GRID_TRACE phase=load_failed space=\(spaceID)", error: error) + PerformanceTrace.breadcrumb( + "Grid snapshot failed", + category: "Grid.Snapshot", + level: .error, + data: ["space_id": spaceID] + ) + } + } + + public func setEnabled(_ enabled: Bool, spaceID: Int64) async throws { + let accessRevision = spaceAccessRevisions[spaceID, default: 0] + let settings = try await api.setEnabled(enabled, spaceID: spaceID) + guard accessRevision == spaceAccessRevisions[spaceID, default: 0] else { return } + applyEnabled(settings.gridEnabled, spaceID: spaceID) + if enabled { + try await reloadGrid(spaceID: spaceID) + } else { + reconcileMediaDemand() + } + await loadHome() + } + + public func createAndJoin(spaceID: Int64) { + guard let accountToken = beginMembershipClaim() else { return } + let expectedMembershipID = observedMembershipFence() + let startedAt = Date() + #if os(iOS) + mediaCoordinator.setMicrophoneEnabled(false) + #endif + let automaticMicrophoneChange = mediaCoordinator.applyAutoUnmuteOnJoin() + #if os(macOS) + mediaCoordinator.requestMicrophonePermission() + #endif + platformEffects.playSound?(.join) + membershipMutationRevision &+= 1 + membershipSync.submit(GridMembershipOperation( + kind: .create, + spaceID: spaceID, + expectedMembershipID: expectedMembershipID, + intentRevision: localIntentRevision, + accountToken: accountToken, + microphoneEnabled: mediaCoordinator.isMicrophoneEnabled, + automaticMicrophoneChange: automaticMicrophoneChange, + accessRevision: spaceAccessRevisions[spaceID, default: 0], + revision: membershipMutationRevision, + startedAt: startedAt + )) + } + + public func join(roomID: Int64) { + guard let destination = grids.values.first(where: { $0.rooms.contains(where: { $0.id == roomID }) }), + let accountToken = beginMembershipClaim() + else { return } + let expectedMembershipID = observedMembershipFence() + let startedAt = Date() + mediaInteractionStartedAt = destination.rooms.first(where: { $0.id == roomID })?.avatars.isEmpty == false + ? startedAt : nil + #if os(iOS) + mediaCoordinator.setMicrophoneEnabled(false) + #endif + let automaticMicrophoneChange = mediaCoordinator.applyAutoUnmuteOnJoin() + guard let mutation = optimisticallyJoin(roomID: roomID) else { + membershipMutationInFlight = false + return + } + #if os(macOS) + mediaCoordinator.requestMicrophonePermission() + #endif + platformEffects.playSound?(.join) + pendingMembershipMutations[mutation.revision] = mutation + membershipSync.submit(GridMembershipOperation( + kind: .join(roomID: roomID), + spaceID: mutation.spaceID, + expectedMembershipID: expectedMembershipID, + intentRevision: localIntentRevision, + accountToken: accountToken, + microphoneEnabled: mediaCoordinator.isMicrophoneEnabled, + automaticMicrophoneChange: automaticMicrophoneChange, + accessRevision: spaceAccessRevisions[mutation.spaceID, default: 0], + revision: mutation.revision, + startedAt: startedAt + )) + } + + /// Explicitly resumes a cold process or moves the same call from another + /// device. Server ownership alone never admits media after browsing. + public func moveCallHere() { + guard callTransferEnabled, let call = currentCall, + !membershipMutationInFlight, + let accountToken = try? auth.beginAccountMutation() + else { return } + guard validateNativeAdmission() else { return } + localIntentRevision &+= 1 + selfReadEpoch &+= 1 + clearLocalAdmission() + membershipMutationInFlight = true + cancelPendingMembershipClaim = false + membershipMutationRevision &+= 1 + mediaCoordinator.setMicrophoneEnabled(false) + membershipSync.submit(GridMembershipOperation( + kind: .move(callID: call.callID, roomID: call.roomID), + spaceID: call.spaceID, + expectedMembershipID: call.membershipID, + intentRevision: localIntentRevision, + accountToken: accountToken, + microphoneEnabled: false, + automaticMicrophoneChange: nil, + accessRevision: spaceAccessRevisions[call.spaceID, default: 0], + revision: membershipMutationRevision, + startedAt: Date() + )) + } + + public func leaveCurrentRoom(spaceID: Int64) { + // A hanging claim must never make Leave or Cancel unable to stop locally. + if membershipMutationInFlight { + withdrawLocalAdmission() + for spaceID in Array(grids.keys) { + _ = optimisticallyLeave(spaceID: spaceID) + } + pendingMembershipMutations.removeAll() + return + } + let membershipID = observedMembershipFence() + let roomID = currentCall?.ownedByCurrentSession == true + ? currentCall?.roomID : grids[spaceID]?.currentRoomID + let isOwned = currentCall?.ownedByCurrentSession == true + || grids[spaceID]?.rooms + .contains(where: { $0.id == roomID && $0.avatars.contains(where: \.ownedByCurrentSession) }) == true + guard isOwned, let roomID, !membershipID.isEmpty, + let accountToken = try? auth.beginAccountMutation() + else { + withdrawLocalAdmission() + return + } + withdrawLocalAdmission() + cancelPendingMembershipClaim = false + let mutation = optimisticallyLeave(spaceID: spaceID) + if let mutation { + pendingMembershipMutations[mutation.revision] = mutation + } + if mutation == nil { + membershipMutationRevision &+= 1 + } + membershipMutationInFlight = true + platformEffects.playSound?(.leave) + membershipSync.submit(GridMembershipOperation( + kind: .leave(roomID: roomID), + spaceID: spaceID, + expectedMembershipID: membershipID, + intentRevision: localIntentRevision, + accountToken: accountToken, + microphoneEnabled: false, + automaticMicrophoneChange: nil, + accessRevision: spaceAccessRevisions[spaceID, default: 0], + revision: membershipMutationRevision, + startedAt: Date() + )) + } + + private func beginMembershipClaim() -> AuthAccountMutationToken? { + guard !membershipMutationInFlight, + let token = try? auth.beginAccountMutation() + else { return nil } + #if os(iOS) + guard callTransferEnabled else { return nil } + #endif + guard !callTransferEnabled || currentCall?.ownedByCurrentSession != false else { + lastError = "Move your call here first" + return nil + } + guard validateNativeAdmission() else { return nil } + localIntentRevision &+= 1 + selfReadEpoch &+= 1 + clearLocalAdmission() + membershipMutationInFlight = true + cancelPendingMembershipClaim = false + return token + } + + private func validateNativeAdmission() -> Bool { + #if os(iOS) + do { + try InlineAudioSession.shared.validateGridAdmission() + } catch { + lastError = error.localizedDescription + return false + } + #endif + lastError = nil + return true + } + + private func observedMembershipFence() -> String { + if let currentCall { + return currentCall.membershipID + } + return grids.values.lazy.flatMap(\.rooms).flatMap(\.avatars) + .first(where: \.ownedByCurrentSession)?.membershipID ?? "" + } + + public func toggleMicrophone(spaceID: Int64) { + guard hasLocalAdmission, let grid = grids[spaceID], grid.hasCurrentRoomID, + let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), + room.avatars.contains(where: \.ownedByCurrentSession) + else { return } + let roomID = grid.currentRoomID + let enabled = mediaCoordinator.toggleMicrophone() + applyAvatarMicrophoneState( + spaceID: spaceID, + roomID: roomID, + userID: nil, + enabled: enabled + ) + avatarStateSync.submitMicrophoneState(roomID: roomID, enabled: enabled) + reconcileMediaDemand() + } + + public func openRoomThread(roomID: Int64) async throws -> Int64 { + let room = try transcriptionRoom(roomID: roomID) + guard let avatar = room.avatars.first(where: \.ownedByCurrentSession), !avatar.membershipID.isEmpty else { + throw GridRoomAPIError.invalidResponse + } + let chatID = try await api.openRoomThread(roomID: roomID, membershipID: avatar.membershipID) + let latestRoom = try transcriptionRoom(roomID: roomID) + guard latestRoom.avatars.contains(where: { $0.ownedByCurrentSession && $0.membershipID == avatar.membershipID }) + else { + throw GridRoomAPIError.invalidResponse + } + // Thread sidecars are applied by the transaction before navigation. + return chatID + } + + public func setTranscription( + room: GridRoom, + enabled: Bool, + destination: GridTranscriptionRequest.Destination = .last + ) async throws { + let roomID = room.id + guard pendingTranscriptionRequestIDs[roomID] == nil else { throw GridRoomAPIError.invalidResponse } + let latestRoom = try transcriptionRoom(roomID: roomID) + guard let request = GridTranscriptionRequest(room: room, enabled: enabled, destination: destination) else { + throw GridRoomAPIError.invalidResponse + } + guard latestRoom.avatars.contains(where: { $0.ownedByCurrentSession && $0.membershipID == request.membershipID }), + latestRoom.hasConnection, latestRoom.connection.generation == request.generation + else { throw GridRoomAPIError.invalidResponse } + let accessRevision = spaceAccessRevisions[room.spaceID, default: 0] + pendingTranscriptionRequestIDs[roomID] = request.requestID + defer { + if pendingTranscriptionRequestIDs[roomID] == request.requestID { + pendingTranscriptionRequestIDs.removeValue(forKey: roomID) + } + } + do { + let grid = try await api.setTranscription(request: request) + guard accessRevision == spaceAccessRevisions[room.spaceID, default: 0] else { return } + applySnapshot([grid]) + } catch { + // A rejected fence or uncertain response must repair from the authority, + // never synthesize an active/stopped state in the client. + if accessRevision == spaceAccessRevisions[room.spaceID, default: 0] { + try? await reloadGrid(spaceID: room.spaceID) + } + throw error + } + } + + public func listTranscripts(roomID: Int64) async throws -> [GridTranscriptDestinationInfo] { + let room = try transcriptionRoom(roomID: roomID) + let membershipID = room.avatars.first(where: \.ownedByCurrentSession)?.membershipID + let transcripts = try await api.listTranscripts(roomID: roomID) + let latestRoom = try transcriptionRoom(roomID: roomID) + guard latestRoom.avatars.contains(where: { $0.ownedByCurrentSession && $0.membershipID == membershipID }) else { + throw GridRoomAPIError.invalidResponse + } + return transcripts + } + + private func transcriptionRoom(roomID: Int64) throws -> GridRoom { + guard let grid = grids.values.first(where: { $0.hasCurrentRoomID && $0.currentRoomID == roomID }), + let room = grid.rooms.first(where: { $0.id == roomID }), + room.avatars.contains(where: { $0.ownedByCurrentSession && !$0.membershipID.isEmpty }) + else { throw GridRoomAPIError.invalidResponse } + return room + } + + public func toggleCurrentMicrophone() { + guard let grid = grids.values.first(where: { grid in + guard grid.hasCurrentRoomID, + let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }) + else { return false } + return room.avatars.contains(where: \.ownedByCurrentSession) + }) else { return } + + toggleMicrophone(spaceID: grid.spaceID) + } + + public func toggleScreenShare() { + guard currentMediaTarget() != nil else { return } + if media.isScreenShareRequested || media.isScreenSharing { + mediaCoordinator.stopScreenSharing() + resetScreenShareAloneGrace() + return + } + let displayID = platformEffects.displayIDUnderPointer?() + Task { [weak self] in + guard let self else { return } + await mediaCoordinator.startScreenSharing(displayID: displayID) + reconcileScreenShareAloneGrace() + } + } + + public func startScreenSharing(_ source: InlineRTCScreenCaptureSource) { + guard currentMediaTarget() != nil else { return } + mediaCoordinator.startScreenSharing(source) + reconcileScreenShareAloneGrace() + } + + public func setScreenShareQualityProfile(_ profile: InlineRTCScreenShareQualityProfile) { + mediaCoordinator.setScreenShareQualityProfile(profile) + } + + public func stopScreenSharing() { + mediaCoordinator.stopScreenSharing() + resetScreenShareAloneGrace() + } + + public func refreshScreenCaptureSources() { + Task { [weak self] in + await self?.mediaCoordinator.refreshScreenCaptureSources() + } + } + + public func openScreenShare(for avatar: GridAvatar) { + guard let participantIdentity = screenShareParticipantIdentity(for: avatar) else { return } + openScreenShare( + user: avatar.user, + participantIdentity: participantIdentity + ) + } + + public func openScreenShareFromNotification( + spaceID: Int64, + roomID: Int64, + userID: Int64, + participantIdentity: String + ) { + Task { [weak self] in + guard let self else { return } + await load(spaceID: spaceID) + guard let grid = grids[spaceID], + let room = grid.rooms.first(where: { $0.id == roomID }), + let avatar = room.avatars.first(where: { $0.user.id == userID }) + else { return } + openScreenShare( + user: avatar.user, + participantIdentity: participantIdentity + ) + } + } + + public func toggleRoomLock(roomID: Int64, locked: Bool) async { + guard let mutation = optimisticallyUpdateRoom( + roomID: roomID, + field: .locked, + update: { $0.locked = locked } + ) else { return } + do { + let grid = try await api.setLocked(locked, roomID: roomID) + guard isCurrent(mutation) else { return } + pendingRoomMutations.removeValue(forKey: mutation.key) + applySnapshot([grid]) + } catch { + guard isCurrent(mutation) else { return } + rollbackRoomMutation(mutation) + lastError = String(describing: error) + if networkAvailable { + try? await reloadGrid(spaceID: mutation.change.spaceID) + } + } + } + + public func setRoomTitle(roomID: Int64, title: String) async { + let normalizedTitle = title.split(whereSeparator: \Character.isWhitespace).joined(separator: " ") + guard let mutation = optimisticallyUpdateRoom( + roomID: roomID, + field: .title, + update: { room in + if normalizedTitle.isEmpty { + room.clearTitle() + } else { + room.title = normalizedTitle + } + } + ) else { return } + do { + let grid = try await api.setTitle(title, roomID: roomID) + guard isCurrent(mutation) else { return } + pendingRoomMutations.removeValue(forKey: mutation.key) + applySnapshot([grid]) + } catch { + guard isCurrent(mutation) else { return } + rollbackRoomMutation(mutation) + lastError = String(describing: error) + if networkAvailable { + try? await reloadGrid(spaceID: mutation.change.spaceID) + } + } + } + + public func deleteRoom(roomID: Int64) async { + guard let spaceID = grids.first(where: { _, grid in + grid.rooms.contains(where: { $0.id == roomID }) + })?.key else { return } + let accessRevision = spaceAccessRevisions[spaceID, default: 0] + do { + let grid = try await api.deleteRoom(roomID: roomID) + guard accessRevision == spaceAccessRevisions[spaceID, default: 0] else { return } + applySnapshot([grid]) + await loadHome() + } catch { + lastError = String(describing: error) + } + } + + private func reloadGrid(spaceID: Int64, renewingMembershipID: String? = nil) async throws { + let accessRevision = spaceAccessRevisions[spaceID, default: 0] + guard let read = beginSelfRead() else { return } + let response = try await api.grid( + spaceID: spaceID, + expectedMembershipID: renewingMembershipID, + accountToken: read.accountToken + ) + guard accessRevision == spaceAccessRevisions[spaceID, default: 0], isCurrent(read) else { return } + applySnapshot([response.grid]) + applySelfRead(response.currentCall, capability: response.grid.callTransferEnabled, fence: read) + syncMicrophoneStateIfNeeded(grid: response.grid) + await prepareConnectionIfNeeded(grid: response.grid) + } + + private func apply(grids nextGrids: [InlineProtocol.Grid], credentials: GridConnectionCredentials? = nil) async { + applySnapshot(nextGrids) + nextGrids.forEach(syncMicrophoneStateIfNeeded) + if let credentials { + await accept(credentials) + } + } + + private func applySnapshot(_ nextGrids: [InlineProtocol.Grid]) { + for incomingGrid in nextGrids { + let previousRevision = lastAcceptedSnapshotRevisions[incomingGrid.spaceID] + if let previousRevision, incomingGrid.revision < previousRevision { + log.debug( + "GRID_TRACE phase=snapshot_rejected_stale space=\(incomingGrid.spaceID) incoming_revision=\(incomingGrid.revision) current_revision=\(previousRevision)" + ) + continue + } + lastAcceptedSnapshotRevisions[incomingGrid.spaceID] = incomingGrid.revision + // Record ownership before pending room/membership UI overlays. + if incomingGrid.enabled, + let room = incomingGrid.rooms.first(where: { $0.avatars.contains(where: \.ownedByCurrentSession) }), + let avatar = room.avatars.first(where: \.ownedByCurrentSession), !avatar.membershipID.isEmpty + { + acceptedOwnedRoom = GridOwnedRoomAuthority( + spaceID: incomingGrid.spaceID, roomID: room.id, membershipID: avatar.membershipID + ) + } else if acceptedOwnedRoom?.spaceID == incomingGrid.spaceID { + acceptedOwnedRoom = nil + } + var grid = incomingGrid + for mutation in pendingRoomMutations.values where mutation.change.spaceID == grid.spaceID { + grid = GridOptimisticState.applyingRoomIntent(mutation.change, to: grid) + } + grids[grid.spaceID] = grid + failedLoadSpaceIDs.remove(grid.spaceID) + pendingConnectionSpaceIDs.remove(grid.spaceID) + applyEnabled(grid.enabled, spaceID: grid.spaceID) + } + applyPendingMembershipIntent() + + reconcileLocalAdmission() + reconcileAvatarStateOwnership() + reconcileMediaDemand() + } + + private func reconcileMediaDemand() { + let target = currentMediaTarget() + if credentialRetryTarget != target { + resetCredentialRetry() + } + if screenShareNotificationTarget != target { + resetScreenSharePresentationEdges(target: target) + } + mediaCoordinator.setTarget(target) + reconcileAloneAutoMute() + reconcileScreenShareAloneGrace() + } + + private func resetScreenSharePresentationEdges(target: GridMediaTarget? = nil) { + pendingScreenShareOpenTasks.values.forEach { $0.task.cancel() } + pendingScreenShareOpenTasks.removeAll() + screenShareNotificationTarget = target + observedRemoteScreenShares.removeAll() + hasPrimedScreenSharePresentationEdges = false + } + + private func reconcileScreenSharePresentationEdges() { + guard let target = currentMediaTarget(), + screenShareNotificationTarget == target, + media.hasParticipantMediaSnapshot, + let grid = grids[target.spaceID], + let room = grid.rooms.first(where: { $0.id == target.roomID }) + else { return } + + var next: [String: GridScreenShareNotice] = [:] + for avatar in room.avatars where !avatar.ownedByCurrentSession && isScreenSharing(avatar: avatar) { + guard let participantIdentity = screenShareParticipantIdentity(for: avatar) else { continue } + next[participantIdentity] = GridScreenShareNotice( + target: target, + user: avatar.user, + participantIdentity: participantIdentity + ) + } + + guard hasPrimedScreenSharePresentationEdges else { + observedRemoteScreenShares = next + hasPrimedScreenSharePresentationEdges = true + return + } + + let previous = observedRemoteScreenShares + observedRemoteScreenShares = next + for (identity, notice) in next where previous[identity] == nil { + presentScreenShareChange(notice, started: true) + } + for (identity, notice) in previous where next[identity] == nil { + pendingScreenShareOpenTasks.removeValue(forKey: identity)?.task.cancel() + presentScreenShareChange(notice, started: false) + } + } + + private func presentScreenShareChange( + _ notice: GridScreenShareNotice, + started: Bool + ) { + platformEffects.presentScreenShareChange?(GridScreenShareChange( + spaceID: notice.target.spaceID, + roomID: notice.target.roomID, + user: notice.user, + participantIdentity: notice.participantIdentity, + started: started + ), self) + } + + private func reconcileAloneAutoMute() { + guard mediaCoordinator.shouldAutoMuteWhenAlone else { + cancelAloneAutoMute() + return + } + let target = grids.values.lazy.compactMap { grid -> GridAloneAutoMuteTarget? in + guard grid.hasCurrentRoomID, + let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), + room.avatars.count == 1, + let avatar = room.avatars.first, + avatar.ownedByCurrentSession, + avatar.microphoneEnabled, + self.mediaCoordinator.isMicrophoneEnabled + else { return nil } + return GridAloneAutoMuteTarget( + spaceID: grid.spaceID, + roomID: room.id, + userID: avatar.user.id + ) + }.first + + guard target != aloneAutoMuteTarget else { return } + cancelAloneAutoMute() + guard let target else { return } + + aloneAutoMuteTarget = target + aloneAutoMuteTask = Task { [weak self] in + try? await Task.sleep(for: .seconds(Self.aloneMediaGraceSeconds)) + guard !Task.isCancelled else { return } + self?.autoMuteIfStillAlone(target) + } + } + + private func autoMuteIfStillAlone(_ target: GridAloneAutoMuteTarget) { + guard aloneAutoMuteTarget == target else { return } + aloneAutoMuteTask = nil + aloneAutoMuteTarget = nil + + guard mediaCoordinator.shouldAutoMuteWhenAlone, + let grid = grids[target.spaceID], + grid.hasCurrentRoomID, + grid.currentRoomID == target.roomID, + let room = grid.rooms.first(where: { $0.id == target.roomID }), + room.avatars.count == 1, + let avatar = room.avatars.first, + avatar.ownedByCurrentSession, + avatar.user.id == target.userID, + avatar.microphoneEnabled, + mediaCoordinator.isMicrophoneEnabled + else { return } + + log.debug("GRID_TRACE phase=alone_auto_mute room=\(target.roomID)") + toggleMicrophone(spaceID: target.spaceID) + } + + private func cancelAloneAutoMute() { + aloneAutoMuteTask?.cancel() + aloneAutoMuteTask = nil + aloneAutoMuteTarget = nil + } + + private func reconcileScreenShareAloneGrace() { + let context = screenShareAloneContext() + let action = screenShareAloneGrace.reconcile(context) + + guard case let .scheduleStop(publicationIDs) = action, + let mediaTarget = currentMediaTarget() + else { + cancelScreenShareAloneTask() + return + } + + let target = GridScreenShareAloneTarget( + mediaTarget: mediaTarget, + episodeID: context.episodeID, + publicationIDs: publicationIDs + ) + guard target != screenShareAloneTarget else { return } + cancelScreenShareAloneTask() + screenShareAloneTarget = target + screenShareAloneTask = Task { [weak self] in + try? await Task.sleep(for: GridScreenShareAloneGraceState.graceDuration) + guard !Task.isCancelled else { return } + self?.stopScreenShareIfStillAlone(target) + } + } + + private func screenShareAloneContext() -> GridScreenShareAloneGraceState.Context { + let localShares = media.screenShares.filter(\.isLocal) + let localPublicationIDs = Set(localShares.map(\.publicationID)) + let localParticipantIdentities = Set(localShares.map(\.participantIdentity)) + let hasRemoteParticipant = media.connectedParticipantIdentities.contains { + !localParticipantIdentities.contains($0) + } + return .init( + episodeID: media.screenShareEpisodeID, + isConnected: media.connectionState == .connected, + isShareRequested: media.isScreenShareRequested, + localPublicationIDs: localPublicationIDs, + hasRemoteParticipant: hasRemoteParticipant + ) + } + + private func stopScreenShareIfStillAlone(_ target: GridScreenShareAloneTarget) { + guard screenShareAloneTarget == target else { return } + screenShareAloneTask = nil + screenShareAloneTarget = nil + guard currentMediaTarget() == target.mediaTarget, + GridScreenShareAloneGraceState.shouldCommitScheduledStop( + screenShareAloneContext(), + expectedEpisodeID: target.episodeID, + expectedPublicationIDs: target.publicationIDs + ) + else { return } + + log.debug( + "GRID_TRACE phase=alone_auto_stop_screen_share room=\(target.mediaTarget.roomID)" + ) + mediaCoordinator.stopScreenSharing() + resetScreenShareAloneGrace() + } + + private func cancelScreenShareAloneTask() { + screenShareAloneTask?.cancel() + screenShareAloneTask = nil + screenShareAloneTarget = nil + } + + private func resetScreenShareAloneGrace() { + cancelScreenShareAloneTask() + screenShareAloneGrace.reset() + } + + private func optimisticallyUpdateRoom( + roomID: Int64, + field: GridOptimisticState.RoomField, + update: (inout GridRoom) -> Void + ) -> OptimisticRoomMutation? { + guard let change = GridOptimisticState.updatingRoom( + in: grids, + roomID: roomID, + update: update + ) else { return nil } + + guard change.fields == Set([field]) else { return nil } + let key = OptimisticRoomMutationKey(roomID: roomID, field: field) + let revision = (roomMutationRevisions[key] ?? 0) + 1 + roomMutationRevisions[key] = revision + grids[change.spaceID] = change.nextGrid + let mutation = OptimisticRoomMutation( + key: key, + revision: revision, + accessRevision: spaceAccessRevisions[change.spaceID, default: 0], + change: change + ) + pendingRoomMutations[key] = mutation + return mutation + } + + private func rollbackRoomMutation(_ mutation: OptimisticRoomMutation) { + guard isCurrent(mutation) else { return } + pendingRoomMutations.removeValue(forKey: mutation.key) + guard let currentGrid = grids[mutation.change.spaceID] else { return } + grids[mutation.change.spaceID] = GridOptimisticState.rollingBackRoomIntent( + mutation.change, + in: currentGrid + ) + } + + private func isCurrent(_ mutation: OptimisticRoomMutation) -> Bool { + roomMutationRevisions[mutation.key] == mutation.revision + && spaceAccessRevisions[mutation.change.spaceID, default: 0] == mutation.accessRevision + } + + private func optimisticallyJoin(roomID: Int64) -> OptimisticMembershipMutation? { + let fallbackAvatar = makeCurrentUserAvatar() + let joinedAt = Int64(Date().timeIntervalSince1970) + guard let change = GridOptimisticState.joining( + roomID: roomID, + in: grids, + avatar: fallbackAvatar, + microphoneEnabled: mediaCoordinator.isMicrophoneEnabled, + joinedAt: joinedAt + ) else { return nil } + + membershipRollbackBaseline.beginIfNeeded(with: change.previousGrids) + + membershipMutationRevision &+= 1 + let mutation = OptimisticMembershipMutation( + roomID: roomID, + spaceID: change.spaceID, + revision: membershipMutationRevision, + previousGrids: change.previousGrids, + intent: .join(fallbackAvatar: fallbackAvatar, joinedAt: joinedAt) + ) + for (spaceID, grid) in change.nextGrids { + grids[spaceID] = grid + } + reconcileAvatarStateOwnership() + log.debug("GRID_TRACE phase=join_optimistic_applied room=\(roomID) revision=\(mutation.revision)") + return mutation + } + + private func optimisticallyLeave(spaceID: Int64) -> OptimisticMembershipMutation? { + guard let change = GridOptimisticState.leaving(spaceID: spaceID, in: grids) else { + return nil + } + + membershipRollbackBaseline.beginIfNeeded(with: change.previousGrids) + + membershipMutationRevision &+= 1 + let mutation = OptimisticMembershipMutation( + roomID: change.roomID, + spaceID: spaceID, + revision: membershipMutationRevision, + previousGrids: change.previousGrids, + intent: .leave + ) + for (changedSpaceID, grid) in change.nextGrids { + grids[changedSpaceID] = grid + } + reconcileAvatarStateOwnership() + log.debug("GRID_TRACE phase=leave_optimistic_applied room=\(change.roomID) revision=\(mutation.revision)") + return mutation + } + + private func rollbackMembershipMutation(_ mutation: OptimisticMembershipMutation) { + guard membershipMutationRevision == mutation.revision else { return } + grids = GridOptimisticState.rollingBackMembershipIntent( + .init( + roomID: mutation.roomID, + spaceID: mutation.spaceID, + previousGrids: membershipRollbackBaseline.grids ?? mutation.previousGrids, + nextGrids: [:] + ), + in: grids + ) + reconcileAvatarStateOwnership() + reconcileMediaDemand() + } + + private func makeCurrentUserAvatar() -> GridAvatar? { + guard let userID = auth.userId() else { return nil } + + var protocolUser = InlineProtocol.User() + protocolUser.id = userID + if let user = ObjectCache.shared.getUser(id: userID)?.user { + if let firstName = user.firstName { + protocolUser.firstName = firstName + } + if let lastName = user.lastName { + protocolUser.lastName = lastName + } + if let username = user.username { + protocolUser.username = username + } + if let profileCdnURL = user.profileCdnUrl { + protocolUser.profilePhoto = .with { + $0.cdnURL = profileCdnURL + if let uniqueID = user.profileFileUniqueId { + $0.fileUniqueID = uniqueID + } + } + } + } + + return .with { + $0.user = protocolUser + $0.joinedAt = Int64(Date().timeIntervalSince1970) + $0.ownedByCurrentSession = true + $0.microphoneEnabled = mediaCoordinator.isMicrophoneEnabled + } + } + + private func applyEnabled(_ enabled: Bool, spaceID: Int64) { + if enabled { + enabledSpaceIDs.insert(spaceID) + } else { + enabledSpaceIDs.remove(spaceID) + grids[spaceID] = .with { $0.spaceID = spaceID + $0.enabled = false + } + if acceptedOwnedRoom?.spaceID == spaceID { + acceptedOwnedRoom = nil + } + if localAdmission?.spaceID == spaceID { + clearLocalAdmission() + } + if currentCall?.spaceID == spaceID { + currentCall = nil + } + reconcileAvatarStateOwnership() + } + } + + func handle(_ event: GridRoomLifecycleEvent) async { + switch event { + case let .grid(event): + await handle(event) + case let .realtimeConnection(state): + await handleConnectionState(state) + case let .network(snapshot): + await handleNetworkPath(snapshot) + case .heartbeat: + await refreshOwnedPresence() + } + } + + private func handle(_ event: GridMediaCoordinatorEvent) async { + switch event { + case let .credentialsNeeded(target): + guard let grid = grids[target.spaceID] else { return } + await prepareConnectionIfNeeded(grid: grid) + case let .connected(_, rtcConnectMilliseconds): + guard let startedAt = mediaInteractionStartedAt else { return } + mediaInteractionStartedAt = nil + let totalMilliseconds = Self.elapsedMilliseconds(since: startedAt) + PerformanceTrace.breadcrumb( + "Grid connection ready", + category: "Grid.Connection", + level: totalMilliseconds >= 3_000 ? .warning : .info, + data: [ + "click_to_connected_ms": totalMilliseconds, + "rtc_connect_ms": rtcConnectMilliseconds ?? -1, + ] + ) + log.debug( + "GRID_TRACE phase=click_to_connected elapsed_ms=\(totalMilliseconds) rtc_ms=\(rtcConnectMilliseconds ?? -1)" + ) + case .microphoneSafetyPaused: + if let grid = grids.values.first(where: { $0.hasCurrentRoomID }) { + syncMicrophoneStateIfNeeded(grid: grid) + } + reconcileMediaDemand() + case let .audioEligibilityChanged(eligible): + publishMediaRetention(eligible && hasLocalAdmission) + case .screenShareContextChanged: + reconcileScreenSharePresentationEdges() + reconcileScreenShareAloneGrace() + } + } + + func handle(_ event: GridMembershipSyncEvent) async { + let operation: GridMembershipOperation = switch event { + case let .created(value, _), let .joined(value, _), let .moved(value, _), + let .left(value, _), let .failed(value, _): + value + } + // A reply can already be buffered when auth resets its executor. Implicit replacement + // may preserve the account token, so an auth-only revision fence must precede all edits. + guard operation.revision > retiredMembershipRevision, + (try? auth.validateAccountMutation(operation.accountToken)) != nil + else { return } + // Reads concurrent with the claim may linearize before its transaction. + // Their self projection must not be relabeled as a later ownership fact. + selfReadEpoch &+= 1 + let mutation = pendingMembershipMutations.removeValue(forKey: operation.revision) + let accessCurrent = operation.accessRevision == spaceAccessRevisions[operation.spaceID, default: 0] + let intentCurrent = operation.intentRevision == localIntentRevision + let wasCancelled = cancelPendingMembershipClaim || !intentCurrent + // The sole executor is done. A cancellation may enqueue one fenced cleanup + // only after this outcome identifies the claim that actually committed. + membershipMutationInFlight = false + cancelPendingMembershipClaim = false + + // A membership response can have committed before access was revoked. + // Its complete snapshot cannot restore that space after the revocation. + // Repair global call display through a fresh, fenced Home read instead. + guard accessCurrent else { + if pendingMembershipMutations.isEmpty { + membershipRollbackBaseline.clear() + } + await loadHome() + return + } + + switch event { + case let .created(_, response), let .joined(_, response), let .moved(_, response): + mediaCoordinator.commitAutomaticMicrophoneChange(operation.automaticMicrophoneChange) + updateMembershipRollbackBaseline(with: response.grids) + applySnapshot(response.grids) + if accessCurrent, intentCurrent, !wasCancelled, response.moved, + let admission = admission(from: response.currentCall, operation: operation, grids: response.grids) + { + currentCall = response.currentCall + localAdmission = admission + reconcileAvatarStateOwnership() + reconcileMediaDemand() + response.grids.forEach(syncMicrophoneStateIfNeeded) + if let credentials = response.credentials { + await accept(credentials) + } + if let grid = grids[admission.spaceID] { + await prepareConnectionIfNeeded(grid: grid) + } + } else { + clearLocalAdmission() + } + if wasCancelled, accessCurrent, response.moved, + let claim = admission(from: response.currentCall, operation: operation, grids: response.grids) + { + let call = response.currentCall ?? GridCurrentCall.with { + $0.callID = claim.callID + $0.membershipID = claim.membershipID + $0.spaceID = claim.spaceID + $0.roomID = claim.roomID + $0.ownedByCurrentSession = true + } + submitCancelledClaimCleanup(call, accountToken: operation.accountToken) + } else { + await loadHome() + } + case let .left(_, response): + updateMembershipRollbackBaseline(with: response.grids) + applySnapshot(response.grids) + clearLocalAdmission() + await loadHome() + case let .failed(_, message): + mediaInteractionStartedAt = nil + if intentCurrent, let mutation { + rollbackMembershipMutation(mutation) + } + // A failed leave or uncertain claim can restore display membership, but + // never restore local permission/capture/admission. + clearLocalAdmission() + mediaCoordinator.restoreAutomaticMicrophoneChangeIfCurrent(operation.automaticMicrophoneChange) + lastError = message + await loadHome() + if accessCurrent { + try? await reloadGrid(spaceID: operation.spaceID) + } + } + if pendingMembershipMutations.isEmpty { + membershipRollbackBaseline.clear() + } + } + + private func submitCancelledClaimCleanup(_ call: GridCurrentCall, accountToken: AuthAccountMutationToken) { + membershipMutationRevision &+= 1 + membershipMutationInFlight = true + membershipSync.submit(GridMembershipOperation( + kind: .leave(roomID: call.roomID), + spaceID: call.spaceID, + expectedMembershipID: call.membershipID, + intentRevision: localIntentRevision, + accountToken: accountToken, + microphoneEnabled: false, + automaticMicrophoneChange: nil, + accessRevision: spaceAccessRevisions[call.spaceID, default: 0], + revision: membershipMutationRevision, + startedAt: Date() + )) + } + + func handle(_ event: GridEvent) async { + switch event.event { + case let .changed(changed): + selfReadEpoch &+= 1 + if let target = aloneAutoMuteTarget, + changed.spaceIds.contains(target.spaceID) + || (changed.hasRoomID && changed.roomID == target.roomID) + { + cancelAloneAutoMute() + } + log.debug( + "GRID_TRACE phase=changed_event_received spaces=\(changed.spaceIds.map { String($0) }.joined(separator: ",")) room=\(changed.hasRoomID ? String(changed.roomID) : "none")" + ) + for spaceID in changed.spaceIds { + if enabledSpaceIDs.contains(spaceID) { + try? await reloadGrid(spaceID: spaceID) + } else { + // A Grid-changed event is also the cross-session enablement signal. + // Refresh settings when this process still believes Grid is disabled. + await load(spaceID: spaceID) + } + } + await loadHome() + case let .connectionReady(ready): + guard ready.hasCredentials else { return } + log.debug( + "GRID_TRACE phase=ready_event_received room=\(ready.credentials.connection.roomID) generation=\(ready.credentials.connection.generation)" + ) + await accept(ready.credentials) + case let .avatarStateChanged(changed): + log.debug( + "GRID_TRACE phase=avatar_state_event_received room=\(changed.roomID) user=\(changed.userID) microphone=\(changed.microphoneEnabled) revision=\(changed.microphoneRevision)" + ) + applyRemoteAvatarMicrophoneState( + spaceID: changed.spaceID, + roomID: changed.roomID, + userID: changed.userID, + enabled: changed.microphoneEnabled, + membershipID: changed.membershipID, + revision: changed.microphoneRevision + ) + case let .accessRevoked(revoked): + selfReadEpoch &+= 1 + clearLocalGrid(spaceID: revoked.spaceID, reason: "server_revoked") + case nil: + break + } + } + + private func clearLocalGrid(spaceID: Int64, reason: String) { + if acceptedOwnedRoom?.spaceID == spaceID { + acceptedOwnedRoom = nil + } + if currentCall?.spaceID == spaceID { + currentCall = nil + } + if localAdmission?.spaceID == spaceID { + clearLocalAdmission() + } + let hadMediaDemand = currentMediaTarget()?.spaceID == spaceID + spaceAccessRevisions[spaceID, default: 0] &+= 1 + homeLoadRevision &+= 1 + pendingMembershipMutations = pendingMembershipMutations.filter { _, mutation in + mutation.spaceID != spaceID + } + membershipRollbackBaseline.remove(spaceID: spaceID) + if pendingMembershipMutations.isEmpty { + membershipRollbackBaseline.clear() + } + roomMutationRevisions = roomMutationRevisions.filter { key, _ in + grids[spaceID]?.rooms.contains(where: { $0.id == key.roomID }) != true + } + pendingRoomMutations = pendingRoomMutations.filter { _, mutation in + mutation.change.spaceID != spaceID + } + grids.removeValue(forKey: spaceID) + enabledSpaceIDs.remove(spaceID) + homeSpaces.removeAll { $0.spaceID == spaceID } + pendingReloadSpaceIDs.remove(spaceID) + pendingConnectionSpaceIDs.remove(spaceID) + failedLoadSpaceIDs.remove(spaceID) + reconcileAvatarStateOwnership() + + if hadMediaDemand { + pendingCredentialTarget = nil + resetCredentialRetry() + mediaInteractionStartedAt = nil + mediaCoordinator.clearCredentials() + } + reconcileMediaDemand() + log.info("GRID_TRACE phase=local_grid_cleared space=\(spaceID) reason=\(reason)") + PerformanceTrace.breadcrumb( + "Local Grid access cleared", + category: "Grid.Access", + level: .info, + data: ["space_id": spaceID, "reason": reason] + ) + } + + private func prepareConnectionIfNeeded(grid: InlineProtocol.Grid) async { + guard let target = currentMediaTarget(), target.spaceID == grid.spaceID, + let room = grid.rooms.first(where: { $0.id == target.roomID }), + let admission = localAdmission + else { + reconcileMediaDemand() + return + } + reconcileMediaDemand() + // A LiveKit token is only admission material. Once this exact generation + // has an established transport, refreshing an expired token on every + // presence heartbeat adds server work without helping the active room. + // A later disconnect clears this state; the RTC engine then requests fresh + // credentials before its next connection attempt. + if mediaCoordinator.isConnected(to: target) { + resetCredentialRetry() + return + } + if mediaCoordinator.hasUsableCredentials(for: target) { + resetCredentialRetry() + return + } + mediaCoordinator.invalidateCredentials(for: target) + guard pendingCredentialTarget != target else { return } + + pendingCredentialTarget = target + defer { + if pendingCredentialTarget == target { + pendingCredentialTarget = nil + } + } + let startedAt = Date() + log.debug( + "GRID_ENGINE phase=credentials_fetch_start room=\(room.id) generation=\(room.connection.generation)" + ) + do { + guard let connection = try await api.prepareConnection( + roomID: room.id, + generation: room.connection.generation, + expectedMembershipID: target.membershipID, + accountToken: admission.accountToken + ) else { + log.debug( + "GRID_ENGINE phase=credentials_fetch_unavailable room=\(room.id) generation=\(room.connection.generation) elapsed_ms=\(Self.elapsedMilliseconds(since: startedAt))" + ) + scheduleCredentialRetry(for: target) + return + } + log.debug( + "GRID_ENGINE phase=credentials_fetch_done room=\(room.id) generation=\(room.connection.generation) elapsed_ms=\(Self.elapsedMilliseconds(since: startedAt))" + ) + await accept(connection) + } catch { + guard currentMediaTarget() == target, + (try? auth.validateAccountMutation(admission.accountToken)) != nil + else { return } + lastError = String(describing: error) + log.error( + "GRID_ENGINE phase=credentials_fetch_failed room=\(room.id) generation=\(room.connection.generation)", + error: error + ) + scheduleCredentialRetry(for: target) + } + } + + private func accept(_ credentials: GridConnectionCredentials) async { + guard let serverURL = URL(string: credentials.serverURL), + let target = currentMediaTarget(), + let admission = localAdmission, + GridCredentialBinding.accepts( + credentials: credentials, target: target, + userID: admission.accountToken.userID, + callTransferEnabled: callTransferEnabled + ) + else { + log.debug( + "GRID_ENGINE phase=credentials_ignored_stale room=\(credentials.connection.roomID) generation=\(credentials.connection.generation)" + ) + return + } + let accepted = mediaCoordinator.accept(InlineRTCCredentials( + target: target.rtcSessionID, + serverURL: serverURL, + participantIdentity: credentials.participantIdentity, + token: credentials.token, + expiresAt: Date(timeIntervalSince1970: TimeInterval(credentials.expiresAt)) + )) + guard accepted else { return } + resetCredentialRetry() + log.debug( + "GRID_ENGINE phase=credentials_accepted room=\(target.roomID) generation=\(target.generation)" + ) + } + + private func scheduleCredentialRetry(for target: GridMediaTarget) { + guard currentMediaTarget() == target, + credentialRetryTask == nil + else { return } + if credentialRetryTarget != target { + credentialRetryTarget = target + credentialRetryAttempt = 0 + } + credentialRetryAttempt &+= 1 + let attempt = credentialRetryAttempt + let delay = Self.credentialRetryDelay(attempt: attempt) + log.debug( + "GRID_ENGINE phase=credentials_retry_scheduled room=\(target.roomID) generation=\(target.generation) attempt=\(attempt) delay_s=\(delay)" + ) + credentialRetryTask = Task { [weak self] in + try? await Task.sleep(for: .seconds(delay)) + guard !Task.isCancelled else { return } + await self?.credentialRetryFired(target: target, attempt: attempt) + } + } + + private func credentialRetryFired(target: GridMediaTarget, attempt: Int) async { + guard credentialRetryTarget == target, + credentialRetryAttempt == attempt + else { return } + credentialRetryTask = nil + guard currentMediaTarget() == target, + let grid = grids[target.spaceID] + else { + resetCredentialRetry() + return + } + await prepareConnectionIfNeeded(grid: grid) + } + + private func resetCredentialRetry() { + credentialRetryTask?.cancel() + credentialRetryTask = nil + credentialRetryTarget = nil + credentialRetryAttempt = 0 + } + + private func refreshOwnedPresence() async { + guard let admission = localAdmission else { return } + try? await reloadGrid(spaceID: admission.spaceID, renewingMembershipID: admission.membershipID) + } + + private func syncMicrophoneStateIfNeeded(grid: InlineProtocol.Grid) { + guard let admission = localAdmission, admission.spaceID == grid.spaceID, grid.hasCurrentRoomID, + let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), + let avatar = room.avatars.first(where: \.ownedByCurrentSession), + avatar.membershipID == admission.membershipID + else { return } + let microphoneEnabled = mediaCoordinator.isMicrophoneEnabled + guard avatar.microphoneEnabled != microphoneEnabled else { return } + applyAvatarMicrophoneState( + spaceID: grid.spaceID, + roomID: room.id, + userID: nil, + enabled: microphoneEnabled + ) + avatarStateSync.submitMicrophoneState(roomID: room.id, enabled: microphoneEnabled) + } + + private func applyAvatarMicrophoneState( + spaceID: Int64, + roomID: Int64, + userID: Int64?, + enabled: Bool, + revision: Int32? = nil + ) { + guard var grid = grids[spaceID], + let roomIndex = grid.rooms.firstIndex(where: { $0.id == roomID }), + let avatarIndex = grid.rooms[roomIndex].avatars.firstIndex(where: { avatar in + if let userID { + return avatar.user.id == userID + } + return avatar.ownedByCurrentSession + }) + else { return } + grid.rooms[roomIndex].avatars[avatarIndex].microphoneEnabled = enabled + if let revision { + grid.rooms[roomIndex].avatars[avatarIndex].microphoneRevision = revision + } + grids[spaceID] = grid + } + + /// Inline's local microphone intent remains authoritative for the avatar + /// owned by this process. A delayed echo from an earlier rapid toggle must + /// not repaint that avatar with stale server state; other avatars always use + /// the transient state sent by their owning session. + private func applyRemoteAvatarMicrophoneState( + spaceID: Int64, + roomID: Int64, + userID: Int64, + enabled: Bool, + membershipID: String, + revision: Int32 + ) { + guard let grid = grids[spaceID], + let room = grid.rooms.first(where: { $0.id == roomID }), + let avatar = room.avatars.first(where: { $0.user.id == userID }), + avatar.membershipID == membershipID, + revision >= avatar.microphoneRevision + else { return } + applyAvatarMicrophoneState( + spaceID: spaceID, + roomID: roomID, + userID: userID, + enabled: avatar.ownedByCurrentSession ? mediaCoordinator.isMicrophoneEnabled : enabled, + revision: revision + ) + } + + private func handleConnectionState(_ state: RealtimeConnectionState) async { + let previousState = lastRealtimeConnectionState + lastRealtimeConnectionState = state + switch state { + case .connecting: + // Inline realtime and LiveKit have independent transports. Keep media + // alive while the app server reconnects; LiveKit handles its own recovery. + break + case .connected, .updating: + if previousState == nil || previousState == .connecting { + selfReadEpoch &+= 1 + networkRefreshRevision &+= 1 + avatarStateSync.retryLatestFailure() + mediaCoordinator.networkBecameAvailable() + await loadHome() + await retryPendingConnectionLoads() + await refreshOwnedPresence() + } + } + } + + private var realtimeReady: Bool { + switch lastRealtimeConnectionState { + case .connected, .updating: + true + case .connecting, nil: + false + } + } + + private func handleNetworkPath(_ snapshot: GridNetworkPathSnapshot) async { + let previousSnapshot = lastNetworkPathSnapshot + lastNetworkPathSnapshot = snapshot + networkAvailable = snapshot.available + log.debug( + "GRID_TRACE phase=network_path_changed available=\(snapshot.available) interface=\(snapshot.interfaceDescription) constrained=\(snapshot.constrained) expensive=\(snapshot.expensive)" + ) + if previousSnapshot == nil { + return + } + if snapshot.available { + networkRefreshRevision &+= 1 + avatarStateSync.retryLatestFailure() + mediaCoordinator.networkBecameAvailable() + await loadHome() + await retryPendingConnectionLoads() + await refreshOwnedPresence() + } + } + + private func retryPendingConnectionLoads() async { + let deferredSpaceIDs = pendingConnectionSpaceIDs.sorted() + pendingConnectionSpaceIDs.removeAll() + for spaceID in deferredSpaceIDs { + await load(spaceID: spaceID) + } + } + + public func applicationDidWake() async { + avatarStateSync.retryLatestFailure() + mediaCoordinator.applicationDidWake() + await loadHome() + await refreshOwnedPresence() + } + + private func reconcileAvatarStateOwnership() { + guard let admission = localAdmission else { + avatarStateSync.setOwnedRoom(nil) + return + } + avatarStateSync.setOwnedRoom( + admission.roomID, + membershipID: admission.membershipID, + accountToken: admission.accountToken + ) + } + + private func currentMediaTarget() -> GridMediaTarget? { + guard let admission = localAdmission, + (try? auth.validateAccountMutation(admission.accountToken)) != nil, + acceptedOwnedRoom?.spaceID == admission.spaceID, + acceptedOwnedRoom?.roomID == admission.roomID, + acceptedOwnedRoom?.membershipID == admission.membershipID, + let grid = grids[admission.spaceID], grid.enabled, + let room = grid.rooms.first(where: { $0.id == admission.roomID }), room.hasConnection, + room.avatars.contains(where: { $0.ownedByCurrentSession && $0.membershipID == admission.membershipID }) + else { return nil } + if callTransferEnabled { + guard let call = currentCall, call.ownedByCurrentSession, + call.callID == admission.callID, call.membershipID == admission.membershipID + else { return nil } + } + return GridMediaTarget( + callID: admission.callID, + membershipID: admission.membershipID, + spaceID: admission.spaceID, + roomID: admission.roomID, + generation: room.connection.generation + ) + } + + private func isOwnedUser(_ userID: Int64) -> Bool { + grids.values.lazy + .flatMap(\.rooms) + .flatMap(\.avatars) + .contains { $0.ownedByCurrentSession && $0.user.id == userID } + } + + private static func liveKitIdentityPrefix(userID: Int64) -> String { + "inline-grid-user-\(userID)" + } + + private static func identity(_ identity: String, matchesUserID userID: Int64) -> Bool { + let prefix = liveKitIdentityPrefix(userID: userID) + return identity == prefix || identity.hasPrefix("\(prefix)-") + } + + private func participantIdentities(for avatar: GridAvatar) -> Set { + if !avatar.membershipID.isEmpty { + return [ + "\(Self.liveKitIdentityPrefix(userID: avatar.user.id))-\(avatar.membershipID)", + ] + } + return Set( + media.connectedParticipantIdentities.filter { + Self.identity($0, matchesUserID: avatar.user.id) + } + ) + } + + private func screenShareParticipantIdentity(for avatar: GridAvatar) -> String? { + let identities = participantIdentities(for: avatar) + if let identity = identities.sorted().first(where: { + media.participantScreenShareIntents[$0] == true + }) { + return identity + } + if let identity = screenShares(avatar: avatar).first?.participantIdentity { + return identity + } + return identities.sorted().first + } + + private func openScreenShare( + user: InlineProtocol.User, + participantIdentity: String + ) { + if let share = screenShares(participantIdentity: participantIdentity).first { + platformEffects.openScreenShare?(user, share, self) + return + } + guard isScreenShareIntended(participantIdentity: participantIdentity) else { return } + + pendingScreenShareOpenTasks.removeValue(forKey: participantIdentity)?.task.cancel() + let requestID = UUID() + let task = Task { [weak self] in + for _ in 0 ..< 80 { + guard !Task.isCancelled, let self else { return } + if let share = screenShares(participantIdentity: participantIdentity).first { + finishPendingScreenShareOpen( + requestID: requestID, + participantIdentity: participantIdentity + ) + platformEffects.openScreenShare?(user, share, self) + return + } + guard isScreenShareIntended(participantIdentity: participantIdentity) else { + finishPendingScreenShareOpen( + requestID: requestID, + participantIdentity: participantIdentity + ) + return + } + try? await Task.sleep(for: .milliseconds(100)) + } + guard !Task.isCancelled, let self else { return } + finishPendingScreenShareOpen( + requestID: requestID, + participantIdentity: participantIdentity + ) + if isScreenShareIntended(participantIdentity: participantIdentity) { + platformEffects.showError?("The shared screen hasn’t arrived yet") + } + } + pendingScreenShareOpenTasks[participantIdentity] = GridPendingScreenShareOpen( + id: requestID, + task: task + ) + } + + private func finishPendingScreenShareOpen( + requestID: UUID, + participantIdentity: String + ) { + guard pendingScreenShareOpenTasks[participantIdentity]?.id == requestID else { return } + pendingScreenShareOpenTasks[participantIdentity] = nil + } + + public func setInputSelection(_ selection: AudioInputSelection) { + mediaCoordinator.setInput(selection) + } + + public func setOutputSelection(_ selection: AudioOutputSelection) { + mediaCoordinator.setOutput(selection) + } + + public func setOutputVolume(_ volume: Float) { + mediaCoordinator.setOutputVolume(volume) + } + + public func refreshInputDevices() { + mediaCoordinator.refreshInputDevices() + } + + public func refreshOutputDevices() { + mediaCoordinator.refreshOutputDevices() + } + + public func retryAudio() { + mediaCoordinator.retryAudio() + } + + private static func elapsedMilliseconds(since date: Date) -> Int { + Int(Date().timeIntervalSince(date) * 1_000) + } + + private static func credentialRetryDelay(attempt: Int) -> Int { + switch attempt { + case 1: 1 + case 2: 2 + case 3: 4 + case 4: 8 + case 5: 15 + default: 30 + } + } + + private func operationRoomID(_ operation: GridMembershipOperation) -> Int64? { + switch operation.kind { + case .create: nil + case let .join(roomID), let .leave(roomID), let .move(_, roomID): roomID + } + } + + private func applyPendingMembershipIntent() { + guard let mutation = pendingMembershipMutations[membershipMutationRevision], + grids[mutation.spaceID] != nil + else { return } + + let change: GridOptimisticState.MembershipChange? = switch mutation.intent { + case let .join(fallbackAvatar, joinedAt): + GridOptimisticState.joining( + roomID: mutation.roomID, + in: grids, + avatar: fallbackAvatar, + microphoneEnabled: mediaCoordinator.isMicrophoneEnabled, + joinedAt: joinedAt + ) + case .leave: + GridOptimisticState.leaving(spaceID: mutation.spaceID, in: grids) + } + guard let change else { return } + for (spaceID, grid) in change.nextGrids { + grids[spaceID] = grid + } + } + + private func updateMembershipRollbackBaseline(with snapshots: [InlineProtocol.Grid]) { + let confirmedSnapshots = snapshots.filter { snapshot in + let previousRevision = lastAcceptedSnapshotRevisions[snapshot.spaceID] + if let previousRevision { + return snapshot.revision >= previousRevision + } + return true + } + membershipRollbackBaseline.mergeConfirmed(confirmedSnapshots) + } + + private func beginSelfRead() -> GridSelfReadFence? { + guard let token = try? auth.beginAccountMutation() else { return nil } + selfReadSequence &+= 1 + return GridSelfReadFence(epoch: selfReadEpoch, sequence: selfReadSequence, accountToken: token) + } + + private func isCurrent(_ read: GridSelfReadFence) -> Bool { + read.epoch == selfReadEpoch + && (try? auth.validateAccountMutation(read.accountToken)) != nil + } + + private func applySelfRead(_ call: GridCurrentCall?, capability: Bool, fence: GridSelfReadFence) { + guard isCurrent(fence), !membershipMutationInFlight, + fence.sequence >= lastAcceptedSelfReadSequence else { return } + lastAcceptedSelfReadSequence = fence.sequence + callTransferEnabled = capability + currentCall = call + #if os(iOS) + if !capability { + clearLocalAdmission() + } + #endif + reconcileLocalAdmission() + reconcileAvatarStateOwnership() + reconcileMediaDemand() + } + + private func admission( + from call: GridCurrentCall?, + operation: GridMembershipOperation, + grids snapshots: [InlineProtocol.Grid] + ) -> GridLocalAdmission? { + if let call, GridCallClaimAdmission.accepts( + committedCall: call, newestGrid: grids[call.spaceID], acceptedAuthority: acceptedOwnedRoom + ) { + return GridLocalAdmission( + callID: call.callID, membershipID: call.membershipID, + spaceID: call.spaceID, roomID: call.roomID, accountToken: operation.accountToken + ) + } + #if os(macOS) + // Legacy OFF mode remains usable by explicit Create/Join only. Browsing + // never creates an admission even if its auth session is still the owner. + if !callTransferEnabled { + for grid in snapshots where grid.hasCurrentRoomID { + if let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), + let avatar = room.avatars.first(where: \.ownedByCurrentSession), + !avatar.membershipID.isEmpty, + acceptedOwnedRoom?.spaceID == grid.spaceID, + acceptedOwnedRoom?.roomID == room.id, + acceptedOwnedRoom?.membershipID == avatar.membershipID, + self.grids[grid.spaceID]?.rooms.first(where: { $0.id == room.id })?.avatars.contains(where: { + $0.ownedByCurrentSession && $0.membershipID == avatar.membershipID + }) == true + { + return GridLocalAdmission( + callID: "", membershipID: avatar.membershipID, + spaceID: grid.spaceID, roomID: room.id, accountToken: operation.accountToken + ) + } + } + } + #endif + return nil + } + + private func reconcileLocalAdmission() { + guard let admission = localAdmission else { return } + let avatarMatches = grids[admission.spaceID]?.rooms.first(where: { $0.id == admission.roomID })?.avatars + .contains(where: { + $0.ownedByCurrentSession && $0.membershipID == admission.membershipID + }) == true + let callMatches = !callTransferEnabled || (currentCall?.ownedByCurrentSession == true + && currentCall?.callID == admission.callID && currentCall?.membershipID == admission.membershipID) + let authorityMatches = acceptedOwnedRoom?.spaceID == admission.spaceID + && acceptedOwnedRoom?.roomID == admission.roomID + && acceptedOwnedRoom?.membershipID == admission.membershipID + if !avatarMatches || !callMatches || !authorityMatches + || (try? auth.validateAccountMutation(admission.accountToken)) == nil + { + clearLocalAdmission() + } + } + + private func clearLocalAdmission() { + publishMediaRetention(false) + localAdmission = nil + pendingCredentialTarget = nil + resetCredentialRetry() + avatarStateSync.setOwnedRoom(nil) + mediaCoordinator.clearCredentials() + mediaCoordinator.setTarget(nil) + mediaInteractionStartedAt = nil + resetScreenSharePresentationEdges() + } + + private func publishMediaRetention(_ eligible: Bool) { + guard eligible != lastRetainedMediaEligible, + let accountToken = localAdmission?.accountToken ?? (try? auth.beginAccountMutation()) + else { return } + lastRetainedMediaEligible = eligible + pendingRetention = (eligible, accountToken) + guard retentionTask == nil else { return } + retentionTask = Task { [weak self] in + guard let self else { return } + while !Task.isCancelled, let next = pendingRetention { + pendingRetention = nil + await realtime.setGridMediaRetention(eligible: next.eligible, accountToken: next.accountToken) + } + retentionTask = nil + } + } +} + +private struct OptimisticRoomMutationKey: Hashable { + let roomID: Int64 + let field: GridOptimisticState.RoomField +} + +private struct GridAloneAutoMuteTarget: Equatable { + let spaceID: Int64 + let roomID: Int64 + let userID: Int64 +} + +private struct GridScreenShareAloneTarget: Equatable { + let mediaTarget: GridMediaTarget + let episodeID: UInt64 + let publicationIDs: Set +} + +private struct GridScreenShareNotice { + let target: GridMediaTarget + let user: InlineProtocol.User + let participantIdentity: String +} + +private struct GridPendingScreenShareOpen { + let id: UUID + let task: Task +} + +private struct OptimisticRoomMutation { + let key: OptimisticRoomMutationKey + let revision: Int + let accessRevision: Int + let change: GridOptimisticState.RoomChange +} + +private struct OptimisticMembershipMutation { + enum Intent { + case join(fallbackAvatar: GridAvatar?, joinedAt: Int64) + case leave + } + + let roomID: Int64 + let spaceID: Int64 + let revision: Int + let previousGrids: [Int64: InlineProtocol.Grid] + let intent: Intent +} + +private struct GridLocalAdmission { + let callID: String + let membershipID: String + let spaceID: Int64 + let roomID: Int64 + let accountToken: AuthAccountMutationToken +} + +private struct GridSelfReadFence { + let epoch: Int + let sequence: Int + let accountToken: AuthAccountMutationToken +} diff --git a/apple/InlineKit/Sources/InlineKit/Experimental/ExperimentalFeatureFlags.swift b/apple/InlineKit/Sources/InlineKit/Experimental/ExperimentalFeatureFlags.swift index 22ec65a88..13c0d71df 100644 --- a/apple/InlineKit/Sources/InlineKit/Experimental/ExperimentalFeatureFlags.swift +++ b/apple/InlineKit/Sources/InlineKit/Experimental/ExperimentalFeatureFlags.swift @@ -5,6 +5,13 @@ public enum ExperimentalFeatureFlags { public static let agentActivityKey = AgentActivityFeature.preferenceKey public static var agentActivityEnabled: Bool { AgentActivityFeature.isEnabled } + public static let gridIOSKey = "experimental.gridIOS" + + /// Off until physical audio, background and device-transfer qualification is complete. + public static var gridIOSEnabled: Bool { + UserDefaults.standard.bool(forKey: gridIOSKey) + } + public static let newThreadAgentPickerKey = "experimental.newThreadAgentPicker" /// macOS-only, opt-in persistent agent selection for the new-thread composer. diff --git a/apple/InlineKit/Sources/InlineKit/Transactions2/GridTransactions.swift b/apple/InlineKit/Sources/InlineKit/Transactions2/GridTransactions.swift index 2ca1e8d61..b1fdcd1aa 100644 --- a/apple/InlineKit/Sources/InlineKit/Transactions2/GridTransactions.swift +++ b/apple/InlineKit/Sources/InlineKit/Transactions2/GridTransactions.swift @@ -9,10 +9,14 @@ public struct GetSpaceSettingsTransaction: Transaction2 { public struct Context: Sendable, Codable { let spaceID: Int64 } enum CodingKeys: String, CodingKey { case context } - public init(spaceID: Int64) { context = Context(spaceID: spaceID) } + public init(spaceID: Int64) { + context = Context(spaceID: spaceID) + } + public func input(from context: Context) -> RpcCall.OneOf_Input? { .getSpaceSettings(.with { $0.spaceID = context.spaceID }) } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .getSpaceSettings = result else { throw .invalid } } @@ -23,12 +27,21 @@ public struct ToggleSpaceGridTransaction: Transaction2 { public var context: Context public var type: TransactionKindType = .mutation(.init(transient: true)) - public struct Context: Sendable, Codable { let spaceID: Int64; let enabled: Bool } + public struct Context: Sendable, Codable { let spaceID: Int64 + let enabled: Bool + } + enum CodingKeys: String, CodingKey { case context } - public init(spaceID: Int64, enabled: Bool) { context = Context(spaceID: spaceID, enabled: enabled) } + public init(spaceID: Int64, enabled: Bool) { + context = Context(spaceID: spaceID, enabled: enabled) + } + public func input(from context: Context) -> RpcCall.OneOf_Input? { - .toggleSpaceGrid(.with { $0.spaceID = context.spaceID; $0.enabled = context.enabled }) + .toggleSpaceGrid(.with { $0.spaceID = context.spaceID + $0.enabled = context.enabled + }) } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .toggleSpaceGrid = result else { throw .invalid } } @@ -38,14 +51,29 @@ public struct GetGridTransaction: Transaction2 { public var method: InlineProtocol.Method = .getGrid public var context: Context public var type: TransactionKindType = .ephemeral() - public var ephemeralCoalescingKey: String? { "space:\(context.spaceID)" } + public var ephemeralCoalescingKey: String? { + "space:\(context.spaceID):request:\(context.requestID)" + } + + public struct Context: Sendable, Codable { let spaceID: Int64 + let expectedMembershipID: String? + let requestID: UUID + } - public struct Context: Sendable, Codable { let spaceID: Int64 } enum CodingKeys: String, CodingKey { case context } - public init(spaceID: Int64) { context = Context(spaceID: spaceID) } + public init(spaceID: Int64, expectedMembershipID: String? = nil) { + context = Context(spaceID: spaceID, expectedMembershipID: expectedMembershipID, requestID: UUID()) + } + public func input(from context: Context) -> RpcCall.OneOf_Input? { - .getGrid(.with { $0.spaceID = context.spaceID }) + .getGrid(.with { + $0.spaceID = context.spaceID + if let membershipID = context.expectedMembershipID { + $0.expectedMembershipID = membershipID + } + }) } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .getGrid = result else { throw .invalid } } @@ -53,14 +81,19 @@ public struct GetGridTransaction: Transaction2 { public struct GetGridHomeTransaction: Transaction2 { public var method: InlineProtocol.Method = .getGridHome - public var context = Context() + public var context = Context(requestID: UUID()) public var type: TransactionKindType = .ephemeral() - public var ephemeralCoalescingKey: String? { "home" } + public var ephemeralCoalescingKey: String? { + "home:\(context.requestID)" + } - public struct Context: Sendable, Codable {} + public struct Context: Sendable, Codable { let requestID: UUID } enum CodingKeys: String, CodingKey { case context } public init() {} - public func input(from _: Context) -> RpcCall.OneOf_Input? { .getGridHome(.init()) } + public func input(from _: Context) -> RpcCall.OneOf_Input? { + .getGridHome(.init()) + } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .getGridHome = result else { throw .invalid } } @@ -71,17 +104,30 @@ public struct CreateGridRoomTransaction: Transaction2 { public var context: Context public var type: TransactionKindType = .mutation(.init(transient: true)) - public struct Context: Sendable, Codable { let spaceID: Int64; let microphoneEnabled: Bool } + public struct Context: Sendable, Codable { let spaceID: Int64 + let microphoneEnabled: Bool + let expectedMembershipID: String? + } + enum CodingKeys: String, CodingKey { case context } - public init(spaceID: Int64, microphoneEnabled: Bool) { - context = Context(spaceID: spaceID, microphoneEnabled: microphoneEnabled) + public init(spaceID: Int64, microphoneEnabled: Bool, expectedMembershipID: String? = nil) { + context = Context( + spaceID: spaceID, + microphoneEnabled: microphoneEnabled, + expectedMembershipID: expectedMembershipID + ) } + public func input(from context: Context) -> RpcCall.OneOf_Input? { .createGridRoom(.with { $0.spaceID = context.spaceID $0.microphoneEnabled = context.microphoneEnabled + if let membershipID = context.expectedMembershipID { + $0.expectedMembershipID = membershipID + } }) } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .createGridRoom = result else { throw .invalid } } @@ -92,17 +138,26 @@ public struct JoinGridRoomTransaction: Transaction2 { public var context: Context public var type: TransactionKindType = .mutation(.init(transient: true)) - public struct Context: Sendable, Codable { let roomID: Int64; let microphoneEnabled: Bool } + public struct Context: Sendable, Codable { let roomID: Int64 + let microphoneEnabled: Bool + let expectedMembershipID: String? + } + enum CodingKeys: String, CodingKey { case context } - public init(roomID: Int64, microphoneEnabled: Bool) { - context = Context(roomID: roomID, microphoneEnabled: microphoneEnabled) + public init(roomID: Int64, microphoneEnabled: Bool, expectedMembershipID: String? = nil) { + context = Context(roomID: roomID, microphoneEnabled: microphoneEnabled, expectedMembershipID: expectedMembershipID) } + public func input(from context: Context) -> RpcCall.OneOf_Input? { .joinGridRoom(.with { $0.roomID = context.roomID $0.microphoneEnabled = context.microphoneEnabled + if let membershipID = context.expectedMembershipID { + $0.expectedMembershipID = membershipID + } }) } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .joinGridRoom = result else { throw .invalid } } @@ -113,12 +168,24 @@ public struct LeaveGridRoomTransaction: Transaction2 { public var context: Context public var type: TransactionKindType = .mutation(.init(transient: true)) - public struct Context: Sendable, Codable { let roomID: Int64 } + public struct Context: Sendable, Codable { let roomID: Int64 + let expectedMembershipID: String? + } + enum CodingKeys: String, CodingKey { case context } - public init(roomID: Int64) { context = Context(roomID: roomID) } + public init(roomID: Int64, expectedMembershipID: String? = nil) { + context = Context(roomID: roomID, expectedMembershipID: expectedMembershipID) + } + public func input(from context: Context) -> RpcCall.OneOf_Input? { - .leaveGridRoom(.with { $0.expectedRoomID = context.roomID }) + .leaveGridRoom(.with { + $0.expectedRoomID = context.roomID + if let membershipID = context.expectedMembershipID { + $0.expectedMembershipID = membershipID + } + }) } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .leaveGridRoom = result else { throw .invalid } } @@ -129,12 +196,21 @@ public struct SetGridRoomLockedTransaction: Transaction2 { public var context: Context public var type: TransactionKindType = .mutation(.init(transient: true)) - public struct Context: Sendable, Codable { let roomID: Int64; let locked: Bool } + public struct Context: Sendable, Codable { let roomID: Int64 + let locked: Bool + } + enum CodingKeys: String, CodingKey { case context } - public init(roomID: Int64, locked: Bool) { context = Context(roomID: roomID, locked: locked) } + public init(roomID: Int64, locked: Bool) { + context = Context(roomID: roomID, locked: locked) + } + public func input(from context: Context) -> RpcCall.OneOf_Input? { - .setGridRoomLocked(.with { $0.roomID = context.roomID; $0.locked = context.locked }) + .setGridRoomLocked(.with { $0.roomID = context.roomID + $0.locked = context.locked + }) } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .setGridRoomLocked = result else { throw .invalid } } @@ -145,12 +221,21 @@ public struct SetGridRoomTitleTransaction: Transaction2 { public var context: Context public var type: TransactionKindType = .mutation(.init(transient: true)) - public struct Context: Sendable, Codable { let roomID: Int64; let title: String } + public struct Context: Sendable, Codable { let roomID: Int64 + let title: String + } + enum CodingKeys: String, CodingKey { case context } - public init(roomID: Int64, title: String) { context = Context(roomID: roomID, title: title) } + public init(roomID: Int64, title: String) { + context = Context(roomID: roomID, title: title) + } + public func input(from context: Context) -> RpcCall.OneOf_Input? { - .setGridRoomTitle(.with { $0.roomID = context.roomID; $0.title = context.title }) + .setGridRoomTitle(.with { $0.roomID = context.roomID + $0.title = context.title + }) } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .setGridRoomTitle = result else { throw .invalid } } @@ -163,10 +248,14 @@ public struct DeleteGridRoomTransaction: Transaction2 { public struct Context: Sendable, Codable { let roomID: Int64 } enum CodingKeys: String, CodingKey { case context } - public init(roomID: Int64) { context = Context(roomID: roomID) } + public init(roomID: Int64) { + context = Context(roomID: roomID) + } + public func input(from context: Context) -> RpcCall.OneOf_Input? { .deleteGridRoom(.with { $0.roomID = context.roomID }) } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .deleteGridRoom = result else { throw .invalid } } @@ -176,16 +265,30 @@ public struct PrepareGridConnectionTransaction: Transaction2 { public var method: InlineProtocol.Method = .prepareGridConnection public var context: Context public var type: TransactionKindType = .ephemeral() - public var ephemeralCoalescingKey: String? { "room:\(context.roomID)" } + public var ephemeralCoalescingKey: String? { + "room:\(context.roomID):generation:\(context.generation):membership:\(context.expectedMembershipID ?? "legacy")" + } + + public struct Context: Sendable, Codable { let roomID: Int64 + let generation: Int32 + let expectedMembershipID: String? + } - public struct Context: Sendable, Codable { let roomID: Int64; let generation: Int32 } enum CodingKeys: String, CodingKey { case context } - public init(roomID: Int64, generation: Int32) { - context = Context(roomID: roomID, generation: generation) + public init(roomID: Int64, generation: Int32, expectedMembershipID: String? = nil) { + context = Context(roomID: roomID, generation: generation, expectedMembershipID: expectedMembershipID) } + public func input(from context: Context) -> RpcCall.OneOf_Input? { - .prepareGridConnection(.with { $0.roomID = context.roomID; $0.generation = context.generation }) + .prepareGridConnection(.with { + $0.roomID = context.roomID + $0.generation = context.generation + if let membershipID = context.expectedMembershipID { + $0.expectedMembershipID = membershipID + } + }) } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .prepareGridConnection = result else { throw .invalid } } @@ -196,73 +299,139 @@ public struct SetGridAvatarMicTransaction: Transaction2 { public var context: Context public var type: TransactionKindType = .mutation(.init(transient: true)) - public struct Context: Sendable, Codable { let roomID: Int64; let enabled: Bool } + public struct Context: Sendable, Codable { let roomID: Int64 + let enabled: Bool + let expectedMembershipID: String? + } + enum CodingKeys: String, CodingKey { case context } - public init(roomID: Int64, enabled: Bool) { context = Context(roomID: roomID, enabled: enabled) } + public init(roomID: Int64, enabled: Bool, expectedMembershipID: String? = nil) { + context = Context(roomID: roomID, enabled: enabled, expectedMembershipID: expectedMembershipID) + } + public func input(from context: Context) -> RpcCall.OneOf_Input? { .setGridAvatarMicrophoneEnabled(.with { $0.expectedRoomID = context.roomID $0.enabled = context.enabled + if let membershipID = context.expectedMembershipID { + $0.expectedMembershipID = membershipID + } }) } + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { guard case .setGridAvatarMicrophoneEnabled = result else { throw .invalid } } } public extension Transaction2 where Self == GetSpaceSettingsTransaction { - static func getSpaceSettings(spaceID: Int64) -> Self { .init(spaceID: spaceID) } + static func getSpaceSettings(spaceID: Int64) -> Self { + .init(spaceID: spaceID) + } } public extension Transaction2 where Self == ToggleSpaceGridTransaction { - static func toggleSpaceGrid(spaceID: Int64, enabled: Bool) -> Self { .init(spaceID: spaceID, enabled: enabled) } + static func toggleSpaceGrid(spaceID: Int64, enabled: Bool) -> Self { + .init(spaceID: spaceID, enabled: enabled) + } } public extension Transaction2 where Self == GetGridTransaction { - static func getGrid(spaceID: Int64) -> Self { .init(spaceID: spaceID) } + static func getGrid(spaceID: Int64, expectedMembershipID: String? = nil) -> Self { + .init(spaceID: spaceID, expectedMembershipID: expectedMembershipID) + } } public extension Transaction2 where Self == GetGridHomeTransaction { - static func getGridHome() -> Self { .init() } + static func getGridHome() -> Self { + .init() + } } public extension Transaction2 where Self == CreateGridRoomTransaction { - static func createGridRoom(spaceID: Int64, microphoneEnabled: Bool) -> Self { - .init(spaceID: spaceID, microphoneEnabled: microphoneEnabled) + static func createGridRoom(spaceID: Int64, microphoneEnabled: Bool, expectedMembershipID: String? = nil) -> Self { + .init(spaceID: spaceID, microphoneEnabled: microphoneEnabled, expectedMembershipID: expectedMembershipID) } } public extension Transaction2 where Self == JoinGridRoomTransaction { - static func joinGridRoom(roomID: Int64, microphoneEnabled: Bool) -> Self { - .init(roomID: roomID, microphoneEnabled: microphoneEnabled) + static func joinGridRoom(roomID: Int64, microphoneEnabled: Bool, expectedMembershipID: String? = nil) -> Self { + .init(roomID: roomID, microphoneEnabled: microphoneEnabled, expectedMembershipID: expectedMembershipID) } } public extension Transaction2 where Self == LeaveGridRoomTransaction { - static func leaveGridRoom(roomID: Int64) -> Self { .init(roomID: roomID) } + static func leaveGridRoom(roomID: Int64, expectedMembershipID: String? = nil) -> Self { + .init(roomID: roomID, expectedMembershipID: expectedMembershipID) + } } public extension Transaction2 where Self == SetGridRoomLockedTransaction { - static func setGridRoomLocked(roomID: Int64, locked: Bool) -> Self { .init(roomID: roomID, locked: locked) } + static func setGridRoomLocked(roomID: Int64, locked: Bool) -> Self { + .init(roomID: roomID, locked: locked) + } } public extension Transaction2 where Self == SetGridRoomTitleTransaction { - static func setGridRoomTitle(roomID: Int64, title: String) -> Self { .init(roomID: roomID, title: title) } + static func setGridRoomTitle(roomID: Int64, title: String) -> Self { + .init(roomID: roomID, title: title) + } } public extension Transaction2 where Self == DeleteGridRoomTransaction { - static func deleteGridRoom(roomID: Int64) -> Self { .init(roomID: roomID) } + static func deleteGridRoom(roomID: Int64) -> Self { + .init(roomID: roomID) + } } public extension Transaction2 where Self == PrepareGridConnectionTransaction { - static func prepareGridConnection(roomID: Int64, generation: Int32) -> Self { - .init(roomID: roomID, generation: generation) + static func prepareGridConnection(roomID: Int64, generation: Int32, expectedMembershipID: String? = nil) -> Self { + .init(roomID: roomID, generation: generation, expectedMembershipID: expectedMembershipID) } } public extension Transaction2 where Self == SetGridAvatarMicTransaction { - static func setGridAvatarMicrophoneEnabled(roomID: Int64, enabled: Bool) -> Self { - .init(roomID: roomID, enabled: enabled) + static func setGridAvatarMicrophoneEnabled( + roomID: Int64, + enabled: Bool, + expectedMembershipID: String? = nil + ) -> Self { + .init(roomID: roomID, enabled: enabled, expectedMembershipID: expectedMembershipID) + } +} + +/// Explicit ownership claim. A retry must reuse the caller-observed fence; +/// a newer self snapshot is display evidence, never permission to retry a claim. +public struct MoveGridCallHereTransaction: Transaction2 { + public var method: InlineProtocol.Method = .moveGridCallHere + public var context: Context + public var type: TransactionKindType = .mutation(.init(transient: true)) + + public struct Context: Sendable, Codable { + let callID: String + let expectedMembershipID: String + } + + enum CodingKeys: String, CodingKey { case context } + public init(callID: String, expectedMembershipID: String) { + context = Context(callID: callID, expectedMembershipID: expectedMembershipID) + } + + public func input(from context: Context) -> RpcCall.OneOf_Input? { + .moveGridCallHere(.with { + $0.callID = context.callID + $0.expectedMembershipID = context.expectedMembershipID + }) + } + + public func apply(_ result: RpcResult.OneOf_Result?) async throws(TransactionExecutionError) { + guard case .moveGridCallHere = result else { throw .invalid } + } +} + +public extension Transaction2 where Self == MoveGridCallHereTransaction { + static func moveGridCallHere(callID: String, expectedMembershipID: String) -> Self { + .init(callID: callID, expectedMembershipID: expectedMembershipID) } } diff --git a/apple/InlineKit/Sources/InlineRTC/Engine/GridAudioEngine.swift b/apple/InlineKit/Sources/InlineRTC/Engine/GridAudioEngine.swift index 372187012..16b4eaf00 100644 --- a/apple/InlineKit/Sources/InlineRTC/Engine/GridAudioEngine.swift +++ b/apple/InlineKit/Sources/InlineRTC/Engine/GridAudioEngine.swift @@ -12,6 +12,11 @@ protocol GridAudioDriver: Sendable { var recordingStartsWithMicrophoneSender: Bool { get } func configure(_ configuration: InlineRTCConfiguration) async throws + func setMediaDemandActive(_ active: Bool, epoch: UInt64) async throws + /// Provider operations and room retirements have all returned. Direction + /// idle alone cannot transfer AVAudioSession writer authority on iOS. + func mediaDidQuiesce(epoch: UInt64) async + func resumeListeningAfterSafetyPause() async throws /// Opens a new media epoch after a completed terminal barrier. This is not /// capture demand; it only allows the next WebRTC transport to invoke the /// custom device lifecycle. @@ -63,6 +68,9 @@ enum GridAudioPlayoutRecoveryDeferral: String, Equatable, Sendable { } extension GridAudioDriver { + func setMediaDemandActive(_: Bool, epoch _: UInt64) async throws {} + func mediaDidQuiesce(epoch _: UInt64) async {} + func resumeListeningAfterSafetyPause() async throws {} var preparationRequiresRTCTransport: Bool { false } var recordingStartsWithMicrophoneSender: Bool { false } @@ -184,6 +192,8 @@ actor GridAudioEngine { private var desiredOutputVolume: Float = 1 private var lastTransitionMilliseconds: Int? private var microphonePermission: InlineRTCMicrophonePermission = .notDetermined + private var safetyPauseRevision: UInt64 = 0 + private var isSafetyPaused = false /// Configuration/prepare failures require an explicit retry. Input-route /// failures have their own narrower suspension and never block leave/stop. private var engineReconcileSuspended = false @@ -507,10 +517,22 @@ actor GridAudioEngine { !rtcTransportReady } + func setMediaDemandActive(_ active: Bool, epoch: UInt64) async throws { + try await driver.setMediaDemandActive(active, epoch: epoch) + } + + func mediaDidQuiesce(epoch: UInt64) async { + await driver.mediaDidQuiesce(epoch: epoch) + } + func rtcTransportPreparationGeneration() -> UInt64 { rtcTransportGeneration } + func rtcTransportRequiresPreparation() -> Bool { + driver.preparationRequiresRTCTransport + } + /// Closes capture preparation while a fresh provider room is being created. /// A custom M144 ADM can legitimately terminate after the previous room /// releases its last media channel; only the matching new connect may reopen @@ -650,6 +672,14 @@ actor GridAudioEngine { } func retry() async { + let outputResume = await mutateDriver("resume-listening") { [driver] in + try await driver.resumeListeningAfterSafetyPause() + } + if case let .failure(error) = outputResume { + state = .failed(error.localizedDescription) + emitSnapshot() + return + } microphonePermission = await permissionDriver.status() engineReconcileSuspended = false cancelInputRouteRetry(resetAttempt: true) @@ -1785,6 +1815,36 @@ actor GridAudioEngine { switch event { case .devicesChanged: deviceListChanged() + case .safetyPaused: + isSafetyPaused = true + safetyPauseRevision &+= 1 + captureLeases.removeAll() + keepPreparedThroughCooldown = false + cooldownGeneration &+= 1 + recoveryDemandGeneration &+= 1 + engineRecoveryTask?.cancel() + engineRecoveryTask = nil + engineRecoveryPending = false + lifetimeHealthTask?.cancel() + lifetimeHealthTask = nil + engineReconcileSuspended = true + let health = await driver.runtimeHealth() + observeRuntimeHealth(health) + appliedPrepared = health.isRecording + captureEngineHealthy = false + state = health.isRecording + ? .failed("Audio could not pause safely. Leave Grid and try again.") + : .cold + emitSnapshot() + case .listeningResumed: + isSafetyPaused = false + engineReconcileSuspended = false + observeRuntimeHealth(await driver.runtimeHealth()) + appliedPrepared = false + captureEngineHealthy = false + updateStableState() + emitSnapshot() + scheduleReconcile() case let .engineStarting(playout, recording): if !engineRecoveryInFlight { engineRecoveryTask?.cancel() @@ -1796,12 +1856,16 @@ actor GridAudioEngine { scheduleEngineHealthCheck() case let .engineStopped(playout, recording), let .engineDisabled(playout, recording): + observeRuntimeHealth(await driver.runtimeHealth()) + emitSnapshot() await audioEngineStopped(playout: playout, recording: recording) case let .expectedEngineStop(playout, recording), let .expectedEngineDisable(playout, recording): log.debug( "GRID_ENGINE phase=audio_engine_transition_expected playout=\(playout) recording=\(recording)" ) + observeRuntimeHealth(await driver.runtimeHealth()) + emitSnapshot() } } @@ -1825,7 +1889,9 @@ actor GridAudioEngine { }, outputVolume: desiredOutputVolume, lastTransitionMilliseconds: lastTransitionMilliseconds, - microphonePermission: microphonePermission + microphonePermission: microphonePermission, + safetyPauseRevision: safetyPauseRevision, + isSafetyPaused: isSafetyPaused ) } diff --git a/apple/InlineKit/Sources/InlineRTC/Engine/GridEngine.swift b/apple/InlineKit/Sources/InlineRTC/Engine/GridEngine.swift index 653930818..05fa2f695 100644 --- a/apple/InlineKit/Sources/InlineRTC/Engine/GridEngine.swift +++ b/apple/InlineKit/Sources/InlineRTC/Engine/GridEngine.swift @@ -22,9 +22,8 @@ public actor InlineRTCSession { private var eventTasks: [Task] = [] private var commandTask: Task? private var permissionRequestTask: Task? - private var shutdownWaiters: [UUID: CheckedContinuation] = [:] - public init(captureCooldown: Duration = .seconds(2)) { + public init(captureCooldown: Duration = InlineRTCSession.defaultCaptureCooldown) { self.init( configuration: .voice, audioDriver: LiveKitGridAUHALAudioDriver(), @@ -34,6 +33,15 @@ public actor InlineRTCSession { ) } + public static var defaultCaptureCooldown: Duration { + #if os(iOS) + // End capture immediately when the authorized multiparty demand ends. + .zero + #else + .seconds(2) + #endif + } + init( configuration: InlineRTCConfiguration = .voice, audioDriver: any GridAudioDriver, @@ -57,19 +65,6 @@ public actor InlineRTCSession { commandTask?.cancel() permissionRequestTask?.cancel() eventTasks.forEach { $0.cancel() } - let receipt = GridMediaShutdownReceipt( - audio: GridAudioShutdownReceipt( - recordingStopped: false, - playoutStopped: false, - mutationReleased: false, - failures: ["InlineRTCSession deinitialized before shutdown completed."] - ), - rtc: GridRTCShutdownReceipt( - locallyActiveRoomCount: 1, - failures: [] - ) - ) - shutdownWaiters.values.forEach { $0.resume(returning: receipt) } subscribers.values.forEach { $0.finish() } commandMailbox.finish() } @@ -118,6 +113,21 @@ public actor InlineRTCSession { enqueue(.shutdown(requestID: UUID())) } + /// Enqueues the exact retirement boundary before returning to its caller. Awaiting the + /// receipt later cannot submit an old shutdown behind a newer account's media demand. + public nonisolated func requestShutdownReceipt() -> Task { + let completion = GridShutdownCompletion() + enqueue(.shutdown(requestID: UUID(), completion: completion)) + return Task { [weak self] in + if let self { + await self.start() + } else { + completion.complete(.unproven) + } + return await completion.value() + } + } + /// Creates an independent watch-style subscription to the process-wide /// media runtime. Every call owns a distinct AsyncStream; Swift consumers /// never compete for elements from a shared iterator. @@ -161,8 +171,11 @@ public actor InlineRTCSession { for await _ in commandMailbox.signals { guard !Task.isCancelled else { return } while let command = commandMailbox.dequeue() { - guard !Task.isCancelled else { return } - await self?.handle(command) + guard !Task.isCancelled, let self else { + command.failUnfinishedShutdown() + return + } + await self.handle(command) } } } @@ -174,13 +187,8 @@ public actor InlineRTCSession { /// followed by an explicit empty demand before media is disconnected. The /// mailbox remains alive so the same authenticated-process runtime can be /// reused if application dependencies survive a logout/login transition. - public func shutdown() async -> GridMediaShutdownReceipt { - await start() - let requestID = UUID() - return await withCheckedContinuation { continuation in - shutdownWaiters[requestID] = continuation - enqueue(.shutdown(requestID: requestID)) - } + public nonisolated func shutdown() async -> GridMediaShutdownReceipt { + await requestShutdownReceipt().value } private func handle(_ command: GridEngineCommand) async { @@ -205,7 +213,7 @@ public actor InlineRTCSession { case .applicationDidWake: await audio.checkRuntimeHealthAfterInterruption() await rtc.applicationDidWake() - case let .shutdown(requestID): + case let .shutdown(_, completion): let rtcReceipt = await rtc.shutdown() let audioReceipt = await audio.shutdown() let receipt = GridMediaShutdownReceipt(audio: audioReceipt, rtc: rtcReceipt) @@ -216,7 +224,7 @@ public actor InlineRTCSession { "GRID_ENGINE phase=engine_shutdown_incomplete locally_quiescent=false active_rooms=\(receipt.locallyActiveRoomCount) rtc_media_mutations=\(receipt.rtcLocalMediaMutationCount) microphone_publications=\(receipt.microphonePublicationCount) screen_publications=\(receipt.screenSharePublicationCount) audio_mutation_released=\(receipt.audioMutationReleased) failures=\(receipt.failures.joined(separator: ","))" ) } - shutdownWaiters.removeValue(forKey: requestID)?.resume(returning: receipt) + completion?.complete(receipt) } } diff --git a/apple/InlineKit/Sources/InlineRTC/Engine/GridEngineCommandMailbox.swift b/apple/InlineKit/Sources/InlineRTC/Engine/GridEngineCommandMailbox.swift index fbb3a5c34..afd62d0e3 100644 --- a/apple/InlineKit/Sources/InlineRTC/Engine/GridEngineCommandMailbox.swift +++ b/apple/InlineKit/Sources/InlineRTC/Engine/GridEngineCommandMailbox.swift @@ -25,7 +25,10 @@ final class GridEngineCommandMailbox: @unchecked Sendable { func enqueue(_ command: GridEngineCommand) { let shouldSignal = lock.withLock { - guard !finished else { return false } + guard !finished else { + command.failUnfinishedShutdown() + return false + } if case .setDemand = command, let last = queue.indices.last, case .setDemand = queue[last] { @@ -53,6 +56,7 @@ final class GridEngineCommandMailbox: @unchecked Sendable { let shouldFinish = lock.withLock { guard !finished else { return false } finished = true + queue.forEach { $0.failUnfinishedShutdown() } queue.removeAll() return true } diff --git a/apple/InlineKit/Sources/InlineRTC/Engine/GridMediaTypes.swift b/apple/InlineKit/Sources/InlineRTC/Engine/GridMediaTypes.swift index 67e4cfc04..c661a7337 100644 --- a/apple/InlineKit/Sources/InlineRTC/Engine/GridMediaTypes.swift +++ b/apple/InlineKit/Sources/InlineRTC/Engine/GridMediaTypes.swift @@ -48,6 +48,9 @@ public struct InlineRTCDemand: Equatable, Sendable { public var target: InlineRTCSessionID? public var credentials: InlineRTCCredentials? public var microphoneEnabled: Bool + /// Explicit capture preparation for this locally admitted call. iOS keeps + /// this false until the user unmutes; a prior OS permission is not intent. + public var microphoneCapturePrepared: Bool public var screenCaptureSource: InlineRTCScreenCaptureSource? public var screenShareQualityProfile: InlineRTCScreenShareQualityProfile public var input: AudioInputSelection @@ -58,6 +61,7 @@ public struct InlineRTCDemand: Equatable, Sendable { target: InlineRTCSessionID? = nil, credentials: InlineRTCCredentials? = nil, microphoneEnabled: Bool = false, + microphoneCapturePrepared: Bool = false, screenCaptureSource: InlineRTCScreenCaptureSource? = nil, screenShareQualityProfile: InlineRTCScreenShareQualityProfile = .automatic, input: AudioInputSelection = .automatic, @@ -67,6 +71,7 @@ public struct InlineRTCDemand: Equatable, Sendable { self.target = target self.credentials = credentials self.microphoneEnabled = microphoneEnabled + self.microphoneCapturePrepared = microphoneCapturePrepared self.screenCaptureSource = screenCaptureSource self.screenShareQualityProfile = screenShareQualityProfile self.input = input @@ -77,6 +82,8 @@ public struct InlineRTCDemand: Equatable, Sendable { enum GridAudioDriverEvent: Equatable, Sendable { case devicesChanged + case safetyPaused + case listeningResumed case engineStarting(playout: Bool, recording: Bool) case engineStopped(playout: Bool, recording: Bool) case engineDisabled(playout: Bool, recording: Bool) @@ -401,6 +408,47 @@ public struct GridMediaShutdownReceipt: Equatable, Sendable { screenSharePublicationCount = rtc.screenSharePublicationCount failures = rtc.failures + audio.failures } + + static let unproven = GridMediaShutdownReceipt( + audio: GridAudioShutdownReceipt( + recordingStopped: false, playoutStopped: false, mutationReleased: false, + failures: ["InlineRTCSession ended before shutdown completed."] + ), + rtc: GridRTCShutdownReceipt(locallyActiveRoomCount: 1, failures: []) + ) +} + +/// One shutdown command owns its acknowledgement from synchronous submission to completion. +/// Completion may precede its waiter; no actor-owned request registry or enqueue hop is needed. +final class GridShutdownCompletion: @unchecked Sendable, Equatable { + private let lock = NSLock() + private var receipt: GridMediaShutdownReceipt? + private var waiter: CheckedContinuation? + + static func == (lhs: GridShutdownCompletion, rhs: GridShutdownCompletion) -> Bool { lhs === rhs } + + func value() async -> GridMediaShutdownReceipt { + await withCheckedContinuation { continuation in + let completed = lock.withLock { () -> GridMediaShutdownReceipt? in + if let receipt { return receipt } + precondition(waiter == nil) + waiter = continuation + return nil as GridMediaShutdownReceipt? + } + if let completed { continuation.resume(returning: completed) } + } + } + + func complete(_ receipt: GridMediaShutdownReceipt) { + let continuation = lock.withLock { + guard self.receipt == nil else { return nil as CheckedContinuation? } + self.receipt = receipt + let continuation = waiter + waiter = nil + return continuation + } + continuation?.resume(returning: receipt) + } } enum GridAudioPlayoutFailureDisposition: Equatable, Sendable { @@ -431,6 +479,8 @@ public struct InlineRTCAudioSnapshot: Equatable, Sendable { public let outputVolume: Float public let lastTransitionMilliseconds: Int? public let microphonePermission: InlineRTCMicrophonePermission + public let safetyPauseRevision: UInt64 + public let isSafetyPaused: Bool public init( state: InlineRTCAudioState, @@ -449,7 +499,9 @@ public struct InlineRTCAudioSnapshot: Equatable, Sendable { currentMutationMilliseconds: Int? = nil, outputVolume: Float, lastTransitionMilliseconds: Int?, - microphonePermission: InlineRTCMicrophonePermission + microphonePermission: InlineRTCMicrophonePermission, + safetyPauseRevision: UInt64 = 0, + isSafetyPaused: Bool = false ) { self.state = state self.isConfigured = isConfigured @@ -468,6 +520,8 @@ public struct InlineRTCAudioSnapshot: Equatable, Sendable { self.outputVolume = outputVolume self.lastTransitionMilliseconds = lastTransitionMilliseconds self.microphonePermission = microphonePermission + self.safetyPauseRevision = safetyPauseRevision + self.isSafetyPaused = isSafetyPaused } } @@ -648,7 +702,11 @@ enum GridEngineCommand: Equatable, Sendable { case retryAudio case networkBecameAvailable case applicationDidWake - case shutdown(requestID: UUID) + case shutdown(requestID: UUID, completion: GridShutdownCompletion? = nil) + + func failUnfinishedShutdown() { + if case let .shutdown(_, completion) = self { completion?.complete(.unproven) } + } } /// A complete immutable projection of the process-wide InlineRTC runtime. diff --git a/apple/InlineKit/Sources/InlineRTC/Engine/GridRTCEngine.swift b/apple/InlineKit/Sources/InlineRTC/Engine/GridRTCEngine.swift index 5656658e0..e13f9dab3 100644 --- a/apple/InlineKit/Sources/InlineRTC/Engine/GridRTCEngine.swift +++ b/apple/InlineKit/Sources/InlineRTC/Engine/GridRTCEngine.swift @@ -43,6 +43,7 @@ actor GridRTCEngine { private var demand = InlineRTCDemand() private var demandRevision = 0 + private var mediaDemandEpoch: UInt64 = 0 private var demandAudioLease: GridAudioLease? private var lifecycleAudioLeases: [GridRTCRoomHandle: GridAudioLease] = [:] private var reconnectsAwaitingMicrophonePublication = Set() @@ -81,6 +82,9 @@ actor GridRTCEngine { private var lastError: String? private var reconcileTask: Task? private var reconcileTaskID: UUID? + /// Cancellation can detach the task handle while an SDK call still owns + /// native work. Keep every actual task until its completion returns. + private var runningReconcileTaskIDs = Set() private var reconcileRequested = false private var backoffTask: Task? private var microphoneRetryTask: Task? @@ -134,9 +138,12 @@ actor GridRTCEngine { if nextDemand.credentials?.target != nextDemand.target { nextDemand.credentials = nil } + if !nextDemand.microphoneCapturePrepared { nextDemand.microphoneEnabled = false } guard nextDemand != demand else { return } let previousTarget = demand.target + if previousTarget != nextDemand.target { mediaDemandEpoch &+= 1 } demandRevision &+= 1 + let admissionRevision = demandRevision demand = nextDemand backoffTask?.cancel() backoffTask = nil @@ -152,10 +159,38 @@ actor GridRTCEngine { startAudioPreparationWait(for: nextDemand.target) } + do { + if let target = nextDemand.target { + // A retained process reservation is not proof that the previous room + // released its local tracks. Do not reopen the native audio engine or + // prepare the replacement microphone until that receipt is complete. + try await waitForRetirementsBeforeConnect(target: target) + guard demandRevision == admissionRevision else { return } + } + try await audio.setMediaDemandActive(nextDemand.target != nil, epoch: mediaDemandEpoch) + } catch { + guard !Task.isCancelled else { return } + guard demandRevision == admissionRevision else { return } + lastError = error.localizedDescription + state = .failed(nextDemand.target, error.localizedDescription) + emitSnapshot() + // Native ownership conflicts require the user or owner to resolve them. + // Keep desired admission and a stable failure; Retry/audio availability + // or a network wake can re-run the admission check without a new claim. + return + } + // Joining a previous native teardown may suspend admission long enough for + // another demand to replace this one. Only its current revision may apply. + guard demandRevision == admissionRevision else { return } + await audio.setInput(nextDemand.input) + guard demandRevision == admissionRevision, !Task.isCancelled else { return } await audio.setOutput(nextDemand.output) + guard demandRevision == admissionRevision, !Task.isCancelled else { return } await audio.setOutputVolume(nextDemand.outputVolume) + guard demandRevision == admissionRevision, !Task.isCancelled else { return } await replaceDemandAudioLease(for: nextDemand.target) + guard demandRevision == admissionRevision, !Task.isCancelled else { return } log.debug( "GRID_ENGINE phase=rtc_demand_replaced revision=\(demandRevision) room=\(nextDemand.target.map(\.rawValue) ?? "none") microphone=\(nextDemand.microphoneEnabled) screen_share=\(nextDemand.screenCaptureSource != nil)" @@ -197,7 +232,9 @@ actor GridRTCEngine { func shutdown() async -> GridRTCShutdownReceipt { demandRevision &+= 1 + mediaDemandEpoch &+= 1 demand = InlineRTCDemand() + try? await audio.setMediaDemandActive(false, epoch: mediaDemandEpoch) backoffTask?.cancel() backoffTask = nil microphoneRetryTask?.cancel() @@ -212,13 +249,14 @@ actor GridRTCEngine { let deadline = Date().addingTimeInterval(configuration.connection.providerTeardownTimeout) while Date() < deadline { if retirementTasks.isEmpty { - guard !failedLocalQuiescence.isEmpty else { break } + guard !failedLocalQuiescence.isEmpty || !runningReconcileTaskIDs.isEmpty else { break } retryFailedLocalQuiescence() } try? await Task.sleep(for: .milliseconds(10)) } let activeRooms = Set(retirementTasks.keys).union(failedLocalQuiescence.keys) let activeCount = activeRooms.count + let providerMutationCount = runningReconcileTaskIDs.count let localMediaMutationCount = failedLocalQuiescence.values .reduce(0) { $0 + $1.localMediaMutationCount } let microphonePublicationCount = failedLocalQuiescence.values @@ -232,12 +270,19 @@ actor GridRTCEngine { + (retirementTasks.isEmpty ? [] : ["Timed out waiting for \(retirementTasks.count) room(s) to release local media."]) - state = activeCount == 0 + + (providerMutationCount == 0 + ? [] + : ["Timed out waiting for \(providerMutationCount) provider operation(s) to return."]) + state = activeCount == 0 && providerMutationCount == 0 ? .idle : .failed(nil, failures.first ?? "Local room quiescence could not be proven.") emitSnapshot() + await reportMediaQuiescenceIfReady() return GridRTCShutdownReceipt( locallyActiveRoomCount: activeCount, + // A retained publication is also owned by its suspended reconcile task; + // count the concrete mutation once. Pending provider work remains an + // explicit failure, so an idle room receipt cannot imply quiescence. localMediaMutationCount: localMediaMutationCount, microphonePublicationCount: microphonePublicationCount, screenSharePublicationCount: screenSharePublicationCount, @@ -246,17 +291,38 @@ actor GridRTCEngine { } private func replaceDemandAudioLease(for target: InlineRTCSessionID?) async { - let nextLease = target.map(GridAudioLease.connectionDemand) + guard target == demand.target else { return } + let revision = demandRevision + let nextLease = demand.microphoneCapturePrepared + ? target.map(GridAudioLease.connectionDemand) + : nil guard nextLease != demandAudioLease else { return } if let nextLease { await audio.acquireCaptureLease(nextLease) } + guard demandRevision == revision else { + await releaseSupersededDemandAudioLease(nextLease) + return + } if let demandAudioLease { await audio.releaseCaptureLease(demandAudioLease) } + guard demandRevision == revision else { + await releaseSupersededDemandAudioLease(nextLease) + return + } demandAudioLease = nextLease } + private func releaseSupersededDemandAudioLease(_ lease: GridAudioLease?) async { + let currentIntent = demand.microphoneCapturePrepared + ? demand.target.map(GridAudioLease.connectionDemand) + : nil + if let lease, lease != currentIntent { + await audio.releaseCaptureLease(lease) + } + } + private func scheduleReconcile() { guard backoffTask == nil else { return } guard reconcileTask == nil else { @@ -274,6 +340,7 @@ actor GridRTCEngine { return } let taskID = UUID() + runningReconcileTaskIDs.insert(taskID) reconcileTaskID = taskID reconcileTask = Task { [weak self] in await self?.runReconcileLoop() @@ -322,6 +389,31 @@ actor GridRTCEngine { return } + let admissionRevision = demandRevision + do { + try await waitForRetirementsBeforeConnect(target: target) + guard !Task.isCancelled else { return } + guard demandRevision == admissionRevision else { continue } + try await audio.setMediaDemandActive(true, epoch: mediaDemandEpoch) + } catch { + guard !Task.isCancelled else { return } + guard demandRevision == admissionRevision else { continue } + lastError = error.localizedDescription + state = .failed(target, error.localizedDescription) + emitSnapshot() + return + } + guard !Task.isCancelled else { return } + guard demandRevision == admissionRevision else { continue } + await audio.setInput(demand.input) + guard demandRevision == admissionRevision, !Task.isCancelled else { continue } + await audio.setOutput(demand.output) + guard demandRevision == admissionRevision, !Task.isCancelled else { continue } + await audio.setOutputVolume(demand.outputVolume) + guard demandRevision == admissionRevision, !Task.isCancelled else { continue } + await replaceDemandAudioLease(for: target) + guard demandRevision == admissionRevision, !Task.isCancelled else { continue } + if room == nil, pendingAudioTransportGeneration == nil { pendingAudioTransportGeneration = await audio.rtcTransportWillInitialize() } @@ -352,6 +444,11 @@ actor GridRTCEngine { continue } if !microphonePublished { + guard demand.microphoneCapturePrepared else { + microphonePublicationState = .notRequested + emitSnapshot() + return + } guard microphoneRetryTask == nil else { emitSnapshot() return @@ -446,6 +543,7 @@ actor GridRTCEngine { } let audioWaitsForRTCTransport = await audio.isWaitingForRTCTransport() if !audioWaitsForRTCTransport, + demand.microphoneCapturePrepared, !audioSnapshot.isPrepared, audioSnapshot.microphonePermission.permitsCapture { if shouldWaitForAudioPreparation(target: target) { @@ -485,7 +583,11 @@ actor GridRTCEngine { let attemptID = UUID() let lifecycleLease = GridAudioLease.rtcLifecycle(attemptID) - await audio.acquireCaptureLease(lifecycleLease) + #if !os(iOS) + if demand.microphoneCapturePrepared { + await audio.acquireCaptureLease(lifecycleLease) + } + #endif let room: GridRTCRoomHandle do { room = try await driver.makeRoom(configuration: configuration) @@ -607,6 +709,7 @@ actor GridRTCEngine { /// audio churn. Wait for the fast path, but preserve bounded forward progress /// when a provider call is genuinely stuck. private func waitForRetirementsBeforeConnect(target: InlineRTCSessionID) async throws { + try Task.checkCancellation() let timeout = configuration.connection.retirementBarrierTimeout guard !retirementTasks.isEmpty || !failedLocalQuiescence.isEmpty else { return } guard timeout > 0 else { @@ -623,6 +726,7 @@ actor GridRTCEngine { } let elapsed = elapsedMilliseconds(since: startedAt) + try Task.checkCancellation() if retirementTasks.isEmpty, failedLocalQuiescence.isEmpty { log.debug( "GRID_ENGINE phase=rtc_retirement_barrier_finished session=\(target.rawValue) elapsed_ms=\(elapsed)" @@ -1092,6 +1196,7 @@ actor GridRTCEngine { "GRID_ENGINE phase=rtc_retired session=\(target.rawValue) elapsed_ms=\(lastDisconnectMilliseconds ?? 0) locally_quiescent=\(receipt.isQuiescent)" ) emitSnapshot() + await reportMediaQuiescenceIfReady() } private func detach(_ detachedRoom: GridRTCRoomHandle) { @@ -1138,6 +1243,9 @@ actor GridRTCEngine { guard retirementsRequiringAudioTransportFence.remove(retiredRoom) != nil, room == nil else { return } + // Only the custom ADM has a transport-generation fence to close. The iOS + // SDK path would merely reopen native playout while old tracks still exist. + guard await audio.rtcTransportRequiresPreparation() else { return } let generation = await audio.rtcTransportWillInitialize() if demand.target != nil, pendingAudioTransportGeneration == nil { pendingAudioTransportGeneration = generation @@ -1812,18 +1920,35 @@ actor GridRTCEngine { reconcileRequested = false } - private func reconcileFinished(taskID: UUID) { + private func reconcileFinished(taskID: UUID) async { + runningReconcileTaskIDs.remove(taskID) if providerCircuitBreaker.contains(id: taskID) { retiredProviderOperationReturned(id: taskID) } - guard reconcileTaskID == taskID else { return } + guard reconcileTaskID == taskID else { + await reportMediaQuiescenceIfReady() + return + } reconcileTask = nil reconcileTaskID = nil - guard reconcileRequested else { return } + guard reconcileRequested else { + await reportMediaQuiescenceIfReady() + return + } reconcileRequested = false scheduleReconcile() } + private func reportMediaQuiescenceIfReady() async { + guard demand.target == nil, + room == nil, + retirementTasks.isEmpty, + failedLocalQuiescence.isEmpty, + runningReconcileTaskIDs.isEmpty, + providerCircuitBreaker.abandonedOperationCount == 0 else { return } + await audio.mediaDidQuiesce(epoch: mediaDemandEpoch) + } + private func startDriverEventsIfNeeded() { if lifecycleEventsTask == nil { lifecycleEventsTask = Task { [weak self, events = driver.lifecycleEvents] in diff --git a/apple/InlineKit/Sources/InlineRTC/Engine/GridScreenShare.swift b/apple/InlineKit/Sources/InlineRTC/Engine/GridScreenShare.swift index d6340185e..b2bfee168 100644 --- a/apple/InlineKit/Sources/InlineRTC/Engine/GridScreenShare.swift +++ b/apple/InlineKit/Sources/InlineRTC/Engine/GridScreenShare.swift @@ -270,7 +270,7 @@ public enum InlineRTCScreenShareWindowSizePolicy { } public final class InlineRTCVideoTrack: @unchecked Sendable, Equatable { - fileprivate let liveKitTrack: VideoTrack + let liveKitTrack: VideoTrack init(_ liveKitTrack: VideoTrack) { self.liveKitTrack = liveKitTrack diff --git a/apple/InlineKit/Sources/InlineRTC/Engine/InlineRTCVideoView+iOS.swift b/apple/InlineKit/Sources/InlineRTC/Engine/InlineRTCVideoView+iOS.swift new file mode 100644 index 000000000..269e7cd1a --- /dev/null +++ b/apple/InlineKit/Sources/InlineRTC/Engine/InlineRTCVideoView+iOS.swift @@ -0,0 +1,82 @@ +#if os(iOS) +import Foundation +import LiveKit +import UIKit + +/// The same renderer contract used by the Mac viewer, backed by UIKit on iOS. +@MainActor +public final class InlineRTCVideoView: UIView { + private let renderer = VideoView() + private let dimensionObserver = IOSGridVideoDimensionObserver() + + public var onVideoDimensionsChanged: ((InlineRTCVideoDimensions) -> Void)? { + didSet { + dimensionObserver.onDimensionsChanged = { [weak self] dimensions in + self?.onVideoDimensionsChanged?(dimensions) + } + reportCurrentDimensions() + } + } + + public var screenShare: InlineRTCScreenShare? { + didSet { + let oldTrack = oldValue?.videoTrack + let newTrack = screenShare?.videoTrack + if oldTrack != newTrack { + oldTrack?.liveKitTrack.remove(delegate: dimensionObserver) + newTrack?.liveKitTrack.add(delegate: dimensionObserver) + } + renderer.track = isVideoEnabled ? newTrack?.liveKitTrack : nil + reportCurrentDimensions() + } + } + + public var isVideoEnabled: Bool { + get { renderer.isEnabled } + set { + guard renderer.isEnabled != newValue else { return } + if newValue { + renderer.isEnabled = true + renderer.track = screenShare?.videoTrack?.liveKitTrack + } else { + renderer.track = nil + renderer.isEnabled = false + } + } + } + + public override init(frame: CGRect) { + super.init(frame: frame) + backgroundColor = .clear + renderer.backgroundColor = .clear + renderer.layoutMode = .fit + renderer.mirrorMode = .off + renderer.translatesAutoresizingMaskIntoConstraints = false + addSubview(renderer) + NSLayoutConstraint.activate([ + renderer.leadingAnchor.constraint(equalTo: leadingAnchor), + renderer.trailingAnchor.constraint(equalTo: trailingAnchor), + renderer.topAnchor.constraint(equalTo: topAnchor), + renderer.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { fatalError("init(coder:) is unavailable") } + + private func reportCurrentDimensions() { + guard let dimensions = screenShare?.videoTrack?.dimensions else { return } + onVideoDimensionsChanged?(dimensions) + } +} + +private final class IOSGridVideoDimensionObserver: NSObject, TrackDelegate, @unchecked Sendable { + @MainActor var onDimensionsChanged: ((InlineRTCVideoDimensions) -> Void)? + + nonisolated func track(_ track: VideoTrack, didUpdateDimensions dimensions: Dimensions?) { + guard let dimensions else { return } + let value = InlineRTCVideoDimensions(width: Int(dimensions.width), height: Int(dimensions.height)) + Task { @MainActor [weak self] in self?.onDimensionsChanged?(value) } + } +} +#endif diff --git a/apple/InlineKit/Sources/InlineRTC/Engine/LiveKitGridAUHALAudioDriver.swift b/apple/InlineKit/Sources/InlineRTC/Engine/LiveKitGridAUHALAudioDriver.swift index ba39d1e4a..d6f87dc41 100644 --- a/apple/InlineKit/Sources/InlineRTC/Engine/LiveKitGridAUHALAudioDriver.swift +++ b/apple/InlineKit/Sources/InlineRTC/Engine/LiveKitGridAUHALAudioDriver.swift @@ -943,160 +943,6 @@ private enum LiveKitGridAUHALDriverError: LocalizedError { } } } -#else -actor LiveKitGridAUHALAudioDriver: GridAudioDriver { - nonisolated let events: AsyncStream - - private nonisolated let eventContinuation: AsyncStream.Continuation - private nonisolated let expectedEngineTransition = ManagedAtomic(false) - private var engineObserver: GridLiveKitAudioEngineObserver? - private var configured = false - private var prepared = false - - init() { - let stream = AsyncStream.makeStream( - of: GridAudioDriverEvent.self, - bufferingPolicy: .bufferingNewest(16) - ) - events = stream.stream - eventContinuation = stream.continuation - } - - deinit { eventContinuation.finish() } - - func configure(_ configuration: InlineRTCConfiguration) async throws { - guard !configured else { return } - try AudioManager.set(audioDeviceModuleType: .audioEngine) - AudioManager.prepare() - try configuration.applyVoiceProcessing() - let observer = GridLiveKitAudioEngineObserver( - eventContinuation: eventContinuation, - expectedEngineTransition: expectedEngineTransition - ) - AudioManager.shared.set(engineObservers: [observer, AudioManager.shared.mixer]) - engineObserver = observer - let eventContinuation = eventContinuation - AudioManager.shared.onDeviceUpdate = { _ in - eventContinuation.yield(.devicesChanged) - } - configured = true - } - - func setPrepared(_ prepared: Bool) async throws { - guard self.prepared != prepared else { return } - expectedEngineTransition.store(true, ordering: .releasing) - defer { expectedEngineTransition.store(false, ordering: .releasing) } - try await AudioManager.shared.setRecordingAlwaysPreparedMode(prepared) - self.prepared = prepared - } - - func recoverPreparedAudio(preserving _: AudioInputRouteTarget?) async throws { - guard prepared else { return } - expectedEngineTransition.store(true, ordering: .releasing) - defer { expectedEngineTransition.store(false, ordering: .releasing) } - try await AudioManager.shared.setRecordingAlwaysPreparedMode(false) - try await AudioManager.shared.setRecordingAlwaysPreparedMode(true) - } - - func isAudioEngineRunning() async -> Bool { AudioManager.shared.isEngineRunning } - - func runtimeHealth() async -> GridAudioRuntimeHealth { - let isRunning = AudioManager.shared.isEngineRunning - return GridAudioRuntimeHealth( - isEngineRunning: isRunning, - isRecording: isRunning, - isPlaying: isRunning, - route: nil - ) - } - - func applyInputRoute( - _ target: AudioInputRouteTarget, - restartPreparedAudio _: Bool - ) async throws -> GridAudioInputRouteApplication { - guard case .automatic = target else { - throw LiveKitGridAudioDriverError.explicitInputRoutingUnsupported - } - return .committed - } - - func inputDeviceInventory() async -> AudioInputDeviceInventory { - AudioInputDeviceInventory( - automaticDeviceID: nil, - automaticDeviceName: "System Default", - devices: [], - routeEpoch: 0 - ) - } -} - -private enum LiveKitGridAudioDriverError: LocalizedError { - case explicitInputRoutingUnsupported - - var errorDescription: String? { - "Explicit microphone selection is not supported on this platform." - } -} - -private final class GridLiveKitAudioEngineObserver: AudioEngineObserver, @unchecked Sendable { - var next: (any AudioEngineObserver)? - - private let eventContinuation: AsyncStream.Continuation - private let expectedEngineTransition: ManagedAtomic - - init( - eventContinuation: AsyncStream.Continuation, - expectedEngineTransition: ManagedAtomic - ) { - self.eventContinuation = eventContinuation - self.expectedEngineTransition = expectedEngineTransition - } - - func engineWillStart( - _ engine: AVAudioEngine, - isPlayoutEnabled: Bool, - isRecordingEnabled: Bool - ) -> Int { - eventContinuation.yield( - .engineStarting(playout: isPlayoutEnabled, recording: isRecordingEnabled) - ) - return next?.engineWillStart( - engine, - isPlayoutEnabled: isPlayoutEnabled, - isRecordingEnabled: isRecordingEnabled - ) ?? 0 - } - - func engineDidStop( - _ engine: AVAudioEngine, - isPlayoutEnabled: Bool, - isRecordingEnabled: Bool - ) -> Int { - let event: GridAudioDriverEvent = expectedEngineTransition.load(ordering: .acquiring) - ? .expectedEngineStop(playout: isPlayoutEnabled, recording: isRecordingEnabled) - : .engineStopped(playout: isPlayoutEnabled, recording: isRecordingEnabled) - eventContinuation.yield(event) - return next?.engineDidStop( - engine, - isPlayoutEnabled: isPlayoutEnabled, - isRecordingEnabled: isRecordingEnabled - ) ?? 0 - } - - func engineDidDisable( - _ engine: AVAudioEngine, - isPlayoutEnabled: Bool, - isRecordingEnabled: Bool - ) -> Int { - let event: GridAudioDriverEvent = expectedEngineTransition.load(ordering: .acquiring) - ? .expectedEngineDisable(playout: isPlayoutEnabled, recording: isRecordingEnabled) - : .engineDisabled(playout: isPlayoutEnabled, recording: isRecordingEnabled) - eventContinuation.yield(event) - return next?.engineDidDisable( - engine, - isPlayoutEnabled: isPlayoutEnabled, - isRecordingEnabled: isRecordingEnabled - ) ?? 0 - } -} +#elseif os(iOS) +typealias LiveKitGridAUHALAudioDriver = LiveKitGridIOSAudioDriver #endif diff --git a/apple/InlineKit/Sources/InlineRTC/Engine/LiveKitGridIOSAudioDriver.swift b/apple/InlineKit/Sources/InlineRTC/Engine/LiveKitGridIOSAudioDriver.swift new file mode 100644 index 000000000..b2632234f --- /dev/null +++ b/apple/InlineKit/Sources/InlineRTC/Engine/LiveKitGridIOSAudioDriver.swift @@ -0,0 +1,521 @@ +#if os(iOS) +import AVFoundation +import Atomics +import Foundation +import InlineAudioPlayback +import LiveKit + +/// iOS uses the SDK AudioEngine ADM and its supported session observer chain. +/// Capture availability is a physical fence: retired room callbacks cannot +/// reopen the microphone after product capture intent has been withdrawn. +actor LiveKitGridIOSAudioDriver: GridAudioDriver { + nonisolated let events: AsyncStream + private nonisolated let continuation: AsyncStream.Continuation + private nonisolated let expectedTransition = ManagedAtomic(false) + private let directions = IOSGridAudioDirections() + private let inputProbe = IOSGridAudioCallbackProbe() + private let outputProbe = IOSGridAudioCallbackProbe() + private var observer: IOSGridAudioEngineObserver? + private let systemObservers = IOSGridSystemObservers() + private var configured = false + private var prepared = false + private var safetyPaused = false + private var interruptionActive = false + private var requiresExplicitOutputResume = false + private var requiresProcessRestart = false + private var mediaDemandActive = false + /// Invalidates suspended warm preparation before any safety or admission + /// change can be undone by its completion. + private var lifecycleEpoch: UInt64 = 0 + private var mediaDemandEpoch: UInt64 = 0 + private var expectedTransitionCount = 0 + private var warmModeMayBeEnabled = false + private var audioProcessingOptions = AudioProcessingOptions() + private let bootstrapError: String? + private let backend: any IOSGridAudioBackend + private let hasNativeEngine: Bool + + init() { + backend = LiveKitIOSGridAudioBackend() + hasNativeEngine = true + // Select the process ADM before any Room can initialize its factory. + do { + try AudioManager.set(audioDeviceModuleType: .audioEngine) + bootstrapError = nil + } catch { + bootstrapError = error.localizedDescription + } + let stream = AsyncStream.makeStream(of: GridAudioDriverEvent.self, bufferingPolicy: .bufferingNewest(16)) + events = stream.stream + continuation = stream.continuation + } + + /// Exercises the real actor lifecycle against suspended provider operations + /// without initializing an ADM or touching the device audio session in tests. + init(testingBackend backend: any IOSGridAudioBackend) { + self.backend = backend + hasNativeEngine = false + bootstrapError = nil + configured = true + let stream = AsyncStream.makeStream(of: GridAudioDriverEvent.self, bufferingPolicy: .bufferingNewest(16)) + events = stream.stream + continuation = stream.continuation + } + + deinit { + continuation.finish() + } + + func configure(_ configuration: InlineRTCConfiguration) async throws { + guard !configured else { return } + if let bootstrapError { throw IOSGridAudioError.bootstrap(bootstrapError) } + // The process owner supplies category and activation through its native FIFO. + // Keep the SDK observer in the chain, without a second session writer. + AudioManager.shared.audioSession.isAutomaticConfigurationEnabled = false + AudioManager.prepare() + try configuration.applyVoiceProcessing() + audioProcessingOptions = configuration.makeAudioProcessingOptions() + let observer = IOSGridAudioEngineObserver( + continuation: continuation, + expectedTransition: expectedTransition, + directions: directions + ) + // Manual configuration retains the stock requirement/observer forwarding chain. + AudioManager.shared.set(engineObservers: [AudioManager.shared.audioSession, observer, AudioManager.shared.mixer]) + self.observer = observer + AudioManager.shared.add(localAudioRenderer: inputProbe) + AudioManager.shared.add(remoteAudioRenderer: outputProbe) + try backend.setEngineAvailability(mediaDemandActive && !safetyPaused ? Self.listenOnlyAvailability : .none) + installSystemObservers() + configured = true + } + + func resumeAfterTerminalShutdown() async { + let epoch = lifecycleEpoch + guard !(await backend.isQuarantined()) else { return } + guard epoch == lifecycleEpoch, mediaDemandActive, !safetyPaused, !interruptionActive else { return } + try? backend.setEngineAvailability(Self.listenOnlyAvailability) + } + + func setMediaDemandActive(_ active: Bool, epoch: UInt64) async throws { + guard epoch >= mediaDemandEpoch else { return } + if mediaDemandActive != active || epoch != mediaDemandEpoch { + lifecycleEpoch &+= 1 + mediaDemandEpoch = epoch + mediaDemandActive = active + if !active { + prepared = false + // Withdraw physical input/output before crossing to the session owner. + try backend.setEngineAvailability(.none) + } + } + do { + try await backend.setGridDemandActive(active, epoch: epoch) + } catch { + if active, epoch == mediaDemandEpoch { + mediaDemandActive = false + lifecycleEpoch &+= 1 + try? backend.setEngineAvailability(.none) + } + throw error + } + } + + func mediaDidQuiesce(epoch: UInt64) async { + guard epoch == mediaDemandEpoch, !mediaDemandActive else { return } + await backend.gridMediaDidQuiesce(epoch: epoch) + guard epoch == mediaDemandEpoch, !mediaDemandActive else { return } + if hasNativeEngine { _ = await runtimeHealth() } + } + + func setPrepared(_ value: Bool) async throws { + guard configured else { throw IOSGridAudioError.notConfigured } + if !value { + lifecycleEpoch &+= 1 + prepared = false + } + let epoch = lifecycleEpoch + guard !(await backend.isQuarantined()) else { + throw InlineAudioSessionError.quarantined + } + guard epoch == lifecycleEpoch else { throw IOSGridAudioError.superseded } + beginExpectedTransition() + defer { endExpectedTransition() } + if value { + guard !requiresProcessRestart else { throw InlineAudioSessionError.quarantined } + guard mediaDemandActive, !safetyPaused, !interruptionActive else { throw IOSGridAudioError.safetyPaused } + warmModeMayBeEnabled = true + do { + try await backend.setRecordingAlwaysPreparedMode(true, audioProcessingOptions: audioProcessingOptions) + } catch { + try? await stopWarmMode() + throw error + } + guard epoch == lifecycleEpoch, mediaDemandActive, !safetyPaused, !interruptionActive else { + // A cancelled SDK await may still have enabled its persistent warm + // demand. Retire that concrete obligation before releasing the writer. + try? await stopWarmMode() + throw IOSGridAudioError.superseded + } + try backend.setEngineAvailability(.default) + } else { + // Highest-priority ADM gate precedes warm-mode release. A late room or + // publication completion can express demand but cannot reopen input. + try backend.setEngineAvailability(mediaDemandActive && !safetyPaused && !interruptionActive ? Self.listenOnlyAvailability : .none) + try await stopWarmMode() + guard epoch == lifecycleEpoch else { throw IOSGridAudioError.superseded } + } + prepared = value + } + + func stopForShutdown() async -> GridAudioDriverShutdownReceipt { + lifecycleEpoch &+= 1 + let epoch = lifecycleEpoch + prepared = false + var failures: [String] = [] + beginExpectedTransition() + defer { endExpectedTransition() } + do { try backend.setEngineAvailability(.none) } + catch { failures.append("availability: \(error)") } + do { try await stopWarmMode() } + catch { failures.append("capture: \(error)") } + if epoch != lifecycleEpoch { failures.append("Audio shutdown was superseded by a new lifecycle transition.") } + let health = await runtimeHealth() + if await backend.isQuarantined() { + failures.append("An audio session requirement failed to release; process restart is required.") + } + return GridAudioDriverShutdownReceipt( + recordingStopped: !health.isRecording, + playoutStopped: !health.isPlaying, + failures: failures + ) + } + + func recoverPreparedAudio(preserving _: AudioInputRouteTarget?) async throws { + guard prepared, mediaDemandActive, !safetyPaused, !interruptionActive else { return } + try await setPrepared(false) + let epoch = lifecycleEpoch + guard mediaDemandActive, !safetyPaused, !interruptionActive else { return } + try await setPrepared(true) + guard epoch == lifecycleEpoch else { throw IOSGridAudioError.superseded } + } + + func resumeListeningAfterSafetyPause() async throws { + guard mediaDemandActive, safetyPaused else { return } + guard !requiresProcessRestart else { throw InlineAudioSessionError.quarantined } + guard !interruptionActive else { throw IOSGridAudioError.interrupted } + try await resumeListening() + } + + func isAudioEngineRunning() async -> Bool { AudioManager.shared.isEngineRunning } + + func runtimeHealth() async -> GridAudioRuntimeHealth { + let enabled = directions.snapshot() + let running = AudioManager.shared.isEngineRunning + let recording = running && enabled.recording + let playing = running && enabled.playing + let input = inputProbe.snapshot() + let output = outputProbe.snapshot() + let route = AVAudioSession.sharedInstance().currentRoute + await InlineAudioSession.shared.observeGridDirections( + recording: recording, + playing: playing, + nativeOperationsQuiescent: expectedTransitionCount == 0 && !warmModeMayBeEnabled, + epoch: mediaDemandEpoch + ) + return GridAudioRuntimeHealth( + isEngineRunning: running, + isRecording: recording, + isRecordingExpected: prepared && !safetyPaused, + isPlaying: playing, + route: InlineRTCAudioRoute( + currentInputID: route.inputs.first?.uid, + defaultInputID: route.inputs.first?.uid, + currentOutputID: route.outputs.first?.uid, + defaultOutputID: route.outputs.first?.uid, + inputDeviceCount: route.inputs.count, + outputDeviceCount: route.outputs.count, + isInputRouteValid: !recording || (!safetyPaused && !route.inputs.isEmpty && input.isFresh), + isOutputRouteValid: !playing || (!safetyPaused && !route.outputs.isEmpty && output.isFresh), + inputCallbackCount: input.count, + outputCallbackCount: output.count, + inputCallbackAgeMilliseconds: input.ageMilliseconds, + outputCallbackAgeMilliseconds: output.ageMilliseconds + ) + ) + } + + func applyInputRoute(_ target: AudioInputRouteTarget, restartPreparedAudio _: Bool) async throws -> GridAudioInputRouteApplication { + guard case .automatic = target else { throw IOSGridAudioError.explicitRoute } + return .committed + } + + func inputDeviceInventory() async -> AudioInputDeviceInventory { + AudioInputDeviceInventory(automaticDeviceID: nil, automaticDeviceName: "Automatic", devices: [], routeEpoch: 0) + } + + private static let listenOnlyAvailability = AudioEngineAvailability(isInputAvailable: false, isOutputAvailable: true) + + private func beginExpectedTransition() { + expectedTransitionCount += 1 + expectedTransition.store(true, ordering: .releasing) + } + + private func endExpectedTransition() { + expectedTransitionCount -= 1 + expectedTransition.store(expectedTransitionCount > 0, ordering: .releasing) + if expectedTransitionCount == 0, hasNativeEngine { + Task { _ = await self.runtimeHealth() } + } + } + + private func stopWarmMode() async throws { + try await backend.setRecordingAlwaysPreparedMode(false) + warmModeMayBeEnabled = false + } + + private func installSystemObservers() { + let center = NotificationCenter.default + systemObservers.tokens.append(center.addObserver(forName: AVAudioSession.routeChangeNotification, object: nil, queue: nil) { [weak self, continuation] notification in + let rawReason = notification.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt + if rawReason == AVAudioSession.RouteChangeReason.oldDeviceUnavailable.rawValue { + Task { await self?.pauseForSafety(requiresExplicitOutputResume: true) } + } else { + continuation.yield(.devicesChanged) + } + }) + if #available(iOS 27.0, *) { + systemObservers.tokens.append(center.addObserver(forName: AVAudioSession.didBecomeInactiveNotification, object: nil, queue: nil) { [weak self] notification in + guard let context = notification.userInfo?[AVAudioSession.deactivationContextKey] as? AVAudioSession.DeactivationContext, + context.source == .system else { return } + Task { await self?.interruptionBegan() } + }) + systemObservers.tokens.append(center.addObserver(forName: AVAudioSession.resumptionRecommendationNotification, object: nil, queue: nil) { [weak self] notification in + guard let context = notification.userInfo?[AVAudioSession.resumptionContextKey] as? AVAudioSession.ResumptionContext else { return } + let shouldResume = context.recommendation == .shouldResume + Task { await self?.interruptionEnded(shouldResume: shouldResume) } + }) + } else { + systemObservers.tokens.append(center.addObserver(forName: AVAudioSession.interruptionNotification, object: nil, queue: nil) { [weak self] notification in + guard let rawType = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt, + let type = AVAudioSession.InterruptionType(rawValue: rawType) else { return } + let rawOptions = notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? UInt ?? 0 + let shouldResume = AVAudioSession.InterruptionOptions(rawValue: rawOptions).contains(.shouldResume) + Task { + if type == .began { await self?.interruptionBegan() } + else if type == .ended { await self?.interruptionEnded(shouldResume: shouldResume) } + } + }) + } + systemObservers.tokens.append(center.addObserver(forName: AVAudioSession.mediaServicesWereResetNotification, object: nil, queue: nil) { [weak self] _ in + Task { await self?.mediaServicesWereReset() } + }) + } + + func interruptionBegan() async { + interruptionActive = true + await pauseForSafety(requiresExplicitOutputResume: false) + } + + func mediaServicesWereReset() async { + // Fence retry synchronously, before a suspended SDK stop can delay the + // process-owner quarantine notification. + requiresProcessRestart = true + await pauseForSafety(requiresExplicitOutputResume: true, quarantiningReset: true) + } + + func interruptionEnded(shouldResume: Bool) async { + interruptionActive = false + guard shouldResume, !requiresExplicitOutputResume, mediaDemandActive, safetyPaused else { return } + try? await resumeListening() + } + + private func resumeListening() async throws { + let epoch = lifecycleEpoch + guard !requiresProcessRestart else { throw InlineAudioSessionError.quarantined } + guard mediaDemandActive, !interruptionActive else { return } + guard !(await backend.isQuarantined()) else { + throw InlineAudioSessionError.quarantined + } + guard epoch == lifecycleEpoch, mediaDemandActive, !interruptionActive else { return } + beginExpectedTransition() + defer { endExpectedTransition() } + // A resumed call always listens first. Neither the OS recommendation nor + // Retry Audio restores a microphone lease withdrawn by the safety pause. + try backend.setEngineAvailability(.none) + try await stopWarmMode() + guard epoch == lifecycleEpoch, mediaDemandActive, !interruptionActive else { return } + try await backend.resumeGridSession(epoch: mediaDemandEpoch) + guard epoch == lifecycleEpoch, mediaDemandActive, !interruptionActive else { return } + prepared = false + try backend.setEngineAvailability(Self.listenOnlyAvailability) + safetyPaused = false + requiresExplicitOutputResume = false + continuation.yield(.listeningResumed) + } + + func pauseForSafety(requiresExplicitOutputResume: Bool, quarantiningReset: Bool = false) async { + lifecycleEpoch &+= 1 + let epoch = lifecycleEpoch + safetyPaused = true + self.requiresExplicitOutputResume = self.requiresExplicitOutputResume || requiresExplicitOutputResume + prepared = false + beginExpectedTransition() + defer { endExpectedTransition() } + try? backend.setEngineAvailability(.none) + // Publish withdrawal before suspending, so a quick end notification cannot + // resume output before the product has received its mute/capture fence. + continuation.yield(.safetyPaused) + if quarantiningReset { await backend.quarantineAfterMediaServicesReset() } + try? await stopWarmMode() + guard epoch == lifecycleEpoch else { return } + } +} + +/// The supported SDK calls form one narrow suspension boundary. The driver, +/// rather than this adapter, owns admission and safety epochs. +protocol IOSGridAudioBackend: Sendable { + func setEngineAvailability(_ availability: AudioEngineAvailability) throws + func setRecordingAlwaysPreparedMode(_ value: Bool, audioProcessingOptions: AudioProcessingOptions?) async throws + func isQuarantined() async -> Bool + func setGridDemandActive(_ active: Bool, epoch: UInt64) async throws + func gridMediaDidQuiesce(epoch: UInt64) async + func resumeGridSession(epoch: UInt64) async throws + func quarantineAfterMediaServicesReset() async +} + +extension IOSGridAudioBackend { + func setRecordingAlwaysPreparedMode(_ value: Bool) async throws { + try await setRecordingAlwaysPreparedMode(value, audioProcessingOptions: nil) + } +} + +private struct LiveKitIOSGridAudioBackend: IOSGridAudioBackend { + func setEngineAvailability(_ availability: AudioEngineAvailability) throws { + try AudioManager.shared.setEngineAvailability(availability) + } + + func setRecordingAlwaysPreparedMode(_ value: Bool, audioProcessingOptions: AudioProcessingOptions?) async throws { + try await AudioManager.shared.setRecordingAlwaysPreparedMode(value, audioProcessingOptions: audioProcessingOptions) + } + + func isQuarantined() async -> Bool { await InlineAudioSession.shared.isQuarantined } + + func setGridDemandActive(_ active: Bool, epoch: UInt64) async throws { + try await InlineAudioSession.shared.setGridDemandActive(active, epoch: epoch) + } + + func gridMediaDidQuiesce(epoch: UInt64) async { + await InlineAudioSession.shared.gridMediaDidQuiesce(epoch: epoch) + } + + func resumeGridSession(epoch: UInt64) async throws { + try await InlineAudioSession.shared.resumeGridSession(epoch: epoch) + } + + func quarantineAfterMediaServicesReset() async { + await InlineAudioSession.shared.quarantineAfterMediaServicesReset() + } +} + +/// Registration is driver-actor confined; destruction may happen on any thread. +private final class IOSGridSystemObservers: @unchecked Sendable { + var tokens: [NSObjectProtocol] = [] + deinit { + for token in tokens { NotificationCenter.default.removeObserver(token) } + } +} + +private final class IOSGridAudioDirections: @unchecked Sendable { + private let lock = NSLock() + private var playing = false + private var recording = false + + func update(playing: Bool, recording: Bool) { + lock.lock() + self.playing = playing + self.recording = recording + lock.unlock() + } + + func snapshot() -> (playing: Bool, recording: Bool) { + lock.lock() + defer { lock.unlock() } + return (playing, recording) + } +} + +private final class IOSGridAudioEngineObserver: AudioEngineObserver, @unchecked Sendable { + var next: (any AudioEngineObserver)? + private let continuation: AsyncStream.Continuation + private let expectedTransition: ManagedAtomic + private let directions: IOSGridAudioDirections + + init(continuation: AsyncStream.Continuation, expectedTransition: ManagedAtomic, directions: IOSGridAudioDirections) { + self.continuation = continuation + self.expectedTransition = expectedTransition + self.directions = directions + } + + func engineWillStart(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int { + directions.update(playing: isPlayoutEnabled, recording: isRecordingEnabled) + continuation.yield(.engineStarting(playout: isPlayoutEnabled, recording: isRecordingEnabled)) + return next?.engineWillStart(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0 + } + + func engineDidStop(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int { + continuation.yield(expectedTransition.load(ordering: .acquiring) + ? .expectedEngineStop(playout: isPlayoutEnabled, recording: isRecordingEnabled) + : .engineStopped(playout: isPlayoutEnabled, recording: isRecordingEnabled)) + return next?.engineDidStop(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0 + } + + func engineDidDisable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int { + directions.update(playing: isPlayoutEnabled, recording: isRecordingEnabled) + continuation.yield(expectedTransition.load(ordering: .acquiring) + ? .expectedEngineDisable(playout: isPlayoutEnabled, recording: isRecordingEnabled) + : .engineDisabled(playout: isPlayoutEnabled, recording: isRecordingEnabled)) + return next?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0 + } +} + +private final class IOSGridAudioCallbackProbe: NSObject, AudioRenderer, @unchecked Sendable { + struct Snapshot { + let count: UInt64 + let ageMilliseconds: UInt64? + var isFresh: Bool { ageMilliseconds.map { $0 < 1_000 } ?? false } + } + private let count = ManagedAtomic(0) + private let lastCallback = ManagedAtomic(0) + + func render(pcmBuffer _: AVAudioPCMBuffer) { + count.wrappingIncrement(ordering: .relaxed) + lastCallback.store(DispatchTime.now().uptimeNanoseconds, ordering: .relaxed) + } + + func snapshot() -> Snapshot { + let last = lastCallback.load(ordering: .relaxed) + let now = DispatchTime.now().uptimeNanoseconds + return Snapshot(count: count.load(ordering: .relaxed), ageMilliseconds: last > 0 && now >= last ? (now - last) / 1_000_000 : nil) + } +} + +private enum IOSGridAudioError: LocalizedError { + case bootstrap(String) + case notConfigured + case explicitRoute + case superseded + case safetyPaused + case interrupted + var errorDescription: String? { + switch self { + case let .bootstrap(message): "Audio could not initialize. \(message)" + case .notConfigured: "Audio is not configured." + case .explicitRoute: "Choose the audio route using the system audio control." + case .superseded: "Audio preparation was superseded by a safety or call change." + case .safetyPaused: "Resume audio before unmuting." + case .interrupted: "Audio is interrupted. Try again when the interruption ends." + } + } +} +#endif diff --git a/apple/InlineKit/Sources/InlineRTC/GridLiveKitConfiguration.swift b/apple/InlineKit/Sources/InlineRTC/GridLiveKitConfiguration.swift index bee275f57..74ccac2e7 100644 --- a/apple/InlineKit/Sources/InlineRTC/GridLiveKitConfiguration.swift +++ b/apple/InlineKit/Sources/InlineRTC/GridLiveKitConfiguration.swift @@ -150,7 +150,12 @@ struct InlineRTCConfiguration: Equatable, Sendable { public var highPassFilter = false /// Keep the high-pass filter provider-selected when it is enabled. public var highPassFilterMode: HighpassFilterMode = .automatic - public init() {} + public init() { + #if os(iOS) + echoCancellationMode = .platform + noiseSuppressionMode = .platform + #endif + } } public struct Publishing: Equatable, Sendable { @@ -198,7 +203,13 @@ struct InlineRTCConfiguration: Equatable, Sendable { /// How much non-Grid audio is reduced while voice processing is active. public var duckingLevel: DuckingLevel = .minimum - public init() {} + public init() { + #if os(iOS) + // Native voice processing and the system route work together on iPhone. + // The desktop AUHAL cold-start measurements do not apply to iOS. + platformVoiceProcessingAllowed = true + #endif + } public enum DuckingLevel: Equatable, Sendable { case systemDefault diff --git a/apple/InlineKit/Sources/RealtimeV2/Connection/ConnectionEvent.swift b/apple/InlineKit/Sources/RealtimeV2/Connection/ConnectionEvent.swift index 02d118240..425d0dbd2 100644 --- a/apple/InlineKit/Sources/RealtimeV2/Connection/ConnectionEvent.swift +++ b/apple/InlineKit/Sources/RealtimeV2/Connection/ConnectionEvent.swift @@ -4,6 +4,7 @@ public enum ConnectionEvent: Sendable { case connectNow case userInitiatedOperationStarted case userInitiatedOperationFinished + case mediaRetentionChanged(eligible: Bool, revision: UInt64) case authAvailable case authLost @@ -44,6 +45,7 @@ extension ConnectionEvent { case .connectNow: "connectNow" case .userInitiatedOperationStarted: "userInitiatedOperationStarted" case .userInitiatedOperationFinished: "userInitiatedOperationFinished" + case .mediaRetentionChanged: "mediaRetentionChanged" case .authAvailable: "authAvailable" case .authLost: "authLost" case .networkAvailable: "networkAvailable" diff --git a/apple/InlineKit/Sources/RealtimeV2/Connection/ConnectionManager.swift b/apple/InlineKit/Sources/RealtimeV2/Connection/ConnectionManager.swift index 75e5f31d7..6a743a3fc 100644 --- a/apple/InlineKit/Sources/RealtimeV2/Connection/ConnectionManager.swift +++ b/apple/InlineKit/Sources/RealtimeV2/Connection/ConnectionManager.swift @@ -71,6 +71,8 @@ actor ConnectionManager { private var probeNonce: UInt64? private var pendingPingNonce: UInt64? private var backgroundConnectionRetained = false + private var mediaConnectionRetained = false + private var mediaRetentionRevision: UInt64 = 0 // System-invoked work may run while UIKit is inactive. Keep lifecycle state truthful. private var userInitiatedOperations = 0 @@ -131,6 +133,10 @@ actor ConnectionManager { await enqueue(.userInitiatedOperationFinished) } + func setMediaRetention(eligible: Bool, revision: UInt64) async { + await enqueue(.mediaRetentionChanged(eligible: eligible, revision: revision)) + } + func setAuthAvailable(_ available: Bool) async { await enqueue(available ? .authAvailable : .authLost) } @@ -218,6 +224,7 @@ actor ConnectionManager { func shutdownForTesting() async { cancelAllTimers() backgroundConnectionRetained = false + mediaConnectionRetained = false pendingPingNonce = nil commandTask?.cancel() sessionTask?.cancel() @@ -244,7 +251,8 @@ actor ConnectionManager { case .userInitiatedOperationFinished: userInitiatedOperations = max(0, userInitiatedOperations - 1) - if userInitiatedOperations == 0, !constraints.appActive, !backgroundConnectionRetained { + if userInitiatedOperations == 0, !constraints.appActive, !backgroundConnectionRetained, + !mediaConnectionRetained { // Preserve auth loss, explicit stop, and network failure as the stronger constraints. if constraints.authAvailable, constraints.networkAvailable, constraints.userWantsConnection { await transition(to: .backgroundSuspended, reason: .backgroundSuspended) @@ -252,11 +260,25 @@ actor ConnectionManager { } } + case let .mediaRetentionChanged(eligible, revision): + guard revision > mediaRetentionRevision else { return } + mediaRetentionRevision = revision + mediaConnectionRetained = eligible && constraints.authAvailable && constraints.userWantsConnection + if !constraints.appActive, !backgroundConnectionRetained, !mediaConnectionRetained, + userInitiatedOperations == 0, constraints.authAvailable, + constraints.networkAvailable, constraints.userWantsConnection { + await transition(to: .backgroundSuspended, reason: .backgroundSuspended) + await stopTransportAndReset() + } else { + await evaluateConstraints(resetBackoff: false) + } + case .start: constraints.userWantsConnection = true await evaluateConstraints(resetBackoff: false) case .stop: + mediaConnectionRetained = false constraints.userWantsConnection = false await transition(to: .stopped, reason: .userStop) await stopTransportAndReset() @@ -275,6 +297,7 @@ actor ConnectionManager { await evaluateConstraints(resetBackoff: true) case .authLost: + mediaConnectionRetained = false constraints.authAvailable = false log.info("Realtime auth constraint applied available=0 state=\(state) session=\(sessionID)") await handleConstraintLoss(reason: .authLost) @@ -332,7 +355,7 @@ actor ConnectionManager { attempt = 0 backgroundConnectionRetained = false cancelBackoff() - if !transportWasRetained, userInitiatedOperations == 0, state == .open { + if !transportWasRetained, userInitiatedOperations == 0, !mediaConnectionRetained, state == .open { await forceReconnect(reason: .none) } else { await evaluateConstraints(resetBackoff: true) @@ -345,7 +368,7 @@ actor ConnectionManager { ) constraints.appActive = false backgroundConnectionRetained = keepConnection - if !keepConnection, userInitiatedOperations == 0 { + if !keepConnection, userInitiatedOperations == 0, !mediaConnectionRetained { await transition(to: .backgroundSuspended, reason: .backgroundSuspended) await stopTransportAndReset() } @@ -458,7 +481,7 @@ actor ConnectionManager { case .backgroundGraceExpired: guard !constraints.appActive else { return } backgroundConnectionRetained = false - guard userInitiatedOperations == 0 else { return } + guard userInitiatedOperations == 0, !mediaConnectionRetained else { return } await transition(to: .backgroundSuspended, reason: .backgroundSuspended) await stopTransportAndReset() @@ -724,7 +747,8 @@ actor ConnectionManager { } private func constraintsSatisfied() -> Bool { - let appActiveEffective = constraints.appActive || backgroundConnectionRetained || userInitiatedOperations > 0 + let appActiveEffective = constraints.appActive || backgroundConnectionRetained + || mediaConnectionRetained || userInitiatedOperations > 0 return constraints.authAvailable && constraints.networkAvailable && appActiveEffective && constraints.userWantsConnection } diff --git a/apple/InlineKit/Sources/RealtimeV2/Realtime/Realtime.swift b/apple/InlineKit/Sources/RealtimeV2/Realtime/Realtime.swift index 68026a2aa..8b8a14c67 100644 --- a/apple/InlineKit/Sources/RealtimeV2/Realtime/Realtime.swift +++ b/apple/InlineKit/Sources/RealtimeV2/Realtime/Realtime.swift @@ -152,6 +152,7 @@ public actor RealtimeV2 { private var transactionOwnerTransitionID: UUID? private var acceptsTransactions: Bool private var isPreparingForTermination = false + private var mediaRetentionRevision: UInt64 = 0 private var transactionRetryTask: Task? private var transactionOperationsInProgress = 0 private var transactionDrainWaiters: [CheckedContinuation] = [] @@ -301,6 +302,8 @@ public actor RealtimeV2 { /// Stop transport. But do not kill the listeners and tasks. This is state is recoverable via a transport start. public func loggedOut() async { log.info("Stopping realtime account generation") + mediaRetentionRevision &+= 1 + await connectionManager.setMediaRetention(eligible: false, revision: mediaRetentionRevision) acceptsTransactions = false authRecoveryTask?.cancel() authRecoveryTask = nil @@ -1600,6 +1603,18 @@ public actor RealtimeV2 { } } + /// Keeps the existing transport eligible while an admitted media owner actually plays/captures. + /// This neither changes UIKit lifecycle state nor grants background execution. + public func setGridMediaRetention( + eligible: Bool, + accountToken: AuthAccountMutationToken + ) async { + guard !isPreparingForTermination, + (try? auth.validateAccountMutation(accountToken)) != nil else { return } + mediaRetentionRevision &+= 1 + await connectionManager.setMediaRetention(eligible: eligible, revision: mediaRetentionRevision) + } + /// Bounded user-requested work (for example system automation) on the existing account owner. /// This does not claim the app is foreground, create a second connection, or enqueue offline work. public func withUserInitiatedConnection( diff --git a/apple/InlineKit/Tests/InlineAudioPlaybackTests/InlineAudioSessionGridTests.swift b/apple/InlineKit/Tests/InlineAudioPlaybackTests/InlineAudioSessionGridTests.swift new file mode 100644 index 000000000..6138e0dcd --- /dev/null +++ b/apple/InlineKit/Tests/InlineAudioPlaybackTests/InlineAudioSessionGridTests.swift @@ -0,0 +1,631 @@ +import Foundation +@testable import InlineAudioPlayback +import Testing + +@MainActor +@Suite("Grid process audio ownership", .timeLimit(.minutes(1))) +struct InlineAudioSessionGridTests { + @Test( + "Grid preflight and admission preserve an active recording, preview, or movie", + arguments: [InlineAudioSession.Owner.recording, .draftPreview, .video] + ) + func competingConsumer(owner: InlineAudioSession.Owner) async throws { + let backend = GridSessionTestBackend() + let session = InlineAudioSession(backend: backend) + defer { Task { await backend.drain() } } + let token = try session.acquire(owner) + + #expect(throws: (any Error).self) { try session.validateGridAdmission() } + await #expect(throws: (any Error).self) { + try await session.setGridDemandActive(true, epoch: 1) + } + #expect(session.owns(token)) + #expect(!session.isGridActive) + #expect(await backend.operations.isEmpty) + let release = try #require(session.release(token)) + try await expectOperation(.release(token), at: 0, backend: backend) + try await backend.completeNext() + try await finishOperation(release, backend: backend) + } + + @Test("Grid takeover waits for suspended voice activation and its native release") + func takeoverWaitsForOrdinaryAudio() async throws { + let backend = GridSessionTestBackend() + let session = InlineAudioSession(backend: backend) + defer { Task { await backend.drain() } } + let voice = try session.acquire(.voice) + session.willAcquire = { owner in + if owner == .call { + session.release(voice) + } + } + let voiceStart = try await beginOperation { + try await session.activate(voice, configuration: .voicePlayback) + } + try await expectOperation(.activate(voice), at: 0, backend: backend) + + let gridStart = try await beginOperation { + try await session.setGridDemandActive(true, epoch: 1) + } + let call = try #require(session.current) + #expect(call.owner == .call) + #expect(!session.owns(voice)) + #expect(await backend.operations == [.activate(voice)]) + + try await backend.completeNext() + try await expectOperation(.release(voice), at: 1, backend: backend) + #expect(await backend.operations == [.activate(voice), .release(voice)]) + try await backend.completeNext() + try await expectOperation(.activate(call), at: 2, backend: backend) + try await backend.completeNext() + try await expectOperation(.speaker(false, call), at: 3, backend: backend) + try await backend.completeNext() + try await finishOperation(gridStart, backend: backend) + await #expect(throws: CancellationError.self) { try await finishOperation(voiceStart, backend: backend) } + #expect(session.owns(call)) + #expect(session.release(voice) == nil) + try await retireGrid(session, backend: backend, epoch: 2) + } + + @Test("withdrawal cancels queued Grid activation before it can touch native audio") + func withdrawalBeforeActivation() async throws { + let backend = GridSessionTestBackend() + let session = InlineAudioSession(backend: backend) + defer { Task { await backend.drain() } } + let voice = try session.acquire(.voice) + session.willAcquire = { owner in + if owner == .call { + session.release(voice) + } + } + let voiceStart = try await beginOperation { + try await session.activate(voice, configuration: .voicePlayback) + } + try await expectOperation(.activate(voice), at: 0, backend: backend) + let gridStart = try await beginOperation { + try await session.setGridDemandActive(true, epoch: 1) + } + let call = try #require(session.current) + try await session.setGridDemandActive(false, epoch: 2) + await session.gridMediaDidQuiesce(epoch: 2) + await session.observeGridDirections( + recording: false, + playing: false, + nativeOperationsQuiescent: true, + epoch: 2 + ) + #expect(session.owns(call)) + + try await backend.completeNext() + try await expectOperation(.release(voice), at: 1, backend: backend) + try await backend.completeNext() + try await expectOperation(.release(call), at: 2, backend: backend) + try await backend.completeNext() + await #expect(throws: CancellationError.self) { try await finishOperation(voiceStart, backend: backend) } + await #expect(throws: CancellationError.self) { try await finishOperation(gridStart, backend: backend) } + #expect(session.current == nil) + #expect(await backend.operations == [.activate(voice), .release(voice), .release(call)]) + } + + @Test( + "generic release cannot bypass matching provider and native idle receipts", + arguments: [false, true] + ) + func callReleaseNeedsBothReceipts(providerFirst: Bool) async throws { + let backend = GridSessionTestBackend() + let session = InlineAudioSession(backend: backend) + defer { Task { await backend.drain() } } + let call = try await startGrid(session, backend: backend) + #expect(session.release(call) == nil) + try await session.setGridDemandActive(false, epoch: 2) + #expect(session.release(call) == nil) + let operationCount = await backend.operations.count + + if providerFirst { + await session.gridMediaDidQuiesce(epoch: 2) + await session.observeGridDirections( + recording: false, + playing: false, + nativeOperationsQuiescent: true, + epoch: 1 + ) + await session.observeGridDirections( + recording: false, + playing: false, + nativeOperationsQuiescent: false, + epoch: 2 + ) + await session.observeGridDirections( + recording: true, + playing: false, + nativeOperationsQuiescent: true, + epoch: 2 + ) + await session.observeGridDirections( + recording: false, + playing: true, + nativeOperationsQuiescent: true, + epoch: 2 + ) + } else { + await session.observeGridDirections( + recording: false, + playing: false, + nativeOperationsQuiescent: true, + epoch: 2 + ) + await session.gridMediaDidQuiesce(epoch: 1) + } + #expect(session.owns(call)) + #expect(await backend.operations.count == operationCount) + + let retirement = try await beginOperation { + if providerFirst { + await session.observeGridDirections( + recording: false, + playing: false, + nativeOperationsQuiescent: true, + epoch: 2 + ) + } else { + await session.gridMediaDidQuiesce(epoch: 2) + } + } + try await expectOperation(.release(call), at: operationCount, backend: backend) + #expect(session.owns(call)) + try await backend.completeNext() + try await finishOperation(retirement, backend: backend) + #expect(session.current == nil) + #expect(!session.isGridActive) + } + + @Test( + "a delayed or failed call teardown cannot admit an ordinary successor", + arguments: [false, true] + ) + func retirementBlocksSuccessor(releaseFails: Bool) async throws { + let backend = GridSessionTestBackend() + let session = InlineAudioSession(backend: backend) + defer { Task { await backend.drain() } } + let call = try await startGrid(session, backend: backend) + try await session.setGridDemandActive(false, epoch: 2) + await session.gridMediaDidQuiesce(epoch: 2) + let index = await backend.operations.count + let retirement = try await beginOperation { + await session.observeGridDirections( + recording: false, + playing: false, + nativeOperationsQuiescent: true, + epoch: 2 + ) + } + try await expectOperation(.release(call), at: index, backend: backend) + for owner in [InlineAudioSession.Owner.voice, .recording, .draftPreview, .video] { + #expect(throws: AudioPlaybackError.self) { try session.acquire(owner) } + } + #expect(session.owns(call)) + try await backend.completeNext(failing: releaseFails) + try await finishOperation(retirement, backend: backend) + + if releaseFails { + #expect(session.isQuarantined) + #expect(session.voiceDenial?.localizedDescription == InlineAudioSessionError.quarantined.localizedDescription) + #expect(session.owns(call)) + #expect(throws: InlineAudioSessionError.self) { try session.acquire(.voice) } + #expect(throws: InlineAudioSessionError.self) { try session.acquire(.recording) } + #expect(await backend.operations.count == index + 1) + } else { + #expect(!session.isQuarantined) + #expect(session.current == nil) + try await exerciseOrdinarySuccessor(session, backend: backend) + } + } + + @Test("failed activation and failed cleanup retain the call reservation") + func activationFailureWithFailedCleanup() async throws { + let backend = GridSessionTestBackend() + let session = InlineAudioSession(backend: backend) + defer { Task { await backend.drain() } } + let start = try await beginOperation { try await session.setGridDemandActive(true, epoch: 1) } + let call = try #require(session.current) + try await expectOperation(.activate(call), at: 0, backend: backend) + // A native activation error can include failed cleanup of partially owned + // hardware. It is not evidence that the call's obligation is already idle. + try await backend.completeNext(failing: true) + await #expect(throws: (any Error).self) { try await finishOperation(start, backend: backend) } + #expect(session.owns(call)) + #expect(!session.isQuarantined) + #expect(session.release(call) == nil) + #expect(throws: AudioPlaybackError.self) { try session.acquire(.voice) } + #expect(throws: AudioPlaybackError.self) { try session.acquire(.recording) } + #expect(await backend.operations == [.activate(call)]) + + try await session.setGridDemandActive(false, epoch: 2) + await session.gridMediaDidQuiesce(epoch: 2) + let cleanup = try await beginOperation { + await session.observeGridDirections( + recording: false, + playing: false, + nativeOperationsQuiescent: true, + epoch: 2 + ) + } + try await expectOperation(.release(call), at: 1, backend: backend) + try await backend.completeNext(failing: true) + try await finishOperation(cleanup, backend: backend) + #expect(session.isQuarantined) + #expect(session.owns(call)) + #expect(session.release(call) == nil) + #expect(session.voiceDenial?.localizedDescription == InlineAudioSessionError.quarantined.localizedDescription) + #expect(throws: InlineAudioSessionError.self) { try session.acquire(.voice) } + #expect(throws: InlineAudioSessionError.self) { try session.validateGridAdmission() } + #expect(await backend.operations == [.activate(call), .release(call)]) + } + + @Test("initial route failure requires complete activation and routing on same-epoch retry") + func routeFailureRetriesActivation() async throws { + let backend = GridSessionTestBackend() + let session = InlineAudioSession(backend: backend) + defer { Task { await backend.drain() } } + let firstStart = try await beginOperation { + try await session.setGridDemandActive(true, epoch: 1) + } + let call = try #require(session.current) + try await expectOperation(.activate(call), at: 0, backend: backend) + try await backend.completeNext() + try await expectOperation(.speaker(false, call), at: 1, backend: backend) + try await backend.completeNext(failing: true) + await #expect(throws: (any Error).self) { try await finishOperation(firstStart, backend: backend) } + #expect(session.owns(call)) + #expect(!session.isQuarantined) + + let retry = try await beginOperation { + try await session.setGridDemandActive(true, epoch: 1) + } + try await expectOperation(.activate(call), at: 2, backend: backend) + try await backend.completeNext() + try await expectOperation(.speaker(false, call), at: 3, backend: backend) + try await backend.completeNext() + try await finishOperation(retry, backend: backend) + #expect(session.owns(call)) + #expect(await backend.operations == [ + .activate(call), + .speaker(false, call), + .activate(call), + .speaker(false, call), + ]) + try await retireGrid(session, backend: backend, epoch: 2) + } + + @Test( + "voice and Grid reset notification order cannot revoke an unretired call", + arguments: [false, true] + ) + func resetRetainsCall(gridNotificationFirst: Bool) async throws { + let backend = GridSessionTestBackend() + let session = InlineAudioSession(backend: backend) + defer { Task { await backend.drain() } } + let call = try await startGrid(session, backend: backend) + let operationCount = await backend.operations.count + if gridNotificationFirst { + session.quarantineAfterMediaServicesReset() + session.invalidate() + } else { + session.invalidate() + session.quarantineAfterMediaServicesReset() + } + #expect(session.isQuarantined) + #expect(session.owns(call)) + #expect(session.release(call) == nil) + #expect(throws: InlineAudioSessionError.self) { try session.acquire(.voice) } + #expect(throws: InlineAudioSessionError.self) { try session.acquire(.recording) } + try await session.setGridDemandActive(false, epoch: 2) + await session.gridMediaDidQuiesce(epoch: 2) + await session.observeGridDirections( + recording: false, + playing: false, + nativeOperationsQuiescent: true, + epoch: 2 + ) + await #expect(throws: InlineAudioSessionError.self) { + try await session.resumeGridSession(epoch: 2) + } + #expect(session.owns(call)) + #expect(await backend.operations.count == operationCount) + } + + @Test( + "reset after Grid retirement preserves ordinary invalidation and ordered recovery", + arguments: [false, true] + ) + func resetAfterRetirement(gridNotificationFirst: Bool) async throws { + let backend = GridSessionTestBackend() + let session = InlineAudioSession(backend: backend) + defer { Task { await backend.drain() } } + _ = try await startGrid(session, backend: backend) + try await retireGrid(session, backend: backend, epoch: 2) + let voice = try session.acquire(.voice) + let index = await backend.operations.count + let voiceStart = try await beginOperation { + try await session.activate(voice, configuration: .voicePlayback) + } + try await expectOperation(.activate(voice), at: index, backend: backend) + if gridNotificationFirst { + session.quarantineAfterMediaServicesReset() + session.invalidate() + } else { + session.invalidate() + session.quarantineAfterMediaServicesReset() + } + #expect(!session.isQuarantined) + #expect(session.current == nil) + #expect(throws: InlineAudioSessionError.self) { try session.validateGridAdmission() } + let successor = try session.acquire(.video) + let successorStart = try await beginOperation { + try await session.activate(successor, configuration: .voicePlayback) + } + #expect(await backend.operations.count == index + 1) + + try await backend.completeNext() + try await expectOperation(.invalidate, at: index + 1, backend: backend) + #expect(session.owns(successor)) + try await backend.completeNext() + try await expectOperation(.activate(successor), at: index + 2, backend: backend) + try await backend.completeNext() + try await finishOperation(successorStart, backend: backend) + await #expect(throws: CancellationError.self) { try await finishOperation(voiceStart, backend: backend) } + #expect(session.owns(successor)) + let release = try #require(session.release(successor)) + try await expectOperation(.release(successor), at: index + 3, backend: backend) + try await backend.completeNext() + try await finishOperation(release, backend: backend) + } + + @Test( + "queued speaker and recovery work from a withdrawn epoch cannot reach a successor", + arguments: [QueuedGridOperation.speaker, .recovery] + ) + func staleGridOperation(operation: QueuedGridOperation) async throws { + let backend = GridSessionTestBackend() + let session = InlineAudioSession(backend: backend) + defer { Task { await backend.drain() } } + let call = try await startGrid(session, backend: backend) + let index = await backend.operations.count + let blockedSpeaker = try await beginOperation { try await session.setSpeakerPreferred(true) } + try await expectOperation(.speaker(true, call), at: index, backend: backend) + let stale = try await beginOperation { + switch operation { + case .speaker: try await session.setSpeakerPreferred(false) + case .recovery: try await session.resumeGridSession(epoch: 1) + } + } + let replacement = try await beginOperation { + try await session.setGridDemandActive(true, epoch: 2) + } + #expect(session.owns(call)) + #expect(await backend.operations.count == index + 1) + + try await backend.completeNext() + // After the controlled boundary, allow any unexpected I/O to finish and + // remain in the trace: a regression should fail an assertion, not hang. + await backend.drain() + await #expect(throws: CancellationError.self) { try await finishOperation(blockedSpeaker, backend: backend) } + await #expect(throws: CancellationError.self) { try await finishOperation(stale, backend: backend) } + try await finishOperation(replacement, backend: backend) + // Reapplying the admitted replacement must still reuse native activation. + // A stale recovery catch must not clear its active state after it completes. + try await session.setGridDemandActive(true, epoch: 2) + #expect(await backend.operations == [.activate(call), .speaker(false, call), .speaker(true, call)]) + #expect(!session.isSpeakerPreferred) + try await session.setGridDemandActive(false, epoch: 3) + await session.gridMediaDidQuiesce(epoch: 3) + await session.observeGridDirections( + recording: false, + playing: false, + nativeOperationsQuiescent: true, + epoch: 3 + ) + #expect(session.current == nil) + let successor = try session.acquire(.video) + try await session.activate(successor, configuration: .voicePlayback) + let finalOperationCount = await backend.operations.count + await #expect(throws: InlineAudioSessionError.self) { + try await session.resumeGridSession(epoch: 1) + } + await #expect(throws: InlineAudioSessionError.self) { + try await session.setSpeakerPreferred(true) + } + #expect(await backend.operations.count == finalOperationCount) + #expect(session.owns(successor)) + let release = try #require(session.release(successor)) + try await finishOperation(release, backend: backend) + } + + enum QueuedGridOperation: Sendable { case speaker, recovery } +} + +@MainActor +private func beginOperation( + _ operation: @escaping @MainActor () async throws -> Void +) async throws -> Task { + let started = AsyncStream.makeStream(of: Bool.self) + let task = Task { @MainActor in + started.continuation.yield(true) + started.continuation.finish() + try await operation() + } + var iterator = started.stream.makeAsyncIterator() + // Both tasks run on MainActor: observing this signal means operation reached + // its first suspension (or completed), rather than relying on Task.yield. + try #require(await iterator.next() == true) + return task +} + +@MainActor +private func startGrid( + _ session: InlineAudioSession, backend: GridSessionTestBackend +) async throws -> InlineAudioSession.Token { + let index = await backend.operations.count + let start = try await beginOperation { try await session.setGridDemandActive(true, epoch: 1) } + let token = try #require(session.current) + #expect(token.owner == .call) + try await expectOperation(.activate(token), at: index, backend: backend) + try await backend.completeNext() + try await expectOperation(.speaker(false, token), at: index + 1, backend: backend) + try await backend.completeNext() + try await finishOperation(start, backend: backend) + return token +} + +@MainActor +private func retireGrid( + _ session: InlineAudioSession, backend: GridSessionTestBackend, epoch: UInt64 +) async throws { + let token = try #require(session.current) + let index = await backend.operations.count + try await session.setGridDemandActive(false, epoch: epoch) + await session.gridMediaDidQuiesce(epoch: epoch) + let retirement = try await beginOperation { + await session.observeGridDirections( + recording: false, + playing: false, + nativeOperationsQuiescent: true, + epoch: epoch + ) + } + try await expectOperation(.release(token), at: index, backend: backend) + try await backend.completeNext() + try await finishOperation(retirement, backend: backend) + #expect(session.current == nil) +} + +@MainActor +private func exerciseOrdinarySuccessor( + _ session: InlineAudioSession, backend: GridSessionTestBackend +) async throws { + let token = try session.acquire(.video) + let index = await backend.operations.count + let start = try await beginOperation { + try await session.activate(token, configuration: .voicePlayback) + } + try await expectOperation(.activate(token), at: index, backend: backend) + try await backend.completeNext() + try await finishOperation(start, backend: backend) + #expect(session.owns(token)) + let release = try #require(session.release(token)) + try await expectOperation(.release(token), at: index + 1, backend: backend) + try await backend.completeNext() + try await finishOperation(release, backend: backend) +} + +@MainActor +private func finishOperation( + _ task: Task, backend: GridSessionTestBackend +) async throws { + try await withTaskCancellationHandler { + try await task.value + try Task.checkCancellation() + } onCancel: { + task.cancel() + Task { await backend.drain() } + } +} + +private func expectOperation( + _ expected: GridSessionTestBackend.Operation, at index: Int, backend: GridSessionTestBackend +) async throws { + let actual = await backend.operation(at: index) + try #require(actual == expected) +} + +/// Every native boundary is explicitly released by its test. The production +/// InlineAudioSession FIFO is exercised unchanged; no AVAudioSession writes occur. +private actor GridSessionTestBackend: InlineAudioSessionBackend { + enum Operation: Equatable, Sendable { + case activate(InlineAudioSession.Token) + case release(InlineAudioSession.Token) + case speaker(Bool, InlineAudioSession.Token) + case invalidate + case drained + } + + private enum Failure: Error { case missingOperation, nativeFailure } + private var pending: [CheckedContinuation] = [] + private var observers: [Int: [UUID: CheckedContinuation]] = [:] + private var isDraining = false + private(set) var operations: [Operation] = [] + + func activate(token: InlineAudioSession.Token, configuration _: InlineAudioSession.Configuration) async throws { + try await suspend(.activate(token)) + } + + func release(token: InlineAudioSession.Token) async throws { + try await suspend(.release(token)) + } + + func setSpeakerPreferred(_ preferred: Bool, token: InlineAudioSession.Token) async throws { + try await suspend(.speaker(preferred, token)) + } + + func invalidate() async { + try? await suspend(.invalidate) + } + + func operation(at index: Int) async -> Operation { + let id = UUID() + return await withTaskCancellationHandler { + await withCheckedContinuation { continuation in + if isDraining || Task.isCancelled { + continuation.resume(returning: .drained) + } else if index < operations.count { + continuation.resume(returning: operations[index]) + } else { + observers[index, default: [:]][id] = continuation + } + } + } onCancel: { + Task { await self.cancelObservation(at: index, id: id) } + } + } + + func completeNext(failing: Bool = false) throws { + guard !pending.isEmpty else { throw Failure.missingOperation } + let continuation = pending.removeFirst() + if failing { + continuation.resume(throwing: Failure.nativeFailure) + } else { + continuation.resume() + } + } + + func drain() { + isDraining = true + let continuations = pending + pending.removeAll() + for continuation in continuations { + continuation.resume() + } + let waiting = observers.values.flatMap { Array($0.values) } + observers.removeAll() + for observer in waiting { + observer.resume(returning: .drained) + } + } + + private func suspend(_ operation: Operation) async throws { + let index = operations.count + operations.append(operation) + for observer in (observers.removeValue(forKey: index) ?? [:]).values { + observer.resume(returning: operation) + } + guard !isDraining else { return } + try await withCheckedThrowingContinuation { pending.append($0) } + } + + private func cancelObservation(at index: Int, id: UUID) { + observers[index]?.removeValue(forKey: id)?.resume(returning: .drained) + if observers[index]?.isEmpty == true { + observers[index] = nil + } + } +} diff --git a/apple/InlineKit/Tests/InlineGridTests/GridAuthAuthorityRetirementTests.swift b/apple/InlineKit/Tests/InlineGridTests/GridAuthAuthorityRetirementTests.swift new file mode 100644 index 000000000..2064e965d --- /dev/null +++ b/apple/InlineKit/Tests/InlineGridTests/GridAuthAuthorityRetirementTests.swift @@ -0,0 +1,284 @@ +import Foundation +import InlineProtocol +import Testing + +@testable import Auth +@testable import InlineGrid +@testable import InlineRTC + +@Suite("Grid auth authority retirement", .serialized, .timeLimit(.minutes(1))) +@MainActor +struct GridAuthAuthorityRetirementTests { + enum ReplacementKind: CaseIterable, Sendable, Equatable { + case explicitNative, implicitNative, changedBearer + } + + @Test("same-account replacement waits for physical retirement before writing or publishing", + arguments: ReplacementKind.allCases) + func replacementWaitsForPhysicalRetirement(kind: ReplacementKind) async throws { + let fixture = GridAuthorityTestFixture() + do { + if kind == .changedBearer { + try await fixture.store.saveCredentials(token: "1:original", userId: 1) + } else { + try await fixture.seedAccount() + } + let oldAccount = try fixture.auth.beginAccountMutation() + try await fixture.admit() + fixture.service.toggleMicrophone(spaceID: 42) + try await eventuallyGridAuthority { + let recording = await fixture.audio.runtimeHealth().isRecording + let muted = await fixture.rtc.microphoneMuted + return recording && !muted + } + await fixture.audio.blockShutdown() + let previousWrites = fixture.writes.value + let attempt = kind == .explicitNative + ? try await fixture.auth.beginLoginAttempt(allowAuthenticated: true) : nil + let replacement = Task { + if kind == .changedBearer { + try await fixture.store.saveCredentials(token: "1:replacement", userId: 1) + } else { + try await fixture.auth.saveInlineProtocolCredentials( + fixture.credentials(sessionID: 11), loginAttempt: attempt + ) + } + if let attempt { return try await fixture.auth.finalizeCredentialsCommittedByLoginAttempt(attempt) } + return try fixture.auth.beginAccountMutation() + } + try await eventuallyGridAuthority { await fixture.audio.shutdownCalls > 0 } + #expect(!fixture.service.hasLocalAdmission) + #expect(fixture.service.mediaSessionIdentity == nil) + #expect(fixture.writes.value == previousWrites) + if kind == .changedBearer { + #expect(fixture.auth.token() == "1:original") + } else { + #expect(fixture.auth.inlineProtocolCredentials()?.accountSessionId == 10) + } + #expect(fixture.auth.hasPendingAccountTransition()) + await fixture.audio.releaseShutdown(succeeded: true) + let newAccount = try await replacement.value + #expect(newAccount.userID == oldAccount.userID) + #expect((newAccount != oldAccount) == (kind == .explicitNative)) + #expect(fixture.writes.value == previousWrites + 1) + if kind == .changedBearer { + #expect(fixture.auth.token() == "1:replacement") + } else { + #expect(fixture.auth.inlineProtocolCredentials()?.accountSessionId == 11) + } + #expect(!(await fixture.audio.runtimeHealth().isRecording)) + #expect(!fixture.service.hasLocalAdmission) + if kind == .explicitNative { + #expect((try? fixture.auth.validateAccountMutation(oldAccount)) == nil) + } else { + try fixture.auth.validateAccountMutation(oldAccount) + } + } catch { + await fixture.close() + throw error + } + await fixture.close() + } + + @Test("a failed local retirement rejects replacement and preserves prior authority without media readmission", + arguments: ReplacementKind.allCases) + func failedRetirementDoesNotWriteAuthority(kind: ReplacementKind) async throws { + let fixture = GridAuthorityTestFixture() + do { + if kind == .changedBearer { + try await fixture.store.saveCredentials(token: "1:original", userId: 1) + } else { + try await fixture.seedAccount() + } + let oldAccount = try fixture.auth.beginAccountMutation() + try await fixture.admit() + await fixture.audio.blockShutdown() + let previousWrites = fixture.writes.value + let attempt = kind == .explicitNative + ? try await fixture.auth.beginLoginAttempt(allowAuthenticated: true) : nil + let replacement = Task { + if kind == .changedBearer { + try await fixture.store.saveCredentials(token: "1:replacement", userId: 1) + } else { + try await fixture.auth.saveInlineProtocolCredentials( + fixture.credentials(sessionID: 11), loginAttempt: attempt + ) + } + } + try await eventuallyGridAuthority { await fixture.audio.shutdownCalls > 0 } + await fixture.audio.releaseShutdown(succeeded: false) + await #expect(throws: AuthStorageError.loginUnavailable) { try await replacement.value } + #expect(fixture.writes.value == previousWrites) + if kind == .changedBearer { + #expect(fixture.auth.token() == "1:original") + } else { + #expect(fixture.auth.inlineProtocolCredentials()?.accountSessionId == 10) + } + #expect(!fixture.auth.hasPendingAccountTransition()) + try fixture.auth.validateAccountMutation(oldAccount) + #expect(!fixture.service.hasLocalAdmission) + #expect(fixture.service.mediaSessionIdentity == nil) + } catch { + await fixture.close() + throw error + } + await fixture.close() + } + + enum UnchangedKind: CaseIterable, Sendable, Equatable { case temporaryKey, identicalBearer } + + @Test("temporary native renewal and identical implicit bearer keep the admitted call uninterrupted", + arguments: UnchangedKind.allCases) + func unchangedAuthorityDoesNotRetire(kind: UnchangedKind) async throws { + let fixture = GridAuthorityTestFixture() + do { + if kind == .identicalBearer { + try await fixture.store.saveCredentials(token: "1:original", userId: 1) + } else { + try await fixture.seedAccount() + } + let account = try fixture.auth.beginAccountMutation() + try await fixture.admit() + let identity = fixture.service.mediaSessionIdentity + let shutdownCalls = await fixture.audio.shutdownCalls + let oldWrites = fixture.writes.value + await fixture.audio.blockShutdown() + let renewal = Task { + if kind == .identicalBearer { + try await fixture.store.saveCredentials(token: "1:original", userId: 1) + } else { + var credentials = try fixture.credentials(sessionID: 10) + let key = Array((UInt8.min...UInt8.max).reversed()) + credentials.temporary = try InlineProtocolAuthorization( + key: key, keyID: InlineSecureTransport.authKeyID(key), serverSalt: 8, + temporary: true, expiresAt: Int64(Date().addingTimeInterval(3600).timeIntervalSince1970) + ) + try await fixture.auth.saveInlineProtocolCredentials(credentials) + } + } + try await eventuallyGridAuthority { + kind == .temporaryKey + ? fixture.auth.inlineProtocolCredentials()?.temporary != nil + : fixture.writes.value == oldWrites + 1 + } + try await renewal.value + try fixture.auth.validateAccountMutation(account) + #expect(fixture.service.hasLocalAdmission) + #expect(fixture.service.mediaSessionIdentity == identity) + #expect(await fixture.audio.shutdownCalls == shutdownCalls) + } catch { + await fixture.close() + throw error + } + await fixture.close() + } + + @Test("replacement waits for prior retirement even after Leave cleared admission") + func replacementWaitsAfterAdmissionCleared() async throws { + let fixture = GridAuthorityTestFixture() + do { + try await fixture.seedAccount() + try await fixture.admit() + await fixture.audio.blockShutdown() + let leaving = fixture.service.withdrawLocalAdmission() + #expect(!fixture.service.hasLocalAdmission) + let previousWrites = fixture.writes.value + let attempt = try await fixture.auth.beginLoginAttempt(allowAuthenticated: true) + let replacement = Task { + try await fixture.auth.saveInlineProtocolCredentials( + fixture.credentials(sessionID: 11), loginAttempt: attempt + ) + return try await fixture.auth.finalizeCredentialsCommittedByLoginAttempt(attempt) + } + try await eventuallyGridAuthority { + await fixture.audio.shutdownCalls > 0 && fixture.auth.hasPendingAccountTransition() + } + #expect(fixture.writes.value == previousWrites) + await fixture.audio.releaseShutdown(succeeded: true) + #expect(await leaving.value.isLocallyQuiescent) + _ = try await replacement.value + #expect(fixture.writes.value == previousWrites + 1) + #expect(!fixture.service.hasLocalAdmission) + } catch { + await fixture.close() + throw error + } + await fixture.close() + } + + @Test("replaying a completed authority request cannot retire a successor admission") + func staleAuthorityRequestCannotRetireSuccessor() async throws { + let fixture = GridAuthorityTestFixture() + var request: AuthAccountAuthorityRetirement? + let observation = fixture.auth.observeAccountAuthorityWillChange { request = $0 } + defer { NotificationCenter.default.removeObserver(observation) } + do { + try await fixture.seedAccount() + try await fixture.admit() + let attempt = try await fixture.auth.beginLoginAttempt(allowAuthenticated: true) + try await fixture.auth.saveInlineProtocolCredentials(fixture.credentials(sessionID: 11), loginAttempt: attempt) + _ = try await fixture.auth.finalizeCredentialsCommittedByLoginAttempt(attempt) + let oldRequest = try #require(request) + #expect(!oldRequest.isCurrent) + try await fixture.admit(membershipID: "membership-b", intentRevision: 1) + let connectedIdentity = fixture.service.mediaSessionIdentity + let shutdownCalls = await fixture.audio.shutdownCalls + NotificationCenter.default.post( + name: .authAccountAuthorityWillChange, object: fixture.cache, + userInfo: ["retirement": oldRequest] + ) + #expect(fixture.service.hasLocalAdmission) + #expect(fixture.service.mediaSessionIdentity == connectedIdentity) + #expect(fixture.service.currentCall?.membershipID == "membership-b") + #expect(await fixture.audio.shutdownCalls == shutdownCalls) + } catch { + await fixture.close() + throw error + } + await fixture.close() + } + + @Test("receipt submission precedes successor demand while engine startup is suspended") + func receiptDoesNotEnqueueBehindSuccessor() async throws { + let audio = GridAuthorityAudioDriver(configurationBlocked: true) + let rtc = GridAuthorityRTCDriver() + let engine = InlineRTCSession( + audioDriver: audio, permissionDriver: GridAuthorityPermission(), rtcDriver: rtc, + captureCooldown: .zero + ) + let oldRetirement = engine.requestShutdownReceipt() + let target = InlineRTCSessionID("grid-test:1:7:3") + engine.setDemand(InlineRTCDemand( + target: target, + credentials: .init( + target: target, serverURL: URL(string: "wss://grid.invalid")!, participantIdentity: "successor", + token: "fixture-only", expiresAt: Date().addingTimeInterval(120) + ), + microphoneEnabled: false, microphoneCapturePrepared: false + )) + try await eventuallyGridAuthority { await audio.configurationCalls > 0 } + await audio.allowConfiguration() + #expect(await oldRetirement.value.isLocallyQuiescent) + try await eventuallyGridAuthority { await rtc.connectedIdentities == ["successor"] } + let snapshots = await engine.subscribe() + var iterator = snapshots.makeAsyncIterator() + var connected = false + while let snapshot = await iterator.next() { + if snapshot.rtc.state == .connected(target) { connected = true; break } + } + #expect(connected) + #expect(await engine.shutdown().isLocallyQuiescent) + } + + @Test("unfinished and rejected mailbox shutdown commands return unproven receipts") + func unfinishedReceiptFailsClosed() async { + let mailbox = GridEngineCommandMailbox() + let queued = GridShutdownCompletion() + mailbox.enqueue(.shutdown(requestID: UUID(), completion: queued)) + mailbox.finish() + #expect(!(await queued.value().isLocallyQuiescent)) + let rejected = GridShutdownCompletion() + mailbox.enqueue(.shutdown(requestID: UUID(), completion: rejected)) + #expect(!(await rejected.value().isLocallyQuiescent)) + } +} diff --git a/apple/InlineKit/Tests/InlineGridTests/GridAuthorityTestSupport.swift b/apple/InlineKit/Tests/InlineGridTests/GridAuthorityTestSupport.swift new file mode 100644 index 000000000..bc8e36564 --- /dev/null +++ b/apple/InlineKit/Tests/InlineGridTests/GridAuthorityTestSupport.swift @@ -0,0 +1,446 @@ +@testable import Auth +import Foundation +@testable import InlineGrid +import InlineProtocol +@testable import InlineRTC +@testable import RealtimeV2 +import Testing + +/// The production product/command/auth boundary, with only native I/O and network replaced. +/// Control storage stays unavailable so no Home/GetGrid response can repair a failed fence. +@MainActor +final class GridAuthorityTestFixture { + private let namespace = UUID().uuidString + private let defaults: UserDefaults + let cache: AuthSnapshotCache + let writes = GridAuthorityWriteProbe() + let store: AuthStore + let auth: AuthHandle + let audio: GridAuthorityAudioDriver + let rtc = GridAuthorityRTCDriver() + let engine: InlineRTCSession + let realtime: RealtimeV2 + let service: GridRoomService + private let control: GridAuthorityMembershipControl? + + init(configurationBlocked: Bool = false, control: GridAuthorityMembershipControl? = nil) { + self.control = control + defaults = UserDefaults(suiteName: "grid-authority-test-\(namespace)")! + cache = AuthSnapshotCache(initial: AuthSnapshot(status: .hydrating, didHydrate: false)) + let writes = writes + store = AuthStore( + cache: cache, mocked: true, namespace: namespace, + credentialWriteInterleavingHook: { writes.record() } + ) + auth = AuthHandle(cache: cache, store: store) + audio = GridAuthorityAudioDriver(configurationBlocked: configurationBlocked) + engine = InlineRTCSession( + audioDriver: audio, permissionDriver: GridAuthorityPermission(), rtcDriver: rtc, + captureCooldown: .zero + ) + realtime = RealtimeV2( + transport: MockTransport(), auth: auth, + applyUpdates: GridAuthorityNoUpdates(), syncStorage: GridAuthoritySyncStorage(), + storageIsReady: { false } + ) + let send: GridRoomAPI.Send? = if let control { + { transaction, token in try await control.send(transaction, account: token) } + } else { + nil + } + service = GridRoomService( + realtime: realtime, userDefaults: defaults, engine: engine, auth: auth, + sendGridTransaction: send + ) + } + + func credentials(sessionID: Int64) throws -> InlineProtocolSessionCredentials { + let key = Array(UInt8.min ... UInt8.max) + return try InlineProtocolSessionCredentials( + userId: 1, accountSessionId: sessionID, + permanent: InlineProtocolAuthorization( + key: key, keyID: InlineSecureTransport.authKeyID(key), serverSalt: 7, + temporary: false, expiresAt: nil + ) + ) + } + + @discardableResult + func seedAccount() async throws -> AuthAccountMutationToken { + try await auth.saveInlineProtocolCredentials(credentials(sessionID: 10)) + return try auth.beginAccountMutation() + } + + func admit(membershipID: String = "membership-a", intentRevision: Int = 0) async throws { + let token = try auth.beginAccountMutation() + let response = claimResult(membershipID: membershipID) + let operation = GridMembershipOperation( + kind: .join(roomID: 7), spaceID: 42, expectedMembershipID: "", + intentRevision: intentRevision, accountToken: token, microphoneEnabled: false, + automaticMicrophoneChange: nil, accessRevision: 0, revision: intentRevision + 1, + startedAt: Date() + ) + await service.handle(.joined(operation, response)) + try await eventuallyGridAuthority { self.service.media.connectionState == .connected } + #expect(service.hasLocalAdmission) + } + + func claimResult(membershipID: String, revision: Int64 = 1) -> GridRoomMutationResult { + let call = GridCurrentCall.with { + $0.callID = "call-\(membershipID)" + $0.membershipID = membershipID + $0.spaceID = 42 + $0.roomID = 7 + $0.ownedByCurrentSession = true + } + let grid = InlineProtocol.Grid.with { + $0.spaceID = 42 + $0.enabled = true + $0.currentRoomID = 7 + $0.revision = revision + $0.rooms = [.with { + $0.id = 7 + $0.spaceID = 42 + $0.connection = .with { $0.roomID = 7 + $0.generation = 3 + } + $0.avatars = [ + .with { + $0.user = .with { $0.id = 1 } + $0.membershipID = membershipID + $0.ownedByCurrentSession = true + }, + .with { $0.user = .with { $0.id = 2 } + $0.membershipID = "remote" + }, + ] + }] + } + let credentials = GridConnectionCredentials.with { + $0.connection = grid.rooms[0].connection + $0.callID = call.callID + $0.membershipID = membershipID + $0.serverURL = "wss://grid.invalid" + $0.participantIdentity = "inline-grid-user-1-\(membershipID)" + $0.token = "fixture-only" + $0.expiresAt = Int64(Date().addingTimeInterval(120).timeIntervalSince1970) + } + return GridRoomMutationResult(grids: [grid], credentials: credentials, currentCall: call) + } + + func enableTransferForPublicActions() async throws { + guard let control, let call = service.currentCall else { throw GridRoomAPIError.invalidResponse } + control.setupHome = .with { + $0.spaces = [.with { $0.spaceID = 42 + $0.activeAvatarCount = 2 + }] + $0.currentCall = call + $0.callTransferEnabled = true + } + await service.handle(GridRoomLifecycleEvent.realtimeConnection(.connected)) + control.setupHome = nil + #expect(service.callTransferEnabled) + } + + func close() async { + control?.failAll() + await audio.allowConfiguration() + await audio.releaseShutdown(succeeded: true) + await service.prepareForLogout() + await realtime.loggedOut() + AuthKeychainConfig.mockDelete("token", namespace: namespace) + AuthKeychainConfig.mockDelete("credentials_v2", namespace: namespace) + AuthKeychainConfig.mockDelete("inline_protocol_credentials_v1", namespace: namespace) + let prefix = AuthKeychainConfig.userDefaultsPrefix(mocked: true, namespace: namespace) + for suffix in ["userId", "logoutPending", "logoutAttemptID", "loginCommitPendingAttemptID"] { + UserDefaults.standard.removeObject(forKey: "\(prefix)\(suffix)") + } + } +} + +/// The typed API's actual transaction send boundary. It deliberately ignores task cancellation +/// while a reply is held, reproducing a provider completion after its worker was invalidated. +@MainActor +final class GridAuthorityMembershipControl { + struct Submission { + let method: InlineProtocol.Method + let account: AuthAccountMutationToken + } + + var setupHome: GetGridHomeResult? + private(set) var submissions: [Submission] = [] + private(set) var completed = Set() + private var pending: [Int: CheckedContinuation] = [:] + + func send(_ transaction: any Transaction2, account: AuthAccountMutationToken) async throws -> RpcResult + .OneOf_Result? + { + switch transaction.method { + case .getGridHome: + guard let setupHome else { throw GridRoomAPIError.invalidResponse } + return .getGridHome(setupHome) + case .createGridRoom, .joinGridRoom, .leaveGridRoom, .moveGridCallHere: + let index = submissions.count + submissions.append(Submission(method: transaction.method, account: account)) + defer { completed.insert(index) } + return try await withCheckedThrowingContinuation { pending[index] = $0 } + default: + // GetGrid/Home repair remains unavailable throughout the auth transition and late reply. + throw GridRoomAPIError.invalidResponse + } + } + + func succeed(_ index: Int, with response: GridRoomMutationResult) { + guard let continuation = pending.removeValue(forKey: index) else { return } + let result: RpcResult.OneOf_Result + switch submissions[index].method { + case .createGridRoom: + result = .createGridRoom(.with { + $0.grids = response.grids + if let call = response.currentCall { + $0.currentCall = call + } + if let credentials = response.credentials { + $0.connection = credentials + } + }) + case .joinGridRoom: + result = .joinGridRoom(.with { + $0.grids = response.grids + if let call = response.currentCall { + $0.currentCall = call + } + if let credentials = response.credentials { + $0.connection = credentials + } + }) + case .leaveGridRoom: + result = .leaveGridRoom(.with { + $0.grids = response.grids + if let call = response.currentCall { + $0.currentCall = call + } + }) + case .moveGridCallHere: + result = .moveGridCallHere(.with { + $0.grids = response.grids + $0.moved = response.moved + if let call = response.currentCall { + $0.currentCall = call + } + if let credentials = response.credentials { + $0.connection = credentials + } + }) + default: + continuation.resume(throwing: GridRoomAPIError.invalidResponse) + return + } + continuation.resume(returning: result) + } + + func fail(_ index: Int) { + pending.removeValue(forKey: index)?.resume(throwing: GridRoomAPIError.invalidResponse) + } + + func failAll() { + let continuations = Array(pending.values) + pending.removeAll() + continuations.forEach { $0.resume(throwing: CancellationError()) } + } +} + +@MainActor +func eventuallyGridAuthority(_ predicate: @escaping @MainActor () async -> Bool) async throws { + let deadline = ContinuousClock.now.advanced(by: .seconds(3)) + while await !predicate() { + guard ContinuousClock.now < deadline else { + throw GridAuthorityTestTimeout() + } + try await Task.sleep(for: .milliseconds(5)) + } +} + +private struct GridAuthorityTestTimeout: Error {} + +actor GridAuthorityAudioDriver: GridAudioDriver { + private var configurationBlocked: Bool + private var configurationWaiters: [CheckedContinuation] = [] + private var shutdownBlocked = false + private var shutdownSucceeds = true + private var shutdownWaiters: [CheckedContinuation] = [] + private(set) var shutdownCalls = 0 + private(set) var configurationCalls = 0 + private var recording = false + private var playing = false + + init(configurationBlocked: Bool) { + self.configurationBlocked = configurationBlocked + } + + func configure(_: InlineRTCConfiguration) async throws { + configurationCalls += 1 + if configurationBlocked { + await withCheckedContinuation { configurationWaiters.append($0) } + } + } + + func allowConfiguration() { + configurationBlocked = false + let waiters = configurationWaiters + configurationWaiters.removeAll() + waiters.forEach { $0.resume() } + } + + func setMediaDemandActive(_ active: Bool, epoch _: UInt64) async throws { + playing = active + } + + func setPrepared(_ prepared: Bool) async throws { + if prepared || !shutdownBlocked { + recording = prepared + } + } + + func blockShutdown() { + shutdownBlocked = true + } + + func releaseShutdown(succeeded: Bool) { + shutdownBlocked = false + shutdownSucceeds = succeeded + let waiters = shutdownWaiters + shutdownWaiters.removeAll() + waiters.forEach { $0.resume() } + } + + func stopForShutdown() async -> GridAudioDriverShutdownReceipt { + shutdownCalls += 1 + if shutdownBlocked { + await withCheckedContinuation { shutdownWaiters.append($0) } + } + if shutdownSucceeds { + recording = false + playing = false + } + return .init( + recordingStopped: shutdownSucceeds, playoutStopped: shutdownSucceeds, + failures: shutdownSucceeds ? [] : ["Fixture native teardown denied"] + ) + } + + func runtimeHealth() async -> GridAudioRuntimeHealth { + .init(isEngineRunning: true, isRecording: recording, isPlaying: playing, route: nil) + } + + func applyInputRoute( + _: AudioInputRouteTarget, + restartPreparedAudio _: Bool + ) async throws -> GridAudioInputRouteApplication { + .committed + } + + func inputDeviceInventory() async -> AudioInputDeviceInventory { + .init(automaticDeviceID: "fixture", automaticDeviceName: "Fixture", devices: [], routeEpoch: 0) + } +} + +actor GridAuthorityRTCDriver: GridRTCDriver { + private(set) var connectedIdentities: [String] = [] + private(set) var microphoneMuted = true + func makeRoom(configuration _: InlineRTCConfiguration) async throws -> GridRTCRoomHandle { + .init() + } + + func connect(_: GridRTCRoomHandle, credentials: InlineRTCCredentials) async throws { + connectedIdentities.append(credentials.participantIdentity) + } + + func publishPreparedMicrophone(_: GridRTCRoomHandle, initiallyMuted: Bool) async throws { + microphoneMuted = initiallyMuted + } + + func setMicrophoneMuted(_ muted: Bool, in _: GridRTCRoomHandle) async throws { + microphoneMuted = muted + } + + func screenCaptureSources() async throws -> [InlineRTCScreenCaptureSource] { + [] + } + + func setScreenShare( + _: InlineRTCScreenCaptureSource?, + qualityProfile _: InlineRTCScreenShareQualityProfile, + in _: GridRTCRoomHandle + ) async throws {} + func quiesceLocally(_ room: GridRTCRoomHandle) async -> GridLocalRoomQuiescenceReceipt { + microphoneMuted = true + return .init(room: room, localResourcesReleased: true, failures: []) + } +} + +struct GridAuthorityPermission: GridMicrophonePermissionDriver { + func status() async -> InlineRTCMicrophonePermission { + .authorized + } + + func request() async -> InlineRTCMicrophonePermission { + .authorized + } +} + +final class GridAuthorityWriteProbe: @unchecked Sendable { + private let lock = NSLock() + private var count = 0 + var value: Int { + lock.withLock { count } + } + + func record() { + lock.withLock { count += 1 } + } +} + +private struct GridAuthorityNoUpdates: ApplyUpdates { + func apply( + updates: [InlineProtocol.Update], + source _: UpdateApplySource, + sidecars _: InlineProtocol.UpdateSidecars? + ) async -> UpdateApplyResult { + .success(count: updates.count) + } +} + +private struct GridAuthoritySyncStorage: SyncStorage { + func getState() async throws -> SyncState { + .init(lastSyncDate: 0) + } + + func setState(_: SyncState) async -> Bool { + true + } + + func getBucketState(for _: BucketKey) async throws -> BucketState { + .init(date: 0, seq: 0) + } + + func setBucketState(for _: BucketKey, state _: BucketState) async -> Bool { + true + } + + func advanceBucketState(for _: BucketKey, state: BucketState) async -> BucketState? { + state + } + + func removeBucketState(for _: BucketKey) async -> Bool { + true + } + + func setBucketStates(states _: [BucketKey: BucketState]) async -> Bool { + true + } + + func clearSyncState() async -> Bool { + true + } +} diff --git a/apple/InlineKit/Tests/InlineGridTests/GridCallClaimAdmissionTests.swift b/apple/InlineKit/Tests/InlineGridTests/GridCallClaimAdmissionTests.swift new file mode 100644 index 000000000..826693420 --- /dev/null +++ b/apple/InlineKit/Tests/InlineGridTests/GridCallClaimAdmissionTests.swift @@ -0,0 +1,85 @@ +@testable import InlineGrid +import InlineProtocol +import Testing + +@Suite("Grid committed call admission") +struct GridCallClaimAdmissionTests { + private func call(membershipID: String = "membership-a", owned: Bool = true) -> GridCurrentCall { + .with { + $0.callID = "stable-call" + $0.membershipID = membershipID + $0.spaceID = 42 + $0.roomID = 7 + $0.ownedByCurrentSession = owned + } + } + + private func authority(membershipID: String = "membership-a") -> GridOwnedRoomAuthority { + .init(spaceID: 42, roomID: 7, membershipID: membershipID) + } + + private func grid(membershipID: String = "membership-a", owned: Bool = true) -> InlineProtocol.Grid { + .with { + $0.spaceID = 42 + $0.enabled = true + $0.rooms = [.with { + $0.id = 7 + $0.avatars = [.with { + $0.membershipID = membershipID + $0.ownedByCurrentSession = owned + }] + }] + } + } + + @Test("the claim survives its own changed-event repair when exact membership agrees") + func ownChangedEventDoesNotLoseClaim() { + let claim = call() + #expect(GridCallClaimAdmission.accepts( + committedCall: claim, newestGrid: grid(), acceptedAuthority: authority() + )) + } + + @Test("an A to B to A move cannot admit the first A response") + func oldOwnerIncarnationCannotRevive() { + #expect(!GridCallClaimAdmission.accepts( + committedCall: call(membershipID: "a-first"), + newestGrid: grid(membershipID: "a-second"), + acceptedAuthority: authority(membershipID: "a-second") + )) + } + + @Test("a later raw Leave prevents an old claim from admitting even if UI still shows its avatar") + func laterLeaveWins() { + #expect(!GridCallClaimAdmission.accepts( + committedCall: call(), newestGrid: grid(), acceptedAuthority: nil + )) + } + + @Test("a claimed owner loses admission if the newest Grid shows takeover") + func newerGridOwnershipWinsWithoutSelfDelivery() { + #expect(!GridCallClaimAdmission.accepts( + committedCall: call(), newestGrid: grid(owned: false), acceptedAuthority: nil + )) + } + + @Test("missing Grid or disabled space cannot grant media access") + func missingAuthorizationFailsClosed() { + #expect(!GridCallClaimAdmission.accepts( + committedCall: call(), newestGrid: nil, acceptedAuthority: nil + )) + var disabled = grid() + disabled.enabled = false + #expect(!GridCallClaimAdmission.accepts( + committedCall: call(), newestGrid: disabled, acceptedAuthority: authority() + )) + } + + @Test("an optimistic owned avatar cannot override a raw takeover snapshot") + func optimisticAvatarCannotGrantAuthority() { + #expect(!GridCallClaimAdmission.accepts( + committedCall: call(), newestGrid: grid(), + acceptedAuthority: authority(membershipID: "newer-owner-membership") + )) + } +} diff --git a/apple/InlineKit/Tests/InlineGridTests/GridCredentialBindingTests.swift b/apple/InlineKit/Tests/InlineGridTests/GridCredentialBindingTests.swift new file mode 100644 index 000000000..32d69da58 --- /dev/null +++ b/apple/InlineKit/Tests/InlineGridTests/GridCredentialBindingTests.swift @@ -0,0 +1,80 @@ +@testable import InlineGrid +import InlineProtocol +import Testing + +@Suite("Grid credential authority binding") +struct GridCredentialBindingTests { + private var target: GridMediaTarget { + .init(callID: "call-a", membershipID: "owner-a", spaceID: 42, roomID: 7, generation: 3) + } + + private func credentials( + callID: String = "call-a", + membershipID: String = "owner-a", + participantIdentity: String = "inline-grid-user-11-owner-a" + ) -> GridConnectionCredentials { + .with { + $0.connection = .with { $0.roomID = 7 + $0.generation = 3 + } + $0.callID = callID + $0.membershipID = membershipID + $0.participantIdentity = participantIdentity + } + } + + @Test("active transfer contract requires stable call and current owner together") + func strictContractBindsBothIdentities() { + #expect(GridCredentialBinding.accepts( + credentials: credentials(), target: target, userID: 11, callTransferEnabled: true + )) + #expect(!GridCredentialBinding.accepts( + credentials: credentials(callID: "previous-call"), target: target, userID: 11, callTransferEnabled: true + )) + #expect(!GridCredentialBinding.accepts( + credentials: credentials(membershipID: "previous-owner"), target: target, userID: 11, callTransferEnabled: true + )) + } + + @Test("old server's absent binding fields remain compatible only capability OFF") + func oldServerCompatibilityStaysOffOnly() { + let legacy = credentials(callID: "", membershipID: "") + #expect(GridCredentialBinding.accepts( + credentials: legacy, target: target, userID: 11, callTransferEnabled: false + )) + #expect(!GridCredentialBinding.accepts( + credentials: legacy, target: target, userID: 11, callTransferEnabled: true + )) + } + + @Test("legacy compatibility never accepts another account or owner incarnation") + func legacyIdentityStillHasExactAuthority() { + #expect(!GridCredentialBinding.accepts( + credentials: credentials(callID: "", membershipID: "", participantIdentity: "inline-grid-user-12-owner-a"), + target: target, userID: 11, callTransferEnabled: false + )) + #expect(!GridCredentialBinding.accepts( + credentials: credentials(callID: "", membershipID: "", participantIdentity: "inline-grid-user-11-previous-owner"), + target: target, userID: 11, callTransferEnabled: false + )) + } + + @Test("supplied binding fields are never ignored in OFF compatibility mode") + func nonemptyBindingsStayStrictWhenOff() { + #expect(!GridCredentialBinding.accepts( + credentials: credentials(callID: "previous-call"), target: target, userID: 11, callTransferEnabled: false + )) + #expect(!GridCredentialBinding.accepts( + credentials: credentials(membershipID: "previous-owner"), target: target, userID: 11, callTransferEnabled: false + )) + } + + @Test("same owner credentials cannot cross a room generation") + func generationIsAlsoRequired() { + var stale = credentials() + stale.connection.generation = 2 + #expect(!GridCredentialBinding.accepts( + credentials: stale, target: target, userID: 11, callTransferEnabled: false + )) + } +} diff --git a/apple/InlineKit/Tests/InlineGridTests/GridMembershipRevocationTests.swift b/apple/InlineKit/Tests/InlineGridTests/GridMembershipRevocationTests.swift new file mode 100644 index 000000000..e7b40220d --- /dev/null +++ b/apple/InlineKit/Tests/InlineGridTests/GridMembershipRevocationTests.swift @@ -0,0 +1,89 @@ +import Auth +import Foundation +import InlineProtocol +import Testing + +@testable import InlineGrid + +@Suite("Grid revoked membership responses", .serialized, .timeLimit(.minutes(1))) +@MainActor +struct GridMembershipRevocationTests { + enum ResponseKind: CaseIterable, Sendable { + case create, join, move, leave + } + + @Test( + "a held membership result cannot restore a space after its access is revoked", + arguments: ResponseKind.allCases) + func revokedSpaceCannotReturn(kind: ResponseKind) async throws { + let fixture = GridAuthorityTestFixture() + do { + try await exerciseRevokedResponse(kind: kind, fixture: fixture) + } catch { + await fixture.close() + throw error + } + await fixture.close() + } + + private func exerciseRevokedResponse(kind: ResponseKind, fixture: GridAuthorityTestFixture) + async throws + { + let token = try await fixture.seedAccount() + try await fixture.admit() + let call = try #require(fixture.service.currentCall) + var heldGrid = try #require(fixture.service.grid(spaceID: call.spaceID)) + heldGrid.revision += 1 + #expect(fixture.service.isEnabled(spaceID: call.spaceID)) + #expect(fixture.service.hasLocalAdmission) + + await fixture.service.handle( + GridEvent.with { + $0.accessRevoked = .with { $0.spaceID = call.spaceID } + }) + #expect(fixture.service.grid(spaceID: call.spaceID) == nil) + #expect(!fixture.service.isEnabled(spaceID: call.spaceID)) + #expect(!fixture.service.hasLocalAdmission) + + let operationKind: GridMembershipOperation.Kind = + switch kind { + case .create: .create + case .join: .join(roomID: call.roomID) + case .move: .move(callID: call.callID, roomID: call.roomID) + case .leave: .leave(roomID: call.roomID) + } + let operation = GridMembershipOperation( + kind: operationKind, + spaceID: call.spaceID, + expectedMembershipID: call.membershipID, + intentRevision: 0, + accountToken: token, + microphoneEnabled: false, + automaticMicrophoneChange: nil, + accessRevision: 0, + revision: 2, + startedAt: Date() + ) + let response = GridRoomMutationResult( + grids: [heldGrid], credentials: nil, currentCall: call + ) + let event: GridMembershipSyncEvent = + switch kind { + case .create: .created(operation, response) + case .join: .joined(operation, response) + case .move: .moved(operation, response) + case .leave: .left(operation, response) + } + // The real event handler attempts Home repair through the unavailable + // control connection. Rejection must hold without a successful refetch. + await fixture.service.handle(event) + #expect(fixture.service.grid(spaceID: call.spaceID) == nil) + #expect(!fixture.service.isEnabled(spaceID: call.spaceID)) + #expect(fixture.service.recentAvatars(spaceID: call.spaceID).isEmpty) + #expect(!fixture.service.homeSpaces.contains { $0.spaceID == call.spaceID }) + #expect(fixture.service.currentCall == nil) + #expect(!fixture.service.hasLocalAdmission) + #expect(fixture.service.mediaSessionIdentity == nil) + #expect(!fixture.service.membershipMutationInFlight) + } +} diff --git a/apple/InlineKit/Tests/InlineGridTests/GridPendingAuthorityRetirementTests.swift b/apple/InlineKit/Tests/InlineGridTests/GridPendingAuthorityRetirementTests.swift new file mode 100644 index 000000000..151950092 --- /dev/null +++ b/apple/InlineKit/Tests/InlineGridTests/GridPendingAuthorityRetirementTests.swift @@ -0,0 +1,169 @@ +import Auth +import Foundation +@testable import InlineGrid +import InlineProtocol +import Testing + +@Suite("Grid pending membership auth retirement", .serialized, .timeLimit(.minutes(1))) +@MainActor +struct GridPendingAuthorityRetirementTests { + enum PendingKind: CaseIterable, Sendable { + case create, join, move, leave + + var method: InlineProtocol.Method { + switch self { + case .create: .createGridRoom + case .join: .joinGridRoom + case .move: .moveGridCallHere + case .leave: .leaveGridRoom + } + } + + func submit(to service: GridRoomService) { + switch self { + case .create: service.createAndJoin(spaceID: 42) + case .join: service.join(roomID: 7) + case .move: service.moveCallHere() + case .leave: service.leaveCurrentRoom(spaceID: 42) + } + } + + var operationKind: GridMembershipOperation.Kind { + switch self { + case .create: .create + case .join: .join(roomID: 7) + case .move: .move(callID: "call-membership-a", roomID: 7) + case .leave: .leave(roomID: 7) + } + } + + func event(operation: GridMembershipOperation, result: GridRoomMutationResult) -> GridMembershipSyncEvent { + switch self { + case .create: .created(operation, result) + case .join: .joined(operation, result) + case .move: .moved(operation, result) + case .leave: .left(operation, result) + } + } + } + + enum Replacement: CaseIterable, Sendable, Equatable { + case explicit, implicit, denied + } + + @Test( + "held public membership work cannot block or clear a successor claim across auth replacement", + arguments: PendingKind.allCases, + Replacement.allCases + ) + func pendingOperationCannotOwnSuccessor(kind: PendingKind, replacement outcome: Replacement) async throws { + let control = GridAuthorityMembershipControl() + let fixture = GridAuthorityTestFixture(control: control) + do { + try await exercise(kind: kind, replacement: outcome, fixture: fixture, control: control) + } catch { + await fixture.close() + throw error + } + await fixture.close() + } + + private func exercise( + kind: PendingKind, replacement outcome: Replacement, + fixture: GridAuthorityTestFixture, control: GridAuthorityMembershipControl + ) async throws { + let oldAccount = try await fixture.seedAccount() + try await fixture.admit() + try await fixture.enableTransferForPublicActions() + let confirmed = try #require(fixture.service.grid(spaceID: 42)) + await fixture.audio.blockShutdown() + kind.submit(to: fixture.service) + #expect(fixture.service.membershipMutationInFlight) + try await eventuallyGridAuthority { control.submissions.count == 1 } + #expect(control.submissions[0].method == kind.method) + #expect(control.submissions[0].account == oldAccount) + + let attempt = outcome == .implicit + ? nil : try await fixture.auth.beginLoginAttempt(allowAuthenticated: true) + let replacing = Task { + try await fixture.auth.saveInlineProtocolCredentials( + fixture.credentials(sessionID: 11), loginAttempt: attempt + ) + if let attempt { + return try await fixture.auth.finalizeCredentialsCommittedByLoginAttempt(attempt) + } + return try fixture.auth.beginAccountMutation() + } + // The old sender is still suspended and deliberately ignores cancellation. Claim bookkeeping + // must be released by the synchronous auth boundary, before its physical barrier completes. + try await eventuallyGridAuthority { + fixture.auth.hasPendingAccountTransition() && !fixture.service.membershipMutationInFlight + } + #expect(!control.completed.contains(0)) + #expect(!fixture.service.hasLocalAdmission) + #expect(fixture.service.currentCall?.membershipID == "membership-a") + #expect(fixture.service.grid(spaceID: 42)?.currentRoomID == confirmed.currentRoomID) + #expect(fixture.service.grid(spaceID: 42)?.rooms[0].avatars.contains { + $0.ownedByCurrentSession && $0.membershipID == "membership-a" + } == true) + + await fixture.audio.releaseShutdown(succeeded: outcome != .denied) + if outcome == .denied { + await #expect(throws: AuthStorageError.loginUnavailable) { try await replacing.value } + try fixture.auth.validateAccountMutation(oldAccount) + #expect(fixture.auth.inlineProtocolCredentials()?.accountSessionId == 10) + // Physical recovery is explicit. The original account may try again once stop is proven. + await fixture.audio.releaseShutdown(succeeded: true) + #expect(await fixture.engine.requestShutdownReceipt().value.isLocallyQuiescent) + } else { + let newAccount = try await replacing.value + #expect((newAccount == oldAccount) == (outcome == .implicit)) + } + + fixture.service.createAndJoin(spaceID: 42) + #expect(fixture.service.membershipMutationInFlight) + try await eventuallyGridAuthority { control.submissions.count == 2 } + #expect(control.submissions[1].method == .createGridRoom) + #expect(!control.completed.contains(0)) + + let oldResponse = fixture.claimResult(membershipID: "old-held", revision: 4) + if kind == .leave { + control.fail(0) + } else { + control.succeed(0, with: oldResponse) + } + try await eventuallyGridAuthority { control.completed.contains(0) } + // A real worker can also have broadcast its event just before reset. Replay that already- + // buffered result through the production handler while the new public claim remains pending. + let oldOperation = GridMembershipOperation( + kind: kind.operationKind, spaceID: 42, expectedMembershipID: "membership-a", + intentRevision: 1, accountToken: oldAccount, microphoneEnabled: false, + automaticMicrophoneChange: nil, accessRevision: 0, revision: 1, startedAt: Date() + ) + let oldEvent = kind.event(operation: oldOperation, result: oldResponse) + await fixture.service.handle(oldEvent) + await fixture.service.handle(.failed(oldOperation, message: "old queued failure")) + #expect(fixture.service.membershipMutationInFlight) + #expect(fixture.service.grid(spaceID: 42)?.revision == confirmed.revision) + #expect(fixture.service.currentCall?.membershipID == "membership-a") + #expect(!fixture.service.hasLocalAdmission) + #expect(fixture.service.lastError != "old queued failure") + + control.succeed(1, with: fixture.claimResult(membershipID: "successor", revision: 3)) + try await eventuallyGridAuthority { + fixture.service.hasLocalAdmission && !fixture.service.membershipMutationInFlight + && fixture.service.media.connectionState == .connected + } + #expect(fixture.service.currentCall?.membershipID == "successor") + let successorIdentity = fixture.service.mediaSessionIdentity + #expect(successorIdentity != nil) + // Normal responses above really admitted media. An older success or failure may neither + // replace its snapshot nor tear it down after the original sender finally returns. + await fixture.service.handle(oldEvent) + await fixture.service.handle(.failed(oldOperation, message: "old queued failure")) + #expect(fixture.service.hasLocalAdmission) + #expect(fixture.service.mediaSessionIdentity == successorIdentity) + #expect(fixture.service.currentCall?.membershipID == "successor") + #expect(fixture.service.grid(spaceID: 42)?.revision == 3) + } +} diff --git a/apple/InlineKit/Tests/InlineKitTests/Grid/GridTransactionLifetimeTests.swift b/apple/InlineKit/Tests/InlineKitTests/Grid/GridTransactionLifetimeTests.swift index 8f183d3ec..762cc1841 100644 --- a/apple/InlineKit/Tests/InlineKitTests/Grid/GridTransactionLifetimeTests.swift +++ b/apple/InlineKit/Tests/InlineKitTests/Grid/GridTransactionLifetimeTests.swift @@ -1,12 +1,11 @@ -import Testing -import RealtimeV2 - @testable import InlineKit +import RealtimeV2 +import Testing @Suite("Grid transaction lifetime") struct GridTransactionLifetimeTests { - @Test("snapshot requests are ephemeral and coalesce by resource") - func snapshotRequestsAreEphemeral() throws { + @Test("snapshot requests are ephemeral and preserve read and ownership fences") + func snapshotRequestsAreEphemeral() { let grid = GetGridTransaction(spaceID: 42) let home = GetGridHomeTransaction() let connection = PrepareGridConnectionTransaction(roomID: 7, generation: 3) @@ -22,9 +21,12 @@ struct GridTransactionLifetimeTests { #expect(gridConfig.maxQueueAge == 5) #expect(homeConfig.maxQueueAge == 5) #expect(connectionConfig.maxQueueAge == 5) - #expect(grid.ephemeralCoalescingKey == "space:42") - #expect(home.ephemeralCoalescingKey == "home") - #expect(connection.ephemeralCoalescingKey == "room:7") + #expect(grid.ephemeralCoalescingKey != GetGridTransaction(spaceID: 42).ephemeralCoalescingKey) + #expect(home.ephemeralCoalescingKey != GetGridHomeTransaction().ephemeralCoalescingKey) + #expect(connection.ephemeralCoalescingKey == "room:7:generation:3:membership:legacy") + #expect(connection.ephemeralCoalescingKey != PrepareGridConnectionTransaction( + roomID: 7, generation: 4, expectedMembershipID: "new-owner" + ).ephemeralCoalescingKey) #expect(grid.effectiveReconnectReplayPolicy == .neverReplay) #expect(home.effectiveReconnectReplayPolicy == .neverReplay) #expect(connection.effectiveReconnectReplayPolicy == .neverReplay) @@ -49,3 +51,45 @@ struct GridTransactionLifetimeTests { #expect(joinInput.microphoneEnabled == false) } } + +@Suite("Grid ownership request fences") +struct GridOwnershipRequestFenceTests { + @Test("empty create fence means no presence, while omitted fence stays distinguishable") + func explicitEmptyFenceIsPresent() { + let fenced = CreateGridRoomTransaction(spaceID: 42, microphoneEnabled: false, expectedMembershipID: "") + let legacy = CreateGridRoomTransaction(spaceID: 42, microphoneEnabled: false) + guard case let .createGridRoom(fencedInput)? = fenced.input(from: fenced.context), + case let .createGridRoom(legacyInput)? = legacy.input(from: legacy.context) + else { Issue.record("Expected create input") + return + } + #expect(fencedInput.hasExpectedMembershipID) + #expect(fencedInput.expectedMembershipID.isEmpty) + #expect(!legacyInput.hasExpectedMembershipID) + } + + @Test("browsing never renews, while admitted heartbeat carries exact ownership") + func snapshotAndRenewalStayDistinct() { + let read = GetGridTransaction(spaceID: 42) + let renewal = GetGridTransaction(spaceID: 42, expectedMembershipID: "admitted-owner") + guard case let .getGrid(readInput)? = read.input(from: read.context), + case let .getGrid(renewInput)? = renewal.input(from: renewal.context) + else { Issue.record("Expected GetGrid input") + return + } + #expect(!readInput.hasExpectedMembershipID) + #expect(renewInput.hasExpectedMembershipID) + #expect(renewInput.expectedMembershipID == "admitted-owner") + } + + @Test("move here preserves stable call and caller-observed ownership fence") + func moveCarriesExactClaim() { + let move = MoveGridCallHereTransaction(callID: "stable-call", expectedMembershipID: "observed-owner") + guard case let .moveGridCallHere(input)? = move.input(from: move.context) else { + Issue.record("Expected MoveGridCallHere input") + return + } + #expect(input.callID == "stable-call") + #expect(input.expectedMembershipID == "observed-owner") + } +} diff --git a/apple/InlineKit/Tests/InlineRTCTests/Engine/GridAudioEngineTests.swift b/apple/InlineKit/Tests/InlineRTCTests/Engine/GridAudioEngineTests.swift index 395421a98..bd5d6fae0 100644 --- a/apple/InlineKit/Tests/InlineRTCTests/Engine/GridAudioEngineTests.swift +++ b/apple/InlineKit/Tests/InlineRTCTests/Engine/GridAudioEngineTests.swift @@ -5,6 +5,50 @@ import Testing @Suite("Grid audio engine", .serialized) struct GridAudioEngineTests { + @Test("retry after a safety pause restores listening without restoring a microphone lease") + func safetyRetryRestoresListeningOnly() async throws { + let driver = FakeGridAudioDriver() + let engine = GridAudioEngine(driver: driver, permissionDriver: TestGridMicrophonePermissionDriver()) + let lease = GridAudioLease.connectionDemand(.init("grid-test:1:76:1")) + await engine.setInput(.automatic) + await engine.acquireCaptureLease(lease) + try await eventually { await engine.currentSnapshot().isPrepared } + let prepareCount = await driver.operations().filter { $0 == "prepared:true" }.count + + await driver.emit(.safetyPaused) + try await eventually { + let snapshot = await engine.currentSnapshot() + return snapshot.isSafetyPaused && snapshot.captureLeaseCount == 0 + } + await engine.retry() + try await eventually { + let snapshot = await engine.currentSnapshot() + return !snapshot.isSafetyPaused && snapshot.isPlaying && !snapshot.isRecording + } + #expect(await engine.currentSnapshot().captureLeaseCount == 0) + #expect(await driver.operations().filter { $0 == "prepared:true" }.count == prepareCount) + _ = await engine.shutdown() + } + + @Test("a denied microphone does not prevent listening-only safety recovery") + func safetyRetryWithoutMicrophonePermission() async throws { + let driver = FakeGridAudioDriver() + let engine = GridAudioEngine( + driver: driver, + permissionDriver: TestGridMicrophonePermissionDriver(current: .denied, requested: .denied) + ) + await engine.start() + await driver.emit(.safetyPaused) + try await eventually { await engine.currentSnapshot().isSafetyPaused } + await engine.retry() + try await eventually { + let snapshot = await engine.currentSnapshot() + return !snapshot.isSafetyPaused && snapshot.isPlaying && !snapshot.isRecording + } + #expect(!(await driver.operations().contains("prepared:true"))) + _ = await engine.shutdown() + } + @Test("permission gates the physical input transaction") func permissionGatesInputRoute() async throws { let driver = FakeGridAudioDriver() @@ -1835,6 +1879,7 @@ private actor FakeGridAudioDriver: GridAudioDriver { nonisolated let recordingStartsWithMicrophoneSender: Bool private nonisolated let eventContinuation: AsyncStream.Continuation private var log: [String] = [] + private var safetyPaused = false private var availableDeviceIDs: Set private var availableOutputDeviceIDs: Set private let blockConfigure: Bool @@ -1949,6 +1994,12 @@ private actor FakeGridAudioDriver: GridAudioDriver { log.append("resume-terminal-gate") } + func resumeListeningAfterSafetyPause() async throws { + guard safetyPaused else { return } + log.append("resume-listening") + emit(.listeningResumed) + } + func setPrepared(_ prepared: Bool) async throws { log.append("prepared:\(prepared)") if prepared, blockPrepare { @@ -2203,6 +2254,16 @@ private actor FakeGridAudioDriver: GridAudioDriver { engineRunning = false recordingActive = false playingActive = false + case .safetyPaused: + safetyPaused = true + engineRunning = false + recordingActive = false + playingActive = false + case .listeningResumed: + safetyPaused = false + engineRunning = true + recordingActive = false + playingActive = true case .devicesChanged: break } diff --git a/apple/InlineKit/Tests/InlineRTCTests/Engine/GridRTCEngineTests.swift b/apple/InlineKit/Tests/InlineRTCTests/Engine/GridRTCEngineTests.swift index 2ae2508c5..532d28250 100644 --- a/apple/InlineKit/Tests/InlineRTCTests/Engine/GridRTCEngineTests.swift +++ b/apple/InlineKit/Tests/InlineRTCTests/Engine/GridRTCEngineTests.swift @@ -2,6 +2,7 @@ import Foundation import Testing @testable import InlineRTC +@testable import InlineAudioPlayback @Suite("Grid RTC engine", .serialized) struct GridRTCEngineTests { @@ -1534,13 +1535,17 @@ struct GridRTCEngineTests { await rtc.setDemand(InlineRTCDemand()) let receipt = await rtc.shutdown() - #expect(receipt.isQuiescent) + #expect(!receipt.isQuiescent) + #expect(receipt.failures.contains { $0.contains("provider operation") }) #expect(receipt.microphonePublicationCount == 0) #expect(receipt.screenSharePublicationCount == 0) #expect(await audio.currentSnapshot().captureLeaseCount == 0) #expect(await driver.operations().filter { $0 == "quiesce-done:26" }.count >= 2) await driver.releaseBlockedConnect() + try await eventuallyRTC { await rtc.currentSnapshot().abandonedProviderOperationCount == 0 } + let releasedReceipt = await rtc.shutdown() + #expect(releasedReceipt.isQuiescent) } @Test("a hung microphone publication fences replacement until mutation ownership returns") @@ -1722,6 +1727,249 @@ struct GridRTCEngineTests { #expect(retiredIndex < replacementIndex) } + @MainActor + @Test("a replacement room reuses the process call reservation while old connect retires", .timeLimit(.minutes(1))) + func rapidRoomReplacementKeepsProcessReservation() async throws { + let backend = RTCProcessAudioBackend() + try await withRTCNativeCancellationDrain(backend: backend) { + let session = InlineAudioSession(backend: backend) + let audioDriver = RTCFakeAudioDriver(initiallyRecordingActive: false, audioSession: session) + let audio = GridAudioEngine(driver: audioDriver, permissionDriver: TestGridMicrophonePermissionDriver()) + let driver = FakeGridRTCDriver(blockedRoomID: 76) + let rtc = GridRTCEngine(audio: audio, driver: driver) + defer { + Task { + await backend.drain() + await driver.releaseBlockedConnect() + _ = await rtc.shutdown() + _ = await audio.shutdown() + } + } + let first = InlineRTCSessionID("grid-test:1:76:1") + let second = InlineRTCSessionID("grid-test:1:77:1") + await rtc.setDemand(demand(target: first, microphoneEnabled: false, microphoneCapturePrepared: false)) + try await eventuallyRTC { await driver.operations().contains("connect:76") } + let reservation = try #require(session.current) + + await rtc.setDemand(InlineRTCDemand()) + try await eventuallyRTC { await driver.operations().contains("quiesce-done:76") } + #expect(session.owns(reservation)) + #expect(throws: InlineAudioSessionError.self) { try session.validateGridAdmission() } + await rtc.setDemand(demand(target: second, microphoneEnabled: false, microphoneCapturePrepared: false)) + #expect(session.owns(reservation)) + #expect(!session.isQuarantined) + await driver.releaseBlockedConnect() + try await eventuallyRTC { await rtc.currentSnapshot().state == .connected(second) } + #expect(await backend.operations() == [.activate(reservation)]) + #expect(session.owns(reservation)) + + // A stale A withdrawal receipt cannot release B, nor can generic release. + await session.gridMediaDidQuiesce(epoch: 2) + await session.observeGridDirections(recording: false, playing: false, + nativeOperationsQuiescent: true, epoch: 2) + #expect(session.release(reservation) == nil) + #expect(session.owns(reservation)) + await rtc.setDemand(InlineRTCDemand()) + try await eventuallyRTC { await session.current == nil } + #expect(await backend.operations() == [.activate(reservation), .release(reservation)]) + try session.validateGridAdmission() + let recorder = try session.acquire(.recording) + let cleanup = session.release(recorder) + try await cleanup?.value + } + } + + @MainActor + @Test("an unmuted replacement cannot reopen native audio before old local teardown completes", + .timeLimit(.minutes(1))) + func replacementAudioWaitsForHeldLocalQuiescence() async throws { + let backend = RTCProcessAudioBackend() + try await withRTCNativeCancellationDrain(backend: backend) { + let session = InlineAudioSession(backend: backend) + let audioDriver = RTCFakeAudioDriver( + initiallyRecordingActive: false, audioSession: session, + nativeAvailabilityTracksMediaDemand: true + ) + let audio = GridAudioEngine(driver: audioDriver, permissionDriver: TestGridMicrophonePermissionDriver(), + captureCooldown: .zero) + let driver = FakeGridRTCDriver(blockLocalQuiescenceRoomID: 83) + var configuration = InlineRTCConfiguration.voice + configuration.connection.retirementBarrierTimeout = 0.05 + let rtc = GridRTCEngine(audio: audio, driver: driver, configuration: configuration) + defer { + Task { + await driver.releaseBlockedLocalQuiescence() + await backend.drain() + _ = await rtc.shutdown() + _ = await audio.shutdown() + } + } + let first = InlineRTCSessionID("grid-test:1:83:1") + let second = InlineRTCSessionID("grid-test:1:84:1") + await rtc.setDemand(demand(target: first, microphoneEnabled: true)) + try await eventuallyRTC { + let snapshot = await rtc.currentSnapshot() + return snapshot.microphonePublished && !snapshot.microphoneMuted + } + #expect(await audioDriver.operations().contains("capture-open:1")) + let reservation = try #require(session.current) + await rtc.setDemand(InlineRTCDemand()) + try await eventuallyRTC { await driver.localQuiescenceIsBlocked() } + #expect(await driver.currentMutedState(roomID: 83) == false) + #expect(!(await audioDriver.recordingIsActive())) + let retainedLeaseCount = await audio.currentSnapshot().captureLeaseCount + + // The old unmuted track still exists while B requests explicit Unmute. + // Admission returns a bounded failure, not permission to reopen its ADM. + await rtc.setDemand(demand(target: second, microphoneEnabled: true)) + if case .failed(second, _) = await rtc.currentSnapshot().state {} else { + Issue.record("replacement should fail closed while local teardown remains held") + } + #expect(session.owns(reservation)) + #expect(await audio.currentSnapshot().captureLeaseCount == retainedLeaseCount) + #expect(!(await audioDriver.recordingIsActive())) + #expect(!(await audioDriver.operations().contains("resume:3"))) + #expect(!(await audioDriver.operations().contains("capture-open:3"))) + #expect(!(await driver.operations().contains("connect:84"))) + #expect(throws: AudioPlaybackError.self) { try session.acquire(.recording) } + + await driver.releaseBlockedLocalQuiescence() + try await eventuallyRTC { + let snapshot = await rtc.currentSnapshot() + return snapshot.state == .connected(second) && snapshot.microphonePublished && !snapshot.microphoneMuted + } + #expect(await audioDriver.operations().contains("resume:3")) + #expect(await audioDriver.operations().contains("capture-open:3")) + let operations = await driver.operations() + let quiet = try #require(operations.firstIndex(of: "quiesce-done:83")) + let replacement = try #require(operations.firstIndex(of: "connect:84")) + #expect(quiet < replacement) + await rtc.setDemand(InlineRTCDemand()) + try await eventuallyRTC { await session.current == nil } + #expect(!session.isQuarantined) + } + } + + @MainActor + @Test("replacement joins committed native retirement before activating its new call", + .timeLimit(.minutes(1)), + arguments: [false, true]) + func replacementWaitsForNativeRetirement(releaseFails: Bool) async throws { + let backend = RTCProcessAudioBackend() + try await withRTCNativeCancellationDrain(backend: backend) { + let session = InlineAudioSession(backend: backend) + let audioDriver = RTCFakeAudioDriver(initiallyRecordingActive: false, audioSession: session) + let audio = GridAudioEngine(driver: audioDriver, permissionDriver: TestGridMicrophonePermissionDriver()) + let driver = FakeGridRTCDriver() + let rtc = GridRTCEngine(audio: audio, driver: driver) + defer { + Task { + await backend.drain() + _ = await rtc.shutdown() + _ = await audio.shutdown() + } + } + let first = InlineRTCSessionID("grid-test:1:78:1") + let second = InlineRTCSessionID("grid-test:1:79:1") + await rtc.setDemand(demand(target: first, microphoneEnabled: false, microphoneCapturePrepared: false)) + try await eventuallyRTC { await rtc.currentSnapshot().state == .connected(first) } + let retired = try #require(session.current) + await backend.blockNextRelease() + await rtc.setDemand(InlineRTCDemand()) + try await eventuallyRTC { await backend.releaseIsBlocked() } + let replacement = Task { + await rtc.setDemand(demand(target: second, microphoneEnabled: false, microphoneCapturePrepared: false)) + } + try await eventuallyRTC { await audioDriver.operations().contains("media:true:3") } + #expect(session.owns(retired)) + #expect(throws: AudioPlaybackError.self) { try session.acquire(.recording) } + #expect(await backend.operations() == [.activate(retired), .release(retired)]) + #expect(!(await driver.operations().contains("connect:79"))) + await backend.completeRelease(failing: releaseFails) + await replacement.value + + if releaseFails { + #expect(session.isQuarantined) + #expect(session.owns(retired)) + #expect(throws: InlineAudioSessionError.self) { try session.acquire(.recording) } + #expect(!(await driver.operations().contains("connect:79"))) + #expect(await backend.operations() == [.activate(retired), .release(retired)]) + } else { + try await eventuallyRTC { await rtc.currentSnapshot().state == .connected(second) } + let admitted = try #require(session.current) + #expect(admitted != retired) + #expect(await backend.operations() == [.activate(retired), .release(retired), .activate(admitted)]) + await rtc.setDemand(InlineRTCDemand()) + try await eventuallyRTC { await session.current == nil } + #expect(!session.isQuarantined) + try session.validateGridAdmission() + } + } + } + + @MainActor + @Test("a replacement waiting for teardown cannot override a newer room or withdrawal", + .timeLimit(.minutes(1)), + arguments: [false, true]) + func waitingReplacementCannotSupersedeNewerDemand(withdraw: Bool) async throws { + let backend = RTCProcessAudioBackend() + try await withRTCNativeCancellationDrain(backend: backend) { + let session = InlineAudioSession(backend: backend) + let audioDriver = RTCFakeAudioDriver(initiallyRecordingActive: false, audioSession: session) + let audio = GridAudioEngine(driver: audioDriver, permissionDriver: TestGridMicrophonePermissionDriver()) + let driver = FakeGridRTCDriver() + let rtc = GridRTCEngine(audio: audio, driver: driver) + defer { + Task { + await backend.drain() + _ = await rtc.shutdown() + _ = await audio.shutdown() + } + } + let first = InlineRTCSessionID("grid-test:1:80:1") + let second = InlineRTCSessionID("grid-test:1:81:1") + let third = InlineRTCSessionID("grid-test:1:82:1") + await rtc.setDemand(demand(target: first, microphoneEnabled: false, microphoneCapturePrepared: false)) + try await eventuallyRTC { await rtc.currentSnapshot().state == .connected(first) } + let retired = try #require(session.current) + await backend.blockNextRelease() + await rtc.setDemand(InlineRTCDemand()) + try await eventuallyRTC { await backend.releaseIsBlocked() } + let waitingReplacement = Task { + await rtc.setDemand(demand(target: second, microphoneEnabled: false, + microphoneCapturePrepared: false, outputVolume: 0.2)) + } + try await eventuallyRTC { await audioDriver.operations().contains("media:true:3") } + let latestDemand = withdraw ? InlineRTCDemand() : demand( + target: third, microphoneEnabled: false, microphoneCapturePrepared: false, outputVolume: 0.8 + ) + let latest = Task { await rtc.setDemand(latestDemand) } + try await eventuallyRTC { await audioDriver.operations().contains("media:\(!withdraw):4") } + #expect(session.owns(retired)) + await backend.completeRelease() + await waitingReplacement.value + await latest.value + #expect(!(await driver.operations().contains("connect:81"))) + + if withdraw { + try await eventuallyRTC { await rtc.currentSnapshot().state == .idle } + #expect(session.current == nil) + #expect(await audio.currentSnapshot().outputVolume == 1) + #expect(await backend.operations() == [.activate(retired), .release(retired)]) + try session.validateGridAdmission() + } else { + try await eventuallyRTC { await rtc.currentSnapshot().state == .connected(third) } + let admitted = try #require(session.current) + #expect(admitted != retired) + #expect(await audio.currentSnapshot().outputVolume == 0.8) + #expect(await backend.operations() == [.activate(retired), .release(retired), .activate(admitted)]) + await rtc.setDemand(InlineRTCDemand()) + try await eventuallyRTC { await session.current == nil } + } + #expect(!session.isQuarantined) + } + } + @Test("room replacement rearms the custom ADM transport fence") func roomReplacementRearmsAudioTransportFence() async throws { let audioDriver = RTCFakeAudioDriver( @@ -2305,9 +2553,146 @@ struct GridRTCEngineTests { } } + @Test("a recording conflict preserves desired Grid admission for a later retry") + func mediaOwnershipConflictRetriesDesiredAdmission() async throws { + let audioDriver = RTCFakeAudioDriver(initiallyRecordingActive: false, mediaAdmissionBlocked: true) + let audio = GridAudioEngine(driver: audioDriver, permissionDriver: TestGridMicrophonePermissionDriver()) + let driver = FakeGridRTCDriver() + let rtc = GridRTCEngine(audio: audio, driver: driver) + let target = InlineRTCSessionID("grid-test:1:70:1") + await rtc.setDemand(demand(target: target, microphoneEnabled: false, microphoneCapturePrepared: false)) + try await eventuallyRTC { + if case .failed(target, _) = await rtc.currentSnapshot().state { return true } + return false + } + #expect(await driver.operations().contains("connect:70") == false) + #expect(await audioDriver.operations().contains("prepared:true") == false) + await audioDriver.allowMediaAdmission() + await rtc.networkBecameAvailable() + try await eventuallyRTC { await rtc.currentSnapshot().state == .connected(target) } + #expect(await audio.currentSnapshot().captureLeaseCount == 0) + _ = await rtc.shutdown() + _ = await audio.shutdown() + } + + @Test("an authorized listen-only admission neither prepares nor publishes a microphone") + func listenOnlyDoesNotCapture() async throws { + let audioDriver = RTCFakeAudioDriver(initiallyRecordingActive: false) + let audio = GridAudioEngine(driver: audioDriver, permissionDriver: TestGridMicrophonePermissionDriver()) + let driver = FakeGridRTCDriver() + let rtc = GridRTCEngine(audio: audio, driver: driver) + let target = InlineRTCSessionID("grid-test:1:71:1") + + await rtc.setDemand(demand(target: target, microphoneEnabled: false, microphoneCapturePrepared: false)) + try await eventuallyRTC { await rtc.currentSnapshot().state == .connected(target) } + #expect(await audio.currentSnapshot().captureLeaseCount == 0) + #expect(await audioDriver.operations().contains("prepared:true") == false) + #expect(await driver.operations().contains("publish:71:muted=true") == false) + #expect(await rtc.currentSnapshot().microphonePublished == false) + _ = await rtc.shutdown() + _ = await audio.shutdown() + } + + @Test("ordinary mute preserves explicit capture preparation until media demand ends") + func warmMuteEndsWithDemand() async throws { + let audioDriver = RTCFakeAudioDriver(initiallyRecordingActive: false) + let audio = GridAudioEngine( + driver: audioDriver, + permissionDriver: TestGridMicrophonePermissionDriver(), + captureCooldown: .zero + ) + let driver = FakeGridRTCDriver() + let rtc = GridRTCEngine(audio: audio, driver: driver) + let target = InlineRTCSessionID("grid-test:1:72:1") + + await rtc.setDemand(demand(target: target, microphoneEnabled: true, microphoneCapturePrepared: true)) + try await eventuallyRTC { + let snapshot = await rtc.currentSnapshot() + return snapshot.microphonePublished && !snapshot.microphoneMuted + } + let prepares = await audioDriver.operations().filter { $0 == "prepared:true" }.count + await rtc.setDemand(demand(target: target, microphoneEnabled: false, microphoneCapturePrepared: true)) + try await eventuallyRTC { await rtc.currentSnapshot().microphoneMuted } + #expect(await audio.currentSnapshot().isPrepared) + #expect(await audioDriver.operations().filter { $0 == "prepared:true" }.count == prepares) + + await rtc.setDemand(InlineRTCDemand()) + try await eventuallyRTC { + let snapshot = await audio.currentSnapshot() + return snapshot.captureLeaseCount == 0 && !snapshot.isRecording + } + #expect(await audioDriver.operations().contains("prepared:false")) + _ = await rtc.shutdown() + _ = await audio.shutdown() + } + + @Test("permission completion after withdrawing admission cannot start capture") + func permissionCompletionAfterWithdrawal() async throws { + let permission = BlockingGridMicrophonePermissionDriver() + let audioDriver = RTCFakeAudioDriver(initiallyRecordingActive: false) + let audio = GridAudioEngine(driver: audioDriver, permissionDriver: permission, captureCooldown: .zero) + let driver = FakeGridRTCDriver() + let rtc = GridRTCEngine(audio: audio, driver: driver) + let target = InlineRTCSessionID("grid-test:1:73:1") + let request = Task { await audio.requestMicrophonePermission() } + try await eventuallyRTC { await permission.hasStartedRequest() } + await rtc.setDemand(demand(target: target, microphoneEnabled: true, microphoneCapturePrepared: true)) + try await eventuallyRTC { await rtc.currentSnapshot().state == .connected(target) } + await rtc.setDemand(InlineRTCDemand()) + await permission.resolve(.authorized) + await request.value + #expect(await audio.currentSnapshot().captureLeaseCount == 0) + #expect(await audioDriver.operations().contains("prepared:true") == false) + #expect(await driver.operations().contains("publish:73:muted=true") == false) + _ = await rtc.shutdown() + _ = await audio.shutdown() + } + + @Test("an idle ADM cannot hand the session to a recorder while connect still owns provider work") + func standaloneHandoffWaitsForSuspendedConnect() async throws { + let audioDriver = RTCFakeAudioDriver(initiallyRecordingActive: false) + let audio = GridAudioEngine(driver: audioDriver, permissionDriver: TestGridMicrophonePermissionDriver()) + let driver = FakeGridRTCDriver(blockedRoomID: 74) + let rtc = GridRTCEngine(audio: audio, driver: driver) + let target = InlineRTCSessionID("grid-test:1:74:1") + + await rtc.setDemand(demand(target: target, microphoneEnabled: false, microphoneCapturePrepared: false)) + try await eventuallyRTC { await driver.operations().contains("connect:74") } + await rtc.setDemand(InlineRTCDemand()) + try await eventuallyRTC { await driver.operations().contains("quiesce-done:74") } + #expect(!(await audioDriver.canAdmitStandaloneRecorder())) + + await driver.releaseBlockedConnect() + try await eventuallyRTC { await audioDriver.canAdmitStandaloneRecorder() } + #expect(!(await driver.operations().contains("publish:74:muted=true"))) + _ = await rtc.shutdown() + _ = await audio.shutdown() + } + + @Test("a retired microphone publication must return before standalone writer admission") + func standaloneHandoffWaitsForSuspendedPublication() async throws { + let audioDriver = RTCFakeAudioDriver(initiallyRecordingActive: false) + let audio = GridAudioEngine(driver: audioDriver, permissionDriver: TestGridMicrophonePermissionDriver()) + let driver = FakeGridRTCDriver(blockedPublishRoomID: 75) + let rtc = GridRTCEngine(audio: audio, driver: driver) + let target = InlineRTCSessionID("grid-test:1:75:1") + + await rtc.setDemand(demand(target: target, microphoneEnabled: true)) + try await eventuallyRTC { await driver.operations().contains("publish:75:muted=true") } + await rtc.setDemand(InlineRTCDemand()) + try await eventuallyRTC { await driver.operations().contains("quiesce:75") } + #expect(!(await audioDriver.canAdmitStandaloneRecorder())) + + await driver.releaseBlockedPublish() + try await eventuallyRTC { await audioDriver.canAdmitStandaloneRecorder() } + _ = await rtc.shutdown() + _ = await audio.shutdown() + } + private func demand( target: InlineRTCSessionID, microphoneEnabled: Bool, + microphoneCapturePrepared: Bool = true, screenCaptureSource: InlineRTCScreenCaptureSource? = nil, screenShareQualityProfile: InlineRTCScreenShareQualityProfile = .automatic, outputVolume: Float = 1 @@ -2322,6 +2707,7 @@ struct GridRTCEngineTests { expiresAt: Date().addingTimeInterval(60) ), microphoneEnabled: microphoneEnabled, + microphoneCapturePrepared: microphoneCapturePrepared, screenCaptureSource: screenCaptureSource, screenShareQualityProfile: screenShareQualityProfile, outputVolume: outputVolume @@ -2334,7 +2720,13 @@ private actor RTCFakeAudioDriver: GridAudioDriver { nonisolated let recordingStartsWithMicrophoneSender: Bool private var log: [String] = [] private var playingActive = true + private var mediaAdmissionBlocked: Bool + private var mediaActive = false + private var mediaEpoch: UInt64 = 0 + private var mediaQuiescent = true private var recordingActive: Bool? + private let audioSession: InlineAudioSession? + private let nativeAvailabilityTracksMediaDemand: Bool private let inputRouteStartsRecording: Bool private var configureFailuresRemaining: Int @@ -2343,9 +2735,16 @@ private actor RTCFakeAudioDriver: GridAudioDriver { initiallyRecordingActive: Bool? = nil, inputRouteStartsRecording: Bool = false, configureFailures: Int = 0, - recordingStartsWithMicrophoneSender: Bool = false + recordingStartsWithMicrophoneSender: Bool = false, + mediaAdmissionBlocked: Bool = false, + audioSession: InlineAudioSession? = nil, + nativeAvailabilityTracksMediaDemand: Bool = false ) { self.preparationRequiresRTCTransport = preparationRequiresRTCTransport + self.mediaAdmissionBlocked = mediaAdmissionBlocked + self.audioSession = audioSession + self.nativeAvailabilityTracksMediaDemand = nativeAvailabilityTracksMediaDemand + if nativeAvailabilityTracksMediaDemand { playingActive = false } recordingActive = initiallyRecordingActive self.inputRouteStartsRecording = inputRouteStartsRecording configureFailuresRemaining = max(configureFailures, 0) @@ -2364,27 +2763,74 @@ private actor RTCFakeAudioDriver: GridAudioDriver { configureFailuresRemaining = 0 } + func setMediaDemandActive(_ active: Bool, epoch: UInt64) async throws { + if active, mediaAdmissionBlocked { throw FakeGridRTCDriverError.audioConfigureFailed } + if epoch != mediaEpoch { + mediaEpoch = epoch + mediaQuiescent = false + } + mediaActive = active + if nativeAvailabilityTracksMediaDemand, !active { + playingActive = false + recordingActive = false + } + log.append("media:\(active):\(epoch)") + try await audioSession?.setGridDemandActive(active, epoch: epoch) + } + + func mediaDidQuiesce(epoch: UInt64) async { + guard epoch == mediaEpoch, !mediaActive else { return } + mediaQuiescent = true + log.append("quiescent:\(epoch)") + await audioSession?.gridMediaDidQuiesce(epoch: epoch) + if audioSession != nil { _ = await runtimeHealth() } + } + + func canAdmitStandaloneRecorder() -> Bool { !mediaActive && mediaQuiescent } + + func allowMediaAdmission() { mediaAdmissionBlocked = false } + func setPrepared(_ prepared: Bool) async throws { log.append("prepared:\(prepared)") + if nativeAvailabilityTracksMediaDemand, prepared { + guard mediaActive else { return } + log.append("capture-open:\(mediaEpoch)") + playingActive = true + } if recordingActive != nil { recordingActive = prepared } } + func resumeAfterTerminalShutdown() async { + guard nativeAvailabilityTracksMediaDemand, mediaActive else { return } + log.append("resume:\(mediaEpoch)") + playingActive = true + } + + func recordingIsActive() -> Bool { recordingActive ?? false } + func recoverPreparedAudio(preserving _: AudioInputRouteTarget?) async throws { log.append("recover") + if nativeAvailabilityTracksMediaDemand, !mediaActive { return } } func recoverPlayout(preserving _: AudioOutputRouteTarget?) async throws { log.append("recover-playout") + if nativeAvailabilityTracksMediaDemand, !mediaActive { return } playingActive = true } func runtimeHealth() async -> GridAudioRuntimeHealth { - GridAudioRuntimeHealth( + if let audioSession { + await audioSession.observeGridDirections(recording: recordingActive ?? false, + playing: nativeAvailabilityTracksMediaDemand ? playingActive : mediaActive, + nativeOperationsQuiescent: true, epoch: mediaEpoch) + } + return GridAudioRuntimeHealth( isEngineRunning: true, isRecording: recordingActive ?? true, - isPlaying: playingActive, + isPlaying: nativeAvailabilityTracksMediaDemand || audioSession == nil ? playingActive : mediaActive, route: InlineRTCAudioRoute( currentInputID: "default", defaultInputID: "default", @@ -2427,6 +2873,58 @@ private actor RTCFakeAudioDriver: GridAudioDriver { } } +/// Controlled native I/O beneath the real process authority. Room/provider +/// ownership and epoch arbitration stay in the production RTC/audio engines. +@MainActor +private func withRTCNativeCancellationDrain( + backend: RTCProcessAudioBackend, _ operation: @MainActor () async throws -> Void +) async throws { + try await withTaskCancellationHandler { + try await operation() + try Task.checkCancellation() + } onCancel: { + Task { await backend.drain() } + } +} + +private actor RTCProcessAudioBackend: InlineAudioSessionBackend { + enum Operation: Equatable, Sendable { + case activate(InlineAudioSession.Token), release(InlineAudioSession.Token) + } + private struct ReleaseFailure: Error {} + private var log: [Operation] = [] + private var blocksRelease = false + private var releaseContinuation: CheckedContinuation? + + func activate(token: InlineAudioSession.Token, configuration _: InlineAudioSession.Configuration) async throws { + log.append(.activate(token)) + } + + func release(token: InlineAudioSession.Token) async throws { + log.append(.release(token)) + guard blocksRelease else { return } + blocksRelease = false + try await withCheckedThrowingContinuation { releaseContinuation = $0 } + } + + func invalidate() async {} + func operations() -> [Operation] { log } + func blockNextRelease() { blocksRelease = true } + func releaseIsBlocked() -> Bool { releaseContinuation != nil } + + func completeRelease(failing: Bool = false) { + let continuation = releaseContinuation + releaseContinuation = nil + if failing { continuation?.resume(throwing: ReleaseFailure()) } + else { continuation?.resume() } + } + + func drain() { + blocksRelease = false + completeRelease() + } +} + private actor FakeGridRTCDriver: GridRTCDriver { nonisolated let lifecycleEvents: AsyncStream nonisolated let participantSnapshots: AsyncStream @@ -2446,6 +2944,7 @@ private actor FakeGridRTCDriver: GridRTCDriver { private let failScreenShareStopRoomID: Int64? private let blockSilenceRoomID: Int64? private let blockDisconnectRoomID: Int64? + private let blockLocalQuiescenceRoomID: Int64? private let localQuiescenceFailureRoomID: Int64? private let disconnectDelay: Duration private var blockedConnectContinuations: [CheckedContinuation] = [] @@ -2459,6 +2958,8 @@ private actor FakeGridRTCDriver: GridRTCDriver { private var blockedScreenShareContinuations: [CheckedContinuation] = [] private var blockedSilenceContinuations: [CheckedContinuation] = [] private var blockedDisconnectContinuations: [Int64: [CheckedContinuation]] = [:] + private var blockedLocalQuiescenceContinuations: [CheckedContinuation] = [] + private var localQuiescenceGateOpen = false private var publishFailuresRemaining: Int private var screenShareFailuresRemaining: Int private var activeScreenShareRoomIDs = Set() @@ -2476,6 +2977,7 @@ private actor FakeGridRTCDriver: GridRTCDriver { failScreenShareStopRoomID: Int64? = nil, blockSilenceRoomID: Int64? = nil, blockDisconnectRoomID: Int64? = nil, + blockLocalQuiescenceRoomID: Int64? = nil, localQuiescenceFailureRoomID: Int64? = nil, publishFailures: Int = 0, screenShareFailures: Int = 0, @@ -2502,6 +3004,7 @@ private actor FakeGridRTCDriver: GridRTCDriver { self.failScreenShareStopRoomID = failScreenShareStopRoomID self.blockSilenceRoomID = blockSilenceRoomID self.blockDisconnectRoomID = blockDisconnectRoomID + self.blockLocalQuiescenceRoomID = blockLocalQuiescenceRoomID self.localQuiescenceFailureRoomID = localQuiescenceFailureRoomID blockedConnectCallsRemaining = blockedRoomID == nil ? 0 : max(blockedConnectCalls, 0) blockedPublishCallsRemaining = blockedPublishRoomID == nil ? 0 : 1 @@ -2647,6 +3150,11 @@ private actor FakeGridRTCDriver: GridRTCDriver { // Keep disconnect vocabulary for ordering assertions while avoiding the // provider-facing silence path that local quiescence intentionally bypasses. log.append("disconnect:\(roomID)") + if blockLocalQuiescenceRoomID == roomID, !localQuiescenceGateOpen { + await withCheckedContinuation { continuation in + blockedLocalQuiescenceContinuations.append(continuation) + } + } try? await Task.sleep(for: disconnectDelay) if localQuiescenceFailureRoomID == roomID { return GridLocalRoomQuiescenceReceipt( @@ -2713,6 +3221,15 @@ private actor FakeGridRTCDriver: GridRTCDriver { continuations.forEach { $0.resume() } } + func localQuiescenceIsBlocked() -> Bool { !blockedLocalQuiescenceContinuations.isEmpty } + + func releaseBlockedLocalQuiescence() { + localQuiescenceGateOpen = true + let continuations = blockedLocalQuiescenceContinuations + blockedLocalQuiescenceContinuations.removeAll() + continuations.forEach { $0.resume() } + } + func operations() -> [String] { log } diff --git a/apple/InlineKit/Tests/InlineRTCTests/Engine/LiveKitGridIOSAudioDriverTests.swift b/apple/InlineKit/Tests/InlineRTCTests/Engine/LiveKitGridIOSAudioDriverTests.swift new file mode 100644 index 000000000..2443aab26 --- /dev/null +++ b/apple/InlineKit/Tests/InlineRTCTests/Engine/LiveKitGridIOSAudioDriverTests.swift @@ -0,0 +1,247 @@ +#if os(iOS) +import Foundation +import LiveKit +import Testing + +@testable import InlineRTC + +@Suite("iOS Grid audio safety", .serialized) +struct LiveKitGridIOSAudioDriverTests { + @Test("an interruption fencing suspended preparation prevents its late input opening") + func interruptionFencesSuspendedPreparation() async throws { + let backend = BlockingIOSGridAudioBackend(blockPreparation: true) + let driver = LiveKitGridIOSAudioDriver(testingBackend: backend) + try await driver.setMediaDemandActive(true, epoch: 1) + let preparation = Task { try await driver.setPrepared(true) } + try await waitUntil { backend.hasBlockedOperation } + + await driver.interruptionBegan() + backend.releaseBlockedOperation() + do { + try await preparation.value + Issue.record("The interrupted preparation must be superseded") + } catch {} + #expect(backend.inputOpeningCount == 0) + #expect(!backend.outputAvailable) + + await driver.interruptionEnded(shouldResume: true) + #expect(backend.activationCount == 1) + #expect(backend.outputAvailable) + #expect(backend.inputOpeningCount == 0) + } + + @Test("admission withdrawal prevents suspended preparation from reopening either direction") + func withdrawalFencesSuspendedPreparation() async throws { + let backend = BlockingIOSGridAudioBackend(blockPreparation: true) + let driver = LiveKitGridIOSAudioDriver(testingBackend: backend) + try await driver.setMediaDemandActive(true, epoch: 1) + let preparation = Task { try await driver.setPrepared(true) } + try await waitUntil { backend.hasBlockedOperation } + + try await driver.setMediaDemandActive(false, epoch: 2) + backend.releaseBlockedOperation() + do { + try await preparation.value + Issue.record("The withdrawn preparation must be superseded") + } catch {} + #expect(backend.inputOpeningCount == 0) + #expect(!backend.outputAvailable) + try await driver.resumeListeningAfterSafetyPause() + #expect(!backend.outputAvailable) + } + + @Test("explicit retry cannot resume listening while a system interruption is active") + func retryCannotResumeAnActiveInterruption() async throws { + let backend = BlockingIOSGridAudioBackend() + let driver = LiveKitGridIOSAudioDriver(testingBackend: backend) + try await driver.setMediaDemandActive(true, epoch: 1) + await driver.interruptionBegan() + do { + try await driver.resumeListeningAfterSafetyPause() + Issue.record("Retry must be rejected while interrupted") + } catch {} + #expect(!backend.outputAvailable) + #expect(backend.inputOpeningCount == 0) + + await driver.interruptionEnded(shouldResume: false) + #expect(!backend.outputAvailable) + try await driver.resumeListeningAfterSafetyPause() + #expect(backend.outputAvailable) + #expect(backend.inputOpeningCount == 0) + } + + @Test("an unplugged private output requires explicit retry even after another interruption ends") + func privateOutputDoesNotAutomaticallyResume() async throws { + let backend = BlockingIOSGridAudioBackend() + let driver = LiveKitGridIOSAudioDriver(testingBackend: backend) + try await driver.setMediaDemandActive(true, epoch: 1) + await driver.pauseForSafety(requiresExplicitOutputResume: true) + await driver.interruptionBegan() + await driver.interruptionEnded(shouldResume: true) + #expect(!backend.outputAvailable) + try await driver.resumeListeningAfterSafetyPause() + #expect(backend.outputAvailable) + #expect(backend.inputOpeningCount == 0) + } + + @Test("withdrawing admission during a suspended listening retry prevents late output reopening") + func withdrawalFencesSuspendedListeningResume() async throws { + let backend = BlockingIOSGridAudioBackend() + let driver = LiveKitGridIOSAudioDriver(testingBackend: backend) + try await driver.setMediaDemandActive(true, epoch: 1) + await driver.pauseForSafety(requiresExplicitOutputResume: true) + backend.blockNextStop() + let retry = Task { try await driver.resumeListeningAfterSafetyPause() } + try await waitUntil { backend.hasBlockedOperation } + + try await driver.setMediaDemandActive(false, epoch: 2) + backend.releaseBlockedOperation() + try await retry.value + #expect(!backend.outputAvailable) + #expect(backend.inputOpeningCount == 0) + } + + @Test("an audio services reset keeps both directions closed until process restart") + func resetDoesNotPromiseUnsupportedRecovery() async throws { + let backend = BlockingIOSGridAudioBackend() + let driver = LiveKitGridIOSAudioDriver(testingBackend: backend) + try await driver.setMediaDemandActive(true, epoch: 1) + await driver.mediaServicesWereReset() + do { + try await driver.resumeListeningAfterSafetyPause() + Issue.record("A reset requires process restart") + } catch {} + #expect(!backend.outputAvailable) + #expect(backend.activationCount == 0) + #expect(backend.inputOpeningCount == 0) + } + + @Test("an interruption during suspended recovery stop prevents its old capture restart") + func interruptionFencesSuspendedRecovery() async throws { + let backend = BlockingIOSGridAudioBackend() + let driver = LiveKitGridIOSAudioDriver(testingBackend: backend) + try await driver.setMediaDemandActive(true, epoch: 1) + try await driver.setPrepared(true) + let openings = backend.inputOpeningCount + backend.blockNextStop() + let recovery = Task { try await driver.recoverPreparedAudio(preserving: nil) } + try await waitUntil { backend.hasBlockedOperation } + + await driver.interruptionBegan() + backend.releaseBlockedOperation() + do { + try await recovery.value + Issue.record("Interrupted recovery must be superseded") + } catch {} + #expect(backend.inputOpeningCount == openings) + #expect(!backend.outputAvailable) + } + + @Test("the process is quarantined before an audio reset waits for native warm teardown") + func resetQuarantinesBeforeSuspendedStop() async throws { + let backend = BlockingIOSGridAudioBackend() + let driver = LiveKitGridIOSAudioDriver(testingBackend: backend) + try await driver.setMediaDemandActive(true, epoch: 1) + backend.blockNextStop() + let reset = Task { await driver.mediaServicesWereReset() } + try await waitUntil { backend.hasBlockedOperation } + #expect(await backend.isQuarantined()) + do { + try await driver.resumeListeningAfterSafetyPause() + Issue.record("Retry must be fenced before reset teardown returns") + } catch {} + #expect(!backend.outputAvailable) + backend.releaseBlockedOperation() + await reset.value + } + + @Test("a receipt from an older call epoch cannot release the current retired writer") + func quiescenceReceiptMatchesRetiredEpoch() async throws { + let backend = BlockingIOSGridAudioBackend() + let driver = LiveKitGridIOSAudioDriver(testingBackend: backend) + try await driver.setMediaDemandActive(true, epoch: 1) + try await driver.setMediaDemandActive(false, epoch: 2) + try await driver.setMediaDemandActive(true, epoch: 3) + try await driver.setMediaDemandActive(false, epoch: 4) + await driver.mediaDidQuiesce(epoch: 2) + #expect(backend.quiescenceEpochs.isEmpty) + await driver.mediaDidQuiesce(epoch: 4) + #expect(backend.quiescenceEpochs == [4]) + } +} + +/// A suspended SDK operation, not a model of the driver's epoch state. +private final class BlockingIOSGridAudioBackend: IOSGridAudioBackend, @unchecked Sendable { + private let lock = NSLock() + private var blocksPreparation: Bool + private var blocksStop = false + private var continuation: CheckedContinuation? + private var inputOpenings = 0 + private var output = false + private var activations = 0 + private var quarantined = false + private var reportedQuiescenceEpochs: [UInt64] = [] + + init(blockPreparation: Bool = false) { blocksPreparation = blockPreparation } + + var hasBlockedOperation: Bool { lock.withLock { continuation != nil } } + var inputOpeningCount: Int { lock.withLock { inputOpenings } } + var outputAvailable: Bool { lock.withLock { output } } + var activationCount: Int { lock.withLock { activations } } + var quiescenceEpochs: [UInt64] { lock.withLock { reportedQuiescenceEpochs } } + + func setEngineAvailability(_ availability: AudioEngineAvailability) throws { + lock.withLock { + if availability.isInputAvailable { inputOpenings += 1 } + output = availability.isOutputAvailable + } + } + + func setRecordingAlwaysPreparedMode(_ value: Bool, audioProcessingOptions _: AudioProcessingOptions?) async throws { + let shouldBlock = lock.withLock { + if value, blocksPreparation { + blocksPreparation = false + return true + } + if !value, blocksStop { + blocksStop = false + return true + } + return false + } + if shouldBlock { + await withCheckedContinuation { continuation in + lock.withLock { self.continuation = continuation } + } + } + } + + func blockNextStop() { lock.withLock { blocksStop = true } } + + func releaseBlockedOperation() { + let continuation = lock.withLock { + let result = self.continuation + self.continuation = nil + return result + } + continuation?.resume() + } + + func isQuarantined() async -> Bool { lock.withLock { quarantined } } + func setGridDemandActive(_: Bool, epoch _: UInt64) async throws {} + func gridMediaDidQuiesce(epoch: UInt64) async { lock.withLock { reportedQuiescenceEpochs.append(epoch) } } + func resumeGridSession(epoch _: UInt64) async throws { lock.withLock { activations += 1 } } + func quarantineAfterMediaServicesReset() async { lock.withLock { quarantined = true } } +} + +private func waitUntil(_ condition: @Sendable () -> Bool) async throws { + let clock = ContinuousClock() + let deadline = clock.now.advanced(by: .seconds(2)) + while !condition() { + if clock.now >= deadline { throw IOSAudioSafetyTestTimeout() } + try await Task.sleep(for: .milliseconds(5)) + } +} + +private struct IOSAudioSafetyTestTimeout: Error {} +#endif diff --git a/apple/InlineKit/Tests/RealtimeV2Tests/UserInitiatedConnectionTests.swift b/apple/InlineKit/Tests/RealtimeV2Tests/UserInitiatedConnectionTests.swift index fe97b012e..e7b1067bf 100644 --- a/apple/InlineKit/Tests/RealtimeV2Tests/UserInitiatedConnectionTests.swift +++ b/apple/InlineKit/Tests/RealtimeV2Tests/UserInitiatedConnectionTests.swift @@ -71,6 +71,52 @@ struct UserInitiatedConnectionTests { } } + @Test("admitted media survives UIKit grace without claiming foreground and ends on release") + func mediaRetentionPastGrace() async { + let (manager, session) = fixture() + await manager.setMediaRetention(eligible: true, revision: 1) + #expect(await opens(manager)) + await manager.applicationBecameInactive(keepConnection: true) + let id = await manager.currentSnapshot().sessionID + let before = await session.stops + await manager.scheduledEventDidFire(.backgroundGraceExpired, sessionID: id) + #expect(await manager.currentSnapshot().state == .open) + #expect(await manager.currentSnapshot().constraints.appActive == false) + #expect(await session.stops == before) + await manager.setMediaRetention(eligible: false, revision: 2) + #expect(await manager.currentSnapshot().state == .backgroundSuspended) + #expect(await session.stops == before + 1) + await manager.shutdownForTesting() + } + + @Test("stale media eligibility cannot reopen a withdrawn media owner") + func staleMediaRetention() async { + let (manager, session) = fixture() + await manager.setMediaRetention(eligible: true, revision: 1) + #expect(await opens(manager)) + await manager.setMediaRetention(eligible: false, revision: 3) + #expect(await manager.currentSnapshot().state == .backgroundSuspended) + let starts = await session.starts + await manager.setMediaRetention(eligible: true, revision: 2) + #expect(await manager.currentSnapshot().state == .backgroundSuspended) + #expect(await session.starts == starts) + await manager.shutdownForTesting() + } + + @Test("auth loss clears media retention before a replacement authentication") + func authLossRetiresMedia() async { + let (manager, session) = fixture() + await manager.setMediaRetention(eligible: true, revision: 1) + #expect(await opens(manager)) + await manager.setAuthAvailable(false) + #expect(await manager.currentSnapshot().state != .open) + let starts = await session.starts + await manager.setAuthAvailable(true) + #expect(await manager.currentSnapshot().state != .open) + #expect(await session.starts == starts) + await manager.shutdownForTesting() + } + private func fixture() -> (ConnectionManager, IntentActivitySession) { let session = IntentActivitySession() return (ConnectionManager(session: session, constraints: .init( diff --git a/apple/InlineMac/App/AppDependencies.swift b/apple/InlineMac/App/AppDependencies.swift index 40dd061a8..fc50cd719 100644 --- a/apple/InlineMac/App/AppDependencies.swift +++ b/apple/InlineMac/App/AppDependencies.swift @@ -1,3 +1,4 @@ +import InlineGrid import Auth import Foundation import GRDB @@ -40,7 +41,11 @@ public struct AppDependencies { let session = tracedLaunchDependency("DependencySessionRefresher") { MainWindowSessionRefresher() } let unreadCounts = tracedLaunchDependency("DependencyUnreadCounts") { UnreadCountsModel.shared } let userSettings = tracedLaunchDependency("DependencyUserSettings") { INUserSettings.current } - let gridRuntime = tracedLaunchDependency("DependencyGridRuntime") { GridRuntime.shared } + let gridRuntime = tracedLaunchDependency("DependencyGridRuntime") { + let runtime = GridRuntime.shared + runtime.configurePlatformEffects(.macOS) + return runtime + } let commandBarCatalog = tracedLaunchDependency("DependencyCommandCatalog") { CommandBarCatalogService(database: AppDatabase.shared) } diff --git a/apple/InlineMac/Features/Grid/GridAvatarStateSync.swift b/apple/InlineMac/Features/Grid/GridAvatarStateSync.swift index 0018c68c7..c7a3d8f7a 100644 --- a/apple/InlineMac/Features/Grid/GridAvatarStateSync.swift +++ b/apple/InlineMac/Features/Grid/GridAvatarStateSync.swift @@ -1,101 +1,2 @@ -import Logger - -/// Serializes the transient state owned by the current Grid avatar. -/// -/// Rapid UI changes are coalesced to the latest value while one RPC is in -/// flight, so task scheduling cannot reorder mute/unmute updates on the wire. -/// Failed latest state is retained for a network/wake retry, but ownership -/// changes immediately discard work for a room this process no longer owns. -@MainActor -final class GridAvatarStateSync { - private struct Request: Equatable { - let roomID: Int64 - let microphoneEnabled: Bool - } - - private let api: GridRoomAPI - private let log = Log.scoped("GridAvatarStateSync") - private var ownedRoomID: Int64? - private var queued: Request? - private var failed: Request? - private var generation = 0 - private var syncTask: Task? - private var workerGeneration: Int? - - init(api: GridRoomAPI) { - self.api = api - } - - deinit { - syncTask?.cancel() - } - - func setOwnedRoom(_ roomID: Int64?) { - guard ownedRoomID != roomID else { return } - generation &+= 1 - ownedRoomID = roomID - if queued?.roomID != roomID { queued = nil } - if failed?.roomID != roomID { failed = nil } - syncTask?.cancel() - syncTask = nil - workerGeneration = nil - startIfNeeded() - } - - func submitMicrophoneState(roomID: Int64, enabled: Bool) { - guard ownedRoomID == roomID else { return } - queued = Request(roomID: roomID, microphoneEnabled: enabled) - failed = nil - startIfNeeded() - } - - func retryLatestFailure() { - guard queued == nil, let failed, failed.roomID == ownedRoomID else { return } - queued = failed - self.failed = nil - startIfNeeded() - } - - private func startIfNeeded() { - guard syncTask == nil, queued != nil else { return } - let workerGeneration = generation - self.workerGeneration = workerGeneration - syncTask = Task { [weak self] in - await self?.run(workerGeneration: workerGeneration) - } - } - - private func run(workerGeneration: Int) async { - while !Task.isCancelled, generation == workerGeneration, let request = queued { - queued = nil - do { - try await api.setMicrophoneEnabled( - request.microphoneEnabled, - roomID: request.roomID - ) - log.debug( - "GRID_TRACE phase=avatar_microphone_sent room=\(request.roomID) enabled=\(request.microphoneEnabled)" - ) - } catch { - guard !Task.isCancelled, generation == workerGeneration else { continue } - // Preserve only the latest still-relevant request. A newer UI intent - // already waiting in `queued` supersedes the failed value. - if ownedRoomID == request.roomID, queued == nil { - failed = request - } - log.warning( - "GRID_TRACE phase=avatar_microphone_send_failed room=\(request.roomID) enabled=\(request.microphoneEnabled)" - ) - PerformanceTrace.breadcrumb( - "Grid avatar microphone sync failed", - category: "Grid.State", - level: .warning - ) - } - } - guard self.workerGeneration == workerGeneration else { return } - syncTask = nil - self.workerGeneration = nil - startIfNeeded() - } -} +// Grid product state is shared by the iOS and macOS clients. +import InlineGrid diff --git a/apple/InlineMac/Features/Grid/GridControlsView.swift b/apple/InlineMac/Features/Grid/GridControlsView.swift index c1e154247..c306dd188 100644 --- a/apple/InlineMac/Features/Grid/GridControlsView.swift +++ b/apple/InlineMac/Features/Grid/GridControlsView.swift @@ -1,3 +1,4 @@ +import InlineGrid import AppKit import CoreGraphics import InlineKit diff --git a/apple/InlineMac/Features/Grid/GridHomePreferences.swift b/apple/InlineMac/Features/Grid/GridHomePreferences.swift index b7b7dc160..c7a3d8f7a 100644 --- a/apple/InlineMac/Features/Grid/GridHomePreferences.swift +++ b/apple/InlineMac/Features/Grid/GridHomePreferences.swift @@ -1,40 +1,2 @@ -import Foundation -import InlineProtocol - -/// Local navigation history used only as a stable tie-breaker after live Grid -/// activity. It does not own eligibility or presence; the server summary does. -@MainActor -final class GridHomePreferences { - private let defaults: UserDefaults - private let key = "grid.home.lastOpenedSpaces" - - init(defaults: UserDefaults = .standard) { - self.defaults = defaults - } - - func recordOpened(spaceID: Int64) { - var values = timestamps - values[String(spaceID)] = Date().timeIntervalSince1970 - defaults.set(values, forKey: key) - } - - func ordered(_ spaces: [GridHomeSpace]) -> [GridHomeSpace] { - let timestamps = timestamps - return spaces.sorted { lhs, rhs in - let lhsActive = lhs.activeAvatarCount > 0 - let rhsActive = rhs.activeAvatarCount > 0 - if lhsActive != rhsActive { return lhsActive } - if lhs.latestActivityAt != rhs.latestActivityAt { - return lhs.latestActivityAt > rhs.latestActivityAt - } - let lhsOpened = timestamps[String(lhs.spaceID)] ?? 0 - let rhsOpened = timestamps[String(rhs.spaceID)] ?? 0 - if lhsOpened != rhsOpened { return lhsOpened > rhsOpened } - return lhs.spaceID < rhs.spaceID - } - } - - private var timestamps: [String: Double] { - defaults.dictionary(forKey: key) as? [String: Double] ?? [:] - } -} +// Grid product state is shared by the iOS and macOS clients. +import InlineGrid diff --git a/apple/InlineMac/Features/Grid/GridMediaCoordinator.swift b/apple/InlineMac/Features/Grid/GridMediaCoordinator.swift index 802d68c52..c7a3d8f7a 100644 --- a/apple/InlineMac/Features/Grid/GridMediaCoordinator.swift +++ b/apple/InlineMac/Features/Grid/GridMediaCoordinator.swift @@ -1,505 +1,2 @@ -import Foundation -import InlineKit -import InlineRTC - -enum GridMediaCoordinatorEvent: Sendable { - case credentialsNeeded(GridMediaTarget) - case connected(GridMediaTarget, rtcConnectMilliseconds: Int?) - case screenShareContextChanged -} - -struct GridAutomaticMicrophoneChange: Equatable, Sendable { - let revision: Int - let previousEnabled: Bool - let appliedEnabled: Bool -} - -/// Main-actor bridge between room product state and the process-wide media -/// engine. This owns media intent, preference persistence, credentials, and -/// snapshot projection; `GridRoomService` never reaches into audio or RTC. -@MainActor -final class GridMediaCoordinator { - let presentation: GridMediaPresentation - - private let engine: InlineRTCSession - private let presentationController: GridMediaPresentationController - private let inputPreferences: AudioInputPreferenceStore - private let outputPreferences: AudioOutputPreferenceStore - private let defaults: UserDefaults - private var microphoneEnabled: Bool - private var microphoneIntentRevision = 0 - private var activeAutomaticMicrophoneChange: GridAutomaticMicrophoneChange? - private var autoUnmuteOnJoin: Bool - private var autoMuteWhenAlone: Bool - private var inputSelection: AudioInputSelection - private var outputSelection: AudioOutputSelection - private var outputVolume: Float = 1 - private var screenCaptureSource: InlineRTCScreenCaptureSource? - private var screenShareQualityProfile: InlineRTCScreenShareQualityProfile - private var screenCaptureSourceRefresh: GridScreenCaptureSourceRefresh? - private var target: GridMediaTarget? - private var connectedTarget: GridMediaTarget? - private var credentials: InlineRTCCredentials? - private var lastDemand: InlineRTCDemand? - private var playedConnectionSoundKeys = Set() - private var engineTask: Task? - private var subscribers: [UUID: AsyncStream.Continuation] = [:] - - private static let microphoneEnabledKey = "grid.microphoneEnabled" - private static let autoUnmuteOnJoinKey = "grid.autoUnmuteOnJoin" - private static let autoMuteWhenAloneKey = "grid.autoMuteWhenAlone" - private static let screenShareQualityProfileKey = "grid.screenShareQualityProfile" - - init( - engine: InlineRTCSession, - defaults: UserDefaults, - inputPreferences: AudioInputPreferenceStore, - outputPreferences: AudioOutputPreferenceStore - ) { - self.engine = engine - self.defaults = defaults - self.inputPreferences = inputPreferences - self.outputPreferences = outputPreferences - inputSelection = inputPreferences.selection - outputSelection = outputPreferences.selection - microphoneEnabled = defaults.bool(forKey: Self.microphoneEnabledKey) - autoUnmuteOnJoin = defaults.bool(forKey: Self.autoUnmuteOnJoinKey) - autoMuteWhenAlone = defaults.bool(forKey: Self.autoMuteWhenAloneKey) - screenShareQualityProfile = defaults.string(forKey: Self.screenShareQualityProfileKey) - .flatMap(InlineRTCScreenShareQualityProfile.init(rawValue:)) - ?? .automatic - let controller = GridMediaPresentationController( - microphoneEnabled: microphoneEnabled, - autoUnmuteOnJoin: autoUnmuteOnJoin, - autoMuteWhenAlone: autoMuteWhenAlone, - inputSelection: inputSelection, - screenShareQualityProfile: screenShareQualityProfile, - outputSelection: outputSelection - ) - presentationController = controller - presentation = controller.presentation - - engineTask = Task { [weak self, engine] in - await engine.start() - guard !Task.isCancelled else { return } - engine.refreshDevices() - self?.submitDemand() - let snapshots = await engine.subscribe() - for await snapshot in snapshots { - guard !Task.isCancelled else { return } - self?.apply(snapshot) - } - } - } - - deinit { - engineTask?.cancel() - subscribers.values.forEach { $0.finish() } - engine.requestShutdown() - } - - func subscribe() -> AsyncStream { - let id = UUID() - let stream = AsyncStream.makeStream( - of: GridMediaCoordinatorEvent.self, - // These are edge notifications backed by complete engine snapshots and - // target-scoped credential retry. A stalled observer must stay bounded. - bufferingPolicy: .bufferingNewest(16) - ) - stream.continuation.onTermination = { [weak self] _ in - Task { @MainActor in self?.subscribers.removeValue(forKey: id) } - } - subscribers[id] = stream.continuation - return stream.stream - } - - func setTarget(_ target: GridMediaTarget?) { - guard self.target != target else { - submitDemand() - return - } - screenCaptureSource = nil - presentationController.setSelectedScreenCaptureSource(nil) - presentationController.clearScreenCaptureError() - GridScreenShareOutlineCoordinator.shared.hide() - GridScreenShareWindowCoordinator.shared.closeAll() - self.target = target - if credentials?.target != target?.rtcSessionID { - credentials = nil - } - submitDemand() - } - - func accept(_ credentials: InlineRTCCredentials) -> Bool { - guard credentials.target == target?.rtcSessionID else { return false } - self.credentials = credentials - submitDemand() - return true - } - - func hasUsableCredentials(for target: GridMediaTarget) -> Bool { - credentials?.target == target.rtcSessionID - && (credentials?.expiresAt.timeIntervalSinceNow ?? 0) > 5 - } - - func isConnected(to target: GridMediaTarget) -> Bool { - connectedTarget == target - } - - func invalidateCredentials(for target: GridMediaTarget) { - guard credentials?.target == target.rtcSessionID else { return } - credentials = nil - submitDemand() - } - - func clearCredentials() { - guard credentials != nil else { return } - credentials = nil - submitDemand() - } - - func toggleMicrophone() -> Bool { - setMicrophoneEnabled(!microphoneEnabled) - } - - @discardableResult - func setMicrophoneEnabled(_ enabled: Bool) -> Bool { - guard microphoneEnabled != enabled else { return enabled } - if enabled, presentation.microphonePermission != .authorized { - engine.requestMicrophonePermission() - } - activeAutomaticMicrophoneChange = nil - microphoneIntentRevision &+= 1 - microphoneEnabled = enabled - presentationController.setMicrophoneEnabled(enabled) - defaults.set(enabled, forKey: Self.microphoneEnabledKey) - submitDemand() - return enabled - } - - func applyAutoUnmuteOnJoin() -> GridAutomaticMicrophoneChange? { - guard autoUnmuteOnJoin else { return nil } - if microphoneEnabled { - return activeAutomaticMicrophoneChange - } - let previousEnabled = microphoneEnabled - setMicrophoneEnabled(true) - let change = GridAutomaticMicrophoneChange( - revision: microphoneIntentRevision, - previousEnabled: previousEnabled, - appliedEnabled: true - ) - activeAutomaticMicrophoneChange = change - return change - } - - @discardableResult - func restoreAutomaticMicrophoneChangeIfCurrent( - _ change: GridAutomaticMicrophoneChange? - ) -> Bool { - guard let change, - activeAutomaticMicrophoneChange == change, - microphoneIntentRevision == change.revision, - microphoneEnabled == change.appliedEnabled - else { return false } - setMicrophoneEnabled(change.previousEnabled) - return true - } - - func commitAutomaticMicrophoneChange(_ change: GridAutomaticMicrophoneChange?) { - guard activeAutomaticMicrophoneChange == change else { return } - activeAutomaticMicrophoneChange = nil - } - - func setAutoUnmuteOnJoin(_ enabled: Bool) { - guard autoUnmuteOnJoin != enabled else { return } - autoUnmuteOnJoin = enabled - defaults.set(enabled, forKey: Self.autoUnmuteOnJoinKey) - presentationController.setAutoUnmuteOnJoin(enabled) - } - - func setAutoMuteWhenAlone(_ enabled: Bool) { - guard autoMuteWhenAlone != enabled else { return } - autoMuteWhenAlone = enabled - defaults.set(enabled, forKey: Self.autoMuteWhenAloneKey) - presentationController.setAutoMuteWhenAlone(enabled) - } - - func requestMicrophonePermission() { - engine.requestMicrophonePermission() - } - - func setInput(_ selection: AudioInputSelection) { - if inputSelection == selection { - if presentation.isFallingBackToAutomaticInput || presentation.audioState.isFailed { - engine.retryInput(selection) - } - return - } - inputSelection = selection - inputPreferences.setSelection(selection) - presentationController.setDesiredInputSelection(selection) - submitDemand() - } - - func setOutput(_ selection: AudioOutputSelection) { - if outputSelection == selection { - if presentation.isFallingBackToAutomaticOutput || presentation.audioState.isFailed { - engine.retryOutput(selection) - } - return - } - outputSelection = selection - outputPreferences.setSelection(selection) - presentationController.setDesiredOutputSelection(selection) - submitDemand() - } - - func setOutputVolume(_ volume: Float) { - outputVolume = min(max(volume, 0), 1) - presentationController.setDesiredOutputVolume(outputVolume) - submitDemand() - } - - func refreshInputDevices() { - engine.refreshDevices() - } - - func refreshOutputDevices() { - engine.refreshDevices() - } - - @discardableResult - func refreshScreenCaptureSources() async -> [InlineRTCScreenCaptureSource] { - if let refresh = screenCaptureSourceRefresh { - return finishScreenCaptureSourceRefresh( - await refresh.task.value, - id: refresh.id - ) - } - - let refresh = GridScreenCaptureSourceRefresh( - id: UUID(), - task: Task { [engine] in - do { - let sources = try await engine.screenCaptureSources() - guard !sources.isEmpty else { - return .failure("No displays are available") - } - return .success(sources) - } catch { - return .failure(error.localizedDescription) - } - } - ) - screenCaptureSourceRefresh = refresh - presentationController.setRefreshingScreenCaptureSources(true) - return finishScreenCaptureSourceRefresh( - await refresh.task.value, - id: refresh.id - ) - } - - func startScreenSharing(displayID: UInt32?) async { - guard let expectedTarget = target else { return } - let sources = await refreshScreenCaptureSources() - guard target == expectedTarget else { return } - let source = displayID.flatMap { displayID in - sources.first { $0.displayID == displayID } - } ?? sources.first - guard let source else { return } - startScreenSharing(source) - } - - func startScreenSharing(_ source: InlineRTCScreenCaptureSource) { - guard target != nil else { return } - screenCaptureSource = source - presentationController.setSelectedScreenCaptureSource(source) - presentationController.clearScreenCaptureError() - submitDemand() - } - - func stopScreenSharing() { - screenCaptureSource = nil - presentationController.setSelectedScreenCaptureSource(nil) - presentationController.clearScreenCaptureError() - GridScreenShareOutlineCoordinator.shared.hide() - submitDemand() - } - - func setScreenShareQualityProfile(_ profile: InlineRTCScreenShareQualityProfile) { - guard screenShareQualityProfile != profile else { return } - screenShareQualityProfile = profile - defaults.set(profile.rawValue, forKey: Self.screenShareQualityProfileKey) - presentationController.setScreenShareQualityProfile(profile) - submitDemand() - } - - func retryAudio() { - engine.retryAudio() - } - - func networkBecameAvailable() { - engine.networkBecameAvailable() - } - - func applicationDidWake() { - engine.applicationDidWake() - } - - func shutdown() async { - target = nil - connectedTarget = nil - credentials = nil - screenCaptureSource = nil - screenCaptureSourceRefresh?.task.cancel() - screenCaptureSourceRefresh = nil - presentationController.setRefreshingScreenCaptureSources(false) - presentationController.setSelectedScreenCaptureSource(nil) - presentationController.clearScreenCaptureError() - GridScreenShareOutlineCoordinator.shared.hide() - GridScreenShareWindowCoordinator.shared.closeAll() - lastDemand = nil - engine.setDemand(InlineRTCDemand()) - _ = await engine.shutdown() - } - - var isMicrophoneEnabled: Bool { microphoneEnabled } - var shouldAutoMuteWhenAlone: Bool { autoMuteWhenAlone } - - private func submitDemand() { - let rtcTarget = target?.rtcSessionID - if credentials?.target != rtcTarget { - credentials = nil - } - let demand = InlineRTCDemand( - target: rtcTarget, - credentials: credentials, - microphoneEnabled: microphoneEnabled, - screenCaptureSource: screenCaptureSource, - screenShareQualityProfile: screenShareQualityProfile, - input: inputSelection, - output: outputSelection, - outputVolume: outputVolume - ) - guard demand != lastDemand else { return } - lastDemand = demand - engine.setDemand(demand) - } - - private func finishScreenCaptureSourceRefresh( - _ result: GridScreenCaptureSourceRefreshResult, - id: UUID - ) -> [InlineRTCScreenCaptureSource] { - let sources = result.sources - guard screenCaptureSourceRefresh?.id == id else { return sources } - screenCaptureSourceRefresh = nil - presentationController.setRefreshingScreenCaptureSources(false) - switch result { - case let .success(sources): - presentationController.applyScreenCaptureSources(.success(sources)) - case let .failure(message): - presentationController.applyScreenCaptureSources( - .failure(GridScreenCaptureSourceRefreshError(message: message)) - ) - } - reconcileScreenShareOutline() - return sources - } - - private func apply(_ snapshot: InlineRTCState) { - presentationController.apply(audio: snapshot.audio) - if let devices = snapshot.devices { - presentationController.apply(devices: devices) - } - if let outputDevices = snapshot.outputDevices { - presentationController.apply(outputDevices: outputDevices) - } - - let wasConnected = presentation.connectionState == .connected - presentationController.apply(rtc: snapshot.rtc) - reconcileScreenShareOutline() - broadcast(.screenShareContextChanged) - if case .failed = snapshot.rtc.screenShareState, screenCaptureSource != nil { - screenCaptureSource = nil - presentationController.setSelectedScreenCaptureSource(nil) - submitDemand() - } - if case let .connected(sessionID) = snapshot.rtc.state, - let target, - target.rtcSessionID == sessionID { - connectedTarget = target - } else { - connectedTarget = nil - } - if !wasConnected, - presentation.connectionState == .connected, - let sessionID = snapshot.rtc.target, - let target, - target.rtcSessionID == sessionID { - let soundKey = sessionID.rawValue - if playedConnectionSoundKeys.count >= 128 { - playedConnectionSoundKeys.removeAll(keepingCapacity: true) - } - if playedConnectionSoundKeys.insert(soundKey).inserted { - GridSoundEffects.shared.play(.connected) - } - broadcast(.connected(target, rtcConnectMilliseconds: snapshot.rtc.lastConnectMilliseconds)) - } - if case let .waitingForCredentials(sessionID) = snapshot.rtc.state, - let target, - target.rtcSessionID == sessionID { - broadcast(.credentialsNeeded(target)) - } - } - - private func broadcast(_ event: GridMediaCoordinatorEvent) { - subscribers.values.forEach { $0.yield(event) } - } - - private func reconcileScreenShareOutline() { - guard let localShare = presentation.screenShares.first(where: \.isLocal), - let sourceID = localShare.captureSourceID - else { - GridScreenShareOutlineCoordinator.shared.hide() - return - } - let sources = presentation.screenCaptureSources - + [presentation.selectedScreenCaptureSource].compactMap { $0 } - guard let source = sources.first(where: { $0.id == sourceID }) else { - GridScreenShareOutlineCoordinator.shared.hide() - return - } - GridScreenShareOutlineCoordinator.shared.show(for: source) - } -} - -private struct GridScreenCaptureSourceRefresh { - let id: UUID - let task: Task -} - -private enum GridScreenCaptureSourceRefreshResult: Sendable { - case success([InlineRTCScreenCaptureSource]) - case failure(String) - - var sources: [InlineRTCScreenCaptureSource] { - switch self { - case let .success(sources): sources - case .failure: [] - } - } -} - -private struct GridScreenCaptureSourceRefreshError: LocalizedError, Sendable { - let message: String - - var errorDescription: String? { message } -} - -private extension InlineRTCAudioState { - var isFailed: Bool { - if case .failed = self { return true } - return false - } -} +// Grid product state is shared by the iOS and macOS clients. +import InlineGrid diff --git a/apple/InlineMac/Features/Grid/GridMediaPresentation.swift b/apple/InlineMac/Features/Grid/GridMediaPresentation.swift index 7097a7b1f..c7a3d8f7a 100644 --- a/apple/InlineMac/Features/Grid/GridMediaPresentation.swift +++ b/apple/InlineMac/Features/Grid/GridMediaPresentation.swift @@ -1,270 +1,2 @@ -import Foundation -import InlineKit -import InlineRTC -import Observation - -enum GridMediaConnectionStatus: Equatable, Sendable { - case disconnected - case connecting - case connected - case failed -} - -enum GridScreenCaptureIssue: Equatable, Sendable { - case sourceDiscovery - case sharing -} - -@MainActor -@Observable -final class GridMediaPresentation { - fileprivate(set) var connectionState: GridMediaConnectionStatus = .disconnected - fileprivate(set) var audioState: InlineRTCAudioState = .cold - fileprivate(set) var isMicrophoneEnabled: Bool - fileprivate(set) var autoUnmuteOnJoin: Bool - fileprivate(set) var autoMuteWhenAlone: Bool - fileprivate(set) var inputDevices: [AudioInputDeviceDescriptor] = [] - fileprivate(set) var automaticInputDeviceName = "System Default" - fileprivate(set) var activeInputDeviceID: String? - fileprivate(set) var inputSelection: AudioInputSelection - fileprivate(set) var isFallingBackToAutomaticInput = false - fileprivate(set) var outputDevices: [AudioOutputDeviceDescriptor] = [] - fileprivate(set) var automaticOutputDeviceName = "System Default" - fileprivate(set) var activeOutputDeviceID: String? - fileprivate(set) var outputSelection: AudioOutputSelection - fileprivate(set) var isFallingBackToAutomaticOutput = false - fileprivate(set) var participantAudioLevels: [String: Float] = [:] - fileprivate(set) var participantScreenShareIntents: [String: Bool] = [:] - fileprivate(set) var connectedParticipantIdentities = Set() - fileprivate(set) var hasParticipantMediaSnapshot = false - fileprivate(set) var reconnectCount = 0 - fileprivate(set) var recoveryAttempt = 0 - fileprivate(set) var lastConnectDurationMilliseconds: Int? - fileprivate(set) var lastDisconnectDurationMilliseconds: Int? - fileprivate(set) var lastConnectionError: String? - fileprivate(set) var isMicrophoneCapturePrepared = false - fileprivate(set) var microphonePermission: InlineRTCMicrophonePermission = .notDetermined - fileprivate(set) var microphonePublicationState: InlineRTCMicrophoneState = .notRequested - fileprivate(set) var localAudioFlowState: InlineRTCAudioFlowState = .unknown - fileprivate(set) var remoteAudioFlowStates: [String: InlineRTCAudioFlowState] = [:] - fileprivate(set) var abandonedProviderOperationCount = 0 - fileprivate(set) var providerCircuitOpen = false - fileprivate(set) var outputVolume: Float - fileprivate(set) var screenCaptureSources: [InlineRTCScreenCaptureSource] = [] - fileprivate(set) var selectedScreenCaptureSource: InlineRTCScreenCaptureSource? - fileprivate(set) var screenShareQualityProfile: InlineRTCScreenShareQualityProfile - fileprivate(set) var screenShareEpisodeID: UInt64 = 0 - fileprivate(set) var screenShareState: InlineRTCScreenShareState = .off - fileprivate(set) var screenShares: [InlineRTCScreenShare] = [] - fileprivate(set) var isRefreshingScreenCaptureSources = false - fileprivate(set) var screenCaptureError: String? - fileprivate(set) var screenCaptureIssue: GridScreenCaptureIssue? - - fileprivate init( - microphoneEnabled: Bool, - autoUnmuteOnJoin: Bool, - autoMuteWhenAlone: Bool, - inputSelection: AudioInputSelection, - screenShareQualityProfile: InlineRTCScreenShareQualityProfile = .automatic, - outputSelection: AudioOutputSelection = .automatic, - outputVolume: Float = 1 - ) { - isMicrophoneEnabled = microphoneEnabled - self.autoUnmuteOnJoin = autoUnmuteOnJoin - self.autoMuteWhenAlone = autoMuteWhenAlone - self.inputSelection = inputSelection - self.screenShareQualityProfile = screenShareQualityProfile - self.outputSelection = outputSelection - self.outputVolume = min(max(outputVolume, 0), 1) - } - - var isUsingAutomaticInput: Bool { - inputSelection == .automatic - } - - var hasRemoteAudioFlowFailure: Bool { - remoteAudioFlowStates.values.contains(.missing) - } - - var isScreenSharing: Bool { - screenShares.contains(where: \.isLocal) - } - - var isScreenShareRequested: Bool { - selectedScreenCaptureSource != nil - } - - func prefersInputDevice(_ device: AudioInputDeviceDescriptor) -> Bool { - inputSelection.matches(device, among: inputDevices) - } - - func prefersOutputDevice(_ device: AudioOutputDeviceDescriptor) -> Bool { - outputSelection.matches(device, among: outputDevices) - } - -} - -/// The sole writer for `GridMediaPresentation`. Views only receive the -/// presentation reference and therefore cannot feed state back into its owner. -@MainActor -final class GridMediaPresentationController { - let presentation: GridMediaPresentation - - init( - microphoneEnabled: Bool, - autoUnmuteOnJoin: Bool, - autoMuteWhenAlone: Bool, - inputSelection: AudioInputSelection, - screenShareQualityProfile: InlineRTCScreenShareQualityProfile = .automatic, - outputSelection: AudioOutputSelection = .automatic, - outputVolume: Float = 1 - ) { - presentation = GridMediaPresentation( - microphoneEnabled: microphoneEnabled, - autoUnmuteOnJoin: autoUnmuteOnJoin, - autoMuteWhenAlone: autoMuteWhenAlone, - inputSelection: inputSelection, - screenShareQualityProfile: screenShareQualityProfile, - outputSelection: outputSelection, - outputVolume: outputVolume - ) - } - - func setMicrophoneEnabled(_ enabled: Bool) { - presentation.isMicrophoneEnabled = enabled - } - - func setAutoUnmuteOnJoin(_ enabled: Bool) { - presentation.autoUnmuteOnJoin = enabled - } - - func setAutoMuteWhenAlone(_ enabled: Bool) { - presentation.autoMuteWhenAlone = enabled - } - - func setDesiredInputSelection(_ selection: AudioInputSelection) { - presentation.inputSelection = selection - } - - func setDesiredOutputSelection(_ selection: AudioOutputSelection) { - presentation.outputSelection = selection - } - - func setDesiredOutputVolume(_ volume: Float) { - presentation.outputVolume = min(max(volume, 0), 1) - } - - func setSelectedScreenCaptureSource(_ source: InlineRTCScreenCaptureSource?) { - if presentation.selectedScreenCaptureSource == nil, source != nil { - presentation.screenShareEpisodeID &+= 1 - } - presentation.selectedScreenCaptureSource = source - } - - func setScreenShareQualityProfile(_ profile: InlineRTCScreenShareQualityProfile) { - presentation.screenShareQualityProfile = profile - } - - func clearScreenCaptureError() { - presentation.screenCaptureError = nil - presentation.screenCaptureIssue = nil - } - - func setRefreshingScreenCaptureSources(_ isRefreshing: Bool) { - presentation.isRefreshingScreenCaptureSources = isRefreshing - } - - func applyScreenCaptureSources( - _ result: Result<[InlineRTCScreenCaptureSource], Error> - ) { - switch result { - case let .success(sources): - presentation.screenCaptureSources = sources - presentation.screenCaptureError = nil - presentation.screenCaptureIssue = nil - case let .failure(error): - presentation.screenCaptureSources = [] - presentation.screenCaptureError = error.localizedDescription - presentation.screenCaptureIssue = .sourceDiscovery - } - } - - func apply(audio snapshot: InlineRTCAudioSnapshot) { - presentation.audioState = snapshot.state - presentation.isMicrophoneCapturePrepared = snapshot.isPrepared - presentation.microphonePermission = snapshot.microphonePermission - if let resolvedInput = snapshot.input { - presentation.activeInputDeviceID = resolvedInput.activeDeviceID - presentation.isFallingBackToAutomaticInput = resolvedInput.isFallingBackToAutomatic - } - if let resolvedOutput = snapshot.output { - presentation.activeOutputDeviceID = resolvedOutput.activeDeviceID - presentation.isFallingBackToAutomaticOutput = resolvedOutput.isFallingBackToAutomatic - } - } - - func apply(rtc snapshot: InlineRTCConnectionSnapshot) { - presentation.connectionState = switch snapshot.state { - case .idle: .disconnected - case .connected: .connected - case .failed: .failed - default: .connecting - } - presentation.participantAudioLevels = Dictionary( - uniqueKeysWithValues: snapshot.participants.map { - ($0.identity, $0.isSpeaking ? $0.audioLevel : 0) - } - ) - presentation.participantScreenShareIntents = Dictionary( - uniqueKeysWithValues: snapshot.participants.compactMap { participant in - participant.screenShareIntent.map { (participant.identity, $0) } - } - ) - presentation.connectedParticipantIdentities = Set(snapshot.participants.map(\.identity)) - presentation.hasParticipantMediaSnapshot = snapshot.hasParticipantMediaSnapshot - presentation.reconnectCount = snapshot.reconnectCount - presentation.recoveryAttempt = snapshot.recoveryAttempt - presentation.lastConnectDurationMilliseconds = snapshot.lastConnectMilliseconds - presentation.lastDisconnectDurationMilliseconds = snapshot.lastDisconnectMilliseconds - presentation.lastConnectionError = snapshot.lastError - presentation.microphonePublicationState = snapshot.microphonePublicationState - presentation.localAudioFlowState = snapshot.localAudioFlowState - presentation.remoteAudioFlowStates = snapshot.remoteAudioFlowStates - presentation.screenShareState = snapshot.screenShareState - presentation.screenShares = snapshot.screenShares - if case let .failed(message) = snapshot.screenShareState, - message != "Screen sharing stopped" { - presentation.screenCaptureError = message - presentation.screenCaptureIssue = .sharing - } else if case let .failed(message) = snapshot.screenShareState, - message == "Screen sharing stopped" { - presentation.screenCaptureError = nil - presentation.screenCaptureIssue = nil - } else if snapshot.screenShareState == .off, - presentation.connectionState == .connected, - presentation.screenCaptureError == "Screen sharing did not stop" { - // A failed Stop is recoverable through room reconstruction. Keep the - // warning visible until the fresh connected projection proves that no - // local publication remains, then retire the transient recovery notice. - presentation.screenCaptureError = nil - presentation.screenCaptureIssue = nil - } - presentation.abandonedProviderOperationCount = snapshot.abandonedProviderOperationCount - presentation.providerCircuitOpen = snapshot.providerCircuitOpen - } - - func apply(devices snapshot: AudioInputDeviceSnapshot) { - presentation.automaticInputDeviceName = snapshot.automaticDeviceName - presentation.inputDevices = snapshot.devices - presentation.activeInputDeviceID = snapshot.resolvedInput.activeDeviceID - presentation.isFallingBackToAutomaticInput = snapshot.resolvedInput.isFallingBackToAutomatic - } - - func apply(outputDevices snapshot: AudioOutputDeviceSnapshot) { - presentation.automaticOutputDeviceName = snapshot.automaticDeviceName - presentation.outputDevices = snapshot.devices - presentation.activeOutputDeviceID = snapshot.resolvedOutput.activeDeviceID - presentation.isFallingBackToAutomaticOutput = - snapshot.resolvedOutput.isFallingBackToAutomatic - } -} +// Grid product state is shared by the iOS and macOS clients. +import InlineGrid diff --git a/apple/InlineMac/Features/Grid/GridMediaTarget.swift b/apple/InlineMac/Features/Grid/GridMediaTarget.swift index c22d334c3..c7a3d8f7a 100644 --- a/apple/InlineMac/Features/Grid/GridMediaTarget.swift +++ b/apple/InlineMac/Features/Grid/GridMediaTarget.swift @@ -1,16 +1,2 @@ -import Foundation -import InlineRTC - -/// Grid's product identity for its current media session. -/// -/// Space, room, and generation remain app-owned. InlineRTC receives only the -/// stable opaque identity produced below and never interprets these fields. -struct GridMediaTarget: Equatable, Hashable, Sendable { - let spaceID: Int64 - let roomID: Int64 - let generation: Int32 - - var rtcSessionID: InlineRTCSessionID { - InlineRTCSessionID("grid:\(spaceID):\(roomID):\(generation)") - } -} +// Grid product state is shared by the iOS and macOS clients. +import InlineGrid diff --git a/apple/InlineMac/Features/Grid/GridMembershipSync.swift b/apple/InlineMac/Features/Grid/GridMembershipSync.swift index b5ec8f894..c7a3d8f7a 100644 --- a/apple/InlineMac/Features/Grid/GridMembershipSync.swift +++ b/apple/InlineMac/Features/Grid/GridMembershipSync.swift @@ -1,144 +1,2 @@ -import Foundation -import InlineProtocol -import Logger - -struct GridMembershipOperation: Equatable, Sendable { - enum Kind: Equatable, Sendable { - case create - case join(roomID: Int64) - case leave(roomID: Int64) - } - - let kind: Kind - let spaceID: Int64 - let microphoneEnabled: Bool - let automaticMicrophoneChange: GridAutomaticMicrophoneChange? - let accessRevision: Int - let revision: Int - let startedAt: Date -} - -enum GridMembershipSyncEvent: Sendable { - case created(GridMembershipOperation, GridRoomMutationResult) - case joined(GridMembershipOperation, GridRoomMutationResult) - case left(GridMembershipOperation, [InlineProtocol.Grid]) - case failed(GridMembershipOperation, message: String) -} - -/// The sole remote executor for Grid avatar membership changes. -/// -/// The UI applies product state optimistically, then submits operations here. -/// A single retained worker preserves create/join/leave order at the server even -/// during rapid interaction. Events carry revisions so the room service can -/// ignore responses superseded by newer local intent without losing wire order. -@MainActor -final class GridMembershipSync { - private struct QueuedOperation { - let generation: Int - let operation: GridMembershipOperation - } - - private let api: GridRoomAPI - private let log = Log.scoped("GridMembershipSync") - private var generation = 0 - private var queue: [QueuedOperation] = [] - private var workerTask: Task? - private var workerGeneration: Int? - private var subscribers: [UUID: AsyncStream.Continuation] = [:] - - init(api: GridRoomAPI) { - self.api = api - } - - deinit { - workerTask?.cancel() - subscribers.values.forEach { $0.finish() } - } - - func subscribe() -> AsyncStream { - let id = UUID() - let stream = AsyncStream.makeStream( - of: GridMembershipSyncEvent.self, - // Results are revisioned and every success carries authoritative Grid - // state. A newer result supersedes an older event for a stalled consumer. - bufferingPolicy: .bufferingNewest(64) - ) - stream.continuation.onTermination = { [weak self] _ in - Task { @MainActor in self?.subscribers.removeValue(forKey: id) } - } - subscribers[id] = stream.continuation - return stream.stream - } - - func submit(_ operation: GridMembershipOperation) { - queue.append(QueuedOperation(generation: generation, operation: operation)) - startIfNeeded() - } - - /// Invalidates queued work during logout. An in-flight RPC may still finish - /// at the transport, but its event is discarded and it cannot retain the - /// worker slot needed by a later authenticated generation. - func reset() { - generation &+= 1 - queue.removeAll() - workerTask?.cancel() - workerTask = nil - workerGeneration = nil - } - - private func startIfNeeded() { - guard workerTask == nil, !queue.isEmpty else { return } - let workerGeneration = generation - self.workerGeneration = workerGeneration - workerTask = Task { [weak self] in - await self?.run(workerGeneration: workerGeneration) - } - } - - private func run(workerGeneration: Int) async { - while !Task.isCancelled, generation == workerGeneration, !queue.isEmpty { - let queued = queue.removeFirst() - guard queued.generation == workerGeneration else { continue } - let operation = queued.operation - do { - let event: GridMembershipSyncEvent = switch operation.kind { - case .create: - .created( - operation, - try await api.createRoom( - spaceID: operation.spaceID, - microphoneEnabled: operation.microphoneEnabled - ) - ) - case let .join(roomID): - .joined( - operation, - try await api.joinRoom( - roomID: roomID, - microphoneEnabled: operation.microphoneEnabled - ) - ) - case let .leave(roomID): - .left(operation, try await api.leaveRoom(roomID: roomID)) - } - guard !Task.isCancelled, workerGeneration == generation else { continue } - broadcast(event) - } catch { - guard !Task.isCancelled, workerGeneration == generation else { continue } - log.error( - "GRID_TRACE phase=membership_rpc_failed revision=\(operation.revision)", - error: error - ) - broadcast(.failed(operation, message: String(describing: error))) - } - } - guard self.workerGeneration == workerGeneration else { return } - workerTask = nil - self.workerGeneration = nil - startIfNeeded() - } - - private func broadcast(_ event: GridMembershipSyncEvent) { - subscribers.values.forEach { $0.yield(event) } - } -} +// Grid product state is shared by the iOS and macOS clients. +import InlineGrid diff --git a/apple/InlineMac/Features/Grid/GridNetworkMonitor.swift b/apple/InlineMac/Features/Grid/GridNetworkMonitor.swift index cd9bbbc1b..c7a3d8f7a 100644 --- a/apple/InlineMac/Features/Grid/GridNetworkMonitor.swift +++ b/apple/InlineMac/Features/Grid/GridNetworkMonitor.swift @@ -1,78 +1,2 @@ -import Foundation -import Network - -struct GridNetworkPathSnapshot: Equatable, Sendable { - let available: Bool - let usesWiFi: Bool - let usesWiredEthernet: Bool - let usesCellular: Bool - let constrained: Bool - let expensive: Bool - - init(path: NWPath) { - available = path.status == .satisfied - usesWiFi = path.usesInterfaceType(.wifi) - usesWiredEthernet = path.usesInterfaceType(.wiredEthernet) - usesCellular = path.usesInterfaceType(.cellular) - constrained = path.isConstrained - expensive = path.isExpensive - } - - var interfaceDescription: String { - if usesWiredEthernet { return "ethernet" } - if usesWiFi { return "wifi" } - if usesCellular { return "cellular" } - return "other" - } -} - -/// Owns `NWPathMonitor` and vends independent watch streams. Consumers never -/// compete for values from one shared `AsyncStream` iterator. -@MainActor -final class GridNetworkMonitor { - private let monitor = NWPathMonitor() - private let queue = DispatchQueue(label: "Grid.NetworkMonitor") - private var subscribers: [UUID: AsyncStream.Continuation] = [:] - private var latest: GridNetworkPathSnapshot? - - init() { - monitor.pathUpdateHandler = { [weak self] path in - let snapshot = GridNetworkPathSnapshot(path: path) - Task { @MainActor [weak self] in - self?.broadcast(snapshot) - } - } - monitor.start(queue: queue) - } - - deinit { - monitor.cancel() - subscribers.values.forEach { $0.finish() } - } - - func subscribe() -> AsyncStream { - let id = UUID() - let stream = AsyncStream.makeStream( - of: GridNetworkPathSnapshot.self, - bufferingPolicy: .bufferingNewest(1) - ) - stream.continuation.onTermination = { [weak self] _ in - Task { @MainActor in self?.subscribers.removeValue(forKey: id) } - } - subscribers[id] = stream.continuation - if let latest { - stream.continuation.yield(latest) - } - return stream.stream - } - - private func broadcast(_ snapshot: GridNetworkPathSnapshot) { - // Realtime and LiveKit observe their own socket/path transitions, including - // same-interface Wi-Fi changes. Grid only needs a separate recovery epoch - // when this coarse availability/interface state actually changes; emitting - // identical callbacks can otherwise create a refresh/retry feedback loop. - guard latest != snapshot else { return } - latest = snapshot - subscribers.values.forEach { $0.yield(snapshot) } - } -} +// Grid product state is shared by the iOS and macOS clients. +import InlineGrid diff --git a/apple/InlineMac/Features/Grid/GridPlatformEffects.swift b/apple/InlineMac/Features/Grid/GridPlatformEffects.swift new file mode 100644 index 000000000..62a30ba5d --- /dev/null +++ b/apple/InlineMac/Features/Grid/GridPlatformEffects.swift @@ -0,0 +1,70 @@ +import AppKit +import CoreGraphics +import InlineGrid +import InlineKit + +extension GridPlatformEffects { + @MainActor + static var macOS: GridPlatformEffects { + var effects = GridPlatformEffects() + effects.playSound = { sound in + switch sound { + case .join: GridSoundEffects.shared.play(.join) + case .leave: GridSoundEffects.shared.play(.leave) + case .connected: GridSoundEffects.shared.play(.connected) + } + } + effects.displayIDUnderPointer = { + let mouseLocation = NSEvent.mouseLocation + guard let screen = NSScreen.screens.first(where: { + NSMouseInRect(mouseLocation, $0.frame, false) + }), + let screenNumber = screen.deviceDescription[ + NSDeviceDescriptionKey("NSScreenNumber") + ] as? NSNumber + else { return CGMainDisplayID() } + return screenNumber.uint32Value + } + effects.updateScreenShareOutline = { source in + if let source { + GridScreenShareOutlineCoordinator.shared.show(for: source) + } else { + GridScreenShareOutlineCoordinator.shared.hide() + } + } + effects.closeScreenShares = { GridScreenShareWindowCoordinator.shared.closeAll() } + effects.openScreenShare = { user, share, service in + GridScreenShareWindowCoordinator.shared.open(user: user, share: share, store: service) + } + effects.presentScreenShareChange = { notice, service in + let displayName = InlineKit.User(from: notice.user).displayName + if notice.started { + ToastCenter.shared.showSuccess( + "\(displayName) started sharing their screen", + actionTitle: "View" + ) { [weak service] in + guard let service, + let grid = service.grid(spaceID: notice.spaceID), + let room = grid.rooms.first(where: { $0.id == notice.roomID }), + let avatar = room.avatars.first(where: { $0.user.id == notice.user.id }) + else { return } + service.openScreenShare(for: avatar) + } + } else { + ToastCenter.shared.showInfo("\(displayName) stopped sharing their screen") + } + Task { + await MacNotifications.shared.showGridScreenShareNotification( + displayName: displayName, + started: notice.started, + spaceID: notice.spaceID, + roomID: notice.roomID, + userID: notice.user.id, + participantIdentity: notice.participantIdentity + ) + } + } + effects.showError = { ToastCenter.shared.showError($0) } + return effects + } +} diff --git a/apple/InlineMac/Features/Grid/GridRoomAPI.swift b/apple/InlineMac/Features/Grid/GridRoomAPI.swift index eea3527dc..c7a3d8f7a 100644 --- a/apple/InlineMac/Features/Grid/GridRoomAPI.swift +++ b/apple/InlineMac/Features/Grid/GridRoomAPI.swift @@ -1,164 +1,2 @@ -import InlineKit -import InlineProtocol -import RealtimeV2 - -struct GridRoomMutationResult: Sendable { - let grids: [InlineProtocol.Grid] - let credentials: GridConnectionCredentials? -} - -/// The typed server boundary for Grid rooms. -/// -/// Product state and optimistic UI never inspect RPC oneofs directly. Keeping -/// validation here makes every room operation return one predictable shape. -@MainActor -final class GridRoomAPI { - private let realtime: RealtimeV2 - - init(realtime: RealtimeV2) { - self.realtime = realtime - } - - func settings(spaceID: Int64) async throws -> SpaceSettings { - let result = try await realtime.send(.getSpaceSettings(spaceID: spaceID)) - guard case let .getSpaceSettings(response)? = result, response.hasSettings else { - throw GridRoomAPIError.invalidResponse - } - return response.settings - } - - func setEnabled(_ enabled: Bool, spaceID: Int64) async throws -> SpaceSettings { - let result = try await realtime.send(.toggleSpaceGrid(spaceID: spaceID, enabled: enabled)) - guard case let .toggleSpaceGrid(response)? = result, response.hasSettings else { - throw GridRoomAPIError.invalidResponse - } - return response.settings - } - - func grid(spaceID: Int64) async throws -> InlineProtocol.Grid { - let result = try await realtime.send(.getGrid(spaceID: spaceID)) - guard case let .getGrid(response)? = result, response.hasGrid else { - throw GridRoomAPIError.invalidResponse - } - return response.grid - } - - func home() async throws -> [GridHomeSpace] { - let result = try await realtime.send(.getGridHome()) - guard case let .getGridHome(response)? = result else { - throw GridRoomAPIError.invalidResponse - } - return response.spaces - } - - func openRoomThread(roomID: Int64, membershipID: String) async throws -> Int64 { - let result = try await realtime.send(.openGridThread(roomID: roomID, membershipID: membershipID)) - guard case let .openGridThread(response)? = result, response.chatID > 0 else { - throw GridRoomAPIError.invalidResponse - } - return response.chatID - } - - func setTranscription(request: GridTranscriptionRequest) async throws -> InlineProtocol.Grid { - let result = try await realtime.send(.setGridTranscription(request: request)) - guard case let .setGridTranscription(response)? = result, response.hasGrid else { - throw GridRoomAPIError.invalidResponse - } - return response.grid - } - - func listTranscripts(roomID: Int64) async throws -> [GridTranscriptDestinationInfo] { - let result = try await realtime.send(.listGridTranscripts(roomID: roomID)) - guard case let .listGridTranscripts(response)? = result else { - throw GridRoomAPIError.invalidResponse - } - return Array(response.transcripts.prefix(20)) - } - - func createRoom( - spaceID: Int64, - microphoneEnabled: Bool - ) async throws -> GridRoomMutationResult { - let result = try await realtime.send( - .createGridRoom(spaceID: spaceID, microphoneEnabled: microphoneEnabled) - ) - guard case let .createGridRoom(response)? = result else { - throw GridRoomAPIError.invalidResponse - } - return GridRoomMutationResult( - grids: response.grids, - credentials: response.hasConnection ? response.connection : nil - ) - } - - func joinRoom( - roomID: Int64, - microphoneEnabled: Bool - ) async throws -> GridRoomMutationResult { - let result = try await realtime.send( - .joinGridRoom(roomID: roomID, microphoneEnabled: microphoneEnabled) - ) - guard case let .joinGridRoom(response)? = result else { - throw GridRoomAPIError.invalidResponse - } - return GridRoomMutationResult( - grids: response.grids, - credentials: response.hasConnection ? response.connection : nil - ) - } - - func leaveRoom(roomID: Int64) async throws -> [InlineProtocol.Grid] { - let result = try await realtime.send(.leaveGridRoom(roomID: roomID)) - guard case let .leaveGridRoom(response)? = result else { - throw GridRoomAPIError.invalidResponse - } - return response.grids - } - - func setLocked(_ locked: Bool, roomID: Int64) async throws -> InlineProtocol.Grid { - let result = try await realtime.send(.setGridRoomLocked(roomID: roomID, locked: locked)) - guard case let .setGridRoomLocked(response)? = result, response.hasGrid else { - throw GridRoomAPIError.invalidResponse - } - return response.grid - } - - func setTitle(_ title: String, roomID: Int64) async throws -> InlineProtocol.Grid { - let result = try await realtime.send(.setGridRoomTitle(roomID: roomID, title: title)) - guard case let .setGridRoomTitle(response)? = result, response.hasGrid else { - throw GridRoomAPIError.invalidResponse - } - return response.grid - } - - func deleteRoom(roomID: Int64) async throws -> InlineProtocol.Grid { - let result = try await realtime.send(.deleteGridRoom(roomID: roomID)) - guard case let .deleteGridRoom(response)? = result, response.hasGrid else { - throw GridRoomAPIError.invalidResponse - } - return response.grid - } - - func prepareConnection(roomID: Int64, generation: Int32) async throws -> GridConnectionCredentials? { - let result = try await realtime.send( - .prepareGridConnection(roomID: roomID, generation: generation) - ) - guard case let .prepareGridConnection(response)? = result else { - throw GridRoomAPIError.invalidResponse - } - return response.hasConnection ? response.connection : nil - } - - func setMicrophoneEnabled(_ enabled: Bool, roomID: Int64) async throws { - let result = try await realtime.send( - .setGridAvatarMicrophoneEnabled(roomID: roomID, enabled: enabled) - ) - guard case .setGridAvatarMicrophoneEnabled? = result else { - throw GridRoomAPIError.invalidResponse - } - } -} - -enum GridRoomAPIError: Error { - case invalidResponse -} +// Grid product state is shared by the iOS and macOS clients. +import InlineGrid diff --git a/apple/InlineMac/Features/Grid/GridRoomLifecycle.swift b/apple/InlineMac/Features/Grid/GridRoomLifecycle.swift index c7f326b1b..c7a3d8f7a 100644 --- a/apple/InlineMac/Features/Grid/GridRoomLifecycle.swift +++ b/apple/InlineMac/Features/Grid/GridRoomLifecycle.swift @@ -1,83 +1,2 @@ -import Foundation -import InlineProtocol -import RealtimeV2 - -enum GridRoomLifecycleEvent: Sendable { - case grid(GridEvent) - case realtimeConnection(RealtimeConnectionState) - case network(GridNetworkPathSnapshot) - case heartbeat -} - -/// Collects external lifecycle sources without owning Grid product state. -/// Every subscriber receives its own watch/event stream. -@MainActor -final class GridRoomLifecycle { - private let realtime: RealtimeV2 - private let network: GridNetworkMonitor - private var sourceTasks: [Task] = [] - private var subscribers: [UUID: AsyncStream.Continuation] = [:] - private var started = false - - init(realtime: RealtimeV2, network: GridNetworkMonitor) { - self.realtime = realtime - self.network = network - } - - deinit { - sourceTasks.forEach { $0.cancel() } - subscribers.values.forEach { $0.finish() } - } - - func subscribe() -> AsyncStream { - startIfNeeded() - let id = UUID() - let stream = AsyncStream.makeStream( - of: GridRoomLifecycleEvent.self, - // Lifecycle edges only trigger authoritative refetch/reconciliation; - // retain a bounded recent window for a temporarily suspended observer. - bufferingPolicy: .bufferingNewest(64) - ) - stream.continuation.onTermination = { [weak self] _ in - Task { @MainActor in self?.subscribers.removeValue(forKey: id) } - } - subscribers[id] = stream.continuation - return stream.stream - } - - private func startIfNeeded() { - guard !started else { return } - started = true - sourceTasks = [ - Task { [weak self, realtime] in - for await event in await realtime.gridEvents() { - guard !Task.isCancelled else { return } - self?.broadcast(.grid(event)) - } - }, - Task { [weak self, realtime] in - for await state in await realtime.connectionStates() { - guard !Task.isCancelled else { return } - self?.broadcast(.realtimeConnection(state)) - } - }, - Task { [weak self, network] in - for await snapshot in network.subscribe() { - guard !Task.isCancelled else { return } - self?.broadcast(.network(snapshot)) - } - }, - Task { [weak self] in - while !Task.isCancelled { - try? await Task.sleep(for: .seconds(30)) - guard !Task.isCancelled else { return } - self?.broadcast(.heartbeat) - } - }, - ] - } - - private func broadcast(_ event: GridRoomLifecycleEvent) { - subscribers.values.forEach { $0.yield(event) } - } -} +// Grid product state is shared by the iOS and macOS clients. +import InlineGrid diff --git a/apple/InlineMac/Features/Grid/GridRoomView.swift b/apple/InlineMac/Features/Grid/GridRoomView.swift index 646830a5c..8261c48ab 100644 --- a/apple/InlineMac/Features/Grid/GridRoomView.swift +++ b/apple/InlineMac/Features/Grid/GridRoomView.swift @@ -1,3 +1,4 @@ +import InlineGrid import InlineKit import InlineProtocol import InlineRTC @@ -68,6 +69,8 @@ struct GridContent: View { let grid: InlineProtocol.Grid? let isLoading: Bool let didFailLoading: Bool + let hasLocalAdmission: Bool + let isJoiningDisabled: Bool let audioLevel: (Int64) -> Float let isScreenSharing: (GridAvatar) -> Bool let connectionState: GridMediaConnectionStatus @@ -89,7 +92,8 @@ struct GridContent: View { ForEach(grid.rooms, id: \.id) { room in GridRoomCard( room: room, - isCurrent: grid.hasCurrentRoomID && grid.currentRoomID == room.id, + isCurrent: hasLocalAdmission && grid.hasCurrentRoomID && grid.currentRoomID == room.id, + isJoiningDisabled: isJoiningDisabled, audioLevel: audioLevel, isScreenSharing: isScreenSharing, connectionState: connectionState, @@ -104,6 +108,7 @@ struct GridContent: View { ) } GridCreateRoomCard(action: onCreate) + .disabled(isJoiningDisabled) } .animation(.smoothSnappy, value: grid.rooms.map(\.id)) } else if isLoading { @@ -164,6 +169,7 @@ private struct GridCreateRoomCard: View { private struct GridRoomCard: View { let room: GridRoom let isCurrent: Bool + let isJoiningDisabled: Bool let audioLevel: (Int64) -> Float let isScreenSharing: (GridAvatar) -> Bool let connectionState: GridMediaConnectionStatus @@ -209,7 +215,7 @@ private struct GridRoomCard: View { ) } .buttonStyle(.plain) - .disabled(room.locked) + .disabled(room.locked || isJoiningDisabled) } GridRoomOptionsMenu( @@ -287,6 +293,7 @@ private struct GridRoomSurface: View { .overlay { GridRoomAvatars( avatars: room.avatars, + allowsLocalControls: isCurrent, audioLevel: audioLevel, isScreenSharing: isScreenSharing, showsLocalConnectingIndicator: isCurrent && connectionState == .connecting, @@ -362,6 +369,7 @@ private struct GridRoomAvatars: View { static let maximumVisibleAvatarCount = 5 let avatars: [GridAvatar] + let allowsLocalControls: Bool let audioLevel: (Int64) -> Float let isScreenSharing: (GridAvatar) -> Bool let showsLocalConnectingIndicator: Bool @@ -375,6 +383,7 @@ private struct GridRoomAvatars: View { ForEach(visibleAvatars, id: \.user.id) { avatar in GridSpeakingAvatar( avatar: avatar, + allowsLocalControls: allowsLocalControls, audioLevel: audioLevel(avatar.user.id), isScreenSharing: isScreenSharing(avatar), showsConnectingIndicator: showsLocalConnectingIndicator && avatar.ownedByCurrentSession, @@ -445,6 +454,7 @@ private struct GridRoomAvatars: View { private struct GridSpeakingAvatar: View { let avatar: GridAvatar + let allowsLocalControls: Bool let audioLevel: Float let isScreenSharing: Bool let showsConnectingIndicator: Bool @@ -457,7 +467,7 @@ private struct GridSpeakingAvatar: View { var body: some View { ZStack(alignment: .topLeading) { - if avatar.ownedByCurrentSession { + if avatar.ownedByCurrentSession && allowsLocalControls { Button(action: onToggleMicrophone) { GridAvatarImage( user: avatar.user, @@ -473,7 +483,7 @@ private struct GridSpeakingAvatar: View { GridAvatarImage( user: avatar.user, audioLevel: audioLevel, - microphoneEnabled: avatar.microphoneEnabled, + microphoneEnabled: avatar.ownedByCurrentSession && !allowsLocalControls ? false : avatar.microphoneEnabled, showsConnectingIndicator: false, size: 45 ) @@ -482,7 +492,7 @@ private struct GridSpeakingAvatar: View { if isScreenSharing { GridAvatarScreenShareControl( - isLocal: avatar.ownedByCurrentSession, + isLocal: avatar.ownedByCurrentSession && allowsLocalControls, displayName: InlineKit.User(from: avatar.user).displayName, onOpen: onOpenScreenShare, onStop: onStopScreenShare @@ -490,7 +500,7 @@ private struct GridSpeakingAvatar: View { .offset(x: 23, y: 23) } - if avatar.ownedByCurrentSession, isHovered { + if avatar.ownedByCurrentSession && allowsLocalControls, isHovered { GridAvatarLeaveButton(action: onLeave) .offset(x: -3, y: -3) .transition(.scale(scale: 0.72).combined(with: .opacity)) @@ -501,7 +511,7 @@ private struct GridSpeakingAvatar: View { } private var helpText: String { - if avatar.ownedByCurrentSession { + if avatar.ownedByCurrentSession && allowsLocalControls { return isScreenSharing ? "You’re sharing · mute or unmute" : "Mute or unmute" } if isScreenSharing { return "Open screen" } diff --git a/apple/InlineMac/Features/Grid/GridRuntime.swift b/apple/InlineMac/Features/Grid/GridRuntime.swift index df226b41f..c7a3d8f7a 100644 --- a/apple/InlineMac/Features/Grid/GridRuntime.swift +++ b/apple/InlineMac/Features/Grid/GridRuntime.swift @@ -1,36 +1,2 @@ -import Foundation -import InlineKit -import InlineRTC -import RealtimeV2 - -/// One authenticated-process owner for Grid's product, RTC, and audio domains. -/// `AppDependencies` is copied per window, but this reference is shared. -@MainActor -final class GridRuntime { - static let shared = GridRuntime() - - private let engine: InlineRTCSession - let rooms: GridRoomService - - private init( - realtime: RealtimeV2 = Api.realtime, - userDefaults: UserDefaults = .standard - ) { - let engine = InlineRTCSession() - self.engine = engine - rooms = GridRoomService( - realtime: realtime, - userDefaults: userDefaults, - engine: engine - ) - } - - func prepareForLogout() async -> GridMediaShutdownReceipt { - await rooms.prepareForLogout() - return await engine.shutdown() - } - - func applicationDidWake() async { - await rooms.applicationDidWake() - } -} +// Grid product state is shared by the iOS and macOS clients. +import InlineGrid diff --git a/apple/InlineMac/Features/Grid/GridScreenShareWindow.swift b/apple/InlineMac/Features/Grid/GridScreenShareWindow.swift index dd5fc5542..4fb63415f 100644 --- a/apple/InlineMac/Features/Grid/GridScreenShareWindow.swift +++ b/apple/InlineMac/Features/Grid/GridScreenShareWindow.swift @@ -1,3 +1,4 @@ +import InlineGrid import AppKit import Foundation import InlineKit diff --git a/apple/InlineMac/Features/Grid/GridStore.swift b/apple/InlineMac/Features/Grid/GridStore.swift index 52115aeb6..c7a3d8f7a 100644 --- a/apple/InlineMac/Features/Grid/GridStore.swift +++ b/apple/InlineMac/Features/Grid/GridStore.swift @@ -1,1820 +1,2 @@ -import AppKit -import Auth -import CoreGraphics -import Foundation -import InlineKit -import InlineRTC -import InlineProtocol -import Logger -import Observation -import RealtimeV2 - -@MainActor -@Observable -final class GridRoomService { - private(set) var grids: [Int64: InlineProtocol.Grid] = [:] - private(set) var enabledSpaceIDs = Set() - private(set) var homeSpaces: [GridHomeSpace] = [] - private(set) var loadingSpaceIDs = Set() - private(set) var failedLoadSpaceIDs = Set() - private(set) var lastError: String? - private(set) var networkRefreshRevision = 0 - private(set) var networkAvailable = true - private(set) var pendingTranscriptionRequestIDs: [Int64: String] = [:] - var connectionRecoveryAttempt: Int { media.recoveryAttempt } - let media: GridMediaPresentation - - var inputSelection: AudioInputSelection { - get { media.inputSelection } - set { mediaCoordinator.setInput(newValue) } - } - - var outputSelection: AudioOutputSelection { - get { media.outputSelection } - set { mediaCoordinator.setOutput(newValue) } - } - - var autoUnmuteOnJoin: Bool { - get { media.autoUnmuteOnJoin } - set { mediaCoordinator.setAutoUnmuteOnJoin(newValue) } - } - - var autoMuteWhenAlone: Bool { - get { media.autoMuteWhenAlone } - set { - mediaCoordinator.setAutoMuteWhenAlone(newValue) - reconcileAloneAutoMute() - } - } - - @ObservationIgnored private let api: GridRoomAPI - @ObservationIgnored private let avatarStateSync: GridAvatarStateSync - @ObservationIgnored private let membershipSync: GridMembershipSync - @ObservationIgnored private let mediaCoordinator: GridMediaCoordinator - @ObservationIgnored private let homePreferences: GridHomePreferences - @ObservationIgnored private var lifecycleTask: Task? - @ObservationIgnored private var mediaEventsTask: Task? - @ObservationIgnored private var membershipEventsTask: Task? - @ObservationIgnored private var lastNetworkPathSnapshot: GridNetworkPathSnapshot? - @ObservationIgnored private let lifecycle: GridRoomLifecycle - @ObservationIgnored private var pendingReloadSpaceIDs = Set() - @ObservationIgnored private var pendingConnectionSpaceIDs = Set() - @ObservationIgnored private var roomMutationRevisions: [OptimisticRoomMutationKey: Int] = [:] - @ObservationIgnored private var pendingRoomMutations: [OptimisticRoomMutationKey: OptimisticRoomMutation] = [:] - @ObservationIgnored private var membershipMutationRevision = 0 - @ObservationIgnored private var pendingMembershipMutations: [Int: OptimisticMembershipMutation] = [:] - @ObservationIgnored private var membershipRollbackBaseline = - GridOptimisticState.MembershipRollbackBaseline() - @ObservationIgnored private var lastAcceptedSnapshotRevisions: [Int64: Int64] = [:] - @ObservationIgnored private var spaceAccessRevisions: [Int64: Int] = [:] - @ObservationIgnored private var homeLoadRevision = 0 - @ObservationIgnored private var lastRealtimeConnectionState: RealtimeConnectionState? - @ObservationIgnored private var pendingCredentialTarget: GridMediaTarget? - @ObservationIgnored private var credentialRetryTask: Task? - @ObservationIgnored private var credentialRetryTarget: GridMediaTarget? - @ObservationIgnored private var credentialRetryAttempt = 0 - @ObservationIgnored private var aloneAutoMuteTask: Task? - @ObservationIgnored private var aloneAutoMuteTarget: GridAloneAutoMuteTarget? - @ObservationIgnored private var screenShareAloneTask: Task? - @ObservationIgnored private var screenShareAloneTarget: GridScreenShareAloneTarget? - @ObservationIgnored private var screenShareAloneGrace = GridScreenShareAloneGraceState() - @ObservationIgnored private var screenShareNotificationTarget: GridMediaTarget? - @ObservationIgnored private var observedRemoteScreenShares: [String: GridScreenShareNotice] = [:] - @ObservationIgnored private var hasPrimedScreenSharePresentationEdges = false - @ObservationIgnored private var pendingScreenShareOpenTasks: - [String: GridPendingScreenShareOpen] = [:] - @ObservationIgnored private var mediaInteractionStartedAt: Date? - @ObservationIgnored private let log = Log.scoped("GridRoomService") - private static let aloneMediaGraceSeconds = 5 - - init( - realtime: RealtimeV2 = Api.realtime, - userDefaults: UserDefaults = .standard, - engine: InlineRTCSession - ) { - let api = GridRoomAPI(realtime: realtime) - self.api = api - avatarStateSync = GridAvatarStateSync(api: api) - let membershipSync = GridMembershipSync(api: api) - self.membershipSync = membershipSync - let networkMonitor = GridNetworkMonitor() - lifecycle = GridRoomLifecycle(realtime: realtime, network: networkMonitor) - let inputPreferences = AudioInputPreferenceStore( - defaults: userDefaults, - deviceIDKey: "grid.preferredInputDeviceID", - deviceNameKey: "grid.preferredInputDeviceName" - ) - let outputPreferences = AudioOutputPreferenceStore( - defaults: userDefaults, - deviceIDKey: "grid.preferredOutputDeviceID", - deviceNameKey: "grid.preferredOutputDeviceName" - ) - homePreferences = GridHomePreferences(defaults: userDefaults) - let mediaCoordinator = GridMediaCoordinator( - engine: engine, - defaults: userDefaults, - inputPreferences: inputPreferences, - outputPreferences: outputPreferences - ) - self.mediaCoordinator = mediaCoordinator - media = mediaCoordinator.presentation - lifecycleTask = Task { [weak self, lifecycle] in - for await event in lifecycle.subscribe() { - guard !Task.isCancelled else { return } - await self?.handle(event) - } - } - mediaEventsTask = Task { [weak self, mediaCoordinator] in - for await event in mediaCoordinator.subscribe() { - guard !Task.isCancelled else { return } - await self?.handle(event) - } - } - membershipEventsTask = Task { [weak self, membershipSync] in - for await event in membershipSync.subscribe() { - guard !Task.isCancelled else { return } - await self?.handle(event) - } - } - } - - deinit { - lifecycleTask?.cancel() - mediaEventsTask?.cancel() - membershipEventsTask?.cancel() - credentialRetryTask?.cancel() - aloneAutoMuteTask?.cancel() - screenShareAloneTask?.cancel() - pendingScreenShareOpenTasks.values.forEach { $0.task.cancel() } - } - - func isEnabled(spaceID: Int64) -> Bool { - enabledSpaceIDs.contains(spaceID) - } - - func prepareForLogout() async { - let invalidatedSpaceIDs = Set(spaceAccessRevisions.keys) - .union(grids.keys) - .union(enabledSpaceIDs) - .union(loadingSpaceIDs) - .union(pendingReloadSpaceIDs) - .union(pendingConnectionSpaceIDs) - pendingCredentialTarget = nil - resetCredentialRetry() - cancelAloneAutoMute() - resetScreenShareAloneGrace() - resetScreenSharePresentationEdges() - grids.removeAll() - homeSpaces.removeAll() - enabledSpaceIDs.removeAll() - loadingSpaceIDs.removeAll() - failedLoadSpaceIDs.removeAll() - pendingReloadSpaceIDs.removeAll() - pendingConnectionSpaceIDs.removeAll() - for spaceID in invalidatedSpaceIDs { - spaceAccessRevisions[spaceID, default: 0] &+= 1 - } - homeLoadRevision &+= 1 - roomMutationRevisions.removeAll() - pendingRoomMutations.removeAll() - pendingTranscriptionRequestIDs.removeAll() - membershipMutationRevision &+= 1 - pendingMembershipMutations.removeAll() - membershipRollbackBaseline.clear() - lastAcceptedSnapshotRevisions.removeAll() - membershipSync.reset() - avatarStateSync.setOwnedRoom(nil) - await mediaCoordinator.shutdown() - } - - func grid(spaceID: Int64) -> InlineProtocol.Grid? { - grids[spaceID] - } - - var activeTranscriptionRoom: GridRoom? { - grids.values.lazy.compactMap { grid in - guard grid.hasCurrentRoomID, - let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), - room.transcriptionIsRunning, - room.avatars.contains(where: { $0.ownedByCurrentSession && !$0.membershipID.isEmpty }) - else { return nil } - return room - }.first - } - - func recentAvatars(spaceID: Int64, limit: Int = 4) -> [GridAvatar] { - guard let grid = grids[spaceID] else { return [] } - return Array( - grid.rooms - .flatMap(\.avatars) - .sorted { $0.joinedAt > $1.joinedAt } - .prefix(limit) - ) - } - - func loadHome() async { - guard realtimeReady else { - log.debug("GRID_TRACE phase=home_load_deferred reason=realtime_not_ready") - return - } - homeLoadRevision &+= 1 - let revision = homeLoadRevision - do { - let nextHomeSpaces = try await api.home() - guard revision == homeLoadRevision else { return } - let previouslyLoadedSpaceIDs = Set(grids.keys) - homeSpaces = nextHomeSpaces - let eligibleSpaceIDs = Set(homeSpaces.map(\.spaceID)) - enabledSpaceIDs = eligibleSpaceIDs - for spaceID in previouslyLoadedSpaceIDs.subtracting(eligibleSpaceIDs) { - clearLocalGrid(spaceID: spaceID, reason: "home_reconciliation") - } - lastError = nil - } catch { - guard revision == homeLoadRevision else { return } - guard realtimeReady, networkAvailable else { - log.debug("GRID_TRACE phase=home_load_deferred reason=connection_lost") - return - } - lastError = String(describing: error) - log.warning("GRID_TRACE phase=home_load_failed") - PerformanceTrace.breadcrumb( - "Grid Home discovery failed", - category: "Grid.Home", - level: .warning - ) - } - } - - var orderedHomeSpaces: [GridHomeSpace] { - homePreferences.ordered(homeSpaces) - } - - func recordGridOpened(spaceID: Int64) { - homePreferences.recordOpened(spaceID: spaceID) - } - - func audioLevel(userID: Int64) -> Float { - let legacyIdentity = Self.liveKitIdentityPrefix(userID: userID) - return media.participantAudioLevels - .filter { $0.key == legacyIdentity || $0.key.hasPrefix("\(legacyIdentity)-") } - .map(\.value) - .max() ?? 0 - } - - func isScreenSharing(avatar: GridAvatar) -> Bool { - if avatar.ownedByCurrentSession { - return media.isScreenShareRequested - } - let identities = participantIdentities(for: avatar) - let knownIntents = identities.compactMap { media.participantScreenShareIntents[$0] } - if !knownIntents.isEmpty { - return knownIntents.contains(true) - } - let shares = screenShares(avatar: avatar) - return !shares.isEmpty - } - - func isScreenShareIntended(participantIdentity: String) -> Bool { - if let intent = media.participantScreenShareIntents[participantIdentity] { - return intent - } - return media.screenShares.contains { $0.participantIdentity == participantIdentity } - } - - func isScreenSharing(userID: Int64) -> Bool { - let shares = screenShares(userID: userID) - if isOwnedUser(userID) { - return shares.contains(where: \.isLocal) - } - return !shares.isEmpty - } - - func screenShare(userID: Int64) -> InlineRTCScreenShare? { - let shares = screenShares(userID: userID) - if isOwnedUser(userID) { - return shares.first(where: \.isLocal) - } - return shares.first - } - - func screenShare(avatar: GridAvatar) -> InlineRTCScreenShare? { - let shares = screenShares(avatar: avatar) - return avatar.ownedByCurrentSession - ? shares.first(where: \.isLocal) - : shares.first - } - - func screenShares(avatar: GridAvatar) -> [InlineRTCScreenShare] { - let identities = participantIdentities(for: avatar) - return media.screenShares.filter { identities.contains($0.participantIdentity) } - } - - func screenShares(userID: Int64) -> [InlineRTCScreenShare] { - media.screenShares.filter { - Self.identity($0.participantIdentity, matchesUserID: userID) - } - } - - func screenShares(participantIdentity: String) -> [InlineRTCScreenShare] { - media.screenShares.filter { $0.participantIdentity == participantIdentity } - } - - func screenShare(publicationID: String) -> InlineRTCScreenShare? { - media.screenShares.first { $0.publicationID == publicationID } - } - - func load(spaceID: Int64) async { - guard realtimeReady else { - failedLoadSpaceIDs.remove(spaceID) - pendingConnectionSpaceIDs.insert(spaceID) - log.debug( - "GRID_TRACE phase=load_deferred space=\(spaceID) reason=realtime_not_ready" - ) - return - } - pendingConnectionSpaceIDs.remove(spaceID) - let accessRevision = spaceAccessRevisions[spaceID, default: 0] - guard loadingSpaceIDs.insert(spaceID).inserted else { - pendingReloadSpaceIDs.insert(spaceID) - return - } - failedLoadSpaceIDs.remove(spaceID) - defer { - loadingSpaceIDs.remove(spaceID) - if pendingReloadSpaceIDs.remove(spaceID) != nil { - Task { [weak self] in await self?.load(spaceID: spaceID) } - } - } - - do { - let settings = try await api.settings(spaceID: spaceID) - guard accessRevision == spaceAccessRevisions[spaceID, default: 0] else { return } - applyEnabled(settings.gridEnabled, spaceID: spaceID) - lastError = nil - guard settings.gridEnabled else { - reconcileMediaDemand() - return - } - try await reloadGrid(spaceID: spaceID) - } catch { - guard accessRevision == spaceAccessRevisions[spaceID, default: 0] else { return } - guard realtimeReady, networkAvailable else { - pendingConnectionSpaceIDs.insert(spaceID) - log.debug( - "GRID_TRACE phase=load_deferred space=\(spaceID) reason=connection_lost" - ) - return - } - failedLoadSpaceIDs.insert(spaceID) - lastError = String(describing: error) - log.error("GRID_TRACE phase=load_failed space=\(spaceID)", error: error) - PerformanceTrace.breadcrumb( - "Grid snapshot failed", - category: "Grid.Snapshot", - level: .error, - data: ["space_id": spaceID] - ) - } - } - - func setEnabled(_ enabled: Bool, spaceID: Int64) async throws { - let accessRevision = spaceAccessRevisions[spaceID, default: 0] - let settings = try await api.setEnabled(enabled, spaceID: spaceID) - guard accessRevision == spaceAccessRevisions[spaceID, default: 0] else { return } - applyEnabled(settings.gridEnabled, spaceID: spaceID) - if enabled { - try await reloadGrid(spaceID: spaceID) - } else { - reconcileMediaDemand() - } - await loadHome() - } - - func createAndJoin(spaceID: Int64) { - let startedAt = Date() - let automaticMicrophoneChange = mediaCoordinator.applyAutoUnmuteOnJoin() - mediaCoordinator.requestMicrophonePermission() - GridSoundEffects.shared.play(.join) - log.debug("GRID_TRACE phase=create_client_start space=\(spaceID)") - membershipMutationRevision &+= 1 - membershipSync.submit(GridMembershipOperation( - kind: .create, - spaceID: spaceID, - microphoneEnabled: mediaCoordinator.isMicrophoneEnabled, - automaticMicrophoneChange: automaticMicrophoneChange, - accessRevision: spaceAccessRevisions[spaceID, default: 0], - revision: membershipMutationRevision, - startedAt: startedAt - )) - } - - func join(roomID: Int64) { - let startedAt = Date() - if let room = grids.values.lazy.flatMap(\.rooms).first(where: { $0.id == roomID }), - room.avatars.isEmpty == false { - mediaInteractionStartedAt = startedAt - } else { - mediaInteractionStartedAt = nil - } - GridSoundEffects.shared.play(.join) - log.debug("GRID_TRACE phase=join_client_start room=\(roomID)") - guard let mutation = optimisticallyJoin(roomID: roomID) else { return } - let automaticMicrophoneChange = mediaCoordinator.applyAutoUnmuteOnJoin() - if automaticMicrophoneChange != nil { - applyAvatarMicrophoneState( - spaceID: mutation.spaceID, - roomID: mutation.roomID, - userID: nil, - enabled: true - ) - } - mediaCoordinator.requestMicrophonePermission() - reconcileMediaDemand() - - pendingMembershipMutations[mutation.revision] = mutation - membershipSync.submit(GridMembershipOperation( - kind: .join(roomID: roomID), - spaceID: mutation.spaceID, - microphoneEnabled: mediaCoordinator.isMicrophoneEnabled, - automaticMicrophoneChange: automaticMicrophoneChange, - accessRevision: spaceAccessRevisions[mutation.spaceID, default: 0], - revision: mutation.revision, - startedAt: startedAt - )) - } - - func leaveCurrentRoom(spaceID: Int64) { - guard let mutation = optimisticallyLeave(spaceID: spaceID) else { return } - GridSoundEffects.shared.play(.leave) - mediaCoordinator.clearCredentials() - mediaInteractionStartedAt = nil - pendingCredentialTarget = nil - reconcileMediaDemand() - - pendingMembershipMutations[mutation.revision] = mutation - membershipSync.submit(GridMembershipOperation( - kind: .leave(roomID: mutation.roomID), - spaceID: spaceID, - microphoneEnabled: mediaCoordinator.isMicrophoneEnabled, - automaticMicrophoneChange: nil, - accessRevision: spaceAccessRevisions[spaceID, default: 0], - revision: mutation.revision, - startedAt: Date() - )) - } - - func toggleMicrophone(spaceID: Int64) { - guard let grid = grids[spaceID], grid.hasCurrentRoomID, - let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), - room.avatars.contains(where: \.ownedByCurrentSession) - else { return } - let roomID = grid.currentRoomID - let enabled = mediaCoordinator.toggleMicrophone() - applyAvatarMicrophoneState( - spaceID: spaceID, - roomID: roomID, - userID: nil, - enabled: enabled - ) - avatarStateSync.submitMicrophoneState(roomID: roomID, enabled: enabled) - reconcileMediaDemand() - } - - func openRoomThread(roomID: Int64) async throws -> Int64 { - let room = try transcriptionRoom(roomID: roomID) - guard let avatar = room.avatars.first(where: \.ownedByCurrentSession), !avatar.membershipID.isEmpty else { - throw GridRoomAPIError.invalidResponse - } - let chatID = try await api.openRoomThread(roomID: roomID, membershipID: avatar.membershipID) - let latestRoom = try transcriptionRoom(roomID: roomID) - guard latestRoom.avatars.contains(where: { $0.ownedByCurrentSession && $0.membershipID == avatar.membershipID }) - else { - throw GridRoomAPIError.invalidResponse - } - // Thread sidecars are applied by the transaction before navigation. - return chatID - } - - func setTranscription( - room: GridRoom, - enabled: Bool, - destination: GridTranscriptionRequest.Destination = .last - ) async throws { - let roomID = room.id - guard pendingTranscriptionRequestIDs[roomID] == nil else { throw GridRoomAPIError.invalidResponse } - let latestRoom = try transcriptionRoom(roomID: roomID) - guard let request = GridTranscriptionRequest(room: room, enabled: enabled, destination: destination) else { - throw GridRoomAPIError.invalidResponse - } - guard latestRoom.avatars.contains(where: { $0.ownedByCurrentSession && $0.membershipID == request.membershipID }), - latestRoom.hasConnection, latestRoom.connection.generation == request.generation - else { throw GridRoomAPIError.invalidResponse } - let accessRevision = spaceAccessRevisions[room.spaceID, default: 0] - pendingTranscriptionRequestIDs[roomID] = request.requestID - defer { - if pendingTranscriptionRequestIDs[roomID] == request.requestID { - pendingTranscriptionRequestIDs.removeValue(forKey: roomID) - } - } - do { - let grid = try await api.setTranscription(request: request) - guard accessRevision == spaceAccessRevisions[room.spaceID, default: 0] else { return } - applySnapshot([grid]) - } catch { - // A rejected fence or uncertain response must repair from the authority, - // never synthesize an active/stopped state in the client. - if accessRevision == spaceAccessRevisions[room.spaceID, default: 0] { - try? await reloadGrid(spaceID: room.spaceID) - } - throw error - } - } - - func listTranscripts(roomID: Int64) async throws -> [GridTranscriptDestinationInfo] { - let room = try transcriptionRoom(roomID: roomID) - let membershipID = room.avatars.first(where: \.ownedByCurrentSession)?.membershipID - let transcripts = try await api.listTranscripts(roomID: roomID) - let latestRoom = try transcriptionRoom(roomID: roomID) - guard latestRoom.avatars.contains(where: { $0.ownedByCurrentSession && $0.membershipID == membershipID }) else { - throw GridRoomAPIError.invalidResponse - } - return transcripts - } - - private func transcriptionRoom(roomID: Int64) throws -> GridRoom { - guard let grid = grids.values.first(where: { $0.hasCurrentRoomID && $0.currentRoomID == roomID }), - let room = grid.rooms.first(where: { $0.id == roomID }), - room.avatars.contains(where: { $0.ownedByCurrentSession && !$0.membershipID.isEmpty }) - else { throw GridRoomAPIError.invalidResponse } - return room - } - - func toggleCurrentMicrophone() { - guard let grid = grids.values.first(where: { grid in - guard grid.hasCurrentRoomID, - let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }) - else { return false } - return room.avatars.contains(where: \.ownedByCurrentSession) - }) else { return } - - toggleMicrophone(spaceID: grid.spaceID) - } - - func toggleScreenShare() { - guard currentMediaTarget() != nil else { return } - if media.isScreenShareRequested || media.isScreenSharing { - mediaCoordinator.stopScreenSharing() - resetScreenShareAloneGrace() - return - } - let displayID = Self.displayIDUnderPointer() - Task { [weak self] in - guard let self else { return } - await mediaCoordinator.startScreenSharing(displayID: displayID) - reconcileScreenShareAloneGrace() - } - } - - func startScreenSharing(_ source: InlineRTCScreenCaptureSource) { - guard currentMediaTarget() != nil else { return } - mediaCoordinator.startScreenSharing(source) - reconcileScreenShareAloneGrace() - } - - func setScreenShareQualityProfile(_ profile: InlineRTCScreenShareQualityProfile) { - mediaCoordinator.setScreenShareQualityProfile(profile) - } - - func stopScreenSharing() { - mediaCoordinator.stopScreenSharing() - resetScreenShareAloneGrace() - } - - func refreshScreenCaptureSources() { - Task { [weak self] in - await self?.mediaCoordinator.refreshScreenCaptureSources() - } - } - - func openScreenShare(for avatar: GridAvatar) { - guard let participantIdentity = screenShareParticipantIdentity(for: avatar) else { return } - openScreenShare( - user: avatar.user, - participantIdentity: participantIdentity - ) - } - - func openScreenShareFromNotification( - spaceID: Int64, - roomID: Int64, - userID: Int64, - participantIdentity: String - ) { - Task { [weak self] in - guard let self else { return } - await load(spaceID: spaceID) - guard let grid = grids[spaceID], - let room = grid.rooms.first(where: { $0.id == roomID }), - let avatar = room.avatars.first(where: { $0.user.id == userID }) - else { return } - openScreenShare( - user: avatar.user, - participantIdentity: participantIdentity - ) - } - } - - func toggleRoomLock(roomID: Int64, locked: Bool) async { - guard let mutation = optimisticallyUpdateRoom( - roomID: roomID, - field: .locked, - update: { $0.locked = locked } - ) else { return } - do { - let grid = try await api.setLocked(locked, roomID: roomID) - guard isCurrent(mutation) else { return } - pendingRoomMutations.removeValue(forKey: mutation.key) - applySnapshot([grid]) - } catch { - guard isCurrent(mutation) else { return } - rollbackRoomMutation(mutation) - lastError = String(describing: error) - if networkAvailable { try? await reloadGrid(spaceID: mutation.change.spaceID) } - } - } - - func setRoomTitle(roomID: Int64, title: String) async { - let normalizedTitle = title.split(whereSeparator: \Character.isWhitespace).joined(separator: " ") - guard let mutation = optimisticallyUpdateRoom( - roomID: roomID, - field: .title, - update: { room in - if normalizedTitle.isEmpty { - room.clearTitle() - } else { - room.title = normalizedTitle - } - } - ) else { return } - do { - let grid = try await api.setTitle(title, roomID: roomID) - guard isCurrent(mutation) else { return } - pendingRoomMutations.removeValue(forKey: mutation.key) - applySnapshot([grid]) - } catch { - guard isCurrent(mutation) else { return } - rollbackRoomMutation(mutation) - lastError = String(describing: error) - if networkAvailable { try? await reloadGrid(spaceID: mutation.change.spaceID) } - } - } - - func deleteRoom(roomID: Int64) async { - guard let spaceID = grids.first(where: { _, grid in - grid.rooms.contains(where: { $0.id == roomID }) - })?.key else { return } - let accessRevision = spaceAccessRevisions[spaceID, default: 0] - do { - let grid = try await api.deleteRoom(roomID: roomID) - guard accessRevision == spaceAccessRevisions[spaceID, default: 0] else { return } - applySnapshot([grid]) - } catch { - lastError = String(describing: error) - } - } - - private func reloadGrid(spaceID: Int64) async throws { - let accessRevision = spaceAccessRevisions[spaceID, default: 0] - let grid = try await api.grid(spaceID: spaceID) - guard accessRevision == spaceAccessRevisions[spaceID, default: 0] else { return } - applySnapshot([grid]) - syncMicrophoneStateIfNeeded(grid: grid) - await prepareConnectionIfNeeded(grid: grid) - } - - private func apply(grids nextGrids: [InlineProtocol.Grid], credentials: GridConnectionCredentials? = nil) async { - applySnapshot(nextGrids) - nextGrids.forEach(syncMicrophoneStateIfNeeded) - if let credentials { - await accept(credentials) - } - } - - private func applySnapshot(_ nextGrids: [InlineProtocol.Grid]) { - for incomingGrid in nextGrids { - let previousRevision = lastAcceptedSnapshotRevisions[incomingGrid.spaceID] - if let previousRevision, incomingGrid.revision < previousRevision { - log.debug( - "GRID_TRACE phase=snapshot_rejected_stale space=\(incomingGrid.spaceID) incoming_revision=\(incomingGrid.revision) current_revision=\(previousRevision)" - ) - continue - } - lastAcceptedSnapshotRevisions[incomingGrid.spaceID] = incomingGrid.revision - var grid = incomingGrid - for mutation in pendingRoomMutations.values where mutation.change.spaceID == grid.spaceID { - grid = GridOptimisticState.applyingRoomIntent(mutation.change, to: grid) - } - grids[grid.spaceID] = grid - failedLoadSpaceIDs.remove(grid.spaceID) - pendingConnectionSpaceIDs.remove(grid.spaceID) - applyEnabled(grid.enabled, spaceID: grid.spaceID) - } - applyPendingMembershipIntent() - - reconcileAvatarStateOwnership() - reconcileMediaDemand() - } - - private func reconcileMediaDemand() { - let target = currentMediaTarget() - if credentialRetryTarget != target { resetCredentialRetry() } - if screenShareNotificationTarget != target { - resetScreenSharePresentationEdges(target: target) - } - mediaCoordinator.setTarget(target) - reconcileAloneAutoMute() - reconcileScreenShareAloneGrace() - } - - private func resetScreenSharePresentationEdges(target: GridMediaTarget? = nil) { - pendingScreenShareOpenTasks.values.forEach { $0.task.cancel() } - pendingScreenShareOpenTasks.removeAll() - screenShareNotificationTarget = target - observedRemoteScreenShares.removeAll() - hasPrimedScreenSharePresentationEdges = false - } - - private func reconcileScreenSharePresentationEdges() { - guard let target = currentMediaTarget(), - screenShareNotificationTarget == target, - media.hasParticipantMediaSnapshot, - let grid = grids[target.spaceID], - let room = grid.rooms.first(where: { $0.id == target.roomID }) - else { return } - - var next: [String: GridScreenShareNotice] = [:] - for avatar in room.avatars where !avatar.ownedByCurrentSession && isScreenSharing(avatar: avatar) { - guard let participantIdentity = screenShareParticipantIdentity(for: avatar) else { continue } - next[participantIdentity] = GridScreenShareNotice( - target: target, - user: avatar.user, - participantIdentity: participantIdentity - ) - } - - guard hasPrimedScreenSharePresentationEdges else { - observedRemoteScreenShares = next - hasPrimedScreenSharePresentationEdges = true - return - } - - let previous = observedRemoteScreenShares - observedRemoteScreenShares = next - for (identity, notice) in next where previous[identity] == nil { - presentScreenShareChange(notice, started: true) - } - for (identity, notice) in previous where next[identity] == nil { - pendingScreenShareOpenTasks.removeValue(forKey: identity)?.task.cancel() - presentScreenShareChange(notice, started: false) - } - } - - private func presentScreenShareChange( - _ notice: GridScreenShareNotice, - started: Bool - ) { - let displayName = InlineKit.User(from: notice.user).displayName - if started { - ToastCenter.shared.showSuccess( - "\(displayName) started sharing their screen", - actionTitle: "View" - ) { [weak self] in - self?.openScreenShare( - user: notice.user, - participantIdentity: notice.participantIdentity - ) - } - } else { - ToastCenter.shared.showInfo("\(displayName) stopped sharing their screen") - } - - Task { - await MacNotifications.shared.showGridScreenShareNotification( - displayName: displayName, - started: started, - spaceID: notice.target.spaceID, - roomID: notice.target.roomID, - userID: notice.user.id, - participantIdentity: notice.participantIdentity - ) - } - } - - private func reconcileAloneAutoMute() { - guard mediaCoordinator.shouldAutoMuteWhenAlone else { - cancelAloneAutoMute() - return - } - let target = grids.values.lazy.compactMap { grid -> GridAloneAutoMuteTarget? in - guard grid.hasCurrentRoomID, - let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), - room.avatars.count == 1, - let avatar = room.avatars.first, - avatar.ownedByCurrentSession, - avatar.microphoneEnabled, - self.mediaCoordinator.isMicrophoneEnabled - else { return nil } - return GridAloneAutoMuteTarget( - spaceID: grid.spaceID, - roomID: room.id, - userID: avatar.user.id - ) - }.first - - guard target != aloneAutoMuteTarget else { return } - cancelAloneAutoMute() - guard let target else { return } - - aloneAutoMuteTarget = target - aloneAutoMuteTask = Task { [weak self] in - try? await Task.sleep(for: .seconds(Self.aloneMediaGraceSeconds)) - guard !Task.isCancelled else { return } - self?.autoMuteIfStillAlone(target) - } - } - - private func autoMuteIfStillAlone(_ target: GridAloneAutoMuteTarget) { - guard aloneAutoMuteTarget == target else { return } - aloneAutoMuteTask = nil - aloneAutoMuteTarget = nil - - guard mediaCoordinator.shouldAutoMuteWhenAlone, - let grid = grids[target.spaceID], - grid.hasCurrentRoomID, - grid.currentRoomID == target.roomID, - let room = grid.rooms.first(where: { $0.id == target.roomID }), - room.avatars.count == 1, - let avatar = room.avatars.first, - avatar.ownedByCurrentSession, - avatar.user.id == target.userID, - avatar.microphoneEnabled, - mediaCoordinator.isMicrophoneEnabled - else { return } - - log.debug("GRID_TRACE phase=alone_auto_mute room=\(target.roomID)") - toggleMicrophone(spaceID: target.spaceID) - } - - private func cancelAloneAutoMute() { - aloneAutoMuteTask?.cancel() - aloneAutoMuteTask = nil - aloneAutoMuteTarget = nil - } - - private func reconcileScreenShareAloneGrace() { - let context = screenShareAloneContext() - let action = screenShareAloneGrace.reconcile(context) - - guard case let .scheduleStop(publicationIDs) = action, - let mediaTarget = currentMediaTarget() - else { - cancelScreenShareAloneTask() - return - } - - let target = GridScreenShareAloneTarget( - mediaTarget: mediaTarget, - episodeID: context.episodeID, - publicationIDs: publicationIDs - ) - guard target != screenShareAloneTarget else { return } - cancelScreenShareAloneTask() - screenShareAloneTarget = target - screenShareAloneTask = Task { [weak self] in - try? await Task.sleep(for: GridScreenShareAloneGraceState.graceDuration) - guard !Task.isCancelled else { return } - self?.stopScreenShareIfStillAlone(target) - } - } - - private func screenShareAloneContext() -> GridScreenShareAloneGraceState.Context { - let localShares = media.screenShares.filter(\.isLocal) - let localPublicationIDs = Set(localShares.map(\.publicationID)) - let localParticipantIdentities = Set(localShares.map(\.participantIdentity)) - let hasRemoteParticipant = media.connectedParticipantIdentities.contains { - !localParticipantIdentities.contains($0) - } - return .init( - episodeID: media.screenShareEpisodeID, - isConnected: media.connectionState == .connected, - isShareRequested: media.isScreenShareRequested, - localPublicationIDs: localPublicationIDs, - hasRemoteParticipant: hasRemoteParticipant - ) - } - - private func stopScreenShareIfStillAlone(_ target: GridScreenShareAloneTarget) { - guard screenShareAloneTarget == target else { return } - screenShareAloneTask = nil - screenShareAloneTarget = nil - guard currentMediaTarget() == target.mediaTarget, - GridScreenShareAloneGraceState.shouldCommitScheduledStop( - screenShareAloneContext(), - expectedEpisodeID: target.episodeID, - expectedPublicationIDs: target.publicationIDs - ) - else { return } - - log.debug( - "GRID_TRACE phase=alone_auto_stop_screen_share room=\(target.mediaTarget.roomID)" - ) - mediaCoordinator.stopScreenSharing() - resetScreenShareAloneGrace() - } - - private func cancelScreenShareAloneTask() { - screenShareAloneTask?.cancel() - screenShareAloneTask = nil - screenShareAloneTarget = nil - } - - private func resetScreenShareAloneGrace() { - cancelScreenShareAloneTask() - screenShareAloneGrace.reset() - } - - private func optimisticallyUpdateRoom( - roomID: Int64, - field: GridOptimisticState.RoomField, - update: (inout GridRoom) -> Void - ) -> OptimisticRoomMutation? { - guard let change = GridOptimisticState.updatingRoom( - in: grids, - roomID: roomID, - update: update - ) else { return nil } - - guard change.fields == Set([field]) else { return nil } - let key = OptimisticRoomMutationKey(roomID: roomID, field: field) - let revision = (roomMutationRevisions[key] ?? 0) + 1 - roomMutationRevisions[key] = revision - grids[change.spaceID] = change.nextGrid - let mutation = OptimisticRoomMutation( - key: key, - revision: revision, - accessRevision: spaceAccessRevisions[change.spaceID, default: 0], - change: change - ) - pendingRoomMutations[key] = mutation - return mutation - } - - private func rollbackRoomMutation(_ mutation: OptimisticRoomMutation) { - guard isCurrent(mutation) else { return } - pendingRoomMutations.removeValue(forKey: mutation.key) - guard let currentGrid = grids[mutation.change.spaceID] else { return } - grids[mutation.change.spaceID] = GridOptimisticState.rollingBackRoomIntent( - mutation.change, - in: currentGrid - ) - } - - private func isCurrent(_ mutation: OptimisticRoomMutation) -> Bool { - roomMutationRevisions[mutation.key] == mutation.revision - && spaceAccessRevisions[mutation.change.spaceID, default: 0] == mutation.accessRevision - } - - private func optimisticallyJoin(roomID: Int64) -> OptimisticMembershipMutation? { - let fallbackAvatar = makeCurrentUserAvatar() - let joinedAt = Int64(Date().timeIntervalSince1970) - guard let change = GridOptimisticState.joining( - roomID: roomID, - in: grids, - avatar: fallbackAvatar, - microphoneEnabled: mediaCoordinator.isMicrophoneEnabled, - joinedAt: joinedAt - ) else { return nil } - - membershipRollbackBaseline.beginIfNeeded(with: change.previousGrids) - - membershipMutationRevision &+= 1 - let mutation = OptimisticMembershipMutation( - roomID: roomID, - spaceID: change.spaceID, - revision: membershipMutationRevision, - previousGrids: change.previousGrids, - intent: .join(fallbackAvatar: fallbackAvatar, joinedAt: joinedAt) - ) - for (spaceID, grid) in change.nextGrids { grids[spaceID] = grid } - reconcileAvatarStateOwnership() - log.debug("GRID_TRACE phase=join_optimistic_applied room=\(roomID) revision=\(mutation.revision)") - return mutation - } - - private func optimisticallyLeave(spaceID: Int64) -> OptimisticMembershipMutation? { - guard let change = GridOptimisticState.leaving(spaceID: spaceID, in: grids) else { - return nil - } - - membershipRollbackBaseline.beginIfNeeded(with: change.previousGrids) - - membershipMutationRevision &+= 1 - let mutation = OptimisticMembershipMutation( - roomID: change.roomID, - spaceID: spaceID, - revision: membershipMutationRevision, - previousGrids: change.previousGrids, - intent: .leave - ) - for (changedSpaceID, grid) in change.nextGrids { grids[changedSpaceID] = grid } - reconcileAvatarStateOwnership() - log.debug("GRID_TRACE phase=leave_optimistic_applied room=\(change.roomID) revision=\(mutation.revision)") - return mutation - } - - private func rollbackMembershipMutation(_ mutation: OptimisticMembershipMutation) { - guard membershipMutationRevision == mutation.revision else { return } - grids = GridOptimisticState.rollingBackMembershipIntent( - .init( - roomID: mutation.roomID, - spaceID: mutation.spaceID, - previousGrids: membershipRollbackBaseline.grids ?? mutation.previousGrids, - nextGrids: [:] - ), - in: grids - ) - reconcileAvatarStateOwnership() - reconcileMediaDemand() - } - - private func makeCurrentUserAvatar() -> GridAvatar? { - guard let userID = Auth.shared.getCurrentUserId() else { return nil } - - var protocolUser = InlineProtocol.User() - protocolUser.id = userID - if let user = ObjectCache.shared.getUser(id: userID)?.user { - if let firstName = user.firstName { protocolUser.firstName = firstName } - if let lastName = user.lastName { protocolUser.lastName = lastName } - if let username = user.username { protocolUser.username = username } - if let profileCdnURL = user.profileCdnUrl { - protocolUser.profilePhoto = .with { - $0.cdnURL = profileCdnURL - if let uniqueID = user.profileFileUniqueId { $0.fileUniqueID = uniqueID } - } - } - } - - return .with { - $0.user = protocolUser - $0.joinedAt = Int64(Date().timeIntervalSince1970) - $0.ownedByCurrentSession = true - $0.microphoneEnabled = mediaCoordinator.isMicrophoneEnabled - } - } - - private func applyEnabled(_ enabled: Bool, spaceID: Int64) { - if enabled { - enabledSpaceIDs.insert(spaceID) - } else { - enabledSpaceIDs.remove(spaceID) - grids[spaceID] = .with { $0.spaceID = spaceID; $0.enabled = false } - reconcileAvatarStateOwnership() - } - } - - private func handle(_ event: GridRoomLifecycleEvent) async { - switch event { - case let .grid(event): - await handle(event) - case let .realtimeConnection(state): - await handleConnectionState(state) - case let .network(snapshot): - await handleNetworkPath(snapshot) - case .heartbeat: - await refreshOwnedPresence() - } - } - - private func handle(_ event: GridMediaCoordinatorEvent) async { - switch event { - case let .credentialsNeeded(target): - guard let grid = grids[target.spaceID] else { return } - await prepareConnectionIfNeeded(grid: grid) - case let .connected(_, rtcConnectMilliseconds): - guard let startedAt = mediaInteractionStartedAt else { return } - mediaInteractionStartedAt = nil - let totalMilliseconds = Self.elapsedMilliseconds(since: startedAt) - PerformanceTrace.breadcrumb( - "Grid connection ready", - category: "Grid.Connection", - level: totalMilliseconds >= 3_000 ? .warning : .info, - data: [ - "click_to_connected_ms": totalMilliseconds, - "rtc_connect_ms": rtcConnectMilliseconds ?? -1, - ] - ) - log.debug( - "GRID_TRACE phase=click_to_connected elapsed_ms=\(totalMilliseconds) rtc_ms=\(rtcConnectMilliseconds ?? -1)" - ) - case .screenShareContextChanged: - reconcileScreenSharePresentationEdges() - reconcileScreenShareAloneGrace() - } - } - - private func handle(_ event: GridMembershipSyncEvent) async { - let operation: GridMembershipOperation - switch event { - case let .created(value, _), let .joined(value, _), let .left(value, _), let .failed(value, _): - operation = value - } - - let mutation = pendingMembershipMutations.removeValue(forKey: operation.revision) - let isLatestIntent = operation.revision == membershipMutationRevision - guard operation.accessRevision == spaceAccessRevisions[operation.spaceID, default: 0] else { - if isLatestIntent { - mediaCoordinator.restoreAutomaticMicrophoneChangeIfCurrent( - operation.automaticMicrophoneChange - ) - } - return - } - - switch event { - case let .created(_, response): - mediaCoordinator.commitAutomaticMicrophoneChange(operation.automaticMicrophoneChange) - updateMembershipRollbackBaseline(with: response.grids) - await apply( - grids: response.grids, - credentials: isLatestIntent ? response.credentials : nil - ) - log.debug( - "GRID_TRACE phase=create_client_done space=\(operation.spaceID) elapsed_ms=\(Self.elapsedMilliseconds(since: operation.startedAt))" - ) - case let .joined(_, response): - mediaCoordinator.commitAutomaticMicrophoneChange(operation.automaticMicrophoneChange) - updateMembershipRollbackBaseline(with: response.grids) - await apply( - grids: response.grids, - credentials: isLatestIntent ? response.credentials : nil - ) - log.debug( - "GRID_TRACE phase=join_client_done room=\(operationRoomID(operation) ?? 0) elapsed_ms=\(Self.elapsedMilliseconds(since: operation.startedAt))" - ) - case let .left(_, nextGrids): - updateMembershipRollbackBaseline(with: nextGrids) - applySnapshot(nextGrids) - case let .failed(_, message): - mediaInteractionStartedAt = nil - if isLatestIntent { - if let mutation { - rollbackMembershipMutation(mutation) - } - if mediaCoordinator.restoreAutomaticMicrophoneChangeIfCurrent( - operation.automaticMicrophoneChange - ), let restoredGrid = grids.values.first(where: { $0.hasCurrentRoomID }) { - syncMicrophoneStateIfNeeded(grid: restoredGrid) - reconcileMediaDemand() - } - if case .leave = operation.kind, let restoredGrid = grids[operation.spaceID] { - await prepareConnectionIfNeeded(grid: restoredGrid) - } - } - lastError = message - log.warning( - "GRID_TRACE phase=membership_client_failed revision=\(operation.revision) elapsed_ms=\(Self.elapsedMilliseconds(since: operation.startedAt))" - ) - } - if pendingMembershipMutations.isEmpty { - membershipRollbackBaseline.clear() - } - } - - private func handle(_ event: GridEvent) async { - switch event.event { - case let .changed(changed): - if let target = aloneAutoMuteTarget, - changed.spaceIds.contains(target.spaceID) - || (changed.hasRoomID && changed.roomID == target.roomID) { - cancelAloneAutoMute() - } - log.debug( - "GRID_TRACE phase=changed_event_received spaces=\(changed.spaceIds.map { String($0) }.joined(separator: ",")) room=\(changed.hasRoomID ? String(changed.roomID) : "none")" - ) - for spaceID in changed.spaceIds { - if enabledSpaceIDs.contains(spaceID) { - try? await reloadGrid(spaceID: spaceID) - } else { - // A Grid-changed event is also the cross-session enablement signal. - // Refresh settings when this process still believes Grid is disabled. - await load(spaceID: spaceID) - } - } - await loadHome() - case let .connectionReady(ready): - guard ready.hasCredentials else { return } - log.debug( - "GRID_TRACE phase=ready_event_received room=\(ready.credentials.connection.roomID) generation=\(ready.credentials.connection.generation)" - ) - await accept(ready.credentials) - case let .avatarStateChanged(changed): - log.debug( - "GRID_TRACE phase=avatar_state_event_received room=\(changed.roomID) user=\(changed.userID) microphone=\(changed.microphoneEnabled) revision=\(changed.microphoneRevision)" - ) - applyRemoteAvatarMicrophoneState( - spaceID: changed.spaceID, - roomID: changed.roomID, - userID: changed.userID, - enabled: changed.microphoneEnabled, - membershipID: changed.membershipID, - revision: changed.microphoneRevision - ) - case let .accessRevoked(revoked): - clearLocalGrid(spaceID: revoked.spaceID, reason: "server_revoked") - case nil: - break - } - } - - private func clearLocalGrid(spaceID: Int64, reason: String) { - let hadMediaDemand = currentMediaTarget()?.spaceID == spaceID - spaceAccessRevisions[spaceID, default: 0] &+= 1 - homeLoadRevision &+= 1 - pendingMembershipMutations = pendingMembershipMutations.filter { _, mutation in - mutation.spaceID != spaceID - } - membershipRollbackBaseline.remove(spaceID: spaceID) - if pendingMembershipMutations.isEmpty { - membershipRollbackBaseline.clear() - } - roomMutationRevisions = roomMutationRevisions.filter { key, _ in - grids[spaceID]?.rooms.contains(where: { $0.id == key.roomID }) != true - } - pendingRoomMutations = pendingRoomMutations.filter { _, mutation in - mutation.change.spaceID != spaceID - } - grids.removeValue(forKey: spaceID) - enabledSpaceIDs.remove(spaceID) - homeSpaces.removeAll { $0.spaceID == spaceID } - pendingReloadSpaceIDs.remove(spaceID) - pendingConnectionSpaceIDs.remove(spaceID) - failedLoadSpaceIDs.remove(spaceID) - reconcileAvatarStateOwnership() - - if hadMediaDemand { - pendingCredentialTarget = nil - resetCredentialRetry() - mediaInteractionStartedAt = nil - mediaCoordinator.clearCredentials() - } - reconcileMediaDemand() - log.info("GRID_TRACE phase=local_grid_cleared space=\(spaceID) reason=\(reason)") - PerformanceTrace.breadcrumb( - "Local Grid access cleared", - category: "Grid.Access", - level: .info, - data: ["space_id": spaceID, "reason": reason] - ) - } - - private func prepareConnectionIfNeeded(grid: InlineProtocol.Grid) async { - guard grid.hasCurrentRoomID, - let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), - room.hasConnection, - room.avatars.contains(where: \.ownedByCurrentSession) - else { - reconcileMediaDemand() - return - } - let target = GridMediaTarget( - spaceID: grid.spaceID, - roomID: room.id, - generation: room.connection.generation - ) - reconcileMediaDemand() - // A LiveKit token is only admission material. Once this exact generation - // has an established transport, refreshing an expired token on every - // presence heartbeat adds server work without helping the active room. - // A later disconnect clears this state; the RTC engine then requests fresh - // credentials before its next connection attempt. - if mediaCoordinator.isConnected(to: target) { - resetCredentialRetry() - return - } - if mediaCoordinator.hasUsableCredentials(for: target) { - resetCredentialRetry() - return - } - mediaCoordinator.invalidateCredentials(for: target) - guard pendingCredentialTarget != target else { return } - - pendingCredentialTarget = target - defer { - if pendingCredentialTarget == target { - pendingCredentialTarget = nil - } - } - let startedAt = Date() - log.debug( - "GRID_ENGINE phase=credentials_fetch_start room=\(room.id) generation=\(room.connection.generation)" - ) - do { - guard let connection = try await api.prepareConnection( - roomID: room.id, - generation: room.connection.generation - ) else { - log.debug( - "GRID_ENGINE phase=credentials_fetch_unavailable room=\(room.id) generation=\(room.connection.generation) elapsed_ms=\(Self.elapsedMilliseconds(since: startedAt))" - ) - scheduleCredentialRetry(for: target) - return - } - log.debug( - "GRID_ENGINE phase=credentials_fetch_done room=\(room.id) generation=\(room.connection.generation) elapsed_ms=\(Self.elapsedMilliseconds(since: startedAt))" - ) - await accept(connection) - } catch { - lastError = String(describing: error) - log.error( - "GRID_ENGINE phase=credentials_fetch_failed room=\(room.id) generation=\(room.connection.generation)", - error: error - ) - scheduleCredentialRetry(for: target) - } - } - - private func accept(_ credentials: GridConnectionCredentials) async { - guard credentials.hasConnection, - let serverURL = URL(string: credentials.serverURL), - let target = currentMediaTarget(), - target.roomID == credentials.connection.roomID, - target.generation == credentials.connection.generation - else { - log.debug( - "GRID_ENGINE phase=credentials_ignored_stale room=\(credentials.connection.roomID) generation=\(credentials.connection.generation)" - ) - return - } - let accepted = mediaCoordinator.accept(InlineRTCCredentials( - target: target.rtcSessionID, - serverURL: serverURL, - participantIdentity: credentials.participantIdentity, - token: credentials.token, - expiresAt: Date(timeIntervalSince1970: TimeInterval(credentials.expiresAt)) - )) - guard accepted else { return } - resetCredentialRetry() - log.debug( - "GRID_ENGINE phase=credentials_accepted room=\(target.roomID) generation=\(target.generation)" - ) - } - - private func scheduleCredentialRetry(for target: GridMediaTarget) { - guard currentMediaTarget() == target, - credentialRetryTask == nil - else { return } - if credentialRetryTarget != target { - credentialRetryTarget = target - credentialRetryAttempt = 0 - } - credentialRetryAttempt &+= 1 - let attempt = credentialRetryAttempt - let delay = Self.credentialRetryDelay(attempt: attempt) - log.debug( - "GRID_ENGINE phase=credentials_retry_scheduled room=\(target.roomID) generation=\(target.generation) attempt=\(attempt) delay_s=\(delay)" - ) - credentialRetryTask = Task { [weak self] in - try? await Task.sleep(for: .seconds(delay)) - guard !Task.isCancelled else { return } - await self?.credentialRetryFired(target: target, attempt: attempt) - } - } - - private func credentialRetryFired(target: GridMediaTarget, attempt: Int) async { - guard credentialRetryTarget == target, - credentialRetryAttempt == attempt - else { return } - credentialRetryTask = nil - guard currentMediaTarget() == target, - let grid = grids[target.spaceID] - else { - resetCredentialRetry() - return - } - await prepareConnectionIfNeeded(grid: grid) - } - - private func resetCredentialRetry() { - credentialRetryTask?.cancel() - credentialRetryTask = nil - credentialRetryTarget = nil - credentialRetryAttempt = 0 - } - - private func refreshOwnedPresence() async { - for grid in grids.values where grid.hasCurrentRoomID { - try? await reloadGrid(spaceID: grid.spaceID) - return - } - } - - private func syncMicrophoneStateIfNeeded(grid: InlineProtocol.Grid) { - guard grid.hasCurrentRoomID, - let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), - let avatar = room.avatars.first(where: \.ownedByCurrentSession) - else { return } - let microphoneEnabled = mediaCoordinator.isMicrophoneEnabled - guard avatar.microphoneEnabled != microphoneEnabled else { return } - applyAvatarMicrophoneState( - spaceID: grid.spaceID, - roomID: room.id, - userID: nil, - enabled: microphoneEnabled - ) - avatarStateSync.submitMicrophoneState(roomID: room.id, enabled: microphoneEnabled) - } - - private func applyAvatarMicrophoneState( - spaceID: Int64, - roomID: Int64, - userID: Int64?, - enabled: Bool, - revision: Int32? = nil - ) { - guard var grid = grids[spaceID], - let roomIndex = grid.rooms.firstIndex(where: { $0.id == roomID }), - let avatarIndex = grid.rooms[roomIndex].avatars.firstIndex(where: { avatar in - if let userID { return avatar.user.id == userID } - return avatar.ownedByCurrentSession - }) - else { return } - grid.rooms[roomIndex].avatars[avatarIndex].microphoneEnabled = enabled - if let revision { - grid.rooms[roomIndex].avatars[avatarIndex].microphoneRevision = revision - } - grids[spaceID] = grid - } - - /// Inline's local microphone intent remains authoritative for the avatar - /// owned by this process. A delayed echo from an earlier rapid toggle must - /// not repaint that avatar with stale server state; other avatars always use - /// the transient state sent by their owning session. - private func applyRemoteAvatarMicrophoneState( - spaceID: Int64, - roomID: Int64, - userID: Int64, - enabled: Bool, - membershipID: String, - revision: Int32 - ) { - guard let grid = grids[spaceID], - let room = grid.rooms.first(where: { $0.id == roomID }), - let avatar = room.avatars.first(where: { $0.user.id == userID }), - avatar.membershipID == membershipID, - revision >= avatar.microphoneRevision - else { return } - applyAvatarMicrophoneState( - spaceID: spaceID, - roomID: roomID, - userID: userID, - enabled: avatar.ownedByCurrentSession ? mediaCoordinator.isMicrophoneEnabled : enabled, - revision: revision - ) - } - - private func handleConnectionState(_ state: RealtimeConnectionState) async { - let previousState = lastRealtimeConnectionState - lastRealtimeConnectionState = state - switch state { - case .connecting: - // Inline realtime and LiveKit have independent transports. Keep media - // alive while the app server reconnects; LiveKit handles its own recovery. - break - case .connected, .updating: - if previousState == nil || previousState == .connecting { - networkRefreshRevision &+= 1 - avatarStateSync.retryLatestFailure() - mediaCoordinator.networkBecameAvailable() - await loadHome() - await retryPendingConnectionLoads() - await refreshOwnedPresence() - } - } - } - - private var realtimeReady: Bool { - switch lastRealtimeConnectionState { - case .connected, .updating: - true - case .connecting, nil: - false - } - } - - private func handleNetworkPath(_ snapshot: GridNetworkPathSnapshot) async { - let previousSnapshot = lastNetworkPathSnapshot - lastNetworkPathSnapshot = snapshot - networkAvailable = snapshot.available - log.debug( - "GRID_TRACE phase=network_path_changed available=\(snapshot.available) interface=\(snapshot.interfaceDescription) constrained=\(snapshot.constrained) expensive=\(snapshot.expensive)" - ) - if previousSnapshot == nil { - return - } - if snapshot.available { - networkRefreshRevision &+= 1 - avatarStateSync.retryLatestFailure() - mediaCoordinator.networkBecameAvailable() - await loadHome() - await retryPendingConnectionLoads() - await refreshOwnedPresence() - } - } - - private func retryPendingConnectionLoads() async { - let deferredSpaceIDs = pendingConnectionSpaceIDs.sorted() - pendingConnectionSpaceIDs.removeAll() - for spaceID in deferredSpaceIDs { - await load(spaceID: spaceID) - } - } - - func applicationDidWake() async { - avatarStateSync.retryLatestFailure() - mediaCoordinator.applicationDidWake() - await loadHome() - await refreshOwnedPresence() - } - - private func reconcileAvatarStateOwnership() { - let ownedRoomID = grids.values.lazy - .filter(\.hasCurrentRoomID) - .compactMap { grid -> Int64? in - guard let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), - room.avatars.contains(where: \.ownedByCurrentSession) - else { return nil } - return room.id - } - .first - avatarStateSync.setOwnedRoom(ownedRoomID) - } - - private func currentMediaTarget() -> GridMediaTarget? { - for grid in grids.values where grid.hasCurrentRoomID { - guard let room = grid.rooms.first(where: { $0.id == grid.currentRoomID }), - room.hasConnection, - room.avatars.contains(where: \.ownedByCurrentSession) - else { continue } - return GridMediaTarget( - spaceID: grid.spaceID, - roomID: room.id, - generation: room.connection.generation - ) - } - return nil - } - - private func isOwnedUser(_ userID: Int64) -> Bool { - grids.values.lazy - .flatMap(\.rooms) - .flatMap(\.avatars) - .contains { $0.ownedByCurrentSession && $0.user.id == userID } - } - - private static func liveKitIdentityPrefix(userID: Int64) -> String { - "inline-grid-user-\(userID)" - } - - private static func identity(_ identity: String, matchesUserID userID: Int64) -> Bool { - let prefix = liveKitIdentityPrefix(userID: userID) - return identity == prefix || identity.hasPrefix("\(prefix)-") - } - - private func participantIdentities(for avatar: GridAvatar) -> Set { - if !avatar.membershipID.isEmpty { - return [ - "\(Self.liveKitIdentityPrefix(userID: avatar.user.id))-\(avatar.membershipID)", - ] - } - return Set( - media.connectedParticipantIdentities.filter { - Self.identity($0, matchesUserID: avatar.user.id) - } - ) - } - - private func screenShareParticipantIdentity(for avatar: GridAvatar) -> String? { - let identities = participantIdentities(for: avatar) - if let identity = identities.sorted().first(where: { - media.participantScreenShareIntents[$0] == true - }) { - return identity - } - if let identity = screenShares(avatar: avatar).first?.participantIdentity { - return identity - } - return identities.sorted().first - } - - private func openScreenShare( - user: InlineProtocol.User, - participantIdentity: String - ) { - if let share = screenShares(participantIdentity: participantIdentity).first { - GridScreenShareWindowCoordinator.shared.open( - user: user, - share: share, - store: self - ) - return - } - guard isScreenShareIntended(participantIdentity: participantIdentity) else { return } - - pendingScreenShareOpenTasks.removeValue(forKey: participantIdentity)?.task.cancel() - let requestID = UUID() - let task = Task { [weak self] in - for _ in 0 ..< 80 { - guard !Task.isCancelled, let self else { return } - if let share = screenShares(participantIdentity: participantIdentity).first { - finishPendingScreenShareOpen( - requestID: requestID, - participantIdentity: participantIdentity - ) - GridScreenShareWindowCoordinator.shared.open( - user: user, - share: share, - store: self - ) - return - } - guard isScreenShareIntended(participantIdentity: participantIdentity) else { - finishPendingScreenShareOpen( - requestID: requestID, - participantIdentity: participantIdentity - ) - return - } - try? await Task.sleep(for: .milliseconds(100)) - } - guard !Task.isCancelled, let self else { return } - finishPendingScreenShareOpen( - requestID: requestID, - participantIdentity: participantIdentity - ) - if isScreenShareIntended(participantIdentity: participantIdentity) { - ToastCenter.shared.showError("The shared screen hasn’t arrived yet") - } - } - pendingScreenShareOpenTasks[participantIdentity] = GridPendingScreenShareOpen( - id: requestID, - task: task - ) - } - - private func finishPendingScreenShareOpen( - requestID: UUID, - participantIdentity: String - ) { - guard pendingScreenShareOpenTasks[participantIdentity]?.id == requestID else { return } - pendingScreenShareOpenTasks[participantIdentity] = nil - } - - private static func displayIDUnderPointer() -> UInt32? { - let mouseLocation = NSEvent.mouseLocation - guard let screen = NSScreen.screens.first(where: { - NSMouseInRect(mouseLocation, $0.frame, false) - }), - let screenNumber = screen.deviceDescription[ - NSDeviceDescriptionKey("NSScreenNumber") - ] as? NSNumber - else { return CGMainDisplayID() } - return screenNumber.uint32Value - } - - func setInputSelection(_ selection: AudioInputSelection) { - mediaCoordinator.setInput(selection) - } - - func setOutputSelection(_ selection: AudioOutputSelection) { - mediaCoordinator.setOutput(selection) - } - - func setOutputVolume(_ volume: Float) { - mediaCoordinator.setOutputVolume(volume) - } - - func refreshInputDevices() { - mediaCoordinator.refreshInputDevices() - } - - func refreshOutputDevices() { - mediaCoordinator.refreshOutputDevices() - } - - func retryAudio() { - mediaCoordinator.retryAudio() - } - - private static func elapsedMilliseconds(since date: Date) -> Int { - Int(Date().timeIntervalSince(date) * 1_000) - } - - private static func credentialRetryDelay(attempt: Int) -> Int { - switch attempt { - case 1: 1 - case 2: 2 - case 3: 4 - case 4: 8 - case 5: 15 - default: 30 - } - } - - private func operationRoomID(_ operation: GridMembershipOperation) -> Int64? { - switch operation.kind { - case .create: nil - case let .join(roomID), let .leave(roomID): roomID - } - } - - private func applyPendingMembershipIntent() { - guard let mutation = pendingMembershipMutations[membershipMutationRevision], - grids[mutation.spaceID] != nil - else { return } - - let change: GridOptimisticState.MembershipChange? - switch mutation.intent { - case let .join(fallbackAvatar, joinedAt): - change = GridOptimisticState.joining( - roomID: mutation.roomID, - in: grids, - avatar: fallbackAvatar, - microphoneEnabled: mediaCoordinator.isMicrophoneEnabled, - joinedAt: joinedAt - ) - case .leave: - change = GridOptimisticState.leaving(spaceID: mutation.spaceID, in: grids) - } - guard let change else { return } - for (spaceID, grid) in change.nextGrids { grids[spaceID] = grid } - } - - private func updateMembershipRollbackBaseline(with snapshots: [InlineProtocol.Grid]) { - let confirmedSnapshots = snapshots.filter { snapshot in - let previousRevision = lastAcceptedSnapshotRevisions[snapshot.spaceID] - if let previousRevision { return snapshot.revision >= previousRevision } - return true - } - membershipRollbackBaseline.mergeConfirmed(confirmedSnapshots) - } - -} - -private struct OptimisticRoomMutationKey: Hashable { - let roomID: Int64 - let field: GridOptimisticState.RoomField -} - -private struct GridAloneAutoMuteTarget: Equatable { - let spaceID: Int64 - let roomID: Int64 - let userID: Int64 -} - -private struct GridScreenShareAloneTarget: Equatable { - let mediaTarget: GridMediaTarget - let episodeID: UInt64 - let publicationIDs: Set -} - -private struct GridScreenShareNotice { - let target: GridMediaTarget - let user: InlineProtocol.User - let participantIdentity: String -} - -private struct GridPendingScreenShareOpen { - let id: UUID - let task: Task -} - -private struct OptimisticRoomMutation { - let key: OptimisticRoomMutationKey - let revision: Int - let accessRevision: Int - let change: GridOptimisticState.RoomChange -} - -private struct OptimisticMembershipMutation { - enum Intent { - case join(fallbackAvatar: GridAvatar?, joinedAt: Int64) - case leave - } - - let roomID: Int64 - let spaceID: Int64 - let revision: Int - let previousGrids: [Int64: InlineProtocol.Grid] - let intent: Intent -} +// Grid product state is shared by the iOS and macOS clients. +import InlineGrid diff --git a/apple/InlineMac/Features/Grid/GridView.swift b/apple/InlineMac/Features/Grid/GridView.swift index ce9792887..0fe93401a 100644 --- a/apple/InlineMac/Features/Grid/GridView.swift +++ b/apple/InlineMac/Features/Grid/GridView.swift @@ -1,3 +1,4 @@ +import InlineGrid import InlineKit import InlineProtocol import SwiftUI @@ -16,12 +17,21 @@ struct GridView: View { grid: store.grid(spaceID: spaceID), isLoading: store.loadingSpaceIDs.contains(spaceID), didFailLoading: store.failedLoadSpaceIDs.contains(spaceID), + hasLocalAdmission: hasLocalAdmissionInSpace, + isJoiningDisabled: store.membershipMutationInFlight + || (store.callTransferEnabled && store.currentCall?.ownedByCurrentSession == false), audioLevel: store.audioLevel, isScreenSharing: { store.isScreenSharing(avatar: $0) }, connectionState: store.connectionRecoveryAttempt > 0 ? .connecting : store.media.connectionState, onCreate: { store.createAndJoin(spaceID: spaceID) }, onRetry: { Task { await store.load(spaceID: spaceID) } }, - onJoin: { roomID in store.join(roomID: roomID) }, + onJoin: { roomID in + if store.callTransferEnabled, store.currentCall?.roomID == roomID { + store.moveCallHere() + } else { + store.join(roomID: roomID) + } + }, onLeave: { store.leaveCurrentRoom(spaceID: spaceID) }, onToggleMicrophone: { store.toggleMicrophone(spaceID: spaceID) }, onOpenScreenShare: { store.openScreenShare(for: $0) }, @@ -66,7 +76,7 @@ struct GridView: View { } } .safeAreaInset(edge: .bottom) { - if let currentRoom { + if let currentRoom, hasLocalAdmissionInSpace { VStack(spacing: 8) { if currentRoom.transcriptionAvailable || currentRoom.hasTranscription || currentRoom.hasRoomThreadID { GridTranscriptionControls( @@ -106,6 +116,29 @@ struct GridView: View { } .padding(.bottom, 18) .transition(.move(edge: .bottom).combined(with: .opacity)) + } else if store.callTransferEnabled, let call = store.currentCall, call.spaceID == spaceID { + HStack(spacing: 12) { + Button(call.ownedByCurrentSession ? "Resume here" : "Move here", systemImage: "arrow.down.to.line") { + store.moveCallHere() + } + .disabled(store.membershipMutationInFlight) + if store.membershipMutationInFlight { + Button("Cancel") { store.withdrawLocalAdmission() } + } + } + .padding(.bottom, 18) + } else if let room = currentRoom { + HStack(spacing: 12) { + Button("Resume here", systemImage: "arrow.down.to.line") { store.join(roomID: room.id) } + .disabled(store.membershipMutationInFlight) + if store.membershipMutationInFlight { + Button("Cancel") { store.withdrawLocalAdmission() } + } + } + .padding(.bottom, 18) + } else if store.membershipMutationInFlight { + Button("Cancel") { store.withdrawLocalAdmission() } + .padding(.bottom, 18) } } .animation(.smoothSnappy, value: currentRoom?.id) @@ -115,10 +148,21 @@ struct GridView: View { } private var currentRoom: GridRoom? { - guard let grid = store.grid(spaceID: spaceID), grid.hasCurrentRoomID else { return nil } + guard let grid = store.grid(spaceID: spaceID) else { return nil } + if store.callTransferEnabled { + guard let call = store.currentCall, call.spaceID == spaceID else { return nil } + return grid.rooms.first { $0.id == call.roomID } + } + guard grid.hasCurrentRoomID else { return nil } return grid.rooms.first { $0.id == grid.currentRoomID } } + private var hasLocalAdmissionInSpace: Bool { + guard store.hasLocalAdmission else { return false } + guard store.callTransferEnabled else { return true } + return store.currentCall?.ownedByCurrentSession == true && store.currentCall?.spaceID == spaceID + } + private var pageTitle: String { guard let spaceName = sidebar.space(id: spaceID)?.displayName, spaceName.isEmpty == false diff --git a/apple/InlineMac/Features/Sidebar/SidebarView.swift b/apple/InlineMac/Features/Sidebar/SidebarView.swift index b982fbb2f..588f10715 100644 --- a/apple/InlineMac/Features/Sidebar/SidebarView.swift +++ b/apple/InlineMac/Features/Sidebar/SidebarView.swift @@ -1,3 +1,4 @@ +import InlineGrid import AppKit import Auth import Foundation diff --git a/apple/InlineMac/Views/MessageList/ServiceMessageRowView.swift b/apple/InlineMac/Views/MessageList/ServiceMessageRowView.swift index 31412ffea..02fc2da67 100644 --- a/apple/InlineMac/Views/MessageList/ServiceMessageRowView.swift +++ b/apple/InlineMac/Views/MessageList/ServiceMessageRowView.swift @@ -1,5 +1,6 @@ import AppKit import InlineKit +import RealtimeV2 enum ServiceMessageRowLayout { struct Plan: Equatable { @@ -134,7 +135,7 @@ enum ServiceMessageRowLayout { } } -final class ServiceMessageViewAppKit: NSView, MessageTableRenderableView { +final class ServiceMessageViewAppKit: NSView, MessageTableRenderableView, NSMenuItemValidation { private let containerView = NSView() private lazy var textView = ServiceMessageTextView { [weak self] link in self?.open(link) @@ -265,14 +266,37 @@ final class ServiceMessageViewAppKit: NSView, MessageTableRenderableView { NSPasteboard.general.setString(fullMessage.message.text ?? "", forType: .string) } + func validateMenuItem(_ menuItem: NSMenuItem) -> Bool { + guard menuItem.action == #selector(deleteMessage) else { return true } + // This row has no Space role projection. Keep server authority for signed-in + // readers rather than fetching membership while constructing a menu. + return dependencies?.auth.getCurrentUserId() != nil + } + @objc private func deleteMessage() { let message = fullMessage.message Task(priority: .userInitiated) { @MainActor in - try await Api.realtime.send(.deleteMessages( - messageIds: [message.messageId], - peerId: message.peerId, - chatId: message.chatId - )) + do { + try await Api.realtime.send(DeleteMessageTransaction( + messageIds: [message.messageId], + peerId: message.peerId, + chatId: message.chatId, + deferLocalDeletion: message.isGridTranscript + )) + } catch { + let notice: String + if let failure = error as? TransactionError, + case let .rpcError(rpc) = failure, + rpc.errorCode == .spaceAdminRequired + { + notice = message.isGridTranscript + ? "Only the person who started transcription or a space admin can delete this message." + : "Only the author or a space admin can delete this message." + } else { + notice = "Could not delete message. Please try again." + } + ToastCenter.shared.showError(notice) + } } } diff --git a/apple/InlineMac/Views/Settings/Views/AudioAndVideoSettingsDetailView.swift b/apple/InlineMac/Views/Settings/Views/AudioAndVideoSettingsDetailView.swift index 6db66795f..aa53321a1 100644 --- a/apple/InlineMac/Views/Settings/Views/AudioAndVideoSettingsDetailView.swift +++ b/apple/InlineMac/Views/Settings/Views/AudioAndVideoSettingsDetailView.swift @@ -1,3 +1,4 @@ +import InlineGrid import InlineRTC import SwiftUI diff --git a/apple/InlineMac/Views/SpaceSettings/SpaceSettingsView.swift b/apple/InlineMac/Views/SpaceSettings/SpaceSettingsView.swift index 87b300684..ce1779ea1 100644 --- a/apple/InlineMac/Views/SpaceSettings/SpaceSettingsView.swift +++ b/apple/InlineMac/Views/SpaceSettings/SpaceSettingsView.swift @@ -1,3 +1,4 @@ +import InlineGrid import Foundation import InlineKit import InlineUI From c01a8b68fe5404646ca7fb4de1b76dbf5e67cd62 Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Tue, 6 Oct 2026 19:41:16 +0330 Subject: [PATCH 08/14] rust: align message fixture with quiet transcript contract --- crates/client/src/sdk_backend.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/client/src/sdk_backend.rs b/crates/client/src/sdk_backend.rs index 602ce5e59..208ebaccc 100644 --- a/crates/client/src/sdk_backend.rs +++ b/crates/client/src/sdk_backend.rs @@ -9615,6 +9615,7 @@ mod tests { block_content: None, agent_session: None, subthread: None, + counts_as_unread: None, }), })), }], From 9c2e5e703dc503137e2c70ccb5b6b63ce7c995d7 Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Tue, 6 Oct 2026 19:41:17 +0330 Subject: [PATCH 09/14] apple: align grid authority tests with current contracts --- .../InlineGridTests/GridAuthAuthorityRetirementTests.swift | 2 +- .../Tests/InlineGridTests/GridAuthorityTestSupport.swift | 4 ++++ .../InlineGridTests/GridPendingAuthorityRetirementTests.swift | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/apple/InlineKit/Tests/InlineGridTests/GridAuthAuthorityRetirementTests.swift b/apple/InlineKit/Tests/InlineGridTests/GridAuthAuthorityRetirementTests.swift index 2064e965d..25265c7fc 100644 --- a/apple/InlineKit/Tests/InlineGridTests/GridAuthAuthorityRetirementTests.swift +++ b/apple/InlineKit/Tests/InlineGridTests/GridAuthAuthorityRetirementTests.swift @@ -151,7 +151,7 @@ struct GridAuthAuthorityRetirementTests { let key = Array((UInt8.min...UInt8.max).reversed()) credentials.temporary = try InlineProtocolAuthorization( key: key, keyID: InlineSecureTransport.authKeyID(key), serverSalt: 8, - temporary: true, expiresAt: Int64(Date().addingTimeInterval(3600).timeIntervalSince1970) + temporary: true, expiresAt: Int32(Date().addingTimeInterval(3600).timeIntervalSince1970) ) try await fixture.auth.saveInlineProtocolCredentials(credentials) } diff --git a/apple/InlineKit/Tests/InlineGridTests/GridAuthorityTestSupport.swift b/apple/InlineKit/Tests/InlineGridTests/GridAuthorityTestSupport.swift index bc8e36564..348424419 100644 --- a/apple/InlineKit/Tests/InlineGridTests/GridAuthorityTestSupport.swift +++ b/apple/InlineKit/Tests/InlineGridTests/GridAuthorityTestSupport.swift @@ -412,6 +412,10 @@ private struct GridAuthorityNoUpdates: ApplyUpdates { } private struct GridAuthoritySyncStorage: SyncStorage { + func canonicalPeer(forChatID _: Int64) async throws -> Peer? { + nil + } + func getState() async throws -> SyncState { .init(lastSyncDate: 0) } diff --git a/apple/InlineKit/Tests/InlineGridTests/GridPendingAuthorityRetirementTests.swift b/apple/InlineKit/Tests/InlineGridTests/GridPendingAuthorityRetirementTests.swift index 151950092..1ffcecad0 100644 --- a/apple/InlineKit/Tests/InlineGridTests/GridPendingAuthorityRetirementTests.swift +++ b/apple/InlineKit/Tests/InlineGridTests/GridPendingAuthorityRetirementTests.swift @@ -19,6 +19,7 @@ struct GridPendingAuthorityRetirementTests { } } + @MainActor func submit(to service: GridRoomService) { switch self { case .create: service.createAndJoin(spaceID: 42) From e41f6085c5cd55cf586664d760674f8119f2929a Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Tue, 6 Oct 2026 19:41:18 +0330 Subject: [PATCH 10/14] server: update sharp to patched 0.35.5 --- bun.lock | 56 ++++++++++++++++++++++----------------------- server/package.json | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/bun.lock b/bun.lock index 62626049c..611b2bf06 100644 --- a/bun.lock +++ b/bun.lock @@ -338,7 +338,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "resend": "^6.18.1", - "sharp": "0.35.4", + "sharp": "0.35.5", "tiny-invariant": "^1.3.3", "zod": "^4.3.6", }, @@ -704,57 +704,57 @@ "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA=="], + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg=="], - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.3" }, "os": "darwin", "cpu": "x64" }, "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw=="], + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.4" }, "os": "darwin", "cpu": "x64" }, "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw=="], - "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.4", "", { "dependencies": { "@img/sharp-wasm32": "0.35.4" }, "os": "freebsd" }, "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA=="], + "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.5", "", { "dependencies": { "@img/sharp-wasm32": "0.35.5" }, "os": "freebsd" }, "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw=="], - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg=="], + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw=="], - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw=="], + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ=="], - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA=="], + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.4", "", { "os": "linux", "cpu": "arm" }, "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw=="], - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A=="], + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A=="], - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w=="], + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ=="], - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.3", "", { "os": "linux", "cpu": "none" }, "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ=="], + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.4", "", { "os": "linux", "cpu": "none" }, "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q=="], - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q=="], + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ=="], - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA=="], + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw=="], - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw=="], + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA=="], - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw=="], + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg=="], - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.3" }, "os": "linux", "cpu": "arm" }, "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A=="], + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.4" }, "os": "linux", "cpu": "arm" }, "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ=="], - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.3" }, "os": "linux", "cpu": "arm64" }, "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ=="], + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.4" }, "os": "linux", "cpu": "arm64" }, "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ=="], - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ=="], + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA=="], - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.3" }, "os": "linux", "cpu": "none" }, "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA=="], + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.4" }, "os": "linux", "cpu": "none" }, "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ=="], - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.3" }, "os": "linux", "cpu": "s390x" }, "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ=="], + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.4" }, "os": "linux", "cpu": "s390x" }, "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA=="], - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.3" }, "os": "linux", "cpu": "x64" }, "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA=="], + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.4" }, "os": "linux", "cpu": "x64" }, "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog=="], - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" }, "os": "linux", "cpu": "arm64" }, "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA=="], + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" }, "os": "linux", "cpu": "arm64" }, "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg=="], - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.3" }, "os": "linux", "cpu": "x64" }, "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg=="], + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.4" }, "os": "linux", "cpu": "x64" }, "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A=="], - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.4", "", { "dependencies": { "@emnapi/runtime": "^1.11.3" } }, "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA=="], + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.5", "", { "dependencies": { "@emnapi/runtime": "^1.11.3" } }, "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ=="], - "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.4", "", { "dependencies": { "@img/sharp-wasm32": "0.35.4" }, "cpu": "none" }, "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA=="], + "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.5", "", { "dependencies": { "@img/sharp-wasm32": "0.35.5" }, "cpu": "none" }, "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA=="], - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw=="], + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q=="], - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw=="], + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A=="], - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.4", "", { "os": "win32", "cpu": "x64" }, "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg=="], + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.5", "", { "os": "win32", "cpu": "x64" }, "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg=="], "@in/protocol": ["@in/protocol@workspace:landing/packages/protocol"], @@ -2590,7 +2590,7 @@ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], - "sharp": ["sharp@0.35.4", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.4", "@img/sharp-darwin-x64": "0.35.4", "@img/sharp-freebsd-wasm32": "0.35.4", "@img/sharp-libvips-darwin-arm64": "1.3.3", "@img/sharp-libvips-darwin-x64": "1.3.3", "@img/sharp-libvips-linux-arm": "1.3.3", "@img/sharp-libvips-linux-arm64": "1.3.3", "@img/sharp-libvips-linux-ppc64": "1.3.3", "@img/sharp-libvips-linux-riscv64": "1.3.3", "@img/sharp-libvips-linux-s390x": "1.3.3", "@img/sharp-libvips-linux-x64": "1.3.3", "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", "@img/sharp-libvips-linuxmusl-x64": "1.3.3", "@img/sharp-linux-arm": "0.35.4", "@img/sharp-linux-arm64": "0.35.4", "@img/sharp-linux-ppc64": "0.35.4", "@img/sharp-linux-riscv64": "0.35.4", "@img/sharp-linux-s390x": "0.35.4", "@img/sharp-linux-x64": "0.35.4", "@img/sharp-linuxmusl-arm64": "0.35.4", "@img/sharp-linuxmusl-x64": "0.35.4", "@img/sharp-webcontainers-wasm32": "0.35.4", "@img/sharp-win32-arm64": "0.35.4", "@img/sharp-win32-ia32": "0.35.4", "@img/sharp-win32-x64": "0.35.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA=="], + "sharp": ["sharp@0.35.5", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.5", "@img/sharp-darwin-x64": "0.35.5", "@img/sharp-freebsd-wasm32": "0.35.5", "@img/sharp-libvips-darwin-arm64": "1.3.4", "@img/sharp-libvips-darwin-x64": "1.3.4", "@img/sharp-libvips-linux-arm": "1.3.4", "@img/sharp-libvips-linux-arm64": "1.3.4", "@img/sharp-libvips-linux-ppc64": "1.3.4", "@img/sharp-libvips-linux-riscv64": "1.3.4", "@img/sharp-libvips-linux-s390x": "1.3.4", "@img/sharp-libvips-linux-x64": "1.3.4", "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", "@img/sharp-libvips-linuxmusl-x64": "1.3.4", "@img/sharp-linux-arm": "0.35.5", "@img/sharp-linux-arm64": "0.35.5", "@img/sharp-linux-ppc64": "0.35.5", "@img/sharp-linux-riscv64": "0.35.5", "@img/sharp-linux-s390x": "0.35.5", "@img/sharp-linux-x64": "0.35.5", "@img/sharp-linuxmusl-arm64": "0.35.5", "@img/sharp-linuxmusl-x64": "0.35.5", "@img/sharp-webcontainers-wasm32": "0.35.5", "@img/sharp-win32-arm64": "0.35.5", "@img/sharp-win32-ia32": "0.35.5", "@img/sharp-win32-x64": "0.35.5" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], diff --git a/server/package.json b/server/package.json index e047630da..56fab9735 100644 --- a/server/package.json +++ b/server/package.json @@ -95,7 +95,7 @@ "react": "^19.2.3", "react-dom": "^19.2.3", "resend": "^6.18.1", - "sharp": "0.35.4", + "sharp": "0.35.5", "tiny-invariant": "^1.3.3", "zod": "^4.3.6" }, From e7aabbb7d2fda66e49e704f5684dc81ceea04679 Mon Sep 17 00:00:00 2001 From: Mohammad Rajabi Date: Tue, 6 Oct 2026 21:22:53 +0330 Subject: [PATCH 11/14] chore: integrate approved hermes compatibility prerequisites --- cli/src/main.rs | 1 - plugins/hermes-agent/README.md | 62 +-- plugins/hermes-agent/package.json | 1 - .../hermes-agent/plugin/inline/activity.py | 268 ------------ plugins/hermes-agent/plugin/inline/adapter.py | 232 +---------- .../plugin/inline/sidecar/index.mjs | 12 +- plugins/hermes-agent/plugin/inline/tools.py | 14 +- plugins/hermes-agent/src/install.ts | 1 - plugins/hermes-agent/src/sidecar/index.ts | 13 +- .../tests/activity-python.test.ts | 257 ------------ .../hermes-agent/tests/adapter-python.test.ts | 383 ++---------------- .../tests/package-artifact.test.ts | 1 - .../tests/sidecar-runtime.test.ts | 31 +- scripts/ci/check-hermes-host.py | 88 ++++ 14 files changed, 150 insertions(+), 1214 deletions(-) delete mode 100644 plugins/hermes-agent/plugin/inline/activity.py delete mode 100644 plugins/hermes-agent/tests/activity-python.test.ts diff --git a/cli/src/main.rs b/cli/src/main.rs index 3573afc42..316f12123 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -8362,7 +8362,6 @@ mod cli_parsing_tests { ..Default::default() }, ], - ..Default::default() }; filter_users_payload(&mut payload, Some("mo")); diff --git a/plugins/hermes-agent/README.md b/plugins/hermes-agent/README.md index 44413310c..4cdfdba1d 100644 --- a/plugins/hermes-agent/README.md +++ b/plugins/hermes-agent/README.md @@ -254,60 +254,14 @@ platforms: enabled: true ``` -Agent activity is experimental and **off by default**. To opt in, set -`INLINE_EXPERIMENTAL_AGENT_ACTIVITY=true` in the gateway process environment, -then restart Hermes. Unset it or set it to `false` and restart to restore ordinary -typing, disabled tool progress, cleaned-up progress messages, and no interim -assistant messages (unless explicitly overridden in your display settings). - -In Inline on each device, enable **Settings → Experimental → Agents → Agent -Activity** and restart the app. Expanded logs also need **Rich Content Renderer** -on macOS or the existing **Message View 2** Debug setting on iOS. Without the -app opt-in, Working falls back to Typing and saved agent rows use their text -fallback. The app setting does not reconfigure a running gateway. - -When enabled, Inline uses a quiet activity timeline: conversational replies stay separate from -collapsed tool logs, and the logs remain available after the turn. The toolbar -uses Inline's `WORKING` compose action while the turn is running; updated Apple -clients with Agent Activity enabled display **Working** with a thin rotating arc. This requires a server and -client that understand the additive compose-action value `6`. - -Collapsed titles come from structured tool names and explicit descriptions. -Commands stay inside the disclosure; a terminal call without a description says -**Running a script**. Finished rows show **Worked for 18s**, **Failed after 18s**, -or **Stopped after 18s**. Monotonic elapsed time runs from the first work event -to the row's closing boundary and excludes final transport latency. Individual -tool errors remain recorded even if a later step recovers. These are bounded -progress previews, not complete tool-output logs or proof the task succeeded. - -When opted in, the adapter registers these display defaults. Existing explicit Hermes display -settings still take precedence; use the per-platform settings below to adopt the -timeline when an older global configuration disables tool progress: - -```yaml -display: - platforms: - inline: - tool_progress: all - tool_progress_grouping: accumulate - cleanup_progress: false - streaming: false - interim_assistant_messages: true -``` - -The installed legacy Hermes gateway does not route tool events through the -public `format_tool_event` adapter hook. A guarded Inline-only compatibility -bridge connects `TurnRunner.progress_callback` and `send_progress_messages` to -the existing host queue. The adapter renders structured starts and explicit -tool errors, batches queued events, and closes rows at reply/rollover boundaries. -The public `on_processing_complete` hook supplies the final processing outcome -(including delivery failures). Normal sender-task cancellation is not treated -as user cancellation. Hosts lacking either compatibility method retain their -ordinary progress presentation and log a warning. Other platforms are untouched. -Activity rows use silent, closed `activity="agent"` disclosures; updated clients -retain their standard message bubbles and disclosure controls without decorative activity icons. -Set `tool_progress: off` to disable tool logs. Token streaming remains opt-in -through both `streaming.enabled: true` and +Experimental tool timelines are unavailable in this adapter. The old +`INLINE_EXPERIMENTAL_AGENT_ACTIVITY` setting is ignored and logs a warning when +enabled. Hermes uses ordinary typing, optional processing reactions, and replies. + +Inline keeps tool progress and interim assistant messages quiet by default. +Explicit Hermes display settings still take precedence; if you enable native +tool progress, Hermes uses its ordinary progress messages. Token streaming remains +opt-in through both `streaming.enabled: true` and `display.platforms.inline.streaming: true`. Hermes-native sends and scheduled deliveries accept Inline's explicit target diff --git a/plugins/hermes-agent/package.json b/plugins/hermes-agent/package.json index e62c3db7f..ad9e34e20 100644 --- a/plugins/hermes-agent/package.json +++ b/plugins/hermes-agent/package.json @@ -32,7 +32,6 @@ "plugin/inline/LICENSE", "plugin/inline/__init__.py", "plugin/inline/adapter.py", - "plugin/inline/activity.py", "plugin/inline/cli.py", "plugin/inline/message_actions.py", "plugin/inline/plugin.yaml", diff --git a/plugins/hermes-agent/plugin/inline/activity.py b/plugins/hermes-agent/plugin/inline/activity.py deleted file mode 100644 index 8862ab9c8..000000000 --- a/plugins/hermes-agent/plugin/inline/activity.py +++ /dev/null @@ -1,268 +0,0 @@ -"""Inline activity presentation. Host events are data; previews are never titles.""" -from __future__ import annotations - -import asyncio -import math -import queue -import re -import time -from contextlib import asynccontextmanager -from dataclasses import dataclass -from pathlib import PurePosixPath -from typing import Any, Callable, Optional - - -def duration_label(seconds: float) -> str: - total = max(0, int(seconds)) if math.isfinite(seconds) else 0 - if total < 1: - return "less than 1s" - hours, rest = divmod(total, 3600) - minutes, seconds = divmod(rest, 60) - return " ".join(value for value in ( - f"{hours}h" if hours else "", f"{minutes}m" if minutes else "", f"{seconds}s" if seconds else "", - ) if value) - - -def step_title(name: str, args: Optional[dict] = None) -> str: - args = args or {} - # Only these explicit presentation fields describe intent. Never use a - # command, arbitrary output, or an assistant reasoning message as a title. - description = args.get("description") - if isinstance(description, str) and description.strip(): - return " ".join(description.split())[:100] - labels = { - "terminal": "Running a script", "exec": "Running a script", "execute_code": "Running code", - "web_search": "Searching the web", "web_extract": "Reading a web page", - "browser": "Using the browser", "browser_exec": "Using the browser", - "read_file": "Reading a file", "write_file": "Writing a file", "file_read": "Reading a file", - "file_write": "Writing a file", "file_edit": "Editing a file", "apply_patch": "Editing files", - "tool_describe": "Checking available tools", "skill_view": "Reading instructions", - "delegate_task": "Delegating a task", "image_generate": "Generating an image", - } - title = labels.get(name, "Using a tool") - if name in {"read_file", "write_file", "file_read", "file_write", "file_edit"}: - path = args.get("path") or args.get("file_path") - if isinstance(path, str) and path.strip(): - title = title.replace("a file", PurePosixPath(path).name) - return title[:100] - - -@dataclass(frozen=True) -class ActivityEvent: - title: str - detail: str - at: float - failed: bool = False - - -def tool_event(event_type: str, name: str, preview: Optional[str], args: Optional[dict], **kwargs: Any) -> Optional[ActivityEvent]: - if event_type == "tool.completed": - # The legacy callback has no call ID. Do not guess which parallel call - # completed; record explicit failures as separate factual events. - if kwargs.get("is_error") is True: - return ActivityEvent(step_title(name), f"{name}: tool reported an error", time.monotonic(), True) - return None - if event_type != "tool.started" or name in {"_thinking", "clarify"}: - return None - detail = preview or "" - if name in {"terminal", "exec", "execute_code"}: - detail = (args or {}).get("command") or (args or {}).get("code") or detail - if not isinstance(detail, str): - detail = "" - # Input is already display-redacted by Hermes. Bound each preview, and label - # truncation rather than promising a complete execution log. - requested_limit = kwargs.get("preview_limit", 1200) - limit = min(1200, requested_limit) if isinstance(requested_limit, int) and requested_limit > 0 else 1200 - if len(detail) > limit: - detail = detail[:limit] + "\n[preview truncated]" - return ActivityEvent(step_title(name, args), detail, time.monotonic()) - - -def render_activity(title: str, details: list[str], *, elapsed: Optional[float] = None, outcome: str = "success", tool_failed: bool = False) -> str: - if elapsed is not None: - prefix = {"failure": "Failed after", "cancelled": "Stopped after"}.get(outcome, "Worked for") - title = f"{prefix} {duration_label(elapsed)}" - if tool_failed and outcome == "success": - title += " · tool error recorded" - escaped = re.sub(r"([\\`*_{}\[\]<>#!|])", r"\\\1", title) - content = "\n\n".join(details) - fence = "`" * max(3, max((len(run) for run in re.findall(r"`+", content)), default=0) + 1) - kind = ' kind="progress"' if elapsed is None else "" - return f'