Sessions Integration Feature
Nucleic-Session: FC94353F-9F0D-4BCC-AD7C-0FB14E38C2E5 Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -527,32 +527,66 @@ final class RemoteStore: ObservableObject {
|
|||||||
return cb
|
return cb
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mirror the active connection's projection onto the flat @Published state the UI binds to.
|
/// Re-merge every connected Mac's projection into the flat @Published state the UI binds to
|
||||||
private func mirrorActive() {
|
/// (mesh P3): all hosts' sessions and dashboards shown together, with the host-specific values
|
||||||
guard let c = activeConnection else { return }
|
/// (host name, capabilities, catalog, connectivity, scope) following the open session's Mac —
|
||||||
connectivity = c.connectivity
|
/// or a representative one. No-op in demo, which seeds the aggregate directly.
|
||||||
hostName = c.hostName
|
private func rebuildAggregate() {
|
||||||
sessions = c.sessions
|
guard !demoMode else { return }
|
||||||
capabilities = c.capabilities
|
sessions = aggregatedSessions()
|
||||||
grantedScope = c.grantedScope
|
dashboard = DashboardSnapshot.merged(connections.values.map(\.dashboard))
|
||||||
modelCatalog = c.modelCatalog
|
meshPeers = connections.values.flatMap(\.meshPeers)
|
||||||
dashboard = c.dashboard
|
|
||||||
meshPeers = c.meshPeers
|
let ctx = contextConnection
|
||||||
|
hostName = ctx?.hostName ?? ""
|
||||||
|
capabilities = ctx?.capabilities
|
||||||
|
?? WireCapabilities(canModifyToolInput: false, allowAlwaysScopes: [])
|
||||||
|
modelCatalog = ctx?.modelCatalog ?? .empty
|
||||||
|
|
||||||
|
if let id = openSessionHostID, let conn = connections[id] {
|
||||||
|
// While a transcript is open, the composer/controls act on *that* Mac — its connectivity
|
||||||
|
// gates send and its scope drives the control affordances.
|
||||||
|
connectivity = conn.connectivity
|
||||||
|
grantedScope = conn.grantedScope
|
||||||
|
} else {
|
||||||
|
connectivity = aggregateConnectivity()
|
||||||
|
// Optimistic new-chat gating: enabled if *any* Mac grants control (the owning Mac still
|
||||||
|
// enforces scope when the intent lands there).
|
||||||
|
grantedScope = connections.values
|
||||||
|
.filter { $0.connectivity.isLive }.map(\.grantedScope).max() ?? .approve
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Every connected Mac's live (non-archived) sessions — the aggregate the app-icon badge and
|
/// The flat sessions list: every connected Mac's sessions, deduped by id (ids are globally
|
||||||
/// Live Activity summarize across hosts.
|
/// unique). Views handle sorting/grouping.
|
||||||
private var allLiveSessions: [WireSessionSummary] {
|
private func aggregatedSessions() -> [WireSessionSummary] {
|
||||||
if demoMode { return demoHosts.flatMap(\.sessions).filter { !$0.archived } }
|
var seen = Set<SessionID>()
|
||||||
return connections.values.flatMap(\.sessions).filter { !$0.archived }
|
var result: [WireSessionSummary] = []
|
||||||
|
for conn in connections.values {
|
||||||
|
for summary in conn.sessions where seen.insert(summary.sessionID).inserted {
|
||||||
|
result.append(summary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One connectivity value for the whole app when no transcript is open (the chip + global
|
||||||
|
/// gating): connected if any Mac is live, else the most-informative in-progress/offline state.
|
||||||
|
private func aggregateConnectivity() -> Connectivity {
|
||||||
|
let all = connections.values.map(\.connectivity)
|
||||||
|
guard !all.isEmpty else { return .unpaired }
|
||||||
|
if let live = all.first(where: { $0.isLive }) { return live }
|
||||||
|
if all.contains(where: { if case .connecting = $0 { return true } else { return false } }) { return .connecting }
|
||||||
|
if all.contains(where: { if case .reconnecting = $0 { return true } else { return false } }) { return .reconnecting }
|
||||||
|
if all.contains(.hostOffline) { return .hostOffline }
|
||||||
|
return all.first ?? .unpaired
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Refresh cross-host aggregates: the app-icon badge (needs-you across *all* Macs) + the Live
|
/// Refresh cross-host aggregates: the app-icon badge (needs-you across *all* Macs) + the Live
|
||||||
/// Activity summary.
|
/// Activity summary. `sessions`/`liveSessions` are already the merged aggregate.
|
||||||
private func refreshAggregate() {
|
private func refreshAggregate() {
|
||||||
let needs = allLiveSessions.filter { $0.status.needsYou($0.disposition) }.count
|
NotificationRouter.shared.updateBadge(needsYouCount)
|
||||||
NotificationRouter.shared.updateBadge(needs)
|
LiveActivityManager.shared.sync(hostName: hostName, sessions: liveSessions)
|
||||||
LiveActivityManager.shared.sync(hostName: hostName, sessions: allLiveSessions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tear down every live connection (leaving the paired registry intact) — for entering demo.
|
/// Tear down every live connection (leaving the paired registry intact) — for entering demo.
|
||||||
|
|||||||
Reference in New Issue
Block a user