Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
ddf389f
cmux NEXT Cloud: Run New Workspaces in Cloud gives each new workspace…
austinywang Oct 27, 2026
b99ff3d
cmux NEXT Cloud: the Cloud toggle's settings text in every app language
austinywang Oct 27, 2026
cb73911
cmux NEXT Cloud toggle: Retry keeps the copied folder; signed out sta…
austinywang Oct 27, 2026
0b46643
cmux NEXT Cloud toggle: the plan and folder copy live in CmuxNextClou…
austinywang Oct 27, 2026
f657bc5
cmux NEXT Cloud: CloudVmHostStore's Observable conformance needs Obse…
austinywang Oct 27, 2026
d6fb4ed
Regenerate the settings exports for the Cloud toggle's keys
austinywang Oct 27, 2026
d9a74e1
cmux NEXT Cloud toggle: copy only a git working copy; a missing machi…
austinywang Oct 27, 2026
891b7c4
cmux NEXT Cloud toggle: resolve the folder to copy off the main thread
austinywang Oct 27, 2026
13ad31d
cmux NEXT Cloud toggle: a creation cancelled while its folder resolve…
austinywang Oct 27, 2026
0912391
Regenerate the settings schema after dropping the vendor keyword
austinywang Oct 27, 2026
c8ad9cd
Merge commit '93d1c8c57f29146f457cd412afca734aa0c3d526' into feat-cmu…
austinywang Oct 27, 2026
d708a53
Merge commit '750a36c4d5e7fcc24fcaafefbb7986d7f3b6c262' into feat-cmu…
austinywang Oct 28, 2026
50814d2
Regenerate the defaults snapshot with the Cloud toggle's settings
austinywang Oct 28, 2026
c47f1a2
Merge latest feat-cmux-next into cloud workspace toggle
austinywang Oct 29, 2026
2a033df
Test Cloud workspace retry and sidebar creation entrypoints
austinywang Oct 29, 2026
5d29a6b
Keep Cloud workspace creation scoped and retryable across entrypoints
austinywang Oct 29, 2026
5f4269d
Merge commit '2241229c6708f7cb07bd2507f86806e49468febc' into feat-cmu…
austinywang Oct 29, 2026
672b286
cmux NEXT Cloud toggle: the sidebar's + in a remote machine's window …
austinywang Oct 29, 2026
4062967
Merge remote-tracking branch 'origin/feat-cmux-next' into feat-cmux-n…
austinywang Oct 29, 2026
bf59563
cmux NEXT Cloud toggle: the sidebar's + in a remote machine's window …
austinywang Oct 29, 2026
4757849
Merge origin/feat-cmux-next into feat-cmux-next-26062-cloud-toggle
austinywang Oct 30, 2026
c544db6
Cloud toggle: ask for a machine only where its progress shows, and sa…
austinywang Oct 30, 2026
d55ff82
Cloud toggle: a second Cmd-N reuses the machine on its way; a team sw…
austinywang Oct 30, 2026
8cd94c9
Cloud toggle: regenerated settings exports, highlight action, registr…
austinywang Oct 30, 2026
296d7f0
Merge remote-tracking branch 'origin/feat-cmux-next' into feat-cmux-n…
austinywang Oct 30, 2026
dd0bfc6
Merge published Cloud toggle updates into the task worktree
austinywang Oct 30, 2026
2b2104b
Merge feat-cmux-next into Cloud workspace toggle
austinywang Oct 30, 2026
ee9ad04
Regenerate cloud workspace settings exports on the current base
austinywang Oct 30, 2026
3703590
Merge origin/feat-cmux-next into the Cloud workspace toggle
austinywang Oct 31, 2026
de81bac
Merge feat-cmux-next and retain Cloud workspace settings
austinywang Oct 31, 2026
00072a1
Regenerate Cloud workspace settings exports on merged base
austinywang Oct 31, 2026
1dfeb7a
Merge latest feat-cmux-next into Cloud workspace toggle
austinywang Oct 31, 2026
e36c0b4
Merge current feat-cmux-next host group changes
austinywang Oct 31, 2026
63c98f0
Merge Settings sections redesign into Cloud workspace toggle
austinywang Oct 31, 2026
2e7d57a
Regenerate Cloud settings exports after Settings sections merge
austinywang Oct 31, 2026
03dd7c5
Merge feat-cmux-next into Cloud workspace toggle
austinywang Oct 31, 2026
a2f47d6
Merge feat-cmux-next and preserve Cloud workspace routing
austinywang Oct 31, 2026
487685d
Regenerate Cloud workspace settings exports on the merged base
austinywang Oct 31, 2026
cb7cad6
Merge commit '2dbca164fb4a06a95cf754a821b88b0672e684d9' into feat-cmu…
austinywang Oct 31, 2026
faee1a5
Regenerate settings exports after base merge
austinywang Oct 31, 2026
6b6e645
Merge branch 'feat-cmux-next' into feat-cmux-next-26062-cloud-toggle
cmux-bot[bot] Oct 31, 2026
50216ab
Merge remote-tracking branch 'origin/feat-cmux-next' into feat-cmux-n…
austinywang Oct 31, 2026
e81c782
Merge origin/feat-cmux-next into feat-cmux-next-26062-cloud-toggle
austinywang Oct 31, 2026
bca9bc5
Merge origin/feat-cmux-next to refresh generated references
austinywang Oct 31, 2026
46800ae
Merge remote-tracking branch 'origin/feat-cmux-next' into feat-cmux-n…
austinywang Oct 31, 2026
f67cc7c
Merge branch 'feat-cmux-next' into feat-cmux-next-26062-cloud-toggle
cmux-bot[bot] Oct 31, 2026
09c73ce
Merge remote-tracking branch 'origin/feat-cmux-next' into feat-cmux-n…
austinywang Oct 31, 2026
3055779
Merge branch 'feat-cmux-next' into feat-cmux-next-26062-cloud-toggle
cmux-bot[bot] Oct 31, 2026
02f6c4a
Merge origin/feat-cmux-next into Cloud workspace toggle
austinywang Oct 31, 2026
8cbe20b
Merge origin/feat-cmux-next with its Swift test isolation repair
austinywang Oct 31, 2026
5a8dc48
Merge latest Cloud chat defaults and preserve generated base exports
austinywang Oct 31, 2026
84053a9
test: cover late first terminal in Cloud workspace toggle
austinywang Oct 31, 2026
978d02c
fix: wait for late Cloud workspace and terminal starts
austinywang Oct 31, 2026
bae8685
test: disambiguate daemon values for late-start reproduction
austinywang Oct 31, 2026
073d770
fix: preserve late Cloud starts with the compiled regression
austinywang Oct 31, 2026
6712ec2
chore: regenerate Cloud workspace settings exports on the fleet
austinywang Oct 31, 2026
969a0c6
test: serve complete workspace replies in late-start regression
austinywang Oct 31, 2026
baec5d0
fix: retain Cloud starts through delayed daemon replies
austinywang Oct 31, 2026
1388f60
Merge remote-tracking branch 'origin/feat-cmux-next' into feat-cmux-n…
austinywang Oct 31, 2026
11a38dc
Merge remote-tracking branch 'origin/feat-cmux-next' into feat-cmux-n…
austinywang Oct 31, 2026
3bece71
Regenerate Cloud settings on the merged base
austinywang Oct 31, 2026
0878faa
Merge latest feat-cmux-next and preserve generated Cloud settings
austinywang Nov 1, 2026
c3101ac
Merge branch 'feat-cmux-next' into feat-cmux-next-26062-cloud-toggle
cmux-bot[bot] Nov 1, 2026
58a6c97
Merge latest feat-cmux-next compiler repair
austinywang Nov 1, 2026
e0e3fac
Merge feat-cmux-next and refresh settings exports
austinywang Nov 1, 2026
d697086
Merge origin/feat-cmux-next into Cloud workspace toggle
austinywang Nov 1, 2026
b625592
test: keep attributed user origins out of Cloud workspace routing
austinywang Nov 1, 2026
55f45da
fix: reserve Cloud workspace toggle for native user actions
austinywang Nov 1, 2026
a4e2b9b
Merge origin/feat-cmux-next and regenerate settings reference
austinywang Nov 1, 2026
e00f45e
Merge branch 'feat-cmux-next' into feat-cmux-next-26062-cloud-toggle
cmux-bot[bot] Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -129,21 +129,27 @@ extension AppActions {
private static func newWorkspace(_ services: AppServices, _ invocation: ActionInvocation) {
SidebarNewRowTimer.shared.pressed() // debug.new_workspace times the row from here
var spawn = WorkspaceSpawn(invocation)
let windows = services.windows
let sourceID = invocation.target.flatMap { $0.kind == .window ? $0.id : nil }
let sourceWindow = sourceID.flatMap(windows.controller(for:)) ?? windows.active
let focus = NewWorkspaceFocus(invocation)
let show = focus.shows
// A person's Cmd-N starts where they work: the focused terminal's folder or the focused
// agent chat's exact folder, unless `window-inherit-working-directory = false`.
if invocation.origin == .user, invocation["machine"] == nil, NewTerminalStart.inherits(.window),
let focused = services.windows.active?.focusedPane, focused.daemon === services.daemon {
let focused = sourceWindow?.focusedPane, focused.daemon === services.daemon {
spawn.inheritedCwd = NewTerminalStart.inherited(besides: focused.selectedTab, in: focused.pane, services: services)
}
// The Cloud toggle (#26062): a person's new workspace gets its own new Cloud machine.
// Only a window shows the machine's progress; without one, or without Cloud, it opens here and says so.
let cloudRoute = CloudHandlers.startNewWorkspaceInCloud(invocation, cwd: spawn.cwd ?? spawn.inheritedCwd, window: focus.shows ? sourceWindow?.state : nil, services)
guard case .thisMac(let cloudNotice) = cloudRoute else { return }

let windows = services.windows
// Shown: the active window, or a new one when none is open. Not
// shown (the CLI default): the most recent window lists it, or a new
// window when none is open (a workspace never lives in no window).
let hasOpenWindow = !windows.registry.value.openWindows.isEmpty
let target: String? = show || !hasOpenWindow ? windows.targetWindow(preferring: windows.active?.state.id) : nil
let target: String? = show || !hasOpenWindow ? windows.targetWindow(preferring: sourceID ?? sourceWindow?.state.id) : nil
// `machine` (App machine id; the CLI's `--session`): born on that
// session's daemon (plans/cmux-next/data-model.md 1.3).
let daemon = (invocation["machine"]?.targetValue?.id ?? invocation["machine"]?.stringValue).flatMap(services.machines.daemon(machine:))
Expand All @@ -156,7 +162,7 @@ extension AppActions {
let input = services.keyRouter.newTabInputCoordinator
let opensPage = show && hasOpenWindow && spawn.opensNewTabPage && spawn.command == nil && spawn.firstChat == nil
&& spawn.firstSession == nil && services.agentTabs.opensFirstPage(on: daemon ?? services.daemon)
let keys = opensPage && target == windows.active?.state.id ? input.beginIncoming(in: windows.active?.window) : nil
let keys = opensPage && target == sourceWindow?.state.id ? input.beginIncoming(in: sourceWindow?.window) : nil
spawn.inputToken = keys
// In this turn: the row, and for Cmd-N's page the workspace itself, show before any daemon reply.
let create = BenchSpans.measure("cmdN.start") { windows.startWorkspace(spawn, on: daemon, into: target) }
Expand All @@ -167,6 +173,10 @@ extension AppActions {
_ = try await create(); BenchSpans.mark("cmdN.created")
pageMade = true
if focus.activatesApp, let target { focusWindow(windows, target) }
// In the window the workspace opened in (with every window closed, the new one).
if let cloudNotice, let window = target.flatMap(windows.controller(for:))?.window ?? windows.active?.window {
CmuxToastCenter.shared.show(CmuxToast(id: "cloud-new-workspace-here", message: cloudNotice, duration: .seconds(8)), in: window)
}
return nil
} catch is CancellationError {
return nil // a close took it before it started (WorkspaceLanes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import CmuxNextBrowser
import CmuxNextBrowserHistory
import CmuxNextChiefPetWiring
import CmuxNextChromiumWarmup
import CmuxNextCloud
import CmuxNextCompat
public import CmuxNextComputerUse
import CmuxNextControl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ final class CloudMachineCreation: Identifiable {
private(set) var machineConfig: CloudMachineBootstrap.Outcome?
/// The flow's work for this creation; cancelled on dismiss and retry.
@ObservationIgnored var task: Task<Void, Never>?
/// What opens the ready machine's workspace instead of the flow's own (the Cloud toggle's,
/// in a copied folder); kept here so a Retry from any surface opens the same one.
@ObservationIgnored var open: (@MainActor (CloudMachineSession) async throws -> String)?
/// What `cloud new-machine` asked for (size, idle pause); a Retry asks for the same, with
/// the same idempotency key.
private(set) var request: CloudMachineRequest
Expand Down Expand Up @@ -220,7 +223,7 @@ final class CloudMachineCreation: Identifiable {
startedAt = now
readyAfter = nil
planRefusal = nil
if session == nil { phase = .requesting }
phase = session == nil ? .requesting : .created
}
}

Expand Down Expand Up @@ -332,9 +335,12 @@ struct CloudCreationFlow {

/// Starts a creation and, when a window asked, shows it there at once
/// (before any await): the click focuses the new work, not the old one.
/// `open`, when given, opens the machine's workspace instead of ``open`` (on every Retry too).
@discardableResult
func start(in window: WindowState?, existing: CloudMachineSession?, request: CloudMachineRequest = CloudMachineRequest()) -> CloudMachineCreation {
func start(in window: WindowState?, existing: CloudMachineSession?, request: CloudMachineRequest = CloudMachineRequest(),
open: (@MainActor (CloudMachineSession) async throws -> String)? = nil) -> CloudMachineCreation {
let creation = creations.begin(window: window?.id, session: existing, request: request)
creation.open = open
if let window {
window.page = nil
window.cloudCreation = creation.id
Expand All @@ -358,7 +364,7 @@ struct CloudCreationFlow {
}

private func run(_ creation: CloudMachineCreation) {
let create = create, open = open, show = show, bootstrap = bootstrap, creations = creations, planRequired = planRequired
let create = create, open = creation.open ?? open, show = show, bootstrap = bootstrap, creations = creations, planRequired = planRequired
let startedByPerson = startedByPerson
// task-owner: the creation's one run; cancelled by retry, dismiss or machine removal
creation.task = Task { @MainActor in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -511,3 +511,88 @@ extension CloudHandlers {

static func bindSignInRecovery(into registry: ActionRegistry, cloud: CloudService) { CloudSignInRecovery(pending: cloud.signedOutCreation, signIn: { await cloud.auth.signIn() }, hasCurrentList: { cloud.hasCurrentList(within: .seconds(30)) }, isSignedIn: { cloud.isSignedIn }, refresh: { await cloud.refresh() }).bind(into: registry) } // HQ #4884
}

// The Cloud toggle (#26062): with `cloud.newWorkspacesInCloud` on, a person's New Workspace runs the
// New Cloud Workspace flow on a new machine of its own, in their folder copied there (as Move to
// Cloud copies a chat's) or in the machine's empty folder. Agent chats in it run on that machine.
extension CloudHandlers {
/// Starts it for `invocation` when the toggle applies, Cloud can create a machine and `window`
/// can show its progress; otherwise asks for nothing and says why New Workspace stays here.
static func startNewWorkspaceInCloud(_ invocation: ActionInvocation, cwd: String?, window: WindowState?, _ services: AppServices) -> CloudNewWorkspacePlan.Outcome {
let cloud = services.cloud, setting = CloudNewWorkspaceSetting(), root = services.settings?.snapshot.root ?? .object([:])
let canCopy = cloud.move.cli != nil && cloud.configuration.linkSource == .legacy
guard let plan = CloudNewWorkspacePlan(inCloud: setting.inCloud(in: root), inheritsFolder: canCopy && setting.folder(in: root) == .inherit, cwd: cwd,
byPerson: invocation.isPersonsOwnRun, namesMachineOrCommand: invocation["machine"] != nil || invocation["command"] != nil)
else { return .thisMac(notice: nil) }
switch CloudNewWorkspacePlan.route(unavailableReason: cloud.unavailableReason, signedIn: cloud.isSignedIn, hasWindow: window != nil) {
case .cloud: break
case .thisMac(.unavailable(let reason)): return .thisMac(notice: CloudStrings.newWorkspaceOpenedHere(reason))
case .thisMac(.signedOut): return .thisMac(notice: CloudStrings.newWorkspaceOpenedHere(CloudStrings.signInFirst))
case .thisMac(.noWindow): return .thisMac(notice: CloudStrings.newWorkspaceOpenedHere(CloudStrings.newWorkspaceNoWindow))
}
// A second press while this window's machine is still on its way shows that one again: one paid machine per wait.
if let window, let pending = pendingNewWorkspaceCreation(in: cloud.creations, window: window.id) {
window.page = nil
window.cloudCreation = pending.id
return .started
}
let userID = cloud.auth.user?.id, teamID = cloud.auth.teamID, profile = window?.profileID ?? .defaultProfile
let checkScope: @MainActor (CloudMachineSession) throws -> Void = { session in
try Task.checkCancellation()
guard let userID, cloud.auth.user?.id == userID, cloud.auth.teamID == teamID else {
// An account or team switch ends the creation, as `checkCreationScope` does, so no row stays stuck at ready.
if let creation = cloud.creations.all.first(where: { $0.session === session }) { cloud.creations.end(creation) }
throw CancellationError()
}
}
creationFlow(AppActionContext(services: services), startedByPerson: true, proceed: { creation in
try checkCreationScope(creation, in: cloud.creations, expectedUserID: userID, expectedTeamID: teamID,
currentUserID: cloud.auth.user?.id, currentTeamID: cloud.auth.teamID)
}).start(in: window, existing: nil) { session in
try checkScope(session)
defer { RemoteGroupHandlers.mark(session: session.daemon.store.registryID) } // one group per Cloud machine
let name = session.machine.title
guard let connection = session.daemon.connection else { throw ActionFailure(message: CloudMoveStrings.notConnected(name)) }
var cwd: String?
let folder = try await plan.folder(work: services.work)
try checkScope(session)
if let folder, let binary = cloud.move.cli {
guard let socket = await connection.endpoint?.socketPath else { throw ActionFailure(message: CloudMoveStrings.notConnected(name)) }
let cli = CloudMoveCLI(binary: binary, timeout: cloud.move.timeout)
do {
cwd = try await CloudNewWorkspacePlan.copy(folder, workRoots: cloud.move.workRoots) { remote in
try checkScope(session)
let answer = try await cli.syncPush(socket: socket, local: folder.root, remote: remote)
return CloudNewWorkspacePlan.CopyAnswer(ok: answer.ok, errorCode: answer.errorCode, error: answer.error)
}.chat
} catch is CancellationError {
throw CancellationError()
} catch CloudNewWorkspacePlan.CopyFailure.outdated {
throw ActionFailure(message: CloudMoveStrings.outdatedMachine(name))
} catch CloudNewWorkspacePlan.CopyFailure.failed(let detail) {
throw ActionFailure(message: CloudMoveStrings.syncFailed(name, detail))
} catch {
throw ActionFailure(message: CloudMoveStrings.syncFailed(name, CloudStrings.errorText(error)))
}
}
try checkScope(session)
let key = WorkspaceKey.generate()
if let room = services.machines.local.store.profile(profile), let sessionID = session.daemon.store.registryID,
let home = services.machines.local.connection {
try await DaemonConnection.retryingLateReplies(PinWorkspaceRequest.command) {
try await home.pinWorkspace(session: sessionID, key: key, to: room.id)
}
}
try checkScope(session)
try await WorkspaceCreation.create(key, name: folder.map { URL(fileURLWithPath: $0.root).lastPathComponent }, on: connection, repair: session.emptyWorkspaces) { _ = try await WorkspaceCreation.firstTerminal(in: $0, cwd: cwd, on: connection) }
return key.rawValue
}
return .started
}

/// The toggle's creation window `id` shows that has not opened its workspace yet (requesting,
/// booting, or failed with Retry), if any.
static func pendingNewWorkspaceCreation(in creations: CloudCreations, window id: String) -> CloudMachineCreation? {
creations.shown(in: id).last { $0.open != nil && $0.workspaceID == nil && !$0.isTornDown }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ extension SidebarBridge {
if ids.count > 1 { return WorkspaceSelectionClose.close(ids.map(\.rawValue), in: state, services) }
for id in ids { services.registry.perform("closeWorkspace", invocation: ActionInvocation(closingRow: id.rawValue)) }
case .newWorkspace(let machine, let group):
// The plain "+" in a local window: the person's New Workspace, as Cmd-N (the Cloud toggle applies).
if machine == nil, group == nil, state.machineID == MachineRegistry.localID {
services.registry.perform("newTab", invocation: ActionInvocation(target: ActionTargetRef(kind: .window, id: state.id), origin: .user))
return
}
// A double-click in a group's empty part, or its "+": that group, where `workspaces.newPlacement` puts a
// new workspace in it (its top under the default `top`).
let daemon = machine.flatMap { services.machines.daemon(machine: $0.rawValue) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import CmuxNextCloud
import Foundation
import Observation
public import Observation

/// Settings > Machines' "Dedicated agent machine" switch on a Cloud machine (``CloudVmHost``):
/// what each machine last said, read once when its row first shows while it is connected, and
Expand All @@ -10,16 +9,16 @@ import Observation
/// forgotten (``forget(_:)``), with any read or set still under way for it.
@MainActor
@Observable
final class CloudVmHostStore {
enum Entry: Equatable {
public final class CloudVmHostStore {
public enum Entry: Equatable {
case known(CloudVmHost.State)
/// The read or the last set failed: why, and when (a read is tried again a minute later).
case failed(String, at: ContinuousClock.Instant, last: CloudVmHost.State?)
}

private(set) var entries: [String: Entry] = [:]
public private(set) var entries: [String: Entry] = [:]
/// The machines a set is running on (the switch is disabled meanwhile).
private(set) var setting: Set<String> = []
public private(set) var setting: Set<String> = []
/// Reads under way: the generation each started at, and its task (``forget(_:)`` cancels
/// it). Not observed: a read starts while the page's host lists are being built (inside
/// their observation), so it changes nothing observed until it answers.
Expand All @@ -30,23 +29,23 @@ final class CloudVmHostStore {
@ObservationIgnored private let vmHost: CloudVmHost
@ObservationIgnored private let exec: @Sendable (_ machineID: String, _ command: String) async throws -> CloudExecResult
@ObservationIgnored private let now: () -> ContinuousClock.Instant
static let retryAfter: Duration = .seconds(60)
public static let retryAfter: Duration = .seconds(60)

init(vmHost: CloudVmHost = CloudVmHost(),
exec: @escaping @Sendable (String, String) async throws -> CloudExecResult,
now: @escaping () -> ContinuousClock.Instant = { ContinuousClock.now }) {
public init(vmHost: CloudVmHost = CloudVmHost(),
exec: @escaping @Sendable (String, String) async throws -> CloudExecResult,
now: @escaping () -> ContinuousClock.Instant = { ContinuousClock.now }) {
self.vmHost = vmHost
self.exec = exec
self.now = now
}

convenience init(api: CloudAPIClient, vmHost: CloudVmHost = CloudVmHost()) {
public convenience init(api: CloudAPIClient, vmHost: CloudVmHost = CloudVmHost()) {
self.init(vmHost: vmHost, exec: { id, command in try await api.exec(id, command: command, timeoutMs: vmHost.timeoutMs) })
}

/// The row's state for `machineID`, starting its read when none is known (or the last failed
/// a minute ago) and `readable` (connected, with exec). Nil until the first answer.
func state(_ machineID: String, readable: Bool) -> Entry? {
public func state(_ machineID: String, readable: Bool) -> Entry? {
let entry = entries[machineID]
let stale: Bool = switch entry {
case nil: true
Expand Down Expand Up @@ -81,7 +80,7 @@ final class CloudVmHostStore {
/// Turns the setting on or off on `machineID`: the error to show, or nil. Nothing runs unless
/// the machine is `reachable` now (connected, with exec): a switch drawn while it was never
/// wakes it after (the switch is disabled then).
func set(_ machineID: String, on: Bool, reachable: Bool) async -> String? {
public func set(_ machineID: String, on: Bool, reachable: Bool) async -> String? {
guard reachable, !setting.contains(machineID) else { return nil }
setting.insert(machineID)
generation[machineID, default: 0] += 1
Expand All @@ -107,7 +106,7 @@ final class CloudVmHostStore {
}

/// Forgets a machine that left the registry, and drops any read or set still under way for it.
func forget(_ machineID: String) {
public func forget(_ machineID: String) {
generation[machineID, default: 0] += 1
reading.removeValue(forKey: machineID)?.task.cancel()
entries[machineID] = nil
Expand Down
Loading
Loading