feat: add tiered channel agent and mobile Codex bridge
This commit is contained in:
parent
950b7e48b3
commit
71db11e7e8
35 changed files with 2242 additions and 76 deletions
|
|
@ -53,7 +53,11 @@ final class HoloLakeSyncClient: ObservableObject {
|
|||
sessionKey: sessionKey,
|
||||
desktopName: response.desktopName,
|
||||
counter: 0,
|
||||
cursor: 0
|
||||
cursor: 0,
|
||||
agentConversationID: nil,
|
||||
pendingAgentRequestID: nil,
|
||||
pendingAgentContent: nil,
|
||||
pendingAgentDestination: nil
|
||||
)
|
||||
try HoloLakeKeychain.save(next)
|
||||
session = next
|
||||
|
|
@ -69,9 +73,28 @@ final class HoloLakeSyncClient: ObservableObject {
|
|||
await pair(with: url)
|
||||
}
|
||||
|
||||
func sync(captureTitle: String? = nil, captureBody: String? = nil) async {
|
||||
func sync(
|
||||
captureTitle: String? = nil,
|
||||
captureBody: String? = nil,
|
||||
agentContent: String? = nil,
|
||||
agentDestination: String = "HOLOLAKE_AGENT"
|
||||
) async {
|
||||
await perform("正在与电脑同步…") {
|
||||
guard var current = session else { throw HoloLakeMobileError.missingSession }
|
||||
let requestedAgentContent = agentContent?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if let requestedAgentContent, !requestedAgentContent.isEmpty {
|
||||
if let pending = current.pendingAgentContent, pending != requestedAgentContent {
|
||||
throw HoloLakeMobileError.pendingAgentRequest
|
||||
}
|
||||
if let pending = current.pendingAgentDestination, pending != agentDestination {
|
||||
throw HoloLakeMobileError.pendingAgentRequest
|
||||
}
|
||||
if current.pendingAgentRequestID == nil {
|
||||
current.pendingAgentRequestID = UUID().uuidString.lowercased()
|
||||
current.pendingAgentContent = requestedAgentContent
|
||||
current.pendingAgentDestination = agentDestination
|
||||
}
|
||||
}
|
||||
current.counter += 1
|
||||
// Consume and persist the counter before transport. If the root node accepts a
|
||||
// request but its response is lost, the next manual retry still moves forward.
|
||||
|
|
@ -87,13 +110,32 @@ final class HoloLakeSyncClient: ObservableObject {
|
|||
} else {
|
||||
capture = nil
|
||||
}
|
||||
let agentMessage: MobileAgentMessageInput?
|
||||
if let requestID = current.pendingAgentRequestID,
|
||||
let content = current.pendingAgentContent,
|
||||
requestedAgentContent != nil {
|
||||
agentMessage = MobileAgentMessageInput(
|
||||
destination: current.pendingAgentDestination ?? "HOLOLAKE_AGENT",
|
||||
codexThreadID: (current.pendingAgentDestination == "CODEX_SELECTED_THREAD") ? snapshot?.codexTarget?.threadID : nil,
|
||||
conversationID: (current.pendingAgentDestination == "CODEX_SELECTED_THREAD") ? nil : current.agentConversationID,
|
||||
content: content,
|
||||
requestID: requestID
|
||||
)
|
||||
} else {
|
||||
agentMessage = nil
|
||||
}
|
||||
let aad = Data("hololake.mobile.sync/v1:\(current.deviceID)".utf8)
|
||||
let responseData = try await Self.exchange(
|
||||
url: current.baseURL.appending(path: "v1/sync"),
|
||||
key: current.sessionKey,
|
||||
aad: aad,
|
||||
headers: ["X-HoloLake-Device-ID": current.deviceID],
|
||||
payload: SyncRequest(counter: current.counter, afterCursor: nil, capture: capture)
|
||||
payload: SyncRequest(
|
||||
counter: current.counter,
|
||||
afterCursor: nil,
|
||||
capture: capture,
|
||||
agentMessage: agentMessage
|
||||
)
|
||||
)
|
||||
let nextSnapshot = try JSONDecoder().decode(MobileSyncSnapshot.self, from: responseData)
|
||||
guard nextSnapshot.schema == "hololake.mobile-sync/v1",
|
||||
|
|
@ -105,6 +147,16 @@ final class HoloLakeSyncClient: ObservableObject {
|
|||
throw HoloLakeMobileError.invalidResponse
|
||||
}
|
||||
current.cursor = nextSnapshot.cursor
|
||||
if let exchange = nextSnapshot.agentExchange,
|
||||
exchange.requestID == current.pendingAgentRequestID,
|
||||
exchange.state == "COMPLETED" {
|
||||
if current.pendingAgentDestination != "CODEX_SELECTED_THREAD" {
|
||||
current.agentConversationID = exchange.conversationID
|
||||
}
|
||||
current.pendingAgentRequestID = nil
|
||||
current.pendingAgentContent = nil
|
||||
current.pendingAgentDestination = nil
|
||||
}
|
||||
try HoloLakeKeychain.save(current)
|
||||
session = current
|
||||
snapshot = nextSnapshot
|
||||
|
|
@ -112,6 +164,10 @@ final class HoloLakeSyncClient: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func sendAgentMessage(_ content: String, destination: String) async {
|
||||
await sync(agentContent: content, agentDestination: destination)
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
do {
|
||||
try HoloLakeKeychain.remove()
|
||||
|
|
@ -156,7 +212,7 @@ final class HoloLakeSyncClient: ObservableObject {
|
|||
)
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.timeoutInterval = 8
|
||||
request.timeoutInterval = 120
|
||||
request.cachePolicy = .reloadIgnoringLocalCacheData
|
||||
request.httpBody = sealed.ciphertext + sealed.tag
|
||||
request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
|
||||
|
|
|
|||
Loading…
Reference in a new issue