Sessions Integration Feature

Nucleic-Session: FC94353F-9F0D-4BCC-AD7C-0FB14E38C2E5
Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
2026-07-05 01:53:43 -07:00
co-authored by Nucleic
parent 6316e7d6b5
commit a0dd6f6a7b
@@ -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.