diff --git a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift index e418c6d..1dbac35 100644 --- a/NucleicRemote/NucleicRemote/Models/RemoteStore.swift +++ b/NucleicRemote/NucleicRemote/Models/RemoteStore.swift @@ -121,10 +121,13 @@ final class RemoteStore: ObservableObject { return (descriptor, candidates) } - /// Dispatch a new chat to the abstract **Mesh** destination from the phone: rank eligible + /// Dispatch a new chat to the abstract **Carbon** destination from the phone: rank eligible /// hosts by the shared scorer and try them in order, one at a time with a correlated, /// idempotent ack (a timeout retries the same candidate once before moving on). On success - /// routes to the landed session. Mirrors `AppStore.dispatchChatToMesh`. + /// routes to the landed session. Mirrors `AppStore.dispatchChatToMesh`. The phone stamps + /// itself as the Carbon origin, so the landed session is Carbon-managed (its owner keeps it + /// on the optimal host between turns); the phone can't own sessions, so the origin exclusion + /// is naturally satisfied by every candidate. func dispatchChatToMesh( projectID: ProjectID, message: String, model: String? = nil, effort: String? = nil, auto: Bool? = nil @@ -141,8 +144,8 @@ final class RemoteStore: ObservableObject { backend: nil, excluding: excluded).first else { showError(tried == 0 - ? "No mesh runner can take this chat right now." - : "No mesh runner could take this chat (\(tried) tried).", sessionID: nil) + ? "No Carbon host can take this chat right now." + : "No Carbon host could take this chat (\(tried) tried).", sessionID: nil) return } tried += 1 @@ -154,7 +157,8 @@ final class RemoteStore: ObservableObject { let requestID = UUID().uuidString let request = StartChatRequest( projectID: targetProjectID, message: text, model: model, effort: effort, - auto: auto, requestID: requestID) + auto: auto, requestID: requestID, + carbonOriginDeviceID: IdentityStore.deviceID()) var outcome = await sendDispatch(request, on: conn, timeout: .seconds(10)) if outcome == nil { outcome = await sendDispatch(request, on: conn, timeout: .seconds(5)) diff --git a/NucleicRemote/NucleicRemote/Views/Composer.swift b/NucleicRemote/NucleicRemote/Views/Composer.swift index ab417d7..bb7e1b3 100644 --- a/NucleicRemote/NucleicRemote/Views/Composer.swift +++ b/NucleicRemote/NucleicRemote/Views/Composer.swift @@ -18,8 +18,10 @@ struct StartChatComposer: View { /// project page's "+", so the chat always lands in that project. var lockedProject: WireProject? = nil @State private var projectID: ProjectID? - /// Mesh dispatch: run on the project's own Mac (false) or auto-route to the best eligible - /// mesh host (true). Shown only when a connected peer could actually take it. + /// Carbon (mesh work queue): run on the project's own Mac (false) or on Carbon (true) — + /// auto-routed to the best eligible mesh host and kept on the optimal host between turns. + /// The phone itself can't run sessions, so every Carbon candidate is already "not here". + /// Shown only when a connected peer could actually take it. @State private var runOnMesh = false @State private var autoOverride: Bool? @State private var model: String? @@ -87,18 +89,19 @@ struct StartChatComposer: View { ? "Auto-approve is always on for Nucleic Control chats — they run autonomously." : "Auto-approve safe actions; destructive ones still ask.") } - // Mesh dispatch: when an eligible peer holds this repo, offer "Auto (Mesh)" so the - // chat routes to whichever host fits rather than always its own owner. + // Carbon (mesh work queue): when an eligible peer holds this repo, offer "Carbon" so + // the chat routes to whichever host fits — and moves between hosts as availability + // changes — rather than always running on its own owner. if meshAvailable { Menu { Picker("Run on", selection: $runOnMesh) { Text("This Mac").tag(false) - Text("Mesh (automatic)").tag(true) + Text("Carbon").tag(true) } .pickerStyle(.inline) } label: { Label( - runOnMesh ? "Mesh" : "This Mac", + runOnMesh ? "Carbon" : "This Mac", systemImage: runOnMesh ? "antenna.radiowaves.left.and.right" : "desktopcomputer") .font(.caption) .foregroundStyle(Palette.accent)